summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/cocoa/window.m73
-rw-r--r--src/common/backend.c72
-rw-r--r--src/common/keyboard.c399
-rw-r--r--src/common/keyboard.h18
-rw-r--r--src/common/meson.build2
-rw-r--r--src/common/utf8.c50
-rw-r--r--src/common/utf8.h14
-rw-r--r--src/wayland/clipboard.c8
-rw-r--r--src/wayland/ime.c2
-rw-r--r--src/wayland/internal.h19
-rw-r--r--src/wayland/keyboard.c94
-rw-r--r--src/wayland/pointer.c126
-rw-r--r--src/x11/window.c102
13 files changed, 788 insertions, 191 deletions
diff --git a/src/cocoa/window.m b/src/cocoa/window.m
index 645ae07..028ba64 100644
--- a/src/cocoa/window.m
+++ b/src/cocoa/window.m
@@ -7,6 +7,7 @@
#include "common/timing.h"
#include "common/loop.h"
#include "cocoa/keycodes.h"
+#include "common/keyboard.h"
#include <assert.h>
#include <math.h>
@@ -67,10 +68,6 @@ struct cocoa_surface {
char *preedit;
struct yasl_rect2i ime_caret;
- char *key_text;
- size_t key_text_capacity;
- size_t key_text_len;
- bool in_key_down;
struct yasl_vector2i size;
uint32_t scale_120;
@@ -269,23 +266,35 @@ static unsigned cocoa_mods(NSEventModifierFlags m)
return mods;
}
+/* Decode a single Unicode scalar, never a lone UTF-16 surrogate. Layout labels
+ * are optional: multi-scalar mappings and native function-key characters are 0. */
+static uint32_t cocoa_key_label(NSString *characters)
+{
+ if (!characters.length) return 0;
+ unichar first = [characters characterAtIndex:0];
+ if (characters.length == 1)
+ return yasl_key_label(first);
+ if (characters.length == 2 && first >= 0xd800 && first <= 0xdbff) {
+ unichar second = [characters characterAtIndex:1];
+ if (second >= 0xdc00 && second <= 0xdfff)
+ return yasl_key_label(0x10000 + ((first - 0xd800) << 10) + second - 0xdc00);
+ }
+ return 0;
+}
+
static struct yasl_key_event cocoa_key_event(NSEvent *ev, enum yasl_key_action action)
{
- unsigned short vk = ev.keyCode;
struct yasl_key_event k = {
- .key = vk,
- .keypad = (vk >= kVK_ANSI_Keypad0 && vk <= kVK_ANSI_Keypad9) ||
- vk == kVK_ANSI_KeypadDecimal || vk == kVK_ANSI_KeypadMultiply ||
- vk == kVK_ANSI_KeypadPlus || vk == kVK_ANSI_KeypadClear ||
- vk == kVK_ANSI_KeypadDivide || vk == kVK_ANSI_KeypadEnter ||
- vk == kVK_ANSI_KeypadMinus || vk == kVK_ANSI_KeypadEquals,
+ .key = yasl_key_from_cocoa(ev.keyCode),
.action = action,
.mods = cocoa_mods(ev.modifierFlags),
- .base = vk,
+ .native_code = ev.keyCode,
};
- NSString *chars = ev.characters;
- if (chars && chars.length > 0)
- k.text = [chars characterAtIndex:0];
+ /* characters properties are invalid on flagsChanged events. */
+ if (ev.type == NSEventTypeKeyDown || ev.type == NSEventTypeKeyUp) {
+ k.codepoint = cocoa_key_label([ev charactersByApplyingModifiers:0]);
+ k.shifted = cocoa_key_label([ev charactersByApplyingModifiers:NSEventModifierFlagShift]);
+ }
return k;
}
@@ -741,9 +750,7 @@ static struct yasl_vector2i dragging_pos(YaslView *view, id<NSDraggingInfo> send
struct cocoa_surface *w = self.surface;
if (!w) return;
on_key_event(w, event, event.isARepeat ? YASL_KEY_REPEAT : YASL_KEY_PRESS);
- NSString *chars = event.characters;
- if (chars && chars.length > 0)
- on_text_input(w, chars.UTF8String, strlen(chars.UTF8String));
+ [self interpretKeyEvents:@[event]];
}
- (void)keyUp:(NSEvent *)event
@@ -818,28 +825,25 @@ static struct yasl_vector2i dragging_pos(YaslView *view, id<NSDraggingInfo> send
{
struct cocoa_surface *w = self.surface;
if (!w) return;
- double dx = event.scrollingDeltaX, dy = event.scrollingDeltaY;
+ double dx = -event.scrollingDeltaX, dy = -event.scrollingDeltaY;
if (event.hasPreciseScrollingDeltas) {
dx /= SCROLL_POINTS_PER_LINE;
dy /= SCROLL_POINTS_PER_LINE;
}
+ if (!isfinite(dx) || !isfinite(dy) || (dx == 0 && dy == 0)) return;
w->mods = cocoa_mods(event.modifierFlags);
w->pointer = event_pos(w, event);
- if (dy != 0.0) {
- struct yasl_input_event input_ev = {
- .kind = YASL_INPUT_MOUSE,
- .surface = &w->base,
- .mouse = {
- .pos = w->pointer,
- .button = dy > 0.0 ? 4 : 5,
- .kind = YASL_PTR_SCROLL,
- .mods = w->mods,
- .scroll_delta = (float)dy,
- .scroll_lines = (int)dy,
- },
- };
- input_queue_push(&w->input, &input_ev);
- }
+ struct yasl_input_event input_ev = {
+ .kind = YASL_INPUT_MOUSE,
+ .surface = &w->base,
+ .mouse = {
+ .pos = w->pointer,
+ .kind = YASL_PTR_SCROLL,
+ .mods = w->mods,
+ .scroll = {dx, dy},
+ },
+ };
+ input_queue_push(&w->input, &input_ev);
}
static NSString *plain_string(id string)
@@ -1409,7 +1413,6 @@ static void surface_free(struct cocoa_surface *w)
[w->view release];
[w->controller release];
[w->window release];
- free(w->key_text);
free(w->preedit);
free(w->pixels);
free(w);
diff --git a/src/common/backend.c b/src/common/backend.c
index ccb9c27..23275d6 100644
--- a/src/common/backend.c
+++ b/src/common/backend.c
@@ -81,10 +81,18 @@ uint32_t yasl_display_caps(const struct yasl_display *d)
int yasl_display_dispatch(struct yasl_display *d, int timeout_ms)
{
- if (!d) return -1;
- if (d->ops->display_dispatch_timeout)
- return d->ops->display_dispatch_timeout(d, timeout_ms);
- return -1;
+ if (!d) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (!d->ops->display_dispatch_timeout) {
+ errno = ENOTSUP;
+ return -1;
+ }
+ errno = 0;
+ int result = d->ops->display_dispatch_timeout(d, timeout_ms);
+ if (result < 0 && !errno) errno = EIO;
+ return result;
}
void yasl_display_flush(struct yasl_display *d)
@@ -127,7 +135,12 @@ bool yasl_display_drag_start(struct yasl_display *d, struct yasl_surface *origin
return 0;
if (!origin || origin->display != d)
return false;
- return d->ops->display_drag_start(d, origin, image, l, ud);
+ if (image && (!image->pixels || image->size.x <= 0 || image->size.y <= 0 ||
+ image->size.x > 32767 || image->size.y > 32767 ||
+ (int64_t)image->size.x * image->size.y * 4 > INT32_MAX))
+ return false;
+ const struct yasl_drag_listener empty = {0};
+ return d->ops->display_drag_start(d, origin, image, l ? l : &empty, ud);
}
void yasl_display_drag_cancel(struct yasl_display *d)
@@ -151,7 +164,10 @@ struct yasl_surface *yasl_create_toplevel(struct yasl_display *d,
errno = ENOTSUP;
return NULL;
}
- return d->ops->surface_create_toplevel(d, spec, graphics_device);
+ errno = 0;
+ struct yasl_surface *s = d->ops->surface_create_toplevel(d, spec, graphics_device);
+ if (!s && !errno) errno = ENODEV;
+ return s;
}
struct yasl_surface *yasl_surface_create_toplevel(struct yasl_display *d,
@@ -160,18 +176,50 @@ struct yasl_surface *yasl_surface_create_toplevel(struct yasl_display *d,
return yasl_create_toplevel(d, spec, NULL);
}
+static bool popup_spec_valid(const struct yasl_popup_spec *spec)
+{
+ return spec && spec->size.x > 0 && spec->size.y > 0 &&
+ spec->size.x <= 32767 && spec->size.y <= 32767 &&
+ (int64_t)spec->size.x * spec->size.y * 4 <= INT32_MAX &&
+ spec->anchor.width >= 0 && spec->anchor.height >= 0 &&
+ spec->content_offset.x >= 0 && spec->content_offset.y >= 0 &&
+ spec->content_size.x >= 0 && spec->content_size.y >= 0 &&
+ (int64_t)spec->content_offset.x + spec->content_size.x <= spec->size.x &&
+ (int64_t)spec->content_offset.y + spec->content_size.y <= spec->size.y;
+}
+
struct yasl_surface *yasl_surface_create_popup(struct yasl_surface *parent, const struct yasl_popup_spec *spec)
{
- if (!parent || !parent->display->ops->surface_create_popup)
- return 0;
- return parent->display->ops->surface_create_popup(parent, spec);
+ if (!parent || !popup_spec_valid(spec)) {
+ errno = EINVAL;
+ return NULL;
+ }
+ if (!(yasl_display_caps(parent->display) & YASL_CAP_POPUPS) ||
+ !parent->display->ops->surface_create_popup) {
+ errno = ENOTSUP;
+ return NULL;
+ }
+ errno = 0;
+ struct yasl_surface *s = parent->display->ops->surface_create_popup(parent, spec);
+ if (!s && !errno) errno = ENODEV;
+ return s;
}
bool yasl_surface_popup_reposition(struct yasl_surface *popup, const struct yasl_popup_spec *spec)
{
- if (!popup || !popup->display->ops->surface_popup_reposition)
- return 0;
- return popup->display->ops->surface_popup_reposition(popup, spec);
+ if (!popup || !popup_spec_valid(spec)) {
+ errno = EINVAL;
+ return false;
+ }
+ if (!(yasl_display_caps(popup->display) & YASL_CAP_POPUPS) ||
+ !popup->display->ops->surface_popup_reposition) {
+ errno = ENOTSUP;
+ return false;
+ }
+ errno = 0;
+ bool result = popup->display->ops->surface_popup_reposition(popup, spec);
+ if (!result && !errno) errno = ENODEV;
+ return result;
}
void yasl_surface_destroy(struct yasl_surface *s)
diff --git a/src/common/keyboard.c b/src/common/keyboard.c
new file mode 100644
index 0000000..80c6702
--- /dev/null
+++ b/src/common/keyboard.c
@@ -0,0 +1,399 @@
+#include "keyboard.h"
+#include "cocoa/keycodes.h"
+#include <string.h>
+
+/* Numeric Linux evdev codes; no platform headers are needed by this table. */
+enum yasl_key yasl_key_from_evdev(uint32_t code)
+{
+ switch (code) {
+ case 1: return YASL_KEY_ESCAPE; /* KEY_ESC */
+ case 2: return YASL_KEY_1; /* KEY_1 */
+ case 3: return YASL_KEY_2; /* KEY_2 */
+ case 4: return YASL_KEY_3; /* KEY_3 */
+ case 5: return YASL_KEY_4; /* KEY_4 */
+ case 6: return YASL_KEY_5; /* KEY_5 */
+ case 7: return YASL_KEY_6; /* KEY_6 */
+ case 8: return YASL_KEY_7; /* KEY_7 */
+ case 9: return YASL_KEY_8; /* KEY_8 */
+ case 10: return YASL_KEY_9; /* KEY_9 */
+ case 11: return YASL_KEY_0; /* KEY_0 */
+ case 12: return YASL_KEY_MINUS; /* KEY_MINUS */
+ case 13: return YASL_KEY_EQUAL; /* KEY_EQUAL */
+ case 14: return YASL_KEY_BACKSPACE; /* KEY_BACKSPACE */
+ case 15: return YASL_KEY_TAB; /* KEY_TAB */
+ case 16: return YASL_KEY_Q; /* KEY_Q */
+ case 17: return YASL_KEY_W; /* KEY_W */
+ case 18: return YASL_KEY_E; /* KEY_E */
+ case 19: return YASL_KEY_R; /* KEY_R */
+ case 20: return YASL_KEY_T; /* KEY_T */
+ case 21: return YASL_KEY_Y; /* KEY_Y */
+ case 22: return YASL_KEY_U; /* KEY_U */
+ case 23: return YASL_KEY_I; /* KEY_I */
+ case 24: return YASL_KEY_O; /* KEY_O */
+ case 25: return YASL_KEY_P; /* KEY_P */
+ case 26: return YASL_KEY_LEFT_BRACKET; /* KEY_LEFTBRACE */
+ case 27: return YASL_KEY_RIGHT_BRACKET; /* KEY_RIGHTBRACE */
+ case 28: return YASL_KEY_ENTER; /* KEY_ENTER */
+ case 29: return YASL_KEY_LEFT_CONTROL; /* KEY_LEFTCTRL */
+ case 30: return YASL_KEY_A; /* KEY_A */
+ case 31: return YASL_KEY_S; /* KEY_S */
+ case 32: return YASL_KEY_D; /* KEY_D */
+ case 33: return YASL_KEY_F; /* KEY_F */
+ case 34: return YASL_KEY_G; /* KEY_G */
+ case 35: return YASL_KEY_H; /* KEY_H */
+ case 36: return YASL_KEY_J; /* KEY_J */
+ case 37: return YASL_KEY_K; /* KEY_K */
+ case 38: return YASL_KEY_L; /* KEY_L */
+ case 39: return YASL_KEY_SEMICOLON; /* KEY_SEMICOLON */
+ case 40: return YASL_KEY_APOSTROPHE; /* KEY_APOSTROPHE */
+ case 41: return YASL_KEY_GRAVE; /* KEY_GRAVE */
+ case 42: return YASL_KEY_LEFT_SHIFT; /* KEY_LEFTSHIFT */
+ case 43: return YASL_KEY_BACKSLASH; /* KEY_BACKSLASH */
+ case 44: return YASL_KEY_Z; /* KEY_Z */
+ case 45: return YASL_KEY_X; /* KEY_X */
+ case 46: return YASL_KEY_C; /* KEY_C */
+ case 47: return YASL_KEY_V; /* KEY_V */
+ case 48: return YASL_KEY_B; /* KEY_B */
+ case 49: return YASL_KEY_N; /* KEY_N */
+ case 50: return YASL_KEY_M; /* KEY_M */
+ case 51: return YASL_KEY_COMMA; /* KEY_COMMA */
+ case 52: return YASL_KEY_PERIOD; /* KEY_DOT */
+ case 53: return YASL_KEY_SLASH; /* KEY_SLASH */
+ case 54: return YASL_KEY_RIGHT_SHIFT; /* KEY_RIGHTSHIFT */
+ case 55: return YASL_KEY_KP_MULTIPLY; /* KEY_KPASTERISK */
+ case 56: return YASL_KEY_LEFT_ALT; /* KEY_LEFTALT */
+ case 57: return YASL_KEY_SPACE; /* KEY_SPACE */
+ case 58: return YASL_KEY_CAPS_LOCK; /* KEY_CAPSLOCK */
+ case 59: return YASL_KEY_F1; /* KEY_F1 */
+ case 60: return YASL_KEY_F2; /* KEY_F2 */
+ case 61: return YASL_KEY_F3; /* KEY_F3 */
+ case 62: return YASL_KEY_F4; /* KEY_F4 */
+ case 63: return YASL_KEY_F5; /* KEY_F5 */
+ case 64: return YASL_KEY_F6; /* KEY_F6 */
+ case 65: return YASL_KEY_F7; /* KEY_F7 */
+ case 66: return YASL_KEY_F8; /* KEY_F8 */
+ case 67: return YASL_KEY_F9; /* KEY_F9 */
+ case 68: return YASL_KEY_F10; /* KEY_F10 */
+ case 69: return YASL_KEY_NUM_LOCK; /* KEY_NUMLOCK */
+ case 70: return YASL_KEY_SCROLL_LOCK; /* KEY_SCROLLLOCK */
+ case 71: return YASL_KEY_KP_7; /* KEY_KP7 */
+ case 72: return YASL_KEY_KP_8; /* KEY_KP8 */
+ case 73: return YASL_KEY_KP_9; /* KEY_KP9 */
+ case 74: return YASL_KEY_KP_SUBTRACT; /* KEY_KPMINUS */
+ case 75: return YASL_KEY_KP_4; /* KEY_KP4 */
+ case 76: return YASL_KEY_KP_5; /* KEY_KP5 */
+ case 77: return YASL_KEY_KP_6; /* KEY_KP6 */
+ case 78: return YASL_KEY_KP_ADD; /* KEY_KPPLUS */
+ case 79: return YASL_KEY_KP_1; /* KEY_KP1 */
+ case 80: return YASL_KEY_KP_2; /* KEY_KP2 */
+ case 81: return YASL_KEY_KP_3; /* KEY_KP3 */
+ case 82: return YASL_KEY_KP_0; /* KEY_KP0 */
+ case 83: return YASL_KEY_KP_DECIMAL; /* KEY_KPDOT */
+ case 86: return YASL_KEY_ISO_BACKSLASH; /* KEY_102ND */
+ case 87: return YASL_KEY_F11; /* KEY_F11 */
+ case 88: return YASL_KEY_F12; /* KEY_F12 */
+ case 89: return YASL_KEY_RO; /* KEY_RO */
+ case 92: return YASL_KEY_CONVERT; /* KEY_HENKAN */
+ case 93: return YASL_KEY_KANA; /* KEY_KATAKANAHIRAGANA */
+ case 94: return YASL_KEY_NONCONVERT; /* KEY_MUHENKAN */
+ case 96: return YASL_KEY_KP_ENTER; /* KEY_KPENTER */
+ case 97: return YASL_KEY_RIGHT_CONTROL; /* KEY_RIGHTCTRL */
+ case 98: return YASL_KEY_KP_DIVIDE; /* KEY_KPSLASH */
+ case 99: return YASL_KEY_PRINT_SCREEN; /* KEY_SYSRQ */
+ case 100: return YASL_KEY_RIGHT_ALT; /* KEY_RIGHTALT */
+ case 102: return YASL_KEY_HOME; /* KEY_HOME */
+ case 103: return YASL_KEY_UP; /* KEY_UP */
+ case 104: return YASL_KEY_PAGE_UP; /* KEY_PAGEUP */
+ case 105: return YASL_KEY_LEFT; /* KEY_LEFT */
+ case 106: return YASL_KEY_RIGHT; /* KEY_RIGHT */
+ case 107: return YASL_KEY_END; /* KEY_END */
+ case 108: return YASL_KEY_DOWN; /* KEY_DOWN */
+ case 109: return YASL_KEY_PAGE_DOWN; /* KEY_PAGEDOWN */
+ case 110: return YASL_KEY_INSERT; /* KEY_INSERT */
+ case 111: return YASL_KEY_DELETE; /* KEY_DELETE */
+ case 113: return YASL_KEY_MUTE; /* KEY_MUTE */
+ case 114: return YASL_KEY_VOLUME_DOWN; /* KEY_VOLUMEDOWN */
+ case 115: return YASL_KEY_VOLUME_UP; /* KEY_VOLUMEUP */
+ case 117: return YASL_KEY_KP_EQUAL; /* KEY_KPEQUAL */
+ case 119: return YASL_KEY_PAUSE; /* KEY_PAUSE */
+ case 121: return YASL_KEY_KP_COMMA; /* KEY_KPCOMMA */
+ case 124: return YASL_KEY_YEN; /* KEY_YEN */
+ case 125: return YASL_KEY_LEFT_SUPER; /* KEY_LEFTMETA */
+ case 126: return YASL_KEY_RIGHT_SUPER; /* KEY_RIGHTMETA */
+ case 127: return YASL_KEY_MENU; /* KEY_COMPOSE */
+ case 183: return YASL_KEY_F13; /* KEY_F13 */
+ case 184: return YASL_KEY_F14; /* KEY_F14 */
+ case 185: return YASL_KEY_F15; /* KEY_F15 */
+ case 186: return YASL_KEY_F16; /* KEY_F16 */
+ case 187: return YASL_KEY_F17; /* KEY_F17 */
+ case 188: return YASL_KEY_F18; /* KEY_F18 */
+ case 189: return YASL_KEY_F19; /* KEY_F19 */
+ case 190: return YASL_KEY_F20; /* KEY_F20 */
+ case 191: return YASL_KEY_F21; /* KEY_F21 */
+ case 192: return YASL_KEY_F22; /* KEY_F22 */
+ case 193: return YASL_KEY_F23; /* KEY_F23 */
+ case 194: return YASL_KEY_F24; /* KEY_F24 */
+ default: return YASL_KEY_UNKNOWN;
+ }
+}
+
+enum yasl_key yasl_key_from_cocoa(uint32_t code)
+{
+ switch (code) {
+ case kVK_ANSI_A: return YASL_KEY_A;
+ case kVK_ANSI_B: return YASL_KEY_B;
+ case kVK_ANSI_C: return YASL_KEY_C;
+ case kVK_ANSI_D: return YASL_KEY_D;
+ case kVK_ANSI_E: return YASL_KEY_E;
+ case kVK_ANSI_F: return YASL_KEY_F;
+ case kVK_ANSI_G: return YASL_KEY_G;
+ case kVK_ANSI_H: return YASL_KEY_H;
+ case kVK_ANSI_I: return YASL_KEY_I;
+ case kVK_ANSI_J: return YASL_KEY_J;
+ case kVK_ANSI_K: return YASL_KEY_K;
+ case kVK_ANSI_L: return YASL_KEY_L;
+ case kVK_ANSI_M: return YASL_KEY_M;
+ case kVK_ANSI_N: return YASL_KEY_N;
+ case kVK_ANSI_O: return YASL_KEY_O;
+ case kVK_ANSI_P: return YASL_KEY_P;
+ case kVK_ANSI_Q: return YASL_KEY_Q;
+ case kVK_ANSI_R: return YASL_KEY_R;
+ case kVK_ANSI_S: return YASL_KEY_S;
+ case kVK_ANSI_T: return YASL_KEY_T;
+ case kVK_ANSI_U: return YASL_KEY_U;
+ case kVK_ANSI_V: return YASL_KEY_V;
+ case kVK_ANSI_W: return YASL_KEY_W;
+ case kVK_ANSI_X: return YASL_KEY_X;
+ case kVK_ANSI_Y: return YASL_KEY_Y;
+ case kVK_ANSI_Z: return YASL_KEY_Z;
+ case kVK_ANSI_0: return YASL_KEY_0;
+ case kVK_ANSI_1: return YASL_KEY_1;
+ case kVK_ANSI_2: return YASL_KEY_2;
+ case kVK_ANSI_3: return YASL_KEY_3;
+ case kVK_ANSI_4: return YASL_KEY_4;
+ case kVK_ANSI_5: return YASL_KEY_5;
+ case kVK_ANSI_6: return YASL_KEY_6;
+ case kVK_ANSI_7: return YASL_KEY_7;
+ case kVK_ANSI_8: return YASL_KEY_8;
+ case kVK_ANSI_9: return YASL_KEY_9;
+ case kVK_Return: return YASL_KEY_ENTER;
+ case kVK_Tab: return YASL_KEY_TAB;
+ case kVK_Space: return YASL_KEY_SPACE;
+ case kVK_Escape: return YASL_KEY_ESCAPE;
+ case kVK_Home: return YASL_KEY_HOME;
+ case kVK_End: return YASL_KEY_END;
+ case kVK_Delete: return YASL_KEY_BACKSPACE;
+ case kVK_ForwardDelete: return YASL_KEY_DELETE;
+ case kVK_PageUp: return YASL_KEY_PAGE_UP;
+ case kVK_PageDown: return YASL_KEY_PAGE_DOWN;
+ case kVK_Help: return YASL_KEY_INSERT;
+ case kVK_LeftArrow: return YASL_KEY_LEFT;
+ case kVK_RightArrow: return YASL_KEY_RIGHT;
+ case kVK_DownArrow: return YASL_KEY_DOWN;
+ case kVK_UpArrow: return YASL_KEY_UP;
+ case kVK_Command: return YASL_KEY_LEFT_SUPER;
+ case kVK_Shift: return YASL_KEY_LEFT_SHIFT;
+ case kVK_CapsLock: return YASL_KEY_CAPS_LOCK;
+ case kVK_Option: return YASL_KEY_LEFT_ALT;
+ case kVK_Control: return YASL_KEY_LEFT_CONTROL;
+ case kVK_RightCommand: return YASL_KEY_RIGHT_SUPER;
+ case kVK_RightShift: return YASL_KEY_RIGHT_SHIFT;
+ case kVK_RightOption: return YASL_KEY_RIGHT_ALT;
+ case kVK_RightControl: return YASL_KEY_RIGHT_CONTROL;
+ case kVK_ContextualMenu: return YASL_KEY_MENU;
+ case kVK_VolumeUp: return YASL_KEY_VOLUME_UP;
+ case kVK_VolumeDown: return YASL_KEY_VOLUME_DOWN;
+ case kVK_Mute: return YASL_KEY_MUTE;
+ case kVK_ISO_Section: return YASL_KEY_ISO_BACKSLASH;
+ case kVK_JIS_Yen: return YASL_KEY_YEN;
+ case kVK_JIS_Underscore: return YASL_KEY_RO;
+ case kVK_JIS_Eisu: return YASL_KEY_EISU;
+ case kVK_JIS_Kana: return YASL_KEY_KANA;
+ case kVK_JIS_KeypadComma: return YASL_KEY_KP_COMMA;
+ case kVK_ANSI_Equal: return YASL_KEY_EQUAL;
+ case kVK_ANSI_Minus: return YASL_KEY_MINUS;
+ case kVK_ANSI_LeftBracket: return YASL_KEY_LEFT_BRACKET;
+ case kVK_ANSI_RightBracket: return YASL_KEY_RIGHT_BRACKET;
+ case kVK_ANSI_Quote: return YASL_KEY_APOSTROPHE;
+ case kVK_ANSI_Semicolon: return YASL_KEY_SEMICOLON;
+ case kVK_ANSI_Backslash: return YASL_KEY_BACKSLASH;
+ case kVK_ANSI_Comma: return YASL_KEY_COMMA;
+ case kVK_ANSI_Slash: return YASL_KEY_SLASH;
+ case kVK_ANSI_Period: return YASL_KEY_PERIOD;
+ case kVK_ANSI_Grave: return YASL_KEY_GRAVE;
+ case kVK_F1: return YASL_KEY_F1;
+ case kVK_F2: return YASL_KEY_F2;
+ case kVK_F3: return YASL_KEY_F3;
+ case kVK_F4: return YASL_KEY_F4;
+ case kVK_F5: return YASL_KEY_F5;
+ case kVK_F6: return YASL_KEY_F6;
+ case kVK_F7: return YASL_KEY_F7;
+ case kVK_F8: return YASL_KEY_F8;
+ case kVK_F9: return YASL_KEY_F9;
+ case kVK_F10: return YASL_KEY_F10;
+ case kVK_F11: return YASL_KEY_F11;
+ case kVK_F12: return YASL_KEY_F12;
+ case kVK_F13: return YASL_KEY_F13;
+ case kVK_F14: return YASL_KEY_F14;
+ case kVK_F15: return YASL_KEY_F15;
+ case kVK_F16: return YASL_KEY_F16;
+ case kVK_F17: return YASL_KEY_F17;
+ case kVK_F18: return YASL_KEY_F18;
+ case kVK_F19: return YASL_KEY_F19;
+ case kVK_F20: return YASL_KEY_F20;
+ case kVK_ANSI_Keypad0: return YASL_KEY_KP_0;
+ case kVK_ANSI_Keypad1: return YASL_KEY_KP_1;
+ case kVK_ANSI_Keypad2: return YASL_KEY_KP_2;
+ case kVK_ANSI_Keypad3: return YASL_KEY_KP_3;
+ case kVK_ANSI_Keypad4: return YASL_KEY_KP_4;
+ case kVK_ANSI_Keypad5: return YASL_KEY_KP_5;
+ case kVK_ANSI_Keypad6: return YASL_KEY_KP_6;
+ case kVK_ANSI_Keypad7: return YASL_KEY_KP_7;
+ case kVK_ANSI_Keypad8: return YASL_KEY_KP_8;
+ case kVK_ANSI_Keypad9: return YASL_KEY_KP_9;
+ case kVK_ANSI_KeypadDecimal: return YASL_KEY_KP_DECIMAL;
+ case kVK_ANSI_KeypadMultiply: return YASL_KEY_KP_MULTIPLY;
+ case kVK_ANSI_KeypadPlus: return YASL_KEY_KP_ADD;
+ case kVK_ANSI_KeypadClear: return YASL_KEY_KP_CLEAR;
+ case kVK_ANSI_KeypadDivide: return YASL_KEY_KP_DIVIDE;
+ case kVK_ANSI_KeypadEnter: return YASL_KEY_KP_ENTER;
+ case kVK_ANSI_KeypadMinus: return YASL_KEY_KP_SUBTRACT;
+ case kVK_ANSI_KeypadEquals: return YASL_KEY_KP_EQUAL;
+ default: return YASL_KEY_UNKNOWN;
+ }
+}
+
+/* XKB standard physical key names, independent of server numeric keycodes. */
+enum yasl_key yasl_key_from_xkb_name(const char name[4])
+{
+ static const struct { char name[4]; enum yasl_key key; } keys[] = {
+ {{'T', 'L', 'D', 'E'}, YASL_KEY_GRAVE},
+ {{'A', 'E', '0', '1'}, YASL_KEY_1},
+ {{'A', 'E', '0', '2'}, YASL_KEY_2},
+ {{'A', 'E', '0', '3'}, YASL_KEY_3},
+ {{'A', 'E', '0', '4'}, YASL_KEY_4},
+ {{'A', 'E', '0', '5'}, YASL_KEY_5},
+ {{'A', 'E', '0', '6'}, YASL_KEY_6},
+ {{'A', 'E', '0', '7'}, YASL_KEY_7},
+ {{'A', 'E', '0', '8'}, YASL_KEY_8},
+ {{'A', 'E', '0', '9'}, YASL_KEY_9},
+ {{'A', 'E', '1', '0'}, YASL_KEY_0},
+ {{'A', 'E', '1', '1'}, YASL_KEY_MINUS},
+ {{'A', 'E', '1', '2'}, YASL_KEY_EQUAL},
+ {{'B', 'K', 'S', 'P'}, YASL_KEY_BACKSPACE},
+ {{'T', 'A', 'B', 0}, YASL_KEY_TAB},
+ {{'A', 'D', '0', '1'}, YASL_KEY_Q},
+ {{'A', 'D', '0', '2'}, YASL_KEY_W},
+ {{'A', 'D', '0', '3'}, YASL_KEY_E},
+ {{'A', 'D', '0', '4'}, YASL_KEY_R},
+ {{'A', 'D', '0', '5'}, YASL_KEY_T},
+ {{'A', 'D', '0', '6'}, YASL_KEY_Y},
+ {{'A', 'D', '0', '7'}, YASL_KEY_U},
+ {{'A', 'D', '0', '8'}, YASL_KEY_I},
+ {{'A', 'D', '0', '9'}, YASL_KEY_O},
+ {{'A', 'D', '1', '0'}, YASL_KEY_P},
+ {{'A', 'D', '1', '1'}, YASL_KEY_LEFT_BRACKET},
+ {{'A', 'D', '1', '2'}, YASL_KEY_RIGHT_BRACKET},
+ {{'B', 'K', 'S', 'L'}, YASL_KEY_BACKSLASH},
+ {{'R', 'T', 'R', 'N'}, YASL_KEY_ENTER},
+ {{'C', 'A', 'P', 'S'}, YASL_KEY_CAPS_LOCK},
+ {{'A', 'C', '0', '1'}, YASL_KEY_A},
+ {{'A', 'C', '0', '2'}, YASL_KEY_S},
+ {{'A', 'C', '0', '3'}, YASL_KEY_D},
+ {{'A', 'C', '0', '4'}, YASL_KEY_F},
+ {{'A', 'C', '0', '5'}, YASL_KEY_G},
+ {{'A', 'C', '0', '6'}, YASL_KEY_H},
+ {{'A', 'C', '0', '7'}, YASL_KEY_J},
+ {{'A', 'C', '0', '8'}, YASL_KEY_K},
+ {{'A', 'C', '0', '9'}, YASL_KEY_L},
+ {{'A', 'C', '1', '0'}, YASL_KEY_SEMICOLON},
+ {{'A', 'C', '1', '1'}, YASL_KEY_APOSTROPHE},
+ {{'L', 'F', 'S', 'H'}, YASL_KEY_LEFT_SHIFT},
+ {{'L', 'S', 'G', 'T'}, YASL_KEY_ISO_BACKSLASH},
+ {{'A', 'B', '0', '1'}, YASL_KEY_Z},
+ {{'A', 'B', '0', '2'}, YASL_KEY_X},
+ {{'A', 'B', '0', '3'}, YASL_KEY_C},
+ {{'A', 'B', '0', '4'}, YASL_KEY_V},
+ {{'A', 'B', '0', '5'}, YASL_KEY_B},
+ {{'A', 'B', '0', '6'}, YASL_KEY_N},
+ {{'A', 'B', '0', '7'}, YASL_KEY_M},
+ {{'A', 'B', '0', '8'}, YASL_KEY_COMMA},
+ {{'A', 'B', '0', '9'}, YASL_KEY_PERIOD},
+ {{'A', 'B', '1', '0'}, YASL_KEY_SLASH},
+ {{'R', 'T', 'S', 'H'}, YASL_KEY_RIGHT_SHIFT},
+ {{'L', 'C', 'T', 'L'}, YASL_KEY_LEFT_CONTROL},
+ {{'L', 'W', 'I', 'N'}, YASL_KEY_LEFT_SUPER},
+ {{'L', 'A', 'L', 'T'}, YASL_KEY_LEFT_ALT},
+ {{'S', 'P', 'C', 'E'}, YASL_KEY_SPACE},
+ {{'R', 'A', 'L', 'T'}, YASL_KEY_RIGHT_ALT},
+ {{'R', 'W', 'I', 'N'}, YASL_KEY_RIGHT_SUPER},
+ {{'C', 'O', 'M', 'P'}, YASL_KEY_MENU},
+ {{'R', 'C', 'T', 'L'}, YASL_KEY_RIGHT_CONTROL},
+ {{'E', 'S', 'C', 0}, YASL_KEY_ESCAPE},
+ {{'F', 'K', '0', '1'}, YASL_KEY_F1},
+ {{'F', 'K', '0', '2'}, YASL_KEY_F2},
+ {{'F', 'K', '0', '3'}, YASL_KEY_F3},
+ {{'F', 'K', '0', '4'}, YASL_KEY_F4},
+ {{'F', 'K', '0', '5'}, YASL_KEY_F5},
+ {{'F', 'K', '0', '6'}, YASL_KEY_F6},
+ {{'F', 'K', '0', '7'}, YASL_KEY_F7},
+ {{'F', 'K', '0', '8'}, YASL_KEY_F8},
+ {{'F', 'K', '0', '9'}, YASL_KEY_F9},
+ {{'F', 'K', '1', '0'}, YASL_KEY_F10},
+ {{'F', 'K', '1', '1'}, YASL_KEY_F11},
+ {{'F', 'K', '1', '2'}, YASL_KEY_F12},
+ {{'P', 'R', 'S', 'C'}, YASL_KEY_PRINT_SCREEN},
+ {{'S', 'C', 'L', 'K'}, YASL_KEY_SCROLL_LOCK},
+ {{'P', 'A', 'U', 'S'}, YASL_KEY_PAUSE},
+ {{'I', 'N', 'S', 0}, YASL_KEY_INSERT},
+ {{'H', 'O', 'M', 'E'}, YASL_KEY_HOME},
+ {{'P', 'G', 'U', 'P'}, YASL_KEY_PAGE_UP},
+ {{'D', 'E', 'L', 'E'}, YASL_KEY_DELETE},
+ {{'E', 'N', 'D', 0}, YASL_KEY_END},
+ {{'P', 'G', 'D', 'N'}, YASL_KEY_PAGE_DOWN},
+ {{'U', 'P', 0, 0}, YASL_KEY_UP},
+ {{'L', 'E', 'F', 'T'}, YASL_KEY_LEFT},
+ {{'D', 'O', 'W', 'N'}, YASL_KEY_DOWN},
+ {{'R', 'G', 'H', 'T'}, YASL_KEY_RIGHT},
+ {{'N', 'M', 'L', 'K'}, YASL_KEY_NUM_LOCK},
+ {{'K', 'P', 'D', 'V'}, YASL_KEY_KP_DIVIDE},
+ {{'K', 'P', 'M', 'U'}, YASL_KEY_KP_MULTIPLY},
+ {{'K', 'P', 'S', 'U'}, YASL_KEY_KP_SUBTRACT},
+ {{'K', 'P', '7', 0}, YASL_KEY_KP_7},
+ {{'K', 'P', '8', 0}, YASL_KEY_KP_8},
+ {{'K', 'P', '9', 0}, YASL_KEY_KP_9},
+ {{'K', 'P', 'A', 'D'}, YASL_KEY_KP_ADD},
+ {{'K', 'P', '4', 0}, YASL_KEY_KP_4},
+ {{'K', 'P', '5', 0}, YASL_KEY_KP_5},
+ {{'K', 'P', '6', 0}, YASL_KEY_KP_6},
+ {{'K', 'P', '1', 0}, YASL_KEY_KP_1},
+ {{'K', 'P', '2', 0}, YASL_KEY_KP_2},
+ {{'K', 'P', '3', 0}, YASL_KEY_KP_3},
+ {{'K', 'P', 'E', 'N'}, YASL_KEY_KP_ENTER},
+ {{'K', 'P', '0', 0}, YASL_KEY_KP_0},
+ {{'K', 'P', 'D', 'L'}, YASL_KEY_KP_DECIMAL},
+ {{'K', 'P', 'E', 'Q'}, YASL_KEY_KP_EQUAL},
+ {{'F', 'K', '1', '3'}, YASL_KEY_F13},
+ {{'F', 'K', '1', '4'}, YASL_KEY_F14},
+ {{'F', 'K', '1', '5'}, YASL_KEY_F15},
+ {{'F', 'K', '1', '6'}, YASL_KEY_F16},
+ {{'F', 'K', '1', '7'}, YASL_KEY_F17},
+ {{'F', 'K', '1', '8'}, YASL_KEY_F18},
+ {{'F', 'K', '1', '9'}, YASL_KEY_F19},
+ {{'F', 'K', '2', '0'}, YASL_KEY_F20},
+ {{'F', 'K', '2', '1'}, YASL_KEY_F21},
+ {{'F', 'K', '2', '2'}, YASL_KEY_F22},
+ {{'F', 'K', '2', '3'}, YASL_KEY_F23},
+ {{'F', 'K', '2', '4'}, YASL_KEY_F24},
+ {{'H', 'K', 'T', 'G'}, YASL_KEY_KANA},
+ {{'A', 'B', '1', '1'}, YASL_KEY_RO},
+ {{'H', 'E', 'N', 'K'}, YASL_KEY_CONVERT},
+ {{'M', 'U', 'H', 'E'}, YASL_KEY_NONCONVERT},
+ {{'A', 'E', '1', '3'}, YASL_KEY_YEN},
+ {{'M', 'U', 'T', 'E'}, YASL_KEY_MUTE},
+ {{'I', '1', '2', '9'}, YASL_KEY_KP_COMMA},
+ };
+ for (size_t i = 0; i < sizeof(keys) / sizeof(keys[0]); i++)
+ if (memcmp(keys[i].name, name, 4) == 0) return keys[i].key;
+ return YASL_KEY_UNKNOWN;
+}
diff --git a/src/common/keyboard.h b/src/common/keyboard.h
new file mode 100644
index 0000000..50ddbb3
--- /dev/null
+++ b/src/common/keyboard.h
@@ -0,0 +1,18 @@
+#ifndef YASL_COMMON_KEYBOARD_H
+#define YASL_COMMON_KEYBOARD_H
+
+#include "yasl/yasl.h"
+
+enum yasl_key yasl_key_from_evdev(uint32_t code);
+enum yasl_key yasl_key_from_xkb_name(const char name[4]);
+enum yasl_key yasl_key_from_cocoa(uint32_t code);
+
+/* Layout labels omit controls, invalid Unicode, and Cocoa function-key PUA. */
+static inline uint32_t yasl_key_label(uint32_t cp)
+{
+ return cp >= 0x20 && cp != 0x7f && cp <= 0x10ffff &&
+ !(cp >= 0x80 && cp <= 0x9f) && !(cp >= 0xd800 && cp <= 0xdfff) &&
+ !(cp >= 0xf700 && cp <= 0xf7ff) ? cp : 0;
+}
+
+#endif
diff --git a/src/common/meson.build b/src/common/meson.build
index ddd0194..1247ff5 100644
--- a/src/common/meson.build
+++ b/src/common/meson.build
@@ -1 +1 @@
-sources += files('backend.c', 'input.c', 'loop.c', 'native.c', 'timing.c', 'utf8.c')
+sources += files('backend.c', 'input.c', 'keyboard.c', 'loop.c', 'native.c', 'timing.c', 'utf8.c')
diff --git a/src/common/utf8.c b/src/common/utf8.c
index 1d4cf13..78a5d8e 100644
--- a/src/common/utf8.c
+++ b/src/common/utf8.c
@@ -1,3 +1,4 @@
+#include "utf8.h"
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
@@ -12,11 +13,12 @@
bool yasl_utf8_validate(const char *str, size_t len)
{
+ if (!str) return len == 0;
const unsigned char *p = (const unsigned char *)str;
const unsigned char *end = p + len;
while (p < end) {
- while (p + 8 <= end) {
+ while ((size_t)(end - p) >= 8) {
uint64_t v;
memcpy(&v, p, 8);
if (v & 0x8080808080808080ull)
@@ -28,18 +30,18 @@ bool yasl_utf8_validate(const char *str, size_t len)
if (likely(*p < 0x80)) {
p++;
} else if ((*p & 0xe0) == 0xc0) {
- if (unlikely(p + 1 >= end || (p[1] & 0xc0) != 0x80 || (*p & 0x1e) == 0))
+ if (unlikely(end - p < 2 || (p[1] & 0xc0) != 0x80 || (*p & 0x1e) == 0))
return false;
p += 2;
} else if ((*p & 0xf0) == 0xe0) {
- if (unlikely(p + 2 >= end || (p[1] & 0xc0) != 0x80 || (p[2] & 0xc0) != 0x80))
+ if (unlikely(end - p < 3 || (p[1] & 0xc0) != 0x80 || (p[2] & 0xc0) != 0x80))
return false;
uint32_t cp = ((p[0] & 0x0f) << 12) | ((p[1] & 0x3f) << 6) | (p[2] & 0x3f);
if (unlikely(cp < 0x800 || (cp >= 0xd800 && cp <= 0xdfff)))
return false;
p += 3;
} else if ((*p & 0xf8) == 0xf0) {
- if (unlikely(p + 3 >= end || (p[1] & 0xc0) != 0x80 || (p[2] & 0xc0) != 0x80 || (p[3] & 0xc0) != 0x80))
+ if (unlikely(end - p < 4 || (p[1] & 0xc0) != 0x80 || (p[2] & 0xc0) != 0x80 || (p[3] & 0xc0) != 0x80))
return false;
uint32_t cp = ((p[0] & 0x07) << 18) | ((p[1] & 0x3f) << 12) | ((p[2] & 0x3f) << 6) | (p[3] & 0x3f);
if (unlikely(cp < 0x10000 || cp > 0x10ffff))
@@ -52,6 +54,46 @@ bool yasl_utf8_validate(const char *str, size_t len)
return true;
}
+char *yasl_utf8_from_latin1(const char *str, size_t len, size_t *out_len)
+{
+ if (len > (SIZE_MAX - 1) / 2) return NULL;
+ char *out = malloc(len * 2 + 1);
+ if (!out) return NULL;
+ size_t n = 0;
+ for (size_t i = 0; i < len; i++) {
+ unsigned char ch = (unsigned char)str[i];
+ if (ch < 0x80) out[n++] = (char)ch;
+ else {
+ out[n++] = (char)(0xc0 | (ch >> 6));
+ out[n++] = (char)(0x80 | (ch & 0x3f));
+ }
+ }
+ out[n] = '\0';
+ *out_len = n;
+ return out;
+}
+
+char *yasl_utf8_to_latin1(const char *str, size_t *out_len)
+{
+ char *out = malloc(strlen(str) + 1);
+ if (!out) return NULL;
+ const unsigned char *p = (const unsigned char *)str;
+ size_t n = 0;
+ while (*p) {
+ if (*p < 0x80) out[n++] = (char)*p++;
+ else if ((*p == 0xc2 || *p == 0xc3) && (p[1] & 0xc0) == 0x80) {
+ out[n++] = (char)(((*p & 3) << 6) | (p[1] & 0x3f));
+ p += 2;
+ } else {
+ free(out);
+ return NULL;
+ }
+ }
+ out[n] = '\0';
+ *out_len = n;
+ return out;
+}
+
char *yasl_utf8_sanitize_dup(const char *str)
{
if (unlikely(!str)) return NULL;
diff --git a/src/common/utf8.h b/src/common/utf8.h
new file mode 100644
index 0000000..f31c99d
--- /dev/null
+++ b/src/common/utf8.h
@@ -0,0 +1,14 @@
+#ifndef YASL_COMMON_UTF8_H
+#define YASL_COMMON_UTF8_H
+
+#include <stdbool.h>
+#include <stddef.h>
+
+bool yasl_utf8_validate(const char *str, size_t len);
+char *yasl_utf8_sanitize_dup(const char *str);
+/* Owned conversions for X11 STRING interoperability. to_latin1 rejects text
+ * outside Latin-1; byte lengths exclude the allocated NUL terminator. */
+char *yasl_utf8_from_latin1(const char *str, size_t len, size_t *out_len);
+char *yasl_utf8_to_latin1(const char *str, size_t *out_len);
+
+#endif
diff --git a/src/wayland/clipboard.c b/src/wayland/clipboard.c
index e3eec0f..a6922c4 100644
--- a/src/wayland/clipboard.c
+++ b/src/wayland/clipboard.c
@@ -131,9 +131,11 @@ static int transfer_io(int fd, short events, void *ud)
if (n > 0) {
t->len += (size_t)n;
} else if (n == 0) {
- t->owner->receive(t->text ? t->text : "", t->len, t->uri_list,
- t->drop_surface, t->owner->ud);
- transfer_end(t, true);
+ bool valid = yasl_utf8_validate(t->text, t->len);
+ if (valid)
+ t->owner->receive(t->text ? t->text : "", t->len, t->uri_list,
+ t->drop_surface, t->owner->ud);
+ transfer_end(t, valid);
} else if (errno != EAGAIN && errno != EINTR) transfer_destroy(t);
}
return 0;
diff --git a/src/wayland/ime.c b/src/wayland/ime.c
index 74db53f..bd9375b 100644
--- a/src/wayland/ime.c
+++ b/src/wayland/ime.c
@@ -98,6 +98,7 @@ static void text_input_preedit_string(void *data,
(void)text_input;
struct wayland_surface *w = ((struct seat *)data)->ime_focus;
if (!w) return;
+ keyboard_text_reset((struct seat *)data);
free(w->ime_preedit);
w->ime_preedit = text ? strndup(text, IME_TEXT_MAX) : NULL;
int len = w->ime_preedit ? (int)strlen(w->ime_preedit) : 0;
@@ -113,6 +114,7 @@ static void text_input_commit_string(void *data,
(void)text_input;
struct wayland_surface *w = ((struct seat *)data)->ime_focus;
if (!w) return;
+ keyboard_text_reset((struct seat *)data);
free(w->ime_commit);
w->ime_commit = (text && text[0]) ? strndup(text, IME_TEXT_MAX) : NULL;
}
diff --git a/src/wayland/internal.h b/src/wayland/internal.h
index ad6531b..b687a61 100644
--- a/src/wayland/internal.h
+++ b/src/wayland/internal.h
@@ -5,6 +5,7 @@
#include "api.h"
#include "common/timing.h"
+#include "common/utf8.h"
#include "clipboard.h"
#include <wayland-client.h>
@@ -38,6 +39,7 @@
#include <unistd.h>
#include <xkbcommon/xkbcommon.h>
#include <xkbcommon/xkbcommon-keysyms.h>
+#include <xkbcommon/xkbcommon-compose.h>
#define NBUF 3
@@ -140,6 +142,8 @@ struct seat {
struct clipboard *clipboard;
struct xkb_keymap *xkb_keymap;
struct xkb_state *xkb_state;
+ struct xkb_compose_state *compose;
+ bool compose_attempted;
uint64_t repeat_started_ns;
struct wayland_surface *pointer_focus;
struct wayland_surface *keyboard_focus;
@@ -152,8 +156,10 @@ struct seat {
struct touch_point *touch_primary;
uint64_t pointer_buttons;
uint64_t tool_buttons;
- double axis_v, axis_h, frame_v;
- double touch_scroll_y, touch_scroll_rest;
+ double axis[2], axis_value120[2];
+ int64_t axis_discrete[2];
+ bool axis_has_value120[2], axis_has_discrete[2];
+ double touch_scroll_y;
/* Embedded structs */
xkb_mod_mask_t input_mod_masks[8];
@@ -174,18 +180,12 @@ struct seat {
uint32_t press_serial;
uint32_t pointer_serial;
int cursor_shape;
- int frame_v120;
- int axis_discrete_v, axis_discrete_h;
- int axis_value120_v, axis_value120_h;
struct yasl_pointer_pen pen;
int tool_wheel;
/* 1-byte booleans grouped tightly */
bool input_mods_ready;
bool repeat_held;
- bool axis_has_v120_v;
- bool axis_has_v120_h;
- bool axis_smooth;
bool motion_pending;
bool ptr_left_motion;
bool tool_motion;
@@ -325,8 +325,6 @@ struct wayland_surface {
bool want_draw;
};
-char *yasl_utf8_sanitize_dup(const char *str);
-bool yasl_utf8_validate(const char *str, size_t len);
int device_px(int logical, uint32_t scale_120);
bool buffer_size(int width, int height, size_t *bytes);
@@ -415,6 +413,7 @@ void seat_pointer_reset(struct seat *s);
extern const struct zwp_text_input_v3_listener text_input_listener;
void ime_reset(struct wayland_surface *w);
+void keyboard_text_reset(struct seat *s);
void ime_send_caret(struct wayland_surface *w);
int surface_init_shell(struct wayland_surface *w, const char *title);
diff --git a/src/wayland/keyboard.c b/src/wayland/keyboard.c
index 11c7f3f..f1170c6 100644
--- a/src/wayland/keyboard.c
+++ b/src/wayland/keyboard.c
@@ -1,8 +1,14 @@
#include "internal.h"
+#include "common/keyboard.h"
#define XKB_EVDEV_OFFSET 8
#define KEYMAP_MAX_BYTES (8u * 1024 * 1024)
+void keyboard_text_reset(struct seat *s)
+{
+ if (s->compose) xkb_compose_state_reset(s->compose);
+}
+
void keyboard_repeat_stop(struct seat *s)
{
if (!s->repeat_held) return;
@@ -28,11 +34,10 @@ void keyboard_repeat_sync(struct seat *s)
timerfd_settime(s->repeat_fd, 0, &timer, NULL);
}
-static const char *const input_mod_names[8] = {
+static const char *const input_mod_names[5] = {
XKB_MOD_NAME_SHIFT, XKB_MOD_NAME_ALT,
XKB_MOD_NAME_CTRL, XKB_MOD_NAME_LOGO,
- "Hyper", "Meta",
- XKB_MOD_NAME_CAPS, XKB_MOD_NAME_NUM
+ XKB_MOD_NAME_CAPS
};
static bool mod_active(const struct seat *s, const char *name)
@@ -48,7 +53,7 @@ static void resolve_input_mod_masks(struct seat *s)
if (!probe) return;
for (unsigned real = 0; real < 8; real++) {
xkb_state_update_mask(probe, 1u << real, 0, 0, 0, 0, 0);
- for (unsigned i = 0; i < 8; i++) {
+ for (unsigned i = 0; i < 5; i++) {
if (xkb_state_mod_name_is_active(probe, input_mod_names[i],
XKB_STATE_MODS_EFFECTIVE) > 0)
s->input_mod_masks[i] |= 1u << real;
@@ -64,6 +69,7 @@ static void keyboard_keymap(void *data, struct wl_keyboard *keyboard,
(void)keyboard;
struct seat *s = data;
keyboard_repeat_stop(s);
+ keyboard_text_reset(s);
s->input_mods_ready = false;
if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
close(fd);
@@ -116,6 +122,7 @@ static void keyboard_reset_keys(struct seat *s, struct wayland_surface *w)
{
(void)w;
keyboard_repeat_stop(s);
+ keyboard_text_reset(s);
}
bool conn_keyboard_on(const struct wayland_display *c, const struct wayland_surface *w,
@@ -194,7 +201,7 @@ static unsigned input_modifiers(struct seat *s)
unsigned mods = 0;
xkb_mod_mask_t reported = 0;
- for (unsigned i = 0; i < 8; i++) {
+ for (unsigned i = 0; i < 5; i++) {
xkb_mod_mask_t mask = s->input_mod_masks[i];
if (mask && (mask & ~reported) && mod_active(s, input_mod_names[i])) {
mods |= 1u << i;
@@ -215,23 +222,75 @@ static struct yasl_key_event xkb_key_event(struct seat *s, uint32_t key,
enum yasl_key_action action)
{
uint32_t code = key + XKB_EVDEV_OFFSET;
- xkb_keysym_t sym = xkb_state_key_get_one_sym(s->xkb_state, code);
xkb_layout_index_t layout = xkb_state_key_get_layout(s->xkb_state, code);
struct yasl_key_event k = {
- .key = sym,
- .keypad = key >= KEY_KP0 && key <= KEY_KPCOMMA,
+ .key = yasl_key_from_evdev(key),
.action = action,
.mods = input_modifiers(s),
- .codepoint = key_codepoint_at_level(s, key, layout, 0),
- .shifted = key_codepoint_at_level(s, key, layout, 1),
- .base = key,
- .text = xkb_state_key_get_utf32(s->xkb_state, code),
+ .codepoint = yasl_key_label(key_codepoint_at_level(s, key, layout, 0)),
+ .shifted = yasl_key_label(key_codepoint_at_level(s, key, layout, 1)),
+ .native_code = key,
};
- if (!k.codepoint) k.codepoint = xkb_keysym_to_utf32(sym);
return k;
}
+static void keyboard_text(struct seat *s, struct wayland_surface *w, uint32_t key)
+{
+ uint32_t code = key + XKB_EVDEV_OFFSET;
+ unsigned mods = input_modifiers(s);
+ if ((mods & (YASL_MOD_CTRL | YASL_MOD_SUPER)) ||
+ ((mods & YASL_MOD_ALT) &&
+ !xkb_state_mod_index_is_consumed(s->xkb_state, code, s->mod_alt))) {
+ keyboard_text_reset(s);
+ return;
+ }
+ if (!s->compose_attempted) {
+ s->compose_attempted = true;
+ const char *locale = getenv("LC_ALL");
+ if (!locale || !*locale) locale = getenv("LC_CTYPE");
+ if (!locale || !*locale) locale = getenv("LANG");
+ if (!locale || !*locale) locale = "C.UTF-8";
+ struct xkb_compose_table *table = xkb_compose_table_new_from_locale(
+ s->conn->xkb_ctx, locale, XKB_COMPOSE_COMPILE_NO_FLAGS);
+ if (table) {
+ s->compose = xkb_compose_state_new(table, XKB_COMPOSE_STATE_NO_FLAGS);
+ xkb_compose_table_unref(table);
+ }
+ }
+ bool composed = false;
+ if (s->compose) {
+ xkb_compose_state_feed(s->compose, xkb_state_key_get_one_sym(s->xkb_state, code));
+ switch (xkb_compose_state_get_status(s->compose)) {
+ case XKB_COMPOSE_COMPOSING: return;
+ case XKB_COMPOSE_CANCELLED: keyboard_text_reset(s); return;
+ case XKB_COMPOSE_COMPOSED: composed = true; break;
+ default: break;
+ }
+ }
+ int len = composed ? xkb_compose_state_get_utf8(s->compose, NULL, 0) :
+ xkb_state_key_get_utf8(s->xkb_state, code, NULL, 0);
+ if (len > 0) {
+ char local[128];
+ char *text = (size_t)len < sizeof(local) ? local : malloc((size_t)len + 1);
+ if (text) {
+ if (composed) xkb_compose_state_get_utf8(s->compose, text, (size_t)len + 1);
+ else xkb_state_key_get_utf8(s->xkb_state, code, text, (size_t)len + 1);
+ /* Editing commands belong to key events, not text insertion. */
+ if ((unsigned char)text[0] >= 0x20 && text[0] != 0x7f) {
+ struct yasl_input_event ev = {.kind = YASL_INPUT_TEXT,
+ .surface = &w->base, .text = {text, (size_t)len}};
+ input_queue_push(&w->input, &ev);
+ }
+ if (text != local) free(text);
+ } else {
+ struct yasl_input_event ev = {.kind = YASL_INPUT_RESET, .surface = &w->base};
+ input_queue_push(&w->input, &ev);
+ }
+ }
+ if (composed) keyboard_text_reset(s);
+}
+
static bool keyboard_dispatch_key(struct seat *s, struct wayland_surface *w, uint32_t key,
enum yasl_key_action action)
{
@@ -243,7 +302,11 @@ static bool keyboard_dispatch_key(struct seat *s, struct wayland_surface *w, uin
.surface = &w->base,
.key = k,
};
- input_queue_push(&w->input, &ev);
+ if (!input_queue_push(&w->input, &ev)) {
+ keyboard_text_reset(s);
+ return false;
+ }
+ if (action != YASL_KEY_RELEASE) keyboard_text(s, w, key);
return action != YASL_KEY_RELEASE;
}
@@ -351,7 +414,8 @@ int seat_modifiers(const struct seat *s)
return (mod_index_active(s, s->mod_shift) * YASL_MOD_SHIFT) |
(mod_index_active(s, s->mod_alt) * YASL_MOD_ALT) |
(mod_index_active(s, s->mod_ctrl) * YASL_MOD_CTRL) |
- (mod_index_active(s, s->mod_super) * YASL_MOD_SUPER);
+ (mod_index_active(s, s->mod_super) * YASL_MOD_SUPER) |
+ (mod_active(s, XKB_MOD_NAME_CAPS) * YASL_MOD_CAPS);
}
int wayland_display_modifiers(const struct wayland_display *c)
diff --git a/src/wayland/pointer.c b/src/wayland/pointer.c
index 065669f..90b7ecc 100644
--- a/src/wayland/pointer.c
+++ b/src/wayland/pointer.c
@@ -109,12 +109,12 @@ static void report_mouse(struct seat *s, struct wayland_surface *w, struct yasl_
});
}
-static void report_wheel(struct seat *s, struct wayland_surface *w, struct yasl_vector2i pos,
- int button, int lines, float delta)
+static void report_wheel(struct seat *s, struct wayland_surface *w,
+ struct yasl_vector2i pos, double x, double y)
{
+ if ((x == 0 && y == 0) || !isfinite(x) || !isfinite(y)) return;
push_mouse(s, w, (struct yasl_mouse_event){
- .pos = pos, .button = button, .kind = YASL_PTR_SCROLL,
- .scroll_lines = lines, .scroll_delta = delta,
+ .pos = pos, .kind = YASL_PTR_SCROLL, .scroll = {x, y},
});
}
@@ -295,12 +295,11 @@ void seat_pointer_reset(struct seat *s)
s->pointer_buttons = 0;
s->pointer_serial = 0;
s->motion_pending = false;
- s->axis_v = s->axis_h = s->frame_v = 0;
- s->frame_v120 = 0;
- s->axis_discrete_v = s->axis_discrete_h = 0;
- s->axis_value120_v = s->axis_value120_h = 0;
- s->axis_has_v120_v = s->axis_has_v120_h = false;
- s->axis_smooth = false;
+ memset(s->axis, 0, sizeof(s->axis));
+ memset(s->axis_value120, 0, sizeof(s->axis_value120));
+ memset(s->axis_discrete, 0, sizeof(s->axis_discrete));
+ memset(s->axis_has_value120, 0, sizeof(s->axis_has_value120));
+ memset(s->axis_has_discrete, 0, sizeof(s->axis_has_discrete));
s->cursor_shape = -1;
}
@@ -405,8 +404,7 @@ static void pointer_axis(void *data, struct wl_pointer *pointer,
if (unlikely(!s->pointer_focus)) return;
double v = wl_fixed_to_double(value);
if (likely(axis < 2)) {
- (&s->axis_v)[axis] += v;
- s->frame_v += v * (!axis);
+ s->axis[axis] += v;
}
pointer_event_done(s);
}
@@ -414,81 +412,49 @@ static void pointer_axis(void *data, struct wl_pointer *pointer,
static void pointer_axis_source(void *data, struct wl_pointer *pointer, uint32_t axis_source)
{
(void)pointer;
- struct seat *s = data;
- s->axis_smooth = (axis_source == WL_POINTER_AXIS_SOURCE_FINGER ||
- axis_source == WL_POINTER_AXIS_SOURCE_CONTINUOUS);
+ (void)data; (void)axis_source;
}
static void pointer_axis_stop(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis)
{
- (void)pointer; (void)time;
- struct seat *s = data;
- if (likely(axis < 2)) {
- (&s->axis_value120_v)[axis] = 0;
- (&s->axis_has_v120_v)[axis] = false;
- }
+ /* A stop ends a gesture, not the offsets already received in this frame. */
+ (void)data; (void)pointer; (void)time; (void)axis;
}
static void pointer_axis_discrete(void *data, struct wl_pointer *pointer, uint32_t axis, int32_t discrete)
{
(void)pointer;
struct seat *s = data;
- if (likely(axis < 2))
- (&s->axis_discrete_v)[axis] += discrete;
+ if (axis < 2) {
+ s->axis_discrete[axis] += discrete;
+ s->axis_has_discrete[axis] = true;
+ }
}
static void pointer_axis_value120(void *data, struct wl_pointer *pointer, uint32_t axis, int32_t value120)
{
(void)pointer;
struct seat *s = data;
- if (likely(axis < 2)) {
- (&s->axis_value120_v)[axis] += value120;
- (&s->axis_has_v120_v)[axis] = true;
- s->frame_v120 += value120 * (!axis);
+ if (axis < 2) {
+ s->axis_value120[axis] += value120;
+ s->axis_has_value120[axis] = true;
}
}
-static void pointer_frame_vertical(struct seat *s, struct wayland_surface *w)
-{
- if (s->axis_has_v120_v) {
- int discrete = s->axis_value120_v / 120;
- s->axis_value120_v -= discrete * 120;
- float delta = -(float)s->frame_v120 / 120.0f;
- int btn = discrete ? (discrete < 0 ? 4 : 5) : (delta > 0 ? 4 : 5);
- if (discrete || delta != 0.0f)
- report_wheel(s, w, s->pointer_pos, btn, -discrete, delta);
- s->axis_v = 0;
- } else if (s->axis_discrete_v) {
- report_wheel(s, w, s->pointer_pos, s->axis_discrete_v > 0 ? 5 : 4, -s->axis_discrete_v, 0.0f);
- s->axis_v = 0;
- } else if (s->frame_v != 0.0) {
- int lines = -(int)(s->axis_v / SCROLL_PX_PER_LINE);
- s->axis_v += (double)lines * SCROLL_PX_PER_LINE;
- float delta = (float)(-s->frame_v / SCROLL_PX_PER_LINE);
- report_wheel(s, w, s->pointer_pos, delta > 0 ? 4 : 5, lines, delta);
- }
- s->frame_v = 0;
- s->frame_v120 = 0;
-}
-
-static void pointer_frame_horizontal(struct seat *s, struct wayland_surface *w)
-{
- if (s->axis_has_v120_h) {
- int discrete = s->axis_value120_h / 120;
- s->axis_value120_h -= discrete * 120;
- if (discrete)
- report_wheel(s, w, s->pointer_pos, discrete > 0 ? 7 : 6, discrete, 0.0f);
- s->axis_h = 0;
- } else if (s->axis_discrete_h) {
- report_wheel(s, w, s->pointer_pos, s->axis_discrete_h > 0 ? 7 : 6, s->axis_discrete_h, 0.0f);
- s->axis_h = 0;
- } else if (s->axis_h) {
- int steps = (int)(s->axis_h / SCROLL_PX_PER_LINE);
- if (steps != 0) {
- s->axis_h -= (double)steps * SCROLL_PX_PER_LINE;
- report_wheel(s, w, s->pointer_pos, steps > 0 ? 7 : 6, steps, 0.0f);
- }
+static void pointer_frame_scroll(struct seat *s, struct wayland_surface *w)
+{
+ double lines[2];
+ for (int axis = 0; axis < 2; axis++) {
+ /* A wheel may provide all three representations. Pick one, never sum
+ * them. value120 preserves high-resolution detents on both axes. */
+ lines[axis] = s->axis_has_value120[axis] ? s->axis_value120[axis] / 120.0 :
+ s->axis_has_discrete[axis] ? (double)s->axis_discrete[axis] :
+ s->axis[axis] / SCROLL_PX_PER_LINE;
+ s->axis[axis] = s->axis_value120[axis] = 0;
+ s->axis_discrete[axis] = 0;
+ s->axis_has_value120[axis] = s->axis_has_discrete[axis] = false;
}
+ report_wheel(s, w, s->pointer_pos, lines[1], lines[0]);
}
static void pointer_frame(void *data, struct wl_pointer *pointer)
@@ -521,18 +487,7 @@ static void pointer_frame(void *data, struct wl_pointer *pointer)
flush_pending_motion(s, w);
press_queue_flush(s, &s->ptr_presses, NULL, &s->pointer_buttons, (struct yasl_mouse_event){0});
- if (s->axis_smooth) {
- if (!s->axis_has_v120_v) s->axis_value120_v = 0;
- if (!s->axis_has_v120_h) s->axis_value120_h = 0;
- s->axis_smooth = false;
- }
- pointer_frame_vertical(s, w);
- pointer_frame_horizontal(s, w);
-
- s->axis_discrete_v = 0;
- s->axis_discrete_h = 0;
- s->axis_has_v120_v = false;
- s->axis_has_v120_h = false;
+ pointer_frame_scroll(s, w);
}
static void pointer_axis_relative_direction(void *data, struct wl_pointer *pointer, uint32_t axis, uint32_t direction)
@@ -651,20 +606,15 @@ static void touch_frame(void *data, struct wl_touch *touch)
s->touch_pressed = false;
s->touch_scrolling = true;
s->touch_scroll_y = centroid_n ? centroid_sum / centroid_n : 0;
- s->touch_scroll_rest = 0;
} else if (s->touch_scrolling && primary && active_n >= 2) {
double y = centroid_n ? centroid_sum / centroid_n : 0;
double dy = y - s->touch_scroll_y;
s->touch_scroll_y = y;
if (dy != 0.0) {
double lines = dy / SCROLL_PX_PER_LINE;
- s->touch_scroll_rest += lines;
- int whole = (int)s->touch_scroll_rest;
- s->touch_scroll_rest -= whole;
push_mouse(s, primary->surface, (struct yasl_mouse_event){
- .pos = touch_pos(primary), .button = lines > 0 ? 4 : 5,
- .kind = YASL_PTR_SCROLL, .scroll_lines = whole,
- .scroll_delta = (float)lines, .device = YASL_PTR_DEV_TOUCH,
+ .pos = touch_pos(primary), .kind = YASL_PTR_SCROLL,
+ .scroll = {0, -lines}, .device = YASL_PTR_DEV_TOUCH,
});
}
} else if (primary && primary->moved && s->touch_pressed) {
@@ -870,7 +820,7 @@ static void tool_frame(void *d, struct zwp_tablet_tool_v2 *t, uint32_t time)
struct yasl_mouse_event ev = like;
ev.pos = s->tool_pos;
ev.kind = YASL_PTR_SCROLL;
- ev.scroll_lines = -s->tool_wheel;
+ ev.scroll.y = s->tool_wheel;
push_mouse(s, w, ev);
s->tool_wheel = 0;
}
@@ -1105,6 +1055,7 @@ static void seat_drop_pointer(struct seat *s)
static void seat_drop_keyboard(struct seat *s)
{
keyboard_repeat_stop(s);
+ keyboard_text_reset(s);
if (s->keyboard) {
if (wl_keyboard_get_version(s->keyboard) >= WL_KEYBOARD_RELEASE_SINCE_VERSION)
wl_keyboard_release(s->keyboard);
@@ -1281,6 +1232,7 @@ void seat_destroy(struct seat *s)
if (s->cursor_viewport) wp_viewport_destroy(s->cursor_viewport);
if (s->cursor_surface) wl_surface_destroy(s->cursor_surface);
if (s->xkb_state) xkb_state_unref(s->xkb_state);
+ if (s->compose) xkb_compose_state_unref(s->compose);
if (s->xkb_keymap) xkb_keymap_unref(s->xkb_keymap);
if (s->proxy) {
if (wl_seat_get_version(s->proxy) >= WL_SEAT_RELEASE_SINCE_VERSION)
diff --git a/src/x11/window.c b/src/x11/window.c
index 1a0db7d..f7d036f 100644
--- a/src/x11/window.c
+++ b/src/x11/window.c
@@ -2,6 +2,8 @@
#define _DEFAULT_SOURCE
#include "common/backend.h"
#include "common/input.h"
+#include "common/keyboard.h"
+#include "common/utf8.h"
#include "common/timing.h"
#include <X11/Xatom.h>
@@ -103,6 +105,8 @@ struct x11_display {
bool xi2;
bool has_shm;
bool has_xkb;
+ int xkb_event;
+ enum yasl_key keys[256];
Cursor cursors[16];
Atom abs_pressure, abs_tilt_x, abs_tilt_y, tool_type, eraser;
struct x11_pointer *pointers;
@@ -379,7 +383,8 @@ static void xi_pointer_event(struct x11_surface *s, const XIDeviceEvent *ev)
else if (ev->detail >= 4 && ev->detail <= 7) {
if (ev->evtype == XI_ButtonRelease) return;
mouse.kind = YASL_PTR_SCROLL;
- mouse.scroll_lines = (ev->detail == 4 || ev->detail == 6) ? -1 : 1;
+ if (ev->detail <= 5) mouse.scroll.y = ev->detail == 4 ? -1 : 1;
+ else mouse.scroll.x = ev->detail == 6 ? -1 : 1;
} else {
mouse.kind = ev->evtype == XI_ButtonPress ? YASL_PTR_PRESS : YASL_PTR_RELEASE;
mouse.button = pointer_button(ev->detail);
@@ -915,20 +920,24 @@ static void selection_request(struct x11_display *d, XSelectionRequestEvent *req
Atom property = request->property ? request->property : request->target;
int slot = request->selection == XA_PRIMARY ? YASL_PRIMARY : YASL_CLIPBOARD;
const char *text = d->selection[slot];
+ size_t latin1_len = 0;
+ char *latin1 = text ? yasl_utf8_to_latin1(text, &latin1_len) : NULL;
if (text && request->target == d->targets) {
Atom targets[] = {d->targets, d->utf8, XA_STRING};
XChangeProperty(d->native, request->requestor, property, XA_ATOM, 32,
- PropModeReplace, (unsigned char *)targets, 3);
+ PropModeReplace, (unsigned char *)targets, latin1 ? 3 : 2);
reply.xselection.property = property;
} else if (text && (request->target == d->utf8 || request->target == XA_STRING)) {
- size_t len = strlen(text);
+ const char *payload = request->target == XA_STRING ? latin1 : text;
+ size_t len = request->target == XA_STRING ? latin1_len : strlen(text);
long max = XMaxRequestSize(d->native) * 4 - 128;
- if (len <= (size_t)max) {
+ if (payload && len <= (size_t)max) {
XChangeProperty(d->native, request->requestor, property, request->target,
- 8, PropModeReplace, (const unsigned char *)text, (int)len);
+ 8, PropModeReplace, (const unsigned char *)payload, (int)len);
reply.xselection.property = property;
}
}
+ free(latin1);
XSendEvent(d->native, request->requestor, False, 0, &reply);
}
@@ -1038,6 +1047,7 @@ static void selection_notify(struct x11_display *d, XSelectionEvent *event)
char *buf = NULL;
size_t total = 0;
unsigned long offset = 0;
+ bool complete = false;
/* Read up to TEXT_LIMIT bytes in 64KB chunks to avoid huge single-request limits */
while (total < TEXT_LIMIT) {
@@ -1053,7 +1063,7 @@ static void selection_notify(struct x11_display *d, XSelectionEvent *event)
if (data) { XFree(data); data = NULL; }
break;
}
- if ((actual != d->utf8 && actual != XA_STRING) || format != 8 || count == 0) {
+ if ((actual != d->utf8 && actual != XA_STRING) || format != 8) {
if (data) { XFree(data); data = NULL; }
break;
}
@@ -1063,22 +1073,41 @@ static void selection_notify(struct x11_display *d, XSelectionEvent *event)
break;
}
buf = nb;
- memcpy(buf + total, data, count);
+ if (count) memcpy(buf + total, data, count);
total += count;
buf[total] = '\0';
offset += count / 4;
XFree(data);
data = NULL;
- if (left == 0) break;
+ if (left == 0) { complete = true; break; }
}
- if (buf && total > 0) {
- push(s, (struct yasl_input_event){.kind = YASL_INPUT_PASTE}, buf, total);
+ if (complete && actual == XA_STRING) {
+ size_t utf8_len;
+ char *utf8 = yasl_utf8_from_latin1(buf, total, &utf8_len);
free(buf);
- } else free(buf);
+ buf = utf8;
+ if (buf) total = utf8_len;
+ }
+ if (complete && buf && yasl_utf8_validate(buf, total))
+ push(s, (struct yasl_input_event){.kind = YASL_INPUT_PASTE}, buf, total);
+ free(buf);
XDestroyWindow(d->native, r->window);
free(r);
}
+static void refresh_keyboard(struct x11_display *d)
+{
+ memset(d->keys, 0, sizeof(d->keys));
+ if (!d->has_xkb) return;
+ XkbDescPtr keyboard = XkbGetKeyboard(d->native, XkbAllComponentsMask, XkbUseCoreKbd);
+ if (!keyboard) return;
+ if (keyboard->names && keyboard->names->keys) {
+ for (unsigned code = keyboard->min_key_code; code <= keyboard->max_key_code; code++)
+ d->keys[code] = yasl_key_from_xkb_name(keyboard->names->keys[code].name);
+ }
+ XkbFreeKeyboard(keyboard, XkbAllComponentsMask, True);
+}
+
static void key_event(struct x11_surface *s, XKeyEvent *key, bool repeat)
{
struct x11_display *d = display_of(&s->base);
@@ -1086,10 +1115,11 @@ static void key_event(struct x11_surface *s, XKeyEvent *key, bool repeat)
unsigned clean_state = key->state & ~(Mod2Mask | LockMask);
KeySym sym = NoSymbol, base = NoSymbol, shifted = NoSymbol;
+ int group = XkbGroupForCoreState(key->state);
if (d->has_xkb) {
- sym = XkbKeycodeToKeysym(d->native, key->keycode, 0, (clean_state & ShiftMask) ? 1 : 0);
- base = XkbKeycodeToKeysym(d->native, key->keycode, 0, 0);
- shifted = XkbKeycodeToKeysym(d->native, key->keycode, 0, 1);
+ sym = XkbKeycodeToKeysym(d->native, key->keycode, group, (clean_state & ShiftMask) ? 1 : 0);
+ base = XkbKeycodeToKeysym(d->native, key->keycode, group, 0);
+ shifted = XkbKeycodeToKeysym(d->native, key->keycode, group, 1);
}
if (sym == NoSymbol) {
XKeyEvent clean_key = *key;
@@ -1111,21 +1141,26 @@ static void key_event(struct x11_surface *s, XKeyEvent *key, bool repeat)
else len = 0;
}
if (status != XLookupChars && status != XLookupBoth) len = 0;
- } else len = XLookupString(key, local, sizeof(local), &sym, NULL);
+ } else {
+ /* XLookupString's bytes are Latin-1. Use its modifier-aware keysym
+ * and encode UTF-8 even when no input method is available. */
+ XLookupString(key, local, sizeof(local), &sym, NULL);
+ len = xkb_keysym_to_utf8((xkb_keysym_t)sym, local, sizeof(local));
+ len = len > 0 ? len - 1 : 0; /* xkb includes the terminating NUL. */
+ }
}
uint32_t cp = xkb_keysym_to_utf32((xkb_keysym_t)base);
- if (!cp) cp = xkb_keysym_to_utf32((xkb_keysym_t)sym);
push(s, (struct yasl_input_event){
.kind = YASL_INPUT_KEY,
- .key = {.key = (uint32_t)sym, .codepoint = cp,
- .base = key->keycode >= 8 ? key->keycode - 8 : 0,
- .keypad = sym >= XK_KP_Space && sym <= XK_KP_Equal,
- .shifted = xkb_keysym_to_utf32((xkb_keysym_t)shifted),
- .text = xkb_keysym_to_utf32((xkb_keysym_t)sym), .mods = d->mods,
+ .key = {.key = key->keycode < 256 ? d->keys[key->keycode] : YASL_KEY_UNKNOWN,
+ .codepoint = yasl_key_label(cp), .native_code = key->keycode,
+ .shifted = yasl_key_label(xkb_keysym_to_utf32((xkb_keysym_t)shifted)),
+ .mods = d->mods,
.action = key->type == KeyRelease ? YASL_KEY_RELEASE :
repeat ? YASL_KEY_REPEAT : YASL_KEY_PRESS},
}, NULL, 0);
- if (len > 0 && text)
+ if (len > 0 && text && (unsigned char)text[0] >= 0x20 && text[0] != 0x7f &&
+ !(d->mods & (YASL_MOD_CTRL | YASL_MOD_SUPER | YASL_MOD_ALT)))
push(s, (struct yasl_input_event){.kind = YASL_INPUT_TEXT}, text, (size_t)len);
if (text != local) free(text);
}
@@ -1256,6 +1291,17 @@ static void dispatch(struct yasl_display *base)
XFreeEventData(d->native, &event.xcookie);
continue;
}
+ if (event.type == MappingNotify) {
+ XRefreshKeyboardMapping(&event.xmapping);
+ refresh_keyboard(d);
+ continue;
+ }
+ if (d->has_xkb && event.type == d->xkb_event) {
+ XkbEvent *xkb = (XkbEvent *)&event;
+ if (xkb->any.xkb_type == XkbMapNotify) XkbRefreshKeyboardMapping(&xkb->map);
+ refresh_keyboard(d);
+ continue;
+ }
if (XFilterEvent(&event, None)) continue;
if (event.type == SelectionRequest) {
selection_request(d, &event.xselectionrequest);
@@ -1353,7 +1399,8 @@ static void dispatch(struct yasl_display *base)
if (button >= 4 && button <= 7) {
if (event.type == ButtonRelease) break;
mouse.kind = YASL_PTR_SCROLL;
- mouse.scroll_lines = (button == 4 || button == 6) ? -1 : 1;
+ if (button <= 5) mouse.scroll.y = button == 4 ? -1 : 1;
+ else mouse.scroll.x = button == 6 ? -1 : 1;
} else {
mouse.kind = event.type == ButtonPress ? YASL_PTR_PRESS : YASL_PTR_RELEASE;
mouse.button = pointer_button(button);
@@ -1519,8 +1566,15 @@ static struct yasl_display *display_open(struct yasl_loop *loop,
d->base.native_handle = d->native;
d->screen = DefaultScreen(d->native);
d->has_shm = XShmQueryExtension(d->native);
- int xkb_opcode, xkb_event, xkb_error, xkb_major = XkbMajorVersion, xkb_minor = XkbMinorVersion;
+ int xkb_opcode = 0, xkb_event = 0, xkb_error = 0;
+ int xkb_major = XkbMajorVersion, xkb_minor = XkbMinorVersion;
d->has_xkb = XkbQueryExtension(d->native, &xkb_opcode, &xkb_event, &xkb_error, &xkb_major, &xkb_minor);
+ d->xkb_event = xkb_event;
+ if (d->has_xkb) {
+ unsigned events = XkbNewKeyboardNotifyMask | XkbMapNotifyMask | XkbNamesNotifyMask;
+ XkbSelectEvents(d->native, XkbUseCoreKbd, events, events);
+ refresh_keyboard(d);
+ }
int xi_event_base, xi_error_base;
int major = 2, minor = 0;
d->xi2 = XQueryExtension(d->native, "XInputExtension", &d->xi_opcode,