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);
281 static int session_raise(idev_session *s, idev_event *ev) {
282 return s->event_fn(s, s->userdata, ev);
285 static int session_raise_device_add(idev_session *s, idev_device *d) {
287 .type = IDEV_EVENT_DEVICE_ADD,
293 return session_raise(s, &event);
296 static int session_raise_device_remove(idev_session *s, idev_device *d) {
298 .type = IDEV_EVENT_DEVICE_REMOVE,
304 return session_raise(s, &event);
307 int idev_session_raise_device_data(idev_session *s, idev_device *d, idev_data *data) {
309 .type = IDEV_EVENT_DEVICE_DATA,
316 return session_raise(s, &event);
319 static int session_add_device(idev_session *s, idev_device *d) {
325 log_debug("idev: %s: add device '%s'", s->name, d->name);
328 r = session_raise_device_add(s, d);
338 log_debug("idev: %s: error while adding device '%s': %s",
339 s->name, d->name, strerror(-r));
343 static int session_remove_device(idev_session *s, idev_device *d) {
349 log_debug("idev: %s: remove device '%s'", s->name, d->name);
352 r = session_raise_device_remove(s, d);
356 idev_device_disable(d);
359 log_debug("idev: %s: error while removing device '%s': %s",
360 s->name, d->name, strerror(-error));
365 static int session_add_element(idev_session *s, idev_element *e) {
369 log_debug("idev: %s: add element '%s'", s->name, e->name);
377 static int session_remove_element(idev_session *s, idev_element *e) {
385 log_debug("idev: %s: remove element '%s'", s->name, e->name);
387 while ((l = e->links)) {
389 LIST_REMOVE(links_by_device, d->links, l);
390 LIST_REMOVE(links_by_element, e->links, l);
394 r = session_remove_device(s, d);
407 log_debug("idev: %s: error while removing element '%s': %s",
408 s->name, e->name, strerror(-r));
409 idev_element_free(e);
413 idev_session *idev_find_session(idev_context *c, const char *name) {
414 assert_return(c, NULL);
415 assert_return(name, NULL);
417 return hashmap_get(c->session_map, name);
420 int idev_session_new(idev_session **out,
424 idev_event_fn event_fn,
426 _cleanup_(idev_session_freep) idev_session *s = NULL;
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);
437 s = new0(idev_session, 1);
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;
447 s->name = strdup(name);
452 r = sd_bus_path_encode("/org/freedesktop/login1/session", s->name, &s->path);
457 s->element_map = hashmap_new(string_hash_func, string_compare_func);
461 s->device_map = hashmap_new(string_hash_func, string_compare_func);
465 r = hashmap_put(c->session_map, s->name, s);
474 idev_session *idev_session_free(idev_session *s) {
480 while ((e = hashmap_first(s->element_map)))
481 session_remove_element(s, e);
483 assert(hashmap_size(s->device_map) == 0);
486 hashmap_remove_value(s->context->session_map, s->name, s);
488 s->context = idev_context_unref(s->context);
489 hashmap_free(s->device_map);
490 hashmap_free(s->element_map);
498 bool idev_session_is_enabled(idev_session *s) {
499 return s && s->enabled;
502 void idev_session_enable(idev_session *s) {
510 HASHMAP_FOREACH(e, s->element_map, i)
515 void idev_session_disable(idev_session *s) {
523 HASHMAP_FOREACH(e, s->element_map, i)
528 static int add_link(idev_element *e, idev_device *d) {
534 l = new0(idev_link, 1);
540 LIST_PREPEND(links_by_element, e->links, l);
541 LIST_PREPEND(links_by_device, d->links, l);
547 static int guess_type(struct udev_device *d) {
550 id_key = udev_device_get_property_value(d, "ID_INPUT_KEY");
551 if (streq_ptr(id_key, "1"))
552 return IDEV_DEVICE_KEYBOARD;
554 return IDEV_DEVICE_CNT;
557 int idev_session_add_evdev(idev_session *s, struct udev_device *ud) {
563 assert_return(s, -EINVAL);
564 assert_return(ud, -EINVAL);
566 devnum = udev_device_get_devnum(ud);
570 e = idev_find_evdev(s, devnum);
574 r = idev_evdev_new(&e, s, ud);
578 r = session_add_element(s, e);
582 type = guess_type(ud);
587 case IDEV_DEVICE_KEYBOARD:
588 d = idev_find_keyboard(s, e->name);
590 log_debug("idev: %s: keyboard for new evdev element '%s' already available",
595 r = idev_keyboard_new(&d, s, e->name);
605 return session_add_device(s, d);
607 /* unknown elements are silently ignored */
612 int idev_session_remove_evdev(idev_session *s, struct udev_device *ud) {
619 devnum = udev_device_get_devnum(ud);
623 e = idev_find_evdev(s, devnum);
627 return session_remove_element(s, e);
634 int idev_context_new(idev_context **out, sd_event *event, sd_bus *sysbus) {
635 _cleanup_(idev_context_unrefp) idev_context *c = NULL;
637 assert_return(out, -EINVAL);
638 assert_return(event, -EINVAL);
640 c = new0(idev_context, 1);
645 c->event = sd_event_ref(event);
648 c->sysbus = sd_bus_ref(sysbus);
650 c->session_map = hashmap_new(string_hash_func, string_compare_func);
654 c->data_map = hashmap_new(string_hash_func, string_compare_func);
663 static void context_cleanup(idev_context *c) {
664 assert(hashmap_size(c->data_map) == 0);
665 assert(hashmap_size(c->session_map) == 0);
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);
674 idev_context *idev_context_ref(idev_context *c) {
675 assert_return(c, NULL);
676 assert_return(c->ref > 0, NULL);
682 idev_context *idev_context_unref(idev_context *c) {
686 assert_return(c->ref > 0, NULL);