Every key copied the whole Im and Search rune by rune, compared them after the transition, and combined the answer with 'eaten', a commit count that is always zero, and a force flag for caret and owner changes, while redraw() kept a separate 'visible' bit to know when to send an empty popup. redraw() now compares the snapshot with the last one sent and treats two empty popups as equal, so imhandlekey just ends in redraw(). keystroke() was only a test entry point and moves there.
885 lines
14 KiB
C
885 lines
14 KiB
C
#include "dat.h"
|
|
#include "fn.h"
|
|
|
|
static Im im;
|
|
static void *activeowner;
|
|
static int activecap;
|
|
static Caret caret;
|
|
static int candidatechosen;
|
|
static Drawcmd lastdraw;
|
|
static Emit transjp(Im*, Rune);
|
|
static void backjp(Im*);
|
|
static int maplookup(Trie*, Str*, Str*);
|
|
|
|
typedef struct Search Search;
|
|
struct Search
|
|
{
|
|
int lang;
|
|
Str raw;
|
|
Str text;
|
|
};
|
|
static Search search;
|
|
|
|
Lang langs[] = {
|
|
{LangEN, nil, nil, nil, nil, nil, nil},
|
|
{LangJP, "hira", "kanji", transjp, backjp, nil, nil},
|
|
{LangJPK, "kata", nil, transjp, backjp, nil, nil},
|
|
{LangKO, nil, nil, transko, backko, nil, nil},
|
|
{LangHANJA, nil, "hanja", nil, nil, nil, nil},
|
|
{LangEMOJI, nil, "emoji", nil, nil, nil, nil},
|
|
{LangVI, "telex", nil, transvi, backvi, nil, nil},
|
|
};
|
|
int nlang = nelem(langs);
|
|
|
|
static void
|
|
clearkouho(void)
|
|
{
|
|
im.nkouho = 0;
|
|
im.sel = -1;
|
|
candidatechosen = 0;
|
|
}
|
|
|
|
static void
|
|
selectfirst(void)
|
|
{
|
|
im.sel = im.nkouho > 0 ? 0 : -1;
|
|
}
|
|
|
|
static int
|
|
pagefirst(void)
|
|
{
|
|
if(im.sel < 0)
|
|
return 0;
|
|
return im.sel / Maxdisp * Maxdisp;
|
|
}
|
|
|
|
static int
|
|
movedelta(u32int ks)
|
|
{
|
|
static struct {
|
|
u32int key;
|
|
int delta;
|
|
} move[] = {
|
|
{Kup, -1}, {Kdown, 1},
|
|
{Kpgup, -Maxdisp}, {Kpgdown, Maxdisp},
|
|
};
|
|
int i;
|
|
|
|
for(i = 0; i < nelem(move); i++)
|
|
if(move[i].key == ks)
|
|
return move[i].delta;
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
movekouho(int delta)
|
|
{
|
|
if(im.sel < 0)
|
|
im.sel = 0;
|
|
else
|
|
im.sel += delta;
|
|
if(im.sel < 0)
|
|
im.sel = 0;
|
|
if(im.sel >= im.nkouho)
|
|
im.sel = im.nkouho - 1;
|
|
candidatechosen = 1;
|
|
}
|
|
|
|
static int
|
|
numberedkouho(u32int ks, u32int mod)
|
|
{
|
|
int n;
|
|
|
|
if(mod != 0 || ks < '1' || ks > '9')
|
|
return -1;
|
|
n = pagefirst() + ks - '1';
|
|
if(n >= im.nkouho)
|
|
return -1;
|
|
return n;
|
|
}
|
|
|
|
static int
|
|
isjp(Im *p)
|
|
{
|
|
return p->l->lang == LangJP || p->l->lang == LangJPK;
|
|
}
|
|
|
|
/*
|
|
* Japanese keeps completed kana in pre and an ambiguous romaji syllable in
|
|
* raw. A mapped raw suffix is part of the reading even while it remains
|
|
* pending (notably n, which may still grow into nya). Returns whether
|
|
* the reading is complete.
|
|
*/
|
|
static int
|
|
jpreading(Trie *map, Str *pre, Str *raw, Str *s)
|
|
{
|
|
Str mapped;
|
|
|
|
*s = *pre;
|
|
if(raw->n == 0)
|
|
return 1;
|
|
if(mapget(map, raw, &mapped)){
|
|
sappend(s, &mapped);
|
|
return 1;
|
|
}
|
|
sappend(s, raw);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
haspre(Im *p)
|
|
{
|
|
return p->pre.n != 0 || p->raw.n != 0;
|
|
}
|
|
|
|
/* The preedit as clients see it: Telex keys read back through the map. */
|
|
static void
|
|
impre(Str *s)
|
|
{
|
|
if(isjp(&im))
|
|
jpreading(im.l->map, &im.pre, &im.raw, s);
|
|
else if(!mapget(im.l->map, &im.pre, s))
|
|
*s = im.pre;
|
|
}
|
|
|
|
static void
|
|
snapshot(Drawcmd *dc)
|
|
{
|
|
Str pre;
|
|
int i, first, n;
|
|
|
|
memset(dc, 0, sizeof *dc);
|
|
if(search.lang)
|
|
pre = search.text;
|
|
else
|
|
impre(&pre);
|
|
if(!(activecap & Cclientpreedit))
|
|
dc->pre = pre;
|
|
first = pagefirst();
|
|
n = im.nkouho - first;
|
|
if(n > Maxdisp) n = Maxdisp;
|
|
dc->nkouho = n;
|
|
dc->sel = im.sel >= 0 ? im.sel - first : -1;
|
|
dc->first = first;
|
|
dc->total = im.nkouho;
|
|
dc->caret = caret;
|
|
for(i = 0; i < n; i++)
|
|
dc->kouho[i] = im.kouho[first + i];
|
|
}
|
|
|
|
static int
|
|
careteq(Caret *a, Caret *b)
|
|
{
|
|
return a->valid == b->valid && a->x == b->x && a->y == b->y &&
|
|
a->h == b->h;
|
|
}
|
|
|
|
static int
|
|
shown(Drawcmd *dc)
|
|
{
|
|
return dc->pre.n != 0 || dc->nkouho != 0;
|
|
}
|
|
|
|
static int
|
|
samedraw(Drawcmd *a, Drawcmd *b)
|
|
{
|
|
int i;
|
|
|
|
if(!shown(a) && !shown(b))
|
|
return 1;
|
|
if(a->nkouho != b->nkouho || a->sel != b->sel ||
|
|
a->first != b->first || a->total != b->total ||
|
|
scmp(&a->pre, &b->pre) != 0 || !careteq(&a->caret, &b->caret))
|
|
return 0;
|
|
for(i = 0; i < a->nkouho; i++)
|
|
if(scmp(&a->kouho[i], &b->kouho[i]) != 0)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
/* Sends the popup a new picture only when it would look different. */
|
|
static void
|
|
redraw(void)
|
|
{
|
|
Drawcmd dc, junk;
|
|
|
|
snapshot(&dc);
|
|
if(samedraw(&dc, &lastdraw))
|
|
return;
|
|
lastdraw = dc;
|
|
/* imthread is the sole producer; keep only the newest pending draw. */
|
|
while(channbrecv(drawc, &junk) > 0)
|
|
;
|
|
channbsend(drawc, &dc);
|
|
}
|
|
|
|
static void
|
|
reset(void)
|
|
{
|
|
sclear(&im.pre);
|
|
sclear(&im.raw);
|
|
search.lang = 0;
|
|
sclear(&search.raw);
|
|
sclear(&search.text);
|
|
memset(&caret, 0, sizeof caret);
|
|
clearkouho();
|
|
}
|
|
|
|
/* Kanji candidates for a complete reading. */
|
|
static void
|
|
dictqjp(void)
|
|
{
|
|
Str reading;
|
|
|
|
clearkouho();
|
|
if(!jpreading(im.l->map, &im.pre, &im.raw, &reading) || reading.n == 0)
|
|
return;
|
|
im.nkouho = dictlookup(im.l, &reading, im.kouho, Maxkouho);
|
|
selectfirst();
|
|
}
|
|
|
|
static int
|
|
setlang(int c)
|
|
{
|
|
Lang *l;
|
|
|
|
l = getlang(c);
|
|
if(l == nil)
|
|
return 0;
|
|
im.l = l;
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
commitim(Im *p, Str *com)
|
|
{
|
|
Str val;
|
|
|
|
if(isjp(p))
|
|
jpreading(p->l->map, &p->pre, &p->raw, &val);
|
|
else if(!mapget(p->l->map, &p->pre, &val))
|
|
val = p->pre;
|
|
sappend(com, &val);
|
|
sclear(&p->pre);
|
|
sclear(&p->raw);
|
|
}
|
|
|
|
static void
|
|
commit(Str *com)
|
|
{
|
|
commitim(&im, com);
|
|
}
|
|
|
|
static void
|
|
backjp(Im *im)
|
|
{
|
|
if(im->raw.n != 0)
|
|
spopr(&im->raw);
|
|
else
|
|
spopr(&im->pre);
|
|
}
|
|
|
|
static int
|
|
issmalltsu(Str *s)
|
|
{
|
|
return s->n == 1 && (s->r[0] == L'っ' || s->r[0] == L'ッ');
|
|
}
|
|
|
|
static void
|
|
keeptail(Str *s)
|
|
{
|
|
int i;
|
|
|
|
for(i = 1; i < s->n; i++)
|
|
s->r[i-1] = s->r[i];
|
|
if(s->n > 0)
|
|
s->n--;
|
|
}
|
|
|
|
static Emit
|
|
transjp(Im *p, Rune c)
|
|
{
|
|
Emit e;
|
|
Str key, mapped;
|
|
|
|
memset(&e, 0, sizeof e);
|
|
e.eat = 1;
|
|
e.next = p->pre;
|
|
e.raw = p->raw;
|
|
if(c == '\'' && e.raw.n == 1 && e.raw.r[0] == 'n'){
|
|
if(mapget(p->l->map, &e.raw, &mapped))
|
|
sappend(&e.next, &mapped);
|
|
sclear(&e.raw);
|
|
return e;
|
|
}
|
|
key = e.raw;
|
|
sputr(&key, c);
|
|
if(maplookup(p->l->map, &key, &mapped)){
|
|
e.raw = key;
|
|
if(issmalltsu(&mapped)){
|
|
sappend(&e.next, &mapped);
|
|
keeptail(&e.raw);
|
|
}
|
|
return e;
|
|
}
|
|
if(e.raw.n != 0 && mapget(p->l->map, &e.raw, &mapped)){
|
|
sappend(&e.next, &mapped);
|
|
sclear(&e.raw);
|
|
sclear(&key);
|
|
sputr(&key, c);
|
|
if(maplookup(p->l->map, &key, &mapped)){
|
|
e.raw = key;
|
|
return e;
|
|
}
|
|
}
|
|
/* The key starts no syllable: flush the reading and pass it on. */
|
|
jpreading(p->l->map, &e.next, &e.raw, &e.s);
|
|
sclear(&e.next);
|
|
sclear(&e.raw);
|
|
e.eat = 0;
|
|
return e;
|
|
}
|
|
|
|
static int
|
|
dotrans(Rune c, Str *com)
|
|
{
|
|
Emit e;
|
|
|
|
e = im.l->trans(&im, c);
|
|
sappend(com, &e.s);
|
|
im.pre = e.next;
|
|
im.raw = e.raw;
|
|
if(e.eat && isjp(&im))
|
|
dictqjp();
|
|
else
|
|
clearkouho();
|
|
return e.eat;
|
|
}
|
|
|
|
Lang*
|
|
getlang(int lang)
|
|
{
|
|
int i;
|
|
|
|
for(i = 0; i < nelem(langs); i++)
|
|
if(langs[i].lang == lang)
|
|
return &langs[i];
|
|
return nil;
|
|
}
|
|
|
|
static int
|
|
maplookup(Trie *t, Str *key, Str *out)
|
|
{
|
|
char *v;
|
|
int match, vlen;
|
|
|
|
sclear(out);
|
|
if(key->n == 0)
|
|
return 0;
|
|
match = trielookup(t, key, &v, &vlen);
|
|
if(match == TrieMiss)
|
|
return 0;
|
|
if(match == TrieExact && !sinit(out, v, vlen))
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
Emit
|
|
transmap(Im *im, Rune c)
|
|
{
|
|
Emit e = {0};
|
|
Str key, mapped;
|
|
Trie *t;
|
|
|
|
t = im->l->map;
|
|
key = im->pre;
|
|
sputr(&key, c);
|
|
if(maplookup(t, &key, &mapped)){
|
|
e.eat = 1;
|
|
e.next = key;
|
|
return e;
|
|
}
|
|
if(!mapget(t, &im->pre, &e.s))
|
|
e.s = im->pre;
|
|
sclear(&key);
|
|
sputr(&key, c);
|
|
if(!maplookup(t, &key, &mapped))
|
|
return e;
|
|
e.eat = 1;
|
|
sputr(&e.next, c);
|
|
return e;
|
|
}
|
|
|
|
static Rune
|
|
ctrlkey(Rune c)
|
|
{
|
|
if(c >= 'A' && c <= 'Z')
|
|
c += 'a' - 'A';
|
|
if(c >= 'a' && c <= 'z')
|
|
return c - 'a' + 1;
|
|
return c;
|
|
}
|
|
|
|
static Rune
|
|
kokey(Rune c, u32int mod)
|
|
{
|
|
if(c >= 'A' && c <= 'Z')
|
|
c += 'a' - 'A';
|
|
if((mod & Mshift) && c >= 'a' && c <= 'z')
|
|
c -= 'a' - 'A';
|
|
return c;
|
|
}
|
|
|
|
static int
|
|
ismodkey(u32int ks)
|
|
{
|
|
return ks >= Kmodfirst && ks <= Kmodlast;
|
|
}
|
|
|
|
static void
|
|
foldascii(Str *s)
|
|
{
|
|
int i;
|
|
|
|
for(i = 0; i < s->n; i++)
|
|
if(s->r[i] >= 'A' && s->r[i] <= 'Z')
|
|
s->r[i] += 'a' - 'A';
|
|
}
|
|
|
|
/* What typing raw in language l would produce, composition included. */
|
|
static void
|
|
transstr(Lang *l, Str *raw, Str *out)
|
|
{
|
|
Im q;
|
|
Emit e;
|
|
int i;
|
|
|
|
sclear(out);
|
|
if(l->trans == nil){
|
|
*out = *raw;
|
|
return;
|
|
}
|
|
memset(&q, 0, sizeof q);
|
|
q.l = l;
|
|
for(i = 0; i < raw->n; i++){
|
|
e = l->trans(&q, raw->r[i]);
|
|
sappend(out, &e.s);
|
|
q.pre = e.next;
|
|
q.raw = e.raw;
|
|
if(!e.eat)
|
|
sputr(out, raw->r[i]);
|
|
}
|
|
commitim(&q, out);
|
|
}
|
|
|
|
static void
|
|
addkouho(Str *s)
|
|
{
|
|
int i;
|
|
|
|
for(i = 0; i < im.nkouho; i++)
|
|
if(scmp(&im.kouho[i], s) == 0)
|
|
return;
|
|
if(im.nkouho < Maxkouho)
|
|
im.kouho[im.nkouho++] = *s;
|
|
}
|
|
|
|
/* The typed keys and their transliteration are both emoji queries. */
|
|
static void
|
|
emojiquery(void)
|
|
{
|
|
Str kouho[Maxkouho], raw, local;
|
|
int i, n, rawhit, localhit;
|
|
|
|
raw = search.raw;
|
|
foldascii(&raw);
|
|
transstr(im.l, &search.raw, &local);
|
|
foldascii(&local);
|
|
clearkouho();
|
|
n = dictlookup(getlang(LangEMOJI), &raw, kouho, Maxkouho);
|
|
rawhit = n != 0;
|
|
for(i = 0; i < n; i++)
|
|
addkouho(&kouho[i]);
|
|
localhit = 0;
|
|
if(scmp(&raw, &local) != 0){
|
|
n = dictlookup(getlang(LangEMOJI), &local, kouho, Maxkouho);
|
|
localhit = n != 0;
|
|
for(i = 0; i < n; i++)
|
|
addkouho(&kouho[i]);
|
|
}
|
|
search.text = rawhit || !localhit ? raw : local;
|
|
selectfirst();
|
|
}
|
|
|
|
static void
|
|
hanjaquery(void)
|
|
{
|
|
Str key;
|
|
|
|
transstr(im.l, &search.raw, &key);
|
|
search.text = key;
|
|
clearkouho();
|
|
im.nkouho = dictlookup(getlang(LangHANJA), &key, im.kouho, Maxkouho);
|
|
selectfirst();
|
|
}
|
|
|
|
static void
|
|
searchquery(void)
|
|
{
|
|
if(search.lang == LangEMOJI)
|
|
emojiquery();
|
|
else if(search.lang == LangHANJA)
|
|
hanjaquery();
|
|
}
|
|
|
|
static int
|
|
searchlang(Rune c, u32int mod)
|
|
{
|
|
if(mod & (Malt|Msuper))
|
|
return 0;
|
|
if(c == LangEMOJI || c == LangHANJA)
|
|
return c;
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
endsearch(void)
|
|
{
|
|
search.lang = 0;
|
|
sclear(&search.raw);
|
|
sclear(&search.text);
|
|
clearkouho();
|
|
}
|
|
|
|
static void
|
|
startsearch(int lang, Str *com)
|
|
{
|
|
if(haspre(&im))
|
|
commit(com);
|
|
sclear(&im.pre);
|
|
sclear(&im.raw);
|
|
clearkouho();
|
|
search.lang = lang;
|
|
sclear(&search.raw);
|
|
sclear(&search.text);
|
|
}
|
|
|
|
static void
|
|
commitsearch(Str *com)
|
|
{
|
|
sappend(com, &search.text);
|
|
endsearch();
|
|
}
|
|
|
|
static int
|
|
picksearch(int n, Str *com)
|
|
{
|
|
if(n < 0 || n >= im.nkouho)
|
|
return 0;
|
|
sappend(com, &im.kouho[n]);
|
|
endsearch();
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
searchkey(u32int ks, u32int mod, Str *com)
|
|
{
|
|
int n;
|
|
Rune c;
|
|
|
|
if(ismodkey(ks))
|
|
return 0;
|
|
n = movedelta(ks);
|
|
if(n != 0){
|
|
if(im.nkouho == 0)
|
|
return 1;
|
|
movekouho(n);
|
|
return 1;
|
|
}
|
|
n = numberedkouho(ks, mod);
|
|
if(n >= 0){
|
|
picksearch(n, com);
|
|
return 1;
|
|
}
|
|
if(ks == Ktab){
|
|
if(im.nkouho == 0)
|
|
return 1;
|
|
if(mod & Mshift){
|
|
if(im.sel <= 0)
|
|
im.sel = im.nkouho - 1;
|
|
else
|
|
im.sel--;
|
|
}else if(im.sel >= im.nkouho - 1)
|
|
im.sel = 0;
|
|
else
|
|
im.sel++;
|
|
return 1;
|
|
}
|
|
if(ks == Kret){
|
|
if(im.nkouho > 0)
|
|
return picksearch(im.sel >= 0 ? im.sel : 0, com);
|
|
commitsearch(com);
|
|
return 1;
|
|
}
|
|
if(ks == Kback){
|
|
if(search.raw.n == 0){
|
|
endsearch();
|
|
return 0;
|
|
}
|
|
spopr(&search.raw);
|
|
searchquery();
|
|
return 1;
|
|
}
|
|
if(ks == Kesc){
|
|
endsearch();
|
|
return 1;
|
|
}
|
|
if(ks >= Kspec || (ks < ' ' && !(mod & Mctrl)) ||
|
|
(mod & (Malt|Msuper))){
|
|
commitsearch(com);
|
|
return 0;
|
|
}
|
|
if(mod & Mctrl){
|
|
c = ctrlkey(ks);
|
|
n = searchlang(c, mod);
|
|
if(n != 0){
|
|
if(n == search.lang)
|
|
endsearch();
|
|
else
|
|
startsearch(n, com);
|
|
return 1;
|
|
}
|
|
endsearch();
|
|
if(setlang(c))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
if(search.raw.n >= Maxrunes){
|
|
commitsearch(com);
|
|
return 0;
|
|
}
|
|
if(im.l->lang == LangKO)
|
|
ks = kokey(ks, mod);
|
|
sputr(&search.raw, ks);
|
|
searchquery();
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
transition(u32int ks, u32int mod, Str *com)
|
|
{
|
|
int n;
|
|
Rune c;
|
|
|
|
if(ismodkey(ks))
|
|
return 0;
|
|
if(search.lang)
|
|
return searchkey(ks, mod, com);
|
|
n = movedelta(ks);
|
|
if(n != 0){
|
|
if(im.nkouho == 0)
|
|
return 0;
|
|
movekouho(n);
|
|
return 1;
|
|
}
|
|
n = numberedkouho(ks, mod);
|
|
if(n >= 0){
|
|
sappend(com, &im.kouho[n]);
|
|
reset();
|
|
return 1;
|
|
}
|
|
if(ks == Ktab || ks == Kret){
|
|
if(candidatechosen && im.sel >= 0 && im.sel < im.nkouho){
|
|
sappend(com, &im.kouho[im.sel]);
|
|
reset();
|
|
return 1;
|
|
}
|
|
if(haspre(&im)){
|
|
commit(com);
|
|
reset();
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
if(ks == Kback){
|
|
if(!haspre(&im))
|
|
return 0;
|
|
im.l->back(&im);
|
|
if(!haspre(&im)){
|
|
reset();
|
|
return 1;
|
|
}
|
|
if(isjp(&im))
|
|
dictqjp();
|
|
else
|
|
clearkouho();
|
|
return 1;
|
|
}
|
|
if(ks == Kesc){
|
|
if(!haspre(&im))
|
|
return 0;
|
|
reset();
|
|
return 1;
|
|
}
|
|
if(ks >= Kspec || (mod & (Malt|Msuper))){
|
|
commit(com);
|
|
reset();
|
|
return 0;
|
|
}
|
|
if(mod & Mctrl){
|
|
c = ctrlkey(ks);
|
|
n = searchlang(c, mod);
|
|
if(n != 0){
|
|
startsearch(n, com);
|
|
return 1;
|
|
}
|
|
if(getlang(c) != nil){
|
|
if(haspre(&im))
|
|
commit(com);
|
|
reset();
|
|
setlang(c);
|
|
return 1;
|
|
}
|
|
if(haspre(&im))
|
|
commit(com);
|
|
reset();
|
|
return 0;
|
|
}
|
|
if(ks == '0' && im.nkouho > 0){
|
|
commit(com);
|
|
reset();
|
|
return 1;
|
|
}
|
|
if(ks > 0x7f || ks == ' '){
|
|
if(!haspre(&im))
|
|
return 0;
|
|
commit(com);
|
|
reset();
|
|
if(com->n >= Maxrunes)
|
|
return 0;
|
|
sputr(com, ks);
|
|
return 1;
|
|
}
|
|
if(im.l->trans == nil)
|
|
return 0;
|
|
if(im.pre.n >= Maxrunes ||
|
|
(isjp(&im) && im.pre.n + im.raw.n >= Maxrunes)){
|
|
commit(com);
|
|
reset();
|
|
return 0;
|
|
}
|
|
if(im.l->lang == LangKO)
|
|
ks = kokey(ks, mod);
|
|
n = dotrans(ks, com);
|
|
return n;
|
|
}
|
|
|
|
static void
|
|
init(void)
|
|
{
|
|
memset(&im, 0, sizeof(im));
|
|
im.l = getlang(LangEN);
|
|
im.sel = -1;
|
|
memset(&search, 0, sizeof search);
|
|
memset(&caret, 0, sizeof caret);
|
|
activeowner = nil;
|
|
activecap = 0;
|
|
candidatechosen = 0;
|
|
memset(&lastdraw, 0, sizeof lastdraw);
|
|
}
|
|
|
|
static int
|
|
meaningful(Keyreq *kr)
|
|
{
|
|
return kr->ks != 0 && !ismodkey(kr->ks);
|
|
}
|
|
|
|
/*
|
|
* The engine belongs to whichever context last typed a real key; only
|
|
* the owner's requests change state. Every request ends in redraw(),
|
|
* which sends the popup a picture only if something visible changed.
|
|
*/
|
|
static void
|
|
imhandlekey(Keyreq *kr)
|
|
{
|
|
Keyres res;
|
|
void *oldowner;
|
|
|
|
oldowner = activeowner;
|
|
sclear(&res.commit);
|
|
sclear(&res.preedit);
|
|
res.eaten = 1;
|
|
switch(kr->op){
|
|
case Keyrelease:
|
|
if(kr->owner == activeowner){
|
|
reset();
|
|
activeowner = nil;
|
|
activecap = 0;
|
|
}
|
|
break;
|
|
case Keyreset:
|
|
if(kr->owner == activeowner)
|
|
reset();
|
|
break;
|
|
case Keycaret:
|
|
if(kr->owner == activeowner)
|
|
caret = kr->caret;
|
|
break;
|
|
case Keycap:
|
|
res.eaten = activeowner != nil && kr->owner == activeowner;
|
|
if(res.eaten)
|
|
activecap = kr->cap & Cclientpreedit;
|
|
break;
|
|
case Keypress:
|
|
res.eaten = 0;
|
|
if(activeowner != nil && kr->owner == activeowner)
|
|
activecap = kr->cap & Cclientpreedit;
|
|
if(meaningful(kr)){
|
|
if(kr->owner != activeowner){
|
|
reset();
|
|
activeowner = kr->owner;
|
|
activecap = kr->cap & Cclientpreedit;
|
|
caret = kr->caret;
|
|
}else if(kr->caret.valid)
|
|
caret = kr->caret;
|
|
res.eaten = transition(kr->ks, kr->mod, &res.commit);
|
|
}
|
|
break;
|
|
}
|
|
redraw();
|
|
if(activeowner != oldowner)
|
|
ximownernotify();
|
|
if(kr->owner == activeowner){
|
|
if(search.lang)
|
|
res.preedit = search.text;
|
|
else
|
|
impre(&res.preedit);
|
|
}
|
|
chansend(kr->reply, &res);
|
|
}
|
|
|
|
void
|
|
imthread(void*)
|
|
{
|
|
Keyreq kr;
|
|
|
|
threadsetname("im");
|
|
init();
|
|
for(;;){
|
|
chanrecv(keyc, &kr);
|
|
imhandlekey(&kr);
|
|
}
|
|
}
|
|
|
|
int
|
|
mapget(Trie *t, Str *key, Str *out)
|
|
{
|
|
char *v;
|
|
int vlen;
|
|
|
|
sclear(out);
|
|
if(key->n == 0 || trielookup(t, key, &v, &vlen) != TrieExact)
|
|
return 0;
|
|
return sinit(out, v, vlen);
|
|
}
|