shithub: riscv

Download patch

ref: 69352f66684cbc75e2cd68c80cd028324964c5aa
parent: 79c1842979b5c5579bb0db41861d63f5562c85f1
author: Jacob Moody <moody@posixcafe.org>
date: Sun Aug 14 23:45:42 EDT 2022

ktrans: tow inside the environment

--- /dev/null
+++ b/sys/man/1/ktrans
@@ -1,0 +1,117 @@
+.TH KTRANS 1
+.SH NAME
+ktrans \- language transliterator
+.SH SYNOPSIS
+.B ktrans
+[
+.B -t
+.I kbdtap
+]
+[
+.B -l
+.I lang
+]
+.SH DESCRIPTION
+.I Ktrans
+provides a transliteration layer
+to keyboard input through reads and
+writes to
+.BR /dev/kbdtap .
+The
+.B -t
+flag changes the
+.I kbdtap
+file used. The
+.B -l
+flag changes the initial
+language.
+.SH CONVERSION
+Conversion is done in two steps: An implicit layer
+that is used for direct mappings between ascii characters and
+an explicit multi rune conversion used for compound mappings.
+.I Ktrans
+does implicit conversion by passing through characters
+as they are input. Then when a sequence of input is matched,
+backspaces are emitted to clear the input sequence and the matched
+rune sequence is emitted. The last 'word' of input may be then
+explicitely transliterated by typing ctrl-\\. A newline character also
+performs this lookup, but additional newline characters will not
+cycle through alternatives.
+.SH CONTROL
+The language is selected by typing a control character:
+.TP
+.B ctl-t
+Passthrough mode
+.TP
+.B ctl-n
+Japanese mode. Implicit layer converts hepburn sequences to hiragana. Explicit
+layer converts sequences of hiragana with optional trailing particle or okurigana.
+.TP
+.B ctl-k
+Implicit only Japanese Katakana layer.
+.TP
+.B ctrl-c
+Chinese Wubi mode. No implicit conversion is done. Explicit layer
+converts sequences of latin characters to hanzi.
+.TP
+.B ctl-l
+Clear the explicit layer's current input sequence.
+.TP
+.B ctl-r
+Russian mode. Implicit layer converts latin to Cyrillic; the transliteration is mostly
+phonetic, with
+.B '
+for
+.IR myagkij-znak
+(ь),
+.B ''
+for
+.I tverdyj-znak
+(ъ)
+.I yo
+for ё,
+.B j
+for
+.IR i-kratkaya
+(й).
+.TP
+.B ctl-o
+Greek mode.
+.TP
+.B ctl-s
+Korean mode. Implicit layer converts latin to Korean Hangul.
+.SH SOURCE
+.B /sys/src/cmd/ktrans
+.SH SEE ALSO
+.IR rio (4)
+.IR kbdfs (8)
+.br
+.IR /sys/src/cmd/ktrans/README.kenji
+.br
+.IR /sys/src/cmd/ktrans/READMEJ.kenji
+.SH EXAMPLES
+To type the following Japanese text:
+
+.ft Jp
+私は毎日35分以上歩いて、 更に10分電車に乗って学校に通います。
+ 健康の維持にも役だっていますが、 なかなかたのしいものです。
+.ft
+
+your keyboard typing stream should be:
+
+watashiHA[^\\]mainichi[^\\]35[^l]fun[^\\]ijou[^\\]aruIte,[^\\]
+saraNI[^\\]10[^l]fun[^\\]denshaNI[^\\]noTte[^\\]gakkouNI[^\\]
+kayoImasu.[\\n]kenkouNO[^\\]ijiNImo[^\\]yakuDAtteimasuga,[^\\]
+nakanakatanoshiImonodesu.[\\n]
+
+where [^\\] and [^l] indicate 'ctl-\\' and 'ctl-l',
+respectively.  See README.kenji for the details of this Japanese input
+method.
+.SH BUGS
+.PP
+There is no way to generate the control characters literally.
+.SH HISTORY
+Ktrans was originally written by Kenji Okamoto in August of 2000 for
+the 2nd edition of Plan 9.  It was imported in to 9front in July of
+2022, with patches by several contributors. It was towed inside
+the environment during the 2022 9front hackathon.
--- a/sys/man/4/ktrans
+++ /dev/null
@@ -1,142 +1,0 @@
-.TH KTRANS 4
-.SH NAME
-ktrans \- language transliterator
-.SH SYNOPSIS
-.B ktrans
-[
-.B -K
-]
-[
-.B -k
-.I kbd
-]
-[
-.B -l
-.I lang
-]
-[
-.B -m
-.I mnt
-]
-.nf
-
-.IB /mnt/ktrans/kbd
-.IB /mnt/ktrans/kbdin
-.IB /mnt/ktrans/lang
-.fi
-.SH DESCRIPTION
-.I ktrans
-is a fileserver that provides a transliteration overlay to
-.IR kbdfs (8).
-When run,
-.I ktrans
-mounts itself to
-.B /mnt/ktrans
-and binds its own
-.B kbd
-and
-.B kbdin
-files over those present in
-.BR /dev .
-.PP
-By default,
-.I ktrans
-also forks and reads input from the existing
-.BR /dev/kbd .
-The
-.B -k
-flag changes which file is read. The
-.B -K
-flag disables this process all together, limiting
-input to only the
-.B kbdin
-file.
-.SH CONVERSION
-Conversion is done in two steps: An implicit layer
-that is used for direct mappings between latin characters and
-an explicit multi rune conversion used for compound mappings.
-.I Ktrans
-does implicit conversion by passing through characters
-as they are input. Then when a sequence of input is matched,
-backspaces are emitted to clear the input sequence and the matched
-rune sequence is emitted. The last 'word' of input may be then
-additionaly transliterated by typing ctrl-\\. A newline character also
-performs this lookup, but additional newline characters will not
-cycle through alternatives.
-.SH CONTROL
-The language is selected by typing a control character:
-.TP
-.B ctl-t
-Passthrough mode
-.TP
-.B ctl-n
-Japanese mode. Implicit layer converts hepburn sequences to hiragana. Explicit
-layer converts sequences of hiragana with optional trailing particle or okurigana.
-.TP
-.B ctl-k
-Implicit only Japanese Katakana layer.
-.TP
-.B ctrl-c
-Chinese Wubi mode. No implicit conversion is done. Explicit layer
-converts sequences of latin characters to hanzi.
-.TP
-.B ctl-l
-Clear the explicit layer's current input sequence.
-.TP
-.B ctl-r
-Russian mode. Implicit layer converts latin to Cyrillic; the transliteration is mostly
-phonetic, with
-.B '
-for
-.IR myagkij-znak
-(ь),
-.B ''
-for
-.I tverdyj-znak
-(ъ)
-.I yo
-for ё,
-.B j
-for
-.IR i-kratkaya
-(й).
-.TP
-.B ctl-o
-Greek mode.
-.TP
-.B ctl-s
-Korean mode. Implicit layer converts latin to Korean Hangul.
-.SH SOURCE
-.B /sys/src/cmd/ktrans
-.SH SEE ALSO
-.IR rio (1)
-.IR kbdfs (8)
-.br
-.IR /sys/src/cmd/ktrans/README.kenji
-.br
-.IR /sys/src/cmd/ktrans/READMEJ.kenji
-.SH EXAMPLES
-To type the following Japanese text:
-
-.ft Jp
-私は毎日35分以上歩いて、 更に10分電車に乗って学校に通います。
- 健康の維持にも役だっていますが、 なかなかたのしいものです。
-.ft
-
-your keyboard typing stream should be:
-
-watashiHA[^\\]mainichi[^\\]35[^l]fun[^\\]ijou[^\\]aruIte,[^\\]
-saraNI[^\\]10[^l]fun[^\\]denshaNI[^\\]noTte[^\\]gakkouNI[^\\]
-kayoImasu.[\\n]kenkouNO[^\\]ijiNImo[^\\]yakuDAtteimasuga,[^\\]
-nakanakatanoshiImonodesu.[\\n]
-
-where [^\\] and [^l] indicate 'ctl-\\' and 'ctl-l',
-respectively.  See README.kenji for the details of this Japanese input
-method.
-.SH BUGS
-.PP
-There is no way to generate the control characters literally.
-.SH HISTORY
-Ktrans was originally written by Kenji Okamoto in August of 2000 for
-the 2nd edition of Plan 9.  It was imported in to 9front in July of
-2022, with patches by several contributors.
--- a/sys/src/cmd/ktrans/fs.c
+++ /dev/null
@@ -1,401 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <fcall.h>
-#include <thread.h>
-#include <9p.h>
-
-#include "hash.h"
-#include "ktrans.h"
-
-static Channel 	*globalkbd;
-static char 	*user;
-
-char*
-parsekbd(Channel *out, char *buf, int n)
-{
-	char *p, *e;
-	Msg msg;
-
-	for(p = buf; p < buf+n;){
-		msg.code = p[0];
-		p++;
-		switch(msg.code){
-		case 'c': case 'k': case 'K':
-			break;
-		default:
-			return "malformed kbd message";
-		}
-		e = utfecpy(msg.buf, msg.buf + sizeof msg.buf, p);
-		if(e == msg.buf)
-			return "short command";
-		p += e - msg.buf;
-		p++;
-		if(send(out, &msg) == -1)
-			return nil;
-	}
-	return nil;
-}
-
-void
-kbdproc(void *a)
-{
-	char *s;
-	int fd, n;
-	char buf[128];
-
-	s = a;
-	fd = open(s, OREAD);
-	if(fd < 0){
-		fprint(2, "could not open file %s: %r", s);
-		chanclose(globalkbd);
-		return;
-	}
-	for(;;){
-		n = read(fd, buf, sizeof buf);
-		if(n < 3){
-			continue;
-		}
-		parsekbd(globalkbd, buf, n);
-	}
-}
-
-Trans*
-spawntrans(int global)
-{
-	Trans *t;
-
-	t = mallocz(sizeof *t, 1);
-	if(global)
-		t->input = globalkbd;
-	else
-		t->input = chancreate(sizeof(Msg), 0);
-	t->output = chancreate(sizeof(Msg), 0);
-	t->dict = chancreate(sizeof(Msg), 0);
-	t->done = chancreate(1, 0);
-	t->lang = chancreate(sizeof(char*), 0);
-	proccreate(keyproc, t, mainstacksize);
-	return t;
-}
-
-void
-closetrans(Trans *t)
-{
-	chanclose(t->input);
-	chanclose(t->output);
-	chanclose(t->dict);
-
-	/* wait for threads to exit */
-	recv(t->done, nil);
-	recv(t->done, nil);
-
-	chanfree(t->done);
-	chanfree(t->input);
-	chanfree(t->output);
-	chanfree(t->dict);
-	free(t);
-}
-
-enum{
-	Qroot,
-	Qkbd,
-	Qkbdin,
-	Qlang,
-};
-
-Dir dirtab[] = {
-	{.qid={Qroot, 0, QTDIR}, .mode=0555, .name="/"},
-	{.qid={Qkbd, 0, QTFILE}, .mode=0600, .name="kbd"},
-	{.qid={Qkbdin, 0, QTFILE}, .mode=0200, .name="kbdin"},
-	{.qid={Qlang, 0, QTFILE}, .mode=0600, .name="lang"},
-};
-
-static int
-dirgen(int n, Dir *dir, void*)
-{
-	n++;
-	if(n >= nelem(dirtab))
-		return -1;
-
-	*dir = dirtab[n];
-	dir->name = estrdup9p(dir->name);
-	dir->uid = estrdup9p(user);
-	dir->gid = estrdup9p(user);
-	dir->muid = estrdup9p(user);
-	return 0;
-}
-
-typedef struct Aux Aux;
-struct Aux {
-	Ref;
-	Reqqueue *q;
-	Trans	 *t;
-};
-
-static void
-fsattach(Req *r)
-{
-	Aux *aux;
-	Trans *t;
-	char *aname;
-
-	/*
-	 * Each attach allocates a new "keyboard".
-	 * The global attach argument denotes to
-	 * use /dev/kbd as the source of keyboard input.
-	 *
-	 * Sessions include one translation
-	 * process, and one read queue. Since
-	 * it is common for clients to constantly be
-	 * blocked on the kbd file, we need to assign it to
-	 * it's own process so we can service other requests
-	 * in the meantime.
-	 */
-
-	aname = r->ifcall.aname;
-	if(aname != nil && strcmp(aname, "global") == 0)
-		t = spawntrans(1);
-	else
-		t = spawntrans(0);
-
-	aux = mallocz(sizeof *aux, 1);
-	aux->t = t;
-	aux->q = reqqueuecreate();
-	incref(aux);
-
-	r->fid->aux = aux;
-	r->ofcall.qid = dirtab[0].qid;
-	r->fid->qid = dirtab[0].qid;
-	respond(r, nil);
-}
-
-static void
-fsopen(Req *r)
-{
-	respond(r, nil);
-}
-
-static void
-fskbd(Req *r)
-{
-	Aux *aux;
-	Msg m;
-	char *p;
-	char buf[1+128], *bp;
-	Rune rn;
-
-	aux = r->fid->aux;
-	if(recv(aux->t->output, &m) == -1){
-		respond(r, "closing");
-		return;
-	}
-	if(m.code != 'c'){
-		bp = seprint(buf, buf + sizeof buf, "%c%s", m.code, m.buf);
-		goto Send;
-	}
-	p = m.buf;
-	bp = buf;
-	for(;bp < buf + sizeof buf;){
-		p += chartorune(&rn, p);
-		if(rn == Runeerror || rn == '\0')
-			break;
-		bp = seprint(bp, buf + sizeof buf, "c%C", rn);
-		bp++;
-	}
-	if(bp >= buf + sizeof buf){
-		while(*bp-- != '\0')
-			;
-		bp++;
-	}
-
-Send:
-	r->ifcall.offset = 0;
-	readbuf(r, buf, (bp-buf)+1);
-	respond(r, nil);
-}
-
-static void
-fsread(Req *r)
-{
-	Aux *aux;
-	Msg m;
-	char *p;
-
-	aux = r->fid->aux;
-	switch((uint)r->fid->qid.path){
-	case Qroot:
-		dirread9p(r, dirgen, nil);
-		respond(r, nil);
-		break;
-	case Qkbd:
-		
-		reqqueuepush(aux->q, r, fskbd);
-		break;
-	case Qlang:
-		m.code = 'q';
-		m.buf[0] = '\0';
-		if(send(aux->t->input, &m) == -1){
-			respond(r, "closing");
-			break;
-		}
-		if(recv(aux->t->lang, &p) == -1){
-			respond(r, "closing");
-			break;
-		}
-		snprint(m.buf, sizeof m.buf, "%s\n", p);
-		readstr(r, m.buf);
-		respond(r, nil);
-		break;
-	default:
-		respond(r, "bad op");
-		break;
-	}
-}
-
-static void
-fswrite(Req *r)
-{
-	Aux *aux;
-	int n, lang;
-	char *err, *p;
-	Msg m;
-
-	aux = r->fid->aux;
-	n = r->ifcall.count;
-	switch((uint)r->fid->qid.path){
-	case Qkbdin:
-		if(n < 3){
-			respond(r, "short write");
-			return;
-		}
-		err = parsekbd(aux->t->input, r->ifcall.data, n);
-		if(err != nil){
-			respond(r, err);
-			return;
-		}
-		break;
-	case Qlang:
-		if(n >= sizeof m.buf){
-			respond(r, "large write");
-			return;
-		}
-		memmove(m.buf, r->ifcall.data, n);
-		m.buf[n] = '\0';
-		p = strchr(m.buf, '\n');
-		if(p != nil)
-			*p = '\0';
-		lang = parselang(m.buf);
-		if(lang < 0){
-			respond(r, "unkonwn lang");
-			return;
-		}
-		m.buf[0] = lang;
-		m.buf[1] = '\0';
-		m.code = 'c';
-		send(aux->t->input, &m);
-	}
-	r->ofcall.count = n;
-	respond(r, nil);
-}
-
-static void
-fsstat(Req *r)
-{
-	if(dirgen(r->fid->qid.path - 1, &r->d, nil) == -1)
-		respond(r, "invalid fid");
-	else
-		respond(r, nil);
-	
-}
-
-static char*
-fswalk1(Fid *fid, char *name, Qid *qid)
-{
-	int i;
-
-	if(fid->qid.path != Qroot)
-		return "walk from non root";
-
-	for(i = 0; i  < nelem(dirtab); i++)
-		if(strcmp(name, dirtab[i].name) == 0){
-			*qid = dirtab[i].qid;
-			break;
-		}
-
-	if(i == nelem(dirtab))
-		return "file does not exist";
-
-	fid->qid = *qid;
-	return nil;
-}
-
-static char*
-fsclone(Fid *oldfid, Fid *newfid)
-{
-	Aux *aux;
-
-	aux = oldfid->aux;
-	incref(aux);
-	newfid->aux = aux;
-
-	return nil;
-}
-
-static void
-fidclunk(Fid *fid)
-{
-	Aux *aux;
-
-	aux = fid->aux;
-	if(decref(aux) != 0)
-		return;
-
-	closetrans(aux->t);
-	reqqueuefree(aux->q);
-}
-
-
-static Srv fs = {
-	.attach=fsattach,
-	.open=fsopen,
-	.read=fsread,
-	.write=fswrite,
-	.stat=fsstat,
-
-	.walk1=fswalk1,
-	.clone=fsclone,
-	.destroyfid=fidclunk,
-};
-
-void
-launchfs(char *srv, char *mnt, char *kbd)
-{
-	int fd;
-	char buf[128];
-
-	user = getenv("user");
-	if(user == nil)
-		user = "glenda";
-	if(kbd != nil){
-		globalkbd = chancreate(sizeof(Msg), 0);
-		proccreate(kbdproc, kbd, mainstacksize);
-	}
-
-	fd = threadpostsrv(&fs, srv);
-	if(fd < 0)
-		sysfatal("postsrv %r");
-
-	if(kbd != nil){
-		if(mount(fd, -1, mnt, MREPL, "global") < 0)
-			sysfatal("mount %r");
-
-		snprint(buf, sizeof buf, "%s/kbd", mnt);
-		if(bind(buf, "/dev/kbd", MREPL) < 0)
-			sysfatal("bind %r");
-
-		snprint(buf, sizeof buf, "%s/kbdin", mnt);
-		if(bind(buf, "/dev/kbdin", MREPL) < 0)
-			sysfatal("bind %r");
-	} else
-		if(mount(fd, -1, mnt, MREPL, "") < 0)
-			sysfatal("mount %r");
-}
--- a/sys/src/cmd/ktrans/ktrans.h
+++ /dev/null
@@ -1,25 +1,0 @@
-typedef	struct	Map	Map;
-struct	Map {
-	char	*roma;
-	char	*kana;
-	char leadstomore;
-};
-
-typedef struct Msg Msg;
-struct Msg {
-	char code;
-	char buf[64];
-};
-
-typedef struct Trans Trans;
-struct Trans {
-	Channel *input;
-	Channel *output;
-	Channel *dict;
-	Channel *done;
-	Channel *lang;
-};
-
-void	keyproc(void*);
-void	launchfs(char*,char*,char*);
-int	parselang(char*);
--- a/sys/src/cmd/ktrans/main.c
+++ b/sys/src/cmd/ktrans/main.c
@@ -1,11 +1,3 @@
-/*
- *   Mostly based on the original source codes of Plan 9 release 2
- *   distribution.
- *             by Kenji Okamoto, August 4 2000
- *                   Osaka Prefecture Univ.
- *                   okamoto@granite.cias.osakafu-u.ac.jp
- */
-
 #include <u.h>
 #include <libc.h>
 #include <ctype.h>
