From cec62cc40ff5045e4d6d6c7f170455b352742fb9 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Wed, 12 Aug 2026 15:49:28 +0900 Subject: [PATCH] fix: repair IBus context lifecycle --- ibus.c | 304 +++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 242 insertions(+), 62 deletions(-) diff --git a/ibus.c b/ibus.c index 82c4901..36e4cff 100644 --- a/ibus.c +++ b/ibus.c @@ -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[] = "\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++){