chiark / gitweb /
terminal: add systemd-modeset debugging tool
[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 /*
278  * Sessions
279  */
280
281 static int session_raise(idev_session *s, idev_event *ev) {
282         return s->event_fn(s, s->userdata, ev);
283 }
284
285 static int session_raise_device_add(idev_session *s, idev_device *d) {
286         idev_event event = {
287                 .type = IDEV_EVENT_DEVICE_ADD,
288                 .device_add = {
289                         .device = d,
290                 },
291         };
292
293         return session_raise(s, &event);
294 }
295
296 static int session_raise_device_remove(idev_session *s, idev_device *d) {
297         idev_event event = {
298                 .type = IDEV_EVENT_DEVICE_REMOVE,
299                 .device_remove = {
300                         .device = d,
301                 },
302         };
303
304         return session_raise(s, &event);
305 }
306
307 int idev_session_raise_device_data(idev_session *s, idev_device *d, idev_data *data) {
308         idev_event event = {
309                 .type = IDEV_EVENT_DEVICE_DATA,
310                 .device_data = {
311                         .device = d,
312                         .data = *data,
313                 },
314         };
315
316         return session_raise(s, &event);
317 }
318
319 static int session_add_device(idev_session *s, idev_device *d) {
320         int r;
321
322         assert(s);
323         assert(d);
324
325         log_debug("idev: %s: add device '%s'", s->name, d->name);
326
327         d->public = true;
328         r = session_raise_device_add(s, d);
329         if (r != 0) {
330                 d->public = false;
331                 goto error;
332         }
333
334         return 0;
335
336 error:
337         if (r < 0)
338                 log_debug("idev: %s: error while adding device '%s': %s",
339                           s->name, d->name, strerror(-r));
340         return r;
341 }
342
343 static int session_remove_device(idev_session *s, idev_device *d) {
344         int r, error = 0;
345
346         assert(s);
347         assert(d);
348
349         log_debug("idev: %s: remove device '%s'", s->name, d->name);
350
351         d->public = false;
352         r = session_raise_device_remove(s, d);
353         if (r != 0)
354                 error = r;
355
356         idev_device_disable(d);
357
358         if (error < 0)
359                 log_debug("idev: %s: error while removing device '%s': %s",
360                           s->name, d->name, strerror(-error));
361         idev_device_free(d);
362         return error;
363 }
364
365 static int session_add_element(idev_session *s, idev_element *e) {
366         assert(s);
367         assert(e);
368
369         log_debug("idev: %s: add element '%s'", s->name, e->name);
370
371         if (s->enabled)
372                 element_enable(e);
373
374         return 0;
375 }
376
377 static int session_remove_element(idev_session *s, idev_element *e) {
378         int r, error = 0;
379         idev_device *d;
380         idev_link *l;
381
382         assert(s);
383         assert(e);
384
385         log_debug("idev: %s: remove element '%s'", s->name, e->name);
386
387         while ((l = e->links)) {
388                 d = l->device;
389                 LIST_REMOVE(links_by_device, d->links, l);
390                 LIST_REMOVE(links_by_element, e->links, l);
391                 device_detach(d, l);
392
393                 if (!d->links) {
394                         r = session_remove_device(s, d);
395                         if (r != 0)
396                                 error = r;
397                 }
398
399                 l->device = NULL;
400                 l->element = NULL;
401                 free(l);
402         }
403
404         element_disable(e);
405
406         if (error < 0)
407                 log_debug("idev: %s: error while removing element '%s': %s",
408                           s->name, e->name, strerror(-r));
409         idev_element_free(e);
410         return error;
411 }
412
413 idev_session *idev_find_session(idev_context *c, const char *name) {
414         assert_return(c, NULL);
415         assert_return(name, NULL);
416
417         return hashmap_get(c->session_map, name);
418 }
419
420 int idev_session_new(idev_session **out,
421                      idev_context *c,
422                      unsigned int flags,
423                      const char *name,
424                      idev_event_fn event_fn,
425                      void *userdata) {
426         _cleanup_(idev_session_freep) idev_session *s = NULL;
427         int r;
428
429         assert_return(out, -EINVAL);
430         assert_return(c, -EINVAL);
431         assert_return(name, -EINVAL);
432         assert_return(event_fn, -EINVAL);
433         assert_return((flags & IDEV_SESSION_CUSTOM) == !session_id_valid(name), -EINVAL);
434         assert_return(!(flags & IDEV_SESSION_CUSTOM) || !(flags & IDEV_SESSION_MANAGED), -EINVAL);
435         assert_return(!(flags & IDEV_SESSION_MANAGED) || c->sysbus, -EINVAL);
436
437         s = new0(idev_session, 1);
438         if (!s)
439                 return -ENOMEM;
440
441         s->context = idev_context_ref(c);
442         s->custom = flags & IDEV_SESSION_CUSTOM;
443         s->managed = flags & IDEV_SESSION_MANAGED;
444         s->event_fn = event_fn;
445         s->userdata = userdata;
446
447         s->name = strdup(name);
448         if (!s->name)
449                 return -ENOMEM;
450
451         if (s->managed) {
452                 r = sd_bus_path_encode("/org/freedesktop/login1/session", s->name, &s->path);
453                 if (r < 0)
454                         return r;
455         }
456
457         s->element_map = hashmap_new(&string_hash_ops);
458         if (!s->element_map)
459                 return -ENOMEM;
460
461         s->device_map = hashmap_new(&string_hash_ops);
462         if (!s->device_map)
463                 return -ENOMEM;
464
465         r = hashmap_put(c->session_map, s->name, s);
466         if (r < 0)
467                 return r;
468
469         *out = s;
470         s = NULL;
471         return 0;
472 }
473
474 idev_session *idev_session_free(idev_session *s) {
475         idev_element *e;
476
477         if (!s)
478                 return NULL;
479
480         while ((e = hashmap_first(s->element_map)))
481                 session_remove_element(s, e);
482
483         assert(hashmap_size(s->device_map) == 0);
484
485         if (s->name)
486                 hashmap_remove_value(s->context->session_map, s->name, s);
487
488         s->context = idev_context_unref(s->context);
489         hashmap_free(s->device_map);
490         hashmap_free(s->element_map);
491         free(s->path);
492         free(s->name);
493         free(s);
494
495         return NULL;
496 }
497
498 bool idev_session_is_enabled(idev_session *s) {
499         return s && s->enabled;
500 }
501
502 void idev_session_enable(idev_session *s) {
503         idev_element *e;
504         Iterator i;
505
506         assert(s);
507
508         if (!s->enabled) {
509                 s->enabled = true;
510                 HASHMAP_FOREACH(e, s->element_map, i)
511                         element_enable(e);
512         }
513 }
514
515 void idev_session_disable(idev_session *s) {
516         idev_element *e;
517         Iterator i;
518
519         assert(s);
520
521         if (s->enabled) {
522                 s->enabled = false;
523                 HASHMAP_FOREACH(e, s->element_map, i)
524                         element_disable(e);
525         }
526 }
527
528 static int add_link(idev_element *e, idev_device *d) {
529         idev_link *l;
530
531         assert(e);
532         assert(d);
533
534         l = new0(idev_link, 1);
535         if (!l)
536                 return -ENOMEM;
537
538         l->element = e;
539         l->device = d;
540         LIST_PREPEND(links_by_element, e->links, l);
541         LIST_PREPEND(links_by_device, d->links, l);
542         device_attach(d, l);
543
544         return 0;
545 }
546
547 static int guess_type(struct udev_device *d) {
548         const char *id_key;
549
550         id_key = udev_device_get_property_value(d, "ID_INPUT_KEY");
551         if (streq_ptr(id_key, "1"))
552                 return IDEV_DEVICE_KEYBOARD;
553
554         return IDEV_DEVICE_CNT;
555 }
556
557 int idev_session_add_evdev(idev_session *s, struct udev_device *ud) {
558         idev_element *e;
559         idev_device *d;
560         dev_t devnum;
561         int r, type;
562
563         assert_return(s, -EINVAL);
564         assert_return(ud, -EINVAL);
565
566         devnum = udev_device_get_devnum(ud);
567         if (devnum == 0)
568                 return 0;
569
570         e = idev_find_evdev(s, devnum);
571         if (e)
572                 return 0;
573
574         r = idev_evdev_new(&e, s, ud);
575         if (r < 0)
576                 return r;
577
578         r = session_add_element(s, e);
579         if (r != 0)
580                 return r;
581
582         type = guess_type(ud);
583         if (type < 0)
584                 return type;
585
586         switch (type) {
587         case IDEV_DEVICE_KEYBOARD:
588                 d = idev_find_keyboard(s, e->name);
589                 if (d) {
590                         log_debug("idev: %s: keyboard for new evdev element '%s' already available",
591                                   s->name, e->name);
592                         return 0;
593                 }
594
595                 r = idev_keyboard_new(&d, s, e->name);
596                 if (r < 0)
597                         return r;
598
599                 r = add_link(e, d);
600                 if (r < 0) {
601                         idev_device_free(d);
602                         return r;
603                 }
604
605                 return session_add_device(s, d);
606         default:
607                 /* unknown elements are silently ignored */
608                 return 0;
609         }
610 }
611
612 int idev_session_remove_evdev(idev_session *s, struct udev_device *ud) {
613         idev_element *e;
614         dev_t devnum;
615
616         assert(s);
617         assert(ud);
618
619         devnum = udev_device_get_devnum(ud);
620         if (devnum == 0)
621                 return 0;
622
623         e = idev_find_evdev(s, devnum);
624         if (!e)
625                 return 0;
626
627         return session_remove_element(s, e);
628 }
629
630 /*
631  * Contexts
632  */
633
634 int idev_context_new(idev_context **out, sd_event *event, sd_bus *sysbus) {
635         _cleanup_(idev_context_unrefp) idev_context *c = NULL;
636
637         assert_return(out, -EINVAL);
638         assert_return(event, -EINVAL);
639
640         c = new0(idev_context, 1);
641         if (!c)
642                 return -ENOMEM;
643
644         c->ref = 1;
645         c->event = sd_event_ref(event);
646
647         if (sysbus)
648                 c->sysbus = sd_bus_ref(sysbus);
649
650         c->session_map = hashmap_new(&string_hash_ops);
651         if (!c->session_map)
652                 return -ENOMEM;
653
654         c->data_map = hashmap_new(&string_hash_ops);
655         if (!c->data_map)
656                 return -ENOMEM;
657
658         *out = c;
659         c = NULL;
660         return 0;
661 }
662
663 static void context_cleanup(idev_context *c) {
664         assert(hashmap_size(c->data_map) == 0);
665         assert(hashmap_size(c->session_map) == 0);
666
667         hashmap_free(c->data_map);
668         hashmap_free(c->session_map);
669         c->sysbus = sd_bus_unref(c->sysbus);
670         c->event = sd_event_unref(c->event);
671         free(c);
672 }
673
674 idev_context *idev_context_ref(idev_context *c) {
675         assert_return(c, NULL);
676         assert_return(c->ref > 0, NULL);
677
678         ++c->ref;
679         return c;
680 }
681
682 idev_context *idev_context_unref(idev_context *c) {
683         if (!c)
684                 return NULL;
685
686         assert_return(c->ref > 0, NULL);
687
688         if (--c->ref == 0)
689                 context_cleanup(c);
690
691         return NULL;
692 }