summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBerke Güzel <wenekar1@gmail.com>2026-10-12 22:49:52 +0300
committerBerke Güzel <wenekar1@gmail.com>2026-10-12 22:49:52 +0300
commit47bd4dbe2e4a576a1f02507e9a87502d99df4f52 (patch)
tree86964635b0f6f083dd799d8e367c4d1be2babdfd
parentac84186b2c91c4ce6334fde71af3b41d0d83f729 (diff)
feat(x11): implement interactive resize, window menu, IME caret/preedit, and advertise YASL_CAP_CSD
-rw-r--r--src/x11/window.c82
-rw-r--r--tests/x11/test_input.c9
-rw-r--r--yasl-gaps-report.md43
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)