[systemd-devel] [RFC 06/12] gfx: add keyboard layer

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


To replace the kernel kbd layer, we need keyboard handling in user-space.
The current de-facto standard is XKB and luckily, in the last 3 years
there was an effort to make XKB indepedent of X11, namely libxkbcommon.

libxkbcommon provides keyboard handling compatible (and even superior) to
classic XKB. The default wayland kbd-protocol was designed around the
xkbcommon API and libxkbcommon is used by all known wayland-compositors.
Its only dependency is glibc. However, you also need the keymap-files,
which are part of "xkeyboard-config", which itself has no dependencies and
only provides the uncompiled keymap source files.

The kbd sd-gfx layer takes an evdev fd as input and provides keyboard
presses as events. Everything in between is handled transparently to the
application.

To access the kernel evdev layer, we use libevdev. It is a small wrapper
around the evdev ioctls with no dependencies but libc. The main reason it
was created is to handle SYN_DROPPED (input even-queue overflow)
transparently. It is a mandatory dependency of xf86-input-evdev as of 2013
and will probably be used in most other wayland compositors, too.

The sd_gfx_kbd API should be straightforward. The only thing to mention is
async-sleep support. That is, whenever your session is moved into
background, the evdev fd is revoked. sd_gfx_kbd detects that and puts the
device asleep. Explicit sleep is also supported via sd_gfx_kbd_sleep().
You need to provide a new fd to wake the device up.
---
 Makefile.am                  |   8 +-
 configure.ac                 |   7 +-
 src/libsystemd-gfx/gfx-kbd.c | 629 +++++++++++++++++++++++++++++++++++++++++++
 src/systemd/sd-gfx.h         |  84 ++++++
 4 files changed, 725 insertions(+), 3 deletions(-)
 create mode 100644 src/libsystemd-gfx/gfx-kbd.c

diff --git a/Makefile.am b/Makefile.am
index 2edb091..6ecbd50 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3859,12 +3859,18 @@ noinst_LTLIBRARIES += \
 
 libsystemd_gfx_la_SOURCES = \
 	src/libsystemd-gfx/sd-gfx.h \
+	src/libsystemd-gfx/gfx-kbd.c \
 	src/libsystemd-gfx/gfx-unifont.c
 
 libsystemd_gfx_la_CFLAGS = \
-	$(AM_CFLAGS)
+	$(AM_CFLAGS) \
+	$(GFX_CFLAGS)
 
 libsystemd_gfx_la_LIBADD = \
+	$(GFX_LIBS) \
+	libsystemd-bus-internal.la \
+	libsystemd-daemon-internal.la \
+	libsystemd-id128-internal.la \
 	libsystemd-shared.la \
 	src/libsystemd-gfx/unifont.bin.lo
 
diff --git a/configure.ac b/configure.ac
index 354673a..74c37ae 100644
--- a/configure.ac
+++ b/configure.ac
@@ -294,8 +294,11 @@ AM_CONDITIONAL(HAVE_KMOD, [test "$have_kmod" = "yes"])
 have_gfx=no
 AC_ARG_ENABLE(gfx, AS_HELP_STRING([--disable-gfx], [disable sd-gfx graphics and input support]))
 if test "x$enable_gfx" != "xno"; then
-        AC_DEFINE(HAVE_GFX, 1, [Define if sd-gfx is built])
-        have_gfx=yes
+        PKG_CHECK_MODULES(GFX, [ libevdev >= 0.4 xkbcommon >= 0.3 ],
+                [AC_DEFINE(HAVE_GFX, 1, [Define if sd-gfx is built]) have_gfx=yes], have_gfx=no)
+        if test "x$have_gfx" = xno -a "x$enable_gfx" = xyes; then
+                AC_MSG_ERROR([*** sd-gfx support requested, but libraries not found])
+        fi
 fi
 AM_CONDITIONAL(HAVE_GFX, [test "$have_gfx" = "yes"])
 
