fix: repair Wayland key lifecycle

This commit is contained in:
2026-08-12 15:58:24 +09:00
parent 1d8d711882
commit 9c43bcb75c
7 changed files with 509 additions and 77 deletions

371
wayland.c
View File

@@ -1,18 +1,20 @@
#include "dat.h"
#include "fn.h"
#include "wayland_state.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <poll.h>
#include <string.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#include <wayland-client.h>
#include <xkbcommon/xkbcommon.h>
#include "input-method-unstable-v2-client-protocol.h"
#include "virtual-keyboard-unstable-v1-client-protocol.h"
static struct wl_display *dpy;
static struct wl_registry *reg;
static struct wl_seat *seat;
static struct zwp_input_method_manager_v2 *immgr;
static struct zwp_virtual_keyboard_manager_v1 *vkmgr;
@@ -24,9 +26,25 @@ static struct xkb_keymap *keymap;
static struct xkb_state *xkbst;
static int active;
static int pending;
static int newactivation;
static int running;
static u32int imserial;
static u32int seatname;
static u32int immgrname;
static u32int vkmgrname;
static Channel *replyc;
static uvlong owner;
static Wlstate keys;
static uvlong
nowms(void)
{
struct timespec ts;
if(clock_gettime(CLOCK_MONOTONIC, &ts) < 0)
return 0;
return (uvlong)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}
static void
sendkey(u32int ks, u32int mod, Keyres *res)
@@ -45,17 +63,20 @@ sendkey(u32int ks, u32int mod, Keyres *res)
}
static void
sendrelease(void)
releaseowner(void)
{
Keyreq kr;
Keyres res;
if(owner == 0)
return;
memset(&kr, 0, sizeof kr);
kr.owner = owner;
kr.op = Keyrelease;
kr.reply = replyc;
chansend(keyc, &kr);
chanrecv(replyc, &res);
owner = 0;
}
static u32int
@@ -95,47 +116,101 @@ mget(void)
}
static void
kpress(uint32_t time, uint32_t keycode, uint32_t state)
putresult(Keyres *res)
{
char commit[Maxutf], preedit[Maxutf];
int plen;
if(im == nil)
return;
stoutf(&res->commit, commit, sizeof commit);
stoutf(&res->preedit, preedit, sizeof preedit);
if(commit[0] != '\0')
zwp_input_method_v2_commit_string(im, commit);
plen = strlen(preedit);
zwp_input_method_v2_set_preedit_string(im, preedit, plen, plen);
zwp_input_method_v2_commit(im, imserial);
}
static void
forwardkey(u32int time, u32int keycode, u32int state)
{
if(vk != nil)
zwp_virtual_keyboard_v1_key(vk, time, keycode, state);
}
static void
keypress(u32int time, u32int keycode)
{
xkb_keysym_t ks;
u32int k, mod;
Keyres res;
char commit[Maxutf], preedit[Maxutf];
int plen;
int repeatable;
if(state != WL_KEYBOARD_KEY_STATE_PRESSED || xkbst == nil){
zwp_virtual_keyboard_v1_key(vk, time, keycode, state);
if(xkbst == nil || keycode >= Wlkeymax){
forwardkey(time, keycode, WL_KEYBOARD_KEY_STATE_PRESSED);
wlstatepress(&keys, keycode, 0, 0, nowms());
return;
}
ks = xkb_state_key_get_one_sym(xkbst, keycode + 8);
k = kget(ks);
if(k == 0){
zwp_virtual_keyboard_v1_key(vk, time, keycode, state);
forwardkey(time, keycode, WL_KEYBOARD_KEY_STATE_PRESSED);
wlstatepress(&keys, keycode, 0, 0, nowms());
return;
}
mod = mget();
sendkey(k, mod, &res);
stoutf(&res.commit, commit, sizeof commit);
stoutf(&res.preedit, preedit, sizeof preedit);
if(commit[0] != '\0'){
zwp_input_method_v2_commit_string(im, commit);
zwp_input_method_v2_commit(im, imserial);
}
plen = strlen(preedit);
zwp_input_method_v2_set_preedit_string(im, preedit, plen, plen);
zwp_input_method_v2_commit(im, imserial);
putresult(&res);
repeatable = keymap != nil &&
xkb_keymap_key_repeats(keymap, keycode + 8);
wlstatepress(&keys, keycode, res.eaten, repeatable, nowms());
if(!res.eaten)
zwp_virtual_keyboard_v1_key(vk, time, keycode, state);
forwardkey(time, keycode, WL_KEYBOARD_KEY_STATE_PRESSED);
}
static void
keyrepeat(u32int time, u32int keycode)
{
xkb_keysym_t ks;
u32int k;
Keyres res;
if(xkbst == nil)
return;
ks = xkb_state_key_get_one_sym(xkbst, keycode + 8);
k = kget(ks);
if(k == 0)
return;
sendkey(k, mget(), &res);
putresult(&res);
/* The original eaten press still owns its eventual physical release. */
if(!res.eaten){
forwardkey(time, keycode, WL_KEYBOARD_KEY_STATE_PRESSED);
forwardkey(time, keycode, WL_KEYBOARD_KEY_STATE_RELEASED);
}
}
static void
kpress(u32int time, u32int keycode, u32int state)
{
if(state == WL_KEYBOARD_KEY_STATE_PRESSED){
keypress(time, keycode);
return;
}
if(wlstaterelease(&keys, keycode))
forwardkey(time, keycode, state);
}
static void
kg_keymap(void *data, struct zwp_input_method_keyboard_grab_v2 *g,
uint32_t format, int32_t fd, uint32_t size)
u32int format, int32_t fd, u32int size)
{
char *s;
(void)data;
(void)g;
wlstatecancelrepeat(&keys);
if(format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1){
close(fd);
return;
@@ -155,13 +230,14 @@ kg_keymap(void *data, struct zwp_input_method_keyboard_grab_v2 *g,
xkbst = xkb_state_new(keymap);
munmap(s, size);
}
zwp_virtual_keyboard_v1_keymap(vk, format, fd, size);
if(vk != nil)
zwp_virtual_keyboard_v1_keymap(vk, format, fd, size);
close(fd);
}
static void
kg_key(void *data, struct zwp_input_method_keyboard_grab_v2 *g,
uint32_t serial, uint32_t time, uint32_t key, uint32_t state)
u32int serial, u32int time, u32int key, u32int state)
{
(void)data;
(void)g;
@@ -171,15 +247,15 @@ kg_key(void *data, struct zwp_input_method_keyboard_grab_v2 *g,
static void
kg_mods(void *data, struct zwp_input_method_keyboard_grab_v2 *g,
uint32_t serial, uint32_t dep, uint32_t lat, uint32_t lck,
uint32_t group)
u32int serial, u32int dep, u32int lat, u32int lck, u32int group)
{
(void)data;
(void)g;
(void)serial;
if(xkbst != nil)
xkb_state_update_mask(xkbst, dep, lat, lck, 0, 0, group);
zwp_virtual_keyboard_v1_modifiers(vk, dep, lat, lck, group);
if(vk != nil)
zwp_virtual_keyboard_v1_modifiers(vk, dep, lat, lck, group);
}
static void
@@ -188,8 +264,7 @@ kg_repeat(void *data, struct zwp_input_method_keyboard_grab_v2 *g,
{
(void)data;
(void)g;
(void)rate;
(void)delay;
wlstaterepeatinfo(&keys, rate, delay);
}
static const struct zwp_input_method_keyboard_grab_v2_listener
@@ -206,6 +281,7 @@ im_activate(void *data, struct zwp_input_method_v2 *m)
(void)data;
(void)m;
pending = 1;
newactivation = 1;
}
static void
@@ -214,11 +290,12 @@ im_deactivate(void *data, struct zwp_input_method_v2 *m)
(void)data;
(void)m;
pending = 0;
wlstateclear(&keys);
}
static void
im_surrounding(void *data, struct zwp_input_method_v2 *m,
const char *text, uint32_t cursor, uint32_t anchor)
const char *text, u32int cursor, u32int anchor)
{
(void)data;
(void)m;
@@ -228,7 +305,7 @@ im_surrounding(void *data, struct zwp_input_method_v2 *m,
}
static void
im_textchange(void *data, struct zwp_input_method_v2 *m, uint32_t cause)
im_textchange(void *data, struct zwp_input_method_v2 *m, u32int cause)
{
(void)data;
(void)m;
@@ -237,7 +314,7 @@ im_textchange(void *data, struct zwp_input_method_v2 *m, uint32_t cause)
static void
im_content(void *data, struct zwp_input_method_v2 *m,
uint32_t hint, uint32_t purpose)
u32int hint, u32int purpose)
{
(void)data;
(void)m;
@@ -245,28 +322,42 @@ im_content(void *data, struct zwp_input_method_v2 *m,
(void)purpose;
}
static void
endactivation(void)
{
wlstateclear(&keys);
active = 0;
releaseowner();
if(grab != nil){
zwp_input_method_keyboard_grab_v2_release(grab);
grab = nil;
}
}
static void
beginactivation(void)
{
if(active)
endactivation();
active = 1;
owner = ownernew();
grab = zwp_input_method_v2_grab_keyboard(im);
if(grab != nil)
zwp_input_method_keyboard_grab_v2_add_listener(grab,
&grab_listener, nil);
}
static void
im_done(void *data, struct zwp_input_method_v2 *m)
{
(void)data;
(void)m;
imserial++;
if(pending && !active){
active = 1;
owner = ownernew();
grab = zwp_input_method_v2_grab_keyboard(im);
if(grab != nil)
zwp_input_method_keyboard_grab_v2_add_listener(grab,
&grab_listener, nil);
}else if(!pending && active){
active = 0;
sendrelease();
owner = 0;
if(grab != nil){
zwp_input_method_keyboard_grab_v2_release(grab);
grab = nil;
}
}
if(newactivation && pending)
beginactivation();
else if(!pending && active)
endactivation();
newactivation = 0;
}
static void
@@ -274,6 +365,8 @@ im_unavail(void *data, struct zwp_input_method_v2 *m)
{
(void)data;
(void)m;
wlstateclear(&keys);
running = 0;
fprint(2, "strans: wayland: input-method unavailable\n");
}
@@ -288,28 +381,34 @@ static const struct zwp_input_method_v2_listener im_listener = {
};
static void
reg_global(void *data, struct wl_registry *r, uint32_t name,
const char *iface, uint32_t version)
reg_global(void *data, struct wl_registry *r, u32int name,
const char *iface, u32int version)
{
(void)data;
if(strcmp(iface, wl_seat_interface.name) == 0)
if(strcmp(iface, wl_seat_interface.name) == 0 && seat == nil){
seatname = name;
seat = wl_registry_bind(r, name, &wl_seat_interface,
version < 5 ? version : 5);
else if(strcmp(iface, zwp_input_method_manager_v2_interface.name) == 0)
}else if(strcmp(iface,
zwp_input_method_manager_v2_interface.name) == 0 && immgr == nil){
immgrname = name;
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)
}else if(strcmp(iface,
zwp_virtual_keyboard_manager_v1_interface.name) == 0 && vkmgr == nil){
vkmgrname = name;
vkmgr = wl_registry_bind(r, name,
&zwp_virtual_keyboard_manager_v1_interface, 1);
}
}
static void
reg_remove(void *data, struct wl_registry *r, uint32_t name)
reg_remove(void *data, struct wl_registry *r, u32int name)
{
(void)data;
(void)r;
(void)name;
if(name == seatname || name == immgrname || name == vkmgrname)
running = 0;
}
static const struct wl_registry_listener reg_listener = {
@@ -317,37 +416,159 @@ static const struct wl_registry_listener reg_listener = {
.global_remove = reg_remove,
};
void
waylandthread(void *_)
static int
dispatch(void)
{
struct wl_registry *reg;
struct pollfd pfd;
u32int key;
uvlong now;
int flush, timeout, n;
(void)_;
threadsetname("wayland");
while((dpy = wl_display_connect(nil)) == nil)
poll(nil, 0, 1000);
xkb = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if(xkb == nil){
pfd.fd = wl_display_get_fd(dpy);
while(running){
if(wl_display_dispatch_pending(dpy) < 0)
return -1;
if(!running)
break;
now = nowms();
if(wlstaterepeat(&keys, now, &key))
keyrepeat((u32int)now, key);
while(wl_display_prepare_read(dpy) != 0)
if(wl_display_dispatch_pending(dpy) < 0)
return -1;
flush = wl_display_flush(dpy);
if(flush < 0 && errno != EAGAIN){
wl_display_cancel_read(dpy);
return -1;
}
pfd.events = POLLIN;
if(flush < 0)
pfd.events |= POLLOUT;
pfd.revents = 0;
timeout = wlstatetimeout(&keys, nowms());
n = poll(&pfd, 1, timeout);
if(n < 0){
wl_display_cancel_read(dpy);
if(errno == EINTR)
continue;
return -1;
}
if(n == 0){
wl_display_cancel_read(dpy);
continue;
}
if(pfd.revents & POLLIN){
if(wl_display_read_events(dpy) < 0)
return -1;
}else
wl_display_cancel_read(dpy);
if(pfd.revents & (POLLERR | POLLHUP | POLLNVAL))
return -1;
if(pfd.revents & POLLOUT)
wl_display_flush(dpy);
}
return 0;
}
static void
resetconnection(void)
{
dpy = nil;
reg = nil;
seat = nil;
immgr = nil;
vkmgr = nil;
im = nil;
grab = nil;
vk = nil;
xkb = nil;
keymap = nil;
xkbst = nil;
active = 0;
pending = 0;
newactivation = 0;
running = 1;
imserial = 0;
seatname = 0;
immgrname = 0;
vkmgrname = 0;
owner = 0;
wlstateinit(&keys);
}
static void
cleanup(void)
{
running = 0;
wlstateclear(&keys);
releaseowner();
active = 0;
if(xkbst != nil)
xkb_state_unref(xkbst);
if(keymap != nil)
xkb_keymap_unref(keymap);
if(xkb != nil)
xkb_context_unref(xkb);
if(grab != nil)
wl_proxy_destroy((struct wl_proxy*)grab);
if(vk != nil)
wl_proxy_destroy((struct wl_proxy*)vk);
if(im != nil)
wl_proxy_destroy((struct wl_proxy*)im);
if(vkmgr != nil)
wl_proxy_destroy((struct wl_proxy*)vkmgr);
if(immgr != nil)
wl_proxy_destroy((struct wl_proxy*)immgr);
if(seat != nil)
wl_proxy_destroy((struct wl_proxy*)seat);
if(reg != nil)
wl_registry_destroy(reg);
if(dpy != nil){
wl_display_disconnect(dpy);
dpy = nil;
return;
}
}
static void
connectonce(void)
{
resetconnection();
dpy = wl_display_connect(nil);
if(dpy == nil)
return;
xkb = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if(xkb == nil)
return;
reg = wl_display_get_registry(dpy);
if(reg == nil)
return;
wl_registry_add_listener(reg, &reg_listener, nil);
wl_display_roundtrip(dpy);
if(wl_display_roundtrip(dpy) < 0)
return;
if(!running)
return;
if(seat == nil || immgr == nil || vkmgr == nil){
fprint(2,
"strans: wayland: compositor missing input-method-v2\n");
wl_display_disconnect(dpy);
dpy = nil;
return;
}
im = zwp_input_method_manager_v2_get_input_method(immgr, seat);
zwp_input_method_v2_add_listener(im, &im_listener, nil);
vk = zwp_virtual_keyboard_manager_v1_create_virtual_keyboard(vkmgr, seat);
replyc = chancreate(sizeof(Keyres), 0);
while(wl_display_dispatch(dpy) != -1)
;
chanfree(replyc);
replyc = nil;
if(im == nil || vk == nil)
return;
zwp_input_method_v2_add_listener(im, &im_listener, nil);
dispatch();
}
void
waylandthread(void *_)
{
(void)_;
threadsetname("wayland");
replyc = chancreate(sizeof(Keyres), 0);
for(;;){
connectonce();
cleanup();
poll(nil, 0, 1000);
}
}