From b979afc3f78d1476bcba2fbfa4965f36d6f104d5 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Mon, 17 Aug 2026 01:59:29 +0900 Subject: [PATCH] xim: a spot is a baseline; a popup flipped above it clears the line An XIM spot has no height, so a popup that flips above it at the bottom of the screen ended on the baseline and covered the line being typed. The spot now stands for the row above it, one popup row tall, as GTK and IBus carets carry their line height; below the spot nothing moves. --- README.md | 7 ++++--- tests/xim_adapter_test.c | 25 +++++++++++++------------ xim.c | 7 ++++++- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index d43ae51..2072bab 100644 --- a/README.md +++ b/README.md @@ -63,9 +63,10 @@ query; the Hanja dictionary lists one modern Hangul syllable at a time. Each XIM style is offered with StatusNothing and with StatusNone. XIM text travels as `COMPOUND_TEXT`, which carries any UTF-8. A client that -sends `XNSpotLocation` gets the popup at that spot whatever its preedit -style; otherwise the popup sits under the focus window, or under the client -window when no focus window is set. +sends `XNSpotLocation` gets the popup under that spot whatever its preedit +style, and above the line when there is no room below; otherwise the popup +sits under the focus window, or under the client window when no focus +window is set. The popup is an X11 window sized by `GDK_SCALE` on HiDPI displays, XIM is X11-only, and GTK popup placement converts the GTK caret to X11 root diff --git a/tests/xim_adapter_test.c b/tests/xim_adapter_test.c index 7777b4b..275b101 100644 --- a/tests/xim_adapter_test.c +++ b/tests/xim_adapter_test.c @@ -667,7 +667,7 @@ xim_adapter_placement(struct ct *t) { /* Spot location first, then the bottom of the focus window, then of * the client window; an unset focus window is the client window. */ - static const struct { + const struct { u32int mask; int focuswin; int fgeo; @@ -677,14 +677,15 @@ xim_adapter_placement(struct ct *t) int valid; int x; int y; + int h; } cases[] = { - {XCB_XIM_XNSpotLocation_MASK, 10, 1, 1, 1, 1, 1, 103, 204}, - {XCB_XIM_XNSpotLocation_MASK, 10, 1, 0, 1, 1, 1, 300, 440}, - {XCB_XIM_XNSpotLocation_MASK, 0, 1, 1, 1, 1, 1, 303, 404}, - {0, 10, 1, 1, 1, 1, 1, 100, 230}, - {0, 0, 1, 1, 1, 1, 1, 300, 440}, - {0, 10, 0, 1, 1, 1, 1, 300, 440}, - {0, 10, 0, 1, 0, 1, 0, 0, 0}, + {XCB_XIM_XNSpotLocation_MASK, 10, 1, 1, 1, 1, 1, 103, 204-Fontsz, Fontsz}, + {XCB_XIM_XNSpotLocation_MASK, 10, 1, 0, 1, 1, 1, 300, 440, 0}, + {XCB_XIM_XNSpotLocation_MASK, 0, 1, 1, 1, 1, 1, 303, 404-Fontsz, Fontsz}, + {0, 10, 1, 1, 1, 1, 1, 100, 230, 0}, + {0, 0, 1, 1, 1, 1, 1, 300, 440, 0}, + {0, 10, 0, 1, 1, 1, 1, 300, 440, 0}, + {0, 10, 0, 1, 0, 1, 0, 0, 0, 0}, }; xcb_im_input_context_t *ic; Icbinding *b; @@ -716,7 +717,7 @@ xim_adapter_placement(struct ct *t) CT_EQ_INT(t, cases[i].valid, state.caret.valid); CT_EQ_INT(t, cases[i].x, state.caret.x); CT_EQ_INT(t, cases[i].y, state.caret.y); - CT_EQ_INT(t, 0, state.caret.h); + CT_EQ_INT(t, cases[i].h, state.caret.h); } } @@ -756,13 +757,13 @@ xim_adapter_placement_updates(struct ct *t) req = nexttrace(t, &f, Keypress, &state); CT_CHECK(t, req.caret.valid); CT_EQ_INT(t, 105, req.caret.x); - CT_EQ_INT(t, 206, req.caret.y); + CT_EQ_INT(t, 206-Fontsz, req.caret.y); wirewins[0].rootx = 150; wirewins[0].rooty = 250; kpress(&state, &ev); req = nexttrace(t, &f, Keypress, &state); CT_EQ_INT(t, 155, req.caret.x); - CT_EQ_INT(t, 256, req.caret.y); + CT_EQ_INT(t, 256-Fontsz, req.caret.y); b->preattr.spot_location.x = 15; b->preattr.spot_location.y = 16; @@ -772,7 +773,7 @@ xim_adapter_placement_updates(struct ct *t) req = nexttrace(t, &f, Keycaret, &state); CT_CHECK(t, req.caret.valid); CT_EQ_INT(t, 165, req.caret.x); - CT_EQ_INT(t, 266, req.caret.y); + CT_EQ_INT(t, 266-Fontsz, req.caret.y); b->preattrmask = 0; wirewins[0].geometryok = 0; hdr.major_opcode = XCB_XIM_SET_IC_FOCUS; diff --git a/xim.c b/xim.c index 28d602e..88b8db7 100644 --- a/xim.c +++ b/xim.c @@ -196,8 +196,13 @@ place(Ic *state) XCB_XIM_XNSpotLocation_MASK){ attr = xcb_im_input_context_get_preedit_attr(state->xic); if(translate(focuswin, attr->spot_location.x, - attr->spot_location.y, &state->caret)) + attr->spot_location.y, &state->caret)){ + /* A spot is a baseline: call the line one row above it, + * so that a popup flipped above it clears the text. */ + state->caret.y -= Fontsz; + state->caret.h = Fontsz; return; + } } if(placebottom(state, focuswin)) return;