chiark / gitweb /
remove unused includes
[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 <libudev.h>
23 #include <stdbool.h>
24 #include <stdlib.h>
25 #include <systemd/sd-bus.h>
26 #include <systemd/sd-event.h>
27 #include "hashmap.h"
28 #include "idev.h"
29 #include "idev-internal.h"
30 #include "login-shared.h"
31 #include "macro.h"
32 #include "util.h"
33
34 static void element_open(idev_element *e);
35 static void element_close(idev_element *e);
36
37 /*
38  * Devices
39  */
40
41 idev_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
48 int 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
67 idev_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
90 int 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
101 void 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
112 static 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
123 static 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
134 void 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
146 void 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
162 idev_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
169 int 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
188 idev_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
211 int 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
228 void 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
237 static 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
244 static 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
252 static 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
262 static 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
272 static 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
280 static 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
288 /*
289  * Sessions
290  */
291
292 static int session_raise(idev_session *s, idev_event *ev) {
293         return s->event_fn(s, s->userdata, ev);
294 }
295
296 static 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
307 static 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
318 int 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
330 static 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
347 error:
348         if (r < 0)
349                 log_debug_errno(r, "idev: %s: error while adding device '%s': %m",
350                                 s->name, d->name);
351         return r;
352 }
353
354 static 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)
370                 log_debug_errno(error, "idev: %s: error while removing device '%s': %m",
371                                 s->name, d->name);
372         idev_device_free(d);
373         return error;
374 }
375
376 static 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
388 static 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)
418                 log_debug_errno(r, "idev: %s: error while removing element '%s': %m",
419                                 s->name, e->name);
420         idev_element_free(e);
421         return error;
422 }
423
424 idev_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
431 static int session_resume_device_fn(sd_bus *bus,
432                                     sd_bus_message *signal,
433                                     void *userdata,
434                                     sd_bus_error *ret_error) {
435         idev_session *s = userdata;
436         idev_element *e;
437         uint32_t major, minor;
438         int r, fd;
439
440         r = sd_bus_message_read(signal, "uuh", &major, &minor, &fd);
441         if (r < 0) {
442                 log_debug("idev: %s: erroneous ResumeDevice signal", s->name);
443                 return 0;
444         }
445
446         e = idev_find_evdev(s, makedev(major, minor));
447         if (!e)
448                 return 0;
449
450         element_resume(e, fd);
451         return 0;
452 }
453
454 static int session_pause_device_fn(sd_bus *bus,
455                                    sd_bus_message *signal,
456                                    void *userdata,
457                                    sd_bus_error *ret_error) {
458         idev_session *s = userdata;
459         idev_element *e;
460         uint32_t major, minor;
461         const char *mode;
462         int r;
463
464         r = sd_bus_message_read(signal, "uus", &major, &minor, &mode);
465         if (r < 0) {
466                 log_debug("idev: %s: erroneous PauseDevice signal", s->name);
467                 return 0;
468         }
469
470         e = idev_find_evdev(s, makedev(major, minor));
471         if (!e)
472                 return 0;
473
474         element_pause(e, mode);
475         return 0;
476 }
477
478 static int session_setup_bus(idev_session *s) {
479         _cleanup_free_ char *match = NULL;
480         int r;
481
482         if (!s->managed)
483                 return 0;
484
485         match = strjoin("type='signal',"
486                         "sender='org.freedesktop.login1',"
487                         "interface='org.freedesktop.login1.Session',"
488                         "member='ResumeDevice',"
489                         "path='", s->path, "'",
490                         NULL);
491         if (!match)
492                 return -ENOMEM;
493
494         r = sd_bus_add_match(s->context->sysbus,
495                              &s->slot_resume_device,
496                              match,
497                              session_resume_device_fn,
498                              s);
499         if (r < 0)
500                 return r;
501
502         free(match);
503         match = strjoin("type='signal',"
504                         "sender='org.freedesktop.login1',"
505                         "interface='org.freedesktop.login1.Session',"
506                         "member='PauseDevice',"
507                         "path='", s->path, "'",
508                         NULL);
509         if (!match)
510                 return -ENOMEM;
511
512         r = sd_bus_add_match(s->context->sysbus,
513                              &s->slot_pause_device,
514                              match,
515                              session_pause_device_fn,
516                              s);
517         if (r < 0)
518                 return r;
519
520         return 0;
521 }
522
523 int idev_session_new(idev_session **out,
524                      idev_context *c,
525                      unsigned int flags,
526                      const char *name,
527                      idev_event_fn event_fn,
528                      void *userdata) {
529         _cleanup_(idev_session_freep) idev_session *s = NULL;
530         int r;
531
532         assert_return(out, -EINVAL);
533         assert_return(c, -EINVAL);
534         assert_return(name, -EINVAL);
535         assert_return(event_fn, -EINVAL);
536         assert_return((flags & IDEV_SESSION_CUSTOM) == !session_id_valid(name), -EINVAL);
537         assert_return(!(flags & IDEV_SESSION_CUSTOM) || !(flags & IDEV_SESSION_MANAGED), -EINVAL);
538         assert_return(!(flags & IDEV_SESSION_MANAGED) || c->sysbus, -EINVAL);
539
540         s = new0(idev_session, 1);
541         if (!s)
542                 return -ENOMEM;
543
544         s->context = idev_context_ref(c);
545         s->custom = flags & IDEV_SESSION_CUSTOM;
546         s->managed = flags & IDEV_SESSION_MANAGED;
547         s->event_fn = event_fn;
548         s->userdata = userdata;
549
550         s->name = strdup(name);
551         if (!s->name)
552                 return -ENOMEM;
553
554         if (s->managed) {
555                 r = sd_bus_path_encode("/org/freedesktop/login1/session", s->name, &s->path);
556                 if (r < 0)
557                         return r;
558         }
559
560         s->element_map = hashmap_new(&string_hash_ops);
561         if (!s->element_map)
562                 return -ENOMEM;
563
564         s->device_map = hashmap_new(&string_hash_ops);
565         if (!s->device_map)
566                 return -ENOMEM;
567
568         r = session_setup_bus(s);
569         if (r < 0)
570                 return r;
571
572         r = hashmap_put(c->session_map, s->name, s);
573         if (r < 0)
574                 return r;
575
576         *out = s;
577         s = NULL;
578         return 0;
579 }
580
581 idev_session *idev_session_free(idev_session *s) {
582         idev_element *e;
583
584         if (!s)
585                 return NULL;
586
587         while ((e = hashmap_first(s->element_map)))
588                 session_remove_element(s, e);
589
590         assert(hashmap_size(s->device_map) == 0);
591
592         if (s->name)
593                 hashmap_remove_value(s->context->session_map, s->name, s);
594
595         s->slot_pause_device = sd_bus_slot_unref(s->slot_pause_device);
596         s->slot_resume_device = sd_bus_slot_unref(s->slot_resume_device);
597         s->context = idev_context_unref(s->context);
598         hashmap_free(s->device_map);
599         hashmap_free(s->element_map);
600         free(s->path);
601         free(s->name);
602         free(s);
603
604         return NULL;
605 }
606
607 bool idev_session_is_enabled(idev_session *s) {
608         return s && s->enabled;
609 }
610
611 void idev_session_enable(idev_session *s) {
612         idev_element *e;
613         Iterator i;
614
615         assert(s);
616
617         if (!s->enabled) {
618                 s->enabled = true;
619                 HASHMAP_FOREACH(e, s->element_map, i)
620                         element_enable(e);
621         }
622 }
623
624 void idev_session_disable(idev_session *s) {
625         idev_element *e;
626         Iterator i;
627
628         assert(s);
629
630         if (s->enabled) {
631                 s->enabled = false;
632                 HASHMAP_FOREACH(e, s->element_map, i)
633                         element_disable(e);
634         }
635 }
636
637 static int add_link(idev_element *e, idev_device *d) {
638         idev_link *l;
639
640         assert(e);
641         assert(d);
642
643         l = new0(idev_link, 1);
644         if (!l)
645                 return -ENOMEM;
646
647         l->element = e;
648         l->device = d;
649         LIST_PREPEND(links_by_element, e->links, l);
650         LIST_PREPEND(links_by_device, d->links, l);
651         device_attach(d, l);
652
653         return 0;
654 }
655
656 static int guess_type(struct udev_device *d) {
657         const char *id_key;
658
659         id_key = udev_device_get_property_value(d, "ID_INPUT_KEY");
660         if (streq_ptr(id_key, "1"))
661                 return IDEV_DEVICE_KEYBOARD;
662
663         return IDEV_DEVICE_CNT;
664 }
665
666 int idev_session_add_evdev(idev_session *s, struct udev_device *ud) {
667         idev_element *e;
668         idev_device *d;
669         dev_t devnum;
670         int r, type;
671
672         assert_return(s, -EINVAL);
673         assert_return(ud, -EINVAL);
674
675         devnum = udev_device_get_devnum(ud);
676         if (devnum == 0)
677                 return 0;
678
679         e = idev_find_evdev(s, devnum);
680         if (e)
681                 return 0;
682
683         r = idev_evdev_new(&e, s, ud);
684         if (r < 0)
685                 return r;
686
687         r = session_add_element(s, e);
688         if (r != 0)
689                 return r;
690
691         type = guess_type(ud);
692         if (type < 0)
693                 return type;
694
695         switch (type) {
696         case IDEV_DEVICE_KEYBOARD:
697                 d = idev_find_keyboard(s, e->name);
698                 if (d) {
699                         log_debug("idev: %s: keyboard for new evdev element '%s' already available",
700                                   s->name, e->name);
701                         return 0;
702                 }
703
704                 r = idev_keyboard_new(&d, s, e->name);
705                 if (r < 0)
706                         return r;
707
708                 r = add_link(e, d);
709                 if (r < 0) {
710                         idev_device_free(d);
711                         return r;
712                 }
713
714                 return session_add_device(s, d);
715         default:
716                 /* unknown elements are silently ignored */
717                 return 0;
718         }
719 }
720
721 int idev_session_remove_evdev(idev_session *s, struct udev_device *ud) {
722         idev_element *e;
723         dev_t devnum;
724
725         assert(s);
726         assert(ud);
727
728         devnum = udev_device_get_devnum(ud);
729         if (devnum == 0)
730                 return 0;
731
732         e = idev_find_evdev(s, devnum);
733         if (!e)
734                 return 0;
735
736         return session_remove_element(s, e);
737 }
738
739 /*
740  * Contexts
741  */
742
743 int idev_context_new(idev_context **out, sd_event *event, sd_bus *sysbus) {
744         _cleanup_(idev_context_unrefp) idev_context *c = NULL;
745
746         assert_return(out, -EINVAL);
747         assert_return(event, -EINVAL);
748
749         c = new0(idev_context, 1);
750         if (!c)
751                 return -ENOMEM;
752
753         c->ref = 1;
754         c->event = sd_event_ref(event);
755
756         if (sysbus)
757                 c->sysbus = sd_bus_ref(sysbus);
758
759         c->session_map = hashmap_new(&string_hash_ops);
760         if (!c->session_map)
761                 return -ENOMEM;
762
763         c->data_map = hashmap_new(&string_hash_ops);
764         if (!c->data_map)
765                 return -ENOMEM;
766
767         *out = c;
768         c = NULL;
769         return 0;
770 }
771
772 static void context_cleanup(idev_context *c) {
773         assert(hashmap_size(c->data_map) == 0);
774         assert(hashmap_size(c->session_map) == 0);
775
776         hashmap_free(c->data_map);
777         hashmap_free(c->session_map);
778         c->sysbus = sd_bus_unref(c->sysbus);
779         c->event = sd_event_unref(c->event);
780         free(c);
781 }
782
783 idev_context *idev_context_ref(idev_context *c) {
784         assert_return(c, NULL);
785         assert_return(c->ref > 0, NULL);
786
787         ++c->ref;
788         return c;
789 }
790
791 idev_context *idev_context_unref(idev_context *c) {
792         if (!c)
793                 return NULL;
794
795         assert_return(c->ref > 0, NULL);
796
797         if (--c->ref == 0)
798                 context_cleanup(c);
799
800         return NULL;
801 }