kmscon/src/uterm_input_uxkb.c
David Herrmann a11d71a987 uterm: input: forward xkbcommon log messages
Instead of letting xkbcommon write to stderr, we now forward these
messages via the llog handler.

Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
2013-11-03 12:25:18 +01:00

480 lines
12 KiB
C

/*
* uterm - Linux User-Space Terminal
*
* Copyright (c) 2011 Ran Benita <ran234@gmail.com>
* Copyright (c) 2012-2013 David Herrmann <dh.herrmann@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <errno.h>
#include <inttypes.h>
#include <linux/input.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <xkbcommon/xkbcommon.h>
#include "shl_hook.h"
#include "shl_llog.h"
#include "shl_misc.h"
#include "uterm_input.h"
#include "uterm_input_internal.h"
#define LLOG_SUBSYSTEM "uterm_uxkb"
extern const char _binary_src_uterm_input_fallback_xkb_bin_start[];
extern const char _binary_src_uterm_input_fallback_xkb_bin_end[];
static void uxkb_log(struct xkb_context *context, enum xkb_log_level level,
const char *format, va_list args)
{
struct uterm_input *input;
unsigned int sev;
input = xkb_context_get_user_data(context);
if (!input->llog)
return;
switch (level) {
case XKB_LOG_LEVEL_CRITICAL:
sev = LLOG_CRITICAL;
break;
case XKB_LOG_LEVEL_ERROR:
sev = LLOG_ERROR;
break;
case XKB_LOG_LEVEL_WARNING:
sev = LLOG_WARNING;
break;
case XKB_LOG_LEVEL_INFO:
sev = LLOG_INFO;
break;
case XKB_LOG_LEVEL_DEBUG:
/* fallthrough */
default:
sev = LLOG_DEBUG;
break;
}
input->llog(input->llog_data,
LLOG_DEFAULT,
sev,
format,
args);
}
int uxkb_desc_init(struct uterm_input *input,
const char *model,
const char *layout,
const char *variant,
const char *options,
const char *keymap)
{
int ret;
struct xkb_rule_names rmlvo = {
.rules = "evdev",
.model = model,
.layout = layout,
.variant = variant,
.options = options,
};
const char *fallback;
fallback = _binary_src_uterm_input_fallback_xkb_bin_start;
input->ctx = xkb_context_new(0);
if (!input->ctx) {
llog_error(input, "cannot create XKB context");
return -ENOMEM;
}
/* Set logging function. You can use XKB_LOG_VERBOSITY and XKB_LOG_LEVEL
* to change the xkbcommon logger. That's why we don't touch the
* verbosity and level here. */
xkb_context_set_user_data(input->ctx, input);
xkb_context_set_log_fn(input->ctx, uxkb_log);
/* If a complete keymap file was given, first try that. */
if (keymap && *keymap) {
input->keymap = xkb_keymap_new_from_string(input->ctx,
keymap, XKB_KEYMAP_FORMAT_TEXT_V1, 0);
if (input->keymap) {
llog_debug(input,
"new keyboard description from memory");
return 0;
}
llog_warn(input, "cannot parse keymap, reverting to rmlvo");
}
input->keymap = xkb_keymap_new_from_names(input->ctx, &rmlvo, 0);
if (!input->keymap) {
llog_warn(input, "failed to create keymap (%s, %s, %s, %s), "
"reverting to default system keymap",
model, layout, variant, options);
rmlvo.model = "";
rmlvo.layout = "";
rmlvo.variant = "";
rmlvo.options = "";
input->keymap = xkb_keymap_new_from_names(input->ctx,
&rmlvo, 0);
if (!input->keymap) {
llog_warn(input, "failed to create XKB default keymap, "
"reverting to built-in fallback");
input->keymap = xkb_keymap_new_from_string(input->ctx,
fallback, XKB_KEYMAP_FORMAT_TEXT_V1, 0);
if (!input->keymap) {
llog_error(input,
"cannot create fallback keymap");
ret = -EFAULT;
goto err_ctx;
}
}
llog_debug(input, "new fallback keyboard description");
} else {
llog_debug(input, "new keyboard description (%s, %s, %s, %s)",
model, layout, variant, options);
}
return 0;
err_ctx:
xkb_context_unref(input->ctx);
return ret;
}
void uxkb_desc_destroy(struct uterm_input *input)
{
xkb_keymap_unref(input->keymap);
xkb_context_unref(input->ctx);
}
static void timer_event(struct ev_timer *timer, uint64_t num, void *data)
{
struct uterm_input_dev *dev = data;
dev->repeat_event.handled = false;
shl_hook_call(dev->input->hook, dev->input, &dev->repeat_event);
}
int uxkb_dev_init(struct uterm_input_dev *dev)
{
int ret;
ret = ev_eloop_new_timer(dev->input->eloop, &dev->repeat_timer, NULL,
timer_event, dev);
if (ret)
return ret;
dev->state = xkb_state_new(dev->input->keymap);
if (!dev->state) {
llog_error(dev->input, "cannot create XKB state");
ret = -ENOMEM;
goto err_timer;
}
return 0;
err_timer:
ev_eloop_rm_timer(dev->repeat_timer);
return ret;
}
void uxkb_dev_destroy(struct uterm_input_dev *dev)
{
xkb_state_unref(dev->state);
ev_eloop_rm_timer(dev->repeat_timer);
}
#define EVDEV_KEYCODE_OFFSET 8
enum {
KEY_RELEASED = 0,
KEY_PRESSED = 1,
KEY_REPEATED = 2,
};
static void uxkb_dev_update_keyboard_leds(struct uterm_input_dev *dev)
{
static const struct {
int evdev_led;
const char *xkb_led;
} leds[] = {
{ LED_NUML, XKB_LED_NAME_NUM },
{ LED_CAPSL, XKB_LED_NAME_CAPS },
{ LED_SCROLLL, XKB_LED_NAME_SCROLL },
};
struct input_event events[sizeof(leds) / sizeof(*leds)];
int i, ret;
if (!(dev->capabilities & UTERM_DEVICE_HAS_LEDS))
return;
memset(events, 0, sizeof(events));
for (i = 0; i < sizeof(leds) / sizeof(*leds); i++) {
events[i].type = EV_LED;
events[i].code = leds[i].evdev_led;
if (xkb_state_led_name_is_active(dev->state,
leds[i].xkb_led) > 0)
events[i].value = 1;
}
ret = write(dev->rfd, events, sizeof(events));
if (ret != sizeof(events))
llog_warning(dev->input, "cannot update LED state (%d): %m",
errno);
}
static inline int uxkb_dev_resize_event(struct uterm_input_dev *dev, size_t s)
{
uint32_t *tmp;
if (s > dev->num_syms) {
tmp = realloc(dev->event.keysyms,
sizeof(uint32_t) * s);
if (!tmp) {
llog_warning(dev->input,
"cannot reallocate keysym buffer");
return -ENOKEY;
}
dev->event.keysyms = tmp;
tmp = realloc(dev->event.codepoints,
sizeof(uint32_t) * s);
if (!tmp) {
llog_warning(dev->input,
"cannot reallocate codepoints buffer");
return -ENOKEY;
}
dev->event.codepoints = tmp;
tmp = realloc(dev->repeat_event.keysyms,
sizeof(uint32_t) * s);
if (!tmp) {
llog_warning(dev->input,
"cannot reallocate keysym buffer");
return -ENOKEY;
}
dev->repeat_event.keysyms = tmp;
tmp = realloc(dev->repeat_event.codepoints,
sizeof(uint32_t) * s);
if (!tmp) {
llog_warning(dev->input,
"cannot reallocate codepoints buffer");
return -ENOKEY;
}
dev->repeat_event.codepoints = tmp;
dev->num_syms = s;
}
return 0;
}
static int uxkb_dev_fill_event(struct uterm_input_dev *dev,
struct uterm_input_event *ev,
xkb_keycode_t code,
int num_syms,
const xkb_keysym_t *syms)
{
int ret, i;
ret = uxkb_dev_resize_event(dev, num_syms);
if (ret)
return ret;
ev->keycode = code;
ev->ascii = shl_get_ascii(dev->state, code, syms, num_syms);
ev->mods = shl_get_xkb_mods(dev->state);
ev->num_syms = num_syms;
memcpy(ev->keysyms, syms, sizeof(uint32_t) * num_syms);
for (i = 0; i < num_syms; ++i) {
ev->codepoints[i] = xkb_keysym_to_utf32(syms[i]);
if (!ev->codepoints[i])
ev->codepoints[i] = UTERM_INPUT_INVALID;
}
return 0;
}
static void uxkb_dev_repeat(struct uterm_input_dev *dev, unsigned int state)
{
struct xkb_keymap *keymap = xkb_state_get_keymap(dev->state);
unsigned int i;
int num_keysyms, ret;
const uint32_t *keysyms;
struct itimerspec spec;
if (dev->repeating && dev->repeat_event.keycode == dev->event.keycode) {
if (state == KEY_RELEASED) {
dev->repeating = false;
ev_timer_update(dev->repeat_timer, NULL);
}
return;
}
if (state == KEY_PRESSED &&
xkb_keymap_key_repeats(keymap, dev->event.keycode)) {
dev->repeat_event.keycode = dev->event.keycode;
dev->repeat_event.ascii = dev->event.ascii;
dev->repeat_event.mods = dev->event.mods;
dev->repeat_event.num_syms = dev->event.num_syms;
for (i = 0; i < dev->event.num_syms; ++i) {
dev->repeat_event.keysyms[i] = dev->event.keysyms[i];
dev->repeat_event.codepoints[i] =
dev->event.codepoints[i];
}
} else if (dev->repeating &&
!xkb_keymap_key_repeats(keymap, dev->event.keycode)) {
num_keysyms = xkb_state_key_get_syms(dev->state,
dev->repeat_event.keycode,
&keysyms);
if (num_keysyms <= 0)
return;
ret = uxkb_dev_fill_event(dev, &dev->repeat_event,
dev->repeat_event.keycode,
num_keysyms, keysyms);
if (ret)
return;
return;
} else {
return;
}
dev->repeating = true;
spec.it_interval.tv_sec = 0;
spec.it_interval.tv_nsec = dev->input->repeat_rate * 1000000;
spec.it_value.tv_sec = 0;
spec.it_value.tv_nsec = dev->input->repeat_delay * 1000000;
ev_timer_update(dev->repeat_timer, &spec);
}
int uxkb_dev_process(struct uterm_input_dev *dev,
uint16_t key_state, uint16_t code)
{
struct xkb_state *state;
xkb_keycode_t keycode;
const xkb_keysym_t *keysyms;
int num_keysyms, ret;
enum xkb_state_component changed;
if (key_state == KEY_REPEATED)
return -ENOKEY;
state = dev->state;
keycode = code + EVDEV_KEYCODE_OFFSET;
num_keysyms = xkb_state_key_get_syms(state, keycode, &keysyms);
changed = 0;
if (key_state == KEY_PRESSED)
changed = xkb_state_update_key(state, keycode, XKB_KEY_DOWN);
else if (key_state == KEY_RELEASED)
changed = xkb_state_update_key(state, keycode, XKB_KEY_UP);
if (changed & XKB_STATE_LEDS)
uxkb_dev_update_keyboard_leds(dev);
if (num_keysyms <= 0)
return -ENOKEY;
ret = uxkb_dev_fill_event(dev, &dev->event, keycode, num_keysyms,
keysyms);
if (ret)
return -ENOKEY;
uxkb_dev_repeat(dev, key_state);
if (key_state == KEY_RELEASED)
return -ENOKEY;
dev->event.handled = false;
shl_hook_call(dev->input->hook, dev->input, &dev->event);
return 0;
}
void uxkb_dev_sleep(struct uterm_input_dev *dev)
{
/*
* While the device is asleep, we don't receive key events. This
* means that when we wake up, the keyboard state may be different
* (e.g. some key is pressed but we don't know about it). This can
* cause various problems, like stuck modifiers: consider if we
* miss a release of the left Shift key. When the user presses it
* again, xkb_state_update_key() will think there is *another* left
* Shift key that was pressed. When the key is released, it's as if
* this "second" key was released, but the "first" is still left
* pressed.
* To handle this, when the device goes to sleep, we save our
* current knowledge of the keyboard's press/release state. On wake
* up, we compare the states before and after, and just feed
* xkb_state_update_key() the deltas.
*/
memset(dev->key_state_bits, 0, sizeof(dev->key_state_bits));
errno = 0;
ioctl(dev->rfd, EVIOCGKEY(sizeof(dev->key_state_bits)),
dev->key_state_bits);
if (errno)
llog_warn(dev->input, "failed to save keyboard state (%d): %m",
errno);
}
void uxkb_dev_wake_up(struct uterm_input_dev *dev)
{
uint32_t code;
char *old_bits, cur_bits[sizeof(dev->key_state_bits)];
char old_bit, cur_bit;
old_bits = dev->key_state_bits;
memset(cur_bits, 0, sizeof(cur_bits));
errno = 0;
ioctl(dev->rfd, EVIOCGKEY(sizeof(cur_bits)), cur_bits);
if (errno) {
llog_warn(dev->input,
"failed to get current keyboard state (%d): %m",
errno);
return;
}
for (code = 0; code < KEY_CNT; code++) {
old_bit = (old_bits[code / 8] & (1 << (code % 8)));
cur_bit = (cur_bits[code / 8] & (1 << (code % 8)));
if (old_bit == cur_bit)
continue;
xkb_state_update_key(dev->state, code + EVDEV_KEYCODE_OFFSET,
cur_bit ? XKB_KEY_DOWN : XKB_KEY_UP);
}
uxkb_dev_update_keyboard_leds(dev);
}