#include <systemd/sd-bus.h>
#include <systemd/sd-event.h>
#include <xkbcommon/xkbcommon.h>
+#include <xkbcommon/xkbcommon-compose.h>
#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;
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;
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;
uint32_t n_syms;
idev_data evdata;
idev_data repdata;
+ uint32_t *compose_res;
bool repeating : 1;
};
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
* 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;
/* 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) {
- log_debug("idev-keyboard: cannot create keymap from locale1: %s",
- strerror(-r));
- return r;
- }
+ if (r < 0)
+ return log_debug_errno(r, "idev-keyboard: cannot create keymap from locale1: %m");
kbdmap_unref(kc->kbdmap);
kc->kbdmap = km;
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) },
return 0;
error:
- log_debug("idev-keyboard: cannot send GetAll to locale1: %s", strerror(-r));
- return r;
+ return log_debug_errno(r, "idev-keyboard: cannot send GetAll to locale1: %m");
}
static int kbdctx_locale_props_changed_fn(sd_bus *bus,
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) {
- log_debug("idev-keyboard: cannot handle PropertiesChanged from locale1: %s", strerror(-r));
- return r;
- }
+ if (r < 0)
+ goto error;
if (r > 0) {
r = kbdctx_query_locale(kc);
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) {
"path='/org/freedesktop/locale1'",
kbdctx_locale_props_changed_fn,
kc);
- if (r < 0) {
- log_debug("idev-keyboard: cannot setup locale1 link: %s", strerror(-r));
- return r;
- }
+ 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 if (lvl >= XKB_LOG_LEVEL_ERROR)
+ sd_lvl = LOG_ERR;
+ else if (lvl >= XKB_LOG_LEVEL_CRITICAL)
+ sd_lvl = LOG_CRIT;
+ else
+ sd_lvl = LOG_CRIT;
+
+ 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);
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);
kc->context = c;
errno = 0;
- kc->xkb_context = xkb_context_new(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)
assert_return(s, NULL);
assert_return(name, NULL);
- kname = strappenda("keyboard/", name);
+ kname = strjoina("keyboard/", name);
return hashmap_get(s->device_map, kname);
}
r = idev_session_raise_device_data(d->session, d, data);
if (r < 0)
- log_debug("idev-keyboard: %s/%s: error while raising data event: %s",
- d->session->name, d->name, strerror(-r));
+ 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;
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);
}
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,
if (r < 0)
return r;
- kname = strappenda("keyboard/", name);
+ kname = strjoina("keyboard/", name);
r = idev_device_add(d, kname);
if (r < 0)
return r;
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);
const xkb_keysym_t *s;
int num;
- if (n_syms == 1 && syms[0] < 128)
+ if (n_syms == 1 && syms[0] < 128 && syms[0] > 0)
return syms[0];
keymap = xkb_state_get_keymap(state);
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)
+ if (num == 1 && s[0] < 128 && s[0] > 0)
return s[0];
}
const uint32_t *keysyms) {
idev_data_keyboard *kev;
uint32_t i;
+ int r;
assert(dst == &k->evdata || dst == &k->repdata);
- if (n_syms > k->n_syms) {
- uint32_t *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;
- }
+ r = keyboard_resize_bufs(k, n_syms);
+ if (r < 0)
+ return r;
dst->type = IDEV_DATA_KEYBOARD;
dst->resync = resync;
}
for (i = 0; i < IDEV_KBDMOD_CNT; ++i) {
- int r;
-
if (k->kbdmap->modmap[i] == XKB_MOD_INVALID)
continue;
r = keyboard_fill(k, repdata, false, evkbd->keycode, KBDKEY_REPEAT, num, keysyms);
if (r < 0) {
- log_debug("idev-keyboard: %s/%s: cannot set key-repeat: %s",
- d->session->name, d->name, strerror(-r));
+ 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 {
r = keyboard_fill(k, repdata, false, repkbd->keycode, KBDKEY_REPEAT, num, keysyms);
if (r < 0) {
- log_debug("idev-keyboard: %s/%s: cannot update key-repeat: %s",
- d->session->name, d->name, strerror(-r));
+ 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;
/* TODO: update LEDs */
}
- if (num < 0)
+ 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)
return keyboard_raise_data(k, &k->evdata);
error:
- log_debug("idev-keyboard: %s/%s: cannot handle event: %s",
- d->session->name, d->name, strerror(-r));
+ 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;
return 0;
error:
- log_debug("idev-keyboard: %s/%s: cannot adopt new keymap: %s",
- d->session->name, d->name, strerror(-r));
- return r;
+ 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 = {