[systemd-devel] [RFC 08/12] gfx: add monitor

David Herrmann dh.herrmann at gmail.com
Wed Nov 27 10:48:43 PST 2013


While all previous sd-gfx interfaces are self-contained and can be used
directly on selected devices, this adds an interface to connect them all
together. The sd_gfx_monitor can be used to monitor a session for device
hotplugging and other device events. It is optional but is supposed to be
the foundation of all systemd-helpers that use sd-gfx.

The main function of sd_gfx_monitor is to watch the system for udev-events
of gpus and keyboards. For each of them, an sd_gfx_card or sd_gfx_kbd
device is created and advertised to the application. Furthermore,
systemd-localed integration is provided so keymap changes are immediately
noticed and applied to active sd_gfx_kbd devices.

An sd_gfx_monitor can run in two modes:
 - system mode: In system mode, no dbus, no logind and localed are assumed
                to be running and seat-information is ignored. This mode
                allows to run applications in initrds or emergency
                situations. It simply takes all devices it can find and
                tries to use them. However, this obviously requires to be
                root, otherwise, devices cannot be accessed.
 - session mode: In session mode, the monitor assumes to be running in an
                 logind session. If not, it returns an error. The monitor
                 will call TakeControl on the active session and get
                 device-access via logind. Only devices attached to the
                 session will be used and no you're not required to be
                 root. The caller is responsible of setting up the session
                 before spawning the monitor.

Note that monitor setup is a blocking call as it is usually called during
application setup (and making that async would have no gain). But at
runtime, the monitor runs all dbus-calls and other calls asynchronously.

The sd_gfx_monitor interface is designed for fallbacks and basic system
applications. It does not allow per-device configurations or any advanced
eye-candy. It is trimmed for usability and simplicity, and it is optimized
for fallback/emergency situations. Thus, the monitor provides some basic
configuration options via the kernel command-line. systemd.gpus= allows to
filter the GPUs to be used (by default, *all* connected GPUs are used
together). systemd.keymap= allows to change the keymap in case localed is
configured incorrectly.

The sd_gfx_monitor interfaces has the nice side-effect that all
applications using it will share the same configuration. They will have
the same monitor-setup, the same keymap setup and use the same devices. So
if you system-boot fails, you can set systemd.XY="" to boot with a working
configuration and all systemd-internal applications will just work.

If we ever export sd-gfx, most users probably want more configurability
(like per-device keymaps) and thus will not use sd_gfx_monitor. However,
for all fallbacks, it is the perfect base.
---
 Makefile.am                      |    3 +
 src/libsystemd-gfx/gfx-monitor.c | 1767 ++++++++++++++++++++++++++++++++++++++
 src/systemd/sd-gfx.h             |   61 ++
 3 files changed, 1831 insertions(+)
 create mode 100644 src/libsystemd-gfx/gfx-monitor.c

diff --git a/Makefile.am b/Makefile.am
index 70148c5..189cc89 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3861,6 +3861,7 @@ libsystemd_gfx_la_SOURCES = \
 	src/libsystemd-gfx/sd-gfx.h \
 	src/libsystemd-gfx/gfx-drm.c \
 	src/libsystemd-gfx/gfx-kbd.c \
+	src/libsystemd-gfx/gfx-monitor.c \
 	src/libsystemd-gfx/gfx-unifont.c
 
 libsystemd_gfx_la_CFLAGS = \
@@ -3872,7 +3873,9 @@ libsystemd_gfx_la_LIBADD = \
 	libsystemd-bus-internal.la \
 	libsystemd-daemon-internal.la \
 	libsystemd-id128-internal.la \
+	libsystemd-login-internal.la \
 	libsystemd-shared.la \
+	libudev-internal.la \
 	src/libsystemd-gfx/unifont.bin.lo
 
 src/libsystemd-gfx/unifont.bin: make-unifont.py src/libsystemd-gfx/unifont.hex
