shithub: riow

Download patch

ref: 424638912db3db1dc88d7b91b8f9d38c11e920b7
parent: 031d7dabfe30a8bf74e5ca5db7f7093fddf07bd1
author: Sigrid Solveig Haflínudóttir <[email protected]>
date: Wed Aug 24 16:47:30 EDT 2022

rewrite riow (in C this time) to use /dev/kbdtap

--- a/9front.diff
+++ /dev/null
@@ -1,117 +1,0 @@
-diff -r ab6ef2653d12 sys/src/cmd/rio/dat.h
---- a/sys/src/cmd/rio/dat.h	Mon Dec 07 15:15:02 2020 +0100
-+++ b/sys/src/cmd/rio/dat.h	Mon Dec 07 15:56:46 2020 +0100
-@@ -346,11 +346,15 @@
- char		*startdir;
- int		sweeping;
- int		wctlfd;
-+int		gkbdfd;
-+Channel*	gkbdc;
- char		srvpipe[];
- char		srvwctl[];
-+char		srvgkbd[];
- int		errorshouldabort;
- int		menuing;		/* menu action is pending; waiting for window to be indicated */
- int		snarfversion;	/* updated each time it is written */
- int		messagesize;		/* negotiated in 9P version setup */
- int		shiftdown;
-+int		mod4down;
- int		debug;
-diff -r ab6ef2653d12 sys/src/cmd/rio/fsys.c
---- a/sys/src/cmd/rio/fsys.c	Mon Dec 07 15:15:02 2020 +0100
-+++ b/sys/src/cmd/rio/fsys.c	Mon Dec 07 15:56:46 2020 +0100
-@@ -50,6 +50,7 @@
- 
- char	srvpipe[64];
- char	srvwctl[64];
-+char	srvgkbd[64];
- 
- static	Xfid*	filsysflush(Filsys*, Xfid*, Fid*);
- static	Xfid*	filsysversion(Filsys*, Xfid*, Fid*);
-@@ -115,6 +116,34 @@
- 	return 0;
- }
- 
-+void
-+gkbdproc(void *v)
-+{
-+	char *s;
-+	int n, eofs;
-+	Channel *c;
-+
-+	threadsetname("GKBDPROC");
-+	c = v;
-+
-+	eofs = 0;
-+	for(;;){
-+		if((s = recvp(c)) == nil)
-+			break;
-+		n = write(gkbdfd, s, strlen(s));	/* room for \0 */
-+		free(s);
-+		if(n < 0)
-+			break;
-+		if(n == 0){
-+			if(++eofs > 20)
-+				break;
-+			continue;
-+		}
-+		eofs = 0;
-+	}
-+	close(gkbdfd);
-+}
-+
- Filsys*
- filsysinit(Channel *cxfidalloc)
- {
-@@ -142,6 +171,12 @@
- 	post(srvwctl, "wctl", p0);
- 	close(p0);
- 
-+	if(cexecpipe(&p0, &gkbdfd) < 0)
-+		goto Rescue;
-+	snprint(srvgkbd, sizeof(srvgkbd), "/srv/riogkbd.%s.%lud", fs->user, (ulong)getpid());
-+	post(srvgkbd, "gkbd", p0);
-+	close(p0);
-+
- 	/*
- 	 * Start server processes
- 	 */
-@@ -150,6 +185,12 @@
- 		error("wctl channel");
- 	proccreate(wctlproc, c, 4096);
- 	threadcreate(wctlthread, c, 4096);
-+
-+	gkbdc = chancreate(sizeof(char*), 0);
-+	if(gkbdc == nil)
-+		error("gkbd channel");
-+	proccreate(gkbdproc, gkbdc, 4096);
-+
- 	proccreate(filsysproc, fs, 10000);
- 
- 	/*
-diff -r ab6ef2653d12 sys/src/cmd/rio/rio.c
---- a/sys/src/cmd/rio/rio.c	Mon Dec 07 15:15:02 2020 +0100
-+++ b/sys/src/cmd/rio/rio.c	Mon Dec 07 15:56:46 2020 +0100
-@@ -340,13 +340,20 @@
- keyboardthread(void*)
- {
- 	char *s;
-+	int mod4downnew;
- 
- 	threadsetname("keyboardthread");
- 
-+	mod4downnew = 0;
- 	while(s = recvp(kbdchan)){
--		if(*s == 'k' || *s == 'K')
-+		if(*s == 'k' || *s == 'K'){
- 			shiftdown = utfrune(s+1, Kshift) != nil;
--		if(input == nil || sendp(input->ck, s) <= 0)
-+			mod4downnew = utfrune(s+1, Kmod4) != nil;
-+		}
-+		if(mod4down || mod4downnew){
-+			mod4down = mod4downnew;
-+			sendp(gkbdc, s);
-+		}else if(input == nil || sendp(input->ck, s) <= 0)
- 			free(s);
- 	}
- }
--- a/README.md
+++ b/README.md
@@ -2,9 +2,8 @@
 
 Some kind of window management experiments with rio.  What does it do?
 It gives you virtual desktops to move window around to, by pressing
-keys, like i3. The window management code is written in `rc` and is done
-by reading and writing `/dev/wsys` for the most part. There are minimal
-changes to `rio` (to provide an additional `/srv/riogkbd.*` file).
+keys, like i3.  The window management is done by reading and writing
+`/dev/wsys` for the most part.
 
 *No guarantees, use at your own risk and blah.  This isn't supposed to
 work with drawterm.*
@@ -23,23 +22,37 @@
 
 ## Installation and usage
 
-Run `mk install` in this repo.  Apply `9front.diff` and rebuild/reinstall `rio`:
+Run `mk install` in this repo.
 
-	cat 9front.diff | @{cd /sys/src/cmd/rio && ape/patch -p5 && mk install}
+`riow` uses `/dev/kbdtap` of rio and should be placed as *last* in the chain of
+`kbdtap` users (after `ktrans` and/or `reform/shortcuts`).
 
-As a matter of fact, you can copy the original `rio` directory
-somewhere, apply the patch, and install under your user's `bin`
-instead.
+Running it alone:
 
-To start `riow`, either through `riostart`, or by hand:
+	riow </dev/kbdtap >/dev/kbdtap >[3]/dev/null
 
-	window -scroll riow
+Note that the current desktop number is printed to fd 3.
 
-If you're *NOT* using [bar](https://git.sr.ht/~ft/bar), run with
-`-hide` as well.
+Running with [bar](https://git.sr.ht/~ft/bar) is recommended.  For
+example, with additional `zuke(1)` controls and `reform/shortcuts`:
 
-Modify `riow` to your own needs.
+	; cat $home/rc/bin/Bar
+	#!/bin/rc
+	rfork ne
+	
+	fn bar {
+		sed -u 's/$/ │ ⏮ │ ⏯ │ ⏭/g' \
+		| /bin/bar \
+		| awk -v 'c=plumb -d audio ''key ' '
+			/⏮/{system(c"<''")}
+			/⏯/{system(c"p''")}
+			/⏭/{system(c">''")}
+			' >[2]/dev/null
+	}
+	</dev/kbdtap reform/shortcuts | riow >/dev/kbdtap |[3] bar
 
+This can be running on rio startup by adding `window Bar` to your `riostart` script.
+
 ## Keys
 
 ```
@@ -49,14 +62,11 @@
 Mod4-[0..9]         switch to a specific virtual desktop
 Mod4-shift-[0..9]   move the current window to a specific virtual desktop
 
-Mod4-[↑↓←→]         move the window (big steps)
-Mod4-shift-[↑↓←→]   move the window (small steps)
+Mod4-[↑↓←→]         move the window (small steps)
+Mod4-ctrl-[↑↓←→]    move the window (big steps)
 
-Mod4-ctrl-[↑↓←→]        drag bottom-right of the window (big steps)
-Mod4-ctrl-shift-[↑↓←→]  drag bottom-right of the window (small steps)
-
-Mod4-alt-[↑↓←→]        drag top-left of the window (big steps)
-Mod4-alt-shift-[↑↓←→]  drag top-left of the window (small steps)
+Mod4-shift-[↑↓←→]       resize the window (small steps)
+Mod4-shift-ctrl-[↑↓←→]  resize the window (big steps)
 ```
 
 ## Extras
--- a/gkbd.c
+++ /dev/null
@@ -1,72 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <keyboard.h>
-#include <ctype.h>
-
-static struct {
-	Rune r;
-	char *k;
-}keys[] = {
-	{'\t', "tab"},
-	{0x0a, "enter"},
-	{0x20, "space"},
-	{Kalt, "alt"},
-	{Kctl, "ctl"},
-	{Kdel, "del"},
-	{Kdown, "down"},
-	{Kesc, "esc"},
-	{Kmod4, "mod4"},
-	{Kleft, "left"},
-	{Kright, "right"},
-	{Kshift, "shift"},
-	{Kup, "up"},
-};
-
-void
-main(int argc, char **argv)
-{
-	char k[32], *s, out[128];
-	Rune r;
-	int n, i;
-
-	USED(argc); USED(argv);
-
-	for(;;){
-		s = k;
-		if((n = read(0, k, sizeof(k)-1)) <= 0)
-			break;
-		k[n] = 0;
-		n = 0;
-		out[n++] = *s++;
-		out[n++] = ' ';
-		while(*s){
-			s += chartorune(&r, s);
-			if(r == Runeerror){
-				n = -1;
-				break;
-			}
-
-			for(i = 0; i < nelem(keys); i++){
-				if(keys[i].r == r){
-					n += sprint(out+n, "%s ", keys[i].k);
-					break;
-				}
-			}
-
-			if(i >= nelem(keys)){
-				if(isprint(r))
-					n += sprint(out+n, "%C ", r);
-				else
-					n += sprint(out+n, "0x%x ", r);
-			}
-		}
-
-		if(n > 0){
-			out[n-1] = '\n';
-			if(write(1, out, n) != n)
-				break;
-		}
-	}
-
-	exits(nil);
-}
--- a/mkfile
+++ b/mkfile
@@ -1,16 +1,11 @@
 </$objtype/mkfile
 
-TARG=gkbd
+TARG=riow
 BIN=/$objtype/bin
 
 OFILES=\
-	gkbd.$O\
+	riow.$O\
 
-default:V:	all
+default:V: all
 
 </sys/src/cmd/mkone
-
-install:V: $BIN/$TARG /rc/bin/riow
-
-/rc/bin/riow: riow
-	cp $prereq $target
--- a/riow
+++ /dev/null
@@ -1,213 +1,0 @@
-#!/bin/rc
-rfork ne
-
-# these are shown on every desktop
-sticky=(bar 'cat clock' clock desktop faces kbmap stats winwatch)
-
-# window move/resize step (in pixels)
-step=96
-stepsmall=8
-
-alt=0
-ctl=0
-shift=0
-curdesktop=1
-
-fn scrsize {
-	s=`{dd -quiet 1 -if /dev/screen -bs 60 -count 1 >[2]/dev/null}
-	screenw=$s(4)
-	screenh=$s(5)
-}
-
-fn winread {
-	dd -quiet 1 -if $1/wctl -bs 120 -count 1 >[2]/dev/null
-}
-
-fn winlabel {
-	cat $1/label
-}
-
-fn curwindow {
-	cur=''
-	for(f in /dev/wsys/*){
-		a=`{winread $f}
-		if(~ $a(5) current)
-			cur=`{basename $f}
-	}
-	echo -n $cur
-}
-
-fn togglefullscreen {
-	winid=`{curwindow}
-	if(! ~ $#winid 0){
-		f=/dev/wsys/^$winid
-		a=`{winread $f}
-		scrsize
-		if(~ $a(1) 0 && ~ $a(2) 0 && ~ $a(3) $screenw && ~ $a(4) $screenh){
-			if(test -f /env/winsize_^$winid)
-				echo resize -r `{cat /env/winsize_^$winid} >$f/wctl >[2]/dev/null
-		}
-		if not {
-			echo -n $a(1 2 3 4) >/env/winsize_^$winid
-			echo resize -r 0 0 9999 9999 >$f/wctl >[2]/dev/null
-		}
-	}
-}
-
-fn desktop {
-	# find and remember the current window
-	if(~ $shift 0){
-		winid=`{curwindow}
-		if(! ~ $#winid 0)
-			echo -n $winid >/env/windeskcur_^$curdesktop
-		if not
-			rm -f /env/windeskcur_^$curdesktop
-	}
-
-	unhide=()
-	hide=()
-	for(f in /dev/wsys/*){
-		winid=`{basename $f}
-		if(! ~ `{winlabel $f} $sticky && ! test -f /env/winsticky_^$winid){
-			a=`{winread $f}
-
-			if(~ $shift 1){ # moving the current window elsewhere
-				if(~ $a(5) current){
-					echo -n $1 >/env/windesk_^$winid
-					hide=($hide $f/wctl)
-				}
-			}
-			if not {
-				# go through all visible windows
-				if(~ $a(6) visible){
-					# assign to the current one
-					echo -n $curdesktop >/env/windesk_^$winid
-					# it was visible, make sure riow doesn't think otherwise later on
-					rm -f /env/winhidden_^$winid
-					# and hide later
-					hide=($hide $f/wctl)
-				}
-				if not { # hidden
-					windesk=`{test -f /env/windesk_^$winid && cat /env/windesk_^$winid}
-					if(~ $windesk $1){ # unhide if should be shown now
-						# unless it's supposed to stay hidden, of course
-						if(! test -f /env/winhidden_^$winid)
-							unhide=($unhide $f/wctl)
-					}
-					# and remember it was hidden on the desktop we're still on
-					if(~ $windesk $curdesktop && ! test -f /env/winhidden_^$winid)
-						touch /env/winhidden_^$winid
-				}
-			}
-		}
-	}
-	if(~ $shift 0){
-		winid=()
-		if(test -f /env/windeskcur_^$1){
-			winid=`{cat /env/windeskcur_^$1}
-			if(test -d /dev/wsys/^$winid && test -f /env/windesk_^$winid && ~ `{cat /env/windesk_^$winid} $1)
-				;
-			if not
-				winid=()
-		}
-		for(w in $unhide)
-			echo unhide >$w
-		if(! ~ $#winid 0){
-			echo top >/dev/wsys/^$winid^/wctl >[2]/dev/null
-			echo current >/dev/wsys/^$winid^/wctl >[2]/dev/null
-		}
-		curdesktop=$1
-	}
-	for(w in $hide)
-		echo hide >$w &
-}
-
-fn togglesticky {
-	winid=`{curwindow}
-	if(! ~ $#winid 0){
-		if(test -f /env/winsticky_^$winid)
-			rm -f /env/winsticky_^$winid
-		if not
-			touch /env/winsticky_^$winid
-	}
-}
-
-fn arrows {
-	winid=`{curwindow}
-	if(! ~ $#winid 0){
-		f=/dev/wsys/^$winid
-		x=+0
-		y=+0
-		if(~ $2 0)
-			s=$step
-		if not
-			s=$stepsmall
-		~ $1 up    && y=-$s
-		~ $1 right && x=+$s
-		~ $1 down  && y=+$s
-		~ $1 left  && x=-$s
-
-		if(~ $3 0 && ~ $4 0)
-			echo move -minx $x -miny $y >> $f/wctl
-		if not {
-			if(~ $4 0)
-				echo resize -maxx $x -maxy $y >> $f/wctl
-			if not
-				echo resize -minx $x -miny $y >> $f/wctl
-		}
-	}
-}
-
-fn handle {
-	# K is key down
-	# k is key up
-	# c is key (and repeat) with the shift applied
-
-	key='' # depending on the shift state (1 → !)
-	ukey='' # stays the same regardless of the shift state
-	m=$1
-	state=0
-	if(~ $1 k)
-		state=1
-	shift
-	if(~ $m K && ~ $#* 0){
-		alt=0
-		ctl=0
-		shift=0
-	}
-	if not while(! ~ $#* 0){
-		if(~ $1 alt || ~ $1 ctl || ~ $1 shift)
-			eval '$1=$state'
-		if not if(~ $m c) key=$1
-		if not if(~ $m k) ukey=$1
-		shift
-	}
-	if(~ $ctl 0 && ~ $alt 0){
-		if(~ $shift 0){
-			if(~ $key enter)    window >[2]/dev/null
-			if not if(~ $key f) togglefullscreen
-			if not if(~ $key s) togglesticky
-		}
-		if(~ $ukey [0-9] && ! ~ $ukey $curdesktop)
-			desktop $ukey
-	}
-	arrows $key $shift $ctl $alt
-}
-
-scrsize
-
-fn work {
-	echo $curdesktop
-	while(s=`{read}){
-		olddesktop=$curdesktop
-		handle $s
-		if(! ~ $olddesktop $curdesktop)
-			echo $curdesktop
-	}
-}
-
-fn display {
-	bar || aux/statusmsg -k -w `{echo $screenw-100|bc}^,`{echo $screenh-60|bc}^,$screenw^,$screenh desktop
-}
-
-gkbd < `{echo $wsys | sed 's/rio\./riogkbd./'} | work | display
--- /dev/null
+++ b/riow.c
@@ -1,0 +1,346 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <keyboard.h>
+
+typedef struct W W;
+
+enum {
+	Mmod4 = 1<<0,
+	Mctl = 1<<1,
+	Mshift = 1<<2,
+
+	Step = 16,
+	Stepbig = 64,
+
+	Fvisible = 1<<0,
+	Fcurrent = 1<<1,
+	Fsticky = 1<<2,
+	Ffullscreen = 1<<3,
+};
+
+struct W {
+	int id;
+	Rectangle r;
+	int vd;
+	int flags;
+};
+
+static int vd = 1; /* current virtual desktop */
+static int wsys; /* rios /dev/wsys fd */
+static int mod;
+static W *ws, *wcur;
+static int wsn;
+
+static char *sticky[] = {
+	"bar", "cat clock", "clock", "faces", "kbmap", "stats", "winwatch",
+};
+
+static int
+wwctl(int id, int mode)
+{
+	char s[64];
+
+	snprint(s, sizeof(s), "/dev/wsys/%d/wctl", id);
+
+	return open(s, mode);
+}
+
+static void
+wsupdate(void)
+{
+	int i, k, n, f, seen, tn, dsn;
+	char s[256], *t[8];
+	W *newws, *w;
+	Dir *ds, *d;
+
+	seek(wsys, 0, 0);
+	if((dsn = dirreadall(wsys, &ds)) < 0)
+		sysfatal("/dev/wsys: %r");
+
+	newws = malloc(sizeof(W)*dsn);
+	wcur = nil;
+	for(i = 0, d = ds, w = newws; i < dsn; i++, d++){
+		if((f = wwctl(atoi(d->name), OREAD)) < 0)
+			continue;
+		n = read(f, s, sizeof(s)-1);
+		close(f);
+		if(n < 12)
+			continue;
+		s[n] = 0;
+		if((tn = tokenize(s, t, nelem(t))) < 6)
+			continue;
+
+		w->id = atoi(d->name);
+		w->r.min.x = atoi(t[0]);
+		w->r.min.y = atoi(t[1]);
+		w->r.max.x = atoi(t[2]);
+		w->r.max.y = atoi(t[3]);
+		w->vd = -1;
+		w->flags = 0;
+
+		/* move over the current state of the window */
+		for(k = 0, seen = 0; k < wsn; k++){
+			if(ws[k].id == w->id){
+				w->vd = ws[k].vd;
+				w->flags = ws[k].flags & ~Fvisible;
+				if(w->flags & Ffullscreen)
+					w->r = ws[k].r;
+				seen = 1;
+				break;
+			}
+		}
+
+		/* update current state */
+		for(k = 4; k < tn; k++){
+			if(strcmp(t[k], "current") == 0){
+				w->flags |= Fcurrent;
+				wcur = w;
+			}else if(strcmp(t[k], "visible") == 0){
+				w->flags |= Fvisible;
+			}
+		}
+
+		if(!seen){
+			/* not seen previously - set the new state for it */
+			w->vd = vd;
+			snprint(s, sizeof(s), "/dev/wsys/%d/label", w->id);
+			if((f = open(s, OREAD)) >= 0){
+				n = read(f, s, sizeof(s)-1);
+				close(f);
+				if(n > 0){
+					s[n] = 0;
+					for(k = 0; k < nelem(sticky); k++){
+						if(strcmp(sticky[k], s) == 0){
+							w->flags |= Fsticky;
+							break;
+						}
+					}
+				}
+			}
+		}
+		w++;
+	}
+
+	free(ds);
+	free(ws);
+	ws = newws;
+	wsn = w - newws;
+}
+
+static void
+spawn(char *s)
+{
+	if(rfork(RFPROC|RFNOWAIT|RFNAMEG|RFENVG|RFCFDG|RFREND) == 0)
+		execl("/bin/rc", "rc", s, nil);
+}
+
+static void
+togglefullscreen(void)
+{
+	int f;
+
+	if(wcur == nil || (f = wwctl(wcur->id, OWRITE)) < 0)
+		return;
+	wcur->flags ^= Ffullscreen;
+	if(wcur->flags & Ffullscreen)
+		fprint(f, "resize -r 0 0 9999 9999");
+	else
+		fprint(f, "resize -r %d %d %d %d", wcur->r.min.x, wcur->r.min.y, wcur->r.max.x, wcur->r.max.y);
+	close(f);
+}
+
+static void
+togglesticky(void)
+{
+	if(wcur != nil)
+		wcur->flags ^= Fsticky;
+}
+
+static void
+vdaction(int nvd)
+{
+	int f, wcurf;
+	W *w;
+
+	if(mod == Mmod4){
+		wcur = nil;
+		wcurf = -1;
+		for(w = ws; w < ws+wsn; w++){
+			if((f = wwctl(w->id, OWRITE)) < 0)
+				continue;
+			if(w->flags & Fvisible)
+				w->vd = vd;
+			if(w->vd != nvd && (w->flags & Fsticky) == 0)
+				fprint(f, "hide");
+			else{
+				fprint(f, "unhide");
+				if((w->flags & Fcurrent) && wcurf < 0){
+					wcur = w;
+					wcurf = f;
+					f = -1;
+				}
+			}
+			if(f >= 0)
+				close(f);
+		}
+		if(wcur != nil){
+			fprint(wcurf, "top");
+			fprint(wcurf, "current");
+			close(wcurf);
+		}
+		vd = nvd;
+		fprint(3, "%d\n", vd);
+	}else if(mod == (Mmod4 | Mshift) && wcur != nil){
+		if((f = wwctl(wcur->id, OWRITE)) >= 0){
+			fprint(f, "hide");
+			wcur->vd = nvd;
+			wcur = nil;
+			close(f);
+		}
+	}
+}
+
+static void
+arrowaction(int x, int y)
+{
+	int f;
+
+	if(wcur == nil || (f = wwctl(wcur->id, OWRITE)) < 0)
+		return;
+
+	x *= (mod & Mctl) ? Stepbig : Step;
+	y *= (mod & Mctl) ? Stepbig : Step;
+	if((mod & Mshift) == 0)
+		fprint(f, "move -minx %+d -miny %+d", x, y);
+	else
+		fprint(f, "resize -maxx %+d -maxy %+d -minx %+d -miny %+d", x, y, -x, -y);
+	close(f);
+}
+
+static void
+keyevent(Rune r)
+{
+	wsupdate();
+
+	if(r == '\n')
+		spawn("window");
+	else if(r == 'f')
+		togglefullscreen();
+	else if(r == 's')
+		togglesticky();
+	else if(r >= '0' && r <= '9')
+		vdaction(r - '0');
+	else if(r == Kup)
+		arrowaction(0, -1);
+	else if(r == Kdown)
+		arrowaction(0, 1);
+	else if(r == Kleft)
+		arrowaction(-1, 0);
+	else if(r == Kright)
+		arrowaction(1, 0);
+}
+
+static void
+process(char *s)
+{
+	char b[128], *p;
+	int n, o;
+	Rune r;
+
+	if(*s == 'K' && s[1] == 0)
+		mod = 0;
+
+	o = 0;
+	b[o++] = *s;
+	for(p = s+1; *p != 0; p += n){
+		if((n = chartorune(&r, p)) == 1 && r == Runeerror){
+			/* bail out */
+			n = strlen(p);
+			memmove(b+o, p, n);
+			o += n;
+			p += n;
+			break;
+		}
+
+		if(*s == 'c' && (mod & Mmod4) != 0){
+			keyevent(r);
+			continue;
+		}
+
+		if(*s == 'k'){
+			if(r == Kmod4)
+				mod |= Mmod4;
+			else if(r == Kctl)
+				mod |= Mctl;
+			else if(r == Kshift)
+				mod |= Mshift;
+			else if(r >= '0' && r <= '9' && (mod & (Mshift|Mmod4)) == (Mshift|Mmod4)){
+				keyevent(r);
+				continue;
+			}
+		}else if(*s == 'K'){
+			if(r == Kmod4)
+				mod &= ~Mmod4;
+			else if(r == Kctl)
+				mod &= ~Mctl;
+			else if(r == Kshift)
+				mod &= ~Mshift;
+		}
+
+		memmove(b+o, p, n);
+		o += n;
+	}
+
+	/* all runes filtered out - ignore completely */
+	if(o == 1 && p-s > 1)
+		return;
+
+	b[o++] = 0;
+	if(write(1, b, o) != o)
+		exits(nil);
+}
+
+static void
+usage(void)
+{
+	fprint(2, "usage: [-l light_step] [-v vol_step] %s\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+	char b[128];
+	int i, j, n;
+
+	ARGBEGIN{
+	default:
+		usage();
+	}ARGEND
+
+	if((wsys = open("/dev/wsys", OREAD)) < 0)
+		sysfatal("%r");
+
+	/* initial state */
+	wsupdate();
+	for(i = 0; i < wsn; i++)
+		ws[i].vd = vd;
+	fprint(3, "%d\n", vd);
+
+	for(i = 0;;){
+		if((n = read(0, b+i, sizeof(b)-i)) <= 0)
+			break;
+		n += i;
+		for(j = 0; j < n; j++){
+			if(b[j] == 0){
+				process(b+i);
+				i = j+1;
+			}
+		}
+		memmove(b, b+i, j-i);
+		i -= j;
+	}
+
+	exits(nil);
+}