diff --git a/lib/ktrans/skk2ktrans b/lib/ktrans/skk2ktrans index 94da097fa..083da0fa7 100755 --- a/lib/ktrans/skk2ktrans +++ b/lib/ktrans/skk2ktrans @@ -1,2 +1,2 @@ #!/bin/rc -tcs -sf jis | awk '$1 !~ /;;/ {gsub("(^\/|\/$)", "", $2); gsub(" ", " "); gsub("\/", " ", $2);} {print}' +tcs -sf ujis | awk '$1 !~ /;;/ {gsub("(^\/|\/$)", "", $2); gsub(" ", " "); gsub("\/", " ", $2);} {print}' diff --git a/sys/man/1/ktrans b/sys/man/1/ktrans index 466dc04f1..3f67f78a7 100644 --- a/sys/man/1/ktrans +++ b/sys/man/1/ktrans @@ -24,29 +24,47 @@ If a .I kbdtap file is given, it is used for both input and output instead. -.I Ktrans -starts in a passthrough mode, echoing out -the input with no conversions. Control characters -are used to give instructions, the following -control sequences are used to switch between languages: +By default +.I ktrans +starts in passthrough mode, echoing out +the input with no conversions. The initial +language is set with the +.B -l +flag. After operation has begun, the language +may be changed by either typing a control sequence +and/or through the plumber. +The following table provides the control +sequence and +.I lang +strings accepted for each supported language respectfully. .TP -.B ctl-t English (Passthrough). +ctl-t and en .TP -.B ctl-n Japanese Hiragana. +ctl-n and jp .TP -.B ctl-k Japanese Katakana. +ctl-k and jpk .TP -.B ctl-c Chinese. +ctl-c and zh .TP -.B ctl-s Korean. +ctl-k and ko .TP -.B ctl-v Vietnamese. +ctl-v and vn +.PP +.I Ktrans +listens on the +.I lang +plumber port for switching languages. The data accepted +on this port is the same as the +.B -l +flag's +.I lang +argument. .SH CONVERSION Conversion is done in two layers, an implicit layer for unambiguous mappings, and an explicit @@ -74,16 +92,27 @@ However in some cases the automatic hinting will be insufficient. Input is always passed along, when a match is found .I Ktrans will emit backspaces to clear the input sequence and replace -it with the matched sequence. +it with the matched sequence. Once +.B ctl-\e +has been used to start the selection of an explicit match, the +up and down arrow keys may be used to thumb around the options. .SH DISPLAY .I Ktrans will provide a graphical display of current explicit conversion candidates as implicit conversion is done. Candidates are highlighted as a user cycles through them. At the bottom of the list is an exit button for quitting the program. Keyboard input typed in to the window is -transliterated but discarded, providing a scratch input space. The +transliterated but discarded, providing a scratch input space. The mouse +may be used to scroll through and select candidates, but it requires that +.I ktrans +is started using +.IR rio (1)'s +.B -k +flag. +.PP +The .B -G -option disables this display. +flag disables the graphical display entirely. .SH "KEY MAPPING" For convenience, the control characters used by .I ktrans @@ -123,14 +152,17 @@ text files within .BR /lib/ktrans . The formats of which are specified within .IR ktrans (6). -Users may create and or modify existing dictionaries by binding over -the system defaults. +Additionally, dictionaries located in +.B $home/lib/ktrans/ +will be merged on top of the system dictionaries. +Merging is done at a list level only; Keys that appear +replace all values of the previous definition. .PP For backwards compatibility the .B jisho and .B zidian -environment variables may also be set to pick explicit lookup dictionaries +environment variables may also be set to pick alternate system dictionaries for Japanese and Chinese respectfully. .SH LANGUAGES .SS JAPANESE diff --git a/sys/src/cmd/ktrans/main.c b/sys/src/cmd/ktrans/main.c index 5c7502155..f0298942e 100644 --- a/sys/src/cmd/ktrans/main.c +++ b/sys/src/cmd/ktrans/main.c @@ -160,8 +160,6 @@ opendict(Hmap *h, char *name) if(h == nil) h = hmapalloc(8192, sizeof(kouho)); - else - hmapreset(h, 1); while(p = Brdstr(b, '\n', 1)){ if(p[0] == '\0' || p[0] == ';'){ Err: @@ -275,7 +273,7 @@ maplkup(int lang, char *s, Map *m) return hmapget(*h, s, m); } -enum { Msgsize = 64 }; +enum { Msgsize = 256 }; static Channel *dictch; static Channel *output; static Channel *input; @@ -291,19 +289,22 @@ displaythread(void*) Mouse m; Keyboardctl *kctl; Rune key; - char *kouho[Maxkouho+1], **s; - Image *back, *text, *board, *high; + 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; + Rectangle r, exitr, selr, scrlr; int selected; - enum { Adisp, Aresize, Amouse, Asel, Akbd, Aend }; + char *mp, move[Msgsize]; + enum { Adisp, Aresize, Amouse, Asel, Akbd, Amove, Aend }; Alt a[] = { - [Adisp] { nil, kouho+1, CHANRCV }, + [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 }, }; @@ -324,24 +325,28 @@ displaythread(void*) sysfatal("failed to get keyboard: %r"); memset(kouho, 0, sizeof kouho); - kouho[0] = "候補"; 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; @@ -350,29 +355,98 @@ displaythread(void*) case Amouse: if(!m.buttons) break; - if(!ptinrect(m.xy, exitr)) + if(ptinrect(m.xy, exitr)){ + closedisplay(display); + threadexitsall(nil); + } + if(kouho[0] == nil) break; - closedisplay(display); - threadexitsall(nil); + 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+1 > 0 && kouho[selected+1] != nil){ + if(selected >= 0 && kouho[selected] != nil){ selr = screen->r; - selr.min.y += f->height*(selected+1); + 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; - for(s = kouho; *s != nil; s++){ + + 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; @@ -414,7 +488,7 @@ dictthread(void*) Str line; Str last; Str okuri; - int selected; + int selected, loop; enum{ Kanji, @@ -425,6 +499,7 @@ dictthread(void*) dict = jisho; selected = -1; + loop = 0; mode = Kanji; memset(kouho, 0, sizeof kouho); resetstr(&last, &line, &okuri, nil); @@ -463,6 +538,24 @@ dictthread(void*) } 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){ @@ -472,20 +565,16 @@ dictthread(void*) line.p[-1] = '\0'; } if(kouho[selected] == nil){ - /* cycled through all matches; bail */ - if(utflen(okuri.b) != 0) - emitutf(output, backspace, utflen(okuri.b)); - emitutf(output, backspace, utflen(last.b)); - emitutf(output, line.b, 0); - emitutf(output, okuri.b, 0); - break; + 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) + if(selected == 0 && !loop) emitutf(output, backspace, utflen(line.b)); else emitutf(output, backspace, utflen(last.b)); @@ -494,6 +583,7 @@ dictthread(void*) 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'、': @@ -585,6 +675,7 @@ keythread(void*) { int lang; char m[Msgsize]; + char *todict; Map lkup; char *p; int n; @@ -596,6 +687,7 @@ keythread(void*) 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){ @@ -624,7 +716,7 @@ keythread(void*) emitutf(output, p, 1); continue; } - if(utfrune(" ", r) != nil){ + if(utfrune(todict, r) != nil){ resetstr(&line, nil); emitutf(dictch, p, 1); continue; @@ -684,7 +776,7 @@ static void kbdtap(void*) { char m[Msgsize]; - char buf[128]; + char buf[Msgsize]; char *p; int n; @@ -774,16 +866,27 @@ usage(void) threadexits("usage"); } + +static char *kdir = "/lib/ktrans"; + struct { char *s; Hmap **m; -} inittab[] = { +} 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; @@ -792,7 +895,8 @@ threadmain(int argc, char *argv[]) { int nogui, i; char buf[128]; - char *jishoname, *zidianname; + char *e, *home; + Hmap *m; deflang = LangEN; nogui = 0; @@ -822,40 +926,48 @@ threadmain(int argc, char *argv[]) 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), 1); - displaych = chancreate(sizeof(char*)*Maxkouho, 1); + 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((jishoname = getenv("jisho")) == nil) - jishoname = "/lib/ktrans/kanji.dict"; - if((jisho = opendict(nil, jishoname)) == nil) - sysfatal("failed to open jisho: %r"); - - if((zidianname = getenv("zidian")) == nil) - zidianname = "/lib/ktrans/wubi.dict"; - if((zidian = opendict(nil, zidianname)) == nil) - sysfatal("failed to open zidian: %r"); + 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(inittab); i++){ - snprint(buf, sizeof buf, "/lib/ktrans/%s.map", inittab[i].s); - if((*inittab[i].m = openmap(buf)) == 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"); } - dictch = chancreate(Msgsize, 0); - input = chancreate(Msgsize, 0); - output = chancreate(Msgsize, 0); - plumbfd = plumbopen("lang", OREAD); if(plumbfd >= 0) proccreate(plumbproc, nil, mainstacksize); diff --git a/sys/src/cmd/ktrans/test.c b/sys/src/cmd/ktrans/test.c index 8c7c91638..0b7626b1f 100644 --- a/sys/src/cmd/ktrans/test.c +++ b/sys/src/cmd/ktrans/test.c @@ -9,7 +9,7 @@ struct { " no", L"の", " nno", L"んの", " neko", L"猫", - " neko", L"ねこ", + " neko", L"猫", " watashi", L"私", " tanoShi", L"楽し", " oreNO", L"俺の", @@ -116,7 +116,7 @@ main(int argc, char **argv) goto Verify; case 8: if(nstack == 0) - sysfatal("buffer underrun"); + sysfatal("buffer underrun on: %s", set[i].input); nstack--; stack[nstack] = 0; break;