state: apply control transformation on utf8/utf32 keysym strings

This is required by the specification:
http://www.x.org/releases/current/doc/kbproto/xkbproto.html#Interpreting_the_Control_Modifier
and clients expect this to happen.

https://bugs.freedesktop.org/show_bug.cgi?id=75892

Reported-by: Gatis Paeglis <gatis.paeglis@digia.com>
Signed-off-by: Ran Benita <ran234@gmail.com>
master
Ran Benita 2014-03-21 23:00:37 +02:00
parent b973d71e82
commit 3cfa7fdac8
2 changed files with 157 additions and 11 deletions

View File

@ -842,6 +842,53 @@ err:
return 0;
}
/*
* http://www.x.org/releases/current/doc/kbproto/xkbproto.html#Interpreting_the_Lock_Modifier
*/
static bool
should_do_caps_transformation(struct xkb_state *state, xkb_keycode_t kc)
{
xkb_mod_index_t caps =
xkb_keymap_mod_get_index(state->keymap, XKB_MOD_NAME_CAPS);
return
xkb_state_mod_index_is_active(state, caps, XKB_STATE_MODS_EFFECTIVE) > 0 &&
xkb_state_mod_index_is_consumed(state, kc, caps) == 0;
}
/*
* http://www.x.org/releases/current/doc/kbproto/xkbproto.html#Interpreting_the_Control_Modifier
*/
static bool
should_do_ctrl_transformation(struct xkb_state *state, xkb_keycode_t kc)
{
xkb_mod_index_t ctrl =
xkb_keymap_mod_get_index(state->keymap, XKB_MOD_NAME_CTRL);
return
xkb_state_mod_index_is_active(state, ctrl, XKB_STATE_MODS_EFFECTIVE) > 0 &&
xkb_state_mod_index_is_consumed(state, kc, ctrl) == 0;
}
/* Verbatim from libX11:src/xkb/XKBBind.c */
static char
XkbToControl(char ch)
{
char c = ch;
if ((c >= '@' && c < '\177') || c == ' ')
c &= 0x1F;
else if (c == '2')
c = '\000';
else if (c >= '3' && c <= '7')
c -= ('3' - '\033');
else if (c == '8')
c = '\177';
else if (c == '/')
c = '_' & 0x1F;
return c;
}
/**
* Provides either exactly one symbol, or XKB_KEY_NoSymbol.
*/
@ -851,7 +898,6 @@ xkb_state_key_get_one_sym(struct xkb_state *state, xkb_keycode_t kc)
const xkb_keysym_t *syms;
xkb_keysym_t sym;
int num_syms;
xkb_mod_index_t caps;
num_syms = xkb_state_key_get_syms(state, kc, &syms);
if (num_syms != 1)
@ -859,18 +905,65 @@ xkb_state_key_get_one_sym(struct xkb_state *state, xkb_keycode_t kc)
sym = syms[0];
/*
* Perform capitalization transformation, see:
* http://www.x.org/releases/current/doc/kbproto/xkbproto.html#Interpreting_the_Lock_Modifier
*/
caps = xkb_keymap_mod_get_index(state->keymap, XKB_MOD_NAME_CAPS);
if (xkb_state_mod_index_is_active(state, caps, XKB_STATE_MODS_EFFECTIVE) > 0 &&
xkb_state_mod_index_is_consumed(state, kc, caps) == 0)
if (should_do_caps_transformation(state, kc))
sym = xkb_keysym_to_upper(sym);
return sym;
}
/*
* The caps and ctrl transformations require some special handling,
* so we cannot simply use xkb_state_get_one_sym() for them.
* In particular, if Control is set, we must try very hard to find
* some layout in which the keysym is ASCII and thus can be (maybe)
* converted to a control character. libX11 allows to disable this
* behavior with the XkbLC_ControlFallback (see XkbSetXlibControls(3)),
* but it is enabled by default, yippee.
*/
static xkb_keysym_t
get_one_sym_for_string(struct xkb_state *state, xkb_keycode_t kc)
{
xkb_level_index_t level;
xkb_layout_index_t layout, num_layouts;
const xkb_keysym_t *syms;
int nsyms;
xkb_keysym_t sym;
layout = xkb_state_key_get_layout(state, kc);
num_layouts = xkb_keymap_num_layouts_for_key(state->keymap, kc);
level = xkb_state_key_get_level(state, kc, layout);
if (layout == XKB_LAYOUT_INVALID || num_layouts == 0 ||
level == XKB_LEVEL_INVALID)
return XKB_KEY_NoSymbol;
nsyms = xkb_keymap_key_get_syms_by_level(state->keymap, kc,
layout, level, &syms);
if (nsyms != 1)
return XKB_KEY_NoSymbol;
sym = syms[0];
if (should_do_ctrl_transformation(state, kc) && sym > 127u) {
for (xkb_layout_index_t i = 0; i < num_layouts; i++) {
level = xkb_state_key_get_level(state, kc, i);
if (level == XKB_LEVEL_INVALID)
continue;
nsyms = xkb_keymap_key_get_syms_by_level(state->keymap, kc,
i, level, &syms);
if (nsyms == 1 && syms[0] <= 127u) {
sym = syms[0];
break;
}
}
}
if (should_do_caps_transformation(state, kc)) {
sym = xkb_keysym_to_upper(sym);
}
return sym;
}
XKB_EXPORT int
xkb_state_key_get_utf8(struct xkb_state *state, xkb_keycode_t kc,
char *buffer, size_t size)
@ -881,8 +974,7 @@ xkb_state_key_get_utf8(struct xkb_state *state, xkb_keycode_t kc,
int offset;
char tmp[7];
/* Make sure the keysym transformations are applied. */
sym = xkb_state_key_get_one_sym(state, kc);
sym = get_one_sym_for_string(state, kc);
if (sym != XKB_KEY_NoSymbol) {
nsyms = 1; syms = &sym;
}
@ -910,6 +1002,10 @@ xkb_state_key_get_utf8(struct xkb_state *state, xkb_keycode_t kc,
if (!is_valid_utf8(buffer, offset))
goto err_bad;
if (offset == 1 && (unsigned int) buffer[0] <= 127u &&
should_do_ctrl_transformation(state, kc))
buffer[0] = XkbToControl(buffer[0]);
return offset;
err_trunc:
@ -929,9 +1025,12 @@ xkb_state_key_get_utf32(struct xkb_state *state, xkb_keycode_t kc)
xkb_keysym_t sym;
uint32_t cp;
sym = xkb_state_key_get_one_sym(state, kc);
sym = get_one_sym_for_string(state, kc);
cp = xkb_keysym_to_utf32(sym);
if (cp <= 127u && should_do_ctrl_transformation(state, kc))
cp = (uint32_t) XkbToControl((char) cp);
return cp;
}

View File

@ -503,6 +503,52 @@ test_get_utf8_utf32(struct xkb_keymap *keymap)
xkb_state_unref(state);
}
static void
test_ctrl_string_transformation(struct xkb_keymap *keymap)
{
char buf[256];
struct xkb_state *state = xkb_state_new(keymap);
xkb_mod_index_t ctrl;
assert(state);
/* See xkb_state_key_get_utf8() for what's this all about. */
ctrl = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_CTRL);
assert(ctrl != XKB_MOD_INVALID);
/* First without. */
TEST_KEY(KEY_A, "a", 0x61);
TEST_KEY(KEY_B, "b", 0x62);
TEST_KEY(KEY_C, "c", 0x63);
TEST_KEY(KEY_ESC, "\x1B", 0x1B);
TEST_KEY(KEY_1, "1", 0x31);
/* And with. */
xkb_state_update_key(state, KEY_RIGHTCTRL + EVDEV_OFFSET, XKB_KEY_DOWN);
assert(xkb_state_mod_index_is_active(state, ctrl, XKB_STATE_MODS_EFFECTIVE) > 0);
TEST_KEY(KEY_A, "\x01", 0x01);
TEST_KEY(KEY_B, "\x02", 0x02);
TEST_KEY(KEY_C, "\x03", 0x03);
TEST_KEY(KEY_ESC, "\x1B", 0x1B);
TEST_KEY(KEY_1, "1", 0x31);
xkb_state_update_key(state, KEY_RIGHTCTRL + EVDEV_OFFSET, XKB_KEY_UP);
/* Switch to ru layout */
xkb_state_update_key(state, KEY_COMPOSE + EVDEV_OFFSET, XKB_KEY_DOWN);
xkb_state_update_key(state, KEY_COMPOSE + EVDEV_OFFSET, XKB_KEY_UP);
assert(xkb_state_key_get_layout(state, KEY_A + 8) == 1);
/* Non ASCII. */
xkb_state_update_key(state, KEY_RIGHTCTRL + EVDEV_OFFSET, XKB_KEY_DOWN);
assert(xkb_state_mod_index_is_active(state, ctrl, XKB_STATE_MODS_EFFECTIVE) > 0);
TEST_KEY(KEY_A, "\x01", 0x01);
TEST_KEY(KEY_B, "\x02", 0x02);
xkb_state_update_key(state, KEY_RIGHTCTRL + EVDEV_OFFSET, XKB_KEY_UP);
xkb_state_unref(state);
}
int
main(void)
{
@ -525,6 +571,7 @@ main(void)
test_consume(keymap);
test_range(keymap);
test_get_utf8_utf32(keymap);
test_ctrl_string_transformation(keymap);
xkb_keymap_unref(keymap);
keymap = test_compile_rules(context, "evdev", NULL, "ch", "fr", NULL);