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