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