diff --git a/src/libsystemd-gfx/gfx-kbd.c b/src/libsystemd-gfx/gfx-kbd.c
new file mode 100644
index 0000000..4de21dd
--- /dev/null
+++ b/src/libsystemd-gfx/gfx-kbd.c
@@ -0,0 +1,629 @@
+/*-*- 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 <libevdev/libevdev.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+#include <xkbcommon/xkbcommon.h>
+
+#include "def.h"
+#include "log.h"
+#include "macro.h"
+#include "missing.h"
+#include "sd-event.h"
+#include "sd-gfx.h"
+#include "time-util.h"
+#include "util.h"
+
+enum {
+        SD_GFX__LED_NUML,
+        SD_GFX__LED_CAPSL,
+        SD_GFX__LED_SCROLLL,
+        SD_GFX__LED_COUNT,
+};
+
+struct sd_gfx_kbd {
+        char *name;
+        struct libevdev *evdev;
+        int fd;
+        struct xkb_state *state;
+        sd_event *ev;
+        sd_event_source *fd_source;
+        sd_event_source *timer;
+        sd_gfx_kbd_event_fn event_fn;
+        void *data;
+        void *fn_data;
+
+        unsigned int delay;
+        unsigned int rate;
+
+        unsigned int sym_count;
+        sd_gfx_kbd_event event;
+        sd_gfx_kbd_event repeat;
+
+        xkb_mod_index_t xkb_mods[SD_GFX__MOD_COUNT];
+        xkb_led_index_t xkb_leds[SD_GFX__LED_COUNT];
+
+        unsigned int awake : 1;
+        unsigned int need_sync : 1;
+        unsigned int repeating : 1;
+};
+
+/* xkb rules="evdev" shifts linux keycodes by +8 */
+#define GFX_XKBCODE(_code) ((_code) + 8)
+
+static void gfx_kbd_update_leds(sd_gfx_kbd *kbd) {
+        enum libevdev_led_value leds[SD_GFX__LED_COUNT];
+        unsigned int i;
+        int r;
+
+        for (i = 0; i < SD_GFX__LED_COUNT; ++i) {
+                if (kbd->xkb_leds[i] != XKB_LED_INVALID &&
+                    xkb_state_led_index_is_active(kbd->state, kbd->xkb_leds[i]) > 0)
+                        leds[i] = LIBEVDEV_LED_ON;
+                else
+                        leds[i] = LIBEVDEV_LED_OFF;
+        }
+
+        r = libevdev_kernel_set_led_values(kbd->evdev,
+                                           LED_NUML, leds[SD_GFX__LED_NUML],
+                                           LED_CAPSL, leds[SD_GFX__LED_CAPSL],
+                                           LED_SCROLLL, leds[SD_GFX__LED_SCROLLL],
+                                           -1);
+        if (r < 0) {
+                if (r == -EACCES)
+                        log_debug("kbd %s: EACCES on led-update", kbd->name);
+                else
+                        log_error("kbd %s: cannot update leds: %d",
+                                  kbd->name, r);
+        }
+}
+
+static int gfx_kbd_resize(sd_gfx_kbd *kbd, size_t s)
+{
+        uint32_t *tmp;
+
+        if (s > kbd->sym_count) {
+                tmp = realloc(kbd->event.keysyms, sizeof(uint32_t) * s);
+                if (!tmp)
+                        return log_oom();
+                kbd->event.keysyms = tmp;
+
+                tmp = realloc(kbd->event.codepoints, sizeof(uint32_t) * s);
+                if (!tmp)
+                        return log_oom();
+                kbd->event.codepoints = tmp;
+
+                tmp = realloc(kbd->repeat.keysyms, sizeof(uint32_t) * s);
+                if (!tmp)
+                        return log_oom();
+                kbd->repeat.keysyms = tmp;
+
+                tmp = realloc(kbd->repeat.codepoints, sizeof(uint32_t) * s);
+                if (!tmp)
+                        return log_oom();
+                kbd->repeat.codepoints = tmp;
+
+                kbd->sym_count = s;
+        }
+
+        return 0;
+}
+
+/*
+ * If multiple layouts are used in a single keymap, one might be US-compatible,
+ * the other might not. If the latter is active, a shortcut like ctrl+c in
+ * terminals/etc. is still expected to work. This helper tries to find a
+ * US-compatible/ASCII-compatible key in all layouts for a given keycode.
+ * The strategy is:
+ *  - Try the current mapping. If it's ascii-compat, we're done.
+ *  - Go through all layouts for the key, starting at 0:
+ *    - Map the key in the currently active level, if it's ascii, we're done.
+ *  - Return XKB_KEY_NoSymbol
+ */
+static uint32_t gfx_get_ascii(struct xkb_state *state,
+                              uint32_t keycode,
+                              unsigned int num_keysyms,
+                              const uint32_t *keysyms) {
+        struct xkb_keymap *keymap;
+        xkb_layout_index_t num_layouts;
+        xkb_layout_index_t layout;
+        xkb_level_index_t level;
+        const xkb_keysym_t *syms;
+        int num_syms;
+
+        if (num_keysyms == 1 && keysyms[0] < 128)
+                return keysyms[0];
+
+        keymap = xkb_state_get_keymap(state);
+        num_layouts = xkb_keymap_num_layouts_for_key(keymap, GFX_XKBCODE(keycode));
+
+        for (layout = 0; layout < num_layouts; layout++) {
+                level = xkb_state_key_get_level(state, GFX_XKBCODE(keycode), layout);
+                num_syms = xkb_keymap_key_get_syms_by_level(keymap, GFX_XKBCODE(keycode), layout, level, &syms);
+                if (num_syms != 1)
+                        continue;
+
+                if (syms[0] < 128)
+                        return syms[0];
+        }
+
+        return XKB_KEY_NoSymbol;
+}
+
+static int gfx_kbd_fill(sd_gfx_kbd *kbd, sd_gfx_kbd_event *ev, xkb_keycode_t code, unsigned int num, const xkb_keysym_t *syms) {
+        int r;
+        unsigned int i;
+
+        r = gfx_kbd_resize(kbd, num);
+        if (r < 0)
+                return r;
+
+        ev->mods = 0;
+
+        for (i = 0; i < SD_GFX__MOD_COUNT; ++i) {
+                if (kbd->xkb_mods[i] != XKB_MOD_INVALID) {
+                        r = xkb_state_mod_index_is_active(kbd->state,
+                                                          kbd->xkb_mods[i],
+                                                          XKB_STATE_MODS_EFFECTIVE);
+                        if (r > 0)
+                                ev->mods |= (1 << i);
+
+                        r = xkb_state_mod_index_is_consumed(kbd->state,
+                                                            GFX_XKBCODE(code),
+                                                            kbd->xkb_mods[i]);
+                        if (r > 0)
+                                ev->consumed_mods |= (1 << i);
+                }
+        }
+
+        ev->keycode = code;
+        ev->ascii = gfx_get_ascii(kbd->state, code, num, syms);
+
+        ev->sym_count = num;
+        memcpy(ev->keysyms, syms, sizeof(uint32_t) * num);
+
+        for (i = 0; i < num; ++i) {
+                ev->codepoints[i] = xkb_keysym_to_utf32(syms[i]);
+                if (!ev->codepoints[i])
+                        ev->codepoints[i] = 0xffffffffULL;
+        }
+
+        return 0;
+}
+
+static int gfx_kbd_timer_fn(sd_event_source *source, uint64_t usec, void *data);
+
+static void gfx_kbd_arm(sd_gfx_kbd *kbd, uint64_t usec) {
+        int r = 0;
+
+        if (usec) {
+                usec += now(CLOCK_MONOTONIC);
+
+                if (!kbd->timer) {
+                        r = sd_event_add_monotonic(kbd->ev,
+                                                   usec,
+                                                   10 * USEC_PER_MSEC,
+                                                   gfx_kbd_timer_fn,
+                                                   kbd,
+                                                   &kbd->timer);
+                } else {
+                        r = sd_event_source_set_time(kbd->timer, usec);
+                        if (r >= 0)
+                                r = sd_event_source_set_enabled(kbd->timer, SD_EVENT_ON);
+                }
+        } else if (kbd->timer) {
+                r = sd_event_source_set_enabled(kbd->timer, SD_EVENT_OFF);
+        }
+
+        if (r < 0 && r != -ESTALE)
+                log_error("kbd %s: cannot arm/disarm repeat timer: %d",
+                          kbd->name, r);
+}
+
+static int gfx_kbd_timer_fn(sd_event_source *source, uint64_t usec, void *data) {
+        sd_gfx_kbd *kbd = data;
+
+        gfx_kbd_arm(kbd, kbd->rate * 1000ULL);
+        if (kbd->event_fn)
+                kbd->event_fn(kbd, kbd->fn_data, &kbd->repeat);
+
+        return 0;
+}
+
+static void gfx_kbd_repeat(sd_gfx_kbd *kbd, bool state) {
+        struct xkb_keymap *keymap = xkb_state_get_keymap(kbd->state);
+        const uint32_t *keysyms;
+        unsigned int i;
+        int num;
+
+        if (kbd->repeating && kbd->repeat.keycode == kbd->event.keycode) {
+                if (!state) {
+                        kbd->repeating = 0;
+                        gfx_kbd_arm(kbd, 0);
+                }
+
+                return;
+        }
+
+        if (state && xkb_keymap_key_repeats(keymap, GFX_XKBCODE(kbd->event.keycode))) {
+                kbd->repeat.mods = kbd->event.mods;
+                kbd->repeat.consumed_mods = kbd->event.consumed_mods;
+                kbd->repeat.keycode = kbd->event.keycode;
+                kbd->repeat.ascii = kbd->event.ascii;
+                kbd->repeat.sym_count = kbd->event.sym_count;
+
+                for (i = 0; i < kbd->event.sym_count; ++i) {
+                        kbd->repeat.keysyms[i] = kbd->event.keysyms[i];
+                        kbd->repeat.codepoints[i] = kbd->event.codepoints[i];
+                }
+
+                kbd->repeating = 1;
+                gfx_kbd_arm(kbd, kbd->delay * 1000ULL);
+        } else if (kbd->repeating && !xkb_keymap_key_repeats(keymap, GFX_XKBCODE(kbd->event.keycode))) {
+                num = xkb_state_key_get_syms(kbd->state, GFX_XKBCODE(kbd->repeat.keycode), &keysyms);
+                if (num <= 0)
+                        return;
+
+                gfx_kbd_fill(kbd, &kbd->repeat, kbd->repeat.keycode, num, keysyms);
+                return;
+        }
+}
+
+static void gfx_kbd_event(sd_gfx_kbd *kbd, struct input_event *ev, bool in_sync) {
+        const xkb_keysym_t *keysyms;
+        int num, r;
+        enum xkb_state_component ch;
+
+        if (ev->type != EV_KEY || ev->value < 0 || ev->value > 1)
+                return;
+
+        /* TODO: If @in_sync XKB shouldn't trigger xkb-actions. However, there
+         * is no flag for that, yet. */
+
+        num = xkb_state_key_get_syms(kbd->state, GFX_XKBCODE(ev->code), &keysyms);
+        ch = xkb_state_update_key(kbd->state, GFX_XKBCODE(ev->code), ev->value);
+
+        if (ch & XKB_STATE_LEDS)
+                gfx_kbd_update_leds(kbd);
+
+        if (num > 0) {
+                r = gfx_kbd_fill(kbd, &kbd->event, ev->code, num, keysyms);
+                if (r < 0)
+                        return;
+
+                /* Synced events are reported out of order. We must not use
+                 * them as application input but only to sync our modifier
+                 * state. */
+                if (in_sync)
+                        return;
+
+                gfx_kbd_repeat(kbd, ev->value);
+
+                if (ev->value && kbd->event_fn)
+                        kbd->event_fn(kbd, kbd->fn_data, &kbd->event);
+        }
+}
+
+static void gfx_kbd_io(sd_gfx_kbd *kbd) {
+        int r;
+        struct input_event ev;
+        unsigned int flags = LIBEVDEV_READ_FLAG_NORMAL;
+
+        if (!kbd->awake)
+                return;
+
+        if (kbd->need_sync) {
+                flags |= LIBEVDEV_READ_FLAG_FORCE_SYNC;
+
+                r = libevdev_next_event(kbd->evdev, flags, &ev);
+                if (r != LIBEVDEV_READ_STATUS_SYNC)
+                        log_error("kbd %s: cannot force resync: %d",
+                                  kbd->name, r);
+
+                gfx_kbd_update_leds(kbd);
+                gfx_kbd_arm(kbd, 0);
+                kbd->need_sync = 0;
+                flags = LIBEVDEV_READ_FLAG_SYNC;
+        }
+
+        for (;;) {
+                r = libevdev_next_event(kbd->evdev, flags, &ev);
+                if (r == -EAGAIN) {
+                        break;
+                } else if (r == LIBEVDEV_READ_STATUS_SUCCESS) {
+                        /* Ummm.. libevdev reads events into its own queue on
+                         * *each* invocation. Thus, we must read until the
+                         * queue is empty, otherwise, we'd have to schedule an
+                         * idle-source just for the next event..
+                         * So continue reading until we get EAGAIN. */
+                        gfx_kbd_event(kbd, &ev, false);
+                } else if (r == LIBEVDEV_READ_STATUS_SYNC) {
+                        /* If STATUS_SYNC is returned during normal invocation,
+                         * it means we read a SYN_DROPPED event and should sync
+                         * the device. We then sync until we get EAGAIN. */
+                        if (flags & LIBEVDEV_READ_FLAG_SYNC) {
+                                gfx_kbd_event(kbd, &ev, true);
+                        } else {
+                                flags = LIBEVDEV_READ_FLAG_SYNC;
+                                gfx_kbd_arm(kbd, 0);
+                        }
+                } else {
+                        if (r == -EACCES)
+                                log_debug("kbd %s: async sleep", kbd->name);
+                        else
+                                log_error("kbd %s: read failed: %d",
+                                          kbd->name, r);
+
+                        sd_gfx_kbd_sleep(kbd);
+                        break;
+                }
+        }
+}
+
+static int gfx_kbd_io_fn(sd_event_source *source, int fd, uint32_t mask, void *data) {
+        sd_gfx_kbd *kbd = data;
+
+        if (mask & (EPOLLHUP | EPOLLERR)) {
+                log_debug("kbd %s: HUP", kbd->name);
+                sd_event_source_set_enabled(source, SD_EVENT_OFF);
+                return 0;
+        }
+
+        if (mask & EPOLLIN)
+                gfx_kbd_io(kbd);
+
+        return 0;
+}
+
+static int gfx_kbd_prepare_fn(sd_event_source *source, void *data) {
+        sd_gfx_kbd *kbd = data;
+
+        sd_event_source_set_prepare(source, NULL);
+        if (kbd->need_sync)
+                gfx_kbd_io(kbd);
+
+        return 0;
+}
+
+static const char *gfx_mods[] = {
+        [SD_GFX__MOD_SHIFT]     = XKB_MOD_NAME_SHIFT,
+        [SD_GFX__MOD_CAPSL]     = XKB_MOD_NAME_CAPS,
+        [SD_GFX__MOD_CTRL]      = XKB_MOD_NAME_CTRL,
+        [SD_GFX__MOD_ALT]       = XKB_MOD_NAME_ALT,
+        [SD_GFX__MOD_LOGO]      = XKB_MOD_NAME_LOGO,
+        [SD_GFX__MOD_COUNT]     = NULL,
+};
+
+static const char *gfx_leds[] = {
+        [SD_GFX__LED_NUML]      = XKB_LED_NAME_NUM,
+        [SD_GFX__LED_CAPSL]     = XKB_LED_NAME_CAPS,
+        [SD_GFX__LED_SCROLLL]   = XKB_LED_NAME_SCROLL,
+        [SD_GFX__LED_COUNT]     = NULL,
+};
+
+static void gfx_kbd_set_state(sd_gfx_kbd *kbd, struct xkb_state *state) {
+        struct xkb_keymap *keymap;
+        unsigned int i;
+        const char *t;
+
+        if (kbd->state) {
+                xkb_state_unref(kbd->state);
+                kbd->state = NULL;
+                kbd->event.state = NULL;
+                kbd->repeat.state = NULL;
+        }
+
+        if (!state)
+                return;
+
+        kbd->state = state;
+        kbd->event.state = state;
+        kbd->repeat.state = state;
+        xkb_state_ref(kbd->state);
+
+        keymap = xkb_state_get_keymap(state);
+
+        for (i = 0; i < SD_GFX__MOD_COUNT; ++i) {
+                t = gfx_mods[i];
+                if (t)
+                        kbd->xkb_mods[i] = xkb_keymap_mod_get_index(keymap, t);
+                else
+                        kbd->xkb_mods[i] = XKB_MOD_INVALID;
+        }
+
+        for (i = 0; i < SD_GFX__LED_COUNT; ++i) {
+                t = gfx_leds[i];
+                if (t)
+                        kbd->xkb_leds[i] = xkb_keymap_led_get_index(keymap, t);
+                else
+                        kbd->xkb_leds[i] = XKB_LED_INVALID;
+        }
+
+        /* TODO: We should actually flush the libevdev state into xkb here,
+         * but there's no libevdev call for that. This causes modifier-state
+         * during state-switch to be lost. Seems acceptable.. */
+}
+
+int sd_gfx_kbd_new(sd_gfx_kbd **out,
+                   const char *name,
+                   struct xkb_state *state,
+                   sd_event *event) {
+        sd_gfx_kbd *kbd;
+        int r;
+
+        assert(out);
+        assert(name);
+        assert(state);
+        assert(event);
+
+        log_debug("kbd %s: new", name);
+
+        kbd = calloc(1, sizeof(*kbd));
+        if (!kbd)
+                return log_oom();
+
+        kbd->fd = -1;
+        kbd->ev = event;
+        sd_event_ref(kbd->ev);
+        kbd->delay = 250;
+        kbd->rate = 50;
+        kbd->awake = 0;
+        gfx_kbd_set_state(kbd, state);
+
+        kbd->name = strdup(name);
+        if (!kbd->name) {
+                r = log_oom();
+                goto err_kbd;
+        }
+
+        *out = kbd;
+        return 0;
+
+err_kbd:
+        gfx_kbd_set_state(kbd, NULL);
+        sd_event_unref(kbd->ev);
+        free(kbd);
+        return r;
+}
+
+void sd_gfx_kbd_free(sd_gfx_kbd *kbd) {
+        if (!kbd)
+                return;
+
+        log_debug("kbd %s: free", kbd->name);
+
+        sd_gfx_kbd_sleep(kbd);
+        libevdev_free(kbd->evdev);
+
+        gfx_kbd_set_state(kbd, NULL);
+        sd_event_source_unref(kbd->timer);
+        sd_event_unref(kbd->ev);
+
+        free(kbd->event.keysyms);
+        free(kbd->event.codepoints);
+        free(kbd->repeat.keysyms);
+        free(kbd->repeat.codepoints);
+        free(kbd->name);
+        free(kbd);
+}
+
+void sd_gfx_kbd_set_data(sd_gfx_kbd *kbd, void *data) {
+        kbd->data = data;
+}
+
+void *sd_gfx_kbd_get_data(sd_gfx_kbd *kbd) {
+        return kbd->data;
+}
+
+void sd_gfx_kbd_set_fn_data(sd_gfx_kbd *kbd, void *fn_data) {
+        kbd->fn_data = fn_data;
+}
+
+void *sd_gfx_kbd_get_fn_data(sd_gfx_kbd *kbd) {
+        return kbd->fn_data;
+}
+
+void sd_gfx_kbd_set_event_fn(sd_gfx_kbd *kbd, sd_gfx_kbd_event_fn event_fn) {
+        kbd->event_fn = event_fn;
+}
+
+void sd_gfx_kbd_set_rate(sd_gfx_kbd *kbd, unsigned int delay, unsigned int rate) {
+        kbd->delay = delay ? CLAMP(delay, 10U, 2000U) : 0U;
+        kbd->rate = rate ? CLAMP(rate, 5U, 2000U) : 0U;
+}
+
+void sd_gfx_kbd_set_state(sd_gfx_kbd *kbd, struct xkb_state *state) {
+        if (!state || kbd->state == state)
+                return;
+
+        gfx_kbd_set_state(kbd, state);
+}
+
+struct xkb_state *sd_gfx_kbd_get_state(sd_gfx_kbd *kbd) {
+        return kbd->state;
+}
+
+void sd_gfx_kbd_wake_up(sd_gfx_kbd *kbd, int fd) {
+        int r;
+
+        assert(fd >= 0);
+
+        sd_gfx_kbd_sleep(kbd);
+        log_debug("kbd %s: wake up", kbd->name);
+
+        if (!kbd->evdev) {
+                r = libevdev_new_from_fd(fd, &kbd->evdev);
+                if (r < 0) {
+                        log_error("kbd %s: cannot open evdev fd during wake-up: %d",
+                                  kbd->name, r);
+                        goto error;
+                }
+        }
+
+        r = sd_event_add_io(kbd->ev, fd, EPOLLIN, gfx_kbd_io_fn, kbd, &kbd->fd_source);
+        if (r < 0) {
+                log_error("kbd %s: cannot change fd during wake-up: %d",
+                          kbd->name, r);
+                goto error;
+        }
+
+        r = sd_event_source_set_prepare(kbd->fd_source, gfx_kbd_prepare_fn);
+        if (r < 0)
+                log_error("kbd %s: cannot schedule kbd-sync on wake-up: %d",
+                          kbd->name, r);
+
+        kbd->fd = fd;
+        libevdev_change_fd(kbd->evdev, fd);
+        kbd->need_sync = 1;
+        kbd->awake = 1;
+        return;
+
+error:
+        close_nointr(fd);
+}
+
+void sd_gfx_kbd_sleep(sd_gfx_kbd *kbd) {
+        if (!kbd->awake)
+                return;
+
+        log_debug("kbd %s: sleep", kbd->name);
+        gfx_kbd_arm(kbd, 0);
+
+        sd_event_source_set_enabled(kbd->fd_source, SD_EVENT_OFF);
+        sd_event_source_unref(kbd->fd_source);
+        kbd->fd_source = NULL;
+
+        /* TODO: can fail with ENODEV on HUP, ugh? */
+        close_nointr(kbd->fd);
+        kbd->fd = -1;
+        kbd->awake = 0;
+}
+
+bool sd_gfx_kbd_is_awake(sd_gfx_kbd *kbd) {
+        return kbd->awake;
+}
diff --git a/src/systemd/sd-gfx.h b/src/systemd/sd-gfx.h
index c46fbd4..96d034a 100644
--- a/src/systemd/sd-gfx.h
+++ b/src/systemd/sd-gfx.h
@@ -27,12 +27,19 @@
 #include <stdbool.h>
 #include <stdlib.h>
 #include <systemd/_sd-common.h>
