From bab40c95a73c212fe648969d8ff487eeed6fccef Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Mon, 17 Aug 2026 01:41:54 +0900 Subject: [PATCH] popup: a border, a preedit as wide as its text, one place by the pointer, and no dying MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The popup was a white box on the usually white text it covered, with no edge to tell them apart; it has a border in the separator's colour. A syllable being composed came in a bar twelve ems wide, wiping out the line under it: only candidate rows share that steady width now, a preedit alone hugs its text. A popup placed by the pointer, when the client sends no caret, was placed again on every key and so followed the mouse; it stays where it came up. Clicks on it no longer fall through to the root window and its menu. And one failed draw — a lost pointer reply, a pixmap the server refused — ended the draw thread and the popup for the rest of the session; only a dead connection does now. --- dat.h | 1 + popup_layout.c | 3 ++- tests/popup_test.c | 4 +-- win.c | 65 ++++++++++++++++++++++++++++------------------ 4 files changed, 45 insertions(+), 28 deletions(-) diff --git a/dat.h b/dat.h index 90efd23..936f6da 100644 --- a/dat.h +++ b/dat.h @@ -41,6 +41,7 @@ extern int popupscale; #define Fontsz (32*popupscale) #define PopupPad (4*popupscale) #define PopupSep popupscale +#define PopupBorder popupscale #define PopupNumw Fontsz #define PopupTextw (12*Fontsz) #define PopupBasew (2*PopupPad + PopupNumw + PopupTextw) diff --git a/popup_layout.c b/popup_layout.c index ad187a6..d506346 100644 --- a/popup_layout.c +++ b/popup_layout.c @@ -149,7 +149,8 @@ popuplayout(Drawcmd *dc, int areaw, int areah, Popup *p) if(nmark == 0) markrow = 0; - width = PopupBasew; + /* Rows share one steady width; a preedit alone hugs its text. */ + width = p->n != 0 ? PopupBasew : 2*PopupPad; if(npre) width = max(width, textwidth(&dc->pre) + 2*PopupPad); for(i = 0; i < p->n; i++) diff --git a/tests/popup_test.c b/tests/popup_test.c index 3a7d2fb..bbdd1c2 100644 --- a/tests/popup_test.c +++ b/tests/popup_test.c @@ -144,10 +144,10 @@ popup_layout(struct ct *t) CT_EQ_INT(t, -1, p.rowsy); CT_EQ_INT(t, -1, p.marky); - /* Preedit only. */ + /* Preedit only: as wide as its text. */ dc.pre = mkstr("preedit"); popuplayout(&dc, 2*PopupBasew, Imgh, &p); - CT_EQ_INT(t, PopupBasew, p.w); + CT_EQ_INT(t, textwidth(&dc.pre) + 2*PopupPad, p.w); CT_EQ_INT(t, PopupPad, p.prey); CT_EQ_INT(t, -1, p.sepy); CT_EQ_INT(t, -1, p.rowsy); diff --git a/win.c b/win.c index 4a52ebb..112ecca 100644 --- a/win.c +++ b/win.c @@ -12,6 +12,7 @@ static u32int *img; static xcb_atom_t currentdesktop; static xcb_atom_t workarea; static int hasrandr, imgh, imgw; +static int shown, ptrx, ptry; /* the popup, and where the pointer was as it came up */ static xcb_screen_t* getscr(xcb_connection_t *c, int n) @@ -201,7 +202,7 @@ static int wininit(void) { int n; - u32int mask, vals[4]; + u32int mask, vals[5]; xcb_atom_t type, tooltip; xcb_randr_query_version_cookie_t rc; xcb_randr_query_version_reply_t *rr; @@ -234,14 +235,17 @@ wininit(void) currentdesktop = getatom("_NET_CURRENT_DESKTOP"); workarea = getatom("_NET_WORKAREA"); win = xcb_generate_id(conn); + /* Clicks on the popup go nowhere, not to the root window under it. */ mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | - XCB_CW_OVERRIDE_REDIRECT | XCB_CW_SAVE_UNDER; + XCB_CW_OVERRIDE_REDIRECT | XCB_CW_SAVE_UNDER | + XCB_CW_DONT_PROPAGATE; vals[0] = Colbg; - vals[1] = 0; + vals[1] = Colsep; vals[2] = 1; vals[3] = 1; + vals[4] = XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE; xcb_create_window(conn, XCB_COPY_FROM_PARENT, win, scr->root, - 0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, + 0, 0, 1, 1, PopupBorder, XCB_WINDOW_CLASS_INPUT_OUTPUT, scr->root_visual, mask, vals); type = getatom("_NET_WM_WINDOW_TYPE"); tooltip = getatom("_NET_WM_WINDOW_TYPE_TOOLTIP"); @@ -302,43 +306,54 @@ putimage(int w, int h) xcb_clear_area(conn, 0, win, 0, 0, w, h); } -static int +static void winhide(void) { xcb_unmap_window(conn, win); - return xcb_flush(conn) > 0; + xcb_flush(conn); + shown = 0; } -/* Draws dc at the caret, or by the pointer when the caret is unknown. */ -static int +/* + * Draws dc at the caret, or by the pointer when the caret is unknown: + * where the pointer was as the popup came up, so that it does not + * follow the mouse from key to key. + */ +static void winshow(Drawcmd *dc) { Area area; Popup p; - int ax, ay, px, py, x, y; + int ax, ay, x, y; u32int vals[5]; xcb_query_pointer_reply_t *ptr; xcb_query_pointer_cookie_t cookie; - if(dc->nkouho == 0 && dc->pre.n == 0) - return winhide(); - px = py = 0; - if(!dc->caret.valid){ + if(dc->nkouho == 0 && dc->pre.n == 0){ + winhide(); + return; + } + if(!dc->caret.valid && !shown){ cookie = xcb_query_pointer(conn, scr->root); ptr = xcb_query_pointer_reply(conn, cookie, nil); if(ptr == nil) - return 0; - px = ptr->root_x; - py = ptr->root_y; + return; + ptrx = ptr->root_x; + ptry = ptr->root_y; free(ptr); } - ax = dc->caret.valid ? dc->caret.x : px; - ay = dc->caret.valid ? dc->caret.y : py; + ax = dc->caret.valid ? dc->caret.x : ptrx; + ay = dc->caret.valid ? dc->caret.y : ptry; popupwork(ax, ay, &area); popuplayout(dc, area.w, area.h, &p); - if(p.w <= 0 || p.h <= 0 || !resizebacking(p.w, p.h)) - return 0; - popupposition(&dc->caret, px, py, &area, p.w, p.h, &x, &y); + if(p.w <= 0 || p.h <= 0){ + winhide(); + return; + } + if(!resizebacking(p.w, p.h)) + return; + popupposition(&dc->caret, ptrx, ptry, &area, p.w + 2*PopupBorder, + p.h + 2*PopupBorder, &x, &y); /* Mapping does not restack: raise above windows opened since. */ vals[0] = x; vals[1] = y; @@ -352,7 +367,8 @@ winshow(Drawcmd *dc) popupdraw(img, dc, &p); putimage(p.w, p.h); xcb_map_window(conn, win); - return xcb_flush(conn) > 0; + xcb_flush(conn); + shown = 1; } void @@ -363,8 +379,7 @@ drawthread(void*) threadsetname("draw"); if(!wininit()) return; - while(chanrecv(drawc, &dc) > 0) - if(!winshow(&dc)) - break; + while(chanrecv(drawc, &dc) > 0 && !xcb_connection_has_error(conn)) + winshow(&dc); wincleanup(); }