diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/cocoa/window.m | 73 | ||||
| -rw-r--r-- | src/common/backend.c | 72 | ||||
| -rw-r--r-- | src/common/keyboard.c | 399 | ||||
| -rw-r--r-- | src/common/keyboard.h | 18 | ||||
| -rw-r--r-- | src/common/meson.build | 2 | ||||
| -rw-r--r-- | src/common/utf8.c | 50 | ||||
| -rw-r--r-- | src/common/utf8.h | 14 | ||||
| -rw-r--r-- | src/wayland/clipboard.c | 8 | ||||
| -rw-r--r-- | src/wayland/ime.c | 2 | ||||
| -rw-r--r-- | src/wayland/internal.h | 19 | ||||
| -rw-r--r-- | src/wayland/keyboard.c | 94 | ||||
| -rw-r--r-- | src/wayland/pointer.c | 126 | ||||
| -rw-r--r-- | src/x11/window.c | 102 |
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, |