+#include <systemd/sd-event.h>
 
 _SD_BEGIN_DECLARATIONS;
 
+struct xkb_state;
+
 typedef struct sd_gfx_buffer sd_gfx_buffer;
+
 typedef struct sd_gfx_font sd_gfx_font;
 
+typedef struct sd_gfx_kbd_event sd_gfx_kbd_event;
+typedef struct sd_gfx_kbd sd_gfx_kbd;
+
 /* memory buffer */
 
 enum {
@@ -60,6 +67,83 @@ unsigned int sd_gfx_font_get_height(sd_gfx_font *font);
 
 void sd_gfx_font_render(sd_gfx_font *font, uint32_t id, const uint32_t *ucs4, size_t len, sd_gfx_buffer **out);
 
+/* keyboard */
+
+enum {
+        SD_GFX_KBD_HUP,
+        SD_GFX_KBD_KEY,
+};
+
+enum {
+        SD_GFX__MOD_SHIFT,
+        SD_GFX__MOD_CAPSL,
+        SD_GFX__MOD_CTRL,
+        SD_GFX__MOD_ALT,
+        SD_GFX__MOD_LOGO,
+        SD_GFX__MOD_COUNT,
+
+        SD_GFX_SHIFT            = (1 << SD_GFX__MOD_SHIFT),
+        SD_GFX_CAPSL            = (1 << SD_GFX__MOD_CAPSL),
+        SD_GFX_CTRL             = (1 << SD_GFX__MOD_CTRL),
+        SD_GFX_ALT              = (1 << SD_GFX__MOD_ALT),
+        SD_GFX_LOGO             = (1 << SD_GFX__MOD_LOGO),
+};
+
+struct sd_gfx_kbd_event {
+        struct xkb_state *state;
+        uint32_t mods;
+        uint32_t consumed_mods;
+
+        uint16_t keycode;
+        uint32_t ascii;
+
+        unsigned int sym_count;
+        uint32_t *keysyms;
+        uint32_t *codepoints;
+};
+
+static inline bool sd_gfx_kbd_match_shortcut(sd_gfx_kbd_event *event,
+                                             uint32_t mods,
+                                             unsigned int sym_count,
+                                             uint32_t *keysyms) {
+        const uint32_t significant = SD_GFX_SHIFT |
+                                     SD_GFX_CTRL |
+                                     SD_GFX_ALT |
+                                     SD_GFX_LOGO;
+        uint32_t real_mods;
+
+        if (sym_count != event->sym_count)
+                return false;
+
+        real_mods = event->mods & ~event->consumed_mods & significant;
+        if (mods != real_mods)
+                return false;
+
+        return !memcmp(keysyms, event->keysyms, sizeof(uint32_t) * sym_count);
+}
+
+typedef void (*sd_gfx_kbd_event_fn) (sd_gfx_kbd *kbd, void *fn_data, sd_gfx_kbd_event *ev);
+
+int sd_gfx_kbd_new(sd_gfx_kbd **out,
+                   const char *name,
+                   struct xkb_state *state,
+                   sd_event *event);
+void sd_gfx_kbd_free(sd_gfx_kbd *kbd);
+
+void sd_gfx_kbd_set_data(sd_gfx_kbd *kbd, void *data);
+void *sd_gfx_kbd_get_data(sd_gfx_kbd *kbd);
+void sd_gfx_kbd_set_fn_data(sd_gfx_kbd *kbd, void *fn_data);
+void *sd_gfx_kbd_get_fn_data(sd_gfx_kbd *kbd);
+void sd_gfx_kbd_set_event_fn(sd_gfx_kbd *kbd, sd_gfx_kbd_event_fn event_fn);
+
+void sd_gfx_kbd_set_rate(sd_gfx_kbd *kbd, unsigned int delay, unsigned int rate);
+void sd_gfx_kbd_set_state(sd_gfx_kbd *kbd, struct xkb_state *state);
+struct xkb_state *sd_gfx_kbd_get_state(sd_gfx_kbd *kbd);
+
+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);
+
 _SD_END_DECLARATIONS;
 
 #endif
-- 
1.8.4.2



More information about the systemd-devel mailing list