Files
strans/srv.c
Hojun-Cho 910bf51347 ipc, srv, gtk: the client's text goes over and a take-back comes back
The engine can reach a Hanja reading back into the text the client
already holds, but only if the frontend hands that text over and can
take some of it away again.  GTK 3 has both: retrieve-surrounding
brings the text around the cursor and delete-surrounding removes runes
before it, and a widget that answers neither leaves the text empty, so
nothing is ever reached into or taken from it.

The wire grows a control frame for the text, sent like the caret only
when it changes, and one byte in every response for the runes to take
back.  That byte moves the length fields along, so the version goes to
2: an old daemon and a new module, either way round, fail the handshake
and the module falls through to GtkIMContextSimple rather than misread
a frame.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 14:31:31 +09:00

171 lines
3.6 KiB
C

#include "dat.h"
#include "fn.h"
#include <sys/stat.h>
#include <sys/un.h>
static char adir[256];
static char sockpath[sizeof(((struct sockaddr_un*)0)->sun_path)];
static dev_t sockdev;
static ino_t sockino;
static Channel *clientc;
/* Removes the socket only while it is still the one we announced. */
static void
srvunlink(void)
{
struct stat st;
if(sockpath[0] != '\0' && lstat(sockpath, &st) == 0 &&
st.st_dev == sockdev && st.st_ino == sockino)
unlink(sockpath);
sockpath[0] = '\0';
}
static int
srvnote(void *v, char *note)
{
USED(v);
if(notefatal(note))
srvunlink();
return 0;
}
/*
* 0 for a request to make, 1 for a frame that only changes what rides
* along with the requests after it, -1 for anything else.
*/
static int
srvreadreq(int fd, Keyreq *kr, int *want)
{
uchar req[Ipccaretsz];
char text[Ipcfieldmax];
int valid;
size_t n;
int32_t x, y, h;
if(ipcreadn(fd, req, Ipcreqsz) < 0)
return -1;
*want = 0;
kr->ks = 0;
kr->mod = 0;
switch(ipcreqtype(req)){
case Ipckey:
ipcunpackreq(req, want, &kr->mod, &kr->ks);
kr->op = ipcreqreset(req) ? Keyreset : Keypress;
return 0;
case Ipccap:
*want = (req[0] & Ipcreqwant) != 0;
kr->op = Keycap;
return 0;
case Ipccaret:
if(ipcreadn(fd, req + Ipcreqsz, Ipccaretsz - Ipcreqsz) < 0 ||
ipcunpackcaret(req, &valid, &x, &y, &h) < 0)
return -1;
kr->op = Keycaret;
kr->caret.valid = valid;
kr->caret.x = x;
kr->caret.y = y;
kr->caret.h = h;
return 0;
case Ipcsurround:
n = ipcsurroundlen(req);
if(n > 0 && ipcreadn(fd, text, n) < 0)
return -1;
stail(&kr->surround, text, n);
return 1;
}
return -1;
}
/*
* One Keyreq persists for the connection: the negotiated preedit and
* the last caret ride along with every request, and the socket closing
* releases the engine.
*/
static void
clientthread(void *arg)
{
Keyreq kr;
Keyres res;
uchar out[Ipcmaxresp], token;
char commit[Maxutf], preedit[Maxutf];
int fd, n, ncommit, npreedit, rv, want;
fd = (int)(uintptr)arg;
threadsetname("client %d", fd);
memset(&kr, 0, sizeof kr);
kr.reply = chancreate(sizeof(Keyres), 0);
kr.owner = &fd;
kr.clientpre = 1;
while((rv = srvreadreq(fd, &kr, &want)) >= 0){
if(rv != 0)
continue;
if(kr.op != Keycaret)
kr.clientpre = want;
chansend(keyc, &kr);
chanrecv(kr.reply, &res);
if(kr.op == Keycaret)
continue;
ncommit = stoutf(&res.commit, commit, sizeof commit);
npreedit = stoutf(&res.preedit, preedit, sizeof preedit);
/* A capability reply is always eaten: it marks the extension. */
n = ipcpackresp(out, sizeof out, kr.op == Keycap || res.eaten,
res.del, commit, ncommit, preedit, npreedit, want);
if(n < 0 || ipcsend(fd, out, n) < 0)
break;
}
kr.op = Keyrelease;
kr.ks = 0;
kr.mod = 0;
chansend(keyc, &kr);
chanrecv(kr.reply, &res);
chanfree(kr.reply);
close(fd);
chanrecv(clientc, &token);
}
void
srvinit(void)
{
struct stat st;
char *addr;
if(ipcpath(sockpath, sizeof sockpath) < 0)
die("IPC path is too long");
addr = smprint("unix!%s", sockpath);
if(addr == nil)
die("out of memory");
if(announce(addr, adir) < 0)
die("IPC endpoint is already in use: %r");
free(addr);
if(chmod(sockpath, 0600) < 0 || lstat(sockpath, &st) < 0)
die("can't protect IPC endpoint: %s", sockpath);
sockdev = st.st_dev;
sockino = st.st_ino;
atexit(srvunlink);
threadnotify(srvnote, 1);
}
void
srvthread(void*)
{
char ldir[40];
int fd;
uchar token;
threadsetname("srv");
token = 0;
clientc = chancreate(sizeof token, Maxclients);
for(;;){
fd = listen(adir, ldir);
if(fd < 0)
continue;
if(channbsend(clientc, &token) <= 0){
close(fd);
continue;
}
proccreate(clientthread, (void*)(uintptr)fd, 8192);
}
}