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>
452 lines
8.1 KiB
C
452 lines
8.1 KiB
C
#define _POSIX_C_SOURCE 200809L
|
|
|
|
#include <errno.h>
|
|
#include <poll.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/un.h>
|
|
#include "ipc.h"
|
|
|
|
static int64_t
|
|
nowms(void)
|
|
{
|
|
struct timespec ts;
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
|
}
|
|
|
|
static int64_t
|
|
deadline(void)
|
|
{
|
|
return nowms() + Ipcwaitms;
|
|
}
|
|
|
|
static int
|
|
waitfd(int fd, short events, int64_t until)
|
|
{
|
|
struct pollfd pfd;
|
|
int64_t left;
|
|
int n;
|
|
|
|
pfd.fd = fd;
|
|
pfd.events = events;
|
|
for(;;){
|
|
left = until - nowms();
|
|
if(left <= 0){
|
|
errno = ETIMEDOUT;
|
|
return -1;
|
|
}
|
|
n = poll(&pfd, 1, left);
|
|
if(n < 0 && errno == EINTR)
|
|
continue;
|
|
if(n < 0)
|
|
return -1;
|
|
if(n == 0){
|
|
errno = ETIMEDOUT;
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static int
|
|
readwait(int fd, void *buf, size_t n, int64_t until)
|
|
{
|
|
unsigned char *p;
|
|
ssize_t r;
|
|
|
|
p = buf;
|
|
while(n > 0){
|
|
r = recv(fd, p, n, MSG_DONTWAIT);
|
|
if(r < 0 && errno == EINTR)
|
|
continue;
|
|
if(r < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)){
|
|
if(waitfd(fd, POLLIN, until) < 0)
|
|
return -1;
|
|
continue;
|
|
}
|
|
if(r < 0)
|
|
return -1;
|
|
if(r == 0){
|
|
errno = ECONNRESET;
|
|
return -1;
|
|
}
|
|
p += r;
|
|
n -= r;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
putlen(unsigned char p[Ipclensz], size_t n)
|
|
{
|
|
p[0] = n;
|
|
p[1] = n >> 8;
|
|
}
|
|
|
|
static size_t
|
|
getlen(const unsigned char p[Ipclensz])
|
|
{
|
|
return p[0] | (p[1] << 8);
|
|
}
|
|
|
|
static void
|
|
put32(unsigned char *p, int32_t v)
|
|
{
|
|
uint32_t u;
|
|
|
|
u = v;
|
|
p[0] = u;
|
|
p[1] = u >> 8;
|
|
p[2] = u >> 16;
|
|
p[3] = u >> 24;
|
|
}
|
|
|
|
static int32_t
|
|
get32(const unsigned char *p)
|
|
{
|
|
return (int32_t)((uint32_t)p[0] |
|
|
((uint32_t)p[1] << 8) |
|
|
((uint32_t)p[2] << 16) |
|
|
((uint32_t)p[3] << 24));
|
|
}
|
|
|
|
int
|
|
ipcpath(char *dst, size_t cap)
|
|
{
|
|
const char *dir, *sep;
|
|
int n;
|
|
|
|
dir = getenv("XDG_RUNTIME_DIR");
|
|
if(dir != NULL && dir[0] == '/'){
|
|
sep = dir[strlen(dir)-1] == '/' ? "" : "/";
|
|
n = snprintf(dst, cap, "%s%sstrans.sock", dir, sep);
|
|
}else
|
|
n = snprintf(dst, cap, "/tmp/strans.%lu",
|
|
(unsigned long)getuid());
|
|
if(n < 0 || (size_t)n >= cap)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* A nonblocking AF_UNIX connect completes at once or fails with EAGAIN
|
|
* when the daemon's backlog is full; the socket stays nonblocking because
|
|
* every transfer below polls with a deadline.
|
|
*/
|
|
int
|
|
ipcconnect(void)
|
|
{
|
|
struct sockaddr_un addr;
|
|
int e, fd;
|
|
|
|
memset(&addr, 0, sizeof addr);
|
|
addr.sun_family = AF_UNIX;
|
|
if(ipcpath(addr.sun_path, sizeof addr.sun_path) < 0){
|
|
errno = ENAMETOOLONG;
|
|
return -1;
|
|
}
|
|
fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
|
|
if(fd < 0)
|
|
return -1;
|
|
if(connect(fd, (struct sockaddr*)&addr, sizeof addr) < 0){
|
|
e = errno;
|
|
close(fd);
|
|
errno = e;
|
|
return -1;
|
|
}
|
|
return fd;
|
|
}
|
|
|
|
/*
|
|
* Engine key for an X keysym and its Unicode value: printable characters
|
|
* as themselves, the function keysyms 0xff00-0xffff as Kspec+offset, and
|
|
* no key at all for a modifier. The keypad keys and Shift+Tab
|
|
* (ISO_Left_Tab) fold onto their plain counterparts.
|
|
*/
|
|
uint32_t
|
|
ipckeysym(uint32_t sym, uint32_t unicode)
|
|
{
|
|
if(unicode >= ' ' && unicode != 0x7f)
|
|
return unicode;
|
|
if(sym >= 0xffe1 && sym <= 0xffee)
|
|
return 0;
|
|
switch(sym){
|
|
case 0xfe20:
|
|
case 0xff89:
|
|
return Ktab;
|
|
case 0xff8d:
|
|
return Kret;
|
|
case 0xff97:
|
|
return Kup;
|
|
case 0xff99:
|
|
return Kdown;
|
|
case 0xff9a:
|
|
return Kpgup;
|
|
case 0xff9b:
|
|
return Kpgdown;
|
|
}
|
|
if(sym >= 0xff00 && sym <= 0xffff)
|
|
return Kspec + (sym - 0xff00);
|
|
return unicode;
|
|
}
|
|
|
|
uint32_t
|
|
ipcmod(uint32_t state)
|
|
{
|
|
uint32_t m;
|
|
|
|
m = state & Mmask;
|
|
if(state & Msupervirt)
|
|
m |= Msuper;
|
|
return m;
|
|
}
|
|
|
|
void
|
|
ipcpackreq(unsigned char req[Ipcreqsz], int want, uint32_t mod,
|
|
uint32_t key)
|
|
{
|
|
req[0] = want ? Ipcreqwant : 0;
|
|
req[1] = mod;
|
|
req[2] = key;
|
|
req[3] = key >> 8;
|
|
req[4] = key >> 16;
|
|
req[5] = key >> 24;
|
|
}
|
|
|
|
void
|
|
ipcpackreset(unsigned char req[Ipcreqsz], int want)
|
|
{
|
|
memset(req, 0, Ipcreqsz);
|
|
req[0] = Ipcreqreset | (want ? Ipcreqwant : 0);
|
|
}
|
|
|
|
/* An old server sees this reserved frame as a key-zero request. */
|
|
void
|
|
ipcpackcap(unsigned char req[Ipcreqsz], int want)
|
|
{
|
|
memset(req, 0, Ipcreqsz);
|
|
req[0] = Ipcext | (want ? Ipcreqwant : 0);
|
|
req[1] = Ipcversion;
|
|
req[2] = Ipcopcap;
|
|
}
|
|
|
|
void
|
|
ipcpackcaret(unsigned char req[Ipccaretsz], int valid, int32_t x,
|
|
int32_t y, int32_t h)
|
|
{
|
|
memset(req, 0, Ipccaretsz);
|
|
req[0] = Ipcext;
|
|
req[1] = Ipcversion;
|
|
req[2] = Ipcopcaret;
|
|
req[3] = valid != 0;
|
|
if(valid){
|
|
put32(req + 4, x);
|
|
put32(req + 8, y);
|
|
put32(req + 12, h);
|
|
}
|
|
}
|
|
|
|
void
|
|
ipcpacksurround(unsigned char req[Ipcreqsz], size_t n)
|
|
{
|
|
memset(req, 0, Ipcreqsz);
|
|
req[0] = Ipcext;
|
|
req[1] = Ipcversion;
|
|
req[2] = Ipcopsurround;
|
|
putlen(req + 4, n);
|
|
}
|
|
|
|
size_t
|
|
ipcsurroundlen(const unsigned char req[Ipcreqsz])
|
|
{
|
|
return getlen(req + 4);
|
|
}
|
|
|
|
void
|
|
ipcunpackreq(const unsigned char req[Ipcreqsz], int *want, uint32_t *mod,
|
|
uint32_t *key)
|
|
{
|
|
*want = (req[0] & Ipcreqwant) != 0;
|
|
*mod = req[1] & Mmask;
|
|
*key = (uint32_t)req[2] |
|
|
((uint32_t)req[3] << 8) |
|
|
((uint32_t)req[4] << 16) |
|
|
((uint32_t)req[5] << 24);
|
|
}
|
|
|
|
int
|
|
ipcreqtype(const unsigned char req[Ipcreqsz])
|
|
{
|
|
if((req[0] & Ipcext) == 0)
|
|
return Ipckey;
|
|
if(req[1] != Ipcversion)
|
|
return Ipcunknown;
|
|
switch(req[2]){
|
|
case Ipcopcap:
|
|
if((req[0] & ~(Ipcext|Ipcreqwant)) != 0 ||
|
|
req[3] != 0 || req[4] != 0 || req[5] != 0)
|
|
return Ipcunknown;
|
|
return Ipccap;
|
|
case Ipcopcaret:
|
|
if(req[0] != Ipcext || req[3] > 1)
|
|
return Ipcunknown;
|
|
return Ipccaret;
|
|
case Ipcopsurround:
|
|
if(req[0] != Ipcext || req[3] != 0 ||
|
|
getlen(req + 4) > Ipcfieldmax)
|
|
return Ipcunknown;
|
|
return Ipcsurround;
|
|
}
|
|
return Ipcunknown;
|
|
}
|
|
|
|
int
|
|
ipcunpackcaret(const unsigned char req[Ipccaretsz], int *valid,
|
|
int32_t *x, int32_t *y, int32_t *h)
|
|
{
|
|
if(ipcreqtype(req) != Ipccaret || get32(req + 12) < 0)
|
|
return -1;
|
|
*valid = req[3];
|
|
*x = *valid ? get32(req + 4) : 0;
|
|
*y = *valid ? get32(req + 8) : 0;
|
|
*h = *valid ? get32(req + 12) : 0;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
ipcreqreset(const unsigned char req[Ipcreqsz])
|
|
{
|
|
return (req[0] & Ipcreqreset) != 0;
|
|
}
|
|
|
|
int
|
|
ipcpackresp(unsigned char *dst, size_t cap, int eaten, int del,
|
|
const char *commit, size_t ncommit, const char *preedit, size_t npreedit,
|
|
int want)
|
|
{
|
|
size_t n;
|
|
|
|
if(ncommit > Ipcfieldmax || npreedit > Ipcfieldmax ||
|
|
del < 0 || del > 0xff)
|
|
return -1;
|
|
n = Ipcresphdrsz + ncommit + (want ? Ipclensz + npreedit : 0);
|
|
if(cap < n)
|
|
return -1;
|
|
dst[0] = eaten != 0;
|
|
dst[1] = del;
|
|
putlen(dst + 2, ncommit);
|
|
memcpy(dst + Ipcresphdrsz, commit, ncommit);
|
|
if(want){
|
|
putlen(dst + Ipcresphdrsz + ncommit, npreedit);
|
|
memcpy(dst + Ipcresphdrsz + ncommit + Ipclensz, preedit, npreedit);
|
|
}
|
|
return n;
|
|
}
|
|
|
|
int
|
|
ipcreadn(int fd, void *buf, size_t n)
|
|
{
|
|
unsigned char *p;
|
|
ssize_t r;
|
|
|
|
p = buf;
|
|
while(n > 0){
|
|
r = read(fd, p, n);
|
|
if(r < 0 && errno == EINTR)
|
|
continue;
|
|
if(r <= 0)
|
|
return -1;
|
|
p += r;
|
|
n -= r;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
ipcsend(int fd, const void *buf, size_t n)
|
|
{
|
|
const unsigned char *p;
|
|
ssize_t r;
|
|
int64_t until;
|
|
|
|
p = buf;
|
|
until = deadline();
|
|
while(n > 0){
|
|
r = send(fd, p, n, MSG_NOSIGNAL|MSG_DONTWAIT);
|
|
if(r < 0 && errno == EINTR)
|
|
continue;
|
|
if(r < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)){
|
|
if(waitfd(fd, POLLOUT, until) < 0)
|
|
return -1;
|
|
continue;
|
|
}
|
|
if(r < 0)
|
|
return -1;
|
|
if(r == 0){
|
|
errno = EPIPE;
|
|
return -1;
|
|
}
|
|
p += r;
|
|
n -= r;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Reads a length-prefixed field into dst[Ipcfieldmax+1]. */
|
|
static int
|
|
readfield(int fd, char *dst, int64_t until)
|
|
{
|
|
unsigned char len[Ipclensz];
|
|
size_t n;
|
|
|
|
if(readwait(fd, len, sizeof len, until) < 0)
|
|
return -1;
|
|
n = getlen(len);
|
|
if(n > Ipcfieldmax){
|
|
errno = EPROTO;
|
|
return -1;
|
|
}
|
|
if(readwait(fd, dst, n, until) < 0)
|
|
return -1;
|
|
dst[n] = '\0';
|
|
return n;
|
|
}
|
|
|
|
int
|
|
ipcreadresp(int fd, int want, char *commit, char *preedit, Ipcresp *resp)
|
|
{
|
|
unsigned char head[2];
|
|
int64_t until;
|
|
int n;
|
|
|
|
commit[0] = '\0';
|
|
preedit[0] = '\0';
|
|
memset(resp, 0, sizeof *resp);
|
|
until = deadline();
|
|
if(readwait(fd, head, sizeof head, until) < 0)
|
|
return -1;
|
|
if(head[0] > 1){
|
|
errno = EPROTO;
|
|
return -1;
|
|
}
|
|
resp->eaten = head[0];
|
|
resp->del = head[1];
|
|
n = readfield(fd, commit, until);
|
|
if(n < 0)
|
|
return -1;
|
|
resp->commitlen = n;
|
|
if(!want)
|
|
return 0;
|
|
n = readfield(fd, preedit, until);
|
|
if(n < 0)
|
|
return -1;
|
|
resp->preeditlen = n;
|
|
return 0;
|
|
}
|