From 94c5e361fbb229b0cd6853e2102a7d95f1f358b3 Mon Sep 17 00:00:00 2001 From: Berke Güzel Date: Mon, 12 Oct 2026 22:12:02 +0300 Subject: better public interface and doxygen --- src/x11/window.c | 102 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 78 insertions(+), 24 deletions(-) (limited to 'src/x11') 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 @@ -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, -- cgit v1.2.3