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+V` | Vietnamese Telex |
| `Ctrl+E` | Emoji and symbol search | | `Ctrl+E` | Emoji and symbol search |
| `Ctrl+H` | One-shot Hanja 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, 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 `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 Keyreq Keyreq;
typedef struct Keyres Keyres; typedef struct Keyres Keyres;
enum enum
{
Cclientpreedit = 1<<0,
};
enum
{ {
Keypress, Keypress,
Keyreset, Keyreset,
@@ -154,6 +158,7 @@ struct Keyres
struct Keyreq struct Keyreq
{ {
void *owner; /* stable until the context's release is acknowledged */ void *owner; /* stable until the context's release is acknowledged */
int cap;
int op; int op;
u32int ks; u32int ks;
u32int mod; 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); memset(&kr, 0, sizeof kr);
kr.owner = ctx; kr.owner = ctx;
kr.cap = Cclientpreedit;
kr.op = op; kr.op = op;
kr.ks = ks; kr.ks = ks;
kr.mod = mod; kr.mod = mod;

1
srv.c
View File

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

View File

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

View File

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

View File

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

View File

@@ -149,6 +149,7 @@ nextrequest(struct ct *t, Enginegate *g, int op)
memset(&req, 0, sizeof req); memset(&req, 0, sizeof req);
chanrecv(g->seen, &req); chanrecv(g->seen, &req);
CT_EQ_INT(t, op, req.op); CT_EQ_INT(t, op, req.op);
CT_EQ_INT(t, Cclientpreedit, req.cap);
return req; 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_lifecycle(struct ct*);
void engine_active_owner_reset(struct ct*); void engine_active_owner_reset(struct ct*);
void engine_active_owner_caret(struct ct*); void engine_active_owner_caret(struct ct*);
void engine_popup_preedit_capability(struct ct*);
void engine_commit_contract(struct ct*); void engine_commit_contract(struct ct*);
void engine_language_switch_state(struct ct*); void engine_language_switch_state(struct ct*);
void engine_telex_history_bound(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-lifecycle", engine_active_owner_lifecycle },
{ "engine/active-owner-reset", engine_active_owner_reset }, { "engine/active-owner-reset", engine_active_owner_reset },
{ "engine/active-owner-caret", engine_active_owner_caret }, { "engine/active-owner-caret", engine_active_owner_caret },
{ "engine/popup-preedit-capability", engine_popup_preedit_capability },
{ "engine/commit-contract", engine_commit_contract }, { "engine/commit-contract", engine_commit_contract },
{ "engine/language-switch-state", engine_language_switch_state }, { "engine/language-switch-state", engine_language_switch_state },
{ "engine/telex-history-bound", engine_telex_history_bound }, { "engine/telex-history-bound", engine_telex_history_bound },