diff --git a/src/libsystemd-gfx/gfx-monitor.c b/src/libsystemd-gfx/gfx-monitor.c
new file mode 100644
index 0000000..0fdf230
--- /dev/null
+++ b/src/libsystemd-gfx/gfx-monitor.c
@@ -0,0 +1,1767 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2013 David Herrmann <dh.herrmann at 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 <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <libudev.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+#include <xkbcommon/xkbcommon.h>
+
+#include "bus-error.h"
+#include "bus-util.h"
+#include "def.h"
+#include "hashmap.h"
+#include "log.h"
+#include "macro.h"
+#include "missing.h"
+#include "sd-bus.h"
+#include "sd-event.h"
+#include "sd-gfx.h"
+#include "sd-login.h"
+#include "strv.h"
+#include "util.h"
+
+typedef struct gfx_device gfx_device;
+
+struct gfx_device {
+        sd_gfx_monitor *mon;
+        struct udev_device *dev;
+        unsigned int devtype;
+
+        dev_t devt;
+        uint64_t logind_open_req;
+
+        unsigned int public : 1;
+
+        union {
+                sd_gfx_card *card;
+                sd_gfx_kbd *kbd;
+        };
+};
+
+struct sd_gfx_monitor {
+        unsigned int devmask;
+        unsigned int flags;
+        sd_event *event;
+        sd_bus *bus;
+        sd_gfx_monitor_event_fn event_fn;
+        void *fn_data;
+        char *sid;
+        char *seat;
+        char *spath;
+        unsigned int vt;
+
+        struct udev *udev;
+        struct udev_monitor *umon;
+        sd_event_source *umon_source;
+
+        Hashmap *devices;
+        Hashmap *devices_by_devt;
+        struct xkb_context *xkb;
+        struct xkb_keymap *keymap;
+
+        char **conf_gpus;
+        char *xkb_model;
+        char *xkb_layout;
+        char *xkb_variant;
+        char *xkb_options;
+        uint64_t localed_req;
+
+        unsigned int logind_active;
+        uint64_t logind_active_req;
+
+        unsigned int seat0 : 1;
+        unsigned int conf_gpus_boot : 1;
+        unsigned int conf_gpus_aux : 1;
+        unsigned int active : 1;
+};
+
+static int gfx_logind_take_device(gfx_device *dev);
+
+static unsigned devt_hash_func(const void *p) {
+        uint64_t u = *(const dev_t*)p;
+
+        return uint64_hash_func(&u);
+}
+
+static int devt_compare_func(const void *_a, const void *_b) {
+        dev_t a, b;
+
+        a = *(const dev_t*) _a;
+        b = *(const dev_t*) _b;
+
+        return a < b ? -1 : (a > b ? 1 : 0);
+}
+
+/*
+ * Device Detection
+ */
+
+static bool gfx_match_seat(struct udev_device *d, const char *want_seat, const char *s) {
+        const char *seat;
+
+        /* NULL means wild-card match */
+        if (!want_seat)
+                return true;
+
+        seat = udev_device_get_property_value(d, "ID_SEAT");
+        if (!seat)
+                seat = "seat0";
+
+        if (!streq(seat, want_seat)) {
+                log_debug("gfx: no seat match, ignore device %s", s);
+                return false;
+        }
+
+        return true;
+}
+
+static bool gfx_match_keyboard(struct udev_device *d, const char *s) {
+        const char *v;
+
+        /* We should really map against ID_INPUT_KEYBOARD instead. But to
+         * provide compatibility to the kernel VT layer, we bind to all
+         * EV_KEY devices here. */
+        v = udev_device_get_property_value(d, "ID_INPUT_KEY");
+        if (streq_ptr(v, "1"))
+                return true;
+
+        log_debug("gfx: ignore non-keyboard device %s", s);
+        return false;
+}
+
+static bool gfx_device_match_gpu(gfx_device *dev) {
+        const char *drv = NULL, *boot = NULL, *id_path = NULL;
+        const char *id_path_tag = NULL, *sname, *c;
+        struct udev_device *p;
+        unsigned int i;
+        sd_gfx_monitor *mon = dev->mon;
+
+        if (!mon->conf_gpus)
+                return true;
+
+        sname = udev_device_get_sysname(dev->dev);
+        id_path = udev_device_get_property_value(dev->dev, "ID_PATH");
+        id_path_tag = udev_device_get_property_value(dev->dev, "ID_PATH_TAG");
+
+        p = udev_device_get_parent(dev->dev);
+        if (p) {
+                drv = udev_device_get_driver(p);
+                boot = udev_device_get_sysattr_value(p, "boot_vga");
+        }
+
+        if (mon->conf_gpus_boot && streq_ptr(boot, "1"))
+                return true;
+
+        if (mon->conf_gpus_aux && drv) {
+                if (streq(drv, "udl"))
+                        return true;
+        }
+
+        for (i = 0; mon->conf_gpus[i]; ++i) {
+                c = mon->conf_gpus[i];
+
+                /* match driver */
+                if (streq_ptr(c, drv))
+                        return true;
+
+                /* match ID_PATH[_TAG] */
+                if (streq_ptr(c, id_path) || streq_ptr(c, id_path_tag))
+                        return true;
+
+                /* match sysname */
+                if (streq_ptr(c, sname))
+                        return true;
+        }
+
+        return false;
+}
+
+static unsigned int gfx_device_detect(gfx_device *dev) {
+        const char *s, *sub, *sname, *node;
+        struct udev_device *p;
+
+        s = udev_device_get_syspath(dev->dev);
+        sub = udev_device_get_subsystem(dev->dev);
+        sname = udev_device_get_sysname(dev->dev);
+        node = udev_device_get_devnode(dev->dev);
+        if (!sub || !sname || !node)
+                goto unknown;
+
+        if (streq(sub, "drm")) {
+                if (!gfx_match_seat(dev->dev, dev->mon->seat, s)) {
+                        goto out;
+                } else if (strchr(node, '-') || !startswith(sname, "card")) {
+                        log_debug("gfx: ignore secondary DRM node %s", s);
+                        goto out;
+                } else if (!gfx_device_match_gpu(dev)) {
+                        log_debug("gfx: no GPU match, ignore DRM device %s", s);
+                        goto out;
+                }
+
+                return SD_GFX_DEV_CARD;
+        } else if (streq(sub, "input")) {
+                if (!startswith(sname, "event")) {
+                        log_debug("gfx: ignore non-evdev input device %s", s);
+                        goto out;
+                }
+
+                p = udev_device_get_parent_with_subsystem_devtype(dev->dev, "input", NULL);
+                if (!p)
+                        goto unknown;
+
+                if (!gfx_match_seat(p, dev->mon->seat, s))
+                        goto out;
+                if (!gfx_match_keyboard(p, s))
+                        goto out;
+
+                return SD_GFX_DEV_KBD;
+        }
+
+unknown:
+        log_debug("gfx: ignore unknown device %s", s);
+out:
+        return SD_GFX_DEV_UNKNOWN;
+}
+
+/*
+ * Card Devices
+ */
+
+static void gfx_device_notify_card(gfx_device *dev, unsigned int type) {
+        sd_gfx_monitor_event ev;
+
+        if (!dev->public)
+                return;
+
+        memset(&ev, 0, sizeof(ev));
+        ev.type = type;
+        ev.devtype = SD_GFX_DEV_CARD;
+        ev.card = dev->card;
+        if (dev->mon->event_fn)
+                dev->mon->event_fn(dev->mon, dev->mon->fn_data, &ev);
+}
+
+static void gfx_device_resume_card(gfx_device *dev, int fd) {
+        /* we don't care for new fds for DRM devices; keep the old one */
+        if (fd > 0)
+                close_nointr(fd);
+        sd_gfx_card_wake_up(dev->card);
+}
+
+static void gfx_device_pause_card(gfx_device *dev) {
+        sd_gfx_card_sleep(dev->card);
+}
+
+static int gfx_device_create_card(gfx_device *dev, int fd, bool paused) {
+        int r;
+
+        if (dev->mon->flags & SD_GFX_MONITOR_IGNORE_SEATS) {
+                /* if we run un-seated, make sure we're master */
+                r = drmSetMaster(fd);
+                if (r < 0) {
+                        log_error("gfx: cannot become DRM-Master on %s: %m",
+                                  udev_device_get_syspath(dev->dev));
+                        goto error;
+                }
+        }
+
+        r = sd_gfx_card_new(&dev->card,
+                            udev_device_get_sysname(dev->dev),
+                            fd,
+                            dev->mon->event);
+        if (r < 0)
+                goto error;
+
+        dev->public = 1;
+        gfx_device_notify_card(dev, SD_GFX_MONITOR_CREATE);
+
+        if (!paused)
+                gfx_device_resume_card(dev, -1);
+
+        return 0;
+
+error:
+        close_nointr(fd);
+        return r;
+}
+
+static int gfx_device_new_card(gfx_device *dev) {
+        int r, fd;
+        const char *node;
+
+        if (dev->mon->sid) {
+                r = gfx_logind_take_device(dev);
+                if (r < 0)
+                        return r;
+        } else {
+                node = udev_device_get_devnode(dev->dev);
+                fd = open(node, O_RDWR | O_CLOEXEC | O_NONBLOCK);
+                if (fd < 0) {
+                        log_error("gfx: cannot open %s: %m", node);
+                        return -errno;
+                }
+
+                r = gfx_device_create_card(dev, fd, false);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
+static void gfx_device_free_card(gfx_device *dev) {
+        gfx_device_notify_card(dev, SD_GFX_MONITOR_DESTROY);
+
+        if (dev->card) {
+                /* If we run on a VT and our session is currently active *and* our
+                 * device is currently awake, we should try to restore the old FB
+                 * of the VT layer.
+                 * Note that this actually puts the card asleep! */
+                if (dev->mon->vt && dev->mon->active && sd_gfx_card_is_awake(dev->card))
+                        sd_gfx_card_restore(dev->card);
+
+                sd_gfx_card_sleep(dev->card);
+                sd_gfx_card_free(dev->card);
+        }
+}
+
+/*
+ * KBD Devices
+ */
+
+static void gfx_device_notify_kbd(gfx_device *dev, unsigned int type) {
+        sd_gfx_monitor_event ev;
+
+        if (!dev->public)
+                return;
+
+        memset(&ev, 0, sizeof(ev));
+        ev.type = type;
+        ev.devtype = SD_GFX_DEV_KBD;
+        ev.kbd = dev->kbd;
+        if (dev->mon->event_fn)
+                dev->mon->event_fn(dev->mon, dev->mon->fn_data, &ev);
+}
+
+static void gfx_device_resume_kbd(gfx_device *dev, int fd) {
+        sd_gfx_kbd_wake_up(dev->kbd, fd);
+}
+
+static void gfx_device_pause_kbd(gfx_device *dev) {
+        sd_gfx_kbd_sleep(dev->kbd);
+}
+
+static int gfx_device_create_kbd(gfx_device *dev, int fd, bool paused) {
+        struct xkb_state *state = NULL;
+        int r;
+
+        state = xkb_state_new(dev->mon->keymap);
+        if (!state) {
+                r = log_oom();
+                goto error;
+        }
+
+        r = sd_gfx_kbd_new(&dev->kbd,
+                           udev_device_get_sysname(dev->dev),
+                           state,
+                           dev->mon->event);
+        if (r < 0)
+                goto error;
+
+        dev->public = 1;
+        gfx_device_notify_kbd(dev, SD_GFX_MONITOR_CREATE);
+
+        if (!paused)
+                gfx_device_resume_kbd(dev, fd);
+        else
+                close_nointr(fd);
+
+        xkb_state_unref(state);
+        return 0;
+
+error:
+        xkb_state_unref(state);
+        close_nointr(fd);
+        return r;
+}
+
+static int gfx_device_new_kbd(gfx_device *dev) {
+        int r, fd = -1;
+        const char *node;
+
+        if (!dev->mon->keymap)
+                return -EINVAL;
+
+        if (dev->mon->sid) {
+                r = gfx_logind_take_device(dev);
+                if (r < 0)
+                        return r;
+        } else {
+                node = udev_device_get_devnode(dev->dev);
+                fd = open(node, O_RDWR | O_CLOEXEC | O_NONBLOCK);
+                if (fd < 0) {
+                        log_error("gfx: cannot open %s: %m", node);
+                        return -errno;
+                }
+
+                r = gfx_device_create_kbd(dev, fd, false);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
+static void gfx_device_free_kbd(gfx_device *dev) {
+        gfx_device_notify_kbd(dev, SD_GFX_MONITOR_DESTROY);
+        if (dev->kbd)
+                sd_gfx_kbd_free(dev->kbd);
+}
+
+/*
+ * Logind Integration
+ */
+
+static void gfx_logind_activate(sd_gfx_monitor *mon) {
+        if (mon->active)
+                return;
+
+        log_debug("gfx: session %s activated", mon->sid);
+        mon->active = 1;
+}
+
+static void gfx_logind_deactivate(sd_gfx_monitor *mon) {
+        if (!mon->active)
+                return;
+
+        log_debug("gfx: session %s deactivated", mon->sid);
+        mon->active = 0;
+}
+
+static void gfx_logind_resume(gfx_device *dev, int fd) {
+        log_debug("gfx: resume device %s",
+                  udev_device_get_syspath(dev->dev));
+
+        switch (dev->devtype) {
+        case SD_GFX_DEV_CARD:
+                gfx_device_resume_card(dev, fd);
+                break;
+        case SD_GFX_DEV_KBD:
+                gfx_device_resume_kbd(dev, fd);
+                break;
+        default:
+                close_nointr(fd);
+                break;
+        }
+}
+
+static void gfx_logind_pause(gfx_device *dev) {
+        log_debug("gfx: pause device %s",
+                  udev_device_get_syspath(dev->dev));
+
+        switch (dev->devtype) {
+        case SD_GFX_DEV_CARD:
+                gfx_device_pause_card(dev);
+                break;
+        case SD_GFX_DEV_KBD:
+                gfx_device_pause_kbd(dev);
+                break;
+        }
+}
+
+static void gfx_logind_open(gfx_device *dev, int fd, bool paused) {
+        sd_bus_message *req = NULL;
+        int r;
+
+        log_debug("gfx: open device %s",
+                  udev_device_get_syspath(dev->dev));
+
+        switch (dev->devtype) {
+        case SD_GFX_DEV_CARD:
+                r = gfx_device_create_card(dev, fd, paused);
+                break;
+        case SD_GFX_DEV_KBD:
+                r = gfx_device_create_kbd(dev, fd, paused);
+                break;
+        default:
+                r = -EINVAL;
+                close_nointr(fd);
+                break;
+        }
+
+        if (r >= 0)
+                return;
+
+        r = sd_bus_message_new_method_call(dev->mon->bus,
+                                           "org.freedesktop.login1",
+                                           dev->mon->spath,
+                                           "org.freedesktop.login1.Session",
+                                           "ReleaseDevice",
+                                           &req);
+        if (r < 0)
+                return;
+
+        r = sd_bus_message_append(req, "uu", major(dev->devt), minor(dev->devt));
+        if (r >= 0)
+                sd_bus_send(dev->mon->bus, req, NULL);
+        sd_bus_message_unref(req);
+}
+
+static int gfx_logind_open_fn(sd_bus *bus, sd_bus_message *m, void *data, sd_bus_error *err) {
+        gfx_device *dev = data;
+        int r, fd = -1;
+        unsigned int paused;
+
+        dev->logind_open_req = 0;
+
+        r = sd_bus_message_read_basic(m, SD_BUS_TYPE_UNIX_FD, &fd);
+        if (r < 0)
+                goto error;
+
+        r = sd_bus_message_read_basic(m, SD_BUS_TYPE_BOOLEAN, &paused);
+        if (r < 0)
+                goto error;
+
+        fd = fcntl(fd, F_DUPFD_CLOEXEC, 3);
+        if (fd < 0)
+                goto error;
+
+        log_error("gfx: control over device %s granted",
+                  udev_device_get_syspath(dev->dev));
+
+        gfx_logind_open(dev, fd, paused);
+        return 0;
+
+error:
+        log_error("gfx: systemd-logind disallowed access to %s: %d",
+                  udev_device_get_syspath(dev->dev), r);
+        return 0;
+}
+
+static int gfx_logind_take_device(gfx_device *dev) {
+        sd_bus_message *req = NULL;
+        int r;
+
+        r = sd_bus_message_new_method_call(dev->mon->bus,
+                                           "org.freedesktop.login1",
+                                           dev->mon->spath,
+                                           "org.freedesktop.login1.Session",
+                                           "TakeDevice",
+                                           &req);
+        if (r < 0)
+                goto error;
+
+        r = sd_bus_message_append(req, "uu", major(dev->devt), minor(dev->devt));
+        if (r < 0)
+                goto error;
+
+        r = sd_bus_call_async(dev->mon->bus, req, gfx_logind_open_fn,
+                              dev, 0, &dev->logind_open_req);
+        if (r < 0)
+                goto error;
+
+        sd_bus_message_unref(req);
+
+        log_debug("gfx: taking control over device %s",
+                  udev_device_get_syspath(dev->dev));
+        return 0;
+
+error:
+        log_error("gfx: cannot request device %s from logind: %d",
+                  udev_device_get_syspath(dev->dev), r);
+        return r;
+}
+
+static int gfx_logind_resume_fn(sd_bus *bus, sd_bus_message *m, void *data, sd_bus_error *err) {
+        sd_gfx_monitor *mon = data;
+        gfx_device *dev;
+        int r, fd;
+        uint32_t major, minor;
+        dev_t devt;
+
+        r = sd_bus_message_read_basic(m, SD_BUS_TYPE_UINT32, &major);
+        if (r < 0)
+                goto error;
+
+        r = sd_bus_message_read_basic(m, SD_BUS_TYPE_UINT32, &minor);
+        if (r < 0)
+                goto error;
+
+        r = sd_bus_message_read_basic(m, SD_BUS_TYPE_UNIX_FD, &fd);
+        if (r < 0)
+                goto error;
+
+        devt = makedev(major, minor);
+        dev = hashmap_get(mon->devices_by_devt, &devt);
+        if (dev) {
+                fd = fcntl(fd, F_DUPFD_CLOEXEC, 3);
+                if (fd < 0)
+                        goto error;
+
+                gfx_logind_resume(dev, fd);
+        }
+
+        return 0;
+
+error:
+        log_error("gfx: cannot parse ResumeDevice signal: %d", r);
+        return 0;
+}
+
+static int gfx_logind_pause_fn(sd_bus *bus, sd_bus_message *m, void *data, sd_bus_error *err) {
+        sd_gfx_monitor *mon = data;
+        gfx_device *dev;
+        int r;
+        const char *type;
+        uint32_t major, minor;
+        dev_t devt;
+
+        r = sd_bus_message_read_basic(m, SD_BUS_TYPE_UINT32, &major);
+        if (r < 0)
+                goto error;
+
+        r = sd_bus_message_read_basic(m, SD_BUS_TYPE_UINT32, &minor);
+        if (r < 0)
+                goto error;
+
+        r = sd_bus_message_read_basic(m, SD_BUS_TYPE_STRING, &type);
+        if (r < 0)
+                goto error;
+
+        devt = makedev(major, minor);
+        dev = hashmap_get(mon->devices_by_devt, &devt);
+        if (dev)
+                gfx_logind_pause(dev);
+
+        return 0;
+
+error:
+        log_error("gfx: cannot parse PauseDevice signal: %d", r);
+        return 0;
+}
+
+static int gfx_logind_session_new_fn(sd_bus *bus, sd_bus_message *m, void *data, sd_bus_error *err) {
+        sd_gfx_monitor *mon = data;
+        int r;
+        const char *sid;
+
+        r = sd_bus_message_read_basic(m, SD_BUS_TYPE_STRING, &sid);
+        if (r < 0)
+                goto error;
+
+        if (strcmp(sid, mon->sid))
+                return 0;
+
+        /* TODO: ... */
+
+        return 0;
+
+error:
+        log_error("gfx: cannot parse SessionNew signal: %d", r);
+        return 0;
+}
+
+static int gfx_logind_session_removed_fn(sd_bus *bus, sd_bus_message *m, void *data, sd_bus_error *err) {
+        sd_gfx_monitor *mon = data;
+        int r;
+        const char *sid;
+
+        r = sd_bus_message_read_basic(m, SD_BUS_TYPE_STRING, &sid);
+        if (r < 0)
+                goto error;
+
+        if (strcmp(sid, mon->sid))
+                return 0;
+
+        /* TODO: ... */
+
+        return 0;
+
+error:
+        log_error("gfx: cannot parse SessionRemoved signal: %d", r);
+        return 0;
+}
+
+static int gfx_logind_get_fn(sd_bus *bus, sd_bus_message *m, void *data, sd_bus_error *err) {
+        sd_gfx_monitor *mon = data;
+        int r;
+
+        mon->logind_active_req = 0;
+
+        r = sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, "b");
+        if (r < 0)
+                goto error;
+
+        r = sd_bus_message_read_basic(m, SD_BUS_TYPE_BOOLEAN, &mon->logind_active);
+        if (r < 0)
+                goto error;
+
+        r = sd_bus_message_exit_container(m);
+        if (r < 0)
+                goto error;
+
+        if (mon->logind_active)
+                gfx_logind_activate(mon);
+        else
+                gfx_logind_deactivate(mon);
+
+        return 0;
+
+error:
+        log_error("gfx: cannot parse session information from logind: %d", r);
+        return 0;
+}
+
+static const struct bus_properties_map gfx_logind_map[]  = {
+        { "Active",    "b",  NULL, offsetof(sd_gfx_monitor, logind_active) },
+        {}
+};
+
+static int gfx_logind_props_changed_fn(sd_bus *bus, sd_bus_message *m, void *data, sd_bus_error *err) {
+        sd_gfx_monitor *mon = data;
+        sd_bus_message *req = NULL;
+        int r;
+
+        sd_bus_call_async_cancel(bus, mon->logind_active_req);
+        mon->logind_active_req = 0;
+
+        r = bus_message_map_properties_changed(m, gfx_logind_map, mon);
+        if (r < 0)
+                goto error;
+
+        if (r > 0) {
+                r = sd_bus_message_new_method_call(bus,
+                                                   "org.freedesktop.login1",
+                                                   mon->spath,
+                                                   "org.freedesktop.DBus.Properties",
+                                                   "Get",
+                                                   &req);
+                if (r < 0)
+                        goto error;
+
+                r = sd_bus_message_append(req, "ss", "org.freedesktop.login1.Session", "Active");
+                if (r < 0)
+                        goto error;
+
+                r = sd_bus_call_async(bus, req, gfx_logind_get_fn,
+                                      mon, 0, &mon->logind_active_req);
+                if (r < 0)
+                        goto error;
+
+                sd_bus_message_unref(req);
+        } else {
+                if (mon->logind_active)
+                        gfx_logind_activate(mon);
+                else
+                        gfx_logind_deactivate(mon);
+        }
+
+        return 0;
+
+error:
+        log_error("gfx: cannot request session information from logind: %d", r);
+        sd_bus_message_unref(req);
+        return 0;
+}
+
+static int gfx_logind_prepare_matches(sd_gfx_monitor *mon) {
+        char *v;
+        int r;
+
+        r = sd_bus_add_match(mon->bus,
+                             "type='signal',"
+                             "sender='org.freedesktop.login1',"
+                             "interface='org.freedesktop.login1.Manager',"
+                             "member='SessionNew',"
+                             "path='/org/freedesktop/login1'",
+                             gfx_logind_session_new_fn,
+                             mon);
+        if (r < 0)
+                return r;
+
+        r = sd_bus_add_match(mon->bus,
+                             "type='signal',"
+                             "sender='org.freedesktop.login1',"
+                             "interface='org.freedesktop.login1.Manager',"
+                             "member='SessionRemoved',"
+                             "path='/org/freedesktop/login1'",
+                             gfx_logind_session_removed_fn,
+                             mon);
+        if (r < 0)
+                return r;
+
+        r = asprintf(&v,
+                     "type='signal',"
+                     "sender='org.freedesktop.login1',"
+                     "interface='org.freedesktop.login1.Session',"
+                     "member='PauseDevice',"
+                     "path='%s'",
+                     mon->spath);
+        if (r < 0)
+                return -ENOMEM;
+
+        r = sd_bus_add_match(mon->bus, v, gfx_logind_pause_fn, mon);
+        free(v);
+        if (r < 0)
+                return r;
+
+        r = asprintf(&v,
+                     "type='signal',"
+                     "sender='org.freedesktop.login1',"
+                     "interface='org.freedesktop.login1.Session',"
+                     "member='ResumeDevice',"
+                     "path='%s'",
+                     mon->spath);
+        if (r < 0)
+                return -ENOMEM;
+
+        r = sd_bus_add_match(mon->bus, v, gfx_logind_resume_fn, mon);
+        free(v);
+        if (r < 0)
+                return r;
+
+        r = asprintf(&v,
+                     "type='signal',"
+                     "sender='org.freedesktop.login1',"
+                     "interface='org.freedesktop.DBus.Properties',"
+                     "member='PropertiesChanged',"
+                     "path='%s'",
+                     mon->spath);
+        if (r < 0)
+                return -ENOMEM;
+
+        r = sd_bus_add_match(mon->bus, v, gfx_logind_props_changed_fn, mon);
+        free(v);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+static int gfx_logind_prepare(sd_gfx_monitor *mon) {
+        _cleanup_free_ char *sid = NULL;
+        _cleanup_bus_error_free_ sd_bus_error err = SD_BUS_ERROR_NULL;
+        int r;
+
+        sid = sd_bus_label_escape(mon->sid);
+        if (!sid)
+                return log_oom();
+
+        r = asprintf(&mon->spath, "/org/freedesktop/login1/session/%s", sid);
+        if (r < 0)
+                goto error;
+
+        r = gfx_logind_prepare_matches(mon);
+        if (r < 0)
+                goto error;
+
+        r = bus_map_all_properties(mon->bus,
+                                   "org.freedesktop.login1",
+                                   mon->spath,
+                                   gfx_logind_map,
+                                   mon);
+        if (r < 0)
+                goto error;
+
+        mon->active = mon->logind_active;
+
+        r = sd_bus_call_method(mon->bus,
+                               "org.freedesktop.login1",
+                               mon->spath,
+                               "org.freedesktop.login1.Session",
+                               "TakeControl",
+                               &err,
+                               NULL,
+                               "b", 0);
+        if (r < 0) {
+                log_error("gfx: cannot take control over session %s: %s",
+                          mon->sid, bus_error_message(&err, -r));
+                return r;
+        }
+
+        log_debug("gfx: control over session %s granted", mon->sid);
+        return 0;
+
+error:
+        log_error("gfx: cannot setup systemd-logind bus connection: %d", r);
+        return r;
+}
+
+/*
+ * Devices
+ */
+
+static void gfx_device_new(sd_gfx_monitor *mon, struct udev_device *d) {
+        gfx_device *dev;
+        dev_t devt;
+        const char *s;
+        int r = 0;
+
+        s = udev_device_get_syspath(d);
+        if (!s || hashmap_get(mon->devices, s))
+                return;
+
+        devt = udev_device_get_devnum(d);
+        if (!devt || hashmap_get(mon->devices_by_devt, &devt))
+                return;
+
+        dev = calloc(1, sizeof(*dev));
+        if (!dev) {
+                r = log_oom();
+                goto err_out;
+        }
+
+        dev->mon = mon;
+        dev->dev = d;
+        dev->devt = devt;
+        dev->devtype = gfx_device_detect(dev);
+
+        if (!(mon->devmask & dev->devtype))
+                goto err_dev;
+
+        if (hashmap_put(mon->devices, s, dev) < 0 ||
+            hashmap_put(mon->devices_by_devt, &dev->devt, dev) < 0) {
+                r = log_oom();
+                goto err_dev;
+        }
+
+        switch (dev->devtype) {
+        case SD_GFX_DEV_CARD:
+                log_info("gfx: new DRM device %s", s);
+                r = gfx_device_new_card(dev);
+                if (r < 0)
+                        goto err_dev;
+                break;
+        case SD_GFX_DEV_KBD:
+                log_info("gfx: new keyboard device %s", s);
+                r = gfx_device_new_kbd(dev);
+                if (r < 0)
+                        goto err_dev;
+                break;
+        case SD_GFX_DEV_UNKNOWN:
+        default:
+                r = 0;
+                goto err_dev;
+        }
+
+        udev_device_ref(dev->dev);
+        return;
+
+err_dev:
+        hashmap_remove(mon->devices_by_devt, &devt);
+        hashmap_remove(mon->devices, s);
+        free(dev);
+err_out:
+        if (r < 0)
+                log_error("gfx: cannot add device %s: %d", s, r);
+}
+
+static void gfx_device_free(gfx_device *dev) {
+        const char *s;
+
+        s = udev_device_get_syspath(dev->dev);
+        log_debug("gfx: free device %s", s);
+
+        switch (dev->devtype) {
+        case SD_GFX_DEV_CARD:
+                gfx_device_free_card(dev);
+                break;
+        case SD_GFX_DEV_KBD:
+                gfx_device_free_kbd(dev);
+                break;
+        }
+
+        sd_bus_call_async_cancel(dev->mon->bus, dev->logind_open_req);
+        hashmap_remove(dev->mon->devices_by_devt, &dev->devt);
+        hashmap_remove(dev->mon->devices, s);
+        udev_device_unref(dev->dev);
+        free(dev);
+}
+
+static void gfx_device_change(gfx_device *dev, struct udev_device *d) {
+        /* TODO: ... */
+}
+
+static gfx_device *gfx_monitor_find(sd_gfx_monitor *mon, struct udev_device *d) {
+        const char *s;
+
+        s = udev_device_get_syspath(d);
+        if (!s)
+                return NULL;
+
+        return hashmap_get(mon->devices, s);
+}
+
+/*
+ * Scanner
+ */
+
+static void gfx_monitor_scan(sd_gfx_monitor *mon) {
+        struct udev_enumerate *e;
+        struct udev_list_entry *l;
+        struct udev_device *d;
+        const char *v;
+        int r;
+
+        log_debug("gfx: scan system");
+
+        e = udev_enumerate_new(mon->udev);
+        if (!e) {
+                log_oom();
+                return;
+        }
+
+        /* We cannot filter for seat-tags as evdev device-nodes are not tagged
+         * (only the input-device parents are tagged). */
+
+        if (mon->devmask & SD_GFX_DEV_CARD) {
+                r = udev_enumerate_add_match_subsystem(e, "drm");
+                if (r < 0)
+                        goto error;
+        }
+
+        if (mon->devmask & SD_GFX_DEV_EVDEV_MASK) {
+                r = udev_enumerate_add_match_subsystem(e, "input");
+                if (r < 0)
+                        goto error;
+        }
+
+        r = udev_enumerate_add_match_is_initialized(e);
+        if (r < 0)
+                goto error;
+
+        r = udev_enumerate_scan_devices(e);
+        if (r < 0)
+                goto error;
+
+        l = udev_enumerate_get_list_entry(e);
+        for ( ; l; l = udev_list_entry_get_next(l)) {
+                v = udev_list_entry_get_name(l);
+                d = udev_device_new_from_syspath(mon->udev, v);
+                if (d) {
+                        if (!gfx_monitor_find(mon, d))
+                                gfx_device_new(mon, d);
+                        udev_device_unref(d);
+                } else {
+                        log_error("gfx: cannot get device %s", v);
+                }
+        }
+
+        udev_enumerate_unref(e);
+        return;
+
+error:
+        log_error("gfx: cannot enumerate udev devices: %d", r);
+        udev_enumerate_unref(e);
+}
+
+static int gfx_monitor_prepare_fn(sd_event_source *source, void *data) {
+        sd_gfx_monitor *mon = data;
+        sd_gfx_monitor_event ev;
+
+        sd_event_source_set_prepare(source, NULL);
+        gfx_monitor_scan(mon);
+
+        if (mon->event_fn) {
+                memset(&ev, 0, sizeof(ev));
+                ev.type = SD_GFX_MONITOR_RUN;
+                mon->event_fn(mon, mon->fn_data, &ev);
+        }
+
+        return 0;
+}
+
+static void gfx_monitor_io(sd_gfx_monitor *mon) {
+        struct udev_device *d;
+        gfx_device *dev;
+        const char *a;
+
+        d = udev_monitor_receive_device(mon->umon);
+        if (!d)
+                return;
+
+        a = udev_device_get_action(d);
+        if (!a)
+                return;
+
+        dev = gfx_monitor_find(mon, d);
+        if (dev) {
+                if (streq(a, "change"))
+                        gfx_device_change(dev, d);
+                else if (streq(a, "remove"))
+                        gfx_device_free(dev);
+        } else {
+                if (streq(a, "add"))
+                        gfx_device_new(mon, d);
+        }
+}
+
+static int gfx_monitor_io_fn(sd_event_source *source, int fd, uint32_t mask, void *data) {
+        sd_gfx_monitor *mon = data;
+
+        if (mask & (EPOLLHUP | EPOLLERR)) {
+                sd_event_source_set_enabled(source, SD_EVENT_OFF);
+                log_error("gfx: HUP on udev-monitor socket");
+                return 0;
+        }
+
+        if (mask & EPOLLIN)
+                gfx_monitor_io(mon);
+
+        return 0;
+}
+
+static int gfx_monitor_prepare_umon(sd_gfx_monitor *mon) {
+        int r, fd;
+
+        mon->umon = udev_monitor_new_from_netlink(mon->udev, "udev");
+        if (!mon->umon)
+                return log_oom();
+
+        /* We cannot filter for seat-tags as evdev device-nodes are not tagged
+         * (only the input-device parents are tagged). */
+
+        if (mon->devmask & SD_GFX_DEV_CARD) {
+                r = udev_monitor_filter_add_match_subsystem_devtype(mon->umon, "drm", NULL);
+                if (r < 0)
+                        goto error;
+        }
+
+        if (mon->devmask & SD_GFX_DEV_EVDEV_MASK) {
+                r = udev_monitor_filter_add_match_subsystem_devtype(mon->umon, "input", NULL);
+                if (r < 0)
+                        goto error;
+        }
+
+        r = udev_monitor_enable_receiving(mon->umon);
+        if (r < 0)
+                goto error;
+
+        fd = udev_monitor_get_fd(mon->umon);
+        r = sd_event_add_io(mon->event, fd, EPOLLIN, gfx_monitor_io_fn, mon, &mon->umon_source);
+        if (r < 0)
+                goto error;
+
+        r = sd_event_source_set_prepare(mon->umon_source, gfx_monitor_prepare_fn);
+        if (r < 0)
+                goto error;
+
+        return 0;
+
+error:
+        log_error("gfx: cannot prepare udev-monitor: %d", r);
+        return r;
+}
+
+/*
+ * Keymaps
+ */
+
+static void gfx_keymap_update(sd_gfx_monitor *mon) {
+        struct xkb_rule_names rmlvo = { };
+        struct xkb_keymap *keymap;
+        struct xkb_state *state;
+        gfx_device *dev;
+        Iterator i;
+
+        log_debug("gfx: change keymap to (%1s, %s, %s, %s)",
+                  mon->xkb_model ? : "", mon->xkb_layout ? : "",
+                  mon->xkb_variant ? : "", mon->xkb_options ? : "");
+
+        rmlvo.rules = "evdev";
+        rmlvo.model = mon->xkb_model;
+        rmlvo.layout = mon->xkb_layout;
+        rmlvo.variant = mon->xkb_variant;
+        rmlvo.options = mon->xkb_options;
+
+        keymap = xkb_keymap_new_from_names(mon->xkb, &rmlvo, 0);
+        if (!keymap) {
+                log_error("gfx: cannot load keymap (%1s, %s, %s, %s)",
+                          mon->xkb_model ? : "", mon->xkb_layout ? : "",
+                          mon->xkb_variant ? : "", mon->xkb_options ? : "");
+                return;
+        }
+
+        HASHMAP_FOREACH(dev, mon->devices, i) {
+                if (dev->devtype != SD_GFX_DEV_KBD)
+                        continue;
+
+                state = sd_gfx_kbd_get_state(dev->kbd);
+                if (xkb_state_get_keymap(state) != mon->keymap)
+                        continue;
+
+                state = xkb_state_new(keymap);
+                if (!state) {
+                        log_oom();
+                        break;
+                }
+
+                sd_gfx_kbd_set_state(dev->kbd, state);
+                xkb_state_unref(state);
+        }
+
+        xkb_keymap_unref(mon->keymap);
+        mon->keymap = keymap;
+}
+
+static const struct bus_properties_map gfx_keymap_map[]  = {
+        { "X11Model",    "s",  NULL, offsetof(sd_gfx_monitor, xkb_model) },
+        { "X11Layout",   "s",  NULL, offsetof(sd_gfx_monitor, xkb_layout) },
+        { "X11Variant",  "s",  NULL, offsetof(sd_gfx_monitor, xkb_variant) },
+        { "X11Options",  "s",  NULL, offsetof(sd_gfx_monitor, xkb_options) },
+        {}
+};
+
+static int gfx_keymap_get_all_fn(sd_bus *bus, sd_bus_message *m, void *data, sd_bus_error *err) {
+        sd_gfx_monitor *mon = data;
+        int r;
+
+        mon->localed_req = 0;
+
+        r = bus_message_map_all_properties(m, gfx_keymap_map, mon);
+        if (r < 0) {
+                log_warning("gfx: cannot fetch keymap from localed: %d", r);
+                return 0;
+        }
+
+        gfx_keymap_update(mon);
+
+        return 0;
+}
+
+static int gfx_keymap_props_changed_fn(sd_bus *bus, sd_bus_message *m, void *data, sd_bus_error *err) {
+        sd_gfx_monitor *mon = data;
+        sd_bus_message *req = NULL;
+        int r;
+
+        sd_bus_call_async_cancel(bus, mon->localed_req);
+        mon->localed_req = 0;
+
+        r = bus_message_map_properties_changed(m, gfx_keymap_map, mon);
+        if (r < 0)
+                goto error;
+
+        if (r > 0) {
+                r = sd_bus_message_new_method_call(bus,
+                                                   "org.freedesktop.locale1",
+                                                   "/org/freedesktop/locale1",
+                                                   "org.freedesktop.DBus.Properties",
+                                                   "GetAll",
+                                                   &req);
+                if (r < 0)
+                        goto error;
+
+                r = sd_bus_message_append(req, "s", "org.freedesktop.locale1");
+                if (r < 0)
+                        goto error;
+
+                r = sd_bus_call_async(bus, req, gfx_keymap_get_all_fn,
+                                      mon, 0, &mon->localed_req);
+                if (r < 0)
+                        goto error;
+
+                sd_bus_message_unref(req);
+        } else {
+                gfx_keymap_update(mon);
+        }
+
+        return 0;
+
+error:
+        log_error("gfx: cannot request keymap from localed: %d", r);
+        sd_bus_message_unref(req);
+        return 0;
+}
+
+static int gfx_keymap_load(sd_gfx_monitor *mon) {
+        struct xkb_rule_names rmlvo = { };
+        int r;
+
+        if (mon->bus) {
+                log_debug("gfx: fetching keymap from localed..");
+                r = bus_map_all_properties(mon->bus,
+                                           "org.freedesktop.locale1",
+                                           "/org/freedesktop/locale1",
+                                           gfx_keymap_map,
+                                           mon);
+                if (r < 0)
+                        log_warning("gfx: cannot fetch keymap from localed: %d", r);
+
+                r = sd_bus_add_match(mon->bus,
+                                     "type='signal',"
+                                     "sender='org.freedesktop.locale1',"
+                                     "interface='org.freedesktop.DBus.Properties',"
+                                     "member='PropertiesChanged',"
+                                     "path='/org/freedesktop/locale1'",
+                                     gfx_keymap_props_changed_fn,
+                                     mon);
+                if (r < 0)
+                        log_warning("gfx: cannot watch localed for events: %d", r);
+        }
+
+        log_debug("gfx: default keymap is (%1s, %s, %s, %s)",
+                  mon->xkb_model ? : "", mon->xkb_layout ? : "",
+                  mon->xkb_variant ? : "", mon->xkb_options ? : "");
+
+        rmlvo.rules = "evdev";
+        rmlvo.model = mon->xkb_model;
+        rmlvo.layout = mon->xkb_layout;
+        rmlvo.variant = mon->xkb_variant;
+        rmlvo.options = mon->xkb_options;
+
+        mon->keymap = xkb_keymap_new_from_names(mon->xkb, &rmlvo, 0);
+        if (!mon->keymap) {
+                log_error("gfx: cannot load keymap (%1s, %s, %s, %s), trying system-default",
+                          mon->xkb_model ? : "", mon->xkb_layout ? : "",
+                          mon->xkb_variant ? : "", mon->xkb_options ? : "");
+
+                mon->keymap = xkb_keymap_new_from_names(mon->xkb, NULL, 0);
+                if (!mon->keymap) {
+                        log_error("gfx: cannot load default keymap");
+
+                        /* TODO: we should add a EN-us built-in keymap to
+                         * xkbcommon so this cannot happen in case no
+                         * x11-keymaps are installed. */
+
+                        return -EINVAL;
+                }
+        }
+
+        return 0;
+}
+
+/*
+ * Monitor
+ */
+
+static void gfx_xkb_log_fn(struct xkb_context *context,
+                           enum xkb_log_level level,
+                           const char *format,
+                           va_list args) {
+        unsigned int l;
+
+        switch (level) {
+        case XKB_LOG_LEVEL_CRITICAL:
+                l = LOG_CRIT;
+                break;
+        case XKB_LOG_LEVEL_ERROR:
+                l = LOG_ERR;
+                break;
+        case XKB_LOG_LEVEL_WARNING:
+                l = LOG_WARNING;
+                break;
+        case XKB_LOG_LEVEL_INFO:
+                l = LOG_INFO;
+                break;
+        case XKB_LOG_LEVEL_DEBUG:
+        default:
+                l = LOG_DEBUG;
+                break;
+        }
+
+        log_metav(l, NULL, 0, NULL, format, args);
+}
+
+int sd_gfx_monitor_new(sd_gfx_monitor **out,
+                       unsigned int devmask,
+                       unsigned int flags,
+                       sd_event *event) {
+        sd_gfx_monitor *mon;
+        int r;
+
+        if (!(flags & SD_GFX_MONITOR_IGNORE_SEATS)) {
+                if (flags & SD_GFX_MONITOR_NO_DBUS)
+                        return -EINVAL;
+
+                flags |= SD_GFX_MONITOR_REQUIRE_DBUS;
+        }
+
+        log_debug("gfx: new monitor");
+
+        mon = calloc(1, sizeof(*mon));
+        if (!mon)
+                return log_oom();
+
+        mon->devmask = devmask;
+        mon->flags = flags;
+        mon->event = event;
+
+        if (flags & SD_GFX_MONITOR_IGNORE_SEATS) {
+                /* Used by boot-splashs, debugging-apps or other system-wide
+                 * gfx-apps. Ignores any seating-information and just returns
+                 * all devices. */
+
+                if (geteuid()) {
+                        log_error("gfx: need to be root to run in system mode");
+                        r = -EACCES;
+                        goto err_mon;
+                }
+
+                mon->sid = NULL;
+                mon->seat = NULL;
+                log_debug("gfx: running in system mode");
+        } else {
+                /* Used by login-screens or normal sessions. Returns only
+                 * devices attached to the process' seat. Uses
+                 * systemd-logind for unprivileged device-access.
+                 * This requires the process to be already running in a
+                 * systemd-session. Must be guaranteed by the caller. */
+
+                r = sd_pid_get_session(getpid(), &mon->sid);
+                if (r < 0) {
+                        log_error("gfx: not running in a logind-session: %d", r);
+                        goto err_mon;
+                }
+
+                r = sd_session_get_seat(mon->sid, &mon->seat);
+                if (r < 0) {
+                        log_error("gfx: session '%s' invalid or not assigned to a seat: %d",
+                                  mon->sid, r);
+                        goto err_mon;
+                }
+                mon->seat0 = streq(mon->seat, "seat0");
+
+                r = sd_session_get_vt(mon->sid, &mon->vt);
+                if (r == -ENOENT) {
+                        mon->vt = 0;
+                } else if (r < 0) {
+                        log_error("gfx: cannot get attached VT of session '%s': %d",
+                                  mon->sid, mon->vt);
+                        goto err_mon;
+                }
+
+                if (mon->vt)
+                        log_debug("gfx: running in session mode as: %s@%s on VT%u",
+                                  mon->sid, mon->seat, mon->vt);
+                else
+                        log_debug("gfx: running in session mode as: %s@%s without VT",
+                                  mon->sid, mon->seat);
+        }
+
+        mon->devices = hashmap_new(string_hash_func, string_compare_func);
+        if (!mon->devices) {
+                r = log_oom();
+                goto err_mon;
+        }
+
+        mon->devices_by_devt = hashmap_new(devt_hash_func, devt_compare_func);
+        if (!mon->devices_by_devt) {
+                r = log_oom();
+                goto err_mon;
+        }
+
+        if (!(flags & SD_GFX_MONITOR_NO_DBUS)) {
+                r = sd_bus_open_system(&mon->bus);
+                if (r < 0) {
+                        log_warning("gfx: cannot open system bus");
+                        if (flags & SD_GFX_MONITOR_REQUIRE_DBUS)
+                                goto err_mon;
+                } else {
+                        r = sd_bus_attach_event(mon->bus, event, 0);
+                        if (r < 0) {
+                                log_error("gfx: cannot attach bus event loop: %d", r);
+                                goto err_mon;
+                        }
+                }
+        }
+
+        mon->udev = udev_new();
+        if (!mon->udev) {
+                r = log_oom();
+                goto err_mon;
+        }
+
+        r = gfx_monitor_prepare_umon(mon);
+        if (r < 0)
+                goto err_mon;
+
+        if (devmask & SD_GFX_DEV_KBD) {
+                mon->xkb = xkb_context_new(0);
+                if (!mon->xkb) {
+                        r = log_oom();
+                        goto err_mon;
+                }
+                xkb_context_set_log_fn(mon->xkb, gfx_xkb_log_fn);
+
+                r = gfx_keymap_load(mon);
+                if (r < 0)
+                        goto err_mon;
+        }
+
+        if (mon->sid) {
+                r = gfx_logind_prepare(mon);
+                if (r < 0)
+                        goto err_mon;
+        } else {
+                mon->active = 1;
+        }
+
+        sd_event_ref(mon->event);
+        *out = mon;
+        return 0;
+
+err_mon:
+        sd_bus_call_async_cancel(mon->bus, mon->logind_active_req);
+
+        sd_bus_call_async_cancel(mon->bus, mon->localed_req);
+        xkb_keymap_unref(mon->keymap);
+        xkb_context_unref(mon->xkb);
+
+        sd_event_source_set_enabled(mon->umon_source, SD_EVENT_OFF);
+        sd_event_source_unref(mon->umon_source);
+        udev_monitor_unref(mon->umon);
+
+        udev_unref(mon->udev);
+
+        sd_bus_detach_event(mon->bus);
+        sd_bus_unref(mon->bus);
+
+        hashmap_free(mon->devices_by_devt);
+        hashmap_free(mon->devices);
+        strv_free(mon->conf_gpus);
+        free(mon->xkb_model);
+        free(mon->xkb_layout);
+        free(mon->xkb_variant);
+        free(mon->xkb_options);
+        free(mon->spath);
+        free(mon->seat);
+        free(mon->sid);
+        free(mon);
+        return r;
+}
+
+void sd_gfx_monitor_free(sd_gfx_monitor *mon) {
+        gfx_device *dev;
+
+        if (!mon)
+                return;
+
+        log_debug("gfx: free monitor");
+
+        while ((dev = hashmap_first(mon->devices)))
+                gfx_device_free(dev);
+
+        sd_bus_call_async_cancel(mon->bus, mon->logind_active_req);
+
+        sd_bus_call_async_cancel(mon->bus, mon->localed_req);
+        xkb_keymap_unref(mon->keymap);
+        xkb_context_unref(mon->xkb);
+
+        sd_event_source_set_enabled(mon->umon_source, SD_EVENT_OFF);
+        sd_event_source_unref(mon->umon_source);
+        udev_monitor_unref(mon->umon);
+
+        udev_unref(mon->udev);
+
+        sd_bus_detach_event(mon->bus);
+        sd_bus_unref(mon->bus);
+
+        sd_event_unref(mon->event);
+
+        hashmap_free(mon->devices_by_devt);
+        hashmap_free(mon->devices);
+        strv_free(mon->conf_gpus);
+        free(mon->xkb_model);
+        free(mon->xkb_layout);
+        free(mon->xkb_variant);
+        free(mon->xkb_options);
+        free(mon->spath);
+        free(mon->seat);
+        free(mon->sid);
+        free(mon);
+}
+
+void sd_gfx_monitor_set_fn_data(sd_gfx_monitor *mon, void *fn_data) {
+        mon->fn_data = fn_data;
+}
+
+void *sd_gfx_monitor_get_fn_data(sd_gfx_monitor *mon) {
+        return mon->fn_data;
+}
+
+void sd_gfx_monitor_set_event_fn(sd_gfx_monitor *mon, sd_gfx_monitor_event_fn event_fn) {
+        mon->event_fn = event_fn;
+}
+
+/*
+ * The "gpus=" option allows to configure which GPUs to use. Following options
+ * are supported:
+ *
+ *      <default>: The default value (or if empty/NULL) is to use all GPUs that
+ *                 are available.
+ *             "": Reset to default.
+ *         "boot": Use boot GPU.
+ *          "aux": Use all auxiliary GPUs.
+ *    "card<num>": Use DRM device "card<num>".
+ *    "<ID_PATH>": Use DRM device with given $ID_PATH.
+ *     "<driver>": Use DRM devices with given driver.
+ *
+ * Options can be combined if separated with a comma, eg., "aux,card1". Spaces
+ * are ignored.
+ */
+void sd_gfx_monitor_set_gpus(sd_gfx_monitor *mon, const char *gpus) {
+        char **src, **dst, *c;
+
+        if (mon->conf_gpus) {
+                strv_free(mon->conf_gpus);
+                mon->conf_gpus = NULL;
+                mon->conf_gpus_boot = 0;
+                mon->conf_gpus_aux = 0;
+        }
+
+        log_debug("gfx: set GPU match to '%s'", gpus ? : "");
+
+        if (!gpus || !*gpus)
+                return;
+
+        mon->conf_gpus = strv_split(gpus, "," WHITESPACE);
+        if (!mon->conf_gpus) {
+                log_oom();
+                return;
+        }
+
+        src = mon->conf_gpus;
+        dst = mon->conf_gpus;
+
+        while (*src) {
+                c = *src++;
+                if (streq(c, "boot")) {
+                        mon->conf_gpus_boot = 1;
+                        free(c);
+                } else if (streq(c, "aux")) {
+                        mon->conf_gpus_aux = 1;
+                        free(c);
+                } else {
+                        *dst++ = c;
+                }
+        }
+
+        *dst = NULL;
+}
+
+/*
+ * The "keymap=" option allows to configure which GPUs to use. Following
+ * options are supported:
+ *
+ *      <default>: The default value (or if empty/NULL) is to let xkbcommon
+ *                 choose the default (usually "us" layout) and query
+ *                 systemd-localed if bus-connection is available.
+ *             "": Reset to default.
+ * "<layout>[,<model>[,<variant>[,<options>]]]":
+ *                 Specify xkb options directly.
+ */
+void sd_gfx_monitor_set_keymap(sd_gfx_monitor *mon, const char *keymap) {
+        char **slots[] = { &mon->xkb_layout, &mon->xkb_model, &mon->xkb_variant, &mon->xkb_options, NULL };
+        char **v, *t, *w, *state;
+        size_t l, i;
+
+        log_debug("gfx: set keymap to '%s'", keymap ? : "");
+
+        if (!keymap || !*keymap) {
+                free(mon->xkb_model);
+                free(mon->xkb_layout);
+                free(mon->xkb_variant);
+                free(mon->xkb_options);
+                mon->xkb_model = NULL;
+                mon->xkb_layout = NULL;
+                mon->xkb_variant = NULL;
+                mon->xkb_options = NULL;
+        } else {
+                i = 0;
+                FOREACH_WORD_SEPARATOR(w, l, keymap, "," WHITESPACE, state) {
+                        v = slots[i++];
+                        if (!v)
+                                break;
+
+                        t = strndup(w, l);
+                        if (!t) {
+                                log_oom();
+                                return;
+                        }
+
+                        free(*v);
+                        *v = t;
+                }
+
+                gfx_keymap_update(mon);
+        }
+}
+
+/*
+ * As sd-gfx may be used as boot-splash, initrd-screen, emergency-console and
+ * more, there is often no way to configure it from a fallback console. Thus,
+ * we allow some rather basic fallback options on the kernel command-line so
+ * users can boot with a limited feature/device set in case something fails.
+ *
+ * These options are not limited to a single application, but rather all
+ * applications that use sd-gfx (and call *_parse_cmdlint) are affected. This
+ * is done on purpose as it should only be used for emergency situations if the
+ * system doesn't boot.
+ * Applications provide their own configurations that you should use if you
+ * want to tweak them.
+ *
+ * Supported options are:
+ *  systemd.gpus: See the *_set_gpus() function.
+ *  systemd.keymap: See the *_set_keymap() function.
+ */
+void sd_gfx_monitor_parse_cmdline(sd_gfx_monitor *mon) {
+        _cleanup_free_ char *line = NULL;
+        const char *v;
+        char *w, *state;
+        size_t l;
+        int r;
+
+        r = proc_cmdline(&line);
+        if (r < 0)
+                log_error("gfx: cannot read /proc/cmdline: %d", r);
+        if (r <= 0)
+                return;
+
+        FOREACH_WORD_QUOTED(w, l, line, state) {
+                _cleanup_free_ char *word = NULL;
+
+                word = strndup(w, l);
+                if (!word) {
+                        log_oom();
+                        return;
+                }
+
+                v = startswith(word, "systemd.gpus=");
+                if (v) {
+                        sd_gfx_monitor_set_gpus(mon, v);
+                        continue;
+                }
+
+                v = startswith(word, "systemd.keymap=");
+                if (v) {
+                        sd_gfx_monitor_set_keymap(mon, v);
+                        continue;
+                }
+        }
+}
diff --git a/src/systemd/sd-gfx.h b/src/systemd/sd-gfx.h
index 44237a1..64e07f1 100644
--- a/src/systemd/sd-gfx.h
+++ b/src/systemd/sd-gfx.h
@@ -31,6 +31,7 @@
 
 _SD_BEGIN_DECLARATIONS;
 
+struct udev_device;
 struct xkb_state;
 
 typedef struct sd_gfx_buffer sd_gfx_buffer;
@@ -47,6 +48,9 @@ typedef struct sd_gfx_card sd_gfx_card;
 typedef struct sd_gfx_kbd_event sd_gfx_kbd_event;
 typedef struct sd_gfx_kbd sd_gfx_kbd;
 
+typedef struct sd_gfx_monitor_event sd_gfx_monitor_event;
+typedef struct sd_gfx_monitor sd_gfx_monitor;
+
 /* memory buffer */
 
 enum {
@@ -339,6 +343,63 @@ void sd_gfx_kbd_wake_up(sd_gfx_kbd *kbd, int fd);
 void sd_gfx_kbd_sleep(sd_gfx_kbd *kbd);
 bool sd_gfx_kbd_is_awake(sd_gfx_kbd *kbd);
 
+/* monitor */
+
+enum {
+        SD_GFX_DEV_UNKNOWN              = 0,
+        SD_GFX_DEV_CARD                 = (1 << 0),
+        SD_GFX_DEV_KBD                  = (1 << 1),
+
+        SD_GFX_DEV_EVDEV_MASK           = SD_GFX_DEV_KBD,
+        SD_GFX_DEV_ALL                  = SD_GFX_DEV_CARD |
+                                          SD_GFX_DEV_EVDEV_MASK,
+};
+
+enum {
+        SD_GFX_MONITOR_RUN,
+        SD_GFX_MONITOR_CREATE,
+        SD_GFX_MONITOR_DESTROY,
+};
+
+struct sd_gfx_monitor_event {
+        unsigned int type;
+        unsigned int devtype;
+
+        union {
+                sd_gfx_card *card;
+                sd_gfx_kbd *kbd;
+        };
+};
+
+typedef void (*sd_gfx_monitor_event_fn) (sd_gfx_monitor *mon,
+                                         void *data,
+                                         sd_gfx_monitor_event *ev);
+
+enum {
+        SD_GFX_MONITOR_DEFAULT          = 0,
+        SD_GFX_MONITOR_IGNORE_SEATS     = (1 << 0),
+        SD_GFX_MONITOR_NO_DBUS          = (1 << 1),
+        SD_GFX_MONITOR_REQUIRE_DBUS     = (1 << 2),
+
+        SD_GFX_MONITOR_MASK             = SD_GFX_MONITOR_IGNORE_SEATS |
+                                          SD_GFX_MONITOR_NO_DBUS |
+                                          SD_GFX_MONITOR_REQUIRE_DBUS,
+};
+
+int sd_gfx_monitor_new(sd_gfx_monitor **out,
+                       unsigned int devmask,
+                       unsigned int flags,
+                       sd_event *event);
+void sd_gfx_monitor_free(sd_gfx_monitor *mon);
+
+void sd_gfx_monitor_set_fn_data(sd_gfx_monitor *mon, void *fn_data);
+void *sd_gfx_monitor_get_fn_data(sd_gfx_monitor *mon);
+void sd_gfx_monitor_set_event_fn(sd_gfx_monitor *mon, sd_gfx_monitor_event_fn event_fn);
+
+void sd_gfx_monitor_set_gpus(sd_gfx_monitor *mon, const char *gpus);
+void sd_gfx_monitor_set_keymap(sd_gfx_monitor *mon, const char *keymap);
+void sd_gfx_monitor_parse_cmdline(sd_gfx_monitor *mon);
+
 _SD_END_DECLARATIONS;
 
 #endif
-- 
1.8.4.2



More information about the systemd-devel mailing list