/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ /*** This file is part of systemd. Copyright (C) 2014 David Herrmann 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 . ***/ #include #include #include #include #include #include #include "bus-util.h" #include "hashmap.h" #include "idev.h" #include "idev-internal.h" #include "macro.h" #include "term-internal.h" #include "util.h" typedef struct kbdtbl kbdtbl; typedef struct kbdmap kbdmap; typedef struct kbdctx kbdctx; typedef struct idev_keyboard idev_keyboard; struct kbdtbl { unsigned long ref; struct xkb_compose_table *xkb_compose_table; }; struct kbdmap { unsigned long ref; struct xkb_keymap *xkb_keymap; xkb_mod_index_t modmap[IDEV_KBDMOD_CNT]; xkb_led_index_t ledmap[IDEV_KBDLED_CNT]; }; struct kbdctx { unsigned long ref; idev_context *context; struct xkb_context *xkb_context; struct kbdmap *kbdmap; struct kbdtbl *kbdtbl; sd_bus_slot *slot_locale_props_changed; sd_bus_slot *slot_locale_get_all; char *locale_lang; char *locale_x11_model; char *locale_x11_layout; char *locale_x11_variant; char *locale_x11_options; char *last_x11_model; char *last_x11_layout; char *last_x11_variant; char *last_x11_options; }; struct idev_keyboard { idev_device device; kbdctx *kbdctx; kbdmap *kbdmap; kbdtbl *kbdtbl; struct xkb_state *xkb_state; struct xkb_compose_state *xkb_compose; usec_t repeat_delay; usec_t repeat_rate; sd_event_source *repeat_timer; uint32_t n_syms; idev_data evdata; idev_data repdata; uint32_t *compose_res; bool repeating : 1; }; #define keyboard_from_device(_d) container_of((_d), idev_keyboard, device) #define KBDCTX_KEY "keyboard.context" /* hashmap key for global kbdctx */ #define KBDXKB_SHIFT (8) /* xkb shifts evdev key-codes by 8 */ #define KBDKEY_UP (0) /* KEY UP event value */ #define KBDKEY_DOWN (1) /* KEY DOWN event value */ #define KBDKEY_REPEAT (2) /* KEY REPEAT event value */ static const idev_device_vtable keyboard_vtable; static int keyboard_update_kbdmap(idev_keyboard *k); static int keyboard_update_kbdtbl(idev_keyboard *k); /* * Keyboard Compose Tables */ static kbdtbl *kbdtbl_ref(kbdtbl *kt) { if (kt) { assert_return(kt->ref > 0, NULL); ++kt->ref; } return kt; } static kbdtbl *kbdtbl_unref(kbdtbl *kt) { if (!kt) return NULL; assert_return(kt->ref > 0, NULL); if (--kt->ref > 0) return NULL; xkb_compose_table_unref(kt->xkb_compose_table); free(kt); return 0; } DEFINE_TRIVIAL_CLEANUP_FUNC(kbdtbl*, kbdtbl_unref); static int kbdtbl_new_from_locale(kbdtbl **out, kbdctx *kc, const char *locale) { _cleanup_(kbdtbl_unrefp) kbdtbl *kt = NULL; assert_return(out, -EINVAL); assert_return(locale, -EINVAL); kt = new0(kbdtbl, 1); if (!kt) return -ENOMEM; kt->ref = 1; kt->xkb_compose_table = xkb_compose_table_new_from_locale(kc->xkb_context, locale, XKB_COMPOSE_COMPILE_NO_FLAGS); if (!kt->xkb_compose_table) return errno > 0 ? -errno : -EFAULT; *out = kt; kt = NULL; return 0; } /* * Keyboard Keymaps */ static const char * const kbdmap_modmap[IDEV_KBDMOD_CNT] = { [IDEV_KBDMOD_IDX_SHIFT] = XKB_MOD_NAME_SHIFT, [IDEV_KBDMOD_IDX_CTRL] = XKB_MOD_NAME_CTRL, [IDEV_KBDMOD_IDX_ALT] = XKB_MOD_NAME_ALT, [IDEV_KBDMOD_IDX_LINUX] = XKB_MOD_NAME_LOGO, [IDEV_KBDMOD_IDX_CAPS] = XKB_MOD_NAME_CAPS, }; static const char * const kbdmap_ledmap[IDEV_KBDLED_CNT] = { [IDEV_KBDLED_IDX_NUM] = XKB_LED_NAME_NUM, [IDEV_KBDLED_IDX_CAPS] = XKB_LED_NAME_CAPS, [IDEV_KBDLED_IDX_SCROLL] = XKB_LED_NAME_SCROLL, }; static kbdmap *kbdmap_ref(kbdmap *km) { assert_return(km, NULL); assert_return(km->ref > 0, NULL); ++km->ref; return km; } static kbdmap *kbdmap_unref(kbdmap *km) { if (!km) return NULL; assert_return(km->ref > 0, NULL); if (--km->ref > 0) return NULL; xkb_keymap_unref(km->xkb_keymap); free(km); return 0; } DEFINE_TRIVIAL_CLEANUP_FUNC(kbdmap*, kbdmap_unref); static int kbdmap_new_from_names(kbdmap **out, kbdctx *kc, const char *model, const char *layout, const char *variant, const char *options) { _cleanup_(kbdmap_unrefp) kbdmap *km = NULL; struct xkb_rule_names rmlvo = { }; unsigned int i; assert_return(out, -EINVAL); km = new0(kbdmap, 1); if (!km) return -ENOMEM; km->ref = 1; rmlvo.rules = "evdev"; rmlvo.model = model; rmlvo.layout = layout; rmlvo.variant = variant; rmlvo.options = options; errno = 0; km->xkb_keymap = xkb_keymap_new_from_names(kc->xkb_context, &rmlvo, 0); if (!km->xkb_keymap) return errno > 0 ? -errno : -EFAULT; for (i = 0; i < IDEV_KBDMOD_CNT; ++i) { const char *t = kbdmap_modmap[i]; if (t) km->modmap[i] = xkb_keymap_mod_get_index(km->xkb_keymap, t); else km->modmap[i] = XKB_MOD_INVALID; } for (i = 0; i < IDEV_KBDLED_CNT; ++i) { const char *t = kbdmap_ledmap[i]; if (t) km->ledmap[i] = xkb_keymap_led_get_index(km->xkb_keymap, t); else km->ledmap[i] = XKB_LED_INVALID; } *out = km; km = NULL; return 0; } /* * Keyboard Context */ static int kbdctx_refresh_compose_table(kbdctx *kc, const char *lang) { kbdtbl *kt; idev_session *s; idev_device *d; Iterator i, j; int r; if (!lang) lang = "C"; if (streq_ptr(kc->locale_lang, lang)) return 0; r = free_and_strdup(&kc->locale_lang, lang); if (r < 0) return r; log_debug("idev-keyboard: new default compose table: [ %s ]", lang); r = kbdtbl_new_from_locale(&kt, kc, lang); if (r < 0) { /* TODO: We need to catch the case where no compose-file is * available. xkb doesn't tell us so far.. so we must not treat * it as a hard-failure but just continue. Preferably, we want * xkb to tell us exactly whether compilation failed or whether * there is no compose file available for this locale. */ log_debug_errno(r, "idev-keyboard: cannot load compose-table for '%s': %m", lang); r = 0; kt = NULL; } kbdtbl_unref(kc->kbdtbl); kc->kbdtbl = kt; HASHMAP_FOREACH(s, kc->context->session_map, i) HASHMAP_FOREACH(d, s->device_map, j) if (idev_is_keyboard(d)) keyboard_update_kbdtbl(keyboard_from_device(d)); return 0; } static void move_str(char **dest, char **src) { free(*dest); *dest = *src; *src = NULL; } static int kbdctx_refresh_keymap(kbdctx *kc) { idev_session *s; idev_device *d; Iterator i, j; kbdmap *km; int r; if (kc->kbdmap && streq_ptr(kc->locale_x11_model, kc->last_x11_model) && streq_ptr(kc->locale_x11_layout, kc->last_x11_layout) && streq_ptr(kc->locale_x11_variant, kc->last_x11_variant) && streq_ptr(kc->locale_x11_options, kc->last_x11_options)) return 0 ; move_str(&kc->last_x11_model, &kc->locale_x11_model); move_str(&kc->last_x11_layout, &kc->locale_x11_layout); move_str(&kc->last_x11_variant, &kc->locale_x11_variant); move_str(&kc->last_x11_options, &kc->locale_x11_options); log_debug("idev-keyboard: new default keymap: [%s / %s / %s / %s]", kc->last_x11_model, kc->last_x11_layout, kc->last_x11_variant, kc->last_x11_options); /* TODO: add a fallback keymap that's compiled-in */ r = kbdmap_new_from_names(&km, kc, kc->last_x11_model, kc->last_x11_layout, kc->last_x11_variant, kc->last_x11_options); if (r < 0) return log_debug_errno(r, "idev-keyboard: cannot create keymap from locale1: %m"); kbdmap_unref(kc->kbdmap); kc->kbdmap = km; HASHMAP_FOREACH(s, kc->context->session_map, i) HASHMAP_FOREACH(d, s->device_map, j) if (idev_is_keyboard(d)) keyboard_update_kbdmap(keyboard_from_device(d)); return 0; } static int kbdctx_set_locale(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) { kbdctx *kc = userdata; const char *s, *ctype = NULL, *lang = NULL; int r; r = sd_bus_message_enter_container(m, 'a', "s"); if (r < 0) goto error; while ((r = sd_bus_message_read(m, "s", &s)) > 0) { if (!ctype) ctype = startswith(s, "LC_CTYPE="); if (!lang) lang = startswith(s, "LANG="); } if (r < 0) goto error; r = sd_bus_message_exit_container(m); if (r < 0) goto error; kbdctx_refresh_compose_table(kc, ctype ? : lang); r = 0; error: if (r < 0) log_debug_errno(r, "idev-keyboard: cannot parse locale property from locale1: %m"); return r; } static const struct bus_properties_map kbdctx_locale_map[] = { { "Locale", "as", kbdctx_set_locale, 0 }, { "X11Model", "s", NULL, offsetof(kbdctx, locale_x11_model) }, { "X11Layout", "s", NULL, offsetof(kbdctx, locale_x11_layout) }, { "X11Variant", "s", NULL, offsetof(kbdctx, locale_x11_variant) }, { "X11Options", "s", NULL, offsetof(kbdctx, locale_x11_options) }, }; static int kbdctx_locale_get_all_fn(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *ret_err) { kbdctx *kc = userdata; int r; kc->slot_locale_get_all = sd_bus_slot_unref(kc->slot_locale_get_all); if (sd_bus_message_is_method_error(m, NULL)) { const sd_bus_error *error = sd_bus_message_get_error(m); log_debug("idev-keyboard: GetAll() on locale1 failed: %s: %s", error->name, error->message); return 0; } r = bus_message_map_all_properties(bus, m, kbdctx_locale_map, kc); if (r < 0) { log_debug("idev-keyboard: erroneous GetAll() reply from locale1"); return 0; } kbdctx_refresh_keymap(kc); return 0; } static int kbdctx_query_locale(kbdctx *kc) { _cleanup_bus_message_unref_ sd_bus_message *m = NULL; int r; kc->slot_locale_get_all = sd_bus_slot_unref(kc->slot_locale_get_all); r = sd_bus_message_new_method_call(kc->context->sysbus, &m, "org.freedesktop.locale1", "/org/freedesktop/locale1", "org.freedesktop.DBus.Properties", "GetAll"); if (r < 0) goto error; r = sd_bus_message_append(m, "s", "org.freedesktop.locale1"); if (r < 0) goto error; r = sd_bus_call_async(kc->context->sysbus, &kc->slot_locale_get_all, m, kbdctx_locale_get_all_fn, kc, 0); if (r < 0) goto error; return 0; error: return log_debug_errno(r, "idev-keyboard: cannot send GetAll to locale1: %m"); } static int kbdctx_locale_props_changed_fn(sd_bus *bus, sd_bus_message *signal, void *userdata, sd_bus_error *ret_err) { kbdctx *kc = userdata; int r; kc->slot_locale_get_all = sd_bus_slot_unref(kc->slot_locale_get_all); /* skip interface name */ r = sd_bus_message_skip(signal, "s"); if (r < 0) goto error; r = bus_message_map_properties_changed(bus, signal, kbdctx_locale_map, kc); if (r < 0) goto error; if (r > 0) { r = kbdctx_query_locale(kc); if (r < 0) return r; } kbdctx_refresh_keymap(kc); return 0; error: return log_debug_errno(r, "idev-keyboard: cannot handle PropertiesChanged from locale1: %m"); } static int kbdctx_setup_bus(kbdctx *kc) { int r; r = sd_bus_add_match(kc->context->sysbus, &kc->slot_locale_props_changed, "type='signal'," "sender='org.freedesktop.locale1'," "interface='org.freedesktop.DBus.Properties'," "member='PropertiesChanged'," "path='/org/freedesktop/locale1'", kbdctx_locale_props_changed_fn, kc); if (r < 0) return log_debug_errno(r, "idev-keyboard: cannot setup locale1 link: %m"); return kbdctx_query_locale(kc); } static void kbdctx_log_fn(struct xkb_context *ctx, enum xkb_log_level lvl, const char *format, va_list args) { char buf[LINE_MAX]; int sd_lvl; if (lvl >= XKB_LOG_LEVEL_DEBUG) sd_lvl = LOG_DEBUG; else if (lvl >= XKB_LOG_LEVEL_INFO) sd_lvl = LOG_INFO; else if (lvl >= XKB_LOG_LEVEL_WARNING) sd_lvl = LOG_INFO; /* most XKB warnings really are informational */ else /* XKB_LOG_LEVEL_ERROR and worse */ sd_lvl = LOG_ERR; snprintf(buf, sizeof(buf), "idev-xkb: %s", format); log_internalv(sd_lvl, 0, __FILE__, __LINE__, __func__, buf, args); } static kbdctx *kbdctx_ref(kbdctx *kc) { assert_return(kc, NULL); assert_return(kc->ref > 0, NULL); ++kc->ref; return kc; } static kbdctx *kbdctx_unref(kbdctx *kc) { if (!kc) return NULL; assert_return(kc->ref > 0, NULL); if (--kc->ref > 0) return NULL; free(kc->last_x11_options); free(kc->last_x11_variant); free(kc->last_x11_layout); free(kc->last_x11_model); free(kc->locale_x11_options); free(kc->locale_x11_variant); free(kc->locale_x11_layout); free(kc->locale_x11_model); free(kc->locale_lang); kc->slot_locale_get_all = sd_bus_slot_unref(kc->slot_locale_get_all); kc->slot_locale_props_changed = sd_bus_slot_unref(kc->slot_locale_props_changed); kc->kbdtbl = kbdtbl_unref(kc->kbdtbl); kc->kbdmap = kbdmap_unref(kc->kbdmap); xkb_context_unref(kc->xkb_context); hashmap_remove_value(kc->context->data_map, KBDCTX_KEY, kc); free(kc); return NULL; } DEFINE_TRIVIAL_CLEANUP_FUNC(kbdctx*, kbdctx_unref); static int kbdctx_new(kbdctx **out, idev_context *c) { _cleanup_(kbdctx_unrefp) kbdctx *kc = NULL; int r; assert_return(out, -EINVAL); assert_return(c, -EINVAL); kc = new0(kbdctx, 1); if (!kc) return -ENOMEM; kc->ref = 1; kc->context = c; errno = 0; kc->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); if (!kc->xkb_context) return errno > 0 ? -errno : -EFAULT; xkb_context_set_log_fn(kc->xkb_context, kbdctx_log_fn); xkb_context_set_log_level(kc->xkb_context, XKB_LOG_LEVEL_DEBUG); r = kbdctx_refresh_keymap(kc); if (r < 0) return r; r = kbdctx_refresh_compose_table(kc, NULL); if (r < 0) return r; if (c->sysbus) { r = kbdctx_setup_bus(kc); if (r < 0) return r; } r = hashmap_put(c->data_map, KBDCTX_KEY, kc); if (r < 0) return r; *out = kc; kc = NULL; return 0; } static int get_kbdctx(idev_context *c, kbdctx **out) { kbdctx *kc; assert_return(c, -EINVAL); assert_return(out, -EINVAL); kc = hashmap_get(c->data_map, KBDCTX_KEY); if (kc) { *out = kbdctx_ref(kc); return 0; } return kbdctx_new(out, c); } /* * Keyboard Devices */ bool idev_is_keyboard(idev_device *d) { return d && d->vtable == &keyboard_vtable; } idev_device *idev_find_keyboard(idev_session *s, const char *name) { char *kname; assert_return(s, NULL); assert_return(name, NULL); kname = strjoina("keyboard/", name); return hashmap_get(s->device_map, kname); } static int keyboard_raise_data(idev_keyboard *k, idev_data *data) { idev_device *d = &k->device; int r; r = idev_session_raise_device_data(d->session, d, data); if (r < 0) log_debug_errno(r, "idev-keyboard: %s/%s: error while raising data event: %m", d->session->name, d->name); return r; } static int keyboard_resize_bufs(idev_keyboard *k, uint32_t n_syms) { uint32_t *t; if (n_syms <= k->n_syms) return 0; t = realloc(k->compose_res, sizeof(*t) * n_syms); if (!t) return -ENOMEM; k->compose_res = t; t = realloc(k->evdata.keyboard.keysyms, sizeof(*t) * n_syms); if (!t) return -ENOMEM; k->evdata.keyboard.keysyms = t; t = realloc(k->evdata.keyboard.codepoints, sizeof(*t) * n_syms); if (!t) return -ENOMEM; k->evdata.keyboard.codepoints = t; t = realloc(k->repdata.keyboard.keysyms, sizeof(*t) * n_syms); if (!t) return -ENOMEM; k->repdata.keyboard.keysyms = t; t = realloc(k->repdata.keyboard.codepoints, sizeof(*t) * n_syms); if (!t) return -ENOMEM; k->repdata.keyboard.codepoints = t; k->n_syms = n_syms; return 0; } static unsigned int keyboard_read_compose(idev_keyboard *k, const xkb_keysym_t **out) { _cleanup_free_ char *t = NULL; term_utf8 u8 = { }; char buf[256], *p; size_t flen = 0; int i, r; r = xkb_compose_state_get_utf8(k->xkb_compose, buf, sizeof(buf)); if (r >= (int)sizeof(buf)) { t = malloc(r + 1); if (!t) return 0; xkb_compose_state_get_utf8(k->xkb_compose, t, r + 1); p = t; } else { p = buf; } for (i = 0; i < r; ++i) { uint32_t *ucs; size_t len, j; len = term_utf8_decode(&u8, &ucs, p[i]); if (len > 0) { r = keyboard_resize_bufs(k, flen + len); if (r < 0) return 0; for (j = 0; j < len; ++j) k->compose_res[flen++] = ucs[j]; } } *out = k->compose_res; return flen; } static void keyboard_arm(idev_keyboard *k, usec_t usecs) { int r; if (usecs != 0) { usecs += now(CLOCK_MONOTONIC); r = sd_event_source_set_time(k->repeat_timer, usecs); if (r >= 0) sd_event_source_set_enabled(k->repeat_timer, SD_EVENT_ONESHOT); } else { sd_event_source_set_enabled(k->repeat_timer, SD_EVENT_OFF); } } static int keyboard_repeat_timer_fn(sd_event_source *source, uint64_t usec, void *userdata) { idev_keyboard *k = userdata; /* never feed REPEAT keys into COMPOSE */ keyboard_arm(k, k->repeat_rate); return keyboard_raise_data(k, &k->repdata); } int idev_keyboard_new(idev_device **out, idev_session *s, const char *name) { _cleanup_(idev_device_freep) idev_device *d = NULL; idev_keyboard *k; char *kname; int r; assert_return(out, -EINVAL); assert_return(s, -EINVAL); assert_return(name, -EINVAL); k = new0(idev_keyboard, 1); if (!k) return -ENOMEM; d = &k->device; k->device = IDEV_DEVICE_INIT(&keyboard_vtable, s); k->repeat_delay = 250 * USEC_PER_MSEC; k->repeat_rate = 30 * USEC_PER_MSEC; /* TODO: add key-repeat configuration */ r = get_kbdctx(s->context, &k->kbdctx); if (r < 0) return r; r = keyboard_update_kbdmap(k); if (r < 0) return r; r = keyboard_update_kbdtbl(k); if (r < 0) return r; r = keyboard_resize_bufs(k, 8); if (r < 0) return r; r = sd_event_add_time(s->context->event, &k->repeat_timer, CLOCK_MONOTONIC, 0, 10 * USEC_PER_MSEC, keyboard_repeat_timer_fn, k); if (r < 0) return r; r = sd_event_source_set_enabled(k->repeat_timer, SD_EVENT_OFF); if (r < 0) return r; kname = strjoina("keyboard/", name); r = idev_device_add(d, kname); if (r < 0) return r; if (out) *out = d; d = NULL; return 0; } static void keyboard_free(idev_device *d) { idev_keyboard *k = keyboard_from_device(d); xkb_compose_state_unref(k->xkb_compose); xkb_state_unref(k->xkb_state); free(k->repdata.keyboard.codepoints); free(k->repdata.keyboard.keysyms); free(k->evdata.keyboard.codepoints); free(k->evdata.keyboard.keysyms); free(k->compose_res); k->repeat_timer = sd_event_source_unref(k->repeat_timer); k->kbdtbl = kbdtbl_unref(k->kbdtbl); k->kbdmap = kbdmap_unref(k->kbdmap); k->kbdctx = kbdctx_unref(k->kbdctx); free(k); } static int8_t guess_ascii(struct xkb_state *state, uint32_t code, uint32_t n_syms, const uint32_t *syms) { xkb_layout_index_t n_lo, lo; xkb_level_index_t lv; struct xkb_keymap *keymap; const xkb_keysym_t *s; int num; if (n_syms == 1 && syms[0] < 128 && syms[0] > 0) return syms[0]; keymap = xkb_state_get_keymap(state); n_lo = xkb_keymap_num_layouts_for_key(keymap, code + KBDXKB_SHIFT); for (lo = 0; lo < n_lo; ++lo) { lv = xkb_state_key_get_level(state, code + KBDXKB_SHIFT, lo); num = xkb_keymap_key_get_syms_by_level(keymap, code + KBDXKB_SHIFT, lo, lv, &s); if (num == 1 && s[0] < 128 && s[0] > 0) return s[0]; } return -1; } static int keyboard_fill(idev_keyboard *k, idev_data *dst, bool resync, uint16_t code, uint32_t value, uint32_t n_syms, const uint32_t *keysyms) { idev_data_keyboard *kev; uint32_t i; int r; assert(dst == &k->evdata || dst == &k->repdata); r = keyboard_resize_bufs(k, n_syms); if (r < 0) return r; dst->type = IDEV_DATA_KEYBOARD; dst->resync = resync; kev = &dst->keyboard; kev->ascii = guess_ascii(k->xkb_state, code, n_syms, keysyms); kev->value = value; kev->keycode = code; kev->mods = 0; kev->consumed_mods = 0; kev->n_syms = n_syms; memcpy(kev->keysyms, keysyms, sizeof(*keysyms) * n_syms); for (i = 0; i < n_syms; ++i) { kev->codepoints[i] = xkb_keysym_to_utf32(keysyms[i]); if (!kev->codepoints[i]) kev->codepoints[i] = 0xffffffffUL; } for (i = 0; i < IDEV_KBDMOD_CNT; ++i) { if (k->kbdmap->modmap[i] == XKB_MOD_INVALID) continue; r = xkb_state_mod_index_is_active(k->xkb_state, k->kbdmap->modmap[i], XKB_STATE_MODS_EFFECTIVE); if (r > 0) kev->mods |= 1 << i; r = xkb_state_mod_index_is_consumed(k->xkb_state, code + KBDXKB_SHIFT, k->kbdmap->modmap[i]); if (r > 0) kev->consumed_mods |= 1 << i; } return 0; } static void keyboard_repeat(idev_keyboard *k) { idev_data *evdata = &k->evdata; idev_data *repdata = &k->repdata; idev_data_keyboard *evkbd = &evdata->keyboard; idev_data_keyboard *repkbd = &repdata->keyboard; const xkb_keysym_t *keysyms; idev_device *d = &k->device; bool repeats; int r, num; if (evdata->resync) { /* * We received a re-sync event. During re-sync, any number of * key-events may have been lost and sync-events may be * re-ordered. Always disable key-repeat for those events. Any * following event will trigger it again. */ k->repeating = false; keyboard_arm(k, 0); return; } repeats = xkb_keymap_key_repeats(k->kbdmap->xkb_keymap, evkbd->keycode + KBDXKB_SHIFT); if (k->repeating && repkbd->keycode == evkbd->keycode) { /* * We received an event for the key we currently repeat. If it * was released, stop key-repeat. Otherwise, ignore the event. */ if (evkbd->value == KBDKEY_UP) { k->repeating = false; keyboard_arm(k, 0); } } else if (evkbd->value == KBDKEY_DOWN && repeats) { /* * We received a key-down event for a key that repeats. The * previous condition caught keys we already repeat, so we know * this is a different key or no key-repeat is running. Start * new key-repeat. */ errno = 0; num = xkb_state_key_get_syms(k->xkb_state, evkbd->keycode + KBDXKB_SHIFT, &keysyms); if (num < 0) r = errno > 0 ? errno : -EFAULT; else r = keyboard_fill(k, repdata, false, evkbd->keycode, KBDKEY_REPEAT, num, keysyms); if (r < 0) { log_debug_errno(r, "idev-keyboard: %s/%s: cannot set key-repeat: %m", d->session->name, d->name); k->repeating = false; keyboard_arm(k, 0); } else { k->repeating = true; keyboard_arm(k, k->repeat_delay); } } else if (k->repeating && !repeats) { /* * We received an event for a key that does not repeat, but we * currently repeat a previously received key. The new key is * usually a modifier, but might be any kind of key. In this * case, we continue repeating the old key, but update the * symbols according to the new state. */ errno = 0; num = xkb_state_key_get_syms(k->xkb_state, repkbd->keycode + KBDXKB_SHIFT, &keysyms); if (num < 0) r = errno > 0 ? errno : -EFAULT; else r = keyboard_fill(k, repdata, false, repkbd->keycode, KBDKEY_REPEAT, num, keysyms); if (r < 0) { log_debug_errno(r, "idev-keyboard: %s/%s: cannot update key-repeat: %m", d->session->name, d->name); k->repeating = false; keyboard_arm(k, 0); } } } static int keyboard_feed_evdev(idev_keyboard *k, idev_data *data) { struct input_event *ev = &data->evdev.event; enum xkb_state_component compch; enum xkb_compose_status cstatus; const xkb_keysym_t *keysyms; idev_device *d = &k->device; int num, r; if (ev->type != EV_KEY || ev->value > KBDKEY_DOWN) return 0; /* TODO: We should audit xkb-actions, whether they need @resync as * flag. Most actions should just be executed, however, there might * be actions that depend on modifier-orders. Those should be * suppressed. */ num = xkb_state_key_get_syms(k->xkb_state, ev->code + KBDXKB_SHIFT, &keysyms); compch = xkb_state_update_key(k->xkb_state, ev->code + KBDXKB_SHIFT, ev->value); if (compch & XKB_STATE_LEDS) { /* TODO: update LEDs */ } if (num < 0) { r = num; goto error; } if (k->xkb_compose && ev->value == KBDKEY_DOWN) { if (num == 1 && !data->resync) { xkb_compose_state_feed(k->xkb_compose, keysyms[0]); cstatus = xkb_compose_state_get_status(k->xkb_compose); } else { cstatus = XKB_COMPOSE_CANCELLED; } switch (cstatus) { case XKB_COMPOSE_NOTHING: /* keep produced keysyms and forward unchanged */ break; case XKB_COMPOSE_COMPOSING: /* consumed by compose-state, drop keysym */ keysyms = NULL; num = 0; break; case XKB_COMPOSE_COMPOSED: /* compose-state produced sth, replace keysym */ num = keyboard_read_compose(k, &keysyms); xkb_compose_state_reset(k->xkb_compose); break; case XKB_COMPOSE_CANCELLED: /* canceled compose, reset, forward cancellation sym */ xkb_compose_state_reset(k->xkb_compose); break; } } else if (k->xkb_compose && num == 1 && keysyms[0] == XKB_KEY_Multi_key && !data->resync && ev->value == KBDKEY_UP) { /* Reset compose state on Multi-Key UP events. This effectively * requires you to hold the key during the whole sequence. I * think it's pretty handy to avoid accidental * Compose-sequences, but this may break Compose for disabled * people. We really need to make this opional! (TODO) */ xkb_compose_state_reset(k->xkb_compose); } if (ev->value == KBDKEY_UP) { /* never produce keysyms for UP */ keysyms = NULL; num = 0; } r = keyboard_fill(k, &k->evdata, data->resync, ev->code, ev->value, num, keysyms); if (r < 0) goto error; keyboard_repeat(k); return keyboard_raise_data(k, &k->evdata); error: log_debug_errno(r, "idev-keyboard: %s/%s: cannot handle event: %m", d->session->name, d->name); k->repeating = false; keyboard_arm(k, 0); return 0; } static int keyboard_feed(idev_device *d, idev_data *data) { idev_keyboard *k = keyboard_from_device(d); switch (data->type) { case IDEV_DATA_RESYNC: /* * If the underlying device is re-synced, key-events might be * sent re-ordered. Thus, we don't know which key was pressed * last. Key-repeat might get confused, hence, disable it * during re-syncs. The first following event will enable it * again. */ k->repeating = false; keyboard_arm(k, 0); return 0; case IDEV_DATA_EVDEV: return keyboard_feed_evdev(k, data); default: return 0; } } static int keyboard_update_kbdmap(idev_keyboard *k) { idev_device *d = &k->device; struct xkb_state *state; kbdmap *km; int r; assert(k); km = k->kbdctx->kbdmap; if (km == k->kbdmap) return 0; errno = 0; state = xkb_state_new(km->xkb_keymap); if (!state) { r = errno > 0 ? -errno : -EFAULT; goto error; } kbdmap_unref(k->kbdmap); k->kbdmap = kbdmap_ref(km); xkb_state_unref(k->xkb_state); k->xkb_state = state; /* TODO: On state-change, we should trigger a resync so the whole * event-state is flushed into the new xkb-state. libevdev currently * does not support that, though. */ return 0; error: return log_debug_errno(r, "idev-keyboard: %s/%s: cannot adopt new keymap: %m", d->session->name, d->name); } static int keyboard_update_kbdtbl(idev_keyboard *k) { idev_device *d = &k->device; struct xkb_compose_state *compose = NULL; kbdtbl *kt; int r; assert(k); kt = k->kbdctx->kbdtbl; if (kt == k->kbdtbl) return 0; if (kt) { errno = 0; compose = xkb_compose_state_new(kt->xkb_compose_table, XKB_COMPOSE_STATE_NO_FLAGS); if (!compose) { r = errno > 0 ? -errno : -EFAULT; goto error; } } kbdtbl_unref(k->kbdtbl); k->kbdtbl = kbdtbl_ref(kt); xkb_compose_state_unref(k->xkb_compose); k->xkb_compose = compose; return 0; error: return log_debug_errno(r, "idev-keyboard: %s/%s: cannot adopt new compose table: %m", d->session->name, d->name); } static const idev_device_vtable keyboard_vtable = { .free = keyboard_free, .feed = keyboard_feed, };