shithub: riscv

ref: 0a7d581eec1319c643337d302176b3f9ba565ea5
dir: /sys/src/cmd/ktrans/main.c/

View raw version
#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);
}