chiark / gitweb /
terminal: make utf8 decoder return length
[elogind.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 }