1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com>
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.
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.
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/>.
24 #include <linux/input.h>
27 #include <systemd/sd-bus.h>
28 #include <systemd/sd-event.h>
29 #include <systemd/sd-login.h>
30 #include <xkbcommon/xkbcommon.h>
33 #include "idev-internal.h"
34 #include "login-shared.h"
36 #include "udev-util.h"
39 static void element_open(idev_element *e);
40 static void element_close(idev_element *e);
46 idev_device *idev_find_device(idev_session *s, const char *name) {
47 assert_return(s, NULL);
48 assert_return(name, NULL);
50 return hashmap_get(s->device_map, name);
53 int idev_device_add(idev_device *d, const char *name) {
56 assert_return(d, -EINVAL);
57 assert_return(d->vtable, -EINVAL);
58 assert_return(d->session, -EINVAL);
59 assert_return(name, -EINVAL);
61 d->name = strdup(name);
65 r = hashmap_put(d->session->device_map, d->name, d);
72 idev_device *idev_device_free(idev_device *d) {
82 assert(d->vtable->free);
85 hashmap_remove_value(d->session->device_map, d->name, d);
95 int idev_device_feed(idev_device *d, idev_data *data) {
98 assert(data->type < IDEV_DATA_CNT);
101 return d->vtable->feed(d, data);
106 void idev_device_feedback(idev_device *d, idev_data *data) {
111 assert(data->type < IDEV_DATA_CNT);
113 LIST_FOREACH(links_by_device, l, d->links)
114 idev_element_feedback(l->element, data);
117 static void device_attach(idev_device *d, idev_link *l) {
121 if (d->vtable->attach)
122 d->vtable->attach(d, l);
125 element_open(l->element);
128 static void device_detach(idev_device *d, idev_link *l) {
133 element_close(l->element);
135 if (d->vtable->detach)
136 d->vtable->detach(d, l);
139 void idev_device_enable(idev_device *d) {
146 LIST_FOREACH(links_by_device, l, d->links)
147 element_open(l->element);
151 void idev_device_disable(idev_device *d) {
158 LIST_FOREACH(links_by_device, l, d->links)
159 element_close(l->element);
167 idev_element *idev_find_element(idev_session *s, const char *name) {
168 assert_return(s, NULL);
169 assert_return(name, NULL);
171 return hashmap_get(s->element_map, name);
174 int idev_element_add(idev_element *e, const char *name) {
177 assert_return(e, -EINVAL);
178 assert_return(e->vtable, -EINVAL);
179 assert_return(e->session, -EINVAL);
180 assert_return(name, -EINVAL);
182 e->name = strdup(name);
186 r = hashmap_put(e->session->element_map, e->name, e);
193 idev_element *idev_element_free(idev_element *e) {
201 assert(e->n_open == 0);
203 assert(e->vtable->free);
206 hashmap_remove_value(e->session->element_map, e->name, e);
216 int idev_element_feed(idev_element *e, idev_data *data) {
222 assert(data->type < IDEV_DATA_CNT);
224 LIST_FOREACH(links_by_element, l, e->links) {
225 r = idev_device_feed(l->device, data);
233 void idev_element_feedback(idev_element *e, idev_data *data) {
236 assert(data->type < IDEV_DATA_CNT);
238 if (e->vtable->feedback)
239 e->vtable->feedback(e, data);
242 static void element_open(idev_element *e) {
245 if (e->n_open++ == 0 && e->vtable->open)
249 static void element_close(idev_element *e) {
251 assert(e->n_open > 0);
253 if (--e->n_open == 0 && e->vtable->close)
257 static void element_enable(idev_element *e) {
262 if (e->vtable->enable)
263 e->vtable->enable(e);
267 static void element_disable(idev_element *e) {
272 if (e->vtable->disable)
273 e->vtable->disable(e);
277 static void element_resume(idev_element *e, int fd) {
281 if (e->vtable->resume)
282 e->vtable->resume(e, fd);
285 static void element_pause(idev_element *e, const char *mode) {
289 if (e->vtable->pause)
290 e->vtable->pause(e, mode);
297 static int session_raise(idev_session *s, idev_event *ev) {
298 return s->event_fn(s, s->userdata, ev);
301 static int session_raise_device_add(idev_session *s, idev_device *d) {
303 .type = IDEV_EVENT_DEVICE_ADD,
309 return session_raise(s, &event);
312 static int session_raise_device_remove(idev_session *s, idev_device *d) {
314 .type = IDEV_EVENT_DEVICE_REMOVE,
320 return session_raise(s, &event);
323 int idev_session_raise_device_data(idev_session *s, idev_device *d, idev_data *data) {
325 .type = IDEV_EVENT_DEVICE_DATA,
332 return session_raise(s, &event);
335 static int session_add_device(idev_session *s, idev_device *d) {
341 log_debug("idev: %s: add device '%s'", s->name, d->name);
344 r = session_raise_device_add(s, d);
354 log_debug("idev: %s: error while adding device '%s': %s",
355 s->name, d->name, strerror(-r));
359 static int session_remove_device(idev_session *s, idev_device *d) {
365 log_debug("idev: %s: remove device '%s'", s->name, d->name);
368 r = session_raise_device_remove(s, d);
372 idev_device_disable(d);
375 log_debug("idev: %s: error while removing device '%s': %s",
376 s->name, d->name, strerror(-error));
381 static int session_add_element(idev_session *s, idev_element *e) {
385 log_debug("idev: %s: add element '%s'", s->name, e->name);
393 static int session_remove_element(idev_session *s, idev_element *e) {
401 log_debug("idev: %s: remove element '%s'", s->name, e->name);
403 while ((l = e->links)) {
405 LIST_REMOVE(links_by_device, d->links, l);
406 LIST_REMOVE(links_by_element, e->links, l);
410 r = session_remove_device(s, d);
423 log_debug("idev: %s: error while removing element '%s': %s",
424 s->name, e->name, strerror(-r));
425 idev_element_free(e);
429 idev_session *idev_find_session(idev_context *c, const char *name) {
430 assert_return(c, NULL);
431 assert_return(name, NULL);
433 return hashmap_get(c->session_map, name);
436 static int session_resume_device_fn(sd_bus *bus,
437 sd_bus_message *signal,
439 sd_bus_error *ret_error) {
440 idev_session *s = userdata;
442 uint32_t major, minor;
445 r = sd_bus_message_read(signal, "uuh", &major, &minor, &fd);
447 log_debug("idev: %s: erroneous ResumeDevice signal", s->name);
451 e = idev_find_evdev(s, makedev(major, minor));
455 element_resume(e, fd);
459 static int session_pause_device_fn(sd_bus *bus,
460 sd_bus_message *signal,
462 sd_bus_error *ret_error) {
463 idev_session *s = userdata;
465 uint32_t major, minor;
469 r = sd_bus_message_read(signal, "uus", &major, &minor, &mode);
471 log_debug("idev: %s: erroneous PauseDevice signal", s->name);
475 e = idev_find_evdev(s, makedev(major, minor));
479 element_pause(e, mode);
483 static int session_setup_bus(idev_session *s) {
484 _cleanup_free_ char *match = NULL;
490 match = strjoin("type='signal',"
491 "sender='org.freedesktop.login1',"
492 "interface='org.freedesktop.login1.Session',"
493 "member='ResumeDevice',"
494 "path='", s->path, "'",
499 r = sd_bus_add_match(s->context->sysbus,
500 &s->slot_resume_device,
502 session_resume_device_fn,
508 match = strjoin("type='signal',"
509 "sender='org.freedesktop.login1',"
510 "interface='org.freedesktop.login1.Session',"
511 "member='PauseDevice',"
512 "path='", s->path, "'",
517 r = sd_bus_add_match(s->context->sysbus,
518 &s->slot_pause_device,
520 session_pause_device_fn,
528 int idev_session_new(idev_session **out,
532 idev_event_fn event_fn,
534 _cleanup_(idev_session_freep) idev_session *s = NULL;
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);
545 s = new0(idev_session, 1);
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;
555 s->name = strdup(name);
560 r = sd_bus_path_encode("/org/freedesktop/login1/session", s->name, &s->path);
565 s->element_map = hashmap_new(&string_hash_ops);
569 s->device_map = hashmap_new(&string_hash_ops);
573 r = session_setup_bus(s);
577 r = hashmap_put(c->session_map, s->name, s);
586 idev_session *idev_session_free(idev_session *s) {
592 while ((e = hashmap_first(s->element_map)))
593 session_remove_element(s, e);
595 assert(hashmap_size(s->device_map) == 0);
598 hashmap_remove_value(s->context->session_map, s->name, s);
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);
612 bool idev_session_is_enabled(idev_session *s) {
613 return s && s->enabled;
616 void idev_session_enable(idev_session *s) {
624 HASHMAP_FOREACH(e, s->element_map, i)
629 void idev_session_disable(idev_session *s) {
637 HASHMAP_FOREACH(e, s->element_map, i)
642 static int add_link(idev_element *e, idev_device *d) {
648 l = new0(idev_link, 1);
654 LIST_PREPEND(links_by_element, e->links, l);
655 LIST_PREPEND(links_by_device, d->links, l);
661 static int guess_type(struct udev_device *d) {
664 id_key = udev_device_get_property_value(d, "ID_INPUT_KEY");
665 if (streq_ptr(id_key, "1"))
666 return IDEV_DEVICE_KEYBOARD;
668 return IDEV_DEVICE_CNT;
671 int idev_session_add_evdev(idev_session *s, struct udev_device *ud) {
677 assert_return(s, -EINVAL);
678 assert_return(ud, -EINVAL);
680 devnum = udev_device_get_devnum(ud);
684 e = idev_find_evdev(s, devnum);
688 r = idev_evdev_new(&e, s, ud);
692 r = session_add_element(s, e);
696 type = guess_type(ud);
701 case IDEV_DEVICE_KEYBOARD:
702 d = idev_find_keyboard(s, e->name);
704 log_debug("idev: %s: keyboard for new evdev element '%s' already available",
709 r = idev_keyboard_new(&d, s, e->name);
719 return session_add_device(s, d);
721 /* unknown elements are silently ignored */
726 int idev_session_remove_evdev(idev_session *s, struct udev_device *ud) {
733 devnum = udev_device_get_devnum(ud);
737 e = idev_find_evdev(s, devnum);
741 return session_remove_element(s, e);
748 int idev_context_new(idev_context **out, sd_event *event, sd_bus *sysbus) {
749 _cleanup_(idev_context_unrefp) idev_context *c = NULL;
751 assert_return(out, -EINVAL);
752 assert_return(event, -EINVAL);
754 c = new0(idev_context, 1);
759 c->event = sd_event_ref(event);
762 c->sysbus = sd_bus_ref(sysbus);
764 c->session_map = hashmap_new(&string_hash_ops);
768 c->data_map = hashmap_new(&string_hash_ops);
777 static void context_cleanup(idev_context *c) {
778 assert(hashmap_size(c->data_map) == 0);
779 assert(hashmap_size(c->session_map) == 0);
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);
788 idev_context *idev_context_ref(idev_context *c) {
789 assert_return(c, NULL);
790 assert_return(c->ref > 0, NULL);
796 idev_context *idev_context_unref(idev_context *c) {
800 assert_return(c->ref > 0, NULL);