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.
This commit is contained in:
2026-08-17 01:59:29 +09:00
parent dd389edc4f
commit b979afc3f7
3 changed files with 23 additions and 16 deletions

View File

@@ -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

View File

@@ -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;

7
xim.c
View File

@@ -196,9 +196,14 @@ 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;
if(clientwin != focuswin)