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>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
diff --git a/src/state.c b/src/state.c
index ebd0ca6..d130b8e 100644
--- a/src/state.c
+++ b/src/state.c
@@ -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,14 +905,61 @@ 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;
}
@@ -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;
}
diff --git a/test/state.c b/test/state.c
index 95852b2..97c2bb6 100644
--- a/test/state.c
+++ b/test/state.c
@@ -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);