wl: the candidate popup
One wl_surface and one zwp_input_popup_surface_v2, made once; the compositor makes them visible on activate, puts them at the text cursor and takes them down again, so popuparea, popupposition and Caret stay X11-only and popuplayout gets a constant area. Two shm buffers are used in turn, each busy from attach until the compositor releases it, because win.c's single grow-only image would be redrawn while the compositor was still reading it; a picture that arrives while both are busy waits for the next release, and the engine will not send it twice. drawthread no longer runs, so wl.c reads GDK_SCALE itself and calls textinit after it, since setfont reads Fontsz. The buffer is rounded up to a multiple of the scale: one that is not is an invalid_size error at attach, and a preedit alone really does lay out to an odd width at GDK_SCALE=2. drawc is taken where the engine's picture and our surface can part: after every key, after the Keyrelease a deactivate sends, and after the owner poll, hiding for the last two. The poll is not an optimisation -- once another frontend takes the engine, its pictures go unconsumed and no later send will ever say hide. main.c now starts either the Wayland frontend or the X11 popup and XIM, which is where the two popups would otherwise collide. Checked by hand under headless sway 1.12, reading grim screenshots: the hanja list for 가 with its selection and 1-9/125 marker, the emoji search, the popup gone after Escape, and the same at GDK_SCALE=2. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
6
main.c
6
main.c
@@ -57,13 +57,17 @@ threadmain(int argc, char **argv)
|
||||
langinit(argv[1]);
|
||||
composeinit();
|
||||
srvinit();
|
||||
proccreate(drawthread, nil, 16384);
|
||||
proccreate(srvthread, nil, 16384);
|
||||
proccreate(ibusthread, nil, 32768);
|
||||
/* One popup per session: the Wayland frontend draws its own, so the
|
||||
* X11 one and the XIM it serves stand down. */
|
||||
if(getenv("WAYLAND_DISPLAY") != nil && wlinit())
|
||||
proccreate(wlthread, nil, 32768);
|
||||
else{
|
||||
proccreate(drawthread, nil, 16384);
|
||||
display = getenv("DISPLAY");
|
||||
if(display != nil && display[0] != '\0')
|
||||
proccreate(ximthread, nil, 32768);
|
||||
}
|
||||
imthread(nil);
|
||||
}
|
||||
|
||||
224
wl.c
224
wl.c
@@ -21,8 +21,36 @@ enum
|
||||
Maxcode = 8*32, /* keycodes the forwarded bitmap holds */
|
||||
};
|
||||
|
||||
/*
|
||||
* What popuplayout may use. The compositor puts the popup at the text
|
||||
* cursor and constrains it there, so this need only be wide enough for
|
||||
* the layout's own width and tall enough for every row it can show.
|
||||
*/
|
||||
#define Popupw PopupBasew
|
||||
#define Popuph ((Maxdisp + 2)*Fontsz + 2*PopupPad + PopupSep)
|
||||
|
||||
/*
|
||||
* One of two shm buffers, busy from attach until the compositor releases
|
||||
* it. win.c's single grow-only image would be redrawn while the
|
||||
* compositor was still reading it.
|
||||
*/
|
||||
typedef struct Buf Buf;
|
||||
struct Buf
|
||||
{
|
||||
struct wl_buffer *b;
|
||||
u32int *img;
|
||||
int size;
|
||||
int w;
|
||||
int h;
|
||||
int busy;
|
||||
};
|
||||
|
||||
static struct wl_display *display;
|
||||
static struct wl_seat *seat;
|
||||
static struct wl_compositor *comp;
|
||||
static struct wl_shm *shm;
|
||||
static struct wl_surface *surface;
|
||||
static struct zwp_input_popup_surface_v2 *popsurf;
|
||||
static struct zwp_input_method_manager_v2 *immgr;
|
||||
static struct zwp_virtual_keyboard_manager_v1 *vkmgr;
|
||||
static struct zwp_input_method_v2 *im;
|
||||
@@ -49,8 +77,13 @@ static int engaged; /* the engine owes this context a release */
|
||||
static int preshown; /* our preedit is on the client's screen */
|
||||
static int haskeymap;
|
||||
static int gone; /* unavailable: the seat is another input method's */
|
||||
static Buf bufs[2];
|
||||
static int shown; /* a buffer is attached and the popup is up */
|
||||
static Drawcmd held; /* a picture that came while both buffers were busy */
|
||||
static int helddraw;
|
||||
|
||||
static void grabkeyboard(void);
|
||||
static void popup(Drawcmd*);
|
||||
|
||||
static void
|
||||
wllog(char *msg)
|
||||
@@ -160,6 +193,155 @@ answer(Keyres *res, char *tail)
|
||||
preshown = n > 0;
|
||||
}
|
||||
|
||||
static void
|
||||
bufrelease(void *p, struct wl_buffer*)
|
||||
{
|
||||
((Buf*)p)->busy = 0;
|
||||
if(helddraw){
|
||||
helddraw = 0;
|
||||
popup(&held);
|
||||
}
|
||||
}
|
||||
|
||||
static const struct wl_buffer_listener buflisten = {bufrelease};
|
||||
|
||||
static void
|
||||
bufclear(Buf *p)
|
||||
{
|
||||
if(p->b != nil)
|
||||
wl_buffer_destroy(p->b);
|
||||
if(p->img != nil)
|
||||
munmap(p->img, p->size);
|
||||
memset(p, 0, sizeof *p);
|
||||
}
|
||||
|
||||
/* An anonymous file of pixels, shared with the compositor. */
|
||||
static int
|
||||
shmfd(int size)
|
||||
{
|
||||
char path[] = "/dev/shm/strans-XXXXXX";
|
||||
int fd;
|
||||
|
||||
fd = mkstemp(path);
|
||||
if(fd < 0)
|
||||
return -1;
|
||||
unlink(path);
|
||||
if(ftruncate(fd, size) < 0){
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
/*
|
||||
* A free buffer of the wanted size. Sizes differ from draw to draw, so
|
||||
* a free one of the wrong size is remade; both busy means the compositor
|
||||
* is still reading them and the picture has to wait.
|
||||
*/
|
||||
static Buf*
|
||||
getbuf(int w, int h)
|
||||
{
|
||||
struct wl_shm_pool *pool;
|
||||
Buf *p;
|
||||
int fd, i, size;
|
||||
|
||||
for(i = 0; i < nelem(bufs); i++)
|
||||
if(!bufs[i].busy && bufs[i].w == w && bufs[i].h == h)
|
||||
return &bufs[i];
|
||||
for(i = 0; i < nelem(bufs); i++)
|
||||
if(!bufs[i].busy)
|
||||
break;
|
||||
if(i == nelem(bufs))
|
||||
return nil;
|
||||
p = &bufs[i];
|
||||
bufclear(p);
|
||||
size = w * h * sizeof p->img[0];
|
||||
fd = shmfd(size);
|
||||
if(fd < 0)
|
||||
return nil;
|
||||
p->img = mmap(nil, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if(p->img == MAP_FAILED){
|
||||
p->img = nil;
|
||||
close(fd);
|
||||
return nil;
|
||||
}
|
||||
pool = wl_shm_create_pool(shm, fd, size);
|
||||
p->b = wl_shm_pool_create_buffer(pool, 0, w, h,
|
||||
w * sizeof p->img[0], WL_SHM_FORMAT_XRGB8888);
|
||||
wl_shm_pool_destroy(pool);
|
||||
close(fd);
|
||||
wl_buffer_add_listener(p->b, &buflisten, p);
|
||||
p->size = size;
|
||||
p->w = w;
|
||||
p->h = h;
|
||||
return p;
|
||||
}
|
||||
|
||||
/* Unmapping takes the popup down; the compositor puts it back at the
|
||||
* next activate with whatever buffer is still attached. */
|
||||
static void
|
||||
hidepopup(void)
|
||||
{
|
||||
helddraw = 0; /* a picture still waiting for a buffer is stale now */
|
||||
if(!shown)
|
||||
return;
|
||||
wl_surface_attach(surface, nil, 0, 0);
|
||||
wl_surface_commit(surface);
|
||||
shown = 0;
|
||||
}
|
||||
|
||||
static void
|
||||
popup(Drawcmd *dc)
|
||||
{
|
||||
Popup p;
|
||||
Buf *b;
|
||||
|
||||
popuplayout(dc, Popupw, Popuph, &p);
|
||||
if(p.w <= 0 || p.h <= 0){
|
||||
hidepopup();
|
||||
return;
|
||||
}
|
||||
/* A buffer that is not a multiple of the scale is an invalid_size
|
||||
* error at attach, which would kill the connection. */
|
||||
p.w = (p.w + popupscale - 1) / popupscale * popupscale;
|
||||
p.h = (p.h + popupscale - 1) / popupscale * popupscale;
|
||||
b = getbuf(p.w, p.h);
|
||||
if(b == nil){
|
||||
held = *dc; /* the engine will not send it twice */
|
||||
helddraw = 1;
|
||||
return;
|
||||
}
|
||||
popupdraw(b->img, dc, &p);
|
||||
wl_surface_attach(surface, b->b, 0, 0);
|
||||
wl_surface_set_buffer_scale(surface, popupscale);
|
||||
wl_surface_damage_buffer(surface, 0, 0, p.w, p.h);
|
||||
wl_surface_commit(surface);
|
||||
b->busy = 1;
|
||||
shown = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* drawc is empty or holds exactly what the engine last published, so one
|
||||
* take is enough. lastdraw follows the engine and not our surface, so
|
||||
* take it wherever the two can part.
|
||||
*/
|
||||
static void
|
||||
takedraw(void)
|
||||
{
|
||||
Drawcmd dc;
|
||||
|
||||
if(channbrecv(drawc, &dc) > 0)
|
||||
popup(&dc);
|
||||
}
|
||||
|
||||
/* The compositor places the popup; where the text is does not concern us. */
|
||||
static void
|
||||
poprect(void*, struct zwp_input_popup_surface_v2*, int, int, int, int)
|
||||
{
|
||||
}
|
||||
|
||||
static const struct zwp_input_popup_surface_v2_listener poplisten = {poprect};
|
||||
|
||||
static void
|
||||
releasegrab(void)
|
||||
{
|
||||
@@ -184,7 +366,9 @@ leave(void)
|
||||
if(engaged){
|
||||
sendrequest(Keyrelease, 0, 0, &res);
|
||||
engaged = 0;
|
||||
takedraw();
|
||||
}
|
||||
hidepopup();
|
||||
preshown = 0;
|
||||
}
|
||||
|
||||
@@ -198,6 +382,7 @@ flushpending(void)
|
||||
return;
|
||||
sendrequest(Keyreset, 0, 0, &res);
|
||||
answer(&res, "");
|
||||
takedraw();
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -333,6 +518,7 @@ grabkey(void*, struct zwp_input_method_keyboard_grab_v2*, u32int,
|
||||
else
|
||||
sendrequest(Keypress, key, modmask(), &res);
|
||||
answer(&res, text);
|
||||
takedraw();
|
||||
if(text[0] == '\0' && !res.eaten)
|
||||
forward(code, state);
|
||||
}
|
||||
@@ -384,7 +570,12 @@ regglobal(void*, struct wl_registry *r, u32int name, const char *iface, u32int)
|
||||
if(strcmp(iface, wl_seat_interface.name) == 0){
|
||||
if(seat == nil) /* the first seat is ours */
|
||||
seat = wl_registry_bind(r, name, &wl_seat_interface, 1);
|
||||
}else if(strcmp(iface, zwp_input_method_manager_v2_interface.name) == 0)
|
||||
}else if(strcmp(iface, wl_compositor_interface.name) == 0)
|
||||
/* 4 for set_buffer_scale and damage_buffer */
|
||||
comp = wl_registry_bind(r, name, &wl_compositor_interface, 4);
|
||||
else if(strcmp(iface, wl_shm_interface.name) == 0)
|
||||
shm = wl_registry_bind(r, name, &wl_shm_interface, 1);
|
||||
else if(strcmp(iface, zwp_input_method_manager_v2_interface.name) == 0)
|
||||
immgr = wl_registry_bind(r, name,
|
||||
&zwp_input_method_manager_v2_interface, 1);
|
||||
else if(strcmp(iface, zwp_virtual_keyboard_manager_v1_interface.name) == 0)
|
||||
@@ -399,21 +590,29 @@ regremove(void*, struct wl_registry*, u32int)
|
||||
|
||||
static const struct wl_registry_listener reglisten = {regglobal, regremove};
|
||||
|
||||
/* Another frontend may have taken the engine; then our preedit is stale. */
|
||||
/*
|
||||
* Another frontend may have taken the engine; then our preedit and our
|
||||
* popup are both stale. Nothing later will say so: the engine's own
|
||||
* pictures go to whoever took it, and it settles back at blank.
|
||||
*/
|
||||
static void
|
||||
checkowner(void)
|
||||
{
|
||||
Keyres res;
|
||||
|
||||
if(!preshown)
|
||||
if(!preshown && !shown)
|
||||
return;
|
||||
sendrequest(Keycap, 0, 0, &res);
|
||||
if(res.eaten)
|
||||
return;
|
||||
takedraw();
|
||||
hidepopup();
|
||||
if(preshown){
|
||||
zwp_input_method_v2_set_preedit_string(im, "", 0, 0);
|
||||
zwp_input_method_v2_commit(im, serial);
|
||||
preshown = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Whether this session has the protocol, not whether it is Wayland:
|
||||
@@ -432,7 +631,8 @@ wlinit(void)
|
||||
wl_registry_add_listener(reg, ®listen, nil);
|
||||
wl_display_roundtrip(display);
|
||||
/* Without the virtual keyboard every key we do not eat is lost. */
|
||||
if(seat != nil && immgr != nil && vkmgr != nil)
|
||||
if(seat != nil && immgr != nil && vkmgr != nil && comp != nil &&
|
||||
shm != nil)
|
||||
return 1;
|
||||
wl_display_disconnect(display);
|
||||
display = nil;
|
||||
@@ -448,6 +648,7 @@ void
|
||||
wlthread(void*)
|
||||
{
|
||||
struct pollfd pfd;
|
||||
char *scale;
|
||||
int n;
|
||||
|
||||
threadsetname("wl");
|
||||
@@ -457,16 +658,25 @@ wlthread(void*)
|
||||
wllog("cannot make an xkb context");
|
||||
return;
|
||||
}
|
||||
/* drawthread no longer runs, so the scale comes first: setfont
|
||||
* reads Fontsz. */
|
||||
scale = getenv("GDK_SCALE");
|
||||
if(scale != nil && atoi(scale) > 1)
|
||||
popupscale = min(atoi(scale), 4);
|
||||
textinit();
|
||||
im = zwp_input_method_manager_v2_get_input_method(immgr, seat);
|
||||
zwp_input_method_v2_add_listener(im, &imlisten, nil);
|
||||
vk = zwp_virtual_keyboard_manager_v1_create_virtual_keyboard(vkmgr, seat);
|
||||
surface = wl_compositor_create_surface(comp);
|
||||
popsurf = zwp_input_method_v2_get_input_popup_surface(im, surface);
|
||||
zwp_input_popup_surface_v2_add_listener(popsurf, &poplisten, nil);
|
||||
pfd.fd = wl_display_get_fd(display);
|
||||
pfd.events = POLLIN;
|
||||
for(;;){
|
||||
if(wl_display_flush(display) < 0 && errno != EAGAIN)
|
||||
break;
|
||||
pfd.revents = 0;
|
||||
n = poll(&pfd, 1, preshown ? Ownerpoll : -1);
|
||||
n = poll(&pfd, 1, preshown || shown ? Ownerpoll : -1);
|
||||
if(n < 0 && errno != EINTR)
|
||||
break;
|
||||
if(pfd.revents & POLLIN){
|
||||
@@ -477,7 +687,11 @@ wlthread(void*)
|
||||
if(gone){
|
||||
wllog("another input method already has the seat");
|
||||
releasegrab();
|
||||
hidepopup();
|
||||
wl_display_flush(display);
|
||||
bufclear(&bufs[0]);
|
||||
bufclear(&bufs[1]);
|
||||
textclose();
|
||||
return;
|
||||
}
|
||||
checkowner();
|
||||
|
||||
Reference in New Issue
Block a user