ref: d6e903846391bca5c5b5e683372c339b03eb7279
parent: 1cae79ca64cfff5b19120f8309fac320da736c76
author: qwx <qwx@sciops.net>
date: Tue Aug 5 06:13:06 EDT 2025
kbdfs: expose right shift and control keys not changing kbmaps and vmx
--- /dev/null
+++ b/sys/include/keyboard.h
@@ -1,0 +1,79 @@
+#pragma src "/sys/src/libdraw"
+#pragma lib "libdraw.a"
+
+typedef struct Keyboardctl Keyboardctl;
+typedef struct Channel Channel;
+
+struct Keyboardctl
+{
+ Channel *c; /* chan(Rune)[20] */
+
+ char *file;
+ int consfd; /* to cons file */
+ int ctlfd; /* to ctl file */
+ int pid; /* of slave proc */
+};
+
+
+extern Keyboardctl* initkeyboard(char*);
+extern int ctlkeyboard(Keyboardctl*, char*);
+extern void closekeyboard(Keyboardctl*);
+
+enum {
+ KF= 0xF000, /* Rune: beginning of private Unicode space */
+ Spec= 0xF800,
+ PF= Spec|0x20, /* num pad function key */
+ Kview= Spec|0x00, /* view (shift window up) */
+ /* KF|1, KF|2, ..., KF|0xC is *respectively* F1, F2, ..., F12 */
+ Khome= KF|0x0D,
+ Kup= KF|0x0E,
+ Kdown= Kview,
+ Kpgup= KF|0x0F,
+ Kprint= KF|0x10,
+ Kleft= KF|0x11,
+ Kright= KF|0x12,
+ Kpgdown= KF|0x13,
+ Kins= KF|0x14,
+
+ Kalt= KF|0x15,
+ Kshift= KF|0x16,
+ Kctl= KF|0x17,
+
+ Kend= KF|0x18,
+ Kscroll= KF|0x19,
+ Kscrolloneup= KF|0x20,
+ Kscrollonedown= KF|0x21,
+
+ /* multimedia keys - no refunds */
+ Ksbwd= KF|0x22, /* skip backwards */
+ Ksfwd= KF|0x23, /* skip forward */
+ Kpause= KF|0x24, /* play/pause */
+ Kvoldn= KF|0x25, /* volume decrement */
+ Kvolup= KF|0x26, /* volume increment */
+ Kmute= KF|0x27, /* (un)mute */
+ Kbrtdn= KF|0x28, /* brightness decrement */
+ Kbrtup= KF|0x29, /* brightness increment */
+
+ Krshift= KF|0x30,
+ Krctl= KF|0x31,
+
+ Ksoh= 0x01,
+ Kstx= 0x02,
+ Ketx= 0x03,
+ Keof= 0x04,
+ Kenq= 0x05,
+ Kack= 0x06,
+ Kbs= 0x08,
+ Knack= 0x15,
+ Ketb= 0x17,
+ Kdel= 0x7f,
+ Kesc= 0x1b,
+
+ Kbreak= Spec|0x61,
+ Kcaps= Spec|0x64,
+ Knum= Spec|0x65,
+ Kmiddle= Spec|0x66,
+ Kaltgr= Spec|0x67,
+ Kmod4= Spec|0x68,
+ Kmouse= Spec|0x100,
+};
--- a/sys/src/cmd/aux/kbdfs/kbdfs.c
+++ b/sys/src/cmd/aux/kbdfs/kbdfs.c
@@ -167,7 +167,7 @@
[0x18] 'o', 'p', '[', ']', '\n', Kctl, 'a', 's',
[0x20] 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';',
[0x28] '\'', '`', Kshift, '\\', 'z', 'x', 'c', 'v',
- [0x30] 'b', 'n', 'm', ',', '.', '/', Kshift, '*',
+ [0x30] 'b', 'n', 'm', ',', '.', '/', Krshift,'*',
[0x38] Kalt, ' ', Kctl, KF|1, KF|2, KF|3, KF|4, KF|5,
[0x40] KF|6, KF|7, KF|8, KF|9, KF|10, Knum, Kscroll,'7',
[0x48] '8', '9', '-', '4', '5', '6', '+', '1',
@@ -187,7 +187,7 @@
[0x18] 'O', 'P', '{', '}', '\n', Kctl, 'A', 'S',
[0x20] 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':',
[0x28] '"', '~', Kshift, '|', 'Z', 'X', 'C', 'V',
- [0x30] 'B', 'N', 'M', '<', '>', '?', Kshift, '*',
+ [0x30] 'B', 'N', 'M', '<', '>', '?', Krshift,'*',
[0x38] Kalt, ' ', Kctl, KF|1, KF|2, KF|3, KF|4, KF|5,
[0x40] KF|6, KF|7, KF|8, KF|9, KF|10, Knum, Kscroll,'7',
[0x48] '8', '9', '-', '4', '5', '6', '+', '1',
@@ -204,7 +204,7 @@
[0x00] 0, 0, 0, 0, 0, 0, 0, 0,
[0x08] 0, 0, 0, 0, 0, 0, 0, 0,
[0x10] Ksbwd, Kbrtdn, 0, 0, 0, 0, 0, 0,
- [0x18] 0, Ksfwd, Kbrtup, 0, '\n', Kctl, 0, 0,
+ [0x18] 0, Ksfwd, Kbrtup, 0, '\n', Krctl, 0, 0,
[0x20] Kmute, 0, Kpause, 0, 0, 0, 0, 0,
[0x28] 0, 0, 0, 0, 0, 0, Kvoldn, 0,
[0x30] Kvolup, 0, 0, 0, 0, '/', 0, Kprint,
@@ -224,7 +224,7 @@
[0x00] 0, 0, 0, 0, 0, 0, 0, 0,
[0x08] 0, 0, 0, 0, 0, 0, 0, 0,
[0x10] 0, 0, 0, 0, 0, 0, 0, 0,
- [0x18] 0, 0, 0, 0, 0, 0, 0, 0,
+ [0x18] 0, 0, 0, 0, 0, Krctl, 0, 0,
[0x20] 0, 0, 0, 0, 0, 0, 0, 0,
[0x28] 0, 0, 0, 0, 0, 0, 0, 0,
[0x30] 0, 0, 0, 0, 0, 0, 0, 0,
@@ -244,7 +244,7 @@
[0x00] 0, 0, 0, 0, 0, 0, 0, 0,
[0x08] 0, 0, 0, 0, 0, 0, 0, 0,
[0x10] 0, 0, 0, 0, 0, 0, 0, 0,
- [0x18] 0, 0, 0, 0, 0, 0, 0, 0,
+ [0x18] 0, 0, 0, 0, 0, Krctl, 0, 0,
[0x20] 0, 0, 0, 0, 0, 0, 0, 0,
[0x28] 0, 0, 0, 0, 0, 0, 0, 0,
[0x30] 0, 0, 0, 0, 0, 0, 0, 0,
@@ -397,27 +397,6 @@
}
void
-emergencywarp(void)
-{
- int fd;
-
- if(debug)
- return;
-
- if(access("/srv/cwfs.cmd", AEXIST) == 0 && (fd = eopen("/srv/cwfs.cmd", OWRITE)) >= 0){
- fprint(fd, "halt\n");
- close(fd);
- }
- if(access("/srv/hjfs.cmd", AEXIST) == 0 && (fd = eopen("/srv/hjfs.cmd", OWRITE)) >= 0){
- fprint(fd, "halt\n");
- close(fd);
- }
- fprint(2, "emergency warp!\n");
- sleep(1000);
- reboot();
-}
-
-void
shutdown(void)
{
if(notefd >= 0)
@@ -496,9 +475,6 @@
if(scan->caps && key.r<='z' && key.r>='a')
key.r += 'A' - 'a';
- if(scan->ctl && scan->altgr && key.r == Kdel)
- emergencywarp();
-
if(scan->ctl && scan->alt && key.r == Kdel){
if(scan->shift)
shiftup();
@@ -511,9 +487,11 @@
switch(key.r){
case Kshift:
+ case Krshift:
scan->shift = key.down;
break;
case Kctl:
+ case Krctl:
scan->ctl = key.down;
break;
case Kaltgr:
@@ -576,22 +554,27 @@
if(kbtabs[Lnone][i] == k.r || kbtabs[Lshift][i] == k.r || (i >= 16 && kbtabs[Lctl][i] == k.r)){
/* assign button from kbtab */
k.b = kbtabs[Lnone][i];
+
/* handle ^X forms */
if(k.r == kbtabs[Lnone][i] && kbtabs[Lctl][i] && !a->shift && !a->altgr && a->ctl)
k.r = kbtabs[Lctl][i];
break;
+ } else if(kbtabs[Lesc1][i] == k.r || kbtabs[Lshiftesc1][i] == k.r){
+ /* check escaped scancodes too */
+ k.b = kbtabs[Lesc1][i];
+ break;
}
}
/* button unknown to kbtab, use rune if no modifier keys are active */
if(k.b == 0 && !a->shift && !a->altgr && !a->ctl)
k.b = k.r;
- if(k.r == Kshift)
+ if(k.r == Kshift || k.r == Krshift)
a->shift = k.down;
else if(k.r == Kaltgr)
a->altgr = k.down;
else if(k.r == Kmod4)
a->mod4 = k.down;
- else if(k.r == Kctl)
+ else if(k.r == Kctl || k.r == Krctl)
a->ctl = k.down;
send(keychan, &k);
break;
@@ -784,6 +767,7 @@
case Kcaps:
case Knum:
case Kshift:
+ case Krshift:
case Kaltgr:
case Kmod4:
/* ignore modifiers */
@@ -790,6 +774,7 @@
continue;
case Kctl:
+ case Krctl:
case Kalt:
/* composing escapes */
return 1;
@@ -828,7 +813,7 @@
continue;
}
- if(r == Kctl){
+ if(r == Kctl || r == Krctl){
ctl = 1;
continue;
}
@@ -926,7 +911,7 @@
break;
}
- if(mctlfd >= 0 && key.r == Kshift){
+ if(mctlfd >= 0 && (key.r == Kshift || key.r == Krshift)){
if(key.down){
fprint(mctlfd, "buttonmap 132");
} else {
--- /dev/null
+++ b/sys/src/cmd/evdump.c
@@ -1,0 +1,339 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include <bio.h>
+#include <draw.h>
+#include <keyboard.h>
+#include <mouse.h>
+#include <thread.h>
+
+typedef struct {
+ u8int e;
+ u8int c;
+ Rune r;
+}K;
+
+enum {
+ Kmbase = 0xf0000,
+};
+
+static K k[10*128];
+static int nk;
+static int kbd;
+static Biobuf *kbmap, *wctl;
+static char *layertab[] = {
+ "none", "shift", "esc", "altgr",
+ "ctl", "ctlesc", "shiftesc", "shiftaltgr",
+ "mod4", "altgrmod4",
+};
+
+static char *
+k2s(Rune r)
+{
+ switch(r){
+ case Ksbwd: return "Ksbwd";
+ case Ksfwd: return "Ksfwd";
+ case Kpause: return "Kpause";
+ case Kvoldn: return "Kvoldn";
+ case Kvolup: return "Kvolup";
+ case Kmute: return "Kmute";
+ case Kbrtdn: return "Kbrtdn";
+ case Kbrtup: return "Kbrtup";
+ case Kack: return "Kack";
+ case Kalt: return "Kalt";
+ case Kaltgr: return "Kaltgr";
+ case Kbreak: return "Kbreak";
+ case Kbs: return "Kbs";
+ case Kcaps: return "Kcaps";
+ case Kctl: return "Kctl";
+ case Kdel: return "Kdel";
+ case Kdown: return "Kdown";
+ case Kend: return "Kend";
+ case Kenq: return "Kenq";
+ case Keof: return "Keof";
+ case Kesc: return "Kesc";
+ case Ketb: return "Ketb";
+ case Ketx: return "Ketx";
+ case Khome: return "Khome";
+ case Kins: return "Kins";
+ case Kleft: return "Kleft";
+ case Kmiddle: return "Kmiddle";
+ case Kmod4: return "Kmod4";
+ case Knack: return "Knack";
+ case Knum: return "Knum";
+ case Kpgdown: return "Kpgdown";
+ case Kpgup: return "Kpgup";
+ case Kprint: return "Kprint";
+ case Krctl: return "Krctl";
+ case Kright: return "Kright";
+ case Krshift: return "Krshift";
+ case Kscroll: return "Kscroll";
+ case Kscrollonedown: return "Kscrollonedown";
+ case Kscrolloneup: return "Kscrolloneup";
+ case Kshift: return "Kshift";
+ case Ksoh: return "Ksoh";
+ case Kstx: return "Kstx";
+ case Kup: return "Kup";
+ case KF|1: return "F1";
+ case KF|2: return "F2";
+ case KF|3: return "F3";
+ case KF|4: return "F4";
+ case KF|5: return "F5";
+ case KF|6: return "F6";
+ case KF|7: return "F7";
+ case KF|8: return "F8";
+ case KF|9: return "F9";
+ case KF|10: return "F10";
+ case KF|11: return "F11";
+ case KF|12: return "F12";
+ case Kmouse|1: return "Kmouse1";
+ case Kmouse|2: return "Kmouse2";
+ case Kmouse|3: return "Kmouse3";
+ case Kmouse|4: return "Kmouse4";
+ case Kmouse|5: return "Kmouse5";
+ case '\n': return "\\n";
+ }
+
+ return nil;
+}
+
+static int
+kmreset(void *, char *)
+{
+ int i;
+
+ for(i = 0; i < nk; i++)
+ Bprint(kbmap, "%d\t%d\t%d\n", k[i].e, k[i].c, k[i].r);
+ Bflush(kbmap);
+
+ return 0;
+}
+
+static void
+kmset(void)
+{
+ int i;
+
+ for(i = 0; i < nk; i++)
+ Bprint(kbmap, "%d\t%d\t%d\n", k[i].e, k[i].c, Kmbase+i);
+ Bflush(kbmap);
+}
+
+static void
+key(Rune r, char *type)
+{
+ char *s, t[32];
+ Rune c;
+ K q;
+
+ if(r < Kmbase || r >= Kmbase+nk){
+ if((s = k2s(r)) != nil)
+ snprint(t, sizeof(t), "%s", s);
+ else if((r < 0x80 && isprint(r)) || r >= 0x20)
+ snprint(t, sizeof(t), "%C (0x%x)", r, r);
+ else
+ snprint(t, sizeof(t), "0x%x", r);
+ return;
+ }
+ q = k[r-Kmbase];
+ c = q.r;
+ if((s = k2s(c)) != nil)
+ snprint(t, sizeof(t), "%s", s);
+ else if((c < 0x80 && isprint(c)) || c >= 0x20)
+ snprint(t, sizeof(t), "%C (0x%x)", c, c);
+ else
+ snprint(t, sizeof(t), "0x%x", c);
+
+ print("key %s %s: %d %d 0x%ux\n", type, t, q.e, q.c, q.r);
+}
+
+static void
+wctlproc(void *)
+{
+ char s[256], *t[8];
+ int wctl, n;
+
+ if((wctl = open("/dev/wctl", OREAD)) < 0)
+ sysfatal("%r");
+ for(;;){
+ if((n = read(wctl, s, sizeof(s)-1)) <= 0)
+ break;
+ s[n] = 0;
+ if(tokenize(s, t, nelem(t)) < 6)
+ continue;
+
+ if(strcmp(t[4], "current") == 0)
+ kmset();
+ else if(strcmp(t[4], "notcurrent") == 0)
+ kmreset(nil, nil);
+
+ print("wctl %s %s\n", t[4], t[5]);
+ }
+ close(wctl);
+
+ threadexits(nil);
+}
+
+static void
+kbproc(void *)
+{
+ char *s, buf[128], buf2[128];
+ int kbd, n;
+ Rune r;
+
+ threadsetname("kbproc");
+ if((kbd = open("/dev/kbd", OREAD)) < 0)
+ sysfatal("/dev/kbd: %r");
+
+ buf2[0] = 0;
+ buf2[1] = 0;
+ buf[0] = 0;
+ for(;;){
+ if(buf[0] != 0){
+ n = strlen(buf)+1;
+ memmove(buf, buf+n, sizeof(buf)-n);
+ }
+ if(buf[0] == 0){
+ n = read(kbd, buf, sizeof(buf)-1);
+ if(n <= 0)
+ break;
+ buf[n-1] = 0;
+ buf[n] = 0;
+ }
+
+ switch(buf[0]){
+ case 'k':
+ for(s = buf+1; *s;){
+ s += chartorune(&r, s);
+ if(utfrune(buf2+1, r) == nil)
+ key(r, "down");
+ }
+ break;
+ case 'K':
+ for(s = buf2+1; *s;){
+ s += chartorune(&r, s);
+ if(utfrune(buf+1, r) == nil)
+ key(r, "up");
+ }
+ break;
+ case 'c':
+ if(chartorune(&r, buf+1) > 0 && r != Runeerror)
+ key(r, "repeat");
+ default:
+ continue;
+ }
+
+ strcpy(buf2, buf);
+ }
+
+ close(kbd);
+
+ threadexits(nil);
+}
+
+static void
+usage(void)
+{
+ fprint(2, "usage: %s\n", argv0);
+ threadexitsall("usage");
+}
+
+void
+threadmain(int argc, char **argv)
+{
+ Mousectl *mctl;
+ char tmp[32], *f[4], *s;
+ int nf, e;
+ Mouse m;
+ enum { Cmouse, Cresize, Numchan };
+ Alt a[Numchan+1] = {
+ [Cmouse] = { nil, &m, CHANRCV },
+ [Cresize] = { nil, nil, CHANRCV },
+ { nil, nil, CHANEND },
+ };
+
+ ARGBEGIN{
+ default:
+ usage();
+ }ARGEND
+
+ if(argc != 0)
+ usage();
+
+ if((kbmap = Bopen("/dev/kbmap", OREAD)) == nil)
+ sysfatal("%r");
+
+ nk = 0;
+ for(;;){
+ if((s = Brdline(kbmap, '\n')) == nil)
+ break;
+ s[Blinelen(kbmap)-1] = '\0';
+ nf = getfields(s, f, nelem(f), 1, " \t");
+ if(nf < 3)
+ continue;
+ for(e = 0; e < nelem(layertab); e++)
+ if(strcmp(f[0], layertab[e]) == 0)
+ break;
+ if(e >= nelem(layertab)){
+ e = strtoul(f[0], &s, 0);
+ if(*s != '\0')
+ continue;
+ }
+ k[nk].e = e;
+ k[nk].c = strtoul(f[1], &s, 0);
+ if(*s != '\0')
+ continue;
+ k[nk].r = strtoul(f[2], &s, 0);
+ if(*s != '\0')
+ continue;
+ if(++nk >= nelem(k))
+ break;
+ }
+ Bterm(kbmap);
+
+ if((kbmap = Bopen("/dev/kbmap", OWRITE)) == nil)
+ sysfatal("%r");
+ threadnotify(kmreset, 1);
+
+ snprint(tmp, sizeof(tmp), "-pid %d -dx %d -dy %d", getpid(), 256, 256);
+ newwindow(tmp);
+
+ if(initdraw(nil, nil, "evdump") < 0)
+ sysfatal("initdraw: %r");
+ if((mctl = initmouse(nil, screen)) == nil)
+ sysfatal("initmouse: %r");
+ a[Cmouse].c = mctl->c;
+ a[Cresize].c = mctl->resizec;
+
+ proccreate(kbproc, nil, mainstacksize);
+ proccreate(wctlproc, nil, mainstacksize);
+
+ for(;;){
+ draw(screen, screen->r, display->black, nil, ZP);
+
+ switch(alt(a)){
+ case -1:
+ goto end;
+
+ case Cmouse:
+ print(
+ "mouse buttons 0x%x x %d y %d\n",
+ m.buttons,
+ m.xy.x, m.xy.y
+ );
+ break;
+
+ case Cresize:
+ getwindow(display, Refnone);
+ print(
+ "resize min %d %d max %d %d\n",
+ screen->r.min.x, screen->r.min.y,
+ screen->r.max.x, screen->r.max.y
+ );
+ break;
+ }
+ }
+
+end:
+ threadexitsall(nil);
+}
--- /dev/null
+++ b/sys/src/cmd/rio/wind.c
@@ -1,0 +1,1850 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include <complete.h>
+#include "dat.h"
+#include "fns.h"
+
+Window*
+wlookid(int id)
+{
+ int i;
+
+ for(i=0; i<nwindow; i++)
+ if(window[i]->id == id)
+ return window[i];
+ return nil;
+}
+
+Window*
+wpointto(Point pt)
+{
+ int i;
+ Window *v, *w;
+
+ w = nil;
+ for(i=0; i<nwindow; i++){
+ v = window[i];
+ if(ptinrect(pt, v->screenr))
+ if(w==nil || v->topped>w->topped)
+ w = v;
+ }
+ return w;
+}
+
+static int topped;
+
+void
+wtopme(Window *w)
+{
+ if(w!=nil && w->i!=nil && w->topped!=topped){
+ w->topped = ++topped;
+ topwindow(w->i);
+ flushimage(display, 1);
+ }
+}
+
+void
+wbottomme(Window *w)
+{
+ if(w!=nil && w->i!=nil){
+ w->topped = - ++topped;
+ bottomwindow(w->i);
+ flushimage(display, 1);
+ }
+}
+
+Window*
+wtop(Point pt)
+{
+ Window *w;
+
+ w = wpointto(pt);
+ if(w!=nil){
+ incref(w);
+ wcurrent(w);
+ wtopme(w);
+ wsendctlmesg(w, Topped, ZR, nil);
+ wclose(w);
+ }
+ return w;
+}
+
+void
+wcurrent(Window *w)
+{
+ Channel *c;
+
+ if(input == nil){
+ input = w;
+ return;
+ }
+ if(w == input)
+ return;
+ incref(input);
+ c = chancreate(sizeof(Window*), 0);
+ wsendctlmesg(input, Repaint, ZR, c);
+ sendp(c, w); /* send the new input */
+ wclose(recvp(c)); /* release old input */
+ chanfree(c);
+}
+
+void
+wuncurrent(Window *w)
+{
+ Channel *c;
+
+ if(input == nil || w != input)
+ return;
+ c = chancreate(sizeof(Window*), 0);
+ wsendctlmesg(w, Repaint, ZR, c);
+ sendp(c, nil);
+ recvp(c);
+ chanfree(c);
+}
+
+static Cursor *lastcursor;
+
+void
+riosetcursor(Cursor *p)
+{
+ if(p==lastcursor)
+ return;
+ setcursor(mousectl, p);
+ lastcursor = p;
+}
+
+void
+wsetcursor(Window *w, int force)
+{
+ Cursor *p;
+
+ if(menuing || sweeping || (w!=input && wpointto(mouse->xy)!=w))
+ return;
+ if(w==nil)
+ p = nil;
+ else {
+ p = w->cursorp;
+ if(p==nil && w->holding)
+ p = &whitearrow;
+ }
+ if(p && force) /* force cursor reload */
+ lastcursor = nil;
+ riosetcursor(p);
+}
+
+static void
+waddraw(Window *w, Rune *r, int nr)
+{
+ w->raw = runerealloc(w->raw, w->nraw+nr);
+ runemove(w->raw+w->nraw, r, nr);
+ w->nraw += nr;
+}
+
+enum
+{
+ HiWater = 640000, /* max size of history */
+ LoWater = 400000, /* min size of history after max'ed */
+ MinWater = 20000, /* room to leave available when reallocating */
+};
+
+static uint
+winsert(Window *w, Rune *r, int n, uint q0)
+{
+ uint m;
+
+ if(n == 0)
+ return q0;
+ if(w->nr+n>HiWater && q0>=w->org && q0>=w->qh){
+ m = min(HiWater-LoWater, min(w->org, w->qh));
+ w->org -= m;
+ w->qh -= m;
+ if(w->q0 > m)
+ w->q0 -= m;
+ else
+ w->q0 = 0;
+ if(w->q1 > m)
+ w->q1 -= m;
+ else
+ w->q1 = 0;
+ w->nr -= m;
+ runemove(w->r, w->r+m, w->nr);
+ q0 -= m;
+ }
+ if(w->nr+n > w->maxr){
+ /*
+ * Minimize realloc breakage:
+ * Allocate at least MinWater
+ * Double allocation size each time
+ * But don't go much above HiWater
+ */
+ m = max(min(2*(w->nr+n), HiWater), w->nr+n)+MinWater;
+ if(m > HiWater)
+ m = max(HiWater+MinWater, w->nr+n);
+ if(m > w->maxr){
+ w->r = runerealloc(w->r, m);
+ w->maxr = m;
+ }
+ }
+ runemove(w->r+q0+n, w->r+q0, w->nr-q0);
+ runemove(w->r+q0, r, n);
+ w->nr += n;
+ /* if output touches, advance selection, not qh; works best for keyboard and output */
+ if(q0 <= w->q1)
+ w->q1 += n;
+ if(q0 <= w->q0)
+ w->q0 += n;
+ if(q0 < w->qh)
+ w->qh += n;
+ if(q0 < w->org)
+ w->org += n;
+ else if(q0 <= w->org+w->nchars)
+ frinsert(w, r, r+n, q0-w->org);
+ return q0;
+}
+
+static void
+wfill(Window *w)
+{
+ Rune *rp;
+ int i, n, m, nl;
+
+ while(w->lastlinefull == FALSE){
+ n = w->nr-(w->org+w->nchars);
+ if(n == 0)
+ break;
+ if(n > 2000) /* educated guess at reasonable amount */
+ n = 2000;
+ rp = w->r+(w->org+w->nchars);
+
+ /*
+ * it's expensive to frinsert more than we need, so
+ * count newlines.
+ */
+ nl = w->maxlines-w->nlines;
+ m = 0;
+ for(i=0; i<n; ){
+ if(rp[i++] == '\n'){
+ m++;
+ if(m >= nl)
+ break;
+ }
+ }
+ frinsert(w, rp, rp+i, w->nchars);
+ }
+}
+
+static void
+wsetselect(Window *w, uint q0, uint q1)
+{
+ int p0, p1;
+
+ /* w->p0 and w->p1 are always right; w->q0 and w->q1 may be off */
+ w->q0 = q0;
+ w->q1 = q1;
+ /* compute desired p0,p1 from q0,q1 */
+ p0 = q0-w->org;
+ p1 = q1-w->org;
+ if(p0 < 0)
+ p0 = 0;
+ if(p1 < 0)
+ p1 = 0;
+ if(p0 > w->nchars)
+ p0 = w->nchars;
+ if(p1 > w->nchars)
+ p1 = w->nchars;
+ if(p0==w->p0 && p1==w->p1)
+ return;
+ /* screen disagrees with desired selection */
+ if(w->p1<=p0 || p1<=w->p0 || p0==p1 || w->p1==w->p0){
+ /* no overlap or too easy to bother trying */
+ frdrawsel(w, frptofchar(w, w->p0), w->p0, w->p1, 0);
+ frdrawsel(w, frptofchar(w, p0), p0, p1, 1);
+ goto Return;
+ }
+ /* overlap; avoid unnecessary painting */
+ if(p0 < w->p0){
+ /* extend selection backwards */
+ frdrawsel(w, frptofchar(w, p0), p0, w->p0, 1);
+ }else if(p0 > w->p0){
+ /* trim first part of selection */
+ frdrawsel(w, frptofchar(w, w->p0), w->p0, p0, 0);
+ }
+ if(p1 > w->p1){
+ /* extend selection forwards */
+ frdrawsel(w, frptofchar(w, w->p1), w->p1, p1, 1);
+ }else if(p1 < w->p1){
+ /* trim last part of selection */
+ frdrawsel(w, frptofchar(w, p1), p1, w->p1, 0);
+ }
+
+ Return:
+ w->p0 = p0;
+ w->p1 = p1;
+}
+
+static void
+wborder(Window *w, int type)
+{
+ Image *col;
+
+ if(w->i == nil)
+ return;
+ if(w->holding){
+ if(type == Selborder)
+ col = holdcol;
+ else
+ col = paleholdcol;
+ }else{
+ if(type == Selborder)
+ col = titlecol;
+ else
+ col = lighttitlecol;
+ }
+ border(w->i, w->i->r, Selborder, col, ZP);
+}
+
+static void
+wsetcols(Window *w, int topped)
+{
+ if(w->holding)
+ if(topped)
+ w->cols[TEXT] = holdcol;
+ else
+ w->cols[TEXT] = lightholdcol;
+ else
+ if(topped)
+ w->cols[TEXT] = cols[TEXT];
+ else
+ w->cols[TEXT] = paletextcol;
+}
+
+void
+wsetname(Window *w)
+{
+ int i, n;
+ char err[ERRMAX];
+
+ n = snprint(w->name, sizeof(w->name)-2, "window.%d.%d", w->id, w->namecount++);
+ for(i='A'; i<='Z'; i++){
+ if(nameimage(w->i, w->name, 1) > 0)
+ return;
+ errstr(err, sizeof err);
+ if(strcmp(err, "image name in use") != 0)
+ break;
+ w->name[n] = i;
+ w->name[n+1] = 0;
+ }
+ w->name[0] = 0;
+ fprint(2, "rio: setname failed: %s\n", err);
+}
+
+static void
+wresize(Window *w, Image *i)
+{
+ Rectangle r;
+
+ w->i = i;
+ w->mc.image = i;
+ r = insetrect(i->r, Selborder+1);
+ w->scrollr = r;
+ w->scrollr.max.x = r.min.x+Scrollwid;
+ w->lastsr = ZR;
+ r.min.x += Scrollwid+Scrollgap;
+ frclear(w, FALSE);
+ frinit(w, r, w->font, w->i, cols);
+ wsetcols(w, w == input);
+ w->maxtab = maxtab*stringwidth(w->font, "0");
+ if(!w->mouseopen || !w->winnameread){
+ r = insetrect(w->i->r, Selborder);
+ draw(w->i, r, cols[BACK], nil, w->entire.min);
+ wfill(w);
+ wsetselect(w, w->q0, w->q1);
+ wscrdraw(w);
+ }
+ if(w == input)
+ wborder(w, Selborder);
+ else
+ wborder(w, Unselborder);
+ flushimage(display, 1);
+ wsetname(w);
+ w->topped = ++topped;
+ w->resized = TRUE;
+ w->winnameread = FALSE;
+ w->mc.buttons = 0; /* avoid re-triggering clicks on resize */
+ w->mouse.counter++;
+ w->wctlready = 1;
+}
+
+static void
+wrepaint(Window *w)
+{
+ wsetcols(w, w == input);
+ if(!w->mouseopen || !w->winnameread)
+ frredraw(w);
+ if(w == input)
+ wborder(w, Selborder);
+ else
+ wborder(w, Unselborder);
+}
+
+static void
+wrefresh(Window *w)
+{
+ Rectangle r;
+
+ if(w == input)
+ wborder(w, Selborder);
+ else
+ wborder(w, Unselborder);
+ r = insetrect(w->i->r, Selborder);
+ draw(w->i, r, w->cols[BACK], nil, w->entire.min);
+ wfill(w);
+ w->ticked = 0;
+ if(w->p0 > 0)
+ frdrawsel(w, frptofchar(w, 0), 0, w->p0, 0);
+ if(w->p1 < w->nchars)
+ frdrawsel(w, frptofchar(w, w->p1), w->p1, w->nchars, 0);
+ frdrawsel(w, frptofchar(w, w->p0), w->p0, w->p1, 1);
+ w->lastsr = ZR;
+ wscrdraw(w);
+}
+
+/*
+ * Need to do this in a separate proc because if process we're interrupting
+ * is dying and trying to print tombstone, kernel is blocked holding p->debug lock.
+ */
+static void
+interruptproc(void *v)
+{
+ int *notefd;
+
+ notefd = v;
+ write(*notefd, "interrupt", 9);
+ close(*notefd);
+ free(notefd);
+}
+
+typedef struct Completejob Completejob;
+struct Completejob
+{
+ char *dir;
+ char *str;
+ Window *win;
+};
+
+static void
+completeproc(void *arg)
+{
+ Completejob *job;
+ Completion *c;
+
+ job = arg;
+ threadsetname("namecomplete %s", job->dir);
+
+ c = complete(job->dir, job->str);
+ if(c != nil && sendp(job->win->complete, c) <= 0)
+ freecompletion(c);
+
+ wclose(job->win);
+
+ free(job->dir);
+ free(job->str);
+ free(job);
+}
+
+static int
+windfilewidth(Window *w, uint q0, int oneelement)
+{
+ uint q;
+ Rune r;
+
+ q = q0;
+ while(q > 0){
+ r = w->r[q-1];
+ if(r<=' ' || r=='=' || r=='^' || r=='(' || r=='{')
+ break;
+ if(oneelement && r=='/')
+ break;
+ --q;
+ }
+ return q0-q;
+}
+
+static void
+namecomplete(Window *w)
+{
+ int nstr, npath;
+ Rune *path, *str;
+ char *dir, *root;
+ Completejob *job;
+
+ /* control-f: filename completion; works back to white space or / */
+ if(w->q0<w->nr && w->r[w->q0]>' ') /* must be at end of word */
+ return;
+ nstr = windfilewidth(w, w->q0, TRUE);
+ str = w->r+(w->q0-nstr);
+ npath = windfilewidth(w, w->q0-nstr, FALSE);
+ path = w->r+(w->q0-nstr-npath);
+
+ /* is path rooted? if not, we need to make it relative to window path */
+ if(npath>0 && path[0]=='/')
+ dir = runetobyte(path, npath, &npath);
+ else {
+ if(strcmp(w->dir, "") == 0)
+ root = ".";
+ else
+ root = w->dir;
+ dir = smprint("%s/%.*S", root, npath, path);
+ }
+ if(dir == nil)
+ return;
+
+ /* run in background, winctl will collect the result on w->complete chan */
+ job = emalloc(sizeof *job);
+ job->str = runetobyte(str, nstr, &nstr);
+ job->dir = cleanname(dir);
+ job->win = w;
+ incref(w);
+ proccreate(completeproc, job, STACK);
+}
+
+static void
+showcandidates(Window *w, Completion *c)
+{
+ int i;
+ Fmt f;
+ Rune *rp;
+ uint nr, qline;
+ char *s;
+
+ runefmtstrinit(&f);
+ if (c->nmatch == 0)
+ s = "[no matches in ";
+ else
+ s = "[";
+ if(c->nfile > 32)
+ fmtprint(&f, "%s%d files]\n", s, c->nfile);
+ else{
+ fmtprint(&f, "%s", s);
+ for(i=0; i<c->nfile; i++){
+ if(i > 0)
+ fmtprint(&f, " ");
+ fmtprint(&f, "%s", c->filename[i]);
+ }
+ fmtprint(&f, "]\n");
+ }
+ rp = runefmtstrflush(&f);
+ nr = runestrlen(rp);
+
+ /* place text at beginning of line before cursor and host point */
+ qline = min(w->qh, w->q0);
+ while(qline>0 && w->r[qline-1] != '\n')
+ qline--;
+
+ if(qline == w->qh){
+ /* advance host point to avoid readback */
+ w->qh = winsert(w, rp, nr, qline)+nr;
+ } else {
+ winsert(w, rp, nr, qline);
+ }
+ free(rp);
+}
+
+static int
+wbswidth(Window *w, Rune c)
+{
+ uint q, eq, stop;
+ Rune r;
+ int skipping;
+
+ /* there is known to be at least one character to erase */
+ if(c == 0x08) /* ^H: erase character */
+ return 1;
+ q = w->q0;
+ stop = 0;
+ if(q > w->qh)
+ stop = w->qh;
+ skipping = TRUE;
+ while(q > stop){
+ r = w->r[q-1];
+ if(r == '\n'){ /* eat at most one more character */
+ if(q == w->q0) /* eat the newline */
+ --q;
+ break;
+ }
+ if(c == 0x17){
+ eq = isalnum(r);
+ if(eq && skipping) /* found one; stop skipping */
+ skipping = FALSE;
+ else if(!eq && !skipping)
+ break;
+ }
+ --q;
+ }
+ return w->q0-q;
+}
+
+void
+wsetorigin(Window *w, uint org, int exact)
+{
+ int i, a, fixup;
+ Rune *r;
+ uint n;
+
+ if(org>0 && !exact){
+ /* org is an estimate of the char posn; find a newline */
+ /* don't try harder than 256 chars */
+ for(i=0; i<256 && org<w->nr; i++){
+ if(w->r[org] == '\n'){
+ org++;
+ break;
+ }
+ org++;
+ }
+ }
+ a = org-w->org;
+ fixup = 0;
+ if(a>=0 && a<w->nchars){
+ frdelete(w, 0, a);
+ fixup = 1; /* frdelete can leave end of last line in wrong selection mode; it doesn't know what follows */
+ }else if(a<0 && -a<w->nchars){
+ n = w->org - org;
+ r = w->r+org;
+ frinsert(w, r, r+n, 0);
+ }else
+ frdelete(w, 0, w->nchars);
+ w->org = org;
+ wfill(w);
+ wscrdraw(w);
+ wsetselect(w, w->q0, w->q1);
+ if(fixup && w->p1 > w->p0)
+ frdrawsel(w, frptofchar(w, w->p1-1), w->p1-1, w->p1, 1);
+}
+
+uint
+wbacknl(Window *w, uint p, uint n)
+{
+ int i, j;
+
+ /* look for start of this line if n==0 */
+ if(n==0 && p>0 && w->r[p-1]!='\n')
+ n = 1;
+ i = n;
+ while(i-->0 && p>0){
+ --p; /* it's at a newline now; back over it */
+ if(p == 0)
+ break;
+ /* at 128 chars, call it a line anyway */
+ for(j=128; --j>0 && p>0; p--)
+ if(w->r[p-1]=='\n')
+ break;
+ }
+ return p;
+}
+
+char*
+wcontents(Window *w, int *ip)
+{
+ return runetobyte(w->r, w->nr, ip);
+}
+
+void
+wshow(Window *w, uint q0)
+{
+ int qe;
+ int nl;
+ uint q;
+
+ qe = w->org+w->nchars;
+ if(w->org<=q0 && (q0<qe || (q0==qe && qe==w->nr)))
+ wscrdraw(w);
+ else{
+ nl = 4*w->maxlines/5;
+ q = wbacknl(w, q0, nl);
+ /* avoid going backwards if trying to go forwards - long lines! */
+ if(!(q0>w->org && q<w->org))
+ wsetorigin(w, q, TRUE);
+ while(q0 > w->org+w->nchars)
+ wsetorigin(w, w->org+1, FALSE);
+ }
+}
+
+void
+wsnarf(Window *w)
+{
+ if(w->q1 == w->q0)
+ return;
+ nsnarf = w->q1-w->q0;
+ snarf = runerealloc(snarf, nsnarf);
+ snarfversion++; /* maybe modified by parent */
+ runemove(snarf, w->r+w->q0, nsnarf);
+ putsnarf();
+}
+
+void
+wsend(Window *w)
+{
+ getsnarf();
+ wsnarf(w);
+ if(nsnarf == 0)
+ return;
+ if(w->rawing){
+ waddraw(w, snarf, nsnarf);
+ if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004')
+ waddraw(w, L"\n", 1);
+ }else{
+ winsert(w, snarf, nsnarf, w->nr);
+ if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004')
+ winsert(w, L"\n", 1, w->nr);
+ }
+ wsetselect(w, w->nr, w->nr);
+ wshow(w, w->nr);
+}
+
+static void
+wdelete(Window *w, uint q0, uint q1)
+{
+ uint n, p0, p1;
+
+ n = q1-q0;
+ if(n == 0)
+ return;
+ runemove(w->r+q0, w->r+q1, w->nr-q1);
+ w->nr -= n;
+ if(q0 < w->q0)
+ w->q0 -= min(n, w->q0-q0);
+ if(q0 < w->q1)
+ w->q1 -= min(n, w->q1-q0);
+ if(q1 < w->qh)
+ w->qh -= n;
+ else if(q0 < w->qh)
+ w->qh = q0;
+ if(q1 <= w->org)
+ w->org -= n;
+ else if(q0 < w->org+w->nchars){
+ p1 = q1 - w->org;
+ if(p1 > w->nchars)
+ p1 = w->nchars;
+ if(q0 < w->org){
+ w->org = q0;
+ p0 = 0;
+ }else
+ p0 = q0 - w->org;
+ frdelete(w, p0, p1);
+ wfill(w);
+ }
+}
+
+void
+wcut(Window *w)
+{
+ if(w->q1 == w->q0)
+ return;
+ wdelete(w, w->q0, w->q1);
+ wsetselect(w, w->q0, w->q0);
+}
+
+void
+wpaste(Window *w)
+{
+ uint q0;
+
+ if(nsnarf == 0)
+ return;
+ wcut(w);
+ q0 = w->q0;
+ if(w->rawing && q0==w->nr){
+ waddraw(w, snarf, nsnarf);
+ wsetselect(w, q0, q0);
+ }else{
+ q0 = winsert(w, snarf, nsnarf, w->q0);
+ wsetselect(w, q0, q0+nsnarf);
+ }
+}
+
+void
+wlook(Window *w)
+{
+ int i, n, e;
+
+ i = w->q1;
+ n = i - w->q0;
+ e = w->nr - n;
+ if(n <= 0 || e < n)
+ return;
+
+ if(i > e)
+ i = 0;
+
+ while(runestrncmp(w->r+w->q0, w->r+i, n) != 0){
+ if(i < e)
+ i++;
+ else
+ i = 0;
+ }
+
+ wsetselect(w, i, i+n);
+ wshow(w, i);
+}
+
+void
+wplumb(Window *w)
+{
+ Plumbmsg *m;
+ static int fd = -2;
+ char buf[32];
+ uint p0, p1;
+ Cursor *c;
+
+ if(fd == -2)
+ fd = plumbopen("send", OWRITE|OCEXEC);
+ if(fd < 0)
+ return;
+ m = emalloc(sizeof(Plumbmsg));
+ m->src = estrdup("rio");
+ m->dst = nil;
+ m->wdir = estrdup(w->dir);
+ m->type = estrdup("text");
+ p0 = w->q0;
+ p1 = w->q1;
+ if(w->q1 > w->q0)
+ m->attr = nil;
+ else{
+ while(p0>0 && w->r[p0-1]!=' ' && w->r[p0-1]!='\t' && w->r[p0-1]!='\n')
+ p0--;
+ while(p1<w->nr && w->r[p1]!=' ' && w->r[p1]!='\t' && w->r[p1]!='\n')
+ p1++;
+ snprint(buf, sizeof(buf), "click=%d", w->q0-p0);
+ m->attr = plumbunpackattr(buf);
+ }
+ if(p1-p0 > messagesize-1024){
+ plumbfree(m);
+ return; /* too large for 9P */
+ }
+ m->data = runetobyte(w->r+p0, p1-p0, &m->ndata);
+ if(plumbsend(fd, m) < 0){
+ c = lastcursor;
+ riosetcursor(&query);
+ sleep(300);
+ riosetcursor(c);
+ }
+ plumbfree(m);
+}
+
+static void
+wkeyctl(Window *w, Rune r)
+{
+ uint q0 ,q1;
+ int n, nb;
+ int *notefd;
+
+ switch(r){
+ case 0:
+ case Kcaps:
+ case Knum:
+ case Kshift:
+ case Kalt:
+ case Kctl:
+ case Kaltgr:
+ case Krctl:
+ case Krshift:
+ return;
+ }
+
+ if(w->i==nil)
+ return;
+ /* navigation keys work only when mouse and kbd is not open */
+ if(!w->mouseopen)
+ switch(r){
+ case Kdown:
+ n = shiftdown ? 1 : w->maxlines/3;
+ goto case_Down;
+ case Kscrollonedown:
+ n = mousescrollsize(w->maxlines);
+ if(n <= 0)
+ n = 1;
+ goto case_Down;
+ case Kpgdown:
+ n = 2*w->maxlines/3;
+ case_Down:
+ q0 = w->org+frcharofpt(w, Pt(w->Frame.r.min.x, w->Frame.r.min.y+n*w->font->height));
+ wsetorigin(w, q0, TRUE);
+ return;
+ case Kup:
+ n = shiftdown ? 1 : w->maxlines/3;
+ goto case_Up;
+ case Kscrolloneup:
+ n = mousescrollsize(w->maxlines);
+ if(n <= 0)
+ n = 1;
+ goto case_Up;
+ case Kpgup:
+ n = 2*w->maxlines/3;
+ case_Up:
+ q0 = wbacknl(w, w->org, n);
+ wsetorigin(w, q0, TRUE);
+ return;
+ case Kleft:
+ if(w->q0 > 0){
+ q0 = w->q0-1;
+ wsetselect(w, q0, q0);
+ wshow(w, q0);
+ }
+ return;
+ case Kright:
+ if(w->q1 < w->nr){
+ q1 = w->q1+1;
+ wsetselect(w, q1, q1);
+ wshow(w, q1);
+ }
+ return;
+ case Khome:
+ wshow(w, 0);
+ return;
+ case Kend:
+ wshow(w, w->nr);
+ return;
+ case Kscroll:
+ w->scrolling ^= 1;
+ wshow(w, w->nr);
+ return;
+ case Ksoh: /* ^A: beginning of line */
+ if(w->q0==0 || w->q0==w->qh || w->r[w->q0-1]=='\n')
+ return;
+ nb = wbswidth(w, 0x15 /* ^U */);
+ wsetselect(w, w->q0-nb, w->q0-nb);
+ wshow(w, w->q0);
+ return;
+ case Kenq: /* ^E: end of line */
+ q0 = w->q0;
+ while(q0 < w->nr && w->r[q0]!='\n')
+ q0++;
+ wsetselect(w, q0, q0);
+ wshow(w, w->q0);
+ return;
+ case Kstx: /* ^B: output point */
+ wsetselect(w, w->qh, w->qh);
+ wshow(w, w->q0);
+ return;
+ }
+ if(w->rawing && (w->q0==w->nr || w->mouseopen)){
+ waddraw(w, &r, 1);
+ return;
+ }
+ if(r==Kesc || (w->holding && r==Kdel)){ /* toggle hold */
+ if(w->holding)
+ --w->holding;
+ else
+ w->holding++;
+ wsetcursor(w, FALSE);
+ wrepaint(w);
+ if(r == Kesc)
+ return;
+ }
+ if(r != Kdel){
+ wsnarf(w);
+ wcut(w);
+ }
+ switch(r){
+ case Kdel: /* send interrupt */
+ w->qh = w->nr;
+ wshow(w, w->qh);
+ if(w->notefd < 0)
+ return;
+ notefd = emalloc(sizeof(int));
+ *notefd = dup(w->notefd, -1);
+ proccreate(interruptproc, notefd, 4096);
+ return;
+ case Kack: /* ^F: file name completion */
+ case Kins: /* Insert: file name completion */
+ namecomplete(w);
+ return;
+ case Kbs: /* ^H: erase character */
+ case Knack: /* ^U: erase line */
+ case Ketb: /* ^W: erase word */
+ if(w->q0==0 || w->q0==w->qh)
+ return;
+ nb = wbswidth(w, r);
+ q1 = w->q0;
+ q0 = q1-nb;
+ if(q0 < w->org){
+ q0 = w->org;
+ nb = q1-q0;
+ }
+ if(nb > 0){
+ wdelete(w, q0, q0+nb);
+ wsetselect(w, q0, q0);
+ }
+ return;
+ }
+ /* otherwise ordinary character; just insert */
+ q0 = w->q0;
+ q0 = winsert(w, &r, 1, q0);
+ wshow(w, q0+1);
+}
+
+static Window *clickwin;
+static uint clickmsec;
+static Point clickpt;
+static uint clickcount;
+static Window *selectwin;
+static uint selectq;
+
+static void
+wframescroll(Window *w, int dl)
+{
+ uint q0;
+
+ if(dl == 0){
+ wscrsleep(w, 100);
+ return;
+ }
+ if(dl < 0){
+ q0 = wbacknl(w, w->org, -dl);
+ if(selectq > w->org+w->p0)
+ wsetselect(w, w->org+w->p0, selectq);
+ else
+ wsetselect(w, selectq, w->org+w->p0);
+ }else{
+ if(w->org+w->nchars == w->nr)
+ return;
+ q0 = w->org+frcharofpt(w, Pt(w->Frame.r.min.x, w->Frame.r.min.y+dl*w->font->height));
+ if(selectq >= w->org+w->p1)
+ wsetselect(w, w->org+w->p1, selectq);
+ else
+ wsetselect(w, selectq, w->org+w->p1);
+ }
+ wsetorigin(w, q0, TRUE);
+}
+
+/*
+ * called from frame library
+ */
+static void
+framescroll(Frame *f, int dl)
+{
+ if(f != &selectwin->Frame)
+ error("frameselect not right frame");
+ wframescroll(selectwin, dl);
+}
+
+static Rune left1[] = { L'{', L'[', L'(', L'<', L'«', 0 };
+static Rune right1[] = { L'}', L']', L')', L'>', L'»', 0 };
+static Rune left2[] = { L'\n', 0 };
+static Rune left3[] = { L'\'', L'"', L'`', 0 };
+
+static Rune *left[] = {
+ left1,
+ left2,
+ left3,
+ nil
+};
+static Rune *right[] = {
+ right1,
+ left2,
+ left3,
+ nil
+};
+
+static int
+wclickmatch(Window *w, int cl, int cr, int dir, uint *q)
+{
+ Rune c;
+ int nest;
+
+ nest = 1;
+ for(;;){
+ if(dir > 0){
+ if(*q == w->nr)
+ break;
+ c = w->r[*q];
+ (*q)++;
+ }else{
+ if(*q == 0)
+ break;
+ (*q)--;
+ c = w->r[*q];
+ }
+ if(c == cr){
+ if(--nest==0)
+ return 1;
+ }else if(c == cl)
+ nest++;
+ }
+ return cl=='\n' && nest==1;
+}
+
+static int
+inmode(Rune r, int mode)
+{
+ return (mode == 1) ? isalnum(r) : r && !isspace(r);
+}
+
+static void
+wstretchsel(Window *w, uint pt, uint *q0, uint *q1, int mode)
+{
+ int c, i, rc, lc;
+ Rune *r, *l, *p;
+ uint q;
+
+ if(mode){
+ lc = *q0 > 0 ? w->r[*q0-1] : '\n';
+ rc = *q1 < w->nr ? w->r[*q1] : '\n';
+ for(i=0; left[i]; i++){
+ l = left[i];
+ r = right[i];
+ p = strrune(l, lc);
+ if(p && r[p-l] == rc){
+ *q0 -= *q0 > 0 && lc != '\n';
+ *q1 += *q1 < w->nr;
+ return;
+ }
+ }
+ }
+
+ *q0 = pt;
+ *q1 = pt;
+ for(i=0; left[i]!=nil; i++){
+ q = *q0;
+ l = left[i];
+ r = right[i];
+ /* try matching character to left, looking right */
+ if(q == 0)
+ c = '\n';
+ else
+ c = w->r[q-1];
+ p = strrune(l, c);
+ if(p != nil){
+ if(wclickmatch(w, c, r[p-l], 1, &q))
+ *q1 = q-(c!='\n');
+ return;
+ }
+ /* try matching character to right, looking left */
+ if(q == w->nr)
+ c = '\n';
+ else
+ c = w->r[q];
+ p = strrune(r, c);
+ if(p != nil){
+ if(wclickmatch(w, c, l[p-r], -1, &q)){
+ *q1 = *q0+(*q0<w->nr && c=='\n');
+ *q0 = q;
+ if(c!='\n' || q!=0 || w->r[0]=='\n')
+ (*q0)++;
+ }
+ return;
+ }
+ }
+ /* try filling out word to right */
+ while(*q1<w->nr && inmode(w->r[*q1], mode))
+ (*q1)++;
+ /* try filling out word to left */
+ while(*q0>0 && inmode(w->r[*q0-1], mode))
+ (*q0)--;
+}
+
+static void
+wselect(Window *w)
+{
+ uint q0, q1;
+ int b, x, y, dx, dy, mode, first;
+
+ first = 1;
+ selectwin = w;
+ /*
+ * Double-click immediately if it might make sense.
+ */
+ b = w->mc.buttons;
+ q0 = w->q0;
+ q1 = w->q1;
+ dx = abs(clickpt.x - w->mc.xy.x);
+ dy = abs(clickpt.y - w->mc.xy.y);
+ clickpt = w->mc.xy;
+ selectq = w->org+frcharofpt(w, w->mc.xy);
+ clickcount++;
+ if(w->mc.msec-clickmsec >= 500 || clickwin != w || clickcount > 3 || dx > 3 || dy > 3)
+ clickcount = 0;
+ if(clickwin == w && clickcount >= 1 && w->mc.msec-clickmsec < 500){
+ mode = (clickcount > 2) ? 2 : clickcount;
+ wstretchsel(w, selectq, &q0, &q1, mode);
+ wsetselect(w, q0, q1);
+ x = w->mc.xy.x;
+ y = w->mc.xy.y;
+ /* stay here until something interesting happens */
+ while(1){
+ readmouse(&w->mc);
+ dx = abs(w->mc.xy.x-x);
+ dy = abs(w->mc.xy.y-y);
+ if(w->mc.buttons != b || dx >= 3 && dy >= 3)
+ break;
+ clickcount++;
+ clickmsec = w->mc.msec;
+ }
+ w->mc.xy.x = x; /* in case we're calling frselect */
+ w->mc.xy.y = y;
+ q0 = w->q0; /* may have changed */
+ q1 = w->q1;
+ selectq = w->org+frcharofpt(w, w->mc.xy);
+ }
+ if(w->mc.buttons == b && clickcount == 0){
+ w->scroll = framescroll;
+ frselect(w, &w->mc);
+ /* horrible botch: while asleep, may have lost selection altogether */
+ if(selectq > w->nr)
+ selectq = w->org + w->p0;
+ w->Frame.scroll = nil;
+ if(selectq < w->org)
+ q0 = selectq;
+ else
+ q0 = w->org + w->p0;
+ if(selectq > w->org+w->nchars)
+ q1 = selectq;
+ else
+ q1 = w->org+w->p1;
+ }
+ if(q0 == q1){
+ mode = (clickcount > 2) ? 2 : clickcount;
+ if(q0==w->q0 && clickwin==w && w->mc.msec-clickmsec<500)
+ wstretchsel(w, selectq, &q0, &q1, mode);
+ else
+ clickwin = w;
+ clickmsec = w->mc.msec;
+ }
+ wsetselect(w, q0, q1);
+ while(w->mc.buttons){
+ w->mc.msec = 0;
+ b = w->mc.buttons;
+ if(b & 6){
+ if(b & 2){
+ wsnarf(w);
+ wcut(w);
+ }else{
+ if(first){
+ first = 0;
+ getsnarf();
+ }
+ wpaste(w);
+ }
+ }
+ wscrdraw(w);
+ while(w->mc.buttons == b)
+ readmouse(&w->mc);
+ if(w->mc.msec-clickmsec >= 500)
+ clickwin = nil;
+ }
+}
+
+/*
+ * Convert back to physical coordinates
+ */
+static void
+wmovemouse(Window *w, Point p)
+{
+ if(w != input || menuing || sweeping)
+ return;
+ p.x += w->screenr.min.x-w->i->r.min.x;
+ p.y += w->screenr.min.y-w->i->r.min.y;
+ moveto(mousectl, p);
+}
+
+
+Window*
+wmk(Image *i, Mousectl *mc, Channel *ck, Channel *cctl, int scrolling)
+{
+ static int id;
+
+ Window *w;
+ Rectangle r;
+
+ w = emalloc(sizeof(Window));
+ w->screenr = i->r;
+ r = insetrect(i->r, Selborder+1);
+ w->i = i;
+ w->mc = *mc;
+ w->ck = ck;
+ w->cctl = cctl;
+ w->cursorp = nil;
+ w->conswrite = chancreate(sizeof(Conswritemesg), 0);
+ w->consread = chancreate(sizeof(Consreadmesg), 0);
+ w->kbdread = chancreate(sizeof(Consreadmesg), 0);
+ w->mouseread = chancreate(sizeof(Mousereadmesg), 0);
+ w->wctlread = chancreate(sizeof(Consreadmesg), 0);
+ w->complete = chancreate(sizeof(Completion*), 0);
+ w->gone = chancreate(sizeof(char*), 0);
+ w->scrollr = r;
+ w->scrollr.max.x = r.min.x+Scrollwid;
+ w->lastsr = ZR;
+ r.min.x += Scrollwid+Scrollgap;
+ frinit(w, r, font, i, cols);
+ w->maxtab = maxtab*stringwidth(font, "0");
+ w->topped = ++topped;
+ w->id = ++id;
+ w->notefd = -1;
+ w->scrolling = scrolling;
+ w->dir = estrdup(startdir);
+ w->label = estrdup("<unnamed>");
+ r = insetrect(w->i->r, Selborder);
+ draw(w->i, r, cols[BACK], nil, w->entire.min);
+ wborder(w, Selborder);
+ wscrdraw(w);
+ incref(w); /* ref will be removed after mounting; avoids delete before ready to be deleted */
+ return w;
+}
+
+static void
+wclosewin(Window *w)
+{
+ Image *i = w->i;
+ if(i == nil)
+ return;
+ w->i = nil;
+ /* move it off-screen to hide it, in case client is slow in letting it go */
+ originwindow(i, i->r.min, view->r.max);
+ freeimage(i);
+}
+
+static void
+wclunk(Window *w)
+{
+ int i;
+
+ if(w->deleted)
+ return;
+ w->deleted = TRUE;
+ if(w == input){
+ input = nil;
+ riosetcursor(nil);
+ }
+ if(w == wkeyboard)
+ wkeyboard = nil;
+ for(i=0; i<nhidden; i++)
+ if(hidden[i] == w){
+ --nhidden;
+ memmove(hidden+i, hidden+i+1, (nhidden-i)*sizeof(hidden[0]));
+ break;
+ }
+ for(i=0; i<nwindow; i++)
+ if(window[i] == w){
+ --nwindow;
+ memmove(window+i, window+i+1, (nwindow-i)*sizeof(window[0]));
+ break;
+ }
+}
+
+int
+wclose(Window *w)
+{
+ int i;
+
+ i = decref(w);
+ if(i > 0)
+ return 0;
+ if(i < 0)
+ error("negative ref count");
+ wclunk(w);
+ wsendctlmesg(w, Exited, ZR, nil);
+ return 1;
+}
+
+void
+wsendctlmesg(Window *w, int type, Rectangle r, void *p)
+{
+ Wctlmesg wcm;
+
+ wcm.type = type;
+ wcm.r = r;
+ wcm.p = p;
+ send(w->cctl, &wcm);
+}
+
+static int
+wctlmesg(Window *w, int m, Rectangle r, void *p)
+{
+ Image *i = p;
+
+ switch(m){
+ default:
+ error("unknown control message");
+ break;
+ case Wakeup:
+ break;
+ case Reshaped:
+ if(w->deleted){
+ freeimage(i);
+ break;
+ }
+ w->screenr = r;
+ wclosewin(w);
+ wresize(w, i);
+ wsetcursor(w, FALSE);
+ break;
+ case Topped:
+ if(w->deleted)
+ break;
+ w->wctlready = 1;
+ wsetcursor(w, FALSE);
+ /* fall thrugh for redraw after input change */
+ case Repaint:
+ if(p != nil){
+ /* sync with input change from wcurrent()/wuncurrent() */
+ Channel *c = p;
+ input = recvp(c);
+
+ /* when we lost input, release mouse and keyboard buttons */
+ if(w->mc.buttons){
+ w->mc.buttons = 0;
+ w->mouse.counter++;
+ }
+ w->keyup = w->kbdopen;
+ w->wctlready = 1;
+
+ sendp(c, w);
+ }
+ if(w->i==nil || Dx(w->screenr)<=0)
+ break;
+ wrepaint(w);
+ flushimage(display, 1);
+ break;
+ case Refresh:
+ if(w->i==nil || Dx(w->screenr)<=0)
+ break;
+ wrefresh(w);
+ flushimage(display, 1);
+ break;
+ case Movemouse:
+ if(w->i==nil || Dx(w->screenr)<=0 || !ptinrect(r.min, w->i->r))
+ break;
+ wmovemouse(w, r.min);
+ case Rawon:
+ break;
+ case Rawoff:
+ while(w->nraw > 0){
+ wkeyctl(w, w->raw[0]);
+ --w->nraw;
+ runemove(w->raw, w->raw+1, w->nraw);
+ }
+ break;
+ case Holdon:
+ case Holdoff:
+ if(w->i==nil)
+ break;
+ wsetcursor(w, FALSE);
+ wrepaint(w);
+ flushimage(display, 1);
+ break;
+ case Truncate:
+ wdelete(w, 0, w->nr);
+ break;
+ case Deleted:
+ wclunk(w);
+ if(w->notefd >= 0)
+ write(w->notefd, "hangup", 6);
+ wclosewin(w);
+ flushimage(display, 1);
+ break;
+ case Exited:
+ wclosewin(w);
+ frclear(w, TRUE);
+ flushimage(display, 1);
+ if(w->notefd >= 0)
+ close(w->notefd);
+ chanfree(w->mc.c);
+ chanfree(w->ck);
+ chanfree(w->cctl);
+ chanfree(w->conswrite);
+ chanfree(w->consread);
+ chanfree(w->mouseread);
+ chanfree(w->wctlread);
+ chanfree(w->kbdread);
+ chanfree(w->complete);
+ chanfree(w->gone);
+ free(w->raw);
+ free(w->r);
+ free(w->dir);
+ free(w->label);
+ free(w);
+ break;
+ }
+ return m;
+}
+
+static void
+wmousectl(Window *w)
+{
+ int but;
+
+ for(but=1;; but++){
+ if(but > 5)
+ return;
+ if(w->mc.buttons == 1<<(but-1))
+ break;
+ }
+
+ incref(w); /* hold up window while we track */
+ if(w->i != nil){
+ if(shiftdown && but > 3)
+ wkeyctl(w, but == 4 ? Kscrolloneup : Kscrollonedown);
+ else if(ptinrect(w->mc.xy, w->scrollr) || (but > 3))
+ wscroll(w, but);
+ else if(but == 1)
+ wselect(w);
+ }
+ wclose(w);
+}
+
+void
+winctl(void *arg)
+{
+ Rune *rp, *up, r;
+ uint qh, q0;
+ int nr, nb, c, wid, i, npart, initial, lastb;
+ char *s, *t, part[3];
+ Window *w;
+ Mousestate *mp, m;
+ enum { WKbd, WKbdread, WMouse, WMouseread, WCtl, WCwrite, WCread, WWread, WComplete, Wgone, NWALT };
+ Alt alts[NWALT+1];
+ Consreadmesg crm;
+ Mousereadmesg mrm;
+ Conswritemesg cwm;
+ Stringpair pair;
+ Wctlmesg wcm;
+ Completion *cr;
+ char *kbdq[32], *kbds;
+ uint kbdqr, kbdqw;
+
+ w = arg;
+ threadsetname("winctl-id%d", w->id);
+
+ mrm.cm = chancreate(sizeof(Mouse), 0);
+ crm.c1 = chancreate(sizeof(Stringpair), 0);
+ crm.c2 = chancreate(sizeof(Stringpair), 0);
+ cwm.cw = chancreate(sizeof(Stringpair), 0);
+
+ alts[WKbd].c = w->ck;
+ alts[WKbd].v = &kbds;
+ alts[WKbd].op = CHANRCV;
+ alts[WKbdread].c = w->kbdread;
+ alts[WKbdread].v = &crm;
+ alts[WKbdread].op = CHANSND;
+ alts[WMouse].c = w->mc.c;
+ alts[WMouse].v = &w->mc.Mouse;
+ alts[WMouse].op = CHANRCV;
+ alts[WMouseread].c = w->mouseread;
+ alts[WMouseread].v = &mrm;
+ alts[WMouseread].op = CHANSND;
+ alts[WCtl].c = w->cctl;
+ alts[WCtl].v = &wcm;
+ alts[WCtl].op = CHANRCV;
+ alts[WCwrite].c = w->conswrite;
+ alts[WCwrite].v = &cwm;
+ alts[WCwrite].op = CHANSND;
+ alts[WCread].c = w->consread;
+ alts[WCread].v = &crm;
+ alts[WCread].op = CHANSND;
+ alts[WWread].c = w->wctlread;
+ alts[WWread].v = &crm;
+ alts[WWread].op = CHANSND;
+ alts[WComplete].c = w->complete;
+ alts[WComplete].v = &cr;
+ alts[WComplete].op = CHANRCV;
+ alts[Wgone].c = w->gone;
+ alts[Wgone].v = "window deleted";
+ alts[Wgone].op = CHANNOP;
+ alts[NWALT].op = CHANEND;
+
+ kbdqr = kbdqw = 0;
+ npart = 0;
+ lastb = -1;
+ for(;;){
+ if(w->i==nil){
+ /* window deleted */
+ alts[Wgone].op = CHANSND;
+
+ alts[WKbdread].op = CHANNOP;
+ alts[WMouseread].op = CHANNOP;
+ alts[WCwrite].op = CHANNOP;
+ alts[WWread].op = CHANNOP;
+ alts[WCread].op = CHANNOP;
+ } else {
+ alts[WKbdread].op = w->kbdopen && (kbdqw != kbdqr || w->keyup) ?
+ CHANSND : CHANNOP;
+ alts[WMouseread].op = w->mouseopen && w->mouse.counter != w->mouse.lastcounter ?
+ CHANSND : CHANNOP;
+ alts[WCwrite].op = w->scrolling || w->mouseopen || (w->qh <= w->org+w->nchars) ?
+ CHANSND : CHANNOP;
+ alts[WWread].op = w->wctlready ?
+ CHANSND : CHANNOP;
+ /* this code depends on NL and EOT fitting in a single byte */
+ /* kind of expensive for each loop; worth precomputing? */
+ if(w->holding)
+ alts[WCread].op = CHANNOP;
+ else if(npart || (w->rawing && w->nraw>0))
+ alts[WCread].op = CHANSND;
+ else{
+ alts[WCread].op = CHANNOP;
+ for(i=w->qh; i<w->nr; i++){
+ c = w->r[i];
+ if(c=='\n' || c=='\004'){
+ alts[WCread].op = CHANSND;
+ break;
+ }
+ }
+ }
+ }
+ switch(alt(alts)){
+ case WKbd:
+ if(kbdqw - kbdqr < nelem(kbdq))
+ kbdq[kbdqw++ % nelem(kbdq)] = kbds;
+ else
+ free(kbds);
+ if(w->kbdopen)
+ continue;
+ while(kbdqr != kbdqw){
+ kbds = kbdq[kbdqr++ % nelem(kbdq)];
+ if(*kbds == 'c'){
+ chartorune(&r, kbds+1);
+ if(r)
+ wkeyctl(w, r);
+ }
+ free(kbds);
+ }
+ break;
+ case WKbdread:
+ recv(crm.c1, &pair);
+ nb = 0;
+ while(kbdqr != kbdqw){
+ kbds = kbdq[kbdqr % nelem(kbdq)];
+ i = strlen(kbds)+1;
+ if(nb+i > pair.ns)
+ break;
+ memmove((char*)pair.s + nb, kbds, i);
+ free(kbds);
+ nb += i;
+ kbdqr++;
+ }
+ if(w->keyup && nb+2 <= pair.ns){
+ w->keyup = 0;
+ memmove((char*)pair.s + nb, "K", 2);
+ nb += 2;
+ }
+ pair.ns = nb;
+ send(crm.c2, &pair);
+ continue;
+ case WMouse:
+ if(w->mouseopen) {
+ w->mouse.counter++;
+
+ /* queue click events */
+ if(!w->mouse.qfull && lastb != w->mc.buttons) { /* add to ring */
+ mp = &w->mouse.queue[w->mouse.wi];
+ if(++w->mouse.wi == nelem(w->mouse.queue))
+ w->mouse.wi = 0;
+ if(w->mouse.wi == w->mouse.ri)
+ w->mouse.qfull = TRUE;
+ mp->Mouse = w->mc;
+ mp->counter = w->mouse.counter;
+ lastb = w->mc.buttons;
+ }
+ } else
+ wmousectl(w);
+ break;
+ case WMouseread:
+ /* send a queued event or, if the queue is empty, the current state */
+ /* if the queue has filled, we discard all the events it contained. */
+ /* the intent is to discard frantic clicking by the user during long latencies. */
+ w->mouse.qfull = FALSE;
+ if(w->mouse.wi != w->mouse.ri) {
+ m = w->mouse.queue[w->mouse.ri];
+ if(++w->mouse.ri == nelem(w->mouse.queue))
+ w->mouse.ri = 0;
+ } else
+ m = (Mousestate){w->mc.Mouse, w->mouse.counter};
+
+ w->mouse.lastcounter = m.counter;
+ send(mrm.cm, &m.Mouse);
+ continue;
+ case WCtl:
+ if(wctlmesg(w, wcm.type, wcm.r, wcm.p) == Exited){
+ while(kbdqr != kbdqw)
+ free(kbdq[kbdqr++ % nelem(kbdq)]);
+ chanfree(crm.c1);
+ chanfree(crm.c2);
+ chanfree(mrm.cm);
+ chanfree(cwm.cw);
+ threadexits(nil);
+ }
+ continue;
+ case WCwrite:
+ recv(cwm.cw, &pair);
+ rp = pair.s;
+ nr = pair.ns;
+ for(i=0; i<nr; i++)
+ if(rp[i] == '\b'){
+ up = rp+i;
+ initial = 0;
+ for(; i<nr; i++){
+ if(rp[i] == '\b'){
+ if(up == rp)
+ initial++;
+ else
+ up--;
+ }else
+ *up++ = rp[i];
+ }
+ if(initial){
+ if(initial > w->qh)
+ initial = w->qh;
+ qh = w->qh-initial;
+ wdelete(w, qh, qh+initial);
+ w->qh = qh;
+ }
+ nr = up - rp;
+ break;
+ }
+ w->qh = winsert(w, rp, nr, w->qh)+nr;
+ if(w->scrolling || w->mouseopen)
+ wshow(w, w->qh);
+ wsetselect(w, w->q0, w->q1);
+ wscrdraw(w);
+ free(rp);
+ break;
+ case WCread:
+ recv(crm.c1, &pair);
+ t = pair.s;
+ nb = pair.ns;
+ i = npart;
+ npart = 0;
+ if(i)
+ memmove(t, part, i);
+ while(i<nb && (w->qh<w->nr || w->nraw>0)){
+ if(w->qh == w->nr){
+ wid = runetochar(t+i, &w->raw[0]);
+ w->nraw--;
+ runemove(w->raw, w->raw+1, w->nraw);
+ }else
+ wid = runetochar(t+i, &w->r[w->qh++]);
+ c = t[i]; /* knows break characters fit in a byte */
+ i += wid;
+ if(!w->rawing && (c == '\n' || c=='\004')){
+ if(c == '\004')
+ i--;
+ break;
+ }
+ }
+ if(i==nb && w->qh<w->nr && w->r[w->qh]=='\004')
+ w->qh++;
+ if(i > nb){
+ npart = i-nb;
+ memmove(part, t+nb, npart);
+ i = nb;
+ }
+ pair.s = t;
+ pair.ns = i;
+ send(crm.c2, &pair);
+ continue;
+ case WWread:
+ w->wctlready = 0;
+ recv(crm.c1, &pair);
+ s = Dx(w->screenr) > 0 ? "visible" : "hidden";
+ t = "notcurrent";
+ if(w == input)
+ t = "current";
+ pair.ns = snprint(pair.s, pair.ns+1, "%11d %11d %11d %11d %11s %11s ",
+ w->i->r.min.x, w->i->r.min.y, w->i->r.max.x, w->i->r.max.y, t, s);
+ send(crm.c2, &pair);
+ continue;
+ case WComplete:
+ if(w->i!=nil){
+ if(!cr->advance)
+ showcandidates(w, cr);
+ if(cr->advance){
+ rp = runesmprint("%s", cr->string);
+ if(rp){
+ nr = runestrlen(rp);
+ q0 = w->q0;
+ q0 = winsert(w, rp, nr, q0);
+ wshow(w, q0+nr);
+ free(rp);
+ }
+ }
+ }
+ freecompletion(cr);
+ break;
+ }
+ if(w->i!=nil && Dx(w->screenr) > 0 && display->bufp > display->buf)
+ flushimage(display, 1);
+ }
+}
+
+void
+wsetpid(Window *w, int pid, int dolabel)
+{
+ char buf[32];
+ int ofd;
+
+ ofd = w->notefd;
+ if(pid <= 0)
+ w->notefd = -1;
+ else {
+ if(dolabel){
+ snprint(buf, sizeof(buf), "rc %lud", (ulong)pid);
+ free(w->label);
+ w->label = estrdup(buf);
+ }
+ snprint(buf, sizeof(buf), "/proc/%lud/notepg", (ulong)pid);
+ w->notefd = open(buf, OWRITE|OCEXEC);
+ }
+ if(ofd >= 0)
+ close(ofd);
+}
+
+void
+winshell(void *args)
+{
+ Window *w;
+ Channel *pidc;
+ void **arg;
+ char *cmd, *dir;
+ char **argv;
+
+ arg = args;
+ w = arg[0];
+ pidc = arg[1];
+ cmd = arg[2];
+ argv = arg[3];
+ dir = arg[4];
+ rfork(RFNAMEG|RFFDG|RFENVG);
+ if(filsysmount(filsys, w->id) < 0){
+ fprint(2, "mount failed: %r\n");
+ sendul(pidc, 0);
+ threadexits("mount failed");
+ }
+ close(0);
+ if(open("/dev/cons", OREAD) < 0){
+ fprint(2, "can't open /dev/cons: %r\n");
+ sendul(pidc, 0);
+ threadexits("/dev/cons");
+ }
+ close(1);
+ if(open("/dev/cons", OWRITE) < 0){
+ fprint(2, "can't open /dev/cons: %r\n");
+ sendul(pidc, 0);
+ threadexits("open"); /* BUG? was terminate() */
+ }
+ if(wclose(w) == 0){ /* remove extra ref hanging from creation */
+ notify(nil);
+ dup(1, 2);
+ if(dir)
+ chdir(dir);
+ procexec(pidc, cmd, argv);
+ _exits("exec failed");
+ }
+}
--- a/sys/src/cmd/riow.c
+++ b/sys/src/cmd/riow.c
@@ -399,7 +399,7 @@
mod = 0;
if(utfrune(s+1, Kmod4) != nil)
mod |= Mmod4;
- if(utfrune(s+1, Kctl) != nil)
+ if(utfrune(s+1, Kctl) != nil || utfrune(s+1, Krctl) != nil)
mod |= Mctl;
if(utfrune(s+1, Kshift) != nil)
mod |= Mshift;
--- /dev/null
+++ b/sys/src/cmd/vnc/kbds.c
@@ -1,0 +1,74 @@
+#include <u.h>
+#include <libc.h>
+#include <keyboard.h>
+#include "compat.h"
+#include "kbd.h"
+#include "ksym2utf.h"
+
+enum {
+ VKSpecial = 0xff00,
+};
+
+static Rune vnckeys[] =
+{
+[0x00] 0, 0, 0, 0, 0, 0, 0, 0,
+[0x08] '\b', '\t', '\r', 0, 0, '\n', 0, 0,
+[0x10] 0, 0, 0, 0, Kscroll,0, 0, 0,
+[0x18] 0, 0, 0, Kesc, 0, 0, 0, 0,
+[0x20] 0, 0, 0, 0, 0, 0, 0, 0,
+[0x28] 0, 0, 0, 0, 0, 0, 0, 0,
+[0x30] 0, 0, 0, 0, 0, 0, 0, 0,
+[0x38] 0, 0, 0, 0, 0, 0, 0, 0,
+[0x40] 0, 0, 0, 0, 0, 0, 0, 0,
+[0x48] 0, 0, 0, 0, 0, 0, 0, 0,
+[0x50] Khome, Kleft, Kup, Kright, Kdown, Kpgup, Kpgdown,Kend,
+[0x58] 0, 0, 0, 0, 0, 0, 0, 0,
+[0x60] 0, Kprint, 0, Kins, 0, 0, 0, 0,
+[0x68] 0, 0, 0, Kbreak, 0, 0, 0, 0,
+[0x70] 0, 0, 0, 0, 0, 0, 0, 0,
+[0x78] 0, 0, 0, 0, 0, 0, 0, Knum,
+[0x80] 0, 0, 0, 0, 0, 0, 0, 0,
+[0x88] 0, 0, 0, 0, 0, 0, 0, 0,
+[0x90] 0, 0, 0, 0, 0, 0, 0, 0,
+[0x98] 0, 0, 0, 0, 0, 0, 0, 0,
+[0xa0] 0, 0, 0, 0, 0, 0, 0, 0,
+[0xa8] 0, 0, '*', '+', 0, '-', '.', '/',
+[0xb0] '0', '1', '2', '3', '4', '5', '6', '7',
+[0xb8] '8', '9', 0, 0, 0, '=', 0, 0,
+[0xc0] 0, 0, 0, 0, 0, 0, 0, 0,
+[0xc8] 0, 0, 0, 0, 0, 0, 0, 0,
+[0xd0] 0, 0, 0, 0, 0, 0, 0, 0,
+[0xd8] 0, 0, 0, 0, 0, 0, 0, 0,
+[0xe0] 0, Kshift, Krshift, Kctl, Krctl, Kcaps, Kcaps, 0,
+[0xe8] 0, Kalt, Kalt, 0, 0, 0, 0, 0,
+[0xf0] 0, 0, 0, 0, 0, 0, 0, 0,
+[0xf8] 0, 0, 0, 0, 0, 0, 0, Kdel,
+};
+
+/*
+ * keyboard interrupt
+ */
+void
+vncputc(int keyup, int c)
+{
+ char buf[16];
+
+ /*
+ * character mapping
+ */
+ if((c & VKSpecial) == VKSpecial){
+ c = vnckeys[c & 0xff];
+ if(c == 0)
+ return;
+ }
+ /*
+ * map an xkeysym onto a utf-8 char
+ */
+ if((c & 0xff00) && c < nelem(ksym2utf) && ksym2utf[c] != 0)
+ c = ksym2utf[c];
+ snprint(buf, sizeof(buf), "r%C", c);
+ if(keyup)
+ buf[0] = 'R';
+ if(kbdin >= 0)
+ write(kbdin, buf, strlen(buf)+1);
+}
--- /dev/null
+++ b/sys/src/cmd/vnc/kbdv.c
@@ -1,0 +1,278 @@
+#include "vnc.h"
+#include <keyboard.h>
+#include "utf2ksym.h"
+
+enum {
+ Xshift = 0xFFE1,
+ Xrshift = 0xFFE2,
+ Xctl = 0xFFE3,
+ Xrctl = 0xFFE4,
+ Xmeta = 0xFFE7,
+ Xalt = 0xFFE9,
+ Xsuper = 0xFFEB,
+};
+
+static struct {
+ Rune kbdc;
+ ulong keysym;
+} ktab[] = {
+ {'\b', 0xff08},
+ {'\t', 0xff09},
+ {'\n', 0xff0d},
+ /* {0x0b, 0xff0b}, */
+ {'\r', 0xff0d},
+ {Kesc, 0xff1b},
+ {Kins, 0xff63},
+ {Kdel, 0xffff},
+ {Khome, 0xff50},
+ {Kend, 0xff57},
+ {Kpgup, 0xff55},
+ {Kpgdown, 0xff56},
+ {Kleft, 0xff51},
+ {Kup, 0xff52},
+ {Kright, 0xff53},
+ {Kdown, 0xff54},
+ {KF|1, 0xffbe},
+ {KF|2, 0xffbf},
+ {KF|3, 0xffc0},
+ {KF|4, 0xffc1},
+ {KF|5, 0xffc2},
+ {KF|6, 0xffc3},
+ {KF|7, 0xffc4},
+ {KF|8, 0xffc5},
+ {KF|9, 0xffc6},
+ {KF|10, 0xffc7},
+ {KF|11, 0xffc8},
+ {KF|12, 0xffc9},
+
+ {Kshift, Xshift},
+ {Krshift, Xrshift},
+ {Kalt, Xalt},
+ {Kaltgr, Xmeta},
+ {Kmod4, Xsuper},
+ {Kctl, Xctl},
+ {Krctl, Xrctl},
+};
+
+static char shiftkey[128] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, /* nul soh stx etx eot enq ack bel */
+ 0, 0, 0, 0, 0, 0, 0, 0, /* bs ht nl vt np cr so si */
+ 0, 0, 0, 0, 0, 0, 0, 0, /* dle dc1 dc2 dc3 dc4 nak syn etb */
+ 0, 0, 0, 0, 0, 0, 0, 0, /* can em sub esc fs gs rs us */
+ 0, 1, 1, 1, 1, 1, 1, 0, /* sp ! " # $ % & ' */
+ 1, 1, 1, 1, 0, 0, 0, 0, /* ( ) * + , - . / */
+ 0, 0, 0, 0, 0, 0, 0, 0, /* 0 1 2 3 4 5 6 7 */
+ 0, 0, 1, 0, 1, 0, 1, 1, /* 8 9 : ; < = > ? */
+ 1, 1, 1, 1, 1, 1, 1, 1, /* @ A B C D E F G */
+ 1, 1, 1, 1, 1, 1, 1, 1, /* H I J K L M N O */
+ 1, 1, 1, 1, 1, 1, 1, 1, /* P Q R S T U V W */
+ 1, 1, 1, 0, 0, 0, 1, 1, /* X Y Z [ \ ] ^ _ */
+ 0, 0, 0, 0, 0, 0, 0, 0, /* ` a b c d e f g */
+ 0, 0, 0, 0, 0, 0, 0, 0, /* h i j k l m n o */
+ 0, 0, 0, 0, 0, 0, 0, 0, /* p q r s t u v w */
+ 0, 0, 0, 1, 1, 1, 1, 0, /* x y z { | } ~ del */
+};
+
+ulong
+runetoksym(Rune r)
+{
+ int i;
+
+ for(i=0; i<nelem(ktab); i++)
+ if(ktab[i].kbdc == r)
+ return ktab[i].keysym;
+ return r;
+}
+
+static void
+keyevent(Vnc *v, ulong ksym, int down)
+{
+ vnclock(v);
+ vncwrchar(v, MKey);
+ vncwrchar(v, down);
+ vncwrshort(v, 0);
+ vncwrlong(v, ksym);
+ vncflush(v);
+ vncunlock(v);
+}
+
+static void
+readcons(Vnc *v)
+{
+ char buf[256], k[10];
+ ulong ks;
+ int ctlfd, fd, kr, kn, w, shift, ctl, alt, super;
+ Rune r;
+
+ snprint(buf, sizeof buf, "%s/cons", display->devdir);
+ if((fd = open(buf, OREAD)) < 0)
+ sysfatal("open %s: %r", buf);
+
+ snprint(buf, sizeof buf, "%s/consctl", display->devdir);
+ if((ctlfd = open(buf, OWRITE)) < 0)
+ sysfatal("open %s: %r", buf);
+ write(ctlfd, "rawon", 5);
+
+ kn = 0;
+ shift = alt = ctl = super = 0;
+ for(;;){
+ while(!fullrune(k, kn)){
+ kr = read(fd, k+kn, sizeof k - kn);
+ if(kr <= 0)
+ sysfatal("bad read from kbd");
+ kn += kr;
+ }
+ w = chartorune(&r, k);
+ kn -= w;
+ memmove(k, &k[w], kn);
+ ks = runetoksym(r);
+
+ switch(r){
+ case Kalt:
+ alt = !alt;
+ keyevent(v, Xalt, alt);
+ break;
+ case Kctl:
+ case Krctl:
+ ctl = !ctl;
+ keyevent(v, Xctl, ctl);
+ break;
+ case Kshift:
+ case Krshift:
+ shift = !shift;
+ keyevent(v, Xshift, shift);
+ break;
+ case Kmod4:
+ super = !super;
+ keyevent(v, Xsuper, super);
+ break;
+ default:
+ if(r == ks && r < 0x1A){ /* control key */
+ keyevent(v, Xctl, 1);
+ keyevent(v, r+0x60, 1); /* 0x60: make capital letter */
+ keyevent(v, r+0x60, 0);
+ keyevent(v, Xctl, 0);
+ }else{
+ /*
+ * to send an upper case letter or shifted
+ * punctuation, mac os x vnc server,
+ * at least, needs a `shift' sent first.
+ */
+ if(!shift && r == ks && r < sizeof shiftkey && shiftkey[r]){
+ shift = 1;
+ keyevent(v, Xshift, 1);
+ }
+ /*
+ * map an xkeysym onto a utf-8 char.
+ * allows Xvnc to read us, see utf2ksym.h
+ */
+ if((ks & 0xff00) && ks < nelem(utf2ksym) && utf2ksym[ks] != 0)
+ ks = utf2ksym[ks];
+ keyevent(v, ks, 1);
+ /*
+ * up event needed by vmware inside linux vnc server,
+ * perhaps others.
+ */
+ keyevent(v, ks, 0);
+ }
+
+ if(alt){
+ keyevent(v, Xalt, 0);
+ alt = 0;
+ }
+ if(ctl){
+ keyevent(v, Xctl, 0);
+ ctl = 0;
+ }
+ if(shift){
+ keyevent(v, Xshift, 0);
+ shift = 0;
+ }
+ if(super){
+ keyevent(v, Xsuper, 0);
+ super = 0;
+ }
+ break;
+ }
+ }
+}
+
+ulong
+runetovnc(Rune r)
+{
+ ulong k;
+
+ k = runetoksym(r);
+ if((k & 0xff00) && k < nelem(utf2ksym) && utf2ksym[k] != 0)
+ k = utf2ksym[k];
+ return k;
+}
+
+void
+readkbd(Vnc *v)
+{
+ char buf[128], buf2[128], *s;
+ int fd, n;
+ Rune r;
+
+ if((fd = open("/dev/kbd", OREAD)) < 0){
+ readcons(v);
+ return;
+ }
+
+ buf2[0] = 0;
+ buf2[1] = 0;
+ buf[0] = 0;
+ for(;;){
+ if(buf[0] != 0){
+ n = strlen(buf)+1;
+ memmove(buf, buf+n, sizeof(buf)-n);
+ }
+ if(buf[0] == 0){
+ n = read(fd, buf, sizeof(buf)-1);
+ if(n <= 0)
+ break;
+ buf[n-1] = 0;
+ buf[n] = 0;
+ }
+ switch(buf[0]){
+ case 'k':
+ s = buf+1;
+ while(*s){
+ s += chartorune(&r, s);
+ if(utfrune(buf2+1, r) == nil)
+ if((r == Kshift) ||
+ utfrune(buf+1, Krshift) ||
+ utfrune(buf+1, Kctl) ||
+ utfrune(buf+1, Krctl) ||
+ utfrune(buf+1, Kalt) ||
+ utfrune(buf+1, Kmod4) ||
+ utfrune(buf+1, Kaltgr))
+ keyevent(v, runetovnc(r), 1);
+ }
+ break;
+ case 'K':
+ s = buf2+1;
+ while(*s){
+ s += chartorune(&r, s);
+ if(utfrune(buf+1, r) == nil)
+ keyevent(v, runetovnc(r), 0);
+ }
+ break;
+ case 'c':
+ if(utfrune(buf2+1, Kctl) ||
+ utfrune(buf2+1, Kalt) ||
+ utfrune(buf2+1, Kmod4) ||
+ utfrune(buf2+1, Kaltgr))
+ continue;
+ chartorune(&r, buf+1);
+ keyevent(v, runetovnc(r), 1);
+ if(utfrune(buf2+1, r) == nil)
+ keyevent(v, runetovnc(r), 0);
+ default:
+ continue;
+ }
+ strcpy(buf2, buf);
+ }
+}
+
--
⑨