fix: repair IBus context lifecycle

This commit is contained in:
2026-08-12 15:49:28 +09:00
parent bc5712b6ac
commit cec62cc40f

304
ibus.c
View File

@@ -14,7 +14,9 @@ enum
{
Maxwatches = 32,
Maxconns = 16,
Maxcontexts = 64,
Relmask = 1<<30,
Ibussupermask = 1<<26,
};
typedef struct Watch Watch;
@@ -23,24 +25,41 @@ struct Watch
DBusWatch *w;
};
typedef struct Ictx Ictx;
struct Ictx
{
DBusConnection *conn;
char path[64];
uvlong owner;
int focused;
Caret caret;
};
static Watch watches[Maxwatches];
static int nwatches;
static DBusConnection *conns[Maxconns];
static int nconns;
static DBusServer *srv;
static char addrfile[256];
static Ictx contexts[Maxcontexts];
static char addrfile[512];
static dev_t addrdev;
static ino_t addrino;
static int addrowned;
static int icctr;
static int busctr;
static Channel *replyc;
static uvlong owner;
static DBusHandlerResult onmsg(DBusConnection*, DBusMessage*, void*);
static void
unlinkaddr(void)
{
if(addrfile[0] != '\0')
struct stat st;
if(addrowned && lstat(addrfile, &st) == 0 &&
st.st_dev == addrdev && st.st_ino == addrino)
unlink(addrfile);
addrowned = 0;
}
static void
@@ -98,7 +117,8 @@ buildaddrpath(char *buf, int sz)
{
char mid[64], host[64], num[8], *cfg, *home;
struct stat st;
char dir[256];
char dir[512];
int n;
machineid(mid, sizeof(mid));
if(mid[0] == '\0')
@@ -106,15 +126,17 @@ buildaddrpath(char *buf, int sz)
xdisplay(host, sizeof(host), num, sizeof(num));
cfg = getenv("XDG_CONFIG_HOME");
if(cfg != nil && cfg[0] != '\0')
snprintf(dir, sizeof(dir), "%s/ibus/bus", cfg);
n = snprintf(dir, sizeof(dir), "%s/ibus/bus", cfg);
else{
home = getenv("HOME");
if(home == nil)
return -1;
snprintf(dir, sizeof(dir), "%s/.config/ibus/bus", home);
n = snprintf(dir, sizeof(dir), "%s/.config/ibus/bus", home);
}
if(n < 0 || n >= (int)sizeof dir)
return -1;
if(stat(dir, &st) < 0){
char tmp[256];
char tmp[512];
char *p;
strncpy(tmp, dir, sizeof(tmp));
tmp[sizeof(tmp)-1] = '\0';
@@ -126,23 +148,55 @@ buildaddrpath(char *buf, int sz)
}
mkdir(tmp, 0700);
}
snprintf(buf, sz, "%s/%s-%s-%s", dir, mid, host, num);
return 0;
n = snprintf(buf, sz, "%s/%s-%s-%s", dir, mid, host, num);
return n < 0 || n >= sz ? -1 : 0;
}
static int
writeaddr(char *path, char *addr)
{
char tmp[576];
FILE *fp;
struct stat st;
int fd, n, ok;
fp = fopen(path, "w");
if(fp == nil)
n = snprintf(tmp, sizeof tmp, "%s.tmp.XXXXXX", path);
if(n < 0 || n >= (int)sizeof tmp)
return -1;
fprintf(fp, "IBUS_ADDRESS=%s\n", addr);
fprintf(fp, "IBUS_DAEMON_PID=%d\n", (int)getpid());
fclose(fp);
chmod(path, 0600);
return 0;
fd = mkstemp(tmp);
if(fd < 0)
return -1;
ok = 0;
fp = nil;
if(fchmod(fd, 0600) < 0)
goto out;
fp = fdopen(fd, "w");
if(fp == nil)
goto out;
fd = -1;
if(fprintf(fp, "IBUS_ADDRESS=%s\n", addr) < 0 ||
fprintf(fp, "IBUS_DAEMON_PID=%d\n", (int)getpid()) < 0 ||
fflush(fp) < 0 || fsync(fileno(fp)) < 0)
goto out;
if(fclose(fp) < 0){
fp = nil;
goto out;
}
fp = nil;
if(rename(tmp, path) < 0 || lstat(path, &st) < 0)
goto out;
addrdev = st.st_dev;
addrino = st.st_ino;
addrowned = 1;
ok = 1;
out:
if(fp != nil)
fclose(fp);
if(fd >= 0)
close(fd);
if(!ok)
unlink(tmp);
return ok ? 0 : -1;
}
static dbus_bool_t
@@ -204,38 +258,72 @@ mget(u32int state)
if(state & (1<<0)) m |= Mshift;
if(state & (1<<2)) m |= Mctrl;
if(state & (1<<3)) m |= Malt;
if(state & (1<<6)) m |= Msuper;
if(state & ((1<<6)|Ibussupermask)) m |= Msuper;
return m;
}
static Ictx*
findcontext(DBusConnection *conn, const char *path)
{
int i;
for(i = 0; i < nelem(contexts); i++)
if(contexts[i].conn == conn && strcmp(contexts[i].path, path) == 0)
return &contexts[i];
return nil;
}
static Ictx*
newcontext(DBusConnection *conn, const char *path)
{
int i;
for(i = 0; i < nelem(contexts); i++)
if(contexts[i].conn == nil){
memset(&contexts[i], 0, sizeof contexts[i]);
contexts[i].conn = conn;
contexts[i].owner = ownernew();
strncpy(contexts[i].path, path, sizeof contexts[i].path);
contexts[i].path[sizeof contexts[i].path-1] = '\0';
return &contexts[i];
}
return nil;
}
static void
sendkey(u32int ks, u32int mod, Keyres *res)
sendrequest(Ictx *ctx, int op, u32int ks, u32int mod, Keyres *res)
{
Keyreq kr;
memset(&kr, 0, sizeof kr);
kr.owner = owner;
kr.op = Keypress;
kr.owner = ctx->owner;
kr.op = op;
kr.ks = ks;
kr.mod = mod;
kr.want = 1;
kr.caret = ctx->caret;
kr.want = op == Keypress;
kr.reply = replyc;
chansend(keyc, &kr);
chanrecv(replyc, res);
}
static void
sendreset(void)
releasecontext(Ictx *ctx)
{
Keyreq kr;
Keyres res;
memset(&kr, 0, sizeof kr);
kr.owner = owner;
kr.op = Keyreset;
kr.reply = replyc;
chansend(keyc, &kr);
chanrecv(replyc, &res);
if(replyc != nil)
sendrequest(ctx, Keyrelease, 0, 0, &res);
ctx->focused = 0;
memset(&ctx->caret, 0, sizeof ctx->caret);
}
static void
dropcontext(Ictx *ctx)
{
releasecontext(ctx);
memset(ctx, 0, sizeof *ctx);
}
static void
@@ -318,15 +406,53 @@ handlehello(DBusConnection *c, DBusMessage *m)
return DBUS_HANDLER_RESULT_HANDLED;
}
static DBusHandlerResult
handleerror(DBusConnection *c, DBusMessage *m, const char *name,
const char *text)
{
DBusMessage *r;
r = dbus_message_new_error(m, name, text);
if(r != nil){
dbus_connection_send(c, r, nil);
dbus_message_unref(r);
}
return DBUS_HANDLER_RESULT_HANDLED;
}
static DBusHandlerResult
handlebool(DBusConnection *c, DBusMessage *m, int value)
{
DBusMessage *r;
dbus_bool_t b;
r = dbus_message_new_method_return(m);
b = value ? TRUE : FALSE;
dbus_message_append_args(r, DBUS_TYPE_BOOLEAN, &b, DBUS_TYPE_INVALID);
dbus_connection_send(c, r, nil);
dbus_message_unref(r);
return DBUS_HANDLER_RESULT_HANDLED;
}
static DBusHandlerResult
handlecreate(DBusConnection *c, DBusMessage *m)
{
DBusMessage *r;
char path[64];
const char *pp;
Ictx *ctx;
int n;
icctr++;
snprintf(path, sizeof(path), "/org/freedesktop/IBus/InputContext_%d", icctr);
n = snprintf(path, sizeof(path),
"/org/freedesktop/IBus/InputContext_%d", icctr);
if(n < 0 || n >= (int)sizeof path)
return handleerror(c, m, DBUS_ERROR_LIMITS_EXCEEDED,
"input context path exhausted");
ctx = newcontext(c, path);
if(ctx == nil)
return handleerror(c, m, DBUS_ERROR_LIMITS_EXCEEDED,
"too many input contexts");
pp = path;
r = dbus_message_new_method_return(m);
dbus_message_append_args(r, DBUS_TYPE_OBJECT_PATH, &pp, DBUS_TYPE_INVALID);
@@ -336,14 +462,12 @@ handlecreate(DBusConnection *c, DBusMessage *m)
}
static DBusHandlerResult
handlekey(DBusConnection *c, DBusMessage *m)
handlekey(DBusConnection *c, DBusMessage *m, Ictx *ctx)
{
DBusMessage *r;
DBusError err;
dbus_uint32_t sym, code, state;
Keyres res;
char commit[Maxutf], preedit[Maxutf];
dbus_bool_t b;
u32int ks, mod;
dbus_error_init(&err);
@@ -356,28 +480,17 @@ handlekey(DBusConnection *c, DBusMessage *m)
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
USED(code);
if(state & Relmask){
r = dbus_message_new_method_return(m);
b = FALSE;
dbus_message_append_args(r, DBUS_TYPE_BOOLEAN, &b, DBUS_TYPE_INVALID);
dbus_connection_send(c, r, nil);
dbus_message_unref(r);
return DBUS_HANDLER_RESULT_HANDLED;
}
if(state & Relmask || !ctx->focused)
return handlebool(c, m, 0);
ks = kget(sym);
mod = mget(state);
sendkey(ks, mod, &res);
sendrequest(ctx, Keypress, ks, mod, &res);
stoutf(&res.commit, commit, sizeof commit);
stoutf(&res.preedit, preedit, sizeof preedit);
if(commit[0] != '\0')
emitcommit(c, dbus_message_get_path(m), commit);
emitpreedit(c, dbus_message_get_path(m), preedit);
r = dbus_message_new_method_return(m);
b = res.eaten ? TRUE : FALSE;
dbus_message_append_args(r, DBUS_TYPE_BOOLEAN, &b, DBUS_TYPE_INVALID);
dbus_connection_send(c, r, nil);
dbus_message_unref(r);
return DBUS_HANDLER_RESULT_HANDLED;
return handlebool(c, m, res.eaten);
}
static DBusHandlerResult
@@ -392,13 +505,66 @@ handlenoop(DBusConnection *c, DBusMessage *m)
}
static DBusHandlerResult
handlereset(DBusConnection *c, DBusMessage *m)
handlereset(DBusConnection *c, DBusMessage *m, Ictx *ctx)
{
sendreset();
Keyres res;
sendrequest(ctx, Keyreset, 0, 0, &res);
emitpreedit(c, dbus_message_get_path(m), "");
return handlenoop(c, m);
}
static DBusHandlerResult
handlefocusin(DBusConnection *c, DBusMessage *m, Ictx *ctx)
{
ctx->focused = 1;
return handlenoop(c, m);
}
static DBusHandlerResult
handlefocusout(DBusConnection *c, DBusMessage *m, Ictx *ctx)
{
releasecontext(ctx);
emitpreedit(c, dbus_message_get_path(m), "");
return handlenoop(c, m);
}
static DBusHandlerResult
handledestroy(DBusConnection *c, DBusMessage *m, Ictx *ctx)
{
dropcontext(ctx);
return handlenoop(c, m);
}
static DBusHandlerResult
handlecursor(DBusConnection *c, DBusMessage *m, Ictx *ctx)
{
DBusError err;
dbus_int32_t x, y, w, h;
Keyres res;
dbus_error_init(&err);
if(!dbus_message_get_args(m, &err,
DBUS_TYPE_INT32, &x, DBUS_TYPE_INT32, &y,
DBUS_TYPE_INT32, &w, DBUS_TYPE_INT32, &h,
DBUS_TYPE_INVALID)){
dbus_error_free(&err);
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
"SetCursorLocation expects four integers");
}
if(w < 0 || h < 0)
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
"cursor dimensions must not be negative");
ctx->caret.valid = 1;
ctx->caret.x = x;
ctx->caret.y = y;
ctx->caret.w = w;
ctx->caret.h = h;
if(ctx->focused)
sendrequest(ctx, Keycaret, 0, 0, &res);
return handlenoop(c, m);
}
static const char introspectxml[] =
"<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n"
" \"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n"
@@ -448,6 +614,7 @@ static DBusHandlerResult
onmsg(DBusConnection *c, DBusMessage *m, void *_)
{
const char *iface, *member, *path;
Ictx *ctx;
USED(_);
if(dbus_message_get_type(m) != DBUS_MESSAGE_TYPE_METHOD_CALL)
@@ -472,15 +639,23 @@ onmsg(DBusConnection *c, DBusMessage *m, void *_)
return handlecreate(c, m);
}
if(strcmp(iface, "org.freedesktop.IBus.InputContext") == 0){
ctx = findcontext(c, path);
if(ctx == nil)
return handleerror(c, m, DBUS_ERROR_UNKNOWN_OBJECT,
"unknown input context");
if(strcmp(member, "ProcessKeyEvent") == 0)
return handlekey(c, m);
if(strcmp(member, "FocusOut") == 0
|| strcmp(member, "Reset") == 0)
return handlereset(c, m);
if(strcmp(member, "FocusIn") == 0
|| strcmp(member, "Destroy") == 0
|| strcmp(member, "SetCapabilities") == 0
|| strcmp(member, "SetCursorLocation") == 0
return handlekey(c, m, ctx);
if(strcmp(member, "FocusIn") == 0)
return handlefocusin(c, m, ctx);
if(strcmp(member, "FocusOut") == 0)
return handlefocusout(c, m, ctx);
if(strcmp(member, "Reset") == 0)
return handlereset(c, m, ctx);
if(strcmp(member, "Destroy") == 0)
return handledestroy(c, m, ctx);
if(strcmp(member, "SetCursorLocation") == 0)
return handlecursor(c, m, ctx);
if(strcmp(member, "SetCapabilities") == 0
|| strcmp(member, "SetEngine") == 0)
return handlenoop(c, m);
}
@@ -507,7 +682,7 @@ newconn(DBusServer *s, DBusConnection *c, void *_)
static void
pruneconns(void)
{
int i, j;
int i, j, k;
j = 0;
for(i = 0; i < nconns; i++){
@@ -515,6 +690,9 @@ pruneconns(void)
conns[j++] = conns[i];
continue;
}
for(k = 0; k < nelem(contexts); k++)
if(contexts[k].conn == conns[i])
dropcontext(&contexts[k]);
dbus_connection_unref(conns[i]);
}
nconns = j;
@@ -531,8 +709,11 @@ ibusinit(void)
fprintf(stderr, "strans: ibus: cannot build address path\n");
return -1;
}
unlink(addrfile);
snprintf(addr, sizeof(addr), "unix:abstract=strans-%d", (int)getpid());
if(snprintf(addr, sizeof(addr), "unix:abstract=strans-%d",
(int)getpid()) >= (int)sizeof addr){
fprintf(stderr, "strans: ibus: address is too long\n");
return -1;
}
dbus_error_init(&err);
srv = dbus_server_listen(addr, &err);
if(srv == nil){
@@ -571,7 +752,6 @@ ibusthread(void *_)
if(ibusinit() < 0)
return;
replyc = chancreate(sizeof(Keyres), 0);
owner = ownernew();
for(;;){
n = 0;
for(i = 0; i < nwatches && n < Maxwatches; i++){