X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=blobdiff_plain;f=src%2Flibsystemd-terminal%2Fidev-keyboard.c;h=6ee3d8150c22aaf49a752173c00faa9e0e2acdf8;hb=efd51554aea0f226a7f6704a72aa6a3c9fc6cb2f;hp=ab9e4811b3bb88837836968f12443c09732d034f;hpb=427c71629ea375e4ca1841a55902b40de21f2ab4;p=elogind.git diff --git a/src/libsystemd-terminal/idev-keyboard.c b/src/libsystemd-terminal/idev-keyboard.c index ab9e4811b..6ee3d8150 100644 --- a/src/libsystemd-terminal/idev-keyboard.c +++ b/src/libsystemd-terminal/idev-keyboard.c @@ -25,17 +25,25 @@ #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; @@ -48,10 +56,12 @@ struct kbdctx { 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; @@ -66,8 +76,10 @@ 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; @@ -76,6 +88,7 @@ struct idev_keyboard { uint32_t n_syms; idev_data evdata; idev_data repdata; + uint32_t *compose_res; bool repeating : 1; }; @@ -91,6 +104,60 @@ struct idev_keyboard { 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 @@ -191,6 +258,49 @@ static int kbdmap_new_from_names(kbdmap **out, * 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; @@ -222,11 +332,8 @@ static int kbdctx_refresh_keymap(kbdctx *kc) { /* 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; @@ -239,7 +346,41 @@ static int kbdctx_refresh_keymap(kbdctx *kc) { 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) }, @@ -304,8 +445,7 @@ static int kbdctx_query_locale(kbdctx *kc) { 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, @@ -336,8 +476,7 @@ static int kbdctx_locale_props_changed_fn(sd_bus *bus, return 0; error: - log_debug("idev-keyboard: cannot handle PropertiesChanged from locale1: %s", strerror(-r)); - return r; + return log_debug_errno(r, "idev-keyboard: cannot handle PropertiesChanged from locale1: %m"); } static int kbdctx_setup_bus(kbdctx *kc) { @@ -352,14 +491,33 @@ 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); @@ -385,8 +543,10 @@ static kbdctx *kbdctx_unref(kbdctx *kc) { 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); @@ -412,14 +572,21 @@ static int kbdctx_new(kbdctx **out, idev_context *c) { 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) @@ -464,7 +631,7 @@ idev_device *idev_find_keyboard(idev_session *s, const char *name) { assert_return(s, NULL); assert_return(name, NULL); - kname = strappenda("keyboard/", name); + kname = strjoina("keyboard/", name); return hashmap_get(s->device_map, kname); } @@ -474,12 +641,85 @@ static int keyboard_raise_data(idev_keyboard *k, idev_data *data) { 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; @@ -496,6 +736,8 @@ static void keyboard_arm(idev_keyboard *k, usec_t usecs) { 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); } @@ -529,6 +771,14 @@ int idev_keyboard_new(idev_device **out, idev_session *s, const char *name) { 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, @@ -543,7 +793,7 @@ int idev_keyboard_new(idev_device **out, idev_session *s, const char *name) { if (r < 0) return r; - kname = strappenda("keyboard/", name); + kname = strjoina("keyboard/", name); r = idev_device_add(d, kname); if (r < 0) return r; @@ -557,12 +807,15 @@ int idev_keyboard_new(idev_device **out, idev_session *s, const char *name) { 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); @@ -575,7 +828,7 @@ static int8_t guess_ascii(struct xkb_state *state, uint32_t code, uint32_t n_sym 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); @@ -584,7 +837,7 @@ static int8_t guess_ascii(struct xkb_state *state, uint32_t code, uint32_t n_sym 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]; } @@ -600,34 +853,13 @@ static int keyboard_fill(idev_keyboard *k, 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; @@ -647,8 +879,6 @@ static int keyboard_fill(idev_keyboard *k, } for (i = 0; i < IDEV_KBDMOD_CNT; ++i) { - int r; - if (k->kbdmap->modmap[i] == XKB_MOD_INVALID) continue; @@ -715,8 +945,8 @@ static void keyboard_repeat(idev_keyboard *k) { 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 { @@ -740,8 +970,8 @@ static void keyboard_repeat(idev_keyboard *k) { 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); } @@ -751,6 +981,7 @@ static void keyboard_repeat(idev_keyboard *k) { 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; @@ -770,8 +1001,56 @@ static int keyboard_feed_evdev(idev_keyboard *k, idev_data *data) { /* 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) @@ -781,8 +1060,8 @@ static int keyboard_feed_evdev(idev_keyboard *k, idev_data *data) { 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; @@ -842,9 +1121,41 @@ static int keyboard_update_kbdmap(idev_keyboard *k) { 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 = {