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>
This commit is contained in:
2026-08-18 14:31:21 +09:00
parent f7277f54a3
commit 910bf51347
11 changed files with 286 additions and 41 deletions

21
ipc.h
View File

@@ -8,9 +8,11 @@
* The server identifies the connection as the engine owner; that identity is
* not sent on the wire.
* Only Mmask modifier bits are retained.
* Response: [eaten, commit-length-low, commit-length-high, commit...],
* followed, when requested, by
* Response: [eaten, take-back, commit-length-low, commit-length-high,
* commit...], followed, when requested, by
* [preedit-length-low, preedit-length-high, preedit...].
* Take-back is the count of runes of the client's own text, just before the
* cursor, to remove before inserting the commit.
* Lengths are little-endian byte counts; fields are at most Ipcfieldmax bytes,
* so callers read them into char[Ipcfieldmax+1].
*
@@ -19,6 +21,10 @@
* frames are understood.
* Caret control is sixteen bytes: [0x80, version, 1, valid, x, y, h],
* with the signed coordinates and height encoded as little-endian 32-bit words.
* Surrounding-text control is six bytes, [0x80, version, 2, 0, length-low,
* length-high], and then that many bytes of the text just before the cursor.
* It changes what rides along with the requests that follow and is not
* answered.
*/
enum
@@ -26,9 +32,10 @@ enum
Ipcreqwant = 1<<0,
Ipcreqreset = 1<<1,
Ipcext = 1<<7,
Ipcversion = 1,
Ipcversion = 2,
Ipcopcap = 0,
Ipcopcaret = 1,
Ipcopsurround = 2,
Kspec = 0x110000,
Kback = Kspec|0x08,
@@ -57,7 +64,7 @@ enum
Ipcreqsz = 6,
Ipccaretsz = 16,
Ipclensz = 2,
Ipcresphdrsz = 1 + Ipclensz,
Ipcresphdrsz = 2 + Ipclensz,
Ipcfieldmax = 256,
Ipcmaxresp = Ipcresphdrsz + Ipcfieldmax + Ipclensz + Ipcfieldmax,
Ipcwaitms = 250,
@@ -69,12 +76,14 @@ enum
Ipckey,
Ipccap,
Ipccaret,
Ipcsurround,
};
typedef struct Ipcresp Ipcresp;
struct Ipcresp
{
int eaten;
int del; /* runes of the client's own text to take back first */
/* Bytes copied to each caller buffer, excluding its trailing NUL. */
size_t commitlen;
size_t preeditlen;
@@ -86,11 +95,13 @@ void ipcpackreq(unsigned char[Ipcreqsz], int, uint32_t, uint32_t);
void ipcpackreset(unsigned char[Ipcreqsz], int);
void ipcpackcap(unsigned char[Ipcreqsz], int);
void ipcpackcaret(unsigned char[Ipccaretsz], int, int32_t, int32_t, int32_t);
void ipcpacksurround(unsigned char[Ipcreqsz], size_t);
size_t ipcsurroundlen(const unsigned char[Ipcreqsz]);
void ipcunpackreq(const unsigned char[Ipcreqsz], int*, uint32_t*, uint32_t*);
int ipcunpackcaret(const unsigned char[Ipccaretsz], int*, int32_t*, int32_t*, int32_t*);
int ipcreqtype(const unsigned char[Ipcreqsz]);
int ipcreqreset(const unsigned char[Ipcreqsz]);
int ipcpackresp(unsigned char*, size_t, int, const char*, size_t, const char*, size_t, int);
int ipcpackresp(unsigned char*, size_t, int, int, const char*, size_t, const char*, size_t, int);
int ipcreadn(int, void*, size_t);
int ipcsend(int, const void*, size_t);
int ipcreadresp(int, int, char*, char*, Ipcresp*);