diff --git a/main.c b/main.c index a79b8c5..afdfe9d 100644 --- a/main.c +++ b/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); - display = getenv("DISPLAY"); - if(display != nil && display[0] != '\0') - proccreate(ximthread, nil, 32768); + else{ + proccreate(drawthread, nil, 16384); + display = getenv("DISPLAY"); + if(display != nil && display[0] != '\0') + proccreate(ximthread, nil, 32768); + } imthread(nil); } diff --git a/wl.c b/wl.c index 5e910fb..dd57216 100644 --- a/wl.c +++ b/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,20 +590,28 @@ 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; - zwp_input_method_v2_set_preedit_string(im, "", 0, 0); - zwp_input_method_v2_commit(im, serial); - preshown = 0; + takedraw(); + hidepopup(); + if(preshown){ + zwp_input_method_v2_set_preedit_string(im, "", 0, 0); + zwp_input_method_v2_commit(im, serial); + preshown = 0; + } } /* @@ -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();