ref: a8da46a1f2dd2580c50562ce42754185b95c31e8
dir: /sys/src/cmd/ktrans/main.c/
#include <u.h>
#include <libc.h>
#include <ctype.h>
#include <bio.h>
#include <plumb.h>
#include <thread.h>
#include <draw.h>
#include <mouse.h>
#include <keyboard.h>
#include "hash.h"
char*
pushutf(char *dst, char *e, char *u, int nrune)
{
Rune r;
char *p;
char *d;
if(dst >= e)
return dst;
d = dst;
p = u;
while(d < e-1){
if(isascii(*p)){
if((*d = *p) == '\0')
return d;
p++;
d++;
} else {
p += chartorune(&r, p);
if(r == Runeerror){
*d = '\0';
return d;
}
d += runetochar(d, &r);
}
if(nrune > 0 && --nrune == 0)
break;
}
if(d > e-1)
d = e-1;
*d = '\0';
return d;
}
char*
peekstr(char *s, char *b)
{
while(s > b && (*--s & 0xC0)==Runesync)
;
return s;
}
typedef struct Str Str;
struct Str {
char b[128];
char *p;
};
#define strend(s) ((s)->b + sizeof (s)->b)
void
resetstr(Str *s, ...)
{
va_list args;
va_start(args, s);
do {
s->p = s->b;
s->p[0] = '\0';
s = va_arg(args, Str*);
} while(s != nil);
va_end(args);
}
void
popstr(Str *s)
{
s->p = peekstr(s->p, s->b);
s->p[0] = '\0';
}
typedef struct Map Map;
struct Map {
char *roma;
char *kana;
char leadstomore;
};
Hmap*
openmap(char *file)
{
Biobuf *b;
char *s;
Map map;
Hmap *h;
char *key, *val;
Str partial;
Rune r;
h = hmapalloc(64, sizeof(Map));
b = Bopen(file, OREAD);
if(b == nil)
return nil;
while(key = Brdstr(b, '\n', 1)){
if(key[0] == '\0'){
Err:
free(key);
continue;
}
val = strchr(key, '\t');
if(val == nil || val[1] == '\0')
goto Err;
*val = '\0';
val++;
resetstr(&partial, nil);
for(s = key; *s; s += chartorune(&r, s)){
partial.p = pushutf(partial.p, strend(&partial), s, 1);
map.leadstomore = 0;
if(hmapget(h, partial.b, &map) == 0){
if(map.leadstomore == 1 && s[1] == '\0')
map.leadstomore = 1;
}
if(s[1] == '\0'){
map.roma = key;
map.kana = val;
hmaprepl(&h, strdup(map.roma), &map, nil, 1);
} else {
map.roma = strdup(partial.b);
map.leadstomore = 1;
map.kana = nil;
hmaprepl(&h, strdup(partial.b), &map, nil, 1);
}
}
}
Bterm(b);
return h;
}
enum{
Maxkouho=32,
};
Hmap*
opendict(Hmap *h, char *name)
{
Biobuf *b;
char *p;
char *dot, *rest;
char *kouho[Maxkouho];
int i;
b = Bopen(name, OREAD);
if(b == nil)
return nil;
if(h == nil)
h = hmapalloc(8192, sizeof(kouho));
while(p = Brdstr(b, '\n', 1)){
if(p[0] == '\0' || p[0] == ';'){
Err:
free(p);
continue;
}
dot = strchr(p, '\t');
if(dot == nil)
goto Err;
*dot = '\0';
rest = dot+1;
if(*rest == '\0')
goto Err;
memset(kouho, 0, sizeof kouho);
i = 0;
while(i < nelem(kouho)-1 && (dot = utfrune(rest, ' '))){
*dot = '\0';
kouho[i++] = rest;
rest = dot+1;
}
if(i < nelem(kouho)-1)
kouho[i] = rest;
/* key is the base pointer; overwrites clean up for us */
hmaprepl(&h, p, kouho, nil, 1);
}
Bterm(b);
return h;
}
enum{
LangEN = '', // ^t
LangJP = '', // ^n
LangJPK = '', // ^k
LangKO = '', // ^s
LangZH = '', // ^c
LangVN = '', // ^v
};
int deflang;
Hmap *natural;
Hmap *hira, *kata, *jisho;
Hmap *hangul;
Hmap *judou, *zidian;
Hmap *telex;
Hmap **langtab[] = {
[LangEN] &natural,
[LangJP] &hira,
[LangJPK] &kata,
[LangKO] &hangul,
[LangZH] &judou,
[LangVN] &telex,
};
char *langcodetab[] = {
[LangEN] "en",
[LangJP] "jp",
[LangJPK] "jpk",
[LangKO] "ko",
[LangZH] "zh",
[LangVN] "vn",
};
int
parselang(char *s)
{
int i;
for(i = 0; i < nelem(langcodetab); i++){
if(langcodetab[i] == nil)
continue;
if(strcmp(langcodetab[i], s) == 0)
return i;
}
return -1;
}
int
checklang(int *dst, int c)
{
Hmap **p;
if(c >= nelem(langtab))
return 0;
p = langtab[c];
if(p == nil)
return 0;
*dst = c;
return c;
}
int
maplkup(int lang, char *s, Map *m)
{
Hmap **h;
if(lang >= nelem(langtab))
return -1;
h = langtab[lang];
if(h == nil || *h == nil)
return -1;
return hmapget(*h, s, m);
}
enum { Msgsize = 256 };
static Channel *dictch;
static Channel *output;
static Channel *input;
static char backspace[Msgsize];
static Channel *displaych;
static Channel *selectch;
static void
displaythread(void*)
{
Mousectl *mctl;
Mouse m;
Keyboardctl *kctl;
Rune key;
char *kouho[Maxkouho], **s, **e;
int i, page, total, height, round;
Image *back, *text, *board, *high, *scroll;
Font *f;
Point p;
Rectangle r, exitr, selr, scrlr;
int selected;
char *mp, move[Msgsize];
enum { Adisp, Aresize, Amouse, Asel, Akbd, Amove, Aend };
Alt a[] = {
[Adisp] { nil, kouho, CHANRCV },
[Aresize] { nil, nil, CHANRCV },
[Amouse] { nil, &m, CHANRCV },
[Asel] { nil, &selected, CHANRCV },
[Akbd] { nil, &key, CHANRCV },
[Amove] { nil, move, CHANNOP },
[Aend] { nil, nil, CHANEND },
};
if(initdraw(nil, nil, "ktrans") < 0)
sysfatal("failed to initdraw: %r");
mctl = initmouse(nil, screen);
if(mctl == nil)
sysfatal("failed to get mouse: %r");
/*
* For keys coming in to our specific window.
* We've already transliterated these, but should
* consume keys and exit on del to avoid artifacts.
*/
kctl = initkeyboard(nil);
if(kctl == nil)
sysfatal("failed to get keyboard: %r");
memset(kouho, 0, sizeof kouho);
selected = -1;
f = display->defaultfont;
high = allocimagemix(display, DYellowgreen, DWhite);
text = display->black;
back = allocimagemix(display, DPaleyellow, DWhite);
board = allocimagemix(display, DBlack, DWhite);
scroll = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DYellowgreen);
a[Adisp].c = displaych;
a[Aresize].c = mctl->resizec;
a[Amouse].c = mctl->c;
a[Asel].c = selectch;
a[Akbd].c = kctl->c;
a[Amove].c = input;
threadsetname("display");
goto Redraw;
for(;;)
switch(alt(a)){
case Amove:
a[Amove].op = CHANNOP;
break;
case Akbd:
if(key != Kdel)
break;
closedisplay(display);
threadexitsall(nil);
case Amouse:
if(!m.buttons)
break;
if(ptinrect(m.xy, exitr)){
closedisplay(display);
threadexitsall(nil);
}
if(kouho[0] == nil)
break;
if(m.xy.x > scrlr.min.x && m.xy.x < scrlr.max.x){
if(m.xy.y > scrlr.min.y && m.xy.y < scrlr.max.y)
break;
if(m.xy.y < scrlr.min.y)
goto Up;
else
goto Down;
}
if(m.buttons & 7){
m.xy.y -= screen->r.min.y;
m.xy.y -= f->height;
if(m.xy.y < 0)
break;
i = round + m.xy.y/f->height + 1;
if(selected != -1)
i = i - selected - 1;
} else if(m.buttons == 8){
Up:
i = -1 * (selected % height + height);
if(selected + i < 0)
i = -(selected + (total % height));
} else if(m.buttons == 16){
Down:
i = height - (selected % height);
if(selected + i > total)
i = total - selected;
} else
break;
memset(move, 0, sizeof move);
move[0] = 'c';
if(i == 0)
break;
else if(i > 0)
memset(move+1, '', i);
else for(mp = move+1; i < 0; i++)
mp = seprint(mp, move + sizeof move, "%C", Kup);
a[Amove].op = CHANSND;
break;
case Aresize:
getwindow(display, Refnone);
case Adisp:
Redraw:
for(s = kouho, total = 0; *s != nil; s++, total++)
;
r = screen->r;
height = Dy(r)/f->height - 2;
draw(screen, r, back, nil, ZP);
r.max.y = r.min.y + f->height;
draw(screen, r, board, nil, ZP);
round = selected - (selected % height);
if(selected >= 0 && kouho[selected] != nil){
selr = screen->r;
selr.min.y += f->height*(selected-round+1);
selr.max.y = selr.min.y + f->height;
draw(screen, selr, high, nil, ZP);
}
scrlr = screen->r;
scrlr.min.y += f->height;
scrlr.max.y -= f->height;
scrlr.max.x = scrlr.min.x + 10;
draw(screen, scrlr, scroll, nil, ZP);
if(kouho[0] != nil){
scrlr.max.x--;
page = Dy(scrlr) / (total / height + 1);
scrlr.min.y = scrlr.min.y + page*(round / height);
scrlr.max.y = scrlr.min.y + page;
/* fill to the bottom on last page */
if((screen->r.max.y - f->height) - scrlr.max.y < page)
scrlr.max.y = screen->r.max.y - f->height;
draw(screen, scrlr, back, nil, ZP);
}
r.min.x += Dx(r)/2;
p.y = r.min.y;
p.x = r.min.x - stringwidth(f, "候補")/2;
string(screen, p, text, ZP, f, "候補");
p.y += f->height;
for(s = kouho+round, e = kouho+round+height; *s != nil && s < e; s++){
p.x = r.min.x - stringwidth(f, *s)/2;
string(screen, p, text, ZP, f, *s);
p.y += f->height;
}
p.x = r.min.x - stringwidth(f, "終了")/2;
p.y = screen->r.max.y - f->height;
exitr = Rpt(Pt(0, p.y), screen->r.max);
draw(screen, exitr, board, nil, ZP);
string(screen, p, text, ZP, f, "終了");
flushimage(display, 1);
break;
}
}
static int
emitutf(Channel *out, char *u, int nrune)
{
char b[Msgsize];
char *e;
b[0] = 'c';
e = pushutf(b+1, b + Msgsize - 1, u, nrune);
send(out, b);
return e - b;
}
static int compacting = 0;
static void
dictthread(void*)
{
char m[Msgsize];
Rune r;
int n;
char *p;
Hmap *dict;
char *kouho[Maxkouho];
Str line;
Str last;
Str okuri;
int selected, loop;
enum{
Kanji,
Okuri,
Joshi,
};
int mode;
dict = jisho;
selected = -1;
loop = 0;
mode = Kanji;
memset(kouho, 0, sizeof kouho);
resetstr(&last, &line, &okuri, nil);
threadsetname("dict");
while(recv(dictch, m) != -1){
compacting = 1;
for(p = m+1; *p; p += n){
n = chartorune(&r, p);
switch(r){
case LangJP:
dict = jisho;
break;
case LangZH:
dict = zidian;
break;
case '':
if(line.b == line.p){
emitutf(output, "", 1);
break;
}
emitutf(output, backspace, utflen(line.b));
/* fallthrough */
case '': case ' ': case '\n':
mode = Kanji;
resetstr(&line, &okuri, &last, nil);
break;
case '\b':
if(mode != Kanji){
if(okuri.p == okuri.b){
mode = Kanji;
popstr(&line);
}else
popstr(&okuri);
break;
}
popstr(&line);
break;
case Kup:
if(selected == -1){
emitutf(output, p, 1);
break;
}
if(--selected < 0){
//wrap
while(kouho[++selected] != nil)
;
selected--;
}
loop = 1;
goto Select;
case Kdown:
if(selected == -1){
emitutf(output, p, 1);
break;
}
case '':
selected++;
if(selected == 0){
if(hmapget(dict, line.b, kouho) < 0)
break;
if(dict == jisho && line.p > line.b && isascii(line.p[-1]))
line.p[-1] = '\0';
}
if(kouho[selected] == nil){
selected = 0;
loop = 1;
}
Select:
send(selectch, &selected);
send(displaych, kouho);
if(okuri.p != okuri.b)
emitutf(output, backspace, utflen(okuri.b));
if(selected == 0 && !loop)
emitutf(output, backspace, utflen(line.b));
else
emitutf(output, backspace, utflen(last.b));
emitutf(output, kouho[selected], 0);
last.p = pushutf(last.b, strend(&last), kouho[selected], 0);
emitutf(output, okuri.b, 0);
mode = Kanji;
loop = 0;
continue;
case ',': case '.':
case L'。': case L'、':
if(dict == zidian || line.p == line.b){
selected = 0; //hit cleanup below
break;
}
mode = Joshi;
okuri.p = pushutf(okuri.p, strend(&okuri), p, 1);
break;
default:
if(dict == zidian)
goto Line;
if(mode == Joshi)
goto Okuri;
if(isupper(*p)){
if(mode == Okuri){
popstr(&line);
mode = Joshi;
goto Okuri;
}
mode = Okuri;
*p = tolower(*p);
okuri.p = pushutf(okuri.b, strend(&okuri), p, 1);
goto Line;
}
switch(mode){
case Kanji:
Line:
line.p = pushutf(line.p, strend(&line), p, 1);
break;
default:
Okuri:
okuri.p = pushutf(okuri.p, strend(&okuri), p, 1);
break;
}
}
if(selected >= 0){
resetstr(&okuri, &last, &line, nil);
if(dict == zidian)
line.p = pushutf(line.p, strend(&line), p, 1);
selected = -1;
send(selectch, &selected);
}
memset(kouho, 0, sizeof kouho);
hmapget(dict, line.b, kouho);
send(displaych, kouho);
}
compacting = 0;
}
}
static void
telexlkup(Str *line)
{
Map lkup;
char buf[UTFmax*3], *p, *e, *dot;
Str out;
int n, ln;
for(dot = line->b; (ln = utflen(dot)) >= 2; dot += n){
p = pushutf(buf, buf+sizeof buf, dot, 1);
n = p-buf;
if(hmapget(telex, buf, &lkup) < 0)
continue;
e = peekstr(line->p, line->b);
pushutf(p, buf+sizeof buf, e, 1);
if(hmapget(telex, buf, &lkup) < 0)
continue;
out.p = pushutf(out.b, strend(&out), lkup.kana, 0);
out.p = pushutf(out.p, strend(&out), dot+n, 0);
popstr(&out);
emitutf(output, backspace, ln);
emitutf(output, out.b, 0);
line->p = pushutf(line->b, strend(line), out.b, 0);
return;
}
}
static void
dubeolbksp(Str *line)
{
Map lkup;
popstr(line);
/* lookup and emit remaining Jamo to output */
if(hmapget(hangul, line->b, &lkup) < 0)
return;
emitutf(output, lkup.kana, 0);
}
static void
dubeollkup(Str *line)
{
Map lkup;
char buf[UTFmax*2];
char *e2, *e1;
e1 = peekstr(line->p, line->b);
if(e1 != line->b){
e2 = peekstr(e1, line->b);
pushutf(buf, buf+sizeof buf, e2, utflen(e2));
}else
pushutf(buf, buf+sizeof buf, e1, utflen(e1));
if(hmapget(hangul, line->b, &lkup) < 0){
if(hmapget(hangul, buf, &lkup) < 0){
resetstr(line, nil);
line->p = pushutf(line->p, strend(line), e1, utflen(e1));
if(hmapget(hangul, line->b, &lkup) < 0)
return;
}else{
/* treat Jongseong as Choseong when it matches with new Jamo */
popstr(line);
popstr(line);
hmapget(hangul, line->b, &lkup);
emitutf(output, backspace, 2);
emitutf(output, lkup.kana, 0);
hmapget(hangul, buf, &lkup);
emitutf(output, lkup.kana, 0);
line->p = pushutf(line->b, strend(line), buf, utflen(buf));
return;
}
}
if(utflen(line->b) == 1)
emitutf(output, backspace, 1);
else
emitutf(output, backspace, 2);
emitutf(output, lkup.kana, 0);
}
static void
keythread(void*)
{
int lang;
char m[Msgsize];
char *todict;
Map lkup;
char *p;
int n;
Rune r;
char peek[UTFmax+1];
Str line;
peek[0] = lang = deflang;
resetstr(&line, nil);
if(lang == LangJP || lang == LangZH)
emitutf(dictch, peek, 1);
todict = smprint("%C%C", Kup, Kdown);
threadsetname("keytrans");
while(recv(input, m) != -1){
if(m[0] == 'z'){
emitutf(dictch, "", 1);
resetstr(&line, nil);
continue;
}
if(m[0] != 'c'){
send(output, m);
continue;
}
for(p = m+1; *p; p += n){
while(compacting)
yield();
n = chartorune(&r, p);
if(checklang(&lang, r)){
emitutf(dictch, "", 1);
if(lang == LangJP || lang == LangZH)
emitutf(dictch, p, 1);
resetstr(&line, nil);
continue;
}
if(lang == LangEN){
emitutf(output, p, 1);
continue;
}
if(utfrune(todict, r) != nil){
resetstr(&line, nil);
emitutf(dictch, p, 1);
continue;
}
emitutf(output, p, 1);
switch(lang){
case LangZH:
emitutf(dictch, p, 1);
break;
case LangJP:
emitutf(dictch, p, 1);
if(isupper(*p))
*p = tolower(*p);
break;
}
if(utfrune("\n\t ", r) != nil){
resetstr(&line, nil);
continue;
} else if(r == '\b'){
if(lang == LangKO){
dubeolbksp(&line);
continue;
}
popstr(&line);
continue;
}
line.p = pushutf(line.p, strend(&line), p, 1);
if(lang == LangVN){
telexlkup(&line);
continue;
}else if(lang == LangKO){
dubeollkup(&line);
continue;
}
if(maplkup(lang, line.b, &lkup) < 0){
resetstr(&line, nil);
pushutf(peek, peek + sizeof peek, p, 1);
if(maplkup(lang, peek, &lkup) == 0)
line.p = pushutf(line.p, strend(&line), p, 1);
continue;
}
if(lkup.kana == nil)
continue;
if(!lkup.leadstomore)
resetstr(&line, nil);
if(lang == LangJP){
emitutf(dictch, backspace, utflen(lkup.roma));
emitutf(dictch, lkup.kana, 0);
}
emitutf(output, backspace, utflen(lkup.roma));
emitutf(output, lkup.kana, 0);
}
}
}
static int kbdin;
static int kbdout;
static void
kbdtap(void*)
{
char m[Msgsize];
char buf[Msgsize];
char *p;
int n;
threadsetname("kbdtap");
for(;;){
Drop:
n = read(kbdin, buf, sizeof buf);
if(n < 0)
break;
for(p = buf; p < buf+n; p += strlen(p) + 1){
switch(*p){
case 'c': case 'k': case 'K':
case 'z':
break;
default:
goto Drop;
}
strcpy(m, p);
if(send(input, m) == -1)
return;
}
}
threadexitsall(nil);
}
static void
kbdsink(void*)
{
char in[Msgsize];
char out[Msgsize];
char *p;
int n;
Rune rn;
out[0] = 'c';
threadsetname("kbdsink");
while(recv(output, in) != -1){
if(in[0] != 'c'){
if(write(kbdout, in, strlen(in)+1) < 0)
break;
continue;
}
for(p = in+1; *p; p += n){
n = chartorune(&rn, p);
if(rn == Runeerror || rn == '\0')
break;
memmove(out+1, p, n);
out[1+n] = '\0';
if(write(kbdout, out, 1+n+1) < 0)
break;
}
}
}
static int plumbfd;
static void
plumbproc(void*)
{
char m[Msgsize];
Plumbmsg *p;
threadsetname("plumbproc");
for(; p = plumbrecv(plumbfd); plumbfree(p)){
if(p->ndata > sizeof m - 1)
continue;
memmove(m, p->data, p->ndata);
m[p->ndata] = '\0';
m[1] = parselang(m);
if(m[1] == -1)
continue;
m[0] = 'c';
m[2] = '\0';
if(send(input, m) == -1)
break;
}
plumbfree(p);
}
void
usage(void)
{
fprint(2, "usage: %s [ -G ] [ -l lang ] [ kbdtap ]\n", argv0);
threadexits("usage");
}
static char *kdir = "/lib/ktrans";
struct {
char *s;
Hmap **m;
} initmaptab[] = {
"judou", &judou,
"hira", &hira,
"kata", &kata,
"hangul", &hangul,
"telex", &telex,
};
struct {
char *env;
char *def;
Hmap **m;
} initdicttab[] = {
"jisho", "kanji.dict", &jisho,
"zidian", "wubi.dict", &zidian,
};
mainstacksize = 8192*2;
void
threadmain(int argc, char *argv[])
{
int nogui, i;
char buf[128];
char *e, *home;
Hmap *m;
deflang = LangEN;
nogui = 0;
ARGBEGIN{
case 'l':
deflang = parselang(EARGF(usage()));
if(deflang < 0)
usage();
break;
case 'G':
nogui++;
break;
default:
usage();
}ARGEND;
switch(argc){
case 0:
kbdin = 0;
kbdout = 1;
break;
case 1:
kbdin = kbdout = open(argv[0], ORDWR);
if(kbdin < 0)
sysfatal("failed to open kbdtap: %r");
break;
default:
usage();
}
dictch = chancreate(Msgsize, 0);
input = chancreate(Msgsize, 0);
output = chancreate(Msgsize, 0);
/* allow gui to warm up while we're busy reading maps */
if(nogui || access("/dev/winid", AEXIST) < 0){
displaych = nil;
selectch = nil;
} else {
selectch = chancreate(sizeof(int), 0);
displaych = chancreate(sizeof(char*)*Maxkouho, 0);
proccreate(displaythread, nil, mainstacksize);
}
memset(backspace, '\b', sizeof backspace-1);
backspace[sizeof backspace-1] = '\0';
if((home = getenv("home")) == nil)
sysfatal("$home undefined");
for(i = 0; i < nelem(initdicttab); i++){
e = getenv(initdicttab[i].env);
if(e != nil){
snprint(buf, sizeof buf, "%s", e);
free(e);
} else
snprint(buf, sizeof buf, "%s/%s", kdir, initdicttab[i].def);
if((*initdicttab[i].m = opendict(*initdicttab[i].m, buf)) == nil)
sysfatal("failed to open dict: %r");
snprint(buf, sizeof buf, "%s/%s/%s", home, kdir, initdicttab[i].def);
m = opendict(*initdicttab[i].m, buf);
if(m != nil)
*initdicttab[i].m = m;
}
free(home);
natural = nil;
for(i = 0; i < nelem(initmaptab); i++){
snprint(buf, sizeof buf, "%s/%s.map", kdir, initmaptab[i].s);
if((*initmaptab[i].m = openmap(buf)) == nil)
sysfatal("failed to open map: %r");
}
plumbfd = plumbopen("lang", OREAD);
if(plumbfd >= 0)
proccreate(plumbproc, nil, mainstacksize);
proccreate(kbdtap, nil, mainstacksize);
proccreate(kbdsink, nil, mainstacksize);
threadcreate(dictthread, nil, mainstacksize);
threadcreate(keythread, nil, mainstacksize);
threadexits(nil);
}