srvnote and addrnote unlinked on any note at all, and plan9port marks SIGPIPE Ignore: notify.c:59 lists it, and signotify runs the handler chain first and only then finds the Ignore flag and returns. So one broken pipe took the IPC socket and the IBus address file away from a daemon that went on running -- measured on a private runtime dir, a single kill -PIPE left the process in state Ssl with both files gone, so every client that focused a widget afterwards silently had no input method and only a restart brought it back. libxcb writes with writev, so the note is a broken X connection away; today xim.c's die() masks it by taking the daemon down on the same event, which is exactly why the two must not depend on each other. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1121 lines
29 KiB
C
1121 lines
29 KiB
C
#include "dat.h"
|
|
#include "fn.h"
|
|
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <sys/stat.h>
|
|
#include <poll.h>
|
|
#include <dbus/dbus.h>
|
|
#include <xkbcommon/xkbcommon.h>
|
|
|
|
enum
|
|
{
|
|
Maxwatches = 2*Maxclients + 1,
|
|
/* Toolkits keep one context per widget that ever took focus. */
|
|
Maxcontexts = 1024,
|
|
Relmask = 1<<30,
|
|
/* IBus wire constants; the daemon does not link libibus. */
|
|
Ibuscappreedit = 1<<0,
|
|
Ibusattrunderline = 1,
|
|
Ibusunderlinesingle = 1,
|
|
Ibuspreeditclear = 0,
|
|
Maxpost = 3,
|
|
};
|
|
|
|
/*
|
|
* A client in IBus's synchronous mode (GTK 4) cannot take signals while
|
|
* it waits for its ProcessKeyEvent reply: it asks afterwards, through the
|
|
* PostProcessKeyEvent property, for what the key produced, as typed
|
|
* (yv) pairs — 'c' a commit, 'u' or 'm' a preedit and then its
|
|
* "cursor,visible[,mode]".
|
|
*/
|
|
typedef struct Post Post;
|
|
struct Post
|
|
{
|
|
char type;
|
|
char text[Maxutf];
|
|
char pos[32];
|
|
};
|
|
|
|
typedef struct Ictx Ictx;
|
|
struct Ictx
|
|
{
|
|
DBusConnection *conn;
|
|
char path[64];
|
|
int focused;
|
|
u32int cap;
|
|
u32int purpose;
|
|
int clientcommitpreedit;
|
|
int postprocess;
|
|
int keying; /* inside ProcessKeyEvent: hold signals for the post */
|
|
int npost;
|
|
Post post[Maxpost];
|
|
Caret caret;
|
|
};
|
|
|
|
static DBusWatch *watches[Maxwatches];
|
|
static int nwatches;
|
|
static DBusConnection *conns[Maxclients];
|
|
static int nconns;
|
|
static DBusServer *srv;
|
|
static Ictx contexts[Maxcontexts];
|
|
static char addrfile[512];
|
|
static dev_t addrdev;
|
|
static ino_t addrino;
|
|
static int icctr;
|
|
static int busctr;
|
|
static Channel *replyc;
|
|
static const char ibusowner[] = ":1.0";
|
|
static Ictx *preowner;
|
|
|
|
/* Removes the address file only while it is still the one we wrote. */
|
|
static void
|
|
unlinkaddr(void)
|
|
{
|
|
struct stat st;
|
|
|
|
if(addrfile[0] != '\0' && lstat(addrfile, &st) == 0 &&
|
|
st.st_dev == addrdev && st.st_ino == addrino)
|
|
unlink(addrfile);
|
|
addrfile[0] = '\0';
|
|
addrdev = 0;
|
|
addrino = 0;
|
|
}
|
|
|
|
static int
|
|
addrnote(void*, char *note)
|
|
{
|
|
if(notefatal(note))
|
|
unlinkaddr();
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
machineidfiles(char *buf, int sz, char **path, int npath)
|
|
{
|
|
int fd, i, n;
|
|
|
|
for(i = 0; i < npath; i++){
|
|
fd = open(path[i], 0);
|
|
if(fd < 0)
|
|
continue;
|
|
n = read(fd, buf, sz - 1);
|
|
close(fd);
|
|
if(n <= 0)
|
|
continue;
|
|
buf[n] = '\0';
|
|
while(n > 0 && (buf[n-1] == '\n' || buf[n-1] == '\r'))
|
|
buf[--n] = '\0';
|
|
if(n > 0)
|
|
return;
|
|
}
|
|
buf[0] = '\0';
|
|
}
|
|
|
|
/* libibus reads the D-Bus file first and calls it "machine-id" if neither is there. */
|
|
static void
|
|
machineid(char *buf, int sz)
|
|
{
|
|
char *path[] = {
|
|
"/var/lib/dbus/machine-id",
|
|
"/etc/machine-id",
|
|
};
|
|
|
|
machineidfiles(buf, sz, path, nelem(path));
|
|
if(buf[0] == '\0')
|
|
snprintf(buf, sz, "machine-id");
|
|
}
|
|
|
|
static void
|
|
copyfield(char *dst, int sz, char *src, int n)
|
|
{
|
|
if(n >= sz)
|
|
n = sz - 1;
|
|
memcpy(dst, src, n);
|
|
dst[n] = '\0';
|
|
}
|
|
|
|
/*
|
|
* The display libibus names the file after: the whole WAYLAND_DISPLAY,
|
|
* else DISPLAY's host and number, else unix and 0.
|
|
*/
|
|
static void
|
|
xdisplay(char *host, int hsz, char *num, int nsz)
|
|
{
|
|
char *d, *colon, *dot, *p;
|
|
|
|
copyfield(host, hsz, "unix", 4);
|
|
copyfield(num, nsz, "0", 1);
|
|
d = getenv("WAYLAND_DISPLAY");
|
|
if(d != nil && d[0] != '\0'){
|
|
copyfield(num, nsz, d, strlen(d));
|
|
return;
|
|
}
|
|
d = getenv("DISPLAY");
|
|
if(d == nil || d[0] == '\0')
|
|
return;
|
|
colon = strchr(d, ':');
|
|
if(colon == nil)
|
|
return;
|
|
if(colon > d)
|
|
copyfield(host, hsz, d, colon - d);
|
|
p = colon + 1;
|
|
dot = strchr(p, '.');
|
|
copyfield(num, nsz, p, dot ? dot - p : (int)strlen(p));
|
|
}
|
|
|
|
/* The IBus address file lives where libibus looks: config/ibus/bus/. */
|
|
static int
|
|
buildaddrpath(char *buf, int sz)
|
|
{
|
|
char mid[64], host[64], num[64], dir[512];
|
|
char *cfg, *home, *explicit, *p;
|
|
int n;
|
|
|
|
explicit = getenv("IBUS_ADDRESS_FILE");
|
|
if(explicit != nil){
|
|
n = snprintf(buf, sz, "%s", explicit);
|
|
return explicit[0] == '\0' || n < 0 || n >= sz ? -1 : 0;
|
|
}
|
|
machineid(mid, sizeof(mid));
|
|
xdisplay(host, sizeof(host), num, sizeof(num));
|
|
cfg = getenv("XDG_CONFIG_HOME");
|
|
if(cfg != nil && cfg[0] != '\0')
|
|
n = snprintf(dir, sizeof(dir), "%s/ibus/bus", cfg);
|
|
else{
|
|
home = getenv("HOME");
|
|
if(home == nil)
|
|
return -1;
|
|
n = snprintf(dir, sizeof(dir), "%s/.config/ibus/bus", home);
|
|
}
|
|
if(n < 0 || n >= (int)sizeof dir)
|
|
return -1;
|
|
for(p = dir+1; *p; p++)
|
|
if(*p == '/'){
|
|
*p = '\0';
|
|
mkdir(dir, 0700);
|
|
*p = '/';
|
|
}
|
|
mkdir(dir, 0700);
|
|
n = snprintf(buf, sz, "%s/%s-%s-%s", dir, mid, host, num);
|
|
return n < 0 || n >= sz ? -1 : 0;
|
|
}
|
|
|
|
/* Publishes the address atomically and remembers the file's identity. */
|
|
static int
|
|
writeaddr(char *path, char *addr)
|
|
{
|
|
char tmp[576], text[256];
|
|
struct stat st;
|
|
int fd, n, ok;
|
|
|
|
n = snprintf(tmp, sizeof tmp, "%s.tmp.XXXXXX", path);
|
|
if(n < 0 || n >= (int)sizeof tmp)
|
|
return -1;
|
|
n = snprintf(text, sizeof text, "IBUS_ADDRESS=%s\nIBUS_DAEMON_PID=%d\n",
|
|
addr, (int)getpid());
|
|
if(n < 0 || n >= (int)sizeof text)
|
|
return -1;
|
|
fd = mkstemp(tmp);
|
|
if(fd < 0)
|
|
return -1;
|
|
ok = write(fd, text, n) == n && fsync(fd) == 0 &&
|
|
fstat(fd, &st) == 0;
|
|
close(fd);
|
|
if(!ok || rename(tmp, path) < 0){
|
|
unlink(tmp);
|
|
return -1;
|
|
}
|
|
addrdev = st.st_dev;
|
|
addrino = st.st_ino;
|
|
return 0;
|
|
}
|
|
|
|
static dbus_bool_t
|
|
addwatch(DBusWatch *w, void*)
|
|
{
|
|
int i;
|
|
|
|
for(i = 0; i < nwatches; i++)
|
|
if(watches[i] == nil){
|
|
watches[i] = w;
|
|
return TRUE;
|
|
}
|
|
if(nwatches >= Maxwatches)
|
|
return FALSE;
|
|
watches[nwatches++] = w;
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
removewatch(DBusWatch *w, void*)
|
|
{
|
|
int i;
|
|
|
|
for(i = 0; i < nwatches; i++)
|
|
if(watches[i] == w){
|
|
watches[i] = nil;
|
|
return;
|
|
}
|
|
}
|
|
|
|
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;
|
|
strncpy(contexts[i].path, path, sizeof contexts[i].path);
|
|
contexts[i].path[sizeof contexts[i].path-1] = '\0';
|
|
return &contexts[i];
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
static int
|
|
clientpreedit(Ictx *ctx)
|
|
{
|
|
return (ctx->cap & Ibuscappreedit) != 0;
|
|
}
|
|
|
|
static void
|
|
sendrequest(Ictx *ctx, int op, u32int ks, u32int mod, Keyres *res)
|
|
{
|
|
Keyreq kr;
|
|
|
|
memset(&kr, 0, sizeof kr);
|
|
kr.owner = ctx;
|
|
kr.clientpre = clientpreedit(ctx);
|
|
kr.op = op;
|
|
kr.ks = ks;
|
|
kr.mod = mod;
|
|
kr.caret = ctx->caret;
|
|
kr.reply = replyc;
|
|
chansend(keyc, &kr);
|
|
chanrecv(replyc, res);
|
|
}
|
|
|
|
static int
|
|
hidden(Ictx *ctx)
|
|
{
|
|
return ctx->purpose == Purposepassword ||
|
|
ctx->purpose == Purposepin;
|
|
}
|
|
|
|
static void
|
|
setcursor(Ictx *ctx, int x, int y, int h)
|
|
{
|
|
Keyres res;
|
|
|
|
ctx->caret.valid = 1;
|
|
ctx->caret.x = x;
|
|
ctx->caret.y = y;
|
|
ctx->caret.h = h;
|
|
if(ctx->focused)
|
|
sendrequest(ctx, Keycaret, 0, 0, &res);
|
|
}
|
|
|
|
/* Every IBus serializable carries an (empty) attachment dictionary. */
|
|
static void
|
|
emptydict(DBusMessageIter *it)
|
|
{
|
|
DBusMessageIter d;
|
|
|
|
dbus_message_iter_open_container(it, DBUS_TYPE_ARRAY, "{sv}", &d);
|
|
dbus_message_iter_close_container(it, &d);
|
|
}
|
|
|
|
/* IBusText: (sa{sv}sv) holding an IBusAttrList of IBusAttributes. */
|
|
static void
|
|
appendibustext(DBusMessageIter *it, const char *s, int underline)
|
|
{
|
|
DBusMessageIter v, st, alv, ali, alist, av, ai;
|
|
const char *name = "IBusText";
|
|
const char *aname = "IBusAttrList";
|
|
const char *iname = "IBusAttribute";
|
|
dbus_uint32_t type, value, start, end;
|
|
|
|
dbus_message_iter_open_container(it, DBUS_TYPE_VARIANT, "(sa{sv}sv)", &v);
|
|
dbus_message_iter_open_container(&v, DBUS_TYPE_STRUCT, nil, &st);
|
|
dbus_message_iter_append_basic(&st, DBUS_TYPE_STRING, &name);
|
|
emptydict(&st);
|
|
dbus_message_iter_append_basic(&st, DBUS_TYPE_STRING, &s);
|
|
dbus_message_iter_open_container(&st, DBUS_TYPE_VARIANT, "(sa{sv}av)", &alv);
|
|
dbus_message_iter_open_container(&alv, DBUS_TYPE_STRUCT, nil, &ali);
|
|
dbus_message_iter_append_basic(&ali, DBUS_TYPE_STRING, &aname);
|
|
emptydict(&ali);
|
|
dbus_message_iter_open_container(&ali, DBUS_TYPE_ARRAY, "v", &alist);
|
|
if(underline && s[0] != '\0'){
|
|
type = Ibusattrunderline;
|
|
value = Ibusunderlinesingle;
|
|
start = 0;
|
|
end = utflen((char*)s);
|
|
dbus_message_iter_open_container(&alist, DBUS_TYPE_VARIANT,
|
|
"(sa{sv}uuuu)", &av);
|
|
dbus_message_iter_open_container(&av, DBUS_TYPE_STRUCT, nil, &ai);
|
|
dbus_message_iter_append_basic(&ai, DBUS_TYPE_STRING, &iname);
|
|
emptydict(&ai);
|
|
dbus_message_iter_append_basic(&ai, DBUS_TYPE_UINT32, &type);
|
|
dbus_message_iter_append_basic(&ai, DBUS_TYPE_UINT32, &value);
|
|
dbus_message_iter_append_basic(&ai, DBUS_TYPE_UINT32, &start);
|
|
dbus_message_iter_append_basic(&ai, DBUS_TYPE_UINT32, &end);
|
|
dbus_message_iter_close_container(&av, &ai);
|
|
dbus_message_iter_close_container(&alist, &av);
|
|
}
|
|
dbus_message_iter_close_container(&ali, &alist);
|
|
dbus_message_iter_close_container(&alv, &ali);
|
|
dbus_message_iter_close_container(&st, &alv);
|
|
dbus_message_iter_close_container(&v, &st);
|
|
dbus_message_iter_close_container(it, &v);
|
|
}
|
|
|
|
static DBusMessage*
|
|
newsignal(const char *path, const char *name)
|
|
{
|
|
DBusMessage *sig;
|
|
|
|
sig = dbus_message_new_signal(path, "org.freedesktop.IBus.InputContext",
|
|
name);
|
|
if(sig == nil)
|
|
die("ibus: out of memory");
|
|
dbus_message_set_sender(sig, ibusowner);
|
|
return sig;
|
|
}
|
|
|
|
static int
|
|
postpush(Ictx *ctx, char type, const char *text, const char *pos)
|
|
{
|
|
Post *p;
|
|
|
|
if(!ctx->postprocess || !ctx->keying)
|
|
return 0;
|
|
if(ctx->npost < Maxpost){
|
|
p = &ctx->post[ctx->npost++];
|
|
p->type = type;
|
|
strcpy(p->text, text);
|
|
strcpy(p->pos, pos);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
emitcommit(Ictx *ctx, const char *text)
|
|
{
|
|
DBusMessage *sig;
|
|
DBusMessageIter it;
|
|
|
|
if(postpush(ctx, 'c', text, ""))
|
|
return;
|
|
sig = newsignal(ctx->path, "CommitText");
|
|
dbus_message_iter_init_append(sig, &it);
|
|
appendibustext(&it, text, 0);
|
|
dbus_connection_send(ctx->conn, sig, nil);
|
|
dbus_message_unref(sig);
|
|
}
|
|
|
|
/* preowner is the one context showing a preedit; clearing anyone else's is moot. */
|
|
static void
|
|
emitpreedit(Ictx *ctx, const char *text)
|
|
{
|
|
DBusMessage *sig;
|
|
DBusMessageIter it;
|
|
dbus_uint32_t cursor, mode;
|
|
dbus_bool_t visible;
|
|
char pos[32];
|
|
|
|
if(text[0] == '\0' && preowner != ctx)
|
|
return;
|
|
cursor = utflen((char*)text);
|
|
visible = text[0] != '\0' ? TRUE : FALSE;
|
|
mode = Ibuspreeditclear;
|
|
if(ctx->clientcommitpreedit)
|
|
snprintf(pos, sizeof pos, "%u,%u,%u", cursor, visible, mode);
|
|
else
|
|
snprintf(pos, sizeof pos, "%u,%u", cursor, visible);
|
|
if(!postpush(ctx, ctx->clientcommitpreedit ? 'm' : 'u', text, pos)){
|
|
sig = newsignal(ctx->path, ctx->clientcommitpreedit ?
|
|
"UpdatePreeditTextWithMode" : "UpdatePreeditText");
|
|
dbus_message_iter_init_append(sig, &it);
|
|
appendibustext(&it, text, 1);
|
|
dbus_message_iter_append_basic(&it, DBUS_TYPE_UINT32, &cursor);
|
|
dbus_message_iter_append_basic(&it, DBUS_TYPE_BOOLEAN, &visible);
|
|
if(ctx->clientcommitpreedit)
|
|
dbus_message_iter_append_basic(&it, DBUS_TYPE_UINT32, &mode);
|
|
dbus_connection_send(ctx->conn, sig, nil);
|
|
dbus_message_unref(sig);
|
|
}
|
|
if(text[0] != '\0')
|
|
preowner = ctx;
|
|
else if(preowner == ctx)
|
|
preowner = nil;
|
|
}
|
|
|
|
/* Another frontend may have taken the engine; then our preedit is stale. */
|
|
static void
|
|
checkpreowner(void)
|
|
{
|
|
Ictx *ctx;
|
|
Keyres res;
|
|
|
|
ctx = preowner;
|
|
if(ctx == nil)
|
|
return;
|
|
sendrequest(ctx, Keycap, 0, 0, &res);
|
|
if(!res.eaten)
|
|
emitpreedit(ctx, "");
|
|
}
|
|
|
|
/* A reset or a lost focus hands the pending text back as committed text. */
|
|
static void
|
|
flushcontext(Ictx *ctx, int op)
|
|
{
|
|
Keyres res;
|
|
char commit[Maxutf];
|
|
|
|
sendrequest(ctx, op, 0, 0, &res);
|
|
if(clientpreedit(ctx))
|
|
emitpreedit(ctx, "");
|
|
stoutf(&res.commit, commit, sizeof commit);
|
|
if(commit[0] != '\0')
|
|
emitcommit(ctx, commit);
|
|
}
|
|
|
|
/* Nobody is left to hand text to: the context or its connection is gone. */
|
|
static void
|
|
releasecontext(Ictx *ctx)
|
|
{
|
|
Keyres res;
|
|
|
|
if(ctx->focused)
|
|
sendrequest(ctx, Keyrelease, 0, 0, &res);
|
|
ctx->focused = 0;
|
|
memset(&ctx->caret, 0, sizeof ctx->caret);
|
|
}
|
|
|
|
static void
|
|
dropcontext(Ictx *ctx)
|
|
{
|
|
releasecontext(ctx);
|
|
composedrop(Composeibus, ctx);
|
|
if(preowner == ctx)
|
|
preowner = nil;
|
|
memset(ctx, 0, sizeof *ctx);
|
|
}
|
|
|
|
static void
|
|
dropconncontexts(DBusConnection *conn)
|
|
{
|
|
int i;
|
|
|
|
for(i = 0; i < nelem(contexts); i++)
|
|
if(contexts[i].conn == conn)
|
|
dropcontext(&contexts[i]);
|
|
}
|
|
|
|
/*
|
|
* A key is proof of focus: some clients never send FocusIn, and the
|
|
* focus events of two applications can cross.
|
|
*/
|
|
static int
|
|
processkey(Ictx *ctx, u32int sym, u32int state, char *text, int n, Keyres *res)
|
|
{
|
|
text[0] = '\0';
|
|
if(state & Relmask || hidden(ctx))
|
|
return 0;
|
|
ctx->focused = 1;
|
|
/* An IBus client drops a dead key we do not take: compose it here. */
|
|
if(composekey(Composeibus, ctx, sym, text, n))
|
|
return -1;
|
|
if(text[0] != '\0')
|
|
/* Composed text follows whatever was pending. */
|
|
sendrequest(ctx, Keyreset, 0, 0, res);
|
|
else
|
|
sendrequest(ctx, Keypress, ipckeysym(sym, xkb_keysym_to_utf32(sym)),
|
|
ipcmod(state), res);
|
|
if(preowner != nil && preowner != ctx)
|
|
checkpreowner();
|
|
return 1;
|
|
}
|
|
|
|
|
|
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)
|
|
die("ibus: out of memory");
|
|
dbus_connection_send(c, r, nil);
|
|
dbus_message_unref(r);
|
|
return DBUS_HANDLER_RESULT_HANDLED;
|
|
}
|
|
|
|
/* Replies with one argument of the given D-Bus type, or none. */
|
|
static DBusHandlerResult
|
|
reply(DBusConnection *c, DBusMessage *m, int type, void *v)
|
|
{
|
|
DBusMessage *r;
|
|
|
|
r = dbus_message_new_method_return(m);
|
|
if(r == nil)
|
|
die("ibus: out of memory");
|
|
if(type != DBUS_TYPE_INVALID)
|
|
dbus_message_append_args(r, type, v, DBUS_TYPE_INVALID);
|
|
dbus_connection_send(c, r, nil);
|
|
dbus_message_unref(r);
|
|
return DBUS_HANDLER_RESULT_HANDLED;
|
|
}
|
|
|
|
static DBusHandlerResult
|
|
replybool(DBusConnection *c, DBusMessage *m, int value)
|
|
{
|
|
dbus_bool_t b;
|
|
|
|
b = value ? TRUE : FALSE;
|
|
return reply(c, m, DBUS_TYPE_BOOLEAN, &b);
|
|
}
|
|
|
|
/* We are the bus as well as the engine: Hello names the caller. */
|
|
static DBusHandlerResult
|
|
handlehello(DBusConnection *c, DBusMessage *m)
|
|
{
|
|
char name[32];
|
|
const char *np;
|
|
|
|
if(!dbus_message_has_signature(m, ""))
|
|
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
|
|
"Hello expects no arguments");
|
|
busctr++;
|
|
snprintf(name, sizeof(name), ":1.%d", busctr);
|
|
np = name;
|
|
return reply(c, m, DBUS_TYPE_STRING, &np);
|
|
}
|
|
|
|
static DBusHandlerResult
|
|
handlenameowner(DBusConnection *c, DBusMessage *m)
|
|
{
|
|
const char *name, *owner;
|
|
|
|
if(!dbus_message_get_args(m, nil, DBUS_TYPE_STRING, &name,
|
|
DBUS_TYPE_INVALID))
|
|
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
|
|
"GetNameOwner expects one bus name");
|
|
if(strcmp(name, "org.freedesktop.IBus") != 0)
|
|
return handleerror(c, m, DBUS_ERROR_NAME_HAS_NO_OWNER,
|
|
"bus name has no owner");
|
|
owner = ibusowner;
|
|
return reply(c, m, DBUS_TYPE_STRING, &owner);
|
|
}
|
|
|
|
static DBusHandlerResult
|
|
handlecreate(DBusConnection *c, DBusMessage *m)
|
|
{
|
|
char path[64];
|
|
const char *pp;
|
|
Ictx *ctx;
|
|
|
|
if(!dbus_message_has_signature(m, "s"))
|
|
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
|
|
"CreateInputContext expects one client name");
|
|
icctr++;
|
|
snprintf(path, sizeof(path),
|
|
"/org/freedesktop/IBus/InputContext_%d", icctr);
|
|
ctx = newcontext(c, path);
|
|
if(ctx == nil)
|
|
return handleerror(c, m, DBUS_ERROR_LIMITS_EXCEEDED,
|
|
"too many input contexts");
|
|
pp = path;
|
|
return reply(c, m, DBUS_TYPE_OBJECT_PATH, &pp);
|
|
}
|
|
|
|
static DBusHandlerResult
|
|
handlekey(DBusConnection *c, DBusMessage *m, Ictx *ctx)
|
|
{
|
|
dbus_uint32_t sym, code, state;
|
|
Keyres res;
|
|
char commit[Maxutf], preedit[Maxutf], text[Maxutf];
|
|
int restart, rv;
|
|
|
|
dbus_message_get_args(m, nil, DBUS_TYPE_UINT32, &sym,
|
|
DBUS_TYPE_UINT32, &code, DBUS_TYPE_UINT32, &state,
|
|
DBUS_TYPE_INVALID);
|
|
rv = processkey(ctx, sym, state, text, sizeof text, &res);
|
|
if(rv <= 0) /* below zero: a sequence in the making */
|
|
return replybool(c, m, rv < 0);
|
|
stoutf(&res.commit, commit, sizeof commit);
|
|
stoutf(&res.preedit, preedit, sizeof preedit);
|
|
/* A commit ends the preedit around it, so clients place it right. */
|
|
restart = clientpreedit(ctx) && commit[0] != '\0' && preowner == ctx;
|
|
ctx->keying = 1;
|
|
if(restart)
|
|
emitpreedit(ctx, "");
|
|
if(commit[0] != '\0')
|
|
emitcommit(ctx, commit);
|
|
if(text[0] != '\0')
|
|
emitcommit(ctx, text);
|
|
if(clientpreedit(ctx) && (!restart || preedit[0] != '\0'))
|
|
emitpreedit(ctx, preedit);
|
|
ctx->keying = 0;
|
|
return replybool(c, m, res.eaten || text[0] != '\0');
|
|
}
|
|
|
|
/* Properties.Get PostProcessKeyEvent: what the last key produced. */
|
|
static DBusHandlerResult
|
|
handlepost(DBusConnection *c, DBusMessage *m, Ictx *ctx)
|
|
{
|
|
DBusMessage *r;
|
|
DBusMessageIter it, v, a, st;
|
|
Post *p;
|
|
int i;
|
|
|
|
r = dbus_message_new_method_return(m);
|
|
if(r == nil)
|
|
die("ibus: out of memory");
|
|
dbus_message_iter_init_append(r, &it);
|
|
dbus_message_iter_open_container(&it, DBUS_TYPE_VARIANT, "a(yv)", &v);
|
|
dbus_message_iter_open_container(&v, DBUS_TYPE_ARRAY, "(yv)", &a);
|
|
for(i = 0; i < ctx->npost; i++){
|
|
p = &ctx->post[i];
|
|
dbus_message_iter_open_container(&a, DBUS_TYPE_STRUCT, nil, &st);
|
|
dbus_message_iter_append_basic(&st, DBUS_TYPE_BYTE, &p->type);
|
|
appendibustext(&st, p->text, p->type != 'c');
|
|
dbus_message_iter_close_container(&a, &st);
|
|
if(p->type == 'c')
|
|
continue;
|
|
dbus_message_iter_open_container(&a, DBUS_TYPE_STRUCT, nil, &st);
|
|
dbus_message_iter_append_basic(&st, DBUS_TYPE_BYTE, &p->type);
|
|
appendibustext(&st, p->pos, 0);
|
|
dbus_message_iter_close_container(&a, &st);
|
|
}
|
|
dbus_message_iter_close_container(&v, &a);
|
|
dbus_message_iter_close_container(&it, &v);
|
|
dbus_connection_send(c, r, nil);
|
|
dbus_message_unref(r);
|
|
ctx->npost = 0;
|
|
return DBUS_HANDLER_RESULT_HANDLED;
|
|
}
|
|
|
|
static DBusHandlerResult
|
|
handlereset(DBusConnection *c, DBusMessage *m, Ictx *ctx)
|
|
{
|
|
flushcontext(ctx, Keyreset);
|
|
return reply(c, m, DBUS_TYPE_INVALID, nil);
|
|
}
|
|
|
|
static DBusHandlerResult
|
|
handlefocusin(DBusConnection *c, DBusMessage *m, Ictx *ctx)
|
|
{
|
|
ctx->focused = 1;
|
|
return reply(c, m, DBUS_TYPE_INVALID, nil);
|
|
}
|
|
|
|
static DBusHandlerResult
|
|
handlefocusout(DBusConnection *c, DBusMessage *m, Ictx *ctx)
|
|
{
|
|
if(ctx->focused)
|
|
flushcontext(ctx, Keyrelease);
|
|
ctx->focused = 0;
|
|
memset(&ctx->caret, 0, sizeof ctx->caret);
|
|
return reply(c, m, DBUS_TYPE_INVALID, nil);
|
|
}
|
|
|
|
static DBusHandlerResult
|
|
handlecap(DBusConnection *c, DBusMessage *m, Ictx *ctx)
|
|
{
|
|
dbus_uint32_t cap;
|
|
Keyres res;
|
|
char preedit[Maxutf];
|
|
u32int old;
|
|
|
|
dbus_message_get_args(m, nil, DBUS_TYPE_UINT32, &cap, DBUS_TYPE_INVALID);
|
|
old = ctx->cap;
|
|
ctx->cap = cap;
|
|
if(ctx->focused){
|
|
sendrequest(ctx, Keycap, 0, 0, &res);
|
|
if((old & Ibuscappreedit) != (ctx->cap & Ibuscappreedit)){
|
|
if((ctx->cap & Ibuscappreedit) && res.eaten){
|
|
stoutf(&res.preedit, preedit, sizeof preedit);
|
|
emitpreedit(ctx, preedit);
|
|
}else if(!(ctx->cap & Ibuscappreedit))
|
|
emitpreedit(ctx, "");
|
|
}
|
|
}
|
|
return reply(c, m, DBUS_TYPE_INVALID, nil);
|
|
}
|
|
|
|
static int
|
|
getproperty(DBusMessage *m, const char **iface, const char **name,
|
|
DBusMessageIter *value)
|
|
{
|
|
DBusMessageIter it;
|
|
|
|
if(!dbus_message_has_signature(m, "ssv") ||
|
|
!dbus_message_iter_init(m, &it))
|
|
return 0;
|
|
dbus_message_iter_get_basic(&it, iface);
|
|
dbus_message_iter_next(&it);
|
|
dbus_message_iter_get_basic(&it, name);
|
|
dbus_message_iter_next(&it);
|
|
dbus_message_iter_recurse(&it, value);
|
|
return 1;
|
|
}
|
|
|
|
/* ContentType is (purpose, hints); only the purpose matters to us. */
|
|
static int
|
|
getcontent(DBusMessageIter *value, u32int *purpose)
|
|
{
|
|
DBusMessageIter st;
|
|
dbus_uint32_t p;
|
|
|
|
if(dbus_message_iter_get_arg_type(value) != DBUS_TYPE_STRUCT)
|
|
return 0;
|
|
dbus_message_iter_recurse(value, &st);
|
|
if(dbus_message_iter_get_arg_type(&st) != DBUS_TYPE_UINT32)
|
|
return 0;
|
|
dbus_message_iter_get_basic(&st, &p);
|
|
if(!dbus_message_iter_next(&st) ||
|
|
dbus_message_iter_get_arg_type(&st) != DBUS_TYPE_UINT32)
|
|
return 0;
|
|
if(dbus_message_iter_next(&st))
|
|
return 0;
|
|
*purpose = p;
|
|
return 1;
|
|
}
|
|
|
|
/* ContentType, ClientCommitPreedit, and EffectivePostProcessKeyEvent are settable. */
|
|
static DBusHandlerResult
|
|
handlepropertyset(DBusConnection *c, DBusMessage *m, Ictx *ctx)
|
|
{
|
|
const char *iface, *name;
|
|
DBusMessageIter value, st;
|
|
dbus_bool_t b;
|
|
u32int purpose;
|
|
int washidden;
|
|
|
|
if(!getproperty(m, &iface, &name, &value))
|
|
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
|
|
"Properties.Set expects interface, property, and value");
|
|
if(strcmp(iface, "org.freedesktop.IBus.InputContext") != 0)
|
|
return handleerror(c, m, DBUS_ERROR_UNKNOWN_INTERFACE,
|
|
"unknown property interface");
|
|
if(strcmp(name, "ContentType") == 0){
|
|
if(!getcontent(&value, &purpose))
|
|
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
|
|
"ContentType expects (uu)");
|
|
washidden = hidden(ctx);
|
|
ctx->purpose = purpose;
|
|
if(ctx->focused && !washidden && hidden(ctx))
|
|
flushcontext(ctx, Keyreset);
|
|
return reply(c, m, DBUS_TYPE_INVALID, nil);
|
|
}
|
|
if(strcmp(name, "ClientCommitPreedit") == 0 ||
|
|
strcmp(name, "EffectivePostProcessKeyEvent") == 0){
|
|
if(dbus_message_iter_get_arg_type(&value) != DBUS_TYPE_STRUCT)
|
|
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
|
|
"the property expects (b)");
|
|
dbus_message_iter_recurse(&value, &st);
|
|
if(dbus_message_iter_get_arg_type(&st) != DBUS_TYPE_BOOLEAN)
|
|
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
|
|
"the property expects (b)");
|
|
dbus_message_iter_get_basic(&st, &b);
|
|
if(dbus_message_iter_next(&st))
|
|
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
|
|
"the property expects (b)");
|
|
if(name[0] == 'C')
|
|
ctx->clientcommitpreedit = b != FALSE;
|
|
else
|
|
ctx->postprocess = b != FALSE;
|
|
return reply(c, m, DBUS_TYPE_INVALID, nil);
|
|
}
|
|
return handleerror(c, m, DBUS_ERROR_UNKNOWN_PROPERTY,
|
|
"unknown input context property");
|
|
}
|
|
|
|
static DBusHandlerResult
|
|
handlepropertygetall(DBusConnection *c, DBusMessage *m)
|
|
{
|
|
DBusMessage *r;
|
|
DBusMessageIter it;
|
|
const char *iface;
|
|
|
|
if(!dbus_message_get_args(m, nil, DBUS_TYPE_STRING, &iface,
|
|
DBUS_TYPE_INVALID))
|
|
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
|
|
"Properties.GetAll expects an interface name");
|
|
if(strcmp(iface, "org.freedesktop.IBus.InputContext") != 0)
|
|
return handleerror(c, m, DBUS_ERROR_UNKNOWN_INTERFACE,
|
|
"unknown property interface");
|
|
r = dbus_message_new_method_return(m);
|
|
if(r == nil)
|
|
die("ibus: out of memory");
|
|
dbus_message_iter_init_append(r, &it);
|
|
emptydict(&it);
|
|
dbus_connection_send(c, r, nil);
|
|
dbus_message_unref(r);
|
|
return DBUS_HANDLER_RESULT_HANDLED;
|
|
}
|
|
|
|
static DBusHandlerResult
|
|
handledestroy(DBusConnection *c, DBusMessage *m, Ictx *ctx)
|
|
{
|
|
dropcontext(ctx);
|
|
return reply(c, m, DBUS_TYPE_INVALID, nil);
|
|
}
|
|
|
|
static DBusHandlerResult
|
|
handlecursor(DBusConnection *c, DBusMessage *m, Ictx *ctx)
|
|
{
|
|
dbus_int32_t x, y, w, h;
|
|
|
|
dbus_message_get_args(m, nil, DBUS_TYPE_INT32, &x, DBUS_TYPE_INT32, &y,
|
|
DBUS_TYPE_INT32, &w, DBUS_TYPE_INT32, &h, DBUS_TYPE_INVALID);
|
|
setcursor(ctx, x, y, h);
|
|
return reply(c, m, DBUS_TYPE_INVALID, nil);
|
|
}
|
|
|
|
/*
|
|
* Accepted and thrown away: there is one engine, so any name is fine,
|
|
* and a relative cursor location is surface local, not a caret.
|
|
*/
|
|
static DBusHandlerResult
|
|
handleok(DBusConnection *c, DBusMessage *m, Ictx*)
|
|
{
|
|
return reply(c, m, DBUS_TYPE_INVALID, nil);
|
|
}
|
|
|
|
/* The InputContext interface: member, argument signature, handler. */
|
|
static struct {
|
|
char *member;
|
|
char *sig;
|
|
DBusHandlerResult (*fn)(DBusConnection*, DBusMessage*, Ictx*);
|
|
} ictab[] = {
|
|
{"ProcessKeyEvent", "uuu", handlekey},
|
|
{"FocusIn", "", handlefocusin},
|
|
{"FocusOut", "", handlefocusout},
|
|
{"Reset", "", handlereset},
|
|
{"Destroy", "", handledestroy},
|
|
{"SetCursorLocation", "iiii", handlecursor},
|
|
{"SetCursorLocationRelative", "iiii", handleok},
|
|
{"SetCapabilities", "u", handlecap},
|
|
{"SetEngine", "s", handleok},
|
|
};
|
|
|
|
/*
|
|
* One handler for everything a client sends: the tiny slice of the bus
|
|
* that libibus needs, then the InputContext interface. Unhandled calls
|
|
* get libdbus's UnknownMethod error.
|
|
*/
|
|
static DBusHandlerResult
|
|
onmsg(DBusConnection *c, DBusMessage *m, void*)
|
|
{
|
|
const char *iface, *member, *path;
|
|
Ictx *ctx;
|
|
int i;
|
|
|
|
if(dbus_message_get_type(m) != DBUS_MESSAGE_TYPE_METHOD_CALL)
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
|
iface = dbus_message_get_interface(m);
|
|
member = dbus_message_get_member(m);
|
|
path = dbus_message_get_path(m);
|
|
if(iface == nil || member == nil || path == nil)
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
|
if(strcmp(iface, "org.freedesktop.DBus") == 0){
|
|
if(strcmp(member, "Hello") == 0)
|
|
return handlehello(c, m);
|
|
if(strcmp(member, "GetNameOwner") == 0)
|
|
return handlenameowner(c, m);
|
|
if(strcmp(member, "AddMatch") == 0
|
|
|| strcmp(member, "RemoveMatch") == 0){
|
|
if(!dbus_message_has_signature(m, "s"))
|
|
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
|
|
"match rule must be one string");
|
|
return reply(c, m, DBUS_TYPE_INVALID, nil);
|
|
}
|
|
}
|
|
if(strcmp(iface, "org.freedesktop.IBus") == 0){
|
|
if(strcmp(member, "CreateInputContext") == 0)
|
|
return handlecreate(c, m);
|
|
}
|
|
if(strcmp(iface, "org.freedesktop.DBus.Properties") == 0){
|
|
ctx = findcontext(c, path);
|
|
if(ctx == nil)
|
|
return handleerror(c, m, DBUS_ERROR_UNKNOWN_OBJECT,
|
|
"unknown input context");
|
|
if(strcmp(member, "Set") == 0)
|
|
return handlepropertyset(c, m, ctx);
|
|
if(strcmp(member, "Get") == 0){
|
|
if(dbus_message_get_args(m, nil, DBUS_TYPE_STRING, &iface,
|
|
DBUS_TYPE_STRING, &member, DBUS_TYPE_INVALID) &&
|
|
strcmp(member, "PostProcessKeyEvent") == 0)
|
|
return handlepost(c, m, ctx);
|
|
return handleerror(c, m, DBUS_ERROR_UNKNOWN_PROPERTY,
|
|
"unknown input context property");
|
|
}
|
|
if(strcmp(member, "GetAll") == 0)
|
|
return handlepropertygetall(c, m);
|
|
}
|
|
/* libibus destroys a context through its Service base interface. */
|
|
if(strcmp(iface, "org.freedesktop.IBus.Service") == 0 &&
|
|
strcmp(member, "Destroy") == 0)
|
|
iface = "org.freedesktop.IBus.InputContext";
|
|
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");
|
|
for(i = 0; i < nelem(ictab); i++){
|
|
if(strcmp(member, ictab[i].member) != 0)
|
|
continue;
|
|
if(!dbus_message_has_signature(m, ictab[i].sig))
|
|
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
|
|
"bad arguments");
|
|
return ictab[i].fn(c, m, ctx);
|
|
}
|
|
}
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
|
}
|
|
|
|
static void
|
|
newconn(DBusServer*, DBusConnection *c, void*)
|
|
{
|
|
DBusObjectPathVTable vt;
|
|
|
|
if(nconns >= Maxclients){
|
|
fprint(2, "strans: ibus: rejecting client: %d-connection limit reached\n",
|
|
Maxclients);
|
|
dbus_connection_close(c);
|
|
return;
|
|
}
|
|
if(!dbus_connection_set_watch_functions(c, addwatch, removewatch,
|
|
nil, nil, nil)){
|
|
fprint(2, "strans: ibus: cannot watch client connection\n");
|
|
dbus_connection_close(c);
|
|
return;
|
|
}
|
|
memset(&vt, 0, sizeof(vt));
|
|
vt.message_function = onmsg;
|
|
if(!dbus_connection_register_fallback(c, "/", &vt, nil)){
|
|
fprint(2, "strans: ibus: cannot register client handler\n");
|
|
dbus_connection_set_watch_functions(c, nil, nil, nil, nil, nil);
|
|
dbus_connection_close(c);
|
|
return;
|
|
}
|
|
dbus_connection_ref(c);
|
|
conns[nconns++] = c;
|
|
}
|
|
|
|
static void
|
|
pruneconns(void)
|
|
{
|
|
int i, j;
|
|
|
|
j = 0;
|
|
for(i = 0; i < nconns; i++){
|
|
if(dbus_connection_get_is_connected(conns[i])){
|
|
conns[j++] = conns[i];
|
|
continue;
|
|
}
|
|
dropconncontexts(conns[i]);
|
|
dbus_connection_unref(conns[i]);
|
|
}
|
|
nconns = j;
|
|
}
|
|
|
|
static void
|
|
ibusinit(void)
|
|
{
|
|
DBusError err;
|
|
char addr[64], *full;
|
|
|
|
if(buildaddrpath(addrfile, sizeof addrfile) < 0)
|
|
die("ibus: cannot build address path");
|
|
snprintf(addr, sizeof addr, "unix:abstract=strans-%d", (int)getpid());
|
|
dbus_error_init(&err);
|
|
srv = dbus_server_listen(addr, &err);
|
|
if(srv == nil)
|
|
die("ibus: listen: %s", err.message);
|
|
dbus_server_set_new_connection_function(srv, newconn, nil, nil);
|
|
if(!dbus_server_set_watch_functions(srv, addwatch, removewatch,
|
|
nil, nil, nil))
|
|
die("ibus: cannot watch server");
|
|
full = dbus_server_get_address(srv);
|
|
if(full == nil)
|
|
die("ibus: cannot get server address");
|
|
atexit(unlinkaddr);
|
|
threadnotify(addrnote, 1);
|
|
if(writeaddr(addrfile, full) < 0)
|
|
die("ibus: cannot write %s", addrfile);
|
|
dbus_free(full);
|
|
}
|
|
|
|
void
|
|
ibusthread(void*)
|
|
{
|
|
struct pollfd pfds[Maxwatches];
|
|
DBusWatch *polled[Maxwatches];
|
|
int wi[Maxwatches];
|
|
int i, n, rv;
|
|
unsigned int f;
|
|
|
|
threadsetname("ibus");
|
|
ibusinit();
|
|
replyc = chancreate(sizeof(Keyres), 0);
|
|
for(;;){
|
|
if(!dbus_server_get_is_connected(srv))
|
|
die("ibus: server disconnected");
|
|
n = 0;
|
|
for(i = 0; i < nwatches && n < Maxwatches; i++){
|
|
if(watches[i] == nil)
|
|
continue;
|
|
if(!dbus_watch_get_enabled(watches[i]))
|
|
continue;
|
|
pfds[n].fd = dbus_watch_get_unix_fd(watches[i]);
|
|
pfds[n].events = 0;
|
|
f = dbus_watch_get_flags(watches[i]);
|
|
if(f & DBUS_WATCH_READABLE) pfds[n].events |= POLLIN;
|
|
if(f & DBUS_WATCH_WRITABLE) pfds[n].events |= POLLOUT;
|
|
wi[n] = i;
|
|
polled[n] = watches[i];
|
|
n++;
|
|
}
|
|
rv = poll(pfds, n, preowner != nil ? Ownerpoll : -1);
|
|
if(rv < 0 && errno == EINTR)
|
|
continue;
|
|
if(rv < 0)
|
|
die("ibus: poll: %r");
|
|
for(i = 0; i < n; i++){
|
|
if(pfds[i].revents == 0)
|
|
continue;
|
|
if(watches[wi[i]] != polled[i])
|
|
continue;
|
|
f = 0;
|
|
if(pfds[i].revents & POLLIN) f |= DBUS_WATCH_READABLE;
|
|
if(pfds[i].revents & POLLOUT) f |= DBUS_WATCH_WRITABLE;
|
|
if(pfds[i].revents & POLLHUP) f |= DBUS_WATCH_HANGUP;
|
|
if(pfds[i].revents & POLLERR) f |= DBUS_WATCH_ERROR;
|
|
dbus_watch_handle(polled[i], f);
|
|
}
|
|
for(i = 0; i < nconns; i++)
|
|
while(dbus_connection_dispatch(conns[i]) == DBUS_DISPATCH_DATA_REMAINS)
|
|
;
|
|
pruneconns();
|
|
checkpreowner();
|
|
}
|
|
}
|