idev-keyboard.c revision cdcd0ccdbe427de53d8e5ff8f2f1b06b3f477bde
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com>
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 <inttypes.h>
#include <stdbool.h>
#include <stdlib.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 "util.h"
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;
};
struct kbdctx {
unsigned long ref;
struct xkb_context *xkb_context;
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 {
struct xkb_compose_state *xkb_compose;
bool repeating : 1;
};
#define KBDKEY_UP (0) /* KEY UP 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
*/
if (kt) {
}
return kt;
}
if (!kt)
return NULL;
return NULL;
return 0;
}
if (!kt)
return -ENOMEM;
if (!kt->xkb_compose_table)
return 0;
}
/*
* Keyboard Keymaps
*/
static const char * const kbdmap_modmap[IDEV_KBDMOD_CNT] = {
};
static const char * const kbdmap_ledmap[IDEV_KBDLED_CNT] = {
};
return km;
}
if (!km)
return NULL;
return NULL;
return 0;
}
const char *model,
const char *layout,
const char *variant,
const char *options) {
struct xkb_rule_names rmlvo = { };
unsigned int i;
if (!km)
return -ENOMEM;
errno = 0;
if (!km->xkb_keymap)
for (i = 0; i < IDEV_KBDMOD_CNT; ++i) {
const char *t = kbdmap_modmap[i];
if (t)
else
}
for (i = 0; i < IDEV_KBDLED_CNT; ++i) {
const char *t = kbdmap_ledmap[i];
if (t)
else
}
return 0;
}
/*
* Keyboard Context
*/
idev_session *s;
idev_device *d;
Iterator i, j;
int r;
if (!lang)
lang = "C";
return 0;
if (r < 0)
return r;
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("idev-keyboard: cannot load compose-table for '%s': %s",
r = 0;
}
HASHMAP_FOREACH(d, s->device_map, j)
if (idev_is_keyboard(d))
return 0;
}
}
idev_session *s;
idev_device *d;
Iterator i, j;
int r;
return 0 ;
log_debug("idev-keyboard: new default keymap: [%s / %s / %s / %s]",
/* TODO: add a fallback keymap that's compiled-in */
if (r < 0) {
log_debug("idev-keyboard: cannot create keymap from locale1: %s",
strerror(-r));
return r;
}
HASHMAP_FOREACH(d, s->device_map, j)
if (idev_is_keyboard(d))
return 0;
}
static int kbdctx_set_locale(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) {
int r;
if (r < 0)
goto error;
while ((r = sd_bus_message_read(m, "s", &s)) > 0) {
if (!ctype)
if (!lang)
}
if (r < 0)
goto error;
r = sd_bus_message_exit_container(m);
if (r < 0)
goto error;
r = 0;
if (r < 0)
return r;
}
static const struct bus_properties_map kbdctx_locale_map[] = {
};
sd_bus_message *m,
void *userdata,
sd_bus_error *ret_err) {
int r;
if (sd_bus_message_is_method_error(m, NULL)) {
log_debug("idev-keyboard: GetAll() on locale1 failed: %s: %s",
return 0;
}
if (r < 0) {
log_debug("idev-keyboard: erroneous GetAll() reply from locale1");
return 0;
}
return 0;
}
int r;
&m,
"org.freedesktop.locale1",
"/org/freedesktop/locale1",
"org.freedesktop.DBus.Properties",
"GetAll");
if (r < 0)
goto error;
if (r < 0)
goto error;
m,
kc,
0);
if (r < 0)
goto error;
return 0;
return r;
}
void *userdata,
sd_bus_error *ret_err) {
int r;
/* skip interface name */
if (r < 0)
goto error;
if (r < 0)
goto error;
if (r > 0) {
r = kbdctx_query_locale(kc);
if (r < 0)
return r;
}
return 0;
return r;
}
int r;
"type='signal',"
"sender='org.freedesktop.locale1',"
"interface='org.freedesktop.DBus.Properties',"
"member='PropertiesChanged',"
"path='/org/freedesktop/locale1'",
kc);
if (r < 0) {
return r;
}
return kbdctx_query_locale(kc);
}
return kc;
}
if (!kc)
return NULL;
return NULL;
return NULL;
}
int r;
assert_return(c, -EINVAL);
if (!kc)
return -ENOMEM;
errno = 0;
if (!kc->xkb_context)
r = kbdctx_refresh_keymap(kc);
if (r < 0)
return r;
if (r < 0)
return r;
if (c->sysbus) {
r = kbdctx_setup_bus(kc);
if (r < 0)
return r;
}
if (r < 0)
return r;
return 0;
}
assert_return(c, -EINVAL);
if (kc) {
return 0;
}
return kbdctx_new(out, c);
}
/*
* Keyboard Devices
*/
bool idev_is_keyboard(idev_device *d) {
return d && d->vtable == &keyboard_vtable;
}
char *kname;
assert_return(s, NULL);
}
idev_device *d = &k->device;
int r;
if (r < 0)
log_debug("idev-keyboard: %s/%s: error while raising data event: %s",
return r;
}
int r;
if (usecs != 0) {
if (r >= 0)
} else {
}
}
idev_keyboard *k = userdata;
keyboard_arm(k, k->repeat_rate);
return keyboard_raise_data(k, &k->repdata);
}
idev_keyboard *k;
char *kname;
int r;
assert_return(s, -EINVAL);
if (!k)
return -ENOMEM;
d = &k->device;
/* TODO: add key-repeat configuration */
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;
&k->repeat_timer,
0,
10 * USEC_PER_MSEC,
k);
if (r < 0)
return r;
if (r < 0)
return r;
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_state_unref(k->xkb_state);
free(k);
}
static int8_t guess_ascii(struct xkb_state *state, uint32_t code, uint32_t n_syms, const uint32_t *syms) {
struct xkb_keymap *keymap;
const xkb_keysym_t *s;
int num;
return syms[0];
return s[0];
}
return -1;
}
static int keyboard_fill(idev_keyboard *k,
bool resync,
uint32_t i;
uint32_t *t;
if (!t)
return -ENOMEM;
if (!t)
return -ENOMEM;
if (!t)
return -ENOMEM;
if (!t)
return -ENOMEM;
}
kev->consumed_mods = 0;
for (i = 0; i < n_syms; ++i) {
if (!kev->codepoints[i])
}
for (i = 0; i < IDEV_KBDMOD_CNT; ++i) {
int r;
continue;
if (r > 0)
if (r > 0)
}
return 0;
}
static void keyboard_repeat(idev_keyboard *k) {
const xkb_keysym_t *keysyms;
idev_device *d = &k->device;
bool repeats;
int r, num;
/*
* 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;
}
/*
* We received an event for the key we currently repeat. If it
* was released, stop key-repeat. Otherwise, ignore the event.
*/
k->repeating = false;
keyboard_arm(k, 0);
}
/*
* 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;
if (num < 0)
else
if (r < 0) {
log_debug("idev-keyboard: %s/%s: cannot set key-repeat: %s",
k->repeating = false;
keyboard_arm(k, 0);
} else {
k->repeating = true;
keyboard_arm(k, k->repeat_delay);
}
/*
* 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;
if (num < 0)
else
if (r < 0) {
log_debug("idev-keyboard: %s/%s: cannot update key-repeat: %s",
k->repeating = false;
keyboard_arm(k, 0);
}
}
}
enum xkb_state_component compch;
const xkb_keysym_t *keysyms;
idev_device *d = &k->device;
int num, r;
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. */
if (compch & XKB_STATE_LEDS) {
/* TODO: update LEDs */
}
if (num < 0) {
r = num;
goto error;
}
if (r < 0)
goto error;
keyboard_repeat(k);
return keyboard_raise_data(k, &k->evdata);
log_debug("idev-keyboard: %s/%s: cannot handle event: %s",
k->repeating = false;
keyboard_arm(k, 0);
return 0;
}
idev_keyboard *k = keyboard_from_device(d);
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;
int r;
assert(k);
return 0;
errno = 0;
if (!state) {
goto error;
}
kbdmap_unref(k->kbdmap);
xkb_state_unref(k->xkb_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;
log_debug("idev-keyboard: %s/%s: cannot adopt new keymap: %s",
return r;
}
static int keyboard_update_kbdtbl(idev_keyboard *k) {
idev_device *d = &k->device;
int r;
assert(k);
return 0;
if (kt) {
errno = 0;
if (!compose) {
goto error;
}
}
kbdtbl_unref(k->kbdtbl);
k->xkb_compose = compose;
return 0;
log_debug("idev-keyboard: %s/%s: cannot adopt new compose table: %s",
return r;
}
static const idev_device_vtable keyboard_vtable = {
.free = keyboard_free,
.feed = keyboard_feed,
};