]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd-terminal/idev.c
build-sys: split internal basic/ library from shared/
[thirdparty/systemd.git] / src / libsystemd-terminal / idev.c
CommitLineData
e202fa31
DH
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
6 Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com>
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20***/
21
c93e5a62 22#include <libudev.h>
e202fa31
DH
23#include <stdbool.h>
24#include <stdlib.h>
ce1daea1
TA
25#include "sd-bus.h"
26#include "sd-event.h"
e202fa31 27#include "hashmap.h"
a095315b 28#include "login-util.h"
e202fa31 29#include "macro.h"
e202fa31 30#include "util.h"
ce1daea1
TA
31#include "idev.h"
32#include "idev-internal.h"
e202fa31
DH
33
34static void element_open(idev_element *e);
35static void element_close(idev_element *e);
36
37/*
38 * Devices
39 */
40
41idev_device *idev_find_device(idev_session *s, const char *name) {
42 assert_return(s, NULL);
43 assert_return(name, NULL);
44
45 return hashmap_get(s->device_map, name);
46}
47
48int idev_device_add(idev_device *d, const char *name) {
49 int r;
50
51 assert_return(d, -EINVAL);
52 assert_return(d->vtable, -EINVAL);
53 assert_return(d->session, -EINVAL);
54 assert_return(name, -EINVAL);
55
56 d->name = strdup(name);
57 if (!d->name)
58 return -ENOMEM;
59
60 r = hashmap_put(d->session->device_map, d->name, d);
61 if (r < 0)
62 return r;
63
64 return 0;
65}
66
67idev_device *idev_device_free(idev_device *d) {
68 idev_device tmp;
69
70 if (!d)
71 return NULL;
72
73 assert(!d->enabled);
74 assert(!d->public);
75 assert(!d->links);
76 assert(d->vtable);
77 assert(d->vtable->free);
78
79 if (d->name)
80 hashmap_remove_value(d->session->device_map, d->name, d);
81
82 tmp = *d;
83 d->vtable->free(d);
84
85 free(tmp.name);
86
87 return NULL;
88}
89
90int idev_device_feed(idev_device *d, idev_data *data) {
91 assert(d);
92 assert(data);
93 assert(data->type < IDEV_DATA_CNT);
94
95 if (d->vtable->feed)
96 return d->vtable->feed(d, data);
97 else
98 return 0;
99}
100
101void idev_device_feedback(idev_device *d, idev_data *data) {
102 idev_link *l;
103
104 assert(d);
105 assert(data);
106 assert(data->type < IDEV_DATA_CNT);
107
108 LIST_FOREACH(links_by_device, l, d->links)
109 idev_element_feedback(l->element, data);
110}
111
112static void device_attach(idev_device *d, idev_link *l) {
113 assert(d);
114 assert(l);
115
116 if (d->vtable->attach)
117 d->vtable->attach(d, l);
118
119 if (d->enabled)
120 element_open(l->element);
121}
122
123static void device_detach(idev_device *d, idev_link *l) {
124 assert(d);
125 assert(l);
126
127 if (d->enabled)
128 element_close(l->element);
129
130 if (d->vtable->detach)
131 d->vtable->detach(d, l);
132}
133
134void idev_device_enable(idev_device *d) {
135 idev_link *l;
136
137 assert(d);
138
139 if (!d->enabled) {
140 d->enabled = true;
141 LIST_FOREACH(links_by_device, l, d->links)
142 element_open(l->element);
143 }
144}
145
146void idev_device_disable(idev_device *d) {
147 idev_link *l;
148
149 assert(d);
150
151 if (d->enabled) {
152 d->enabled = false;
153 LIST_FOREACH(links_by_device, l, d->links)
154 element_close(l->element);
155 }
156}
157
158/*
159 * Elements
160 */
161
162idev_element *idev_find_element(idev_session *s, const char *name) {
163 assert_return(s, NULL);
164 assert_return(name, NULL);
165
166 return hashmap_get(s->element_map, name);
167}
168
169int idev_element_add(idev_element *e, const char *name) {
170 int r;
171
172 assert_return(e, -EINVAL);
173 assert_return(e->vtable, -EINVAL);
174 assert_return(e->session, -EINVAL);
175 assert_return(name, -EINVAL);
176
177 e->name = strdup(name);
178 if (!e->name)
179 return -ENOMEM;
180
181 r = hashmap_put(e->session->element_map, e->name, e);
182 if (r < 0)
183 return r;
184
185 return 0;
186}
187
188idev_element *idev_element_free(idev_element *e) {
189 idev_element tmp;
190
191 if (!e)
192 return NULL;
193
194 assert(!e->enabled);
195 assert(!e->links);
196 assert(e->n_open == 0);
197 assert(e->vtable);
198 assert(e->vtable->free);
199
200 if (e->name)
201 hashmap_remove_value(e->session->element_map, e->name, e);
202
203 tmp = *e;
204 e->vtable->free(e);
205
206 free(tmp.name);
207
208 return NULL;
209}
210
211int idev_element_feed(idev_element *e, idev_data *data) {
212 int r, error = 0;
213 idev_link *l;
214
215 assert(e);
216 assert(data);
217 assert(data->type < IDEV_DATA_CNT);
218
219 LIST_FOREACH(links_by_element, l, e->links) {
220 r = idev_device_feed(l->device, data);
221 if (r != 0)
222 error = r;
223 }
224
225 return error;
226}
227
228void idev_element_feedback(idev_element *e, idev_data *data) {
229 assert(e);
230 assert(data);
231 assert(data->type < IDEV_DATA_CNT);
232
233 if (e->vtable->feedback)
234 e->vtable->feedback(e, data);
235}
236
237static void element_open(idev_element *e) {
238 assert(e);
239
240 if (e->n_open++ == 0 && e->vtable->open)
241 e->vtable->open(e);
242}
243
244static void element_close(idev_element *e) {
245 assert(e);
246 assert(e->n_open > 0);
247
248 if (--e->n_open == 0 && e->vtable->close)
249 e->vtable->close(e);
250}
251
252static void element_enable(idev_element *e) {
253 assert(e);
254
255 if (!e->enabled) {
256 e->enabled = true;
257 if (e->vtable->enable)
258 e->vtable->enable(e);
259 }
260}
261
262static void element_disable(idev_element *e) {
263 assert(e);
264
265 if (e->enabled) {
266 e->enabled = false;
267 if (e->vtable->disable)
268 e->vtable->disable(e);
269 }
270}
271
5d301b8a
DH
272static void element_resume(idev_element *e, int fd) {
273 assert(e);
274 assert(fd >= 0);
275
276 if (e->vtable->resume)
277 e->vtable->resume(e, fd);
278}
279
280static void element_pause(idev_element *e, const char *mode) {
281 assert(e);
282 assert(mode);
283
284 if (e->vtable->pause)
285 e->vtable->pause(e, mode);
286}
287
e202fa31
DH
288/*
289 * Sessions
290 */
291
292static int session_raise(idev_session *s, idev_event *ev) {
293 return s->event_fn(s, s->userdata, ev);
294}
295
296static int session_raise_device_add(idev_session *s, idev_device *d) {
297 idev_event event = {
298 .type = IDEV_EVENT_DEVICE_ADD,
299 .device_add = {
300 .device = d,
301 },
302 };
303
304 return session_raise(s, &event);
305}
306
307static int session_raise_device_remove(idev_session *s, idev_device *d) {
308 idev_event event = {
309 .type = IDEV_EVENT_DEVICE_REMOVE,
310 .device_remove = {
311 .device = d,
312 },
313 };
314
315 return session_raise(s, &event);
316}
317
318int idev_session_raise_device_data(idev_session *s, idev_device *d, idev_data *data) {
319 idev_event event = {
320 .type = IDEV_EVENT_DEVICE_DATA,
321 .device_data = {
322 .device = d,
323 .data = *data,
324 },
325 };
326
327 return session_raise(s, &event);
328}
329
330static int session_add_device(idev_session *s, idev_device *d) {
331 int r;
332
333 assert(s);
334 assert(d);
335
336 log_debug("idev: %s: add device '%s'", s->name, d->name);
337
338 d->public = true;
339 r = session_raise_device_add(s, d);
340 if (r != 0) {
341 d->public = false;
342 goto error;
343 }
344
345 return 0;
346
347error:
348 if (r < 0)
c33b3297
MS
349 log_debug_errno(r, "idev: %s: error while adding device '%s': %m",
350 s->name, d->name);
e202fa31
DH
351 return r;
352}
353
354static int session_remove_device(idev_session *s, idev_device *d) {
355 int r, error = 0;
356
357 assert(s);
358 assert(d);
359
360 log_debug("idev: %s: remove device '%s'", s->name, d->name);
361
362 d->public = false;
363 r = session_raise_device_remove(s, d);
364 if (r != 0)
365 error = r;
366
367 idev_device_disable(d);
368
369 if (error < 0)
c33b3297
MS
370 log_debug_errno(error, "idev: %s: error while removing device '%s': %m",
371 s->name, d->name);
e202fa31
DH
372 idev_device_free(d);
373 return error;
374}
375
376static int session_add_element(idev_session *s, idev_element *e) {
377 assert(s);
378 assert(e);
379
380 log_debug("idev: %s: add element '%s'", s->name, e->name);
381
382 if (s->enabled)
383 element_enable(e);
384
385 return 0;
386}
387
388static int session_remove_element(idev_session *s, idev_element *e) {
389 int r, error = 0;
390 idev_device *d;
391 idev_link *l;
392
393 assert(s);
394 assert(e);
395
396 log_debug("idev: %s: remove element '%s'", s->name, e->name);
397
398 while ((l = e->links)) {
399 d = l->device;
400 LIST_REMOVE(links_by_device, d->links, l);
401 LIST_REMOVE(links_by_element, e->links, l);
402 device_detach(d, l);
403
404 if (!d->links) {
405 r = session_remove_device(s, d);
406 if (r != 0)
407 error = r;
408 }
409
410 l->device = NULL;
411 l->element = NULL;
412 free(l);
413 }
414
415 element_disable(e);
416
417 if (error < 0)
c33b3297
MS
418 log_debug_errno(r, "idev: %s: error while removing element '%s': %m",
419 s->name, e->name);
e202fa31
DH
420 idev_element_free(e);
421 return error;
422}
423
424idev_session *idev_find_session(idev_context *c, const char *name) {
425 assert_return(c, NULL);
426 assert_return(name, NULL);
427
428 return hashmap_get(c->session_map, name);
429}
430
470d7e17 431static int session_resume_device_fn(sd_bus_message *signal,
5d301b8a
DH
432 void *userdata,
433 sd_bus_error *ret_error) {
434 idev_session *s = userdata;
435 idev_element *e;
436 uint32_t major, minor;
437 int r, fd;
438
439 r = sd_bus_message_read(signal, "uuh", &major, &minor, &fd);
440 if (r < 0) {
441 log_debug("idev: %s: erroneous ResumeDevice signal", s->name);
442 return 0;
443 }
444
445 e = idev_find_evdev(s, makedev(major, minor));
446 if (!e)
447 return 0;
448
449 element_resume(e, fd);
450 return 0;
451}
452
470d7e17 453static int session_pause_device_fn(sd_bus_message *signal,
5d301b8a
DH
454 void *userdata,
455 sd_bus_error *ret_error) {
456 idev_session *s = userdata;
457 idev_element *e;
458 uint32_t major, minor;
459 const char *mode;
460 int r;
461
462 r = sd_bus_message_read(signal, "uus", &major, &minor, &mode);
463 if (r < 0) {
464 log_debug("idev: %s: erroneous PauseDevice signal", s->name);
465 return 0;
466 }
467
468 e = idev_find_evdev(s, makedev(major, minor));
469 if (!e)
470 return 0;
471
472 element_pause(e, mode);
473 return 0;
474}
475
476static int session_setup_bus(idev_session *s) {
477 _cleanup_free_ char *match = NULL;
478 int r;
479
480 if (!s->managed)
481 return 0;
482
483 match = strjoin("type='signal',"
484 "sender='org.freedesktop.login1',"
485 "interface='org.freedesktop.login1.Session',"
486 "member='ResumeDevice',"
487 "path='", s->path, "'",
488 NULL);
489 if (!match)
490 return -ENOMEM;
491
492 r = sd_bus_add_match(s->context->sysbus,
493 &s->slot_resume_device,
494 match,
495 session_resume_device_fn,
496 s);
497 if (r < 0)
498 return r;
499
500 free(match);
501 match = strjoin("type='signal',"
502 "sender='org.freedesktop.login1',"
503 "interface='org.freedesktop.login1.Session',"
504 "member='PauseDevice',"
505 "path='", s->path, "'",
506 NULL);
507 if (!match)
508 return -ENOMEM;
509
510 r = sd_bus_add_match(s->context->sysbus,
511 &s->slot_pause_device,
512 match,
513 session_pause_device_fn,
514 s);
515 if (r < 0)
516 return r;
517
518 return 0;
519}
520
e202fa31
DH
521int idev_session_new(idev_session **out,
522 idev_context *c,
523 unsigned int flags,
524 const char *name,
525 idev_event_fn event_fn,
526 void *userdata) {
527 _cleanup_(idev_session_freep) idev_session *s = NULL;
528 int r;
529
530 assert_return(out, -EINVAL);
531 assert_return(c, -EINVAL);
532 assert_return(name, -EINVAL);
533 assert_return(event_fn, -EINVAL);
534 assert_return((flags & IDEV_SESSION_CUSTOM) == !session_id_valid(name), -EINVAL);
535 assert_return(!(flags & IDEV_SESSION_CUSTOM) || !(flags & IDEV_SESSION_MANAGED), -EINVAL);
536 assert_return(!(flags & IDEV_SESSION_MANAGED) || c->sysbus, -EINVAL);
537
538 s = new0(idev_session, 1);
539 if (!s)
540 return -ENOMEM;
541
542 s->context = idev_context_ref(c);
543 s->custom = flags & IDEV_SESSION_CUSTOM;
544 s->managed = flags & IDEV_SESSION_MANAGED;
545 s->event_fn = event_fn;
546 s->userdata = userdata;
547
548 s->name = strdup(name);
549 if (!s->name)
550 return -ENOMEM;
551
552 if (s->managed) {
553 r = sd_bus_path_encode("/org/freedesktop/login1/session", s->name, &s->path);
554 if (r < 0)
555 return r;
556 }
557
440046e9 558 s->element_map = hashmap_new(&string_hash_ops);
e202fa31
DH
559 if (!s->element_map)
560 return -ENOMEM;
561
440046e9 562 s->device_map = hashmap_new(&string_hash_ops);
e202fa31
DH
563 if (!s->device_map)
564 return -ENOMEM;
565
5d301b8a
DH
566 r = session_setup_bus(s);
567 if (r < 0)
568 return r;
569
e202fa31
DH
570 r = hashmap_put(c->session_map, s->name, s);
571 if (r < 0)
572 return r;
573
574 *out = s;
575 s = NULL;
576 return 0;
577}
578
579idev_session *idev_session_free(idev_session *s) {
580 idev_element *e;
581
582 if (!s)
583 return NULL;
584
585 while ((e = hashmap_first(s->element_map)))
586 session_remove_element(s, e);
587
588 assert(hashmap_size(s->device_map) == 0);
589
590 if (s->name)
591 hashmap_remove_value(s->context->session_map, s->name, s);
592
5d301b8a
DH
593 s->slot_pause_device = sd_bus_slot_unref(s->slot_pause_device);
594 s->slot_resume_device = sd_bus_slot_unref(s->slot_resume_device);
e202fa31
DH
595 s->context = idev_context_unref(s->context);
596 hashmap_free(s->device_map);
597 hashmap_free(s->element_map);
598 free(s->path);
599 free(s->name);
600 free(s);
601
602 return NULL;
603}
604
605bool idev_session_is_enabled(idev_session *s) {
606 return s && s->enabled;
607}
608
609void idev_session_enable(idev_session *s) {
610 idev_element *e;
611 Iterator i;
612
613 assert(s);
614
615 if (!s->enabled) {
616 s->enabled = true;
617 HASHMAP_FOREACH(e, s->element_map, i)
618 element_enable(e);
619 }
620}
621
622void idev_session_disable(idev_session *s) {
623 idev_element *e;
624 Iterator i;
625
626 assert(s);
627
628 if (s->enabled) {
629 s->enabled = false;
630 HASHMAP_FOREACH(e, s->element_map, i)
631 element_disable(e);
632 }
633}
634
e06cc7b0
DH
635static int add_link(idev_element *e, idev_device *d) {
636 idev_link *l;
637
638 assert(e);
639 assert(d);
640
641 l = new0(idev_link, 1);
642 if (!l)
643 return -ENOMEM;
644
645 l->element = e;
646 l->device = d;
647 LIST_PREPEND(links_by_element, e->links, l);
648 LIST_PREPEND(links_by_device, d->links, l);
649 device_attach(d, l);
650
651 return 0;
652}
653
654static int guess_type(struct udev_device *d) {
655 const char *id_key;
656
657 id_key = udev_device_get_property_value(d, "ID_INPUT_KEY");
658 if (streq_ptr(id_key, "1"))
659 return IDEV_DEVICE_KEYBOARD;
660
661 return IDEV_DEVICE_CNT;
662}
663
c93e5a62
DH
664int idev_session_add_evdev(idev_session *s, struct udev_device *ud) {
665 idev_element *e;
e06cc7b0 666 idev_device *d;
c93e5a62 667 dev_t devnum;
e06cc7b0 668 int r, type;
c93e5a62
DH
669
670 assert_return(s, -EINVAL);
671 assert_return(ud, -EINVAL);
672
673 devnum = udev_device_get_devnum(ud);
674 if (devnum == 0)
675 return 0;
676
677 e = idev_find_evdev(s, devnum);
678 if (e)
679 return 0;
680
681 r = idev_evdev_new(&e, s, ud);
682 if (r < 0)
683 return r;
684
685 r = session_add_element(s, e);
686 if (r != 0)
687 return r;
688
e06cc7b0
DH
689 type = guess_type(ud);
690 if (type < 0)
691 return type;
692
693 switch (type) {
694 case IDEV_DEVICE_KEYBOARD:
695 d = idev_find_keyboard(s, e->name);
696 if (d) {
697 log_debug("idev: %s: keyboard for new evdev element '%s' already available",
698 s->name, e->name);
699 return 0;
700 }
701
702 r = idev_keyboard_new(&d, s, e->name);
703 if (r < 0)
704 return r;
705
706 r = add_link(e, d);
707 if (r < 0) {
708 idev_device_free(d);
709 return r;
710 }
711
712 return session_add_device(s, d);
713 default:
714 /* unknown elements are silently ignored */
715 return 0;
716 }
c93e5a62
DH
717}
718
719int idev_session_remove_evdev(idev_session *s, struct udev_device *ud) {
720 idev_element *e;
721 dev_t devnum;
722
723 assert(s);
724 assert(ud);
725
726 devnum = udev_device_get_devnum(ud);
727 if (devnum == 0)
728 return 0;
729
730 e = idev_find_evdev(s, devnum);
731 if (!e)
732 return 0;
733
734 return session_remove_element(s, e);
735}
736
e202fa31
DH
737/*
738 * Contexts
739 */
740
741int idev_context_new(idev_context **out, sd_event *event, sd_bus *sysbus) {
742 _cleanup_(idev_context_unrefp) idev_context *c = NULL;
743
744 assert_return(out, -EINVAL);
745 assert_return(event, -EINVAL);
746
747 c = new0(idev_context, 1);
748 if (!c)
749 return -ENOMEM;
750
751 c->ref = 1;
752 c->event = sd_event_ref(event);
753
754 if (sysbus)
755 c->sysbus = sd_bus_ref(sysbus);
756
440046e9 757 c->session_map = hashmap_new(&string_hash_ops);
e202fa31
DH
758 if (!c->session_map)
759 return -ENOMEM;
760
440046e9 761 c->data_map = hashmap_new(&string_hash_ops);
e202fa31
DH
762 if (!c->data_map)
763 return -ENOMEM;
764
765 *out = c;
766 c = NULL;
767 return 0;
768}
769
770static void context_cleanup(idev_context *c) {
771 assert(hashmap_size(c->data_map) == 0);
772 assert(hashmap_size(c->session_map) == 0);
773
774 hashmap_free(c->data_map);
775 hashmap_free(c->session_map);
776 c->sysbus = sd_bus_unref(c->sysbus);
777 c->event = sd_event_unref(c->event);
778 free(c);
779}
780
781idev_context *idev_context_ref(idev_context *c) {
782 assert_return(c, NULL);
783 assert_return(c->ref > 0, NULL);
784
785 ++c->ref;
786 return c;
787}
788
789idev_context *idev_context_unref(idev_context *c) {
790 if (!c)
791 return NULL;
792
793 assert_return(c->ref > 0, NULL);
794
795 if (--c->ref == 0)
796 context_cleanup(c);
797
798 return NULL;
799}