Files
strans/ipc.c
Hojun-Cho 33023d7f51 ipc: a modifier keysym is no key
Shift, Control, and their kin were mapped to special keys that the
engine then had a range and a check to ignore, and the GTK module made a
round trip to the daemon for each press.  ipckeysym maps them to key 0,
which was already the "no key" every frontend and the engine skip;
Kmodfirst, Kmodlast, and ismodkey go.
2026-08-17 10:56:03 +09:00

428 lines
7.6 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
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;
}
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,
const char *commit, size_t ncommit, const char *preedit, size_t npreedit,
int want)
{
size_t n;
if(ncommit > Ipcfieldmax || npreedit > Ipcfieldmax)
return -1;
n = Ipcresphdrsz + ncommit + (want ? Ipclensz + npreedit : 0);
if(cap < n)
return -1;
dst[0] = eaten != 0;
putlen(dst + 1, 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 eaten;
int64_t until;
int n;
commit[0] = '\0';
preedit[0] = '\0';
memset(resp, 0, sizeof *resp);
until = deadline();
if(readwait(fd, &eaten, 1, until) < 0)
return -1;
if(eaten > 1){
errno = EPROTO;
return -1;
}
resp->eaten = eaten;
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;
}