@@ -13,16 +5,8 @@
 #include <fcall.h>
 #include <thread.h>
 #include <9p.h>
-
 #include "hash.h"
-#include "ktrans.h"
 
-static Hmap  *jisho, *zidian;
-static int   deflang;
-static char  backspace[64];
-
-mainstacksize = 8192*2;
-
 char*
 pushutf(char *dst, char *e, char *u, int nrune)
 {
@@ -83,12 +67,19 @@
 void
 popstr(Str *s)
 {
-	while(s->p > s->b && (*--s->p & 0xC0)==0x80)
+	while(s->p > s->b && (*--s->p & 0xC0)==Runesync)
 		;
 
 	s->p[0] = '\0';
 }
 
+typedef	struct Map Map;
+struct Map {
+	char	*roma;
+	char	*kana;
+	char	leadstomore;
+};
+
 Hmap*
 openmap(char *file)
 {
@@ -201,12 +192,14 @@
 	LangZH	= '',	// ^c
 };
 
+int deflang;
+
 Hmap *natural;
-Hmap *hira, *kata;
+Hmap *hira, *kata, *jisho;
 Hmap *cyril;
 Hmap *greek;
 Hmap *hangul;
-Hmap *hanzi;
+Hmap *hanzi, *zidian;
 
 Hmap **langtab[] = {
 	[LangEN]  &natural,
@@ -274,6 +267,16 @@
 	return hmapget(*h, s, m);
 }
 
+typedef struct Msg Msg;
+struct Msg {
+	char code;
+	char buf[64];
+};
+static Channel *dictch;
+static Channel *output;
+static Channel *input;
+static char  backspace[64];
+
 static int
 emitutf(Channel *out, char *u, int nrune)
 {
@@ -287,9 +290,8 @@
 }
 
 static void
-dictthread(void *a)
+dictthread(void*)
 {
-	Trans *t;
 	Msg m;
 	Rune r;
 	int n;
@@ -308,7 +310,6 @@
 	};
 	int mode;
 
-	t = a;
 	dict = jisho;
 	selected = -1;
 	kouho[0] = nil;
@@ -316,7 +317,7 @@
 	resetstr(&last, &line, &okuri, nil);
 
 	threadsetname("dict");
-	while(recv(t->dict, &m) != -1){
+	while(recv(dictch, &m) != -1){
 		for(p = m.buf; *p; p += n){
 			n = chartorune(&r, p);
 			if(r != ''){
@@ -337,10 +338,10 @@
 				break;
 			case '':
 				if(line.b == line.p){
-					emitutf(t->output, "", 1);
+					emitutf(output, "", 1);
 					break;
 				}
-				emitutf(t->output, backspace, utflen(line.b));
+				emitutf(output, backspace, utflen(line.b));
 				/* fallthrough */
 			case ' ': case ',': case '.':
 			case '':
@@ -360,7 +361,7 @@
 				break;
 			case '\n':
 				if(line.b == line.p){
-					emitutf(t->output, "\n", 1);
+					emitutf(output, "\n", 1);
 					break;
 				}
 				/* fallthrough */
@@ -377,8 +378,8 @@
 				}
 				if(kouho[selected] == nil){
 					/* cycled through all matches; bail */
-					emitutf(t->output, backspace, utflen(last.b));
-					emitutf(t->output, line.b, 0);
+					emitutf(output, backspace, utflen(last.b));
+					emitutf(output, line.b, 0);
 					resetstr(&line, &last, &okuri, nil);
 					selected = -1;
 					break;
@@ -385,15 +386,15 @@
 				}
 
 				if(okuri.p != okuri.b)
-					emitutf(t->output, backspace, utflen(okuri.b));
+					emitutf(output, backspace, utflen(okuri.b));
 				if(selected == 0)
-					emitutf(t->output, backspace, utflen(line.b));
+					emitutf(output, backspace, utflen(line.b));
 				else
-					emitutf(t->output, backspace, utflen(last.b));
+					emitutf(output, backspace, utflen(last.b));
 
-				emitutf(t->output, kouho[selected], 0);
+				emitutf(output, kouho[selected], 0);
 				last.p = pushutf(last.b, strend(&last), kouho[selected], 0);
-				emitutf(t->output, okuri.b, 0);
+				emitutf(output, okuri.b, 0);
 
 				resetstr(&line, nil);
 				mode = Kanji;
@@ -430,14 +431,11 @@
 			}
 		}
 	}
-
-	send(t->done, nil);
 }
 
