Six handlers built method returns by hand and only two of them checked for a nil message; reply() builds them all and dies on OOM like the rest of the daemon. DBusError objects were initialised and freed but never read: libdbus accepts nil. Properties.Get always errored, so it is one line in onmsg; the introspection XML served nobody (libibus never asks) and had to be kept in sync by hand. Ibushinthidden was not an IBus hint, so that term of hidden() never fired. writeaddr uses the syscalls directly and the address file's dev/ino is the ownership proof; buildaddrpath makes the directory in place. An empty UpdatePreeditText goes out only to the context that shows a preedit.
1052 lines
26 KiB
C
1052 lines
26 KiB
C
#include "dat.h"
|
|
#include "fn.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
#include <poll.h>
|
|
#include <dbus/dbus.h>
|
|
#include <xkbcommon/xkbcommon.h>
|
|
|
|
enum
|
|
{
|
|
Maxconns = Maxclients,
|
|
Maxwatches = 2*Maxconns + 1,
|
|
Maxcontexts = Maxclients,
|
|
Relmask = 1<<30,
|
|
Ownerpoll = 200, /* ms between owner checks while a preedit shows */
|
|
/* IBus wire constants; the daemon does not link libibus. */
|
|
Ibuscappreedit = 1<<0,
|
|
Ibuspurposepassword = 8,
|
|
Ibuspurposepin = 9,
|
|
Ibusattrunderline = 1,
|
|
Ibusunderlinesingle = 1,
|
|
Ibuspreeditclear = 0,
|
|
};
|
|
|
|
typedef struct Ictx Ictx;
|
|
struct Ictx
|
|
{
|
|
DBusConnection *conn;
|
|
char path[64];
|
|
int focused;
|
|
u32int cap;
|
|
u32int purpose;
|
|
u32int hints;
|
|
int clientcommitpreedit;
|
|
Caret caret;
|
|
};
|
|
|
|
static DBusWatch *watches[Maxwatches];
|
|
static int nwatches;
|
|
static DBusConnection *conns[Maxconns];
|
|
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 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';
|
|
}
|
|
|
|
static void
|
|
machineid(char *buf, int sz)
|
|
{
|
|
char *path[] = {
|
|
"/etc/machine-id",
|
|
"/var/lib/dbus/machine-id",
|
|
};
|
|
|
|
machineidfiles(buf, sz, path, nelem(path));
|
|
}
|
|
|
|
static void
|
|
xdisplay(char *host, int hsz, char *num, int nsz)
|
|
{
|
|
char *d, *colon, *dot, *p;
|
|
int n;
|
|
|
|
strncpy(host, "unix", hsz);
|
|
host[hsz-1] = '\0';
|
|
strncpy(num, "0", nsz);
|
|
num[nsz-1] = '\0';
|
|
d = getenv("DISPLAY");
|
|
if(d == nil || d[0] == '\0')
|
|
return;
|
|
colon = strchr(d, ':');
|
|
if(colon == nil)
|
|
return;
|
|
if(colon > d){
|
|
n = colon - d;
|
|
if(n >= hsz) n = hsz - 1;
|
|
memcpy(host, d, n);
|
|
host[n] = '\0';
|
|
}
|
|
p = colon + 1;
|
|
dot = strchr(p, '.');
|
|
n = dot ? dot - p : (int)strlen(p);
|
|
if(n >= nsz) n = nsz - 1;
|
|
memcpy(num, p, n);
|
|
num[n] = '\0';
|
|
}
|
|
|
|
/* The IBus address file lives where libibus looks: config/ibus/bus/. */
|
|
static int
|
|
buildaddrpath(char *buf, int sz)
|
|
{
|
|
char mid[64], host[64], num[8], 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));
|
|
if(mid[0] == '\0')
|
|
return -1;
|
|
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;
|
|
|
|
USED(_);
|
|
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;
|
|
|
|
USED(_);
|
|
for(i = 0; i < nwatches; i++)
|
|
if(watches[i] == w){
|
|
watches[i] = nil;
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void
|
|
togglewatch(DBusWatch *w, void *_)
|
|
{
|
|
USED(w);
|
|
USED(_);
|
|
}
|
|
|
|
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.cap = clientpreedit(ctx) ? Cclientpreedit : 0;
|
|
kr.op = op;
|
|
kr.ks = ks;
|
|
kr.mod = mod;
|
|
kr.caret = ctx->caret;
|
|
kr.reply = replyc;
|
|
chansend(keyc, &kr);
|
|
chanrecv(replyc, res);
|
|
}
|
|
|
|
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);
|
|
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]);
|
|
}
|
|
|
|
static int
|
|
hidden(Ictx *ctx)
|
|
{
|
|
return ctx->purpose == Ibuspurposepassword ||
|
|
ctx->purpose == Ibuspurposepin;
|
|
}
|
|
|
|
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 void
|
|
emitcommit(DBusConnection *c, const char *path, const char *text)
|
|
{
|
|
DBusMessage *sig;
|
|
DBusMessageIter it;
|
|
|
|
sig = newsignal(path, "CommitText");
|
|
dbus_message_iter_init_append(sig, &it);
|
|
appendibustext(&it, text, 0);
|
|
dbus_connection_send(c, 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;
|
|
|
|
if(text[0] == '\0' && preowner != ctx)
|
|
return;
|
|
sig = newsignal(ctx->path, ctx->clientcommitpreedit ?
|
|
"UpdatePreeditTextWithMode" : "UpdatePreeditText");
|
|
dbus_message_iter_init_append(sig, &it);
|
|
appendibustext(&it, text, 1);
|
|
cursor = utflen((char*)text);
|
|
visible = text[0] != '\0' ? TRUE : FALSE;
|
|
dbus_message_iter_append_basic(&it, DBUS_TYPE_UINT32, &cursor);
|
|
dbus_message_iter_append_basic(&it, DBUS_TYPE_BOOLEAN, &visible);
|
|
if(ctx->clientcommitpreedit){
|
|
mode = Ibuspreeditclear;
|
|
dbus_message_iter_append_basic(&it, DBUS_TYPE_UINT32, &mode);
|
|
}
|
|
if(dbus_connection_send(ctx->conn, sig, nil)){
|
|
if(text[0] != '\0')
|
|
preowner = ctx;
|
|
else if(preowner == ctx)
|
|
preowner = nil;
|
|
}
|
|
dbus_message_unref(sig);
|
|
}
|
|
|
|
/* 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, "");
|
|
}
|
|
|
|
static int
|
|
processkey(Ictx *ctx, u32int sym, u32int state, Keyres *res)
|
|
{
|
|
if(state & Relmask || !ctx->focused || hidden(ctx))
|
|
return 0;
|
|
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];
|
|
int restart;
|
|
|
|
if(!dbus_message_get_args(m, nil,
|
|
DBUS_TYPE_UINT32, &sym,
|
|
DBUS_TYPE_UINT32, &code,
|
|
DBUS_TYPE_UINT32, &state,
|
|
DBUS_TYPE_INVALID))
|
|
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
|
|
"ProcessKeyEvent expects three unsigned integers");
|
|
if(!processkey(ctx, sym, state, &res))
|
|
return replybool(c, m, 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;
|
|
if(restart)
|
|
emitpreedit(ctx, "");
|
|
if(commit[0] != '\0')
|
|
emitcommit(c, dbus_message_get_path(m), commit);
|
|
if(clientpreedit(ctx) && (!restart || preedit[0] != '\0'))
|
|
emitpreedit(ctx, preedit);
|
|
return replybool(c, m, res.eaten);
|
|
}
|
|
|
|
static DBusHandlerResult
|
|
handlereset(DBusConnection *c, DBusMessage *m, Ictx *ctx)
|
|
{
|
|
Keyres res;
|
|
|
|
sendrequest(ctx, Keyreset, 0, 0, &res);
|
|
if(clientpreedit(ctx))
|
|
emitpreedit(ctx, "");
|
|
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)
|
|
{
|
|
releasecontext(ctx);
|
|
if(clientpreedit(ctx))
|
|
emitpreedit(ctx, "");
|
|
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;
|
|
|
|
if(!dbus_message_get_args(m, nil, DBUS_TYPE_UINT32, &cap,
|
|
DBUS_TYPE_INVALID))
|
|
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
|
|
"SetCapabilities expects one unsigned integer");
|
|
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;
|
|
}
|
|
|
|
static int
|
|
getcontent(DBusMessageIter *value, u32int *purpose, u32int *hints)
|
|
{
|
|
DBusMessageIter st;
|
|
dbus_uint32_t p, h;
|
|
|
|
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;
|
|
dbus_message_iter_get_basic(&st, &h);
|
|
if(dbus_message_iter_next(&st))
|
|
return 0;
|
|
*purpose = p;
|
|
*hints = h;
|
|
return 1;
|
|
}
|
|
|
|
/* Only ContentType and ClientCommitPreedit are settable; none is readable. */
|
|
static DBusHandlerResult
|
|
handlepropertyset(DBusConnection *c, DBusMessage *m, Ictx *ctx)
|
|
{
|
|
const char *iface, *name;
|
|
DBusMessageIter value, st;
|
|
dbus_bool_t b;
|
|
u32int purpose, hints;
|
|
Keyres res;
|
|
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, &hints))
|
|
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
|
|
"ContentType expects (uu)");
|
|
washidden = hidden(ctx);
|
|
ctx->purpose = purpose;
|
|
ctx->hints = hints;
|
|
if(ctx->focused && !washidden && hidden(ctx)){
|
|
sendrequest(ctx, Keyreset, 0, 0, &res);
|
|
if(clientpreedit(ctx))
|
|
emitpreedit(ctx, "");
|
|
}
|
|
return reply(c, m, DBUS_TYPE_INVALID, nil);
|
|
}
|
|
if(strcmp(name, "ClientCommitPreedit") == 0){
|
|
if(dbus_message_iter_get_arg_type(&value) != DBUS_TYPE_STRUCT)
|
|
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
|
|
"ClientCommitPreedit 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,
|
|
"ClientCommitPreedit expects (b)");
|
|
dbus_message_iter_get_basic(&st, &b);
|
|
if(dbus_message_iter_next(&st))
|
|
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
|
|
"ClientCommitPreedit expects (b)");
|
|
ctx->clientcommitpreedit = 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;
|
|
|
|
if(!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))
|
|
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");
|
|
setcursor(ctx, x, y, h);
|
|
return reply(c, m, DBUS_TYPE_INVALID, nil);
|
|
}
|
|
|
|
/* Argument-free InputContext calls, checked for an empty signature. */
|
|
static DBusHandlerResult
|
|
handleplain(DBusConnection *c, DBusMessage *m, Ictx *ctx, const char *member)
|
|
{
|
|
if(!dbus_message_has_signature(m, ""))
|
|
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
|
|
"this call takes no arguments");
|
|
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);
|
|
return handledestroy(c, m, ctx);
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
|
|
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){
|
|
if(findcontext(c, path) == nil)
|
|
return handleerror(c, m, DBUS_ERROR_UNKNOWN_OBJECT,
|
|
"unknown input context");
|
|
if(strcmp(member, "Set") == 0)
|
|
return handlepropertyset(c, m, findcontext(c, path));
|
|
if(strcmp(member, "Get") == 0)
|
|
return handleerror(c, m, DBUS_ERROR_UNKNOWN_PROPERTY,
|
|
"input context properties are write-only");
|
|
if(strcmp(member, "GetAll") == 0)
|
|
return handlepropertygetall(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, ctx);
|
|
if(strcmp(member, "FocusIn") == 0 ||
|
|
strcmp(member, "FocusOut") == 0 ||
|
|
strcmp(member, "Reset") == 0 ||
|
|
strcmp(member, "Destroy") == 0)
|
|
return handleplain(c, m, ctx, member);
|
|
if(strcmp(member, "SetCursorLocation") == 0)
|
|
return handlecursor(c, m, ctx);
|
|
if(strcmp(member, "SetCapabilities") == 0)
|
|
return handlecap(c, m, ctx);
|
|
if(strcmp(member, "SetEngine") == 0){
|
|
if(!dbus_message_has_signature(m, "s"))
|
|
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
|
|
"SetEngine expects one engine name");
|
|
return reply(c, m, DBUS_TYPE_INVALID, nil);
|
|
}
|
|
}
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
|
}
|
|
|
|
static void
|
|
newconn(DBusServer *s, DBusConnection *c, void *_)
|
|
{
|
|
DBusObjectPathVTable vt;
|
|
|
|
USED(s);
|
|
USED(_);
|
|
if(nconns >= Maxconns){
|
|
fprintf(stderr, "strans: ibus: rejecting client: %d-connection limit reached\n",
|
|
Maxconns);
|
|
dbus_connection_close(c);
|
|
return;
|
|
}
|
|
if(!dbus_connection_set_watch_functions(c, addwatch, removewatch,
|
|
togglewatch, nil, nil)){
|
|
fprintf(stderr, "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)){
|
|
fprintf(stderr, "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 int
|
|
ibusinit(void)
|
|
{
|
|
DBusError err;
|
|
char addr[128];
|
|
char *full;
|
|
int cleanupregistered;
|
|
|
|
full = nil;
|
|
cleanupregistered = 0;
|
|
if(buildaddrpath(addrfile, sizeof(addrfile)) < 0){
|
|
fprintf(stderr, "strans: ibus: cannot build address path\n");
|
|
return -1;
|
|
}
|
|
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){
|
|
fprintf(stderr, "strans: ibus: listen: %s\n", err.message);
|
|
dbus_error_free(&err);
|
|
addrfile[0] = '\0';
|
|
return -1;
|
|
}
|
|
dbus_server_set_new_connection_function(srv, newconn, nil, nil);
|
|
if(!dbus_server_set_watch_functions(srv, addwatch, removewatch,
|
|
togglewatch, nil, nil)){
|
|
fprintf(stderr, "strans: ibus: cannot watch server\n");
|
|
goto fail;
|
|
}
|
|
full = dbus_server_get_address(srv);
|
|
if(full == nil){
|
|
fprintf(stderr, "strans: ibus: cannot get server address\n");
|
|
goto fail;
|
|
}
|
|
if(!atexit(unlinkaddr)){
|
|
fprintf(stderr, "strans: ibus: cannot register address cleanup\n");
|
|
goto fail;
|
|
}
|
|
cleanupregistered = 1;
|
|
if(writeaddr(addrfile, full) < 0){
|
|
fprintf(stderr, "strans: ibus: cannot write %s\n", addrfile);
|
|
goto fail;
|
|
}
|
|
dbus_free(full);
|
|
return 0;
|
|
fail:
|
|
if(cleanupregistered)
|
|
atexitdont(unlinkaddr);
|
|
if(full != nil)
|
|
dbus_free(full);
|
|
dbus_server_disconnect(srv);
|
|
dbus_server_unref(srv);
|
|
srv = nil;
|
|
addrfile[0] = '\0';
|
|
return -1;
|
|
}
|
|
|
|
void
|
|
ibusthread(void *_)
|
|
{
|
|
struct pollfd pfds[Maxwatches];
|
|
DBusWatch *polled[Maxwatches];
|
|
int wi[Maxwatches];
|
|
int i, n, rv;
|
|
unsigned int f;
|
|
|
|
USED(_);
|
|
threadsetname("ibus");
|
|
if(ibusinit() < 0)
|
|
die("ibus: initialization failed");
|
|
replyc = chancreate(sizeof(Keyres), 0);
|
|
if(replyc == nil)
|
|
die("ibus: cannot create reply channel");
|
|
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: %s", strerror(errno));
|
|
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();
|
|
}
|
|
}
|