engine: route popup preedit by client capability

This commit is contained in:
2026-08-14 17:02:46 +09:00
parent c1b1b7016e
commit 2376035524
10 changed files with 81 additions and 28 deletions

View File

@@ -15,7 +15,6 @@ Vietnamese Telex is also available as a compatibility mode.
| `Ctrl+V` | Vietnamese Telex |
| `Ctrl+E` | Emoji and symbol search |
| `Ctrl+H` | One-shot Hanja search |
| `Ctrl+P` | Toggle popup preedit |
Use `Up`/`Down` to move through candidates, `Enter` or `1`-`9` to select,
`Tab` to cycle temporary search results, and `Esc` to cancel. `0` commits the

5
dat.h
View File

@@ -138,6 +138,10 @@ struct Drawcmd
typedef struct Keyreq Keyreq;
typedef struct Keyres Keyres;
enum
{
Cclientpreedit = 1<<0,
};
enum
{
Keypress,
Keyreset,
@@ -154,6 +158,7 @@ struct Keyres
struct Keyreq
{
void *owner; /* stable until the context's release is acknowledged */
int cap;
int op;
u32int ks;
u32int mod;

1
ibus.c
View File

@@ -303,6 +303,7 @@ sendrequest(Ictx *ctx, int op, u32int ks, u32int mod, Keyres *res)
memset(&kr, 0, sizeof kr);
kr.owner = ctx;
kr.cap = Cclientpreedit;
kr.op = op;
kr.ks = ks;
kr.mod = mod;

1
srv.c
View File

@@ -41,6 +41,7 @@ clientthread(void *arg)
reply = chancreate(sizeof(Keyres), 0);
kr.reply = reply;
kr.owner = &fd;
kr.cap = Cclientpreedit;
while((want = srvreadkey(fd, &kr)) >= 0){
chansend(keyc, &kr);
chanrecv(reply, &res);

View File

@@ -2,9 +2,9 @@
#include "fn.h"
static Im im;
static int popup = 0;
static int visible = 0;
static void *activeowner;
static int activecap;
static Caret caret;
static void dictqmap(Im*);
static void dictqjp(Im*);
@@ -105,7 +105,7 @@ show(void)
pre = search.text;
else
impre(&pre);
if(popup){
if(!(activecap & Cclientpreedit)){
if(search.lang || isjp(&im) || im.l->map == nil ||
!mapget(im.l->map, &pre, &dc.pre))
dc.pre = pre;
@@ -632,11 +632,6 @@ searchkey(u32int ks, u32int mod, Str *com)
startsearch(n, com);
return 1;
}
if(c == 0x10){
popup = !popup;
show();
return 1;
}
endsearch();
if(setlang(c))
return 1;
@@ -725,11 +720,6 @@ keystroke(u32int ks, u32int mod, Str *com)
startsearch(n, com);
return 1;
}
if(c == 0x10){
popup = !popup;
show();
return 1;
}
if(getlang(c) != nil){
if(isjp(&im) && haspre(&im))
commit(com);
@@ -778,6 +768,7 @@ init(void)
memset(&search, 0, sizeof search);
memset(&caret, 0, sizeof caret);
activeowner = nil;
activecap = 0;
visible = 0;
}
@@ -800,6 +791,7 @@ imhandlekey(Keyreq *kr)
if(kr->owner == activeowner){
reset();
activeowner = nil;
activecap = 0;
}
break;
case Keyreset:
@@ -818,6 +810,7 @@ imhandlekey(Keyreq *kr)
if(kr->owner != activeowner){
reset();
activeowner = kr->owner;
activecap = kr->cap;
caret = kr->caret;
}else if(kr->caret.valid)
caret = kr->caret;

View File

@@ -192,7 +192,8 @@ engine_dictionary_queue_latest_wins(struct ct *t)
}
static Keyres
ownerrequestat(void *owner, int op, Rune key, u32int mod, Caret *caret)
ownerrequestatcap(void *owner, int cap, int op, Rune key, u32int mod,
Caret *caret)
{
Channel *reply;
Keyreq req;
@@ -201,6 +202,7 @@ ownerrequestat(void *owner, int op, Rune key, u32int mod, Caret *caret)
reply = chancreate(sizeof(Keyres), 1);
memset(&req, 0, sizeof req);
req.owner = owner;
req.cap = cap;
req.op = op;
req.ks = key;
req.mod = mod;
@@ -213,12 +215,24 @@ ownerrequestat(void *owner, int op, Rune key, u32int mod, Caret *caret)
return res;
}
static Keyres
ownerrequestat(void *owner, int op, Rune key, u32int mod, Caret *caret)
{
return ownerrequestatcap(owner, 0, op, key, mod, caret);
}
static Keyres
ownerrequest(void *owner, int op, Rune key, u32int mod)
{
return ownerrequestat(owner, op, key, mod, nil);
}
static Keyres
ownerrequestcap(void *owner, int cap, int op, Rune key, u32int mod)
{
return ownerrequestatcap(owner, cap, op, key, mod, nil);
}
void
engine_active_owner_lifecycle(struct ct *t)
{
@@ -566,7 +580,7 @@ struct Searchfix
Search search;
Lang *dictlang;
Hmap *dict;
int popup;
int activecap;
int visible;
};
@@ -601,7 +615,7 @@ searchsave(Searchfix *f, int lang)
f->search = search;
f->dictlang = getlang(lang);
f->dict = f->dictlang->dict;
f->popup = popup;
f->activecap = activecap;
f->visible = visible;
draindraw(nil);
}
@@ -629,7 +643,7 @@ emojibegin(Searchfix *f, int showpre)
"c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 c11 c12");
init();
im.l = getlang(LangKO);
popup = showpre;
activecap = showpre ? 0 : Cclientpreedit;
}
static void
@@ -643,10 +657,54 @@ searchend(Searchfix *f)
draindraw(nil);
im = f->im;
search = f->search;
popup = f->popup;
activecap = f->activecap;
visible = f->visible;
}
void
engine_popup_preedit_capability(struct ct *t)
{
Dictres res;
Drawcmd dc;
char client, popupowner;
init();
im.l = getlang(LangJP);
draindraw(nil);
ownerrequestcap(&client, Cclientpreedit, Keypress, 'k', 0);
ownerrequestcap(&client, Cclientpreedit, Keypress, 'a', 0);
memset(&res, 0, sizeof res);
res.lang = LangJP;
res.key = mkstr("");
res.kouho[0] = mkstr("");
res.nkouho = 1;
dictresult(&res);
if(CT_CHECK(t, draindraw(&dc) > 0)){
CT_EQ_INT(t, 0, dc.pre.n);
CT_EQ_INT(t, 1, dc.nkouho);
checkstr(t, "client-preedit candidate", "", &dc.kouho[0]);
}
ownerrequestcap(&popupowner, 0, Keypress, 'n', 0);
if(CT_CHECK(t, draindraw(&dc) > 0)){
checkstr(t, "popup preedit", "", &dc.pre);
CT_EQ_INT(t, 0, dc.nkouho);
}
memset(&res, 0, sizeof res);
res.lang = LangJP;
res.key = mkstr("");
res.kouho[0] = mkstr("");
res.nkouho = 1;
dictresult(&res);
if(CT_CHECK(t, draindraw(&dc) > 0)){
checkstr(t, "async popup preedit", "", &dc.pre);
CT_EQ_INT(t, 1, dc.nkouho);
checkstr(t, "popup candidate", "", &dc.kouho[0]);
}
ownerrequest(&popupowner, Keyrelease, 0, 0);
draindraw(nil);
}
static void
hanjabegin(Searchfix *f, int lang)
{
@@ -841,10 +899,8 @@ engine_emoji_single_candidate(struct ct *t)
Searchfix f;
Str com;
emojibegin(&f, 1);
emojibegin(&f, 0);
sclear(&com);
CT_CHECK(t, keystroke('p', Mctrl, &com));
CT_EQ_INT(t, 0, popup);
CT_CHECK(t, keystroke('e', Mctrl, &com));
CT_CHECK(t, search.lang);
CT_EQ_INT(t, LangKO, im.l->lang);
@@ -1083,7 +1139,6 @@ engine_emoji_preedit_languages(struct ct *t)
{
Searchfix f;
Str com, shown;
int p;
emojibegin(&f, 0);
init();
@@ -1105,15 +1160,10 @@ engine_emoji_preedit_languages(struct ct *t)
CT_CHECK(t, keystroke('E', Mctrl|Mshift, &com));
CT_EQ_INT(t, LangJP, im.l->lang);
CT_CHECK(t, keystroke('a', 0, &com));
p = popup;
CT_CHECK(t, !keystroke(Kmodfirst+2, 0, &com));
CT_CHECK(t, search.lang);
checkstr(t, "modifier preserves search", "a", &search.text);
CT_CHECK(t, keystroke('P', Mctrl|Mshift, &com));
CT_EQ_INT(t, !p, popup);
CT_CHECK(t, search.lang);
CT_CHECK(t, !keystroke(Kmodfirst+2, 0, &com));
CT_CHECK(t, keystroke('E', Mctrl|Mshift, &com));
CT_CHECK(t, !keystroke('P', Mctrl|Mshift, &com));
CT_CHECK(t, !search.lang);
sclear(&com);
if(typekeys(t, "na", &com)){

View File

@@ -238,6 +238,7 @@ nexttrace(struct ct *t, Ibusfix *f, int op, Ictx *ctx)
return req;
CT_EQ_INT(t, op, req.op);
CT_EQ_PTR(t, ctx, req.owner);
CT_EQ_INT(t, Cclientpreedit, req.cap);
CT_EQ_PTR(t, replyc, req.reply);
return req;
}

View File

@@ -149,6 +149,7 @@ nextrequest(struct ct *t, Enginegate *g, int op)
memset(&req, 0, sizeof req);
chanrecv(g->seen, &req);
CT_EQ_INT(t, op, req.op);
CT_EQ_INT(t, Cclientpreedit, req.cap);
return req;
}

View File

@@ -36,6 +36,7 @@ void engine_dictionary_queue_latest_wins(struct ct*);
void engine_active_owner_lifecycle(struct ct*);
void engine_active_owner_reset(struct ct*);
void engine_active_owner_caret(struct ct*);
void engine_popup_preedit_capability(struct ct*);
void engine_commit_contract(struct ct*);
void engine_language_switch_state(struct ct*);
void engine_telex_history_bound(struct ct*);

View File

@@ -90,6 +90,7 @@ static const struct ct_test tests[] = {
{ "engine/active-owner-lifecycle", engine_active_owner_lifecycle },
{ "engine/active-owner-reset", engine_active_owner_reset },
{ "engine/active-owner-caret", engine_active_owner_caret },
{ "engine/popup-preedit-capability", engine_popup_preedit_capability },
{ "engine/commit-contract", engine_commit_contract },
{ "engine/language-switch-state", engine_language_switch_state },
{ "engine/telex-history-bound", engine_telex_history_bound },