-void
-keyproc(void *a)
+static void
+keythread(void*)
 {
-	Trans *t;
 	int lang;
 	Msg m;
 	Map lkup;
@@ -448,21 +446,21 @@
 	Str line;
 	int mode;
 
-	t = a;
 	mode = 0;
 	peek[0] = lang = deflang;
-	threadcreate(dictthread, a, mainstacksize);
 	resetstr(&line, nil);
 	if(lang == LangJP || lang == LangZH)
-		emitutf(t->dict, peek, 1);
+		emitutf(dictch, peek, 1);
 
-	threadsetname("key");
-	while(recv(t->input, &m) != -1){
+	threadsetname("keytrans");
+	while(recv(input, &m) != -1){
+		if(m.code == 'r'){
+			emitutf(dictch, "", 1);
+			resetstr(&line, nil);
+			continue;
+		}
 		if(m.code != 'c'){
-			if(m.code == 'q')
-				send(t->lang, &langcodetab[lang]);
-			else
-				send(t->output, &m);
+			send(output, &m);
 			continue;
 		}
 
@@ -469,14 +467,14 @@
 		for(p = m.buf; *p; p += n){
 			n = chartorune(&r, p);
 			if(checklang(&lang, r)){
-				emitutf(t->dict, "", 1);
+				emitutf(dictch, "", 1);
 				if(lang == LangJP || lang == LangZH)
-					emitutf(t->dict, p, 1);
+					emitutf(dictch, p, 1);
 				resetstr(&line, nil);
 				continue;
 			}
 			if(lang == LangZH || lang == LangJP){
-				emitutf(t->dict, p, 1);
+				emitutf(dictch, p, 1);
 				if(utfrune("\n", r) != nil){
 					resetstr(&line, nil);
 					continue;
@@ -489,7 +487,7 @@
 				}
 			}
 
-			emitutf(t->output, p, 1);
+			emitutf(output, p, 1);
 			if(lang == LangEN || lang == LangZH)
 				continue;
 			if(r == '\b'){
@@ -512,52 +510,101 @@
 				resetstr(&line, nil);
 
 			if(lang == LangJP){
-				emitutf(t->dict, backspace, utflen(lkup.roma));
-				emitutf(t->dict, lkup.kana, 0);
+				emitutf(dictch, backspace, utflen(lkup.roma));
+				emitutf(dictch, lkup.kana, 0);
 			}
-			emitutf(t->output, backspace, utflen(lkup.roma));
-			emitutf(t->output, lkup.kana, 0);
+			emitutf(output, backspace, utflen(lkup.roma));
+			emitutf(output, lkup.kana, 0);
 		}
 	}
-	send(t->done, nil);
 }
 
+static int kbdin;
+static int kbdout;
+
 void
+kbdtap(void*)
+{
+	Msg msg;
+	char buf[128];
+	char *p, *e;
+	int n;
+
+	threadsetname("kbdtap");
+	for(;;){
+Drop:
+		n = read(kbdin, buf, sizeof buf);
+		if(n < 0)
+			break;
+		for(p = buf; p < buf+n;){
+			msg.code = p[0];
+			p++;
+			switch(msg.code){
+			case 'c': case 'k': case 'K':
+			case 'r':
+				break;
+			default:
+				goto Drop;
+			}
+			e = utfecpy(msg.buf, msg.buf + sizeof msg.buf, p);
+			p += e - msg.buf;
+			p++;
+			if(send(input, &msg) == -1)
+				return;
+		}
+	}
+}
+
+void
+kbdsink(void*)
+{
+	Msg m;
+	char *p;
+	Rune rn;
+
+	threadsetname("kbdsink");
+	while(recv(output, &m) != -1){
+		if(m.code != 'c'){
+			fprint(kbdout, "%c%s", m.code, m.buf);
+			continue;
+		}
+		p = m.buf;
+		for(;;){
+			p += chartorune(&rn, p);
+			if(rn == Runeerror || rn == '\0')
+				break;
+			fprint(kbdout, "c%C", rn);
+		}
+	}
+}
+
+void
 usage(void)
 {
-	fprint(2, "usage: %s [ -K ] [ -l lang ]\n", argv0);
-	exits("usage");
+	fprint(2, "usage: %s [ -t tap ] [ -l lang ]\n", argv0);
+	threadexits("usage");
 }
 
+mainstacksize = 8192*2;
+
 void
 threadmain(int argc, char *argv[])
 {
 
 	char *jishoname, *zidianname;
-	char *kbd, *srv, *mntpt;
+	char *tap;
 
-	kbd = "/dev/kbd";
-	srv = nil;
-	mntpt = "/mnt/ktrans";
+	tap = "/dev/kbdtap";
 	deflang = LangEN;
 	ARGBEGIN{
-	case 'K':
-		kbd = nil;
+	case 't':
+		tap = EARGF(usage());
 		break;
-	case 'k':
-		kbd = EARGF(usage());
-		break;
 	case 'l':
 		deflang = parselang(EARGF(usage()));
 		if(deflang < 0)
 			usage();
 		break;
-	case 's':
-		srv = EARGF(usage());
-		break;
-	case 'm':
-		mntpt = EARGF(usage());
-		break;
 	default:
 		usage();
 	}ARGEND;
@@ -564,6 +611,10 @@
 	if(argc != 0)
 		usage();
 
+	kbdin = kbdout = open(tap, ORDWR);
+	if(kbdin < 0 || kbdout < 0)
+		sysfatal("failed to get keyboard: %r");
+
 	memset(backspace, '\b', sizeof backspace-1);
 	backspace[sizeof backspace-1] = '\0';
 
@@ -582,5 +633,14 @@
 	cyril 	= openmap("/lib/ktrans/cyril.map");
 	hangul 	= openmap("/lib/ktrans/hangul.map");
 
-	launchfs(srv, mntpt, kbd);
+	dictch 	= chancreate(sizeof(Msg), 0);
+	input 	= chancreate(sizeof(Msg), 0);
+	output 	= chancreate(sizeof(Msg), 0);
+
+	proccreate(kbdtap, nil, mainstacksize);
+	proccreate(kbdsink, nil, mainstacksize);
+	threadcreate(dictthread, nil, mainstacksize);
+	threadcreate(keythread, nil, mainstacksize);
+
+	threadexits(nil);
 }
--- a/sys/src/cmd/ktrans/mkfile
+++ b/sys/src/cmd/ktrans/mkfile
@@ -2,10 +2,9 @@
 
 BIN=/$objtype/bin
 TARG=ktrans
-HFILES=ktrans.h
+HFILES=hash.h
 OFILES=\
 	hash.$O\
 	main.$O\
-	fs.$O\
 
 </sys/src/cmd/mkone