grabkey returns before it asks for the mask when there is no keyboard state, so the guard inside modmask answered a question nobody put to it -- except the test, which was the only caller that could reach it. A check kept alive by the test that reaches it is one line of code and one line of test to delete. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
709 lines
17 KiB
C
709 lines
17 KiB
C
#include "dat.h"
|
|
#include "fn.h"
|
|
|
|
#include <errno.h>
|
|
#include <poll.h>
|
|
#include <sys/mman.h>
|
|
#include <wayland-client.h>
|
|
#include <xkbcommon/xkbcommon.h>
|
|
#include "imv2.h"
|
|
#include "vkv1.h"
|
|
|
|
/*
|
|
* text-input-v3 content purposes whose keys the engine never sees. IBus
|
|
* numbers these alike but does not share them: one enum here is cheaper
|
|
* than reaching into dat.h and ibus.c both.
|
|
*/
|
|
enum
|
|
{
|
|
Purposepassword = 8,
|
|
Purposepin = 9,
|
|
Maxcode = 0x300, /* KEY_MAX+1: what evdev can send */
|
|
};
|
|
|
|
/*
|
|
* 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;
|
|
static struct zwp_virtual_keyboard_v1 *vk;
|
|
static struct zwp_input_method_keyboard_grab_v2 *grab;
|
|
static struct xkb_context *xkbctx;
|
|
static struct xkb_state *kstate;
|
|
static Channel *replyc;
|
|
|
|
/*
|
|
* The protocol gives one input context per seat, so the engine knows us
|
|
* by one address; nil would mean no owner at all.
|
|
*/
|
|
static int context;
|
|
|
|
static u32int serial; /* done events counted; the serial commit takes */
|
|
static int active; /* a text input is being served */
|
|
static int pendingactive;
|
|
static int activated; /* an activate arrived since the last done */
|
|
static u32int purpose, pendingpurpose;
|
|
static u32int sent[Maxcode/32]; /* keycodes passed on and still down */
|
|
static u32int lasttime; /* the compositor's clock, for a release of our own */
|
|
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)
|
|
{
|
|
fprint(2, "strans: wl: %s\n", msg);
|
|
}
|
|
|
|
static int
|
|
hidden(void)
|
|
{
|
|
return purpose == Purposepassword || purpose == Purposepin;
|
|
}
|
|
|
|
static void
|
|
sendrequest(int op, u32int ks, u32int mod, Keyres *res)
|
|
{
|
|
Keyreq kr;
|
|
|
|
memset(&kr, 0, sizeof kr);
|
|
kr.owner = &context;
|
|
kr.clientpre = 1; /* set_preedit_string draws it; the popup does not */
|
|
kr.op = op;
|
|
kr.ks = ks;
|
|
kr.mod = mod;
|
|
kr.reply = replyc;
|
|
chansend(keyc, &kr);
|
|
chanrecv(replyc, res);
|
|
}
|
|
|
|
/* The engine's modifier bits, read from the grab's own keyboard state. */
|
|
static u32int
|
|
modmask(void)
|
|
{
|
|
static char *name[] = {
|
|
XKB_MOD_NAME_SHIFT, XKB_MOD_NAME_CTRL,
|
|
XKB_MOD_NAME_ALT, XKB_MOD_NAME_LOGO,
|
|
};
|
|
static u32int bit[] = {Mshift, Mctrl, Malt, Msuper};
|
|
u32int m;
|
|
int i;
|
|
|
|
m = 0;
|
|
for(i = 0; i < nelem(name); i++)
|
|
if(xkb_state_mod_name_is_active(kstate, name[i],
|
|
XKB_STATE_MODS_EFFECTIVE) > 0)
|
|
m |= bit[i];
|
|
return m;
|
|
}
|
|
|
|
static int
|
|
issent(u32int code)
|
|
{
|
|
return code < Maxcode && (sent[code/32] & 1<<(code%32)) != 0;
|
|
}
|
|
|
|
/*
|
|
* A key the engine did not take goes to the client as a key, not as
|
|
* text: eaten = 0 means it is not ours, and committing it would lie to a
|
|
* terminal and to anything with a keybinding.
|
|
*/
|
|
static void
|
|
forward(u32int code, u32int state)
|
|
{
|
|
if(!haskeymap || code >= Maxcode)
|
|
return;
|
|
zwp_virtual_keyboard_v1_key(vk, lasttime, code, state);
|
|
if(state == WL_KEYBOARD_KEY_STATE_PRESSED)
|
|
sent[code/32] |= 1<<(code%32);
|
|
else
|
|
sent[code/32] &= ~(1<<(code%32));
|
|
}
|
|
|
|
/* Whatever we passed on is still down; the client must not keep it. */
|
|
static void
|
|
releasekeys(void)
|
|
{
|
|
u32int code;
|
|
|
|
for(code = 0; code < Maxcode; code++)
|
|
if(issent(code))
|
|
forward(code, WL_KEYBOARD_KEY_STATE_RELEASED);
|
|
}
|
|
|
|
/*
|
|
* The engine's text, then the composed text, in one commit_string: the
|
|
* compositor keeps only the last one sent before a commit. A commit is
|
|
* an event and goes only when there is something, while a preedit is
|
|
* state and goes every time, empty to take the last one down.
|
|
*/
|
|
static void
|
|
answer(Keyres *res, char *tail)
|
|
{
|
|
char utf[2*Maxutf];
|
|
int n;
|
|
|
|
n = stoutf(&res->commit, utf, sizeof utf);
|
|
if(tail[0] != '\0')
|
|
n += snprint(utf+n, sizeof utf - n, "%s", tail);
|
|
if(n > 0)
|
|
zwp_input_method_v2_commit_string(im, utf);
|
|
n = stoutf(&res->preedit, utf, sizeof utf);
|
|
if(n > 0 || preshown) /* an empty one only to take the last down */
|
|
zwp_input_method_v2_set_preedit_string(im, utf, n, n);
|
|
zwp_input_method_v2_commit(im, serial);
|
|
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)
|
|
{
|
|
if(grab == nil)
|
|
return;
|
|
zwp_input_method_keyboard_grab_v2_release(grab);
|
|
grab = nil;
|
|
}
|
|
|
|
/*
|
|
* The context is going: give the client its keys back and the pending
|
|
* text, as a lost focus does everywhere else. A client that only
|
|
* disabled its text input still holds the focus and takes the commit;
|
|
* when the focus itself moved, the compositor sent the text input its
|
|
* leave first and drops the commit whatever serial it carries.
|
|
*/
|
|
static void
|
|
leave(void)
|
|
{
|
|
Keyres res;
|
|
|
|
releasekeys();
|
|
if(engaged){
|
|
sendrequest(Keyrelease, 0, 0, &res);
|
|
answer(&res, "");
|
|
engaged = 0;
|
|
takedraw();
|
|
}
|
|
hidepopup();
|
|
preshown = 0;
|
|
}
|
|
|
|
/* A purpose that turned secret with text pending gets the text first. */
|
|
static void
|
|
flushpending(void)
|
|
{
|
|
Keyres res;
|
|
|
|
if(!engaged)
|
|
return;
|
|
sendrequest(Keyreset, 0, 0, &res);
|
|
answer(&res, "");
|
|
takedraw();
|
|
}
|
|
|
|
/*
|
|
* A purpose belongs to the activation: the compositor sends a content type
|
|
* only for a client that asked for one, so a client that asks for none has
|
|
* none, whatever the last one wanted.
|
|
*/
|
|
static void
|
|
imactivate(void*, struct zwp_input_method_v2*)
|
|
{
|
|
pendingactive = 1;
|
|
activated = 1;
|
|
pendingpurpose = 0;
|
|
}
|
|
|
|
static void
|
|
imdeactivate(void*, struct zwp_input_method_v2*)
|
|
{
|
|
pendingactive = 0;
|
|
}
|
|
|
|
static void
|
|
imsurrounding(void*, struct zwp_input_method_v2*, const char*, u32int, u32int)
|
|
{
|
|
}
|
|
|
|
static void
|
|
imcause(void*, struct zwp_input_method_v2*, u32int)
|
|
{
|
|
}
|
|
|
|
static void
|
|
imcontent(void*, struct zwp_input_method_v2*, u32int, u32int what)
|
|
{
|
|
pendingpurpose = what;
|
|
}
|
|
|
|
/*
|
|
* activate, deactivate and content_type are pending until done, and the
|
|
* count of done events is the serial a commit must carry: wlroots throws
|
|
* away a whole commit whose serial does not match, without a word.
|
|
*/
|
|
static void
|
|
imdone(void*, struct zwp_input_method_v2*)
|
|
{
|
|
int washidden;
|
|
|
|
serial++;
|
|
washidden = hidden();
|
|
purpose = pendingpurpose;
|
|
if(activated || (active && !pendingactive))
|
|
leave();
|
|
else if(active && !washidden && hidden())
|
|
flushpending();
|
|
if(activated && pendingactive)
|
|
grabkeyboard();
|
|
else if(!pendingactive)
|
|
releasegrab();
|
|
active = pendingactive;
|
|
activated = 0;
|
|
}
|
|
|
|
static void
|
|
imgone(void*, struct zwp_input_method_v2*)
|
|
{
|
|
gone = 1;
|
|
}
|
|
|
|
static const struct zwp_input_method_v2_listener imlisten = {
|
|
imactivate, imdeactivate, imsurrounding, imcause, imcontent, imdone,
|
|
imgone,
|
|
};
|
|
|
|
/*
|
|
* The same fd goes to the virtual keyboard, so a key we pass on means
|
|
* there what it meant here.
|
|
*/
|
|
static void
|
|
grabkeymap(void*, struct zwp_input_method_keyboard_grab_v2*, u32int format,
|
|
int fd, u32int size)
|
|
{
|
|
struct xkb_keymap *keymap;
|
|
struct xkb_state *state;
|
|
char *s;
|
|
|
|
if(format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1){
|
|
close(fd);
|
|
return;
|
|
}
|
|
s = mmap(nil, size, PROT_READ, MAP_PRIVATE, fd, 0);
|
|
if(s == MAP_FAILED){
|
|
close(fd);
|
|
return;
|
|
}
|
|
keymap = xkb_keymap_new_from_string(xkbctx, s,
|
|
XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
|
|
munmap(s, size);
|
|
state = keymap != nil ? xkb_state_new(keymap) : nil;
|
|
xkb_keymap_unref(keymap);
|
|
if(state == nil){
|
|
wllog("cannot read the keyboard mapping");
|
|
close(fd);
|
|
return;
|
|
}
|
|
xkb_state_unref(kstate);
|
|
kstate = state;
|
|
zwp_virtual_keyboard_v1_keymap(vk, format, fd, size);
|
|
close(fd);
|
|
haskeymap = 1;
|
|
}
|
|
|
|
static void
|
|
grabkey(void*, struct zwp_input_method_keyboard_grab_v2*, u32int,
|
|
u32int time, u32int code, u32int state)
|
|
{
|
|
Keyres res;
|
|
char text[Maxutf];
|
|
u32int key, sym;
|
|
|
|
lasttime = time;
|
|
if(state != WL_KEYBOARD_KEY_STATE_PRESSED){
|
|
if(issent(code))
|
|
forward(code, state);
|
|
return;
|
|
}
|
|
if(!active || hidden() || kstate == nil){
|
|
forward(code, state);
|
|
return;
|
|
}
|
|
sym = xkb_state_key_get_one_sym(kstate, code + 8);
|
|
if(composekey(sym, text, sizeof text))
|
|
return; /* a sequence in the making */
|
|
key = ipckeysym(sym, xkb_keysym_to_utf32(sym));
|
|
if(keymeaningful(key))
|
|
engaged = 1;
|
|
if(text[0] != '\0')
|
|
/* Composed text follows whatever was pending. */
|
|
sendrequest(Keyreset, 0, 0, &res);
|
|
else
|
|
sendrequest(Keypress, key, modmask(), &res);
|
|
answer(&res, text);
|
|
takedraw();
|
|
if(text[0] == '\0' && !res.eaten)
|
|
forward(code, state);
|
|
}
|
|
|
|
/*
|
|
* Our own lookup must see Shift, and so must the client: wlroots derives
|
|
* no state from a virtual keyboard's keys, so a modifier forwarded as a
|
|
* keycode arrives bare and Ctrl+C reaches the client as c.
|
|
*/
|
|
static void
|
|
grabmodifiers(void*, struct zwp_input_method_keyboard_grab_v2*, u32int,
|
|
u32int depressed, u32int latched, u32int locked, u32int group)
|
|
{
|
|
if(kstate != nil)
|
|
xkb_state_update_mask(kstate, depressed, latched, locked,
|
|
0, 0, group);
|
|
if(haskeymap)
|
|
zwp_virtual_keyboard_v1_modifiers(vk, depressed, latched,
|
|
locked, group);
|
|
}
|
|
|
|
static void
|
|
grabrepeat(void*, struct zwp_input_method_keyboard_grab_v2*, int, int)
|
|
{
|
|
}
|
|
|
|
static const struct zwp_input_method_keyboard_grab_v2_listener grablisten = {
|
|
grabkeymap, grabkey, grabmodifiers, grabrepeat,
|
|
};
|
|
|
|
/*
|
|
* sway hands keys to the grab holder whenever the grab object exists,
|
|
* without asking whether the input method is active, so a grab held
|
|
* while inactive takes every key in the session. Two activates can also
|
|
* arrive without a deactivate between them, and a second grab_keyboard
|
|
* then fails and leaves a dead object: release before grabbing again.
|
|
*/
|
|
static void
|
|
grabkeyboard(void)
|
|
{
|
|
releasegrab();
|
|
grab = zwp_input_method_v2_grab_keyboard(im);
|
|
zwp_input_method_keyboard_grab_v2_add_listener(grab, &grablisten, nil);
|
|
}
|
|
|
|
static void
|
|
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, 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)
|
|
vkmgr = wl_registry_bind(r, name,
|
|
&zwp_virtual_keyboard_manager_v1_interface, 1);
|
|
}
|
|
|
|
static void
|
|
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 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 && !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:
|
|
* GNOME and KWin set WAYLAND_DISPLAY and have no manager, and XIM is the
|
|
* only path left to them.
|
|
*/
|
|
int
|
|
wlinit(void)
|
|
{
|
|
struct wl_registry *reg;
|
|
|
|
display = wl_display_connect(nil);
|
|
if(display == nil)
|
|
return 0;
|
|
reg = wl_display_get_registry(display);
|
|
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 && comp != nil &&
|
|
shm != nil)
|
|
return 1;
|
|
wl_display_disconnect(display);
|
|
display = nil;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* One process touches libwayland: it reads events, dispatches them and
|
|
* answers the engine, in order. dispatch reads only when poll says
|
|
* there is something, so it never blocks and the owner check still runs.
|
|
*/
|
|
void
|
|
wlthread(void*)
|
|
{
|
|
struct pollfd pfd;
|
|
char *scale;
|
|
int n;
|
|
|
|
threadsetname("wl");
|
|
replyc = chancreate(sizeof(Keyres), 0);
|
|
xkbctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
|
|
if(xkbctx == nil){
|
|
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 || shown ? Ownerpoll : -1);
|
|
if(n < 0 && errno != EINTR)
|
|
break;
|
|
if(pfd.revents & POLLIN){
|
|
if(wl_display_dispatch(display) < 0)
|
|
break;
|
|
}else if(wl_display_dispatch_pending(display) < 0)
|
|
break;
|
|
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();
|
|
}
|
|
if(wl_display_get_error(display) != 0)
|
|
die("wl: compositor disconnected");
|
|
die("wl: frontend stopped");
|
|
}
|