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/>.
25 #include <systemd/sd-bus.h>
26 #include <systemd/sd-event.h>
29 #include "idev-internal.h"
30 #include "login-shared.h"
34 static void element_open(idev_element *e);
35 static void element_close(idev_element *e);
41 idev_device *idev_find_device(idev_session *s, const char *name) {
42 assert_return(s, NULL);
43 assert_return(name, NULL);
45 return hashmap_get(s->device_map, name);
48 int idev_device_add(idev_device *d, const char *name) {
51 assert_return(d, -EINVAL);
52 assert_return(d->vtable, -EINVAL);
53 assert_return(d->session, -EINVAL);
54 assert_return(name, -EINVAL);
56 d->name = strdup(name);
60 r = hashmap_put(d->session->device_map, d->name, d);
67 idev_device *idev_device_free(idev_device *d) {
77 assert(d->vtable->free);
80 hashmap_remove_value(d->session->device_map, d->name, d);
90 int idev_device_feed(idev_device *d, idev_data *data) {
93 assert(data->type < IDEV_DATA_CNT);
96 return d->vtable->feed(d, data);
101 void idev_device_feedback(idev_device *d, idev_data *data) {
106 assert(data->type < IDEV_DATA_CNT);
108 LIST_FOREACH(links_by_device, l, d->links)
109 idev_element_feedback(l->element, data);
112 static void device_attach(idev_device *d, idev_link *l) {
116 if (d->vtable->attach)
117 d->vtable->attach(d, l);
120 element_open(l->element);
123 static void device_detach(idev_device *d, idev_link *l) {
128 element_close(l->element);
130 if (d->vtable->detach)
131 d->vtable->detach(d, l);
134 void idev_device_enable(idev_device *d) {
141 LIST_FOREACH(links_by_device, l, d->links)
142 element_open(l->element);
146 void idev_device_disable(idev_device *d) {
153 LIST_FOREACH(links_by_device, l, d->links)
154 element_close(l->element);
162 idev_element *idev_find_element(idev_session *s, const char *name) {
163 assert_return(s, NULL);
164 assert_return(name, NULL);
166 return hashmap_get(s->element_map, name);
169 int idev_element_add(idev_element *e, const char *name) {
172 assert_return(e, -EINVAL);
173 assert_return(e->vtable, -EINVAL);
174 assert_return(e->session, -EINVAL);
175 assert_return(name, -EINVAL);
177 e->name = strdup(name);
181 r = hashmap_put(e->session->element_map, e->name, e);
188 idev_element *idev_element_free(idev_element *e) {
196 assert(e->n_open == 0);
198 assert(e->vtable->free);
201 hashmap_remove_value(e->session->element_map, e->name, e);
211 int idev_element_feed(idev_element *e, idev_data *data) {
217 assert(data->type < IDEV_DATA_CNT);
219 LIST_FOREACH(links_by_element, l, e->links) {
220 r = idev_device_feed(l->device, data);
228 void idev_element_feedback(idev_element *e, idev_data *data) {
231 assert(data->type < IDEV_DATA_CNT);
233 if (e->vtable->feedback)
234 e->vtable->feedback(e, data);
237 static void element_open(idev_element *e) {
240 if (e->n_open++ == 0 && e->vtable->open)
244 static void element_close(idev_element *e) {
246 assert(e->n_open > 0);
248 if (--e->n_open == 0 && e->vtable->close)
252 static void element_enable(idev_element *e) {
257 if (e->vtable->enable)
258 e->vtable->enable(e);
262 static void element_disable(idev_element *e) {
267 if (e->vtable->disable)
268 e->vtable->disable(e);
272 static void element_resume(idev_element *e, int fd) {
276 if (e->vtable->resume)
277 e->vtable->resume(e, fd);
280 static void element_pause(idev_element *e, const char *mode) {
284 if (e->vtable->pause)
285 e->vtable->pause(e, mode);
292 static int session_raise(idev_session *s, idev_event *ev) {
293 return s->event_fn(s, s->userdata, ev);
296 static int session_raise_device_add(idev_session *s, idev_device *d) {
298 .type = IDEV_EVENT_DEVICE_ADD,
304 return session_raise(s, &event);
307 static int session_raise_device_remove(idev_session *s, idev_device *d) {
309 .type = IDEV_EVENT_DEVICE_REMOVE,
315 return session_raise(s, &event);
318 int idev_session_raise_device_data(idev_session *s, idev_device *d, idev_data *data) {
320 .type = IDEV_EVENT_DEVICE_DATA,
327 return session_raise(s, &event);
330 static int session_add_device(idev_session *s, idev_device *d) {
336 log_debug("idev: %s: add device '%s'", s->name, d->name);
339 r = session_raise_device_add(s, d);
349 log_debug_errno(r, "idev: %s: error while adding device '%s': %m",
354 static int session_remove_device(idev_session *s, idev_device *d) {
360 log_debug("idev: %s: remove device '%s'", s->name, d->name);
363 r = session_raise_device_remove(s, d);
367 idev_device_disable(d);
370 log_debug_errno(error, "idev: %s: error while removing device '%s': %m",
376 static int session_add_element(idev_session *s, idev_element *e) {
380 log_debug("idev: %s: add element '%s'", s->name, e->name);
388 static int session_remove_element(idev_session *s, idev_element *e) {
396 log_debug("idev: %s: remove element '%s'", s->name, e->name);
398 while ((l = e->links)) {
400 LIST_REMOVE(links_by_device, d->links, l);
401 LIST_REMOVE(links_by_element, e->links, l);
405 r = session_remove_device(s, d);
418 log_debug_errno(r, "idev: %s: error while removing element '%s': %m",
420 idev_element_free(e);
424 idev_session *idev_find_session(idev_context *c, const char *name) {
425 assert_return(c, NULL);
426 assert_return(name, NULL);
428 return hashmap_get(c->session_map, name);
431 static int session_resume_device_fn(sd_bus *bus,
432 sd_bus_message *signal,
434 sd_bus_error *ret_error) {
435 idev_session *s = userdata;
437 uint32_t major, minor;
440 r = sd_bus_message_read(signal, "uuh", &major, &minor, &fd);
442 log_debug("idev: %s: erroneous ResumeDevice signal", s->name);
446 e = idev_find_evdev(s, makedev(major, minor));
450 element_resume(e, fd);
454 static int session_pause_device_fn(sd_bus *bus,
455 sd_bus_message *signal,
457 sd_bus_error *ret_error) {
458 idev_session *s = userdata;
460 uint32_t major, minor;
464 r = sd_bus_message_read(signal, "uus", &major, &minor, &mode);
466 log_debug("idev: %s: erroneous PauseDevice signal", s->name);
470 e = idev_find_evdev(s, makedev(major, minor));
474 element_pause(e, mode);
478 static int session_setup_bus(idev_session *s) {
479 _cleanup_free_ char *match = NULL;
485 match = strjoin("type='signal',"
486 "sender='org.freedesktop.login1',"
487 "interface='org.freedesktop.login1.Session',"
488 "member='ResumeDevice',"
489 "path='", s->path, "'",
494 r = sd_bus_add_match(s->context->sysbus,
495 &s->slot_resume_device,
497 session_resume_device_fn,
503 match = strjoin("type='signal',"
504 "sender='org.freedesktop.login1',"
505 "interface='org.freedesktop.login1.Session',"
506 "member='PauseDevice',"
507 "path='", s->path, "'",
512 r = sd_bus_add_match(s->context->sysbus,
513 &s->slot_pause_device,
515 session_pause_device_fn,
523 int idev_session_new(idev_session **out,
527 idev_event_fn event_fn,
529 _cleanup_(idev_session_freep) idev_session *s = NULL;
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);
540 s = new0(idev_session, 1);
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;
550 s->name = strdup(name);
555 r = sd_bus_path_encode("/org/freedesktop/login1/session", s->name, &s->path);
560 s->element_map = hashmap_new(&string_hash_ops);
564 s->device_map = hashmap_new(&string_hash_ops);
568 r = session_setup_bus(s);
572 r = hashmap_put(c->session_map, s->name, s);
581 idev_session *idev_session_free(idev_session *s) {
587 while ((e = hashmap_first(s->element_map)))
588 session_remove_element(s, e);
590 assert(hashmap_size(s->device_map) == 0);
593 hashmap_remove_value(s->context->session_map, s->name, s);
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);
607 bool idev_session_is_enabled(idev_session *s) {
608 return s && s->enabled;
611 void idev_session_enable(idev_session *s) {
619 HASHMAP_FOREACH(e, s->element_map, i)
624 void idev_session_disable(idev_session *s) {
632 HASHMAP_FOREACH(e, s->element_map, i)
637 static int add_link(idev_element *e, idev_device *d) {
643 l = new0(idev_link, 1);
649 LIST_PREPEND(links_by_element, e->links, l);
650 LIST_PREPEND(links_by_device, d->links, l);
656 static int guess_type(struct udev_device *d) {
659 id_key = udev_device_get_property_value(d, "ID_INPUT_KEY");
660 if (streq_ptr(id_key, "1"))
661 return IDEV_DEVICE_KEYBOARD;
663 return IDEV_DEVICE_CNT;
666 int idev_session_add_evdev(idev_session *s, struct udev_device *ud) {
672 assert_return(s, -EINVAL);
673 assert_return(ud, -EINVAL);
675 devnum = udev_device_get_devnum(ud);
679 e = idev_find_evdev(s, devnum);
683 r = idev_evdev_new(&e, s, ud);
687 r = session_add_element(s, e);
691 type = guess_type(ud);
696 case IDEV_DEVICE_KEYBOARD:
697 d = idev_find_keyboard(s, e->name);
699 log_debug("idev: %s: keyboard for new evdev element '%s' already available",
704 r = idev_keyboard_new(&d, s, e->name);
714 return session_add_device(s, d);
716 /* unknown elements are silently ignored */
721 int idev_session_remove_evdev(idev_session *s, struct udev_device *ud) {
728 devnum = udev_device_get_devnum(ud);
732 e = idev_find_evdev(s, devnum);
736 return session_remove_element(s, e);
743 int idev_context_new(idev_context **out, sd_event *event, sd_bus *sysbus) {
744 _cleanup_(idev_context_unrefp) idev_context *c = NULL;
746 assert_return(out, -EINVAL);
747 assert_return(event, -EINVAL);
749 c = new0(idev_context, 1);
754 c->event = sd_event_ref(event);
757 c->sysbus = sd_bus_ref(sysbus);
759 c->session_map = hashmap_new(&string_hash_ops);
763 c->data_map = hashmap_new(&string_hash_ops);
772 static void context_cleanup(idev_context *c) {
773 assert(hashmap_size(c->data_map) == 0);
774 assert(hashmap_size(c->session_map) == 0);
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);
783 idev_context *idev_context_ref(idev_context *c) {
784 assert_return(c, NULL);
785 assert_return(c->ref > 0, NULL);
791 idev_context *idev_context_unref(idev_context *c) {
795 assert_return(c->ref > 0, NULL);