chiark / gitweb /
terminal: add input interface
[elogind.git] / src / libsystemd-terminal / idev.c
diff --git a/src/libsystemd-terminal/idev.c b/src/libsystemd-terminal/idev.c
new file mode 100644 (file)
index 0000000..5e30807
--- /dev/null
@@ -0,0 +1,587 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com>
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <systemd/sd-bus.h>
+#include <systemd/sd-event.h>
+#include <systemd/sd-login.h>
+#include "hashmap.h"
+#include "idev.h"
+#include "idev-internal.h"
+#include "login-shared.h"
+#include "macro.h"
+#include "set.h"
+#include "util.h"
+
+static void element_open(idev_element *e);
+static void element_close(idev_element *e);
+
+/*
+ * Devices
+ */
+
+idev_device *idev_find_device(idev_session *s, const char *name) {
+        assert_return(s, NULL);
+        assert_return(name, NULL);
+
+        return hashmap_get(s->device_map, name);
+}
+
+int idev_device_add(idev_device *d, const char *name) {
+        int r;
+
+        assert_return(d, -EINVAL);
+        assert_return(d->vtable, -EINVAL);
+        assert_return(d->session, -EINVAL);
+        assert_return(name, -EINVAL);
+
+        d->name = strdup(name);
+        if (!d->name)
+                return -ENOMEM;
+
+        r = hashmap_put(d->session->device_map, d->name, d);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+idev_device *idev_device_free(idev_device *d) {
+        idev_device tmp;
+
+        if (!d)
+                return NULL;
+
+        assert(!d->enabled);
+        assert(!d->public);
+        assert(!d->links);
+        assert(d->vtable);
+        assert(d->vtable->free);
+
+        if (d->name)
+                hashmap_remove_value(d->session->device_map, d->name, d);
+
+        tmp = *d;
+        d->vtable->free(d);
+
+        free(tmp.name);
+
+        return NULL;
+}
+
+int idev_device_feed(idev_device *d, idev_data *data) {
+        assert(d);
+        assert(data);
+        assert(data->type < IDEV_DATA_CNT);
+
+        if (d->vtable->feed)
+                return d->vtable->feed(d, data);
+        else
+                return 0;
+}
+
+void idev_device_feedback(idev_device *d, idev_data *data) {
+        idev_link *l;
+
+        assert(d);
+        assert(data);
+        assert(data->type < IDEV_DATA_CNT);
+
+        LIST_FOREACH(links_by_device, l, d->links)
+                idev_element_feedback(l->element, data);
+}
+
+static void device_attach(idev_device *d, idev_link *l) {
+        assert(d);
+        assert(l);
+
+        if (d->vtable->attach)
+                d->vtable->attach(d, l);
+
+        if (d->enabled)
+                element_open(l->element);
+}
+
+static void device_detach(idev_device *d, idev_link *l) {
+        assert(d);
+        assert(l);
+
+        if (d->enabled)
+                element_close(l->element);
+
+        if (d->vtable->detach)
+                d->vtable->detach(d, l);
+}
+
+void idev_device_enable(idev_device *d) {
+        idev_link *l;
+
+        assert(d);
+
+        if (!d->enabled) {
+                d->enabled = true;
+                LIST_FOREACH(links_by_device, l, d->links)
+                        element_open(l->element);
+        }
+}
+
+void idev_device_disable(idev_device *d) {
+        idev_link *l;
+
+        assert(d);
+
+        if (d->enabled) {
+                d->enabled = false;
+                LIST_FOREACH(links_by_device, l, d->links)
+                        element_close(l->element);
+        }
+}
+
+/*
+ * Elements
+ */
+
+idev_element *idev_find_element(idev_session *s, const char *name) {
+        assert_return(s, NULL);
+        assert_return(name, NULL);
+
+        return hashmap_get(s->element_map, name);
+}
+
+int idev_element_add(idev_element *e, const char *name) {
+        int r;
+
+        assert_return(e, -EINVAL);
+        assert_return(e->vtable, -EINVAL);
+        assert_return(e->session, -EINVAL);
+        assert_return(name, -EINVAL);
+
+        e->name = strdup(name);
+        if (!e->name)
+                return -ENOMEM;
+
+        r = hashmap_put(e->session->element_map, e->name, e);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+idev_element *idev_element_free(idev_element *e) {
+        idev_element tmp;
+
+        if (!e)
+                return NULL;
+
+        assert(!e->enabled);
+        assert(!e->links);
+        assert(e->n_open == 0);
+        assert(e->vtable);
+        assert(e->vtable->free);
+
+        if (e->name)
+                hashmap_remove_value(e->session->element_map, e->name, e);
+
+        tmp = *e;
+        e->vtable->free(e);
+
+        free(tmp.name);
+
+        return NULL;
+}
+
+int idev_element_feed(idev_element *e, idev_data *data) {
+        int r, error = 0;
+        idev_link *l;
+
+        assert(e);
+        assert(data);
+        assert(data->type < IDEV_DATA_CNT);
+
+        LIST_FOREACH(links_by_element, l, e->links) {
+                r = idev_device_feed(l->device, data);
+                if (r != 0)
+                        error = r;
+        }
+
+        return error;
+}
+
+void idev_element_feedback(idev_element *e, idev_data *data) {
+        assert(e);
+        assert(data);
+        assert(data->type < IDEV_DATA_CNT);
+
+        if (e->vtable->feedback)
+               e->vtable->feedback(e, data);
+}
+
+static void element_open(idev_element *e) {
+        assert(e);
+
+        if (e->n_open++ == 0 && e->vtable->open)
+                e->vtable->open(e);
+}
+
+static void element_close(idev_element *e) {
+        assert(e);
+        assert(e->n_open > 0);
+
+        if (--e->n_open == 0 && e->vtable->close)
+                e->vtable->close(e);
+}
+
+static void element_enable(idev_element *e) {
+        assert(e);
+
+        if (!e->enabled) {
+                e->enabled = true;
+                if (e->vtable->enable)
+                        e->vtable->enable(e);
+        }
+}
+
+static void element_disable(idev_element *e) {
+        assert(e);
+
+        if (e->enabled) {
+                e->enabled = false;
+                if (e->vtable->disable)
+                        e->vtable->disable(e);
+        }
+}
+
+/*
+ * Sessions
+ */
+
+static int session_raise(idev_session *s, idev_event *ev) {
+        return s->event_fn(s, s->userdata, ev);
+}
+
+static int session_raise_device_add(idev_session *s, idev_device *d) {
+        idev_event event = {
+                .type = IDEV_EVENT_DEVICE_ADD,
+                .device_add = {
+                        .device = d,
+                },
+        };
+
+        return session_raise(s, &event);
+}
+
+static int session_raise_device_remove(idev_session *s, idev_device *d) {
+        idev_event event = {
+                .type = IDEV_EVENT_DEVICE_REMOVE,
+                .device_remove = {
+                        .device = d,
+                },
+        };
+
+        return session_raise(s, &event);
+}
+
+int idev_session_raise_device_data(idev_session *s, idev_device *d, idev_data *data) {
+        idev_event event = {
+                .type = IDEV_EVENT_DEVICE_DATA,
+                .device_data = {
+                        .device = d,
+                        .data = *data,
+                },
+        };
+
+        return session_raise(s, &event);
+}
+
+static int session_add_device(idev_session *s, idev_device *d) {
+        int r;
+
+        assert(s);
+        assert(d);
+
+        log_debug("idev: %s: add device '%s'", s->name, d->name);
+
+        d->public = true;
+        r = session_raise_device_add(s, d);
+        if (r != 0) {
+                d->public = false;
+                goto error;
+        }
+
+        return 0;
+
+error:
+        if (r < 0)
+                log_debug("idev: %s: error while adding device '%s': %s",
+                          s->name, d->name, strerror(-r));
+        return r;
+}
+
+static int session_remove_device(idev_session *s, idev_device *d) {
+        int r, error = 0;
+
+        assert(s);
+        assert(d);
+
+        log_debug("idev: %s: remove device '%s'", s->name, d->name);
+
+        d->public = false;
+        r = session_raise_device_remove(s, d);
+        if (r != 0)
+                error = r;
+
+        idev_device_disable(d);
+
+        if (error < 0)
+                log_debug("idev: %s: error while removing device '%s': %s",
+                          s->name, d->name, strerror(-error));
+        idev_device_free(d);
+        return error;
+}
+
+static int session_add_element(idev_session *s, idev_element *e) {
+        assert(s);
+        assert(e);
+
+        log_debug("idev: %s: add element '%s'", s->name, e->name);
+
+        if (s->enabled)
+                element_enable(e);
+
+        return 0;
+}
+
+static int session_remove_element(idev_session *s, idev_element *e) {
+        int r, error = 0;
+        idev_device *d;
+        idev_link *l;
+
+        assert(s);
+        assert(e);
+
+        log_debug("idev: %s: remove element '%s'", s->name, e->name);
+
+        while ((l = e->links)) {
+                d = l->device;
+                LIST_REMOVE(links_by_device, d->links, l);
+                LIST_REMOVE(links_by_element, e->links, l);
+                device_detach(d, l);
+
+                if (!d->links) {
+                        r = session_remove_device(s, d);
+                        if (r != 0)
+                                error = r;
+                }
+
+                l->device = NULL;
+                l->element = NULL;
+                free(l);
+        }
+
+        element_disable(e);
+
+        if (error < 0)
+                log_debug("idev: %s: error while removing element '%s': %s",
+                          s->name, e->name, strerror(-r));
+        idev_element_free(e);
+        return error;
+}
+
+idev_session *idev_find_session(idev_context *c, const char *name) {
+        assert_return(c, NULL);
+        assert_return(name, NULL);
+
+        return hashmap_get(c->session_map, name);
+}
+
+int idev_session_new(idev_session **out,
+                     idev_context *c,
+                     unsigned int flags,
+                     const char *name,
+                     idev_event_fn event_fn,
+                     void *userdata) {
+        _cleanup_(idev_session_freep) idev_session *s = NULL;
+        int r;
+
+        assert_return(out, -EINVAL);
+        assert_return(c, -EINVAL);
+        assert_return(name, -EINVAL);
+        assert_return(event_fn, -EINVAL);
+        assert_return((flags & IDEV_SESSION_CUSTOM) == !session_id_valid(name), -EINVAL);
+        assert_return(!(flags & IDEV_SESSION_CUSTOM) || !(flags & IDEV_SESSION_MANAGED), -EINVAL);
+        assert_return(!(flags & IDEV_SESSION_MANAGED) || c->sysbus, -EINVAL);
+
+        s = new0(idev_session, 1);
+        if (!s)
+                return -ENOMEM;
+
+        s->context = idev_context_ref(c);
+        s->custom = flags & IDEV_SESSION_CUSTOM;
+        s->managed = flags & IDEV_SESSION_MANAGED;
+        s->event_fn = event_fn;
+        s->userdata = userdata;
+
+        s->name = strdup(name);
+        if (!s->name)
+                return -ENOMEM;
+
+        if (s->managed) {
+                r = sd_bus_path_encode("/org/freedesktop/login1/session", s->name, &s->path);
+                if (r < 0)
+                        return r;
+        }
+
+        s->element_map = hashmap_new(string_hash_func, string_compare_func);
+        if (!s->element_map)
+                return -ENOMEM;
+
+        s->device_map = hashmap_new(string_hash_func, string_compare_func);
+        if (!s->device_map)
+                return -ENOMEM;
+
+        r = hashmap_put(c->session_map, s->name, s);
+        if (r < 0)
+                return r;
+
+        *out = s;
+        s = NULL;
+        return 0;
+}
+
+idev_session *idev_session_free(idev_session *s) {
+        idev_element *e;
+
+        if (!s)
+                return NULL;
+
+        while ((e = hashmap_first(s->element_map)))
+                session_remove_element(s, e);
+
+        assert(hashmap_size(s->device_map) == 0);
+
+        if (s->name)
+                hashmap_remove_value(s->context->session_map, s->name, s);
+
+        s->context = idev_context_unref(s->context);
+        hashmap_free(s->device_map);
+        hashmap_free(s->element_map);
+        free(s->path);
+        free(s->name);
+        free(s);
+
+        return NULL;
+}
+
+bool idev_session_is_enabled(idev_session *s) {
+        return s && s->enabled;
+}
+
+void idev_session_enable(idev_session *s) {
+        idev_element *e;
+        Iterator i;
+
+        assert(s);
+
+        if (!s->enabled) {
+                s->enabled = true;
+                HASHMAP_FOREACH(e, s->element_map, i)
+                        element_enable(e);
+        }
+}
+
+void idev_session_disable(idev_session *s) {
+        idev_element *e;
+        Iterator i;
+
+        assert(s);
+
+        if (s->enabled) {
+                s->enabled = false;
+                HASHMAP_FOREACH(e, s->element_map, i)
+                        element_disable(e);
+        }
+}
+
+/*
+ * Contexts
+ */
+
+int idev_context_new(idev_context **out, sd_event *event, sd_bus *sysbus) {
+        _cleanup_(idev_context_unrefp) idev_context *c = NULL;
+
+        assert_return(out, -EINVAL);
+        assert_return(event, -EINVAL);
+
+        c = new0(idev_context, 1);
+        if (!c)
+                return -ENOMEM;
+
+        c->ref = 1;
+        c->event = sd_event_ref(event);
+
+        if (sysbus)
+                c->sysbus = sd_bus_ref(sysbus);
+
+        c->session_map = hashmap_new(string_hash_func, string_compare_func);
+        if (!c->session_map)
+                return -ENOMEM;
+
+        c->data_map = hashmap_new(string_hash_func, string_compare_func);
+        if (!c->data_map)
+                return -ENOMEM;
+
+        *out = c;
+        c = NULL;
+        return 0;
+}
+
+static void context_cleanup(idev_context *c) {
+        assert(hashmap_size(c->data_map) == 0);
+        assert(hashmap_size(c->session_map) == 0);
+
+        hashmap_free(c->data_map);
+        hashmap_free(c->session_map);
+        c->sysbus = sd_bus_unref(c->sysbus);
+        c->event = sd_event_unref(c->event);
+        free(c);
+}
+
+idev_context *idev_context_ref(idev_context *c) {
+        assert_return(c, NULL);
+        assert_return(c->ref > 0, NULL);
+
+        ++c->ref;
+        return c;
+}
+
+idev_context *idev_context_unref(idev_context *c) {
+        if (!c)
+                return NULL;
+
+        assert_return(c->ref > 0, NULL);
+
+        if (--c->ref == 0)
+                context_cleanup(c);
+
+        return NULL;
+}