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