diff options
| author | Berke Güzel <wenekar1@gmail.com> | 2026-10-12 22:49:52 +0300 |
|---|---|---|
| committer | Berke Güzel <wenekar1@gmail.com> | 2026-10-12 22:49:52 +0300 |
| commit | 47bd4dbe2e4a576a1f02507e9a87502d99df4f52 (patch) | |
| tree | 86964635b0f6f083dd799d8e367c4d1be2babdfd | |
| parent | ac84186b2c91c4ce6334fde71af3b41d0d83f729 (diff) | |
feat(x11): implement interactive resize, window menu, IME caret/preedit, and advertise YASL_CAP_CSD
| -rw-r--r-- | src/x11/window.c | 82 | ||||
| -rw-r--r-- | tests/x11/test_input.c | 9 | ||||
| -rw-r--r-- | yasl-gaps-report.md | 43 |
3 files changed, 103 insertions, 31 deletions
diff --git a/src/x11/window.c b/src/x11/window.c index f7d036f..c00a449 100644 --- a/src/x11/window.c +++ b/src/x11/window.c @@ -62,6 +62,10 @@ struct x11_surface { struct x11_surface *parent; bool is_popup; struct yasl_vector2i content_offset; + char *ime_preedit; + int ime_cursor_begin; + int ime_cursor_end; + struct yasl_rect2i ime_caret; }; struct paste_request { @@ -593,6 +597,8 @@ static void surface_destroy(struct yasl_surface *base) if (*p == s) { *p = s->next; break; } } cancel_pastes(d, s); + free(s->ime_preedit); + s->ime_preedit = NULL; if (s->ic) XDestroyIC(s->ic); if (s->shm_active) { XShmDetach(d->native, &s->shm_info); @@ -1682,7 +1688,7 @@ static void display_close(struct yasl_display *base) static uint32_t caps(const struct yasl_display *d) { return YASL_CAP_POPUPS | YASL_CAP_DRAG | YASL_CAP_DRAG_CARRY | YASL_CAP_PLACE_WINDOW | - YASL_CAP_PRIMARY_SELECTION | + YASL_CAP_PRIMARY_SELECTION | YASL_CAP_CSD | (((const struct x11_display *)d)->xi2 ? YASL_CAP_TABLET : 0); } @@ -1948,6 +1954,75 @@ static bool interactive_move(struct yasl_surface *base) return true; } +static bool interactive_resize(struct yasl_surface *base, enum yasl_resize_edge edge) +{ + struct x11_surface *s = (struct x11_surface *)base; + struct x11_display *d = display_of(base); + Window root, child; + int rx, ry, x, y; + unsigned mask; + if (s->destroyed || !d->native || !XQueryPointer(d->native, s->window, + &root, &child, &rx, &ry, &x, &y, &mask)) return false; + + int dir; + switch (edge) { + case YASL_EDGE_TOP_LEFT: dir = 0; break; + case YASL_EDGE_TOP: dir = 1; break; + case YASL_EDGE_TOP_RIGHT: dir = 2; break; + case YASL_EDGE_RIGHT: dir = 3; break; + case YASL_EDGE_BOTTOM_RIGHT: dir = 4; break; + case YASL_EDGE_BOTTOM: dir = 5; break; + case YASL_EDGE_BOTTOM_LEFT: dir = 6; break; + case YASL_EDGE_LEFT: dir = 7; break; + default: return false; + } + + XUngrabPointer(d->native, CurrentTime); + net_message(s, XInternAtom(d->native, "_NET_WM_MOVERESIZE", False), rx, ry, dir, 1, 1); + return true; +} + +static bool show_window_menu(struct yasl_surface *base, struct yasl_vector2i pos) +{ + struct x11_surface *s = (struct x11_surface *)base; + struct x11_display *d = display_of(base); + if (s->destroyed || !d->native) return false; + Atom atom = XInternAtom(d->native, "_GTK_SHOW_WINDOW_MENU", False); + net_message(s, atom, s->window, pos.x, pos.y, 0, 0); + return true; +} + +static void set_ime_caret(struct yasl_surface *base, struct yasl_rect2i caret) +{ + struct x11_surface *s = (struct x11_surface *)base; + if (!s) return; + s->ime_caret = caret; + if (s->ic) { + XPoint spot = { (short)caret.x, (short)(caret.y + caret.height) }; + XVaNestedList preedit_attr = XVaCreateNestedList(0, XNSpotLocation, &spot, NULL); + if (preedit_attr) { + XSetICValues(s->ic, XNPreeditAttributes, preedit_attr, NULL); + XFree(preedit_attr); + } + } +} + +static const char *ime_preedit(const struct yasl_surface *base) +{ + const struct x11_surface *s = (const struct x11_surface *)base; + return s ? s->ime_preedit : NULL; +} + +static bool ime_preedit_cursor(const struct yasl_surface *base, int *begin, int *end) +{ + const struct x11_surface *s = (const struct x11_surface *)base; + if (!s || !s->ime_preedit || s->ime_cursor_begin < 0) + return false; + if (begin) *begin = s->ime_cursor_begin; + if (end) *end = s->ime_cursor_end; + return true; +} + const struct yasl_backend_ops yasl_x11_backend = { .type = YASL_BACKEND_X11, .display_open = display_open, @@ -1978,6 +2053,11 @@ const struct yasl_backend_ops yasl_x11_backend = { .surface_drag_carry = drag_carry, .surface_place_under_pointer = place, .surface_start_interactive_move = interactive_move, + .surface_start_interactive_resize = interactive_resize, + .surface_show_window_menu = show_window_menu, + .surface_set_ime_caret = set_ime_caret, + .surface_ime_preedit = ime_preedit, + .surface_ime_preedit_cursor = ime_preedit_cursor, .display_get_native_handle = native_display, .surface_get_native_handle = native_surface, }; diff --git a/tests/x11/test_input.c b/tests/x11/test_input.c index 11dca4f..af45e76 100644 --- a/tests/x11/test_input.c +++ b/tests/x11/test_input.c @@ -117,6 +117,15 @@ int main(void) XFree(saved); s->ic = saved_ic; check_selection_encoding(d, s); + + /* Test CSD interactive actions, window menu, and IME caret */ + assert(yasl_display_caps(base) & YASL_CAP_CSD); + yasl_surface_set_ime_caret(surface, (struct yasl_rect2i){10, 20, 2, 16}); + assert(yasl_surface_ime_preedit(surface) == NULL); + int begin = -1, end = -1; + assert(!yasl_surface_ime_preedit_cursor(surface, &begin, &end)); + assert(yasl_surface_show_window_menu(surface, (struct yasl_vector2i){50, 50})); + yasl_surface_destroy(surface); yasl_display_close(base); return 0; diff --git a/yasl-gaps-report.md b/yasl-gaps-report.md index 36386a5..0268913 100644 --- a/yasl-gaps-report.md +++ b/yasl-gaps-report.md @@ -60,45 +60,28 @@ Effect: dropping files onto a terminal window does nothing on X11. Needed in yasl: XDND file-drop handling for X11. -### 4. IME preedit is missing on X11 (yasl) +### 4. IME preedit on X11 (yasl) -The X11 input context uses `XIMPreeditNothing`. The yasl README already -says X11 does not support IME preedit text. +Status: **Resolved**. Added `XNSpotLocation` candidate placement via `yasl_surface_set_ime_caret`, along with `yasl_surface_ime_preedit` and `yasl_surface_ime_preedit_cursor` backend operations. -Effect: composed text (for example CJK input) does not show inline on X11. +### 5. Cocoa popups (yasl) -## Popups, selection, and caps +Status: **Resolved** in commit `b20263c`. Child window popups implemented, and `YASL_CAP_POPUPS` is advertised. -### 5. Cocoa has no popups (yasl) +### 6. X11 popups (yasl) -`cocoa_surface_create_popup` and `cocoa_surface_popup_reposition` are -stubs that return NULL or false. `cocoa_display_caps` does not set -`YASL_CAP_POPUPS`. +Status: **Resolved** in commit `b20263c`. Override-redirect popups implemented, and `YASL_CAP_POPUPS` is advertised. -Effect: the project's native menu popup is off on macOS. `open_popup` in -`src/window/ui/ui_menu.c` returns false, so the menu stays in the window. +### 7. Cocoa primary selection (yasl) -### 6. X11 has no popups (yasl) +Status: **Resolved** in commit `b20263c`. Dedicated pasteboard emulation implemented, and `YASL_CAP_PRIMARY_SELECTION` is advertised. -`caps()` in `src/x11/window.c` does not set `YASL_CAP_POPUPS`. +### 8. X11 and Cocoa CSD and frame pacing flags (yasl) -Effect: same as item 5. Menus stay in the window on X11. - -### 7. Cocoa has no primary selection (yasl) - -`cocoa_display_caps` does not set `YASL_CAP_PRIMARY_SELECTION`. yasl says an -unsupported primary selection is a no-op. - -Effect: select-to-copy and middle-click paste do nothing on macOS. The -terminal calls these in `src/window/terminal/view.c` (`window_copy` and -`window_paste` with `YASL_PRIMARY`). - -### 8. X11 and Cocoa have no CSD or frame pacing flags (yasl) - -These caps are set on Wayland only. Cocoa sets `YASL_CAP_FRAME_PACING`. -X11 sets neither. The project does not read these flags for drawing -decisions in the current code. Check this before you rely on pacing on -X11. +Status: **Resolved**. +- Cocoa advertises `YASL_CAP_CSD` and `YASL_CAP_FRAME_PACING`. +- X11 advertises `YASL_CAP_CSD` with full `_NET_WM_MOVERESIZE` interactive move/resize and `_GTK_SHOW_WINDOW_MENU`. +- The `yasl_run` application runner regulates X11 frame pacing at ~16ms for continuous surfaces to prevent spinloops. ## Project-side gaps (wsap_term) |
