From 38475318db08b8645599f5a841a72c0d0765763b Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Fri, 14 Aug 2026 22:54:15 +0900 Subject: [PATCH] fix(popup): fit the current X11 monitor --- Makefile | 5 +- dat.h | 12 ++- fn.h | 6 +- font.c | 8 +- popup_layout.c | 179 ++++++++++++++++++++++++++++++------ tests/font_test.c | 3 + tests/popup_test.c | 185 ++++++++++++++++++++++++------------- win.c | 222 +++++++++++++++++++++++++++++++++++++++++---- 8 files changed, 505 insertions(+), 115 deletions(-) diff --git a/Makefile b/Makefile index 8e8af6b..dbf69ef 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,8 @@ XIM_CFLAGS = $(shell pkg-config --cflags xcb-imdkit xkbcommon-x11) XIM_LIBS = $(shell pkg-config --libs xcb-imdkit xkbcommon-x11) TEXT_CFLAGS = $(shell pkg-config --cflags pangocairo cairo) TEXT_LIBS = $(shell pkg-config --libs pangocairo cairo) +POPUP_CFLAGS = $(shell pkg-config --cflags xcb-randr) +POPUP_LIBS = $(shell pkg-config --libs xcb-randr) CFLAGS = -Wall -Wextra -O2 -g $(PKG_CFLAGS) PROG = strans DOCKER_IMAGE = strans-build @@ -22,10 +24,11 @@ all: $(PROG) gtk $(PROG): $(OBJS) $(LD) -o $@ $(OBJS) -lthread -lbio -lxcb $(PKG_LIBS) $(TEXT_LIBS) \ - $(XIM_LIBS) + $(XIM_LIBS) $(POPUP_LIBS) $(OBJS): dat.h fn.h ipc.h font.o: CFLAGS += $(TEXT_CFLAGS) +win.o: CFLAGS += $(POPUP_CFLAGS) $(XIMOBJS): CFLAGS += -I. $(XIM_CFLAGS) clean: diff --git a/dat.h b/dat.h index 9bb1482..75bced6 100644 --- a/dat.h +++ b/dat.h @@ -22,6 +22,7 @@ enum PopupSep = 1, PopupNumw = Fontsz, PopupTextw = 12*Fontsz, + PopupBasew = 2*PopupPad + PopupNumw + PopupTextw, Maxrunes = 64, Maxutf = Maxrunes * UTFmax + 1, }; @@ -31,7 +32,6 @@ enum Maxclients = 64, Maxkouho = 32, Maxdisp = 9, - Imgw = 2*PopupPad + PopupNumw + PopupTextw, Imgh = 2*PopupPad + (Maxdisp + 2)*Fontsz + PopupSep, Colfg = 0x000000, @@ -124,6 +124,15 @@ struct Im typedef struct Drawcmd Drawcmd; typedef struct Caret Caret; +typedef struct Area Area; +struct Area +{ + int x; + int y; + int w; + int h; +}; + struct Caret { int valid; @@ -149,6 +158,7 @@ struct Popup int w; int h; int n; + int row0; int prey; int sepy; int rowsy; diff --git a/fn.h b/fn.h index 3f930e5..d8fc16f 100644 --- a/fn.h +++ b/fn.h @@ -28,9 +28,10 @@ void dictthread(void*); void dictlookup(Dictreq*, Dictres*); void drawthread(void*); -void popupposition(Caret*, int, int, int, int, int, int, int*, int*); +void popuparea(Area*, int, Area*, int, int, Area*); +void popupposition(Caret*, int, int, Area*, int, int, int*, int*); int pagemarker(char*, int, int, int, int); -void popuplayout(Drawcmd*, int, Popup*); +void popuplayout(Drawcmd*, int, int, Popup*); void popupdraw(u32int*, Drawcmd*, Popup*); void imthread(void*); Emit transmap(Im*, Rune); @@ -49,6 +50,7 @@ void* emalloc(ulong); void* erealloc(void*, ulong); int textinit(void); +void textclose(void); int textwidth(Str*); void textdraw(u32int*, int, int, int, int, Str*); void textdrawfit(u32int*, int, int, int, int, int, Str*); diff --git a/font.c b/font.c index 41cab15..2009ca6 100644 --- a/font.c +++ b/font.c @@ -13,8 +13,8 @@ resetlayout(void) pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_NONE); } -static void -textclear(void) +void +textclose(void) { if(layout != nil) g_object_unref(layout); @@ -52,7 +52,7 @@ setfont(void) int textinit(void) { - textclear(); + textclose(); fontmap = pango_cairo_font_map_new(); if(fontmap == nil){ fprint(2, "strans: popup: can't initialize PangoCairo\n"); @@ -64,7 +64,7 @@ textinit(void) layout = pango_layout_new(context); if(context == nil || layout == nil){ fprint(2, "strans: popup: can't create text layout\n"); - textclear(); + textclose(); return 0; } pango_layout_set_single_paragraph_mode(layout, TRUE); diff --git a/popup_layout.c b/popup_layout.c index 1f5cc04..432e759 100644 --- a/popup_layout.c +++ b/popup_layout.c @@ -28,24 +28,98 @@ fillrect(u32int *buf, int w, int h, int x, int y, int rw, int rh, fill(buf + y0*w + x0, max(x1 - x0, 0), color); } +static int +validarea(Area *a) +{ + return a != nil && a->w > 0 && a->h > 0; +} + +static vlong +areadistance(Area *a, int x, int y) +{ + vlong dx, dy, right, bottom; + + right = (vlong)a->x + a->w; + bottom = (vlong)a->y + a->h; + dx = x < a->x ? (vlong)a->x - x : x >= right ? x - right : 0; + dy = y < a->y ? (vlong)a->y - y : y >= bottom ? y - bottom : 0; + return dx + dy; +} + +void +popuparea(Area *mon, int nmon, Area *work, int x, int y, Area *out) +{ + vlong best, bottom, d, right, x0, x1, y0, y1; + int i, pick; + + memset(out, 0, sizeof *out); + pick = -1; + best = (vlong)1 << 62; + for(i = 0; i < nmon; i++){ + if(!validarea(&mon[i])) + continue; + right = (vlong)mon[i].x + mon[i].w; + bottom = (vlong)mon[i].y + mon[i].h; + if(x >= mon[i].x && x < right && + y >= mon[i].y && y < bottom){ + pick = i; + break; + } + d = areadistance(&mon[i], x, y); + if(d < best){ + best = d; + pick = i; + } + } + if(pick < 0) + return; + *out = mon[pick]; + if(!validarea(work)) + return; + x0 = max((vlong)out->x, work->x); + y0 = max((vlong)out->y, work->y); + x1 = min((vlong)out->x + out->w, (vlong)work->x + work->w); + y1 = min((vlong)out->y + out->h, (vlong)work->y + work->h); + if(x1 <= x0 || y1 <= y0) + return; + out->x = x0; + out->y = y0; + out->w = x1 - x0; + out->h = y1 - y0; +} + int pagemarker(char *buf, int nbuf, int first, int shown, int total) { if(nbuf <= 0) return 0; buf[0] = 0; - if(total <= Maxdisp || shown <= 0) + if(shown <= 0 || (first == 0 && shown >= total)) return 0; return snprint(buf, nbuf, "%d-%d/%d", first + 1, first + shown, total); } +static int +fitrows(int h, int pre, int mark, int n) +{ + int fixed; + + fixed = 2*PopupPad + mark*Fontsz; + if(pre) + fixed += Fontsz + (n != 0 ? PopupSep : 0); + if(h <= fixed) + return 0; + return min(n, (h - fixed) / Fontsz); +} + void -popuplayout(Drawcmd *dc, int screenw, Popup *p) +popuplayout(Drawcmd *dc, int areaw, int areah, Popup *p) { char buf[32]; Str mark; - int nmark, npre, y; + vlong width; + int first, i, markrow, nall, nmark, npre, pad, total, y; memset(p, 0, sizeof *p); p->prey = -1; @@ -54,15 +128,52 @@ popuplayout(Drawcmd *dc, int screenw, Popup *p) p->markx = -1; p->marky = -1; p->sely = -1; - p->n = min(max(dc->nkouho, 0), Maxdisp); + p->row0 = 0; + pad = PopupPad; + nall = min(max(dc->nkouho, 0), Maxdisp); npre = dc->pre.n != 0; - if(p->n == 0 && !npre) + if((nall == 0 && !npre) || areaw <= 0 || areah <= 0) return; - p->w = min(Imgw, max(screenw, 0)); - nmark = pagemarker(buf, sizeof buf, dc->first, p->n, dc->total); + total = max(dc->total, dc->first + nall); + markrow = dc->first > 0 || nall < total; + p->n = fitrows(areah, npre, markrow, nall); + if(p->n < nall && !markrow){ + markrow = 1; + p->n = fitrows(areah, npre, markrow, nall); + } + if(p->n == 0 && markrow){ + markrow = 0; + p->n = fitrows(areah, npre, markrow, nall); + } + if(p->n == 0 && npre && nall != 0){ + npre = 0; + p->n = fitrows(areah, npre, markrow, nall); + } + if(p->n == 0 && nall != 0){ + p->n = 1; + pad = 0; + } + if(p->n != 0 && dc->sel >= p->n) + p->row0 = min(dc->sel - p->n + 1, nall - p->n); + first = dc->first + p->row0; + nmark = markrow ? pagemarker(buf, sizeof buf, first, p->n, total) : 0; + if(nmark == 0) + markrow = 0; - y = PopupPad; + width = PopupBasew; + if(npre) + width = max(width, (vlong)textwidth(&dc->pre) + 2*PopupPad); + for(i = 0; i < p->n; i++) + width = max(width, (vlong)textwidth(&dc->kouho[p->row0+i]) + + PopupNumw + 2*PopupPad); + if(nmark != 0){ + sinit(&mark, buf, nmark); + width = max(width, (vlong)textwidth(&mark) + 2*PopupPad); + } + p->w = min(width, areaw); + + y = pad; if(npre){ p->prey = y; y += Fontsz; @@ -75,23 +186,22 @@ popuplayout(Drawcmd *dc, int screenw, Popup *p) p->rowsy = y; y += p->n*Fontsz; } - if(nmark != 0){ + if(markrow){ p->marky = y; y += Fontsz; } - p->h = y + PopupPad; + p->h = min(y + pad, areah); p->numx = PopupPad; p->textx = PopupPad + PopupNumw; p->textw = max(p->w - PopupPad - p->textx, 0); - if(nmark != 0){ - sinit(&mark, buf, nmark); + if(markrow){ p->markw = min(textwidth(&mark), max(p->w - 2*PopupPad, 0)); p->markx = p->w - PopupPad - p->markw; } p->selx = PopupPad; p->selw = max(p->w - 2*PopupPad, 0); - if(dc->sel >= 0 && dc->sel < p->n) - p->sely = p->rowsy + dc->sel*Fontsz; + if(dc->sel >= p->row0 && dc->sel < p->row0 + p->n) + p->sely = p->rowsy + (dc->sel - p->row0)*Fontsz; } void @@ -100,11 +210,11 @@ popupdraw(u32int *img, Drawcmd *dc, Popup *p) char buf[32]; Str mark, num; u32int color; - int i, nmark, y; + int first, i, j, nmark, total, y; if(img == nil || p->n < 0 || p->n > Maxdisp || - p->w <= 0 || p->w > Imgw || - p->h <= 0 || p->h > Imgh) + p->row0 < 0 || p->row0 + p->n > dc->nkouho || + p->w <= 0 || p->h <= 0 || p->h > Imgh) return; fill(img, p->w*p->h, Colbg); if(p->prey >= 0) @@ -117,16 +227,19 @@ popupdraw(u32int *img, Drawcmd *dc, Popup *p) fillrect(img, p->w, p->h, p->selx, p->sely, p->selw, Fontsz, Colsel); for(i = 0, y = p->rowsy; i < p->n; i++, y += Fontsz){ - color = i == dc->sel ? Colselfg : Colfg; + j = p->row0 + i; + color = j == dc->sel ? Colselfg : Colfg; sclear(&num); - sputr(&num, '1' + i + Asciitofull); + sputr(&num, '1' + j + Asciitofull); textdrawfitcolor(img, p->w, p->h, p->numx, y, min(PopupNumw, max(p->w - PopupPad - p->numx, 0)), color, &num); textdrawfitcolor(img, p->w, p->h, p->textx, y, - p->textw, color, &dc->kouho[i]); + p->textw, color, &dc->kouho[j]); } - nmark = pagemarker(buf, sizeof buf, dc->first, p->n, dc->total); + first = dc->first + p->row0; + total = max(dc->total, dc->first + dc->nkouho); + nmark = pagemarker(buf, sizeof buf, first, p->n, total); if(nmark != 0 && p->marky >= 0){ sinit(&mark, buf, nmark); textdrawfit(img, p->w, p->h, p->markx, p->marky, @@ -135,15 +248,23 @@ popupdraw(u32int *img, Drawcmd *dc, Popup *p) } void -popupposition(Caret *caret, int pointerx, int pointery, int screenw, - int screenh, int w, int h, int *x, int *y) +popupposition(Caret *caret, int pointerx, int pointery, Area *area, + int w, int h, int *x, int *y) { - vlong below, px, py, xmax, ymax; + vlong below, bottom, px, py, right, xmax, ymax; + + if(!validarea(area)){ + *x = 0; + *y = 0; + return; + } + right = (vlong)area->x + area->w; + bottom = (vlong)area->y + area->h; if(caret->valid){ px = caret->x; below = (vlong)caret->y + max(caret->h, 0); - if(below >= 0 && below + h <= screenh) + if(below >= area->y && below + h <= bottom) py = below; else py = (vlong)caret->y - h; @@ -151,8 +272,8 @@ popupposition(Caret *caret, int pointerx, int pointery, int screenw, px = (vlong)pointerx + 10; py = (vlong)pointery + 10; } - xmax = max((vlong)screenw - w, 0); - ymax = max((vlong)screenh - h, 0); - *x = max(0, min(px, xmax)); - *y = max(0, min(py, ymax)); + xmax = max(right - w, area->x); + ymax = max(bottom - h, area->y); + *x = max((vlong)area->x, min(px, xmax)); + *y = max((vlong)area->y, min(py, ymax)); } diff --git a/tests/font_test.c b/tests/font_test.c index f57d9e6..3548341 100644 --- a/tests/font_test.c +++ b/tests/font_test.c @@ -253,4 +253,7 @@ font_render(struct ct *t) } checkguards(t, mem); free(mem); + textclose(); + textclose(); + CT_EQ_INT(t, 0, textwidth(&a)); } diff --git a/tests/popup_test.c b/tests/popup_test.c index 5f9cd2d..c186bb9 100644 --- a/tests/popup_test.c +++ b/tests/popup_test.c @@ -1,5 +1,4 @@ #include "test.h" -#include enum { Rgbmask = 0xffffff, @@ -39,6 +38,9 @@ checkpadding(struct ct *t, u32int *img, Popup *p) void popup_layout(struct ct *t) { + static int tinyheight[] = { + 1, PopupPad, 2*PopupPad + Fontsz - 1, + }; static const struct { int first; int shown; @@ -47,6 +49,8 @@ popup_layout(struct ct *t) } markers[] = { { 0, 0, 0, "" }, { 0, 9, 9, "" }, + { 0, 3, 9, "1-3/9" }, + { 8, 1, 9, "9-9/9" }, { 0, 9, 10, "1-9/10" }, { 9, 1, 10, "10-10/10" }, { 0, 9, 18, "1-9/18" }, @@ -56,6 +60,7 @@ popup_layout(struct ct *t) { 27, 5, 32, "28-32/32" }, }; char buf[32]; + Area area, mon[2], out, work; Caret caret; Drawcmd dc; Popup p; @@ -70,59 +75,62 @@ popup_layout(struct ct *t) CT_CHECK(t, strcmp(markers[i].want, buf) == 0); } CT_EQ_INT(t, 12*Fontsz, PopupTextw); - CT_EQ_INT(t, 2*PopupPad + PopupNumw + PopupTextw, Imgw); + CT_EQ_INT(t, 2*PopupPad + PopupNumw + PopupTextw, PopupBasew); CT_EQ_INT(t, 2*PopupPad + (1 + Maxdisp + 1)*Fontsz + PopupSep, Imgh); + /* Monitor selection retains origins and intersects the EWMH work area. */ + mon[0] = (Area){0, 0, 1920, 1080}; + mon[1] = (Area){1920, 200, 1280, 1024}; + work = (Area){0, 30, 3200, 1070}; + popuparea(mon, 2, &work, 2500, 500, &out); + CT_EQ_INT(t, 1920, out.x); + CT_EQ_INT(t, 200, out.y); + CT_EQ_INT(t, 1280, out.w); + CT_EQ_INT(t, 900, out.h); + work = (Area){60, 0, 3140, 1224}; + popuparea(mon, 2, &work, 100, 100, &out); + CT_EQ_INT(t, 60, out.x); + CT_EQ_INT(t, 0, out.y); + CT_EQ_INT(t, 1860, out.w); + CT_EQ_INT(t, 1080, out.h); + work = (Area){5000, 0, 100, 100}; + popuparea(mon, 2, &work, 2500, 500, &out); + CT_EQ_INT(t, mon[1].x, out.x); + CT_EQ_INT(t, mon[1].y, out.y); + CT_EQ_INT(t, mon[1].w, out.w); + CT_EQ_INT(t, mon[1].h, out.h); + mon[0] = (Area){0, 0, 100, 100}; + mon[1] = (Area){200, 0, 100, 100}; + popuparea(mon, 2, nil, 150, 50, &out); + CT_EQ_INT(t, 0, out.x); + memset(&caret, 0, sizeof caret); - popupposition(&caret, 40, 50, 800, 600, 100, 60, &x, &y); - CT_EQ_INT(t, 50, x); + area = (Area){1920, 30, 1280, 900}; + popupposition(&caret, 2000, 50, &area, 100, 60, &x, &y); + CT_EQ_INT(t, 2010, x); CT_EQ_INT(t, 60, y); caret.valid = 1; - caret.x = 70; + caret.x = 2100; caret.y = 80; caret.h = 20; - popupposition(&caret, 40, 50, 800, 600, 100, 60, &x, &y); - CT_EQ_INT(t, 70, x); + popupposition(&caret, 0, 0, &area, 100, 60, &x, &y); + CT_EQ_INT(t, 2100, x); CT_EQ_INT(t, 100, y); - caret.x = 790; - caret.y = 590; - popupposition(&caret, 0, 0, 800, 600, 100, 60, &x, &y); - CT_EQ_INT(t, 700, x); - CT_EQ_INT(t, 530, y); + caret.x = 3150; + caret.y = 850; + popupposition(&caret, 0, 0, &area, 200, 100, &x, &y); + CT_EQ_INT(t, 3000, x); + CT_EQ_INT(t, 750, y); - /* Neither side fits: choose above, then clamp to the screen. */ - caret.x = 20; - caret.y = 30; + /* A popup no larger than a small work area stays inside its origin. */ + area = (Area){100, 200, 80, 80}; + caret.x = 120; + caret.y = 230; caret.h = 20; - popupposition(&caret, 0, 0, 80, 80, 100, 100, &x, &y); - CT_EQ_INT(t, 0, x); - CT_EQ_INT(t, 0, y); - - caret.x = 70; - caret.y = 80; - caret.h = 0; - popupposition(&caret, 0, 0, 800, 600, 100, 60, &x, &y); - CT_EQ_INT(t, 70, x); - CT_EQ_INT(t, 80, y); - caret.h = -20; - popupposition(&caret, 0, 0, 800, 600, 100, 60, &x, &y); - CT_EQ_INT(t, 70, x); - CT_EQ_INT(t, 80, y); - - caret.x = INT_MIN; - caret.y = INT_MAX; - caret.h = INT_MAX; - popupposition(&caret, 0, 0, 800, 600, 100, 60, &x, &y); - CT_EQ_INT(t, 0, x); - CT_EQ_INT(t, 540, y); - caret.x = INT_MAX; - caret.y = INT_MIN; - caret.h = INT_MIN; - popupposition(&caret, 0, 0, INT_MAX, INT_MAX, - INT_MAX, INT_MAX, &x, &y); - CT_EQ_INT(t, 0, x); - CT_EQ_INT(t, 0, y); + popupposition(&caret, 0, 0, &area, 80, 80, &x, &y); + CT_EQ_INT(t, 100, x); + CT_EQ_INT(t, 200, y); if(!CT_CHECK(t, textinit())) return; @@ -130,7 +138,7 @@ popup_layout(struct ct *t) /* Empty sections reserve nothing. */ memset(&dc, 0, sizeof dc); dc.sel = -1; - popuplayout(&dc, Imgw, &p); + popuplayout(&dc, PopupBasew, Imgh, &p); CT_EQ_INT(t, 0, p.w); CT_EQ_INT(t, 0, p.h); CT_EQ_INT(t, -1, p.prey); @@ -139,8 +147,8 @@ popup_layout(struct ct *t) /* Preedit only. */ dc.pre = mkstr("preedit"); - popuplayout(&dc, 2*Imgw, &p); - CT_EQ_INT(t, Imgw, p.w); + popuplayout(&dc, 2*PopupBasew, Imgh, &p); + CT_EQ_INT(t, PopupBasew, p.w); CT_EQ_INT(t, PopupPad, p.prey); CT_EQ_INT(t, -1, p.sepy); CT_EQ_INT(t, -1, p.rowsy); @@ -153,8 +161,8 @@ popup_layout(struct ct *t) dc.nkouho = 2; dc.kouho[0] = mkstr("short"); dc.kouho[1] = mkstr("candidate"); - popuplayout(&dc, 2*Imgw, &p); - CT_EQ_INT(t, Imgw, p.w); + popuplayout(&dc, 2*PopupBasew, Imgh, &p); + CT_EQ_INT(t, PopupBasew, p.w); CT_EQ_INT(t, -1, p.prey); CT_EQ_INT(t, -1, p.sepy); CT_EQ_INT(t, PopupPad, p.rowsy); @@ -167,8 +175,8 @@ popup_layout(struct ct *t) /* Preedit and candidates meet at one separator, with no empty rows. */ dc.pre = mkstr("preedit"); - popuplayout(&dc, 2*Imgw, &p); - CT_EQ_INT(t, Imgw, p.w); + popuplayout(&dc, 2*PopupBasew, Imgh, &p); + CT_EQ_INT(t, PopupBasew, p.w); CT_EQ_INT(t, PopupPad, p.prey); CT_EQ_INT(t, p.prey + Fontsz, p.sepy); CT_EQ_INT(t, p.sepy + PopupSep, p.rowsy); @@ -176,6 +184,15 @@ popup_layout(struct ct *t) CT_EQ_INT(t, p.rowsy + 2*Fontsz + PopupPad, p.h); CT_EQ_INT(t, 2*PopupPad + 3*Fontsz + PopupSep, p.h); + /* Long preedit expands to the monitor cap just like a candidate. */ + sclear(&longrow); + for(i = 0; i < Maxrunes; i++) + sputr(&longrow, 'W'); + memset(&dc, 0, sizeof dc); + dc.pre = longrow; + popuplayout(&dc, 2*PopupBasew, Imgh, &p); + CT_EQ_INT(t, 2*PopupBasew, p.w); + /* The largest panel includes all sections and fits its backing image. */ memset(&dc, 0, sizeof dc); dc.pre = mkstr("preedit"); @@ -183,13 +200,10 @@ popup_layout(struct ct *t) dc.sel = 4; dc.first = 0; dc.total = Maxdisp + 1; - sclear(&longrow); - for(i = 0; i < Maxrunes; i++) - sputr(&longrow, 'W'); for(i = 0; i < Maxdisp; i++) dc.kouho[i] = mkstr(i == dc.sel ? "M" : "candidate"); dc.kouho[0] = longrow; - popuplayout(&dc, 2*Imgw, &p); + popuplayout(&dc, 2*PopupBasew, Imgh, &p); CT_EQ_INT(t, Maxdisp, p.n); CT_EQ_INT(t, PopupPad, p.prey); CT_EQ_INT(t, p.prey + Fontsz, p.sepy); @@ -197,7 +211,7 @@ popup_layout(struct ct *t) CT_EQ_INT(t, p.rowsy + Maxdisp*Fontsz, p.marky); CT_EQ_INT(t, p.marky + Fontsz + PopupPad, p.h); CT_EQ_INT(t, Imgh, p.h); - CT_EQ_INT(t, Imgw, p.w); + CT_EQ_INT(t, 2*PopupBasew, p.w); CT_CHECK(t, p.h <= Imgh); CT_EQ_INT(t, p.w - PopupPad, p.markx + p.markw); CT_EQ_INT(t, PopupPad, p.selx); @@ -207,7 +221,7 @@ popup_layout(struct ct *t) CT_EQ_INT(t, p.numx + PopupNumw, p.textx); CT_EQ_INT(t, p.w - PopupPad, p.textx + p.textw); - img = emalloc(Imgw*Imgh*sizeof img[0]); + img = emalloc(2*PopupBasew*Imgh*sizeof img[0]); popupdraw(img, &dc, &p); checkpadding(t, img, &p); for(x = 0; x < p.w; x++) @@ -254,15 +268,15 @@ popup_layout(struct ct *t) } CT_CHECK(t, markink > 0); - /* A fitted long row is bounded by the fixed candidate content area. */ + /* A long candidate grows from the stable base to the monitor cap. */ memset(&dc, 0, sizeof dc); dc.sel = -1; dc.nkouho = 1; dc.kouho[0] = longrow; n = textwidth(&longrow); - popuplayout(&dc, 2*Imgw, &p); - CT_EQ_INT(t, Imgw, p.w); - CT_EQ_INT(t, PopupTextw, p.textw); + popuplayout(&dc, 2*PopupBasew, Imgh, &p); + CT_EQ_INT(t, 2*PopupBasew, p.w); + CT_CHECK(t, p.textw > PopupTextw); CT_CHECK(t, n > p.textw); CT_EQ_INT(t, PopupPad, p.rowsy); CT_EQ_INT(t, p.w - PopupPad, p.textx + p.textw); @@ -281,7 +295,54 @@ popup_layout(struct ct *t) CT_CHECK(t, ink > 0); CT_EQ_INT(t, n, textwidth(&longrow)); - popuplayout(&dc, Imgw - 1, &p); - CT_EQ_INT(t, Imgw - 1, p.w); + popuplayout(&dc, PopupBasew - 1, Imgh, &p); + CT_EQ_INT(t, PopupBasew - 1, p.w); + + /* Height truncation scrolls the row window to the selected candidate. */ + memset(&dc, 0, sizeof dc); + dc.nkouho = Maxdisp; + dc.sel = Maxdisp - 1; + dc.total = Maxdisp; + for(i = 0; i < Maxdisp; i++) + dc.kouho[i] = mkstr("candidate"); + popuplayout(&dc, PopupBasew, 2*PopupPad + 2*Fontsz, &p); + CT_EQ_INT(t, 1, p.n); + CT_EQ_INT(t, Maxdisp - 1, p.row0); + CT_EQ_INT(t, PopupPad, p.sely); + CT_CHECK(t, p.marky >= 0); + CT_CHECK(t, p.h <= 2*PopupPad + 2*Fontsz); + popupdraw(img, &dc, &p); + numink = 0; + for(y = p.rowsy; y < p.rowsy + Fontsz; y++) + for(x = p.numx; x < p.numx + PopupNumw; x++) + if(pixel(img, &p, x, y) != (Colsel & Rgbmask)) + numink++; + markink = 0; + for(y = p.marky; y < p.marky + Fontsz; y++) + for(x = p.markx; x < p.markx + p.markw; x++) + if(pixel(img, &p, x, y) != (Colbg & Rgbmask)) + markink++; + CT_CHECK(t, numink > 0); /* full-width 9 */ + CT_CHECK(t, markink > 0); /* 9-9/9 */ + + /* The selected row wins over preedit and footer in one-row height. */ + dc.pre = mkstr("preedit"); + popuplayout(&dc, PopupBasew, 2*PopupPad + Fontsz, &p); + CT_EQ_INT(t, -1, p.prey); + CT_EQ_INT(t, 1, p.n); + CT_EQ_INT(t, Maxdisp - 1, p.row0); + CT_EQ_INT(t, -1, p.marky); + + /* With less than one padded row, selected content starts at the top. */ + for(i = 0; i < nelem(tinyheight); i++){ + popuplayout(&dc, PopupBasew, tinyheight[i], &p); + CT_EQ_INT(t, min(tinyheight[i], Fontsz), p.h); + CT_EQ_INT(t, 0, p.rowsy); + CT_EQ_INT(t, 0, p.sely); + CT_EQ_INT(t, 1, p.n); + CT_EQ_INT(t, Maxdisp - 1, p.row0); + popupdraw(img, &dc, &p); + CT_EQ_UINT(t, Colsel, pixel(img, &p, p.w - PopupPad - 1, 0)); + } free(img); } diff --git a/win.c b/win.c index 3876721..256bbed 100644 --- a/win.c +++ b/win.c @@ -1,4 +1,5 @@ #include +#include #include "dat.h" #include "fn.h" @@ -8,7 +9,9 @@ static xcb_window_t win; static xcb_gcontext_t gc; static xcb_pixmap_t pix; static u32int *img; -static int depth; +static xcb_atom_t currentdesktop; +static xcb_atom_t workarea; +static int depth, hasrandr, imgh, imgw; static xcb_screen_t* getscr(xcb_connection_t *c, int n) @@ -69,9 +72,137 @@ validformat(xcb_connection_t *c, xcb_screen_t *s) return 0; } +static xcb_atom_t +getatom(char *name, int exists) +{ + xcb_intern_atom_cookie_t cookie; + xcb_intern_atom_reply_t *reply; + xcb_atom_t atom; + + cookie = xcb_intern_atom(conn, exists, strlen(name), name); + reply = xcb_intern_atom_reply(conn, cookie, nil); + atom = reply == nil ? XCB_ATOM_NONE : reply->atom; + free(reply); + return atom; +} + +static int +getcardinals(xcb_atom_t atom, u32int off, int n, u32int *v) +{ + xcb_get_property_cookie_t cookie; + xcb_get_property_reply_t *reply; + int ok; + + if(atom == XCB_ATOM_NONE) + return 0; + cookie = xcb_get_property(conn, 0, scr->root, atom, + XCB_ATOM_CARDINAL, off, n); + reply = xcb_get_property_reply(conn, cookie, nil); + ok = reply != nil && reply->type == XCB_ATOM_CARDINAL && + reply->format == 32 && + xcb_get_property_value_length(reply) == n*(int)sizeof v[0]; + if(ok) + memmove(v, xcb_get_property_value(reply), n*sizeof v[0]); + free(reply); + return ok; +} + +static int +getworkarea(Area *a) +{ + u32int desktop, v[4]; + + if(!getcardinals(currentdesktop, 0, 1, &desktop) || + desktop > 0x3fffffff || + !getcardinals(workarea, desktop*4, 4, v) || + v[0] > 0x7fffffff || v[1] > 0x7fffffff || + v[2] == 0 || v[2] > 0x7fffffff || + v[3] == 0 || v[3] > 0x7fffffff) + return 0; + a->x = v[0]; + a->y = v[1]; + a->w = v[2]; + a->h = v[3]; + return 1; +} + +static void +getrootarea(Area *a) +{ + xcb_get_geometry_cookie_t cookie; + xcb_get_geometry_reply_t *reply; + + a->x = 0; + a->y = 0; + a->w = scr->width_in_pixels; + a->h = scr->height_in_pixels; + cookie = xcb_get_geometry(conn, scr->root); + reply = xcb_get_geometry_reply(conn, cookie, nil); + if(reply != nil && reply->width > 0 && reply->height > 0){ + a->w = reply->width; + a->h = reply->height; + } + free(reply); +} + +static void +popupwork(int x, int y, Area *out) +{ + xcb_generic_error_t *err; + xcb_randr_get_monitors_cookie_t cookie; + xcb_randr_get_monitors_reply_t *reply; + xcb_randr_monitor_info_iterator_t it; + Area root, net, *mon; + int i, j, n; + + mon = nil; + n = 0; + err = nil; + reply = nil; + if(hasrandr){ + cookie = xcb_randr_get_monitors(conn, scr->root, 1); + reply = xcb_randr_get_monitors_reply(conn, cookie, &err); + if(reply != nil) + n = xcb_randr_get_monitors_monitors_length(reply); + if(n > 0 && (uvlong)n <= (uvlong)(~(ulong)0)/sizeof mon[0]) + mon = malloc(n*sizeof mon[0]); + if(mon != nil){ + it = xcb_randr_get_monitors_monitors_iterator(reply); + for(i = j = 0; i < n && it.rem; i++, xcb_randr_monitor_info_next(&it)){ + if(it.data->width == 0 || it.data->height == 0) + continue; + mon[j].x = it.data->x; + mon[j].y = it.data->y; + mon[j].w = it.data->width; + mon[j].h = it.data->height; + j++; + } + n = j; + if(n == 0){ + free(mon); + mon = nil; + } + } + } + if(mon == nil){ + getrootarea(&root); + mon = &root; + n = 1; + } + if(getworkarea(&net)) + popuparea(mon, n, &net, x, y, out); + else + popuparea(mon, n, nil, x, y, out); + if(mon != &root) + free(mon); + free(reply); + free(err); +} + static void wincleanup(void) { + textclose(); free(img); img = nil; if(conn != nil){ @@ -82,6 +213,11 @@ wincleanup(void) win = 0; gc = 0; pix = 0; + currentdesktop = XCB_ATOM_NONE; + workarea = XCB_ATOM_NONE; + hasrandr = 0; + imgh = 0; + imgw = 0; } static int @@ -91,6 +227,9 @@ wininit(void) u32int mask, vals[4]; xcb_intern_atom_cookie_t c1, c2; xcb_intern_atom_reply_t *r1, *r2; + xcb_randr_query_version_cookie_t rc; + xcb_randr_query_version_reply_t *rr; + const xcb_query_extension_reply_t *rext; conn = xcb_connect(nil, &n); if(conn == nil || xcb_connection_has_error(conn)){ @@ -105,6 +244,16 @@ wininit(void) return 0; } depth = scr->root_depth; + rext = xcb_get_extension_data(conn, &xcb_randr_id); + if(rext != nil && rext->present){ + rc = xcb_randr_query_version(conn, 1, 5); + rr = xcb_randr_query_version_reply(conn, rc, nil); + hasrandr = rr != nil && (rr->major_version > 1 || + (rr->major_version == 1 && rr->minor_version >= 5)); + free(rr); + } + currentdesktop = getatom("_NET_CURRENT_DESKTOP", 0); + workarea = getatom("_NET_WORKAREA", 0); win = xcb_generate_id(conn); mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_SAVE_UNDER; @@ -129,11 +278,6 @@ wininit(void) free(r2); gc = xcb_generate_id(conn); xcb_create_gc(conn, gc, win, 0, nil); - pix = xcb_generate_id(conn); - xcb_create_pixmap(conn, depth, pix, win, Imgw, Imgh); - mask = XCB_CW_BACK_PIXMAP; - xcb_change_window_attributes(conn, win, mask, &pix); - img = emalloc(Imgw * Imgh * sizeof(img[0])); if(!textinit()){ fprint(2, "strans: popup disabled: no usable fonts\n"); wincleanup(); @@ -142,10 +286,53 @@ wininit(void) return 1; } +static int +resizebacking(int w, int h) +{ + xcb_generic_error_t *err; + xcb_pixmap_t old, new; + xcb_void_cookie_t cookie; + uvlong pixels; + int nh, nw; + + if(w <= 0 || h <= 0) + return 0; + if(w <= imgw && h <= imgh) + return 1; + nw = max(w, imgw); + nh = max(h, imgh); + pixels = (uvlong)nw * nh; + if(pixels > (uvlong)(~(ulong)0)/sizeof img[0]) + return 0; + img = erealloc(img, pixels*sizeof img[0]); + new = xcb_generate_id(conn); + cookie = xcb_create_pixmap_checked(conn, depth, new, win, nw, nh); + err = xcb_request_check(conn, cookie); + if(err != nil){ + free(err); + return 0; + } + cookie = xcb_change_window_attributes_checked(conn, win, + XCB_CW_BACK_PIXMAP, &new); + err = xcb_request_check(conn, cookie); + if(err != nil){ + free(err); + xcb_free_pixmap(conn, new); + return 0; + } + old = pix; + pix = new; + imgw = nw; + imgh = nh; + if(old != 0) + xcb_free_pixmap(conn, old); + return 1; +} + static void putimage(int w, int h) { - if(w <= 0 || w > Imgw || h <= 0 || h > Imgh) + if(w <= 0 || w > imgw || h <= 0 || h > imgh) return; xcb_put_image(conn, XCB_IMAGE_FORMAT_Z_PIXMAP, pix, gc, w, h, 0, 0, 0, depth, w * h * 4, (u8int*)img); @@ -163,8 +350,9 @@ winhide(void) static int winshow(Drawcmd *dc) { + Area area; Popup p; - int px, py; + int ax, ay, px, py; u32int vals[4]; xcb_query_pointer_reply_t *ptr; xcb_query_pointer_cookie_t cookie; @@ -172,11 +360,6 @@ winshow(Drawcmd *dc) if(dc->nkouho <= 0 && dc->pre.n == 0){ return winhide(); } - popuplayout(dc, scr->width_in_pixels, &p); - if(p.w <= 0 || p.w > Imgw || p.h <= 0 || p.h > Imgh) - return 0; - vals[2] = p.w; - vals[3] = p.h; px = py = 0; if(!dc->caret.valid){ cookie = xcb_query_pointer(conn, scr->root); @@ -187,17 +370,24 @@ winshow(Drawcmd *dc) py = ptr->root_y; free(ptr); } - popupposition(&dc->caret, px, py, scr->width_in_pixels, - scr->height_in_pixels, p.w, p.h, &px, &py); + ax = dc->caret.valid ? dc->caret.x : px; + ay = dc->caret.valid ? dc->caret.y : py; + 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; + vals[2] = p.w; + vals[3] = p.h; + popupposition(&dc->caret, px, py, &area, p.w, p.h, &px, &py); vals[1] = py; vals[0] = px; xcb_configure_window(conn, win, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, vals); - xcb_map_window(conn, win); popupdraw(img, dc, &p); putimage(p.w, p.h); + xcb_map_window(conn, win); return xcb_flush(conn) > 0; }