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"
37 #include "udev-util.h"
40 static void element_open(idev_element *e);
41 static void element_close(idev_element *e);
47 idev_device *idev_find_device(idev_session *s, const char *name) {
48 assert_return(s, NULL);
49 assert_return(name, NULL);
51 return hashmap_get(s->device_map, name);
54 int idev_device_add(idev_device *d, const char *name) {
57 assert_return(d, -EINVAL);
58 assert_return(d->vtable, -EINVAL);
59 assert_return(d->session, -EINVAL);
60 assert_return(name, -EINVAL);
62 d->name = strdup(name);
66 r = hashmap_put(d->session->device_map, d->name, d);
73 idev_device *idev_device_free(idev_device *d) {
83 assert(d->vtable->free);
86 hashmap_remove_value(d->session->device_map, d->name, d);
96 int idev_device_feed(idev_device *d, idev_data *data) {
99 assert(data->type < IDEV_DATA_CNT);
102 return d->vtable->feed(d, data);
107 void idev_device_feedback(idev_device *d, idev_data *data) {
112 assert(data->type < IDEV_DATA_CNT);
114 LIST_FOREACH(links_by_device, l, d->links)
115 idev_element_feedback(l->element, data);
118 static void device_attach(idev_device *d, idev_link *l) {
122 if (d->vtable->attach)
123 d->vtable->attach(d, l);
126 element_open(l->element);
129 static void device_detach(idev_device *d, idev_link *l) {
134 element_close(l->element);
136 if (d->vtable->detach)
137 d->vtable->detach(d, l);
140 void idev_device_enable(idev_device *d) {
147 LIST_FOREACH(links_by_device, l, d->links)
148 element_open(l->element);
152 void idev_device_disable(idev_device *d) {
159 LIST_FOREACH(links_by_device, l, d->links)
160 element_close(l->element);
168 idev_element *idev_find_element(idev_session *s, const char *name) {
169 assert_return(s, NULL);
170 assert_return(name, NULL);
172 return hashmap_get(s->element_map, name);
175 int idev_element_add(idev_element *e, const char *name) {
178 assert_return(e, -EINVAL);
179 assert_return(e->vtable, -EINVAL);
180 assert_return(e->session, -EINVAL);
181 assert_return(name, -EINVAL);
183 e->name = strdup(name);
187 r = hashmap_put(e->session->element_map, e->name, e);
194 idev_element *idev_element_free(idev_element *e) {
202 assert(e->n_open == 0);
204 assert(e->vtable->free);
207 hashmap_remove_value(e->session->element_map, e->name, e);
217 int idev_element_feed(idev_element *e, idev_data *data) {
223 assert(data->type < IDEV_DATA_CNT);
225 LIST_FOREACH(links_by_element, l, e->links) {
226 r = idev_device_feed(l->device, data);
234 void idev_element_feedback(idev_element *e, idev_data *data) {
237 assert(data->type < IDEV_DATA_CNT);
239 if (e->vtable->feedback)
240 e->vtable->feedback(e, data);
243 static void element_open(idev_element *e) {
246 if (e->n_open++ == 0 && e->vtable->open)
250 static void element_close(idev_element *e) {
252 assert(e->n_open > 0);
254 if (--e->n_open == 0 && e->vtable->close)
258 static void element_enable(idev_element *e) {
263 if (e->vtable->enable)
264 e->vtable->enable(e);
268 static void element_disable(idev_element *e) {
273 if (e->vtable->disable)
274 e->vtable->disable(e);
282 static int session_raise(idev_session *s, idev_event *ev) {
283 return s->event_fn(s, s->userdata, ev);
286 static int session_raise_device_add(idev_session *s, idev_device *d) {
288 .type = IDEV_EVENT_DEVICE_ADD,
294 return session_raise(s, &event);
297 static int session_raise_device_remove(idev_session *s, idev_device *d) {
299 .type = IDEV_EVENT_DEVICE_REMOVE,
305 return session_raise(s, &event);
308 int idev_session_raise_device_data(idev_session *s, idev_device *d, idev_data *data) {
310 .type = IDEV_EVENT_DEVICE_DATA,
317 return session_raise(s, &event);
320 static int session_add_device(idev_session *s, idev_device *d) {
326 log_debug("idev: %s: add device '%s'", s->name, d->name);
329 r = session_raise_device_add(s, d);
339 log_debug("idev: %s: error while adding device '%s': %s",
340 s->name, d->name, strerror(-r));
344 static int session_remove_device(idev_session *s, idev_device *d) {
350 log_debug("idev: %s: remove device '%s'", s->name, d->name);
353 r = session_raise_device_remove(s, d);
357 idev_device_disable(d);
360 log_debug("idev: %s: error while removing device '%s': %s",
361 s->name, d->name, strerror(-error));
366 static int session_add_element(idev_session *s, idev_element *e) {
370 log_debug("idev: %s: add element '%s'", s->name, e->name);
378 static int session_remove_element(idev_session *s, idev_element *e) {
386 log_debug("idev: %s: remove element '%s'", s->name, e->name);
388 while ((l = e->links)) {
390 LIST_REMOVE(links_by_device, d->links, l);
391 LIST_REMOVE(links_by_element, e->links, l);
395 r = session_remove_device(s, d);
408 log_debug("idev: %s: error while removing element '%s': %s",
409 s->name, e->name, strerror(-r));
410 idev_element_free(e);
414 idev_session *idev_find_session(idev_context *c, const char *name) {
415 assert_return(c, NULL);
416 assert_return(name, NULL);
418 return hashmap_get(c->session_map, name);
421 int idev_session_new(idev_session **out,
425 idev_event_fn event_fn,
427 _cleanup_(idev_session_freep) idev_session *s = NULL;
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);
438 s = new0(idev_session, 1);
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;
448 s->name = strdup(name);
453 r = sd_bus_path_encode("/org/freedesktop/login1/session", s->name, &s->path);
458 s->element_map = hashmap_new(string_hash_func, string_compare_func);
462 s->device_map = hashmap_new(string_hash_func, string_compare_func);
466 r = hashmap_put(c->session_map, s->name, s);
475 idev_session *idev_session_free(idev_session *s) {
481 while ((e = hashmap_first(s->element_map)))
482 session_remove_element(s, e);
484 assert(hashmap_size(s->device_map) == 0);
487 hashmap_remove_value(s->context->session_map, s->name, s);
489 s->context = idev_context_unref(s->context);
490 hashmap_free(s->device_map);
491 hashmap_free(s->element_map);
499 bool idev_session_is_enabled(idev_session *s) {
500 return s && s->enabled;
503 void idev_session_enable(idev_session *s) {
511 HASHMAP_FOREACH(e, s->element_map, i)
516 void idev_session_disable(idev_session *s) {
524 HASHMAP_FOREACH(e, s->element_map, i)
529 static int add_link(idev_element *e, idev_device *d) {
535 l = new0(idev_link, 1);
541 LIST_PREPEND(links_by_element, e->links, l);
542 LIST_PREPEND(links_by_device, d->links, l);
548 static int guess_type(struct udev_device *d) {
551 id_key = udev_device_get_property_value(d, "ID_INPUT_KEY");
552 if (streq_ptr(id_key, "1"))
553 return IDEV_DEVICE_KEYBOARD;
555 return IDEV_DEVICE_CNT;
558 int idev_session_add_evdev(idev_session *s, struct udev_device *ud) {
564 assert_return(s, -EINVAL);
565 assert_return(ud, -EINVAL);
567 devnum = udev_device_get_devnum(ud);
571 e = idev_find_evdev(s, devnum);
575 r = idev_evdev_new(&e, s, ud);
579 r = session_add_element(s, e);
583 type = guess_type(ud);
588 case IDEV_DEVICE_KEYBOARD:
589 d = idev_find_keyboard(s, e->name);
591 log_debug("idev: %s: keyboard for new evdev element '%s' already available",
596 r = idev_keyboard_new(&d, s, e->name);
606 return session_add_device(s, d);
608 /* unknown elements are silently ignored */
613 int idev_session_remove_evdev(idev_session *s, struct udev_device *ud) {
620 devnum = udev_device_get_devnum(ud);
624 e = idev_find_evdev(s, devnum);
628 return session_remove_element(s, e);
635 int idev_context_new(idev_context **out, sd_event *event, sd_bus *sysbus) {
636 _cleanup_(idev_context_unrefp) idev_context *c = NULL;
638 assert_return(out, -EINVAL);
639 assert_return(event, -EINVAL);
641 c = new0(idev_context, 1);
646 c->event = sd_event_ref(event);
649 c->sysbus = sd_bus_ref(sysbus);
651 c->session_map = hashmap_new(string_hash_func, string_compare_func);
655 c->data_map = hashmap_new(string_hash_func, string_compare_func);
664 static void context_cleanup(idev_context *c) {
665 assert(hashmap_size(c->data_map) == 0);
666 assert(hashmap_size(c->session_map) == 0);
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);
675 idev_context *idev_context_ref(idev_context *c) {
676 assert_return(c, NULL);
677 assert_return(c->ref > 0, NULL);
683 idev_context *idev_context_unref(idev_context *c) {
687 assert_return(c->ref > 0, NULL);