Update websocket, with better error handling and removing debug output.
Warning: I don't have getbe() on my machine, so Bgetbe() is untested in this form!
Includes 9webdraw javascript to drive it in /usr/web/9wd/.
Reference: /n/atom/patch/applied/websocket-cleanup-addjs
Date: Mon Feb 17 21:50:51 CET 2014
Signed-off-by: root@davidrhoskin.com
--- /sys/man/8/websocket Mon Feb 17 21:42:08 2014
+++ /sys/man/8/websocket Mon Feb 17 21:42:10 2014
@@ -1,6 +1,6 @@
.TH WEBSOCKET 8
.SH NAME
-websocket \- a 9P-over-websocket bridge for httpd(8)
+websocket \- tunnel 9P over WebSocket
.SH SYNOPSIS
.B websocket
.I "magic parameters" ...
@@ -18,14 +18,11 @@
Currently, it always mounts the connection over
.B /dev/
and launches
-.IR catclock ,
+.IR acme ,
which expects the
-.B /dev/draw/
+.B /dev/draw
provided by
.IR 9webdraw .
-.SH FILES
-.TP
-.B /sys/log/websocket
.SH SOURCE
.B /sys/src/cmd/ip/httpd/websocket.c
.PP
@@ -37,29 +34,18 @@
.B https://bitbucket.org/dhoskin/9webdraw
.SH BUGS
The command
-.B /bin/games/catclock
+.B /bin/acme
is hardcoded.
.PP
No authentication is performed, and raw 9P is used rather than
.IR cpu (1)'s
protocol.
.PP
-More interesting programs such as
-.IR acme (1)
-cannot run as user
-.IR none ,
-because its default name\%space does not include a writeable
-.BR /tmp/ .
-.PP
Rather than hardcoding 9P, plugins for different protocols could
be chosen using the WebSocket subprotocol header.
.PP
Rather than running under
-.IR httpd (8)
-and starting a given command for each connection,
-it could be generalised to serve a
-.B /net/websocket/
-directory under which arbitrary programs could
-.IR announce (2),
-with the port field specifying the subprotocol:
-.BR "announce(``websocket!*!9p'', nil)" .
+.IR httpd (8),
+.I websocket
+could present a standard network connection directory in
+.BR /net/websocket .
--- /sys/src/cmd/ip/httpd/websocket.c Mon Feb 17 21:42:13 2014
+++ /sys/src/cmd/ip/httpd/websocket.c Mon Feb 17 21:42:15 2014
@@ -165,27 +165,18 @@
return 0;
}
-uvlong
-Bgetbe(Biobuf *b, int sz)
+int
+Bgetbe(Biobuf *b, uvlong *u, int sz)
{
uchar buf[8];
- int i;
- uvlong x;
if(Bread(b, buf, sz) != sz)
return -1;
- x = 0;
- for(i = 0; i < sz; ++i)
- x |= buf[i] << (8 * (sz - 1 - i));
-
- return x;
+ *u = getbe(buf, sz);
+ return 1;
}
-/* Assumptions:
-* We will never be masking the data.
-* Messages will be atomic: all frames are final.
-*/
int
sendpkt(Biobuf *b, Wspkt *pkt)
{
@@ -246,12 +237,11 @@
pkt->n &= 0x7F;
if(pkt->n >= 127){
- pkt->n = Bgetbe(b, 8);
+ if(Bgetbe(b, (uvlong *)&pkt->n, 8) != 1)
+ return -1;
}else if(pkt->n == 126){
- pkt->n = Bgetbe(b, 2);
- }
- if(pkt->n < 0){
- return -1;
+ if(Bgetbe(b, (uvlong *)&pkt->n, 2) != 1)
+ return -1;
}
if(masked){
@@ -274,8 +264,10 @@
if(pkt->buf == nil)
return -1;
- if(Bread(b, pkt->buf, pkt->n) != pkt->n)
+ if(Bread(b, pkt->buf, pkt->n) != pkt->n){
+ free(pkt->buf);
return -1;
+ }
if(masked)
for(x = 0; x < pkt->n; ++x)
@@ -300,8 +292,10 @@
for(;;){
if(recvpkt(&pkt, b) < 0)
break;
- if(send(c, &pkt) < 0)
+ if(send(c, &pkt) < 0){
+ free(pkt.buf);
break;
+ }
}
chanclose(c);
@@ -323,8 +317,10 @@
for(;;){
if(recv(c, &pkt) < 0)
break;
- if(sendpkt(b, &pkt) < 0)
+ if(sendpkt(b, &pkt) < 0){
+ free(pkt.buf);
break;
+ }
free(pkt.buf);
}
@@ -346,6 +342,8 @@
for(;;){
b.buf = malloc(BUFSZ);
+ if(b.buf == nil)
+ break;
b.n = read(fd, b.buf, BUFSZ);
if(b.n < 1)
break;
@@ -353,6 +351,7 @@
break;
}
+ free(b.buf);
chanclose(c);
threadexits(nil);
}
@@ -372,8 +371,10 @@
for(;;){
if(recv(c, &b) != 1)
break;
- if(write(fd, b.buf, b.n) != b.n)
+ if(write(fd, b.buf, b.n) != b.n){
+ free(b.buf);
break;
+ }
free(b.buf);
}
@@ -382,11 +383,20 @@
}
void
+ramfsproc(void *arg)
+{
+ Channel *c = (Channel *)arg;
+
+ procexecl(c, "/bin/ramfs", nil);
+}
+
+void
mountproc(void *arg)
{
Procio *pio;
int fd, i;
char **argv;
+ Channel *c;
pio = (Procio *)arg;
fd = pio->fd;
@@ -399,6 +409,11 @@
newns("none", nil);
+ c = chancreate(sizeof(ulong), 0);
+
+ proccreate(ramfsproc, c, STACKSZ);
+ recv(c, nil);
+
if(mount(fd, -1, "/dev/", MBEFORE, "") == -1)
sysfatal("mount failed: %r");
@@ -490,15 +505,13 @@
};
Procio fromws, tows, frompipe, topipe;
Procio mountp, echop;
- char *argv[] = {"/bin/games/catclock", nil};
+ char *argv[] = {"/bin/acme", nil};
fromws.c = chancreate(sizeof(Wspkt), CHANBUF);
tows.c = chancreate(sizeof(Wspkt), CHANBUF);
frompipe.c = chancreate(sizeof(Buf), CHANBUF);
topipe.c = chancreate(sizeof(Buf), CHANBUF);
- syslog(1, "websocket", "created chans");
-
a[0].c = fromws.c;
a[1].c = frompipe.c;
@@ -527,8 +540,6 @@
//proccreate(echoproc, &echop, STACKSZ);
procrfork(mountproc, &mountp, STACKSZ, RFNAMEG|RFFDG);
- syslog(1, "websocket", "created procs");
-
for(;;){
int i;
@@ -564,7 +575,6 @@
}
}
done:
- syslog(1, "websocket", "closing down cleanly");
return 1;
}
@@ -572,12 +582,6 @@
threadmain(int argc, char **argv)
{
HConnect *c;
- int errfd;
-
- errfd = open("/sys/log/websocket", OWRITE);
- dup(errfd, 2);
-
- syslog(1, "websocket", "websocket process %d", getpid());
c = init(argc, argv);
if(hparseheaders(c, HSTIMEOUT) >= 0)
--- /usr/web/9wd Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd Mon Feb 17 21:42:20 2014
@@ -0,0 +1,45 @@
+
+
+ 9webdraw
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 9webdraw
+
+
+
+
+
+
+
+
--- /usr/web/9wd/9wd.html Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/9wd.html Mon Feb 17 21:43:41 2014
@@ -0,0 +1,45 @@
+
+
+ 9webdraw
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 9webdraw
+
+
+
+
+
+
+
+
--- /usr/web/9wd/css Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/css Mon Feb 17 21:43:46 2014
@@ -0,0 +1,39 @@
+/*
+ riobg: #9cefef
+ riofg: #52aaad
+*/
+
+#container {
+ position: relative;
+}
+
+#webdraw {
+ border-width: medium;
+ border-style: solid;
+ border-color: #9cefef;
+}
+
+#cursor {
+ position: absolute;
+ top: 0em;
+ left: 0em;
+}
+
+#cons {
+ border-style: solid;
+ border-color: #52aaad;
+ width: 80em;
+ height: 25em;
+ overflow-y: scroll; /* XXX non-standard! */
+ font-family: monospace;
+}
+
+#cons span {
+ float: left;
+ clear: left;
+}
+
+#settings label{
+ float: left;
+ clear: left;
+}
--- /usr/web/9wd/css/9wd.css Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/css/9wd.css Mon Feb 17 21:43:47 2014
@@ -0,0 +1,39 @@
+/*
+ riobg: #9cefef
+ riofg: #52aaad
+*/
+
+#container {
+ position: relative;
+}
+
+#webdraw {
+ border-width: medium;
+ border-style: solid;
+ border-color: #9cefef;
+}
+
+#cursor {
+ position: absolute;
+ top: 0em;
+ left: 0em;
+}
+
+#cons {
+ border-style: solid;
+ border-color: #52aaad;
+ width: 80em;
+ height: 25em;
+ overflow-y: scroll; /* XXX non-standard! */
+ font-family: monospace;
+}
+
+#cons span {
+ float: left;
+ clear: left;
+}
+
+#settings label{
+ float: left;
+ clear: left;
+}
--- /usr/web/9wd/js Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js Mon Feb 17 21:43:52 2014
@@ -0,0 +1,74 @@
+/* See /sys/src/9/port/latin1.c */
+
+function Compose(parent){
+ var mode = false;
+ var buf = [];
+
+ var handle = function(){
+ if(buf.length < 2) return;
+ if(buf[0] == "X"){
+ if(buf.length < 5) return;
+ handleX();
+ }else{
+ for(var k in Composetab){
+ if(buf[0] == k.charAt(0)){
+ if(k.length == 1)
+ var c = buf[1];
+ else if(k.charAt(1) != buf[1])
+ continue;
+ else if(buf.length < 3)
+ return;
+ else
+ var c = buf[2];
+ /* parent.take so[si.find(c)] */
+ var i = Composetab[k].from.indexOf(c);
+ if(i < 0)
+ return reset();
+ else{
+ parent.take(Composetab[k].to[i]);
+ return reset();
+ }
+ }
+ }
+ return reset();
+ }
+ }
+
+ var handleX = function(){
+ var xdigits = "0123456789ABCDEF";
+ var i, x, c = 0;
+
+ if(buf.length != 5) return reset();
+
+ for(i = 1; i < 5; ++i){
+ x = xdigits.indexOf(buf[i].toUpperCase());
+ if(x < 0) return reset();
+ c |= x << ((4 - i) * 4);
+ }
+ parent.take(String.fromCharCode(c));
+ }
+
+ this.set = function(){
+ mode = true;
+ }
+
+ /* XXX Why can't I call ``this.reset()'' directly? */
+ var reset = function(){
+ mode = false;
+ buf = [];
+ }
+ this.reset = reset;
+
+ this.getmode = function(){
+ return mode;
+ }
+
+ this.push = function(c){
+ if(mode){
+ buf.push(c);
+ handle();
+ }else{
+ throw("Not in compose mode!");
+ }
+ }
+}
--- /usr/web/9wd/js Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js Mon Feb 17 21:43:53 2014
@@ -0,0 +1,102 @@
+var Composetab = {
+ " ": {from:" i", to:"␣ı"},
+ "!~": {from:"-=~", to:"≄≇≉"},
+ "!": {from:"!<=>?bmp", to:"¡≮≠≯‽⊄∉⊅"},
+ "\"*": {from:"IUiu", to:"ΪΫϊϋ"},
+ "\"": {from:"\"AEIOUYaeiouy", to:"¨ÄËÏÖÜŸäëïöüÿ"},
+ "$*": {from:"fhk", to:"ϕϑϰ"},
+ "$": {from:"BEFHILMRVaefglopv", to:"ℬℰℱℋℐℒℳℛƲɑℯƒℊℓℴ℘ʋ"},
+ "\'\"": {from:"Uu", to:"Ǘǘ"},
+ "\'": {from:"\'ACEILNORSUYZacegilnorsuyz", to:"´ÁĆÉÍĹŃÓŔŚÚÝŹáćéģíĺńóŕśúýź"},
+ "*": {from:"*ABCDEFGHIKLMNOPQRSTUWXYZabcdefghiklmnopqrstuwxyz", to:"∗ΑΒΞΔΕΦΓΘΙΚΛΜΝΟΠΨΡΣΤΥΩΧΗΖαβξδεφγθικλμνοπψρστυωχηζ"},
+ "+": {from:"-O", to:"±⊕"},
+ ",": {from:",ACEGIKLNORSTUacegiklnorstu", to:"¸ĄÇĘĢĮĶĻŅǪŖŞŢŲąçęģįķļņǫŗşţų"},
+ "-*": {from:"l", to:"ƛ"},
+ "-": {from:"+-2:>DGHILOTZbdghiltuz~", to:"∓ƻ÷→ÐǤĦƗŁ⊖ŦƵƀðǥℏɨłŧʉƶ≂"},
+ ".": {from:".CEGILOZceglz", to:"·ĊĖĠİĿ⊙Żċėġŀż"},
+ "/": {from:"Oo", to:"Øø"},
+ "1": {from:".234568", to:"․½⅓¼⅕⅙⅛"},
+ "2": {from:"-.35", to:"ƻ‥⅔⅖"},
+ "3": {from:".458", to:"…¾⅗⅜"},
+ "4": {from:"5", to:"⅘"},
+ "5": {from:"68", to:"⅚⅝"},
+ "7": {from:"8", to:"⅞"},
+ ":": {from:"()-=", to:"☹☺÷≔"},
+ "~", to:"←«≤≶≲"},
+ "=": {from:":<=>OV", to:"≕⋜≡⋝⊜⇒"},
+ ">!": {from:"=~", to:"≩⋧"},
+ ">": {from:"<=>~", to:"≷≥»≳"},
+ "?": {from:"!?", to:"‽¿"},
+ "@\'": {from:"\'", to:"ъ"},
+ "@@": {from:"\'EKSTYZekstyz", to:"ьЕКСТЫЗекстыз"},
+ "@C": {from:"Hh", to:"ЧЧ"},
+ "@E": {from:"Hh", to:"ЭЭ"},
+ "@K": {from:"Hh", to:"ХХ"},
+ "@S": {from:"CHch", to:"ЩШЩШ"},
+ "@T": {from:"Ss", to:"ЦЦ"},
+ "@Y": {from:"AEOUaeou", to:"ЯЕЁЮЯЕЁЮ"},
+ "@Z": {from:"Hh", to:"ЖЖ"},
+ "@c": {from:"h", to:"ч"},
+ "@e": {from:"h", to:"э"},
+ "@k": {from:"h", to:"х"},
+ "@s": {from:"ch", to:"щш"},
+ "@t": {from:"s", to:"ц"},
+ "@y": {from:"aeou", to:"яеёю"},
+ "@z": {from:"h", to:"ж"},
+ "@": {from:"ABDFGIJLMNOPRUVXabdfgijlmnopruvx", to:"АБДФГИЙЛМНОПРУВХабдфгийлмнопрувх"},
+ "A": {from:"E", to:"Æ"},
+ "C": {from:"ACU", to:"⋂ℂ⋃"},
+ "Dv": {from:"Zz", to:"DŽDž"},
+ "D": {from:"-e", to:"Ð∆"},
+ "G": {from:"-", to:"Ǥ"},
+ "H": {from:"-H", to:"Ħℍ"},
+ "I": {from:"-J", to:"ƗIJ"},
+ "L": {from:"&-Jj|", to:"⋀ŁLJLj⋁"},
+ "M": {from:"#48bs", to:"♮♩♪♭♯"},
+ "N": {from:"JNj", to:"NJℕNj"},
+ "O": {from:"*+-./=EIcoprx", to:"⊛⊕⊖⊙⊘⊜ŒƢ©⊚℗®⊗"},
+ "P": {from:"P", to:"ℙ"},
+ "Q": {from:"Q", to:"ℚ"},
+ "R": {from:"R", to:"ℝ"},
+ "S": {from:"123S", to:"¹²³§"},
+ "T": {from:"-u", to:"Ŧ⊨"},
+ "V": {from:"=", to:"⇐"},
+ "Y": {from:"R", to:"Ʀ"},
+ "Z": {from:"-ACSZ", to:"Ƶℤ"},
+ "^": {from:"ACEGHIJOSUWYaceghijosuwy", to:"ÂĈÊĜĤÎĴÔŜÛŴŶâĉêĝĥîĵôŝûŵŷ"},
+ "_\"": {from:"AUau", to:"ǞǕǟǖ"},
+ "_,": {from:"Oo", to:"Ǭǭ"},
+ "_.": {from:"Aa", to:"Ǡǡ"},
+ "_": {from:"AEIOU_aeiou", to:"ĀĒĪŌŪ¯āēīōū"},
+ "`\"": {from:"Uu", to:"Ǜǜ"},
+ "`": {from:"AEIOUaeiou", to:"ÀÈÌÒÙàèìòù"},
+ "a": {from:"ben", to:"↔æ∠"},
+ "b": {from:"()+-0123456789=bknpqru", to:"₍₎₊₋₀₁₂₃₄₅₆₇₈₉₌♝♚♞♟♛♜•"},
+ "c": {from:"$Oagu", to:"¢©∩≅∪"},
+ "dv": {from:"z", to:"dž"},
+ "d": {from:"-adegz", to:"ð↓‡°†ʣ"},
+ "e": {from:"$lmns", to:"€⋯—–∅"},
+ "f": {from:"a", to:"∀"},
+ "g": {from:"$-r", to:"¤ǥ∇"},
+ "h": {from:"-v", to:"ℏƕ"},
+ "i": {from:"-bfjps", to:"ɨ⊆∞ij⊇∫"},
+ "l": {from:"\"$&\'-jz|", to:"“£∧‘łlj⋄∨"},
+ "m": {from:"iou", to:"µ∈×"},
+ "n": {from:"jo", to:"nj¬"},
+ "o": {from:"AOUaeiu", to:"Å⊚Ůåœƣů"},
+ "p": {from:"Odgrt", to:"℗∂¶∏∝"},
+ "r": {from:"\"\'O", to:"”’®"},
+ "s": {from:"()+-0123456789=abnoprstu", to:"⁽⁾⁺⁻⁰ⁱ⁴⁵⁶⁷⁸⁹⁼ª⊂ⁿº⊃√ß∍∑"},
+ "t": {from:"-efmsu", to:"ŧ∃∴™ς⊢"},
+ "u": {from:"-AEGIOUaegiou", to:"ʉĂĔĞĬŎŬ↑ĕğĭŏŭ"},
+ "v\"": {from:"Uu", to:"Ǚǚ"},
+ "v": {from:"ACDEGIKLNORSTUZacdegijklnorstuz", to:"ǍČĎĚǦǏǨĽŇǑŘŠŤǓŽǎčďěǧǐǰǩľňǒřšťǔž"},
+ "w": {from:"bknpqr", to:"♗♔♘♙♕♖"},
+ "x": {from:"O", to:"⊗"},
+ "y": {from:"$", to:"¥"},
+ "z": {from:"-", to:"ƶ"},
+ "|": {from:"Pp|", to:"Þþ¦"},
+ "~!": {from:"=", to:"≆"},
+ "~": {from:"-=AINOUainou~", to:"≃≅ÃĨÑÕŨãĩñõũ≈"},
+};
--- /usr/web/9wd/js Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js Mon Feb 17 21:43:54 2014
@@ -0,0 +1,99 @@
+var cons;
+
+function Cons(){
+ this.elem = elem("cons");
+ this.buf = "";
+ this.callbacks = [];
+ this.kbd = {down: "down", up: "up", press: "press"};
+
+ var compose = new Compose(this);
+
+ this.log = function(s){
+ var span = document.createElement("span");
+ span.textContent = s;
+ this.elem.appendChild(span);
+ }
+
+ this.write = function(s){
+ this.log(s);
+ /* ninep.write(s); */
+ }
+
+ this.showhide = function(b){
+ this.elem.style.display = b? "block": "none";
+ }
+
+ this.handlekeys = function(e, dir){
+ if(!mouse.handlefkeys(e, dir == cons.kbd.down?
+ mouse.states.down : mouse.states.up)){
+ return 0;
+ }
+
+ if(dir == cons.kbd.press){
+ if(compose.getmode()){
+ compose.push(String.fromCharCode(e.which));
+ }else{
+ this.buf += String.fromCharCode(e.which);
+ this.flushcallbacks();
+ }
+ e.preventDefault();
+ e.stopPropagation();
+ return 0;
+ }
+
+ if(dir == cons.kbd.down){
+ /* XXX control characters should break compose mode! */
+ if(compose.getmode()) return 0;
+
+ var s = this.key2str(e);
+ if(s == "") return 1;
+ this.buf += s;
+ this.flushcallbacks();
+ e.preventDefault();
+ e.stopPropagation();
+ return 0;
+ }
+ return 0;
+ }
+
+ this.key2str = function(e){
+
+ switch(e.which){
+ case 0:
+ break;
+ case 13:
+ return "\n";
+ case 18:
+ compose.set();
+ /* fall through */
+ default:
+ return "";
+ }
+
+ return "[control character]";
+ }
+
+ this.take = function(s){
+ this.buf += s;
+ this.flushcallbacks();
+ }
+
+ this.addcallback = function(callback){
+ this.callbacks.push(callback);
+ this.flushcallbacks();
+ }
+
+ this.flushcallbacks = function(){
+ if(this.buf == ""){
+ return;
+ }
+
+ for(var i in this.callbacks){
+ this.callbacks[i].read(this.buf.toUTF8Array());
+ this.buf = "";
+ }
+ this.callbacks = [];
+ }
+
+}
+
--- /usr/web/9wd/js Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js Mon Feb 17 21:44:18 2014
@@ -0,0 +1,74 @@
+function elem(name){
+ return document.getElementById(name);
+}
+
+function addevent(elem, evt, handler){
+ elem.addEventListener(evt, handler, true);
+}
+
+/* this should not be necessary, but */
+/* addevent does not seem to let me */
+/* keep the F3 key event from propagating */
+/* on Firefox 20. */
+function setevent(elem, evt, handler){
+ elem["on" + evt] = handler;
+}
+
+var basetime;
+var cons;
+var mouse;
+var settings;
+var ninep;
+
+window.onload = function(){
+ //var wsurl = Socket.wsurl(window.location.toString());
+ var wsurl = "ws://172.16.0.17/magic/websocket";
+ var webdraw = elem("webdraw");
+
+ basetime = Date.now();
+ cons = new Cons();
+ mouse = new Mouse(elem("cursor"));
+ settings = new Settings();
+ ninep = new NineP(wsurl, Draw9p, cons);
+
+ /* XXX Draw9p should be instantiated and have a constructor. */
+ Draw9p.rootcanvas = webdraw;
+ Draw9p.imgnames["webdraw"] = Draw9p.RootImage();
+ Draw9p.label = "webdraw".toUTF8Array();
+
+ addevent(webdraw, "mousedown", function(e){
+ return mouse.handlebutton(e, 1);
+ });
+ addevent(webdraw, "mouseup", function(e){
+ return mouse.handlebutton(e, 0);
+ });
+ addevent(webdraw, "mousemove", function(e){
+ return mouse.handlemove(e);
+ });
+ setevent(window, "keydown", function(e){
+ return cons.handlekeys(e, cons.kbd.down);
+ });
+ setevent(window, "keypress", function(e){
+ return cons.handlekeys(e, cons.kbd.press);
+ });
+ setevent(window, "keyup", function(e){
+ return cons.handlekeys(e, cons.kbd.up);
+ });
+
+ setevent(webdraw, "click", function(e){
+ if(
+ document.pointerLockElement !== webdraw &&
+ document.mozPointerLockElement !== webdraw &&
+ document.webkitPointerLockElement !== webdraw
+ ){
+ webdraw.requestPointerLock =
+ webdraw.requestPointerLock ||
+ webdraw.mozRequestPointerLock ||
+ webdraw.webkitRequestPointerLock;
+ webdraw.requestPointerLock();
+ return false;
+ }else{
+ return true;
+ }
+ });
+}
--- /usr/web/9wd/js Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js Mon Feb 17 21:44:38 2014
@@ -0,0 +1,167 @@
+var mouse;
+
+function Mouse(cursorelem){
+ var State = function(position, buttons){
+ this.position = position;
+ this.buttons = buttons;
+ this.timestamp = Date.now() - basetime;
+ }
+ State.prototype.copy = function(){
+ return new State(this.position, this.buttons);
+ }
+ State.prototype.toWireFormat = function(){
+ var buf = "m".toUTF8Array();
+ buf = buf.concat(pad11(this.position.x));
+ buf = buf.concat(pad11(this.position.y));
+ buf = buf.concat(pad11(this.buttons));
+ buf = buf.concat(pad11(this.timestamp));
+ return buf;
+ }
+
+ this.states = {down: 1, up: 0};
+ this.state = new State({x: 0, y: 0}, 0);
+
+ this.usefkeys = false;
+ this.callbacks = [];
+ this.buf = [];
+
+ this.handlefkeys = function(e, state){
+ if(!this.usefkeys){
+ return true;
+ }
+ switch(e.keyCode){
+ case 112:
+ this.state.buttons = (this.state.buttons& ~1) | state<<0;
+ break;
+ case 113:
+ this.state.buttons = (this.state.buttons& ~2) | state<<1;
+ break;
+ case 114:
+ this.state.buttons = (this.state.buttons& ~4) | state<<2;
+ break;
+ default:
+ return true;
+ }
+ this.generatemovement(this.state);
+ return false;
+ }
+
+ this.handlebutton = function(e, state){
+ this.state.buttons = (this.state.buttons& ~(1< Draw9p.rootcanvas.width){
+ this.state.position.x = Draw9p.rootcanvas.width;
+ }
+ if(this.state.position.x < 0){
+ this.state.position.x = 0;
+ }
+ this.state.position.y +=
+ e.movementY ||
+ e.mozMovementY ||
+ e.webkitMovementY ||
+ 0;
+ if(this.state.position.y > Draw9p.rootcanvas.height){
+ this.state.position.y = Draw9p.rootcanvas.height;
+ }
+ if(this.state.position.y < 0){
+ this.state.position.y = 0;
+ }
+
+ this.cursor.goto(this.state.position);
+ this.generatemovement(this.state);
+ return false;
+}
+
+ this.generatemovement = function(state){
+ cons.write("m " + state.position.x + ", " + state.position.y +
+ " : " + state.buttons);
+ this.buf.push(this.state.copy());
+ this.flushcallbacks();
+ }
+
+ this.addcallback = function(callback){
+ this.callbacks.push(callback);
+ this.flushcallbacks();
+ }
+
+ this.flushcallbacks = function(){
+ while(this.callbacks.length > 0 && this.buf.length > 0){
+ this.callbacks.shift().read(this.buf.shift().toWireFormat());
+ }
+ }
+
+ this.cursor = {
+ arrow: [
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+
+ 0xFF, 0xFF, 0x80, 0x01, 0x80, 0x02, 0x80, 0x0C,
+ 0x80, 0x10, 0x80, 0x10, 0x80, 0x08, 0x80, 0x04,
+ 0x80, 0x02, 0x80, 0x01, 0x80, 0x02, 0x8C, 0x04,
+ 0x92, 0x08, 0x91, 0x10, 0xA0, 0xA0, 0xC0, 0x40,
+
+ 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFC, 0x7F, 0xF0,
+ 0x7F, 0xE0, 0x7F, 0xE0, 0x7F, 0xF0, 0x7F, 0xF8,
+ 0x7F, 0xFC, 0x7F, 0xFE, 0x7F, 0xFC, 0x73, 0xF8,
+ 0x61, 0xF0, 0x60, 0xE0, 0x40, 0x40, 0x00, 0x00,
+ ],
+ img: (function(elem){
+ var c = elem;
+ c.width = c.height = 16;
+ return {
+ canvas: c,
+ ctx: c.getContext("2d"),
+ clear: function(){
+ this.ctx.clearRect(0, 0, 16, 16);
+ },
+ fill: function(data, px){
+ var id = this.ctx.getImageData(0, 0, 16, 16);
+ var cp = 0; /* canvas pointer */
+ for(var i=0; i<32; ++i){
+ for(var b=7; b>=0; --b){
+ var p = (data[i]>>b) & 1;
+ if(p){
+ id.data[cp++] = px;
+ id.data[cp++] = px;
+ id.data[cp++] = px;
+ id.data[cp++] = 0xFF;
+ }else{
+ cp += 4;
+ }
+ }
+ }
+ this.ctx.putImageData(id, 0, 0);
+ }
+ };
+ })(cursorelem),
+ offset: {x: 0, y: 0},
+ write: function(data){
+ if(data.length != 72){
+ data = this.arrow;
+ }
+ this.img.clear();
+ var ai = new ArrayIterator(data);
+ this.offset = ai.getPoint();
+ this.img.fill(ai.getBytes(32), 0xFF);
+ this.img.fill(ai.getBytes(32), 0x00);
+ return data.length;
+ },
+ goto: function(pos){
+ var x = pos.x - this.offset.x;
+ var y = pos.y - this.offset.y;
+ this.img.canvas.style.left = x + "px";
+ this.img.canvas.style.top = y + "px";
+ }
+ }
+ this.cursor.write([]);
+
+}
--- /usr/web/9wd/js Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js Mon Feb 17 21:44:40 2014
@@ -0,0 +1,31 @@
+function Settings(){
+
+ this.settings = ["mousefkeys", "showcons"];
+
+ this.addsetting = function(setting){
+ setevent(elem(setting), "click", function(){
+ return settings.set(setting, this.checked? true: false);
+ });
+ }
+
+ this.set = function(name, value){
+
+ switch(name){
+ case "mousefkeys":
+ mouse.usefkeys = value;
+ return false;
+ case "showcons":
+ cons.showhide(value);
+ return true;
+ default:
+ return true;
+ }
+ localStorage.setItem(name, value);
+
+ }
+
+ for(var setting in this.settings){
+ this.set(this.settings[setting], localStorage.getItem(this.settings[setting]) == "true"? true: false);
+ this.addsetting(this.settings[setting]);
+ }
+}
--- /usr/web/9wd/js/compose.js Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/compose.js Mon Feb 17 21:44:49 2014
@@ -0,0 +1,74 @@
+/* See /sys/src/9/port/latin1.c */
+
+function Compose(parent){
+ var mode = false;
+ var buf = [];
+
+ var handle = function(){
+ if(buf.length < 2) return;
+ if(buf[0] == "X"){
+ if(buf.length < 5) return;
+ handleX();
+ }else{
+ for(var k in Composetab){
+ if(buf[0] == k.charAt(0)){
+ if(k.length == 1)
+ var c = buf[1];
+ else if(k.charAt(1) != buf[1])
+ continue;
+ else if(buf.length < 3)
+ return;
+ else
+ var c = buf[2];
+ /* parent.take so[si.find(c)] */
+ var i = Composetab[k].from.indexOf(c);
+ if(i < 0)
+ return reset();
+ else{
+ parent.take(Composetab[k].to[i]);
+ return reset();
+ }
+ }
+ }
+ return reset();
+ }
+ }
+
+ var handleX = function(){
+ var xdigits = "0123456789ABCDEF";
+ var i, x, c = 0;
+
+ if(buf.length != 5) return reset();
+
+ for(i = 1; i < 5; ++i){
+ x = xdigits.indexOf(buf[i].toUpperCase());
+ if(x < 0) return reset();
+ c |= x << ((4 - i) * 4);
+ }
+ parent.take(String.fromCharCode(c));
+ }
+
+ this.set = function(){
+ mode = true;
+ }
+
+ /* XXX Why can't I call ``this.reset()'' directly? */
+ var reset = function(){
+ mode = false;
+ buf = [];
+ }
+ this.reset = reset;
+
+ this.getmode = function(){
+ return mode;
+ }
+
+ this.push = function(c){
+ if(mode){
+ buf.push(c);
+ handle();
+ }else{
+ throw("Not in compose mode!");
+ }
+ }
+}
--- /usr/web/9wd/js/composetab.js Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/composetab.js Mon Feb 17 21:44:50 2014
@@ -0,0 +1,102 @@
+var Composetab = {
+ " ": {from:" i", to:"␣ı"},
+ "!~": {from:"-=~", to:"≄≇≉"},
+ "!": {from:"!<=>?bmp", to:"¡≮≠≯‽⊄∉⊅"},
+ "\"*": {from:"IUiu", to:"ΪΫϊϋ"},
+ "\"": {from:"\"AEIOUYaeiouy", to:"¨ÄËÏÖÜŸäëïöüÿ"},
+ "$*": {from:"fhk", to:"ϕϑϰ"},
+ "$": {from:"BEFHILMRVaefglopv", to:"ℬℰℱℋℐℒℳℛƲɑℯƒℊℓℴ℘ʋ"},
+ "\'\"": {from:"Uu", to:"Ǘǘ"},
+ "\'": {from:"\'ACEILNORSUYZacegilnorsuyz", to:"´ÁĆÉÍĹŃÓŔŚÚÝŹáćéģíĺńóŕśúýź"},
+ "*": {from:"*ABCDEFGHIKLMNOPQRSTUWXYZabcdefghiklmnopqrstuwxyz", to:"∗ΑΒΞΔΕΦΓΘΙΚΛΜΝΟΠΨΡΣΤΥΩΧΗΖαβξδεφγθικλμνοπψρστυωχηζ"},
+ "+": {from:"-O", to:"±⊕"},
+ ",": {from:",ACEGIKLNORSTUacegiklnorstu", to:"¸ĄÇĘĢĮĶĻŅǪŖŞŢŲąçęģįķļņǫŗşţų"},
+ "-*": {from:"l", to:"ƛ"},
+ "-": {from:"+-2:>DGHILOTZbdghiltuz~", to:"∓ƻ÷→ÐǤĦƗŁ⊖ŦƵƀðǥℏɨłŧʉƶ≂"},
+ ".": {from:".CEGILOZceglz", to:"·ĊĖĠİĿ⊙Żċėġŀż"},
+ "/": {from:"Oo", to:"Øø"},
+ "1": {from:".234568", to:"․½⅓¼⅕⅙⅛"},
+ "2": {from:"-.35", to:"ƻ‥⅔⅖"},
+ "3": {from:".458", to:"…¾⅗⅜"},
+ "4": {from:"5", to:"⅘"},
+ "5": {from:"68", to:"⅚⅝"},
+ "7": {from:"8", to:"⅞"},
+ ":": {from:"()-=", to:"☹☺÷≔"},
+ "~", to:"←«≤≶≲"},
+ "=": {from:":<=>OV", to:"≕⋜≡⋝⊜⇒"},
+ ">!": {from:"=~", to:"≩⋧"},
+ ">": {from:"<=>~", to:"≷≥»≳"},
+ "?": {from:"!?", to:"‽¿"},
+ "@\'": {from:"\'", to:"ъ"},
+ "@@": {from:"\'EKSTYZekstyz", to:"ьЕКСТЫЗекстыз"},
+ "@C": {from:"Hh", to:"ЧЧ"},
+ "@E": {from:"Hh", to:"ЭЭ"},
+ "@K": {from:"Hh", to:"ХХ"},
+ "@S": {from:"CHch", to:"ЩШЩШ"},
+ "@T": {from:"Ss", to:"ЦЦ"},
+ "@Y": {from:"AEOUaeou", to:"ЯЕЁЮЯЕЁЮ"},
+ "@Z": {from:"Hh", to:"ЖЖ"},
+ "@c": {from:"h", to:"ч"},
+ "@e": {from:"h", to:"э"},
+ "@k": {from:"h", to:"х"},
+ "@s": {from:"ch", to:"щш"},
+ "@t": {from:"s", to:"ц"},
+ "@y": {from:"aeou", to:"яеёю"},
+ "@z": {from:"h", to:"ж"},
+ "@": {from:"ABDFGIJLMNOPRUVXabdfgijlmnopruvx", to:"АБДФГИЙЛМНОПРУВХабдфгийлмнопрувх"},
+ "A": {from:"E", to:"Æ"},
+ "C": {from:"ACU", to:"⋂ℂ⋃"},
+ "Dv": {from:"Zz", to:"DŽDž"},
+ "D": {from:"-e", to:"Ð∆"},
+ "G": {from:"-", to:"Ǥ"},
+ "H": {from:"-H", to:"Ħℍ"},
+ "I": {from:"-J", to:"ƗIJ"},
+ "L": {from:"&-Jj|", to:"⋀ŁLJLj⋁"},
+ "M": {from:"#48bs", to:"♮♩♪♭♯"},
+ "N": {from:"JNj", to:"NJℕNj"},
+ "O": {from:"*+-./=EIcoprx", to:"⊛⊕⊖⊙⊘⊜ŒƢ©⊚℗®⊗"},
+ "P": {from:"P", to:"ℙ"},
+ "Q": {from:"Q", to:"ℚ"},
+ "R": {from:"R", to:"ℝ"},
+ "S": {from:"123S", to:"¹²³§"},
+ "T": {from:"-u", to:"Ŧ⊨"},
+ "V": {from:"=", to:"⇐"},
+ "Y": {from:"R", to:"Ʀ"},
+ "Z": {from:"-ACSZ", to:"Ƶℤ"},
+ "^": {from:"ACEGHIJOSUWYaceghijosuwy", to:"ÂĈÊĜĤÎĴÔŜÛŴŶâĉêĝĥîĵôŝûŵŷ"},
+ "_\"": {from:"AUau", to:"ǞǕǟǖ"},
+ "_,": {from:"Oo", to:"Ǭǭ"},
+ "_.": {from:"Aa", to:"Ǡǡ"},
+ "_": {from:"AEIOU_aeiou", to:"ĀĒĪŌŪ¯āēīōū"},
+ "`\"": {from:"Uu", to:"Ǜǜ"},
+ "`": {from:"AEIOUaeiou", to:"ÀÈÌÒÙàèìòù"},
+ "a": {from:"ben", to:"↔æ∠"},
+ "b": {from:"()+-0123456789=bknpqru", to:"₍₎₊₋₀₁₂₃₄₅₆₇₈₉₌♝♚♞♟♛♜•"},
+ "c": {from:"$Oagu", to:"¢©∩≅∪"},
+ "dv": {from:"z", to:"dž"},
+ "d": {from:"-adegz", to:"ð↓‡°†ʣ"},
+ "e": {from:"$lmns", to:"€⋯—–∅"},
+ "f": {from:"a", to:"∀"},
+ "g": {from:"$-r", to:"¤ǥ∇"},
+ "h": {from:"-v", to:"ℏƕ"},
+ "i": {from:"-bfjps", to:"ɨ⊆∞ij⊇∫"},
+ "l": {from:"\"$&\'-jz|", to:"“£∧‘łlj⋄∨"},
+ "m": {from:"iou", to:"µ∈×"},
+ "n": {from:"jo", to:"nj¬"},
+ "o": {from:"AOUaeiu", to:"Å⊚Ůåœƣů"},
+ "p": {from:"Odgrt", to:"℗∂¶∏∝"},
+ "r": {from:"\"\'O", to:"”’®"},
+ "s": {from:"()+-0123456789=abnoprstu", to:"⁽⁾⁺⁻⁰ⁱ⁴⁵⁶⁷⁸⁹⁼ª⊂ⁿº⊃√ß∍∑"},
+ "t": {from:"-efmsu", to:"ŧ∃∴™ς⊢"},
+ "u": {from:"-AEGIOUaegiou", to:"ʉĂĔĞĬŎŬ↑ĕğĭŏŭ"},
+ "v\"": {from:"Uu", to:"Ǚǚ"},
+ "v": {from:"ACDEGIKLNORSTUZacdegijklnorstuz", to:"ǍČĎĚǦǏǨĽŇǑŘŠŤǓŽǎčďěǧǐǰǩľňǒřšťǔž"},
+ "w": {from:"bknpqr", to:"♗♔♘♙♕♖"},
+ "x": {from:"O", to:"⊗"},
+ "y": {from:"$", to:"¥"},
+ "z": {from:"-", to:"ƶ"},
+ "|": {from:"Pp|", to:"Þþ¦"},
+ "~!": {from:"=", to:"≆"},
+ "~": {from:"-=AINOUainou~", to:"≃≅ÃĨÑÕŨãĩñõũ≈"},
+};
--- /usr/web/9wd/js/cons.js Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/cons.js Mon Feb 17 21:44:52 2014
@@ -0,0 +1,99 @@
+var cons;
+
+function Cons(){
+ this.elem = elem("cons");
+ this.buf = "";
+ this.callbacks = [];
+ this.kbd = {down: "down", up: "up", press: "press"};
+
+ var compose = new Compose(this);
+
+ this.log = function(s){
+ var span = document.createElement("span");
+ span.textContent = s;
+ this.elem.appendChild(span);
+ }
+
+ this.write = function(s){
+ this.log(s);
+ /* ninep.write(s); */
+ }
+
+ this.showhide = function(b){
+ this.elem.style.display = b? "block": "none";
+ }
+
+ this.handlekeys = function(e, dir){
+ if(!mouse.handlefkeys(e, dir == cons.kbd.down?
+ mouse.states.down : mouse.states.up)){
+ return 0;
+ }
+
+ if(dir == cons.kbd.press){
+ if(compose.getmode()){
+ compose.push(String.fromCharCode(e.which));
+ }else{
+ this.buf += String.fromCharCode(e.which);
+ this.flushcallbacks();
+ }
+ e.preventDefault();
+ e.stopPropagation();
+ return 0;
+ }
+
+ if(dir == cons.kbd.down){
+ /* XXX control characters should break compose mode! */
+ if(compose.getmode()) return 0;
+
+ var s = this.key2str(e);
+ if(s == "") return 1;
+ this.buf += s;
+ this.flushcallbacks();
+ e.preventDefault();
+ e.stopPropagation();
+ return 0;
+ }
+ return 0;
+ }
+
+ this.key2str = function(e){
+
+ switch(e.which){
+ case 0:
+ break;
+ case 13:
+ return "\n";
+ case 18:
+ compose.set();
+ /* fall through */
+ default:
+ return "";
+ }
+
+ return "[control character]";
+ }
+
+ this.take = function(s){
+ this.buf += s;
+ this.flushcallbacks();
+ }
+
+ this.addcallback = function(callback){
+ this.callbacks.push(callback);
+ this.flushcallbacks();
+ }
+
+ this.flushcallbacks = function(){
+ if(this.buf == ""){
+ return;
+ }
+
+ for(var i in this.callbacks){
+ this.callbacks[i].read(this.buf.toUTF8Array());
+ this.buf = "";
+ }
+ this.callbacks = [];
+ }
+
+}
+
--- /usr/web/9wd/js/draw Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/draw Mon Feb 17 21:44:56 2014
@@ -0,0 +1,94 @@
+var dc = function(type, nbits){
+ return ((type & 0xF) << 4) | (nbits & 0xF);
+}
+var c1 = function(a,b){
+ return dc(a,b);
+}
+var c2 = function(a,b,c,d){
+ return c1(a,b)<<8 | dc(c,d);
+}
+var c3 = function(a,b,c,d,e,f){
+ return c2(a,b,c,d)<<8 | dc(e,f);
+}
+var c4 = function(a,b,c,d,e,f,g,h){
+ return c3(a,b,c,d,e,f)<<8 | dc(g,h);
+}
+
+Chan = {
+ NBITS: function(c){
+ return c & 0xF;
+ },
+ TYPE: function(c){
+ return (c >> 4) & 0xF;
+ },
+ channames: ['r', 'g', 'b', 'k', 'a', 'm', 'x'],
+ chans: {
+ CRed: 0,
+ CGreen: 1,
+ CBlue: 2,
+ CGrey: 3,
+ CAlpha: 4,
+ CMap: 5,
+ CIgnore: 6,
+ NChan: 7
+ },
+ chantostr: function(cc){
+ var buf = [];
+ var c, rc;
+
+ if(chantodepth(cc) == 0){
+ return undefined;
+ }
+
+ rc = 0;
+ for(c = cc; c; c >>= 8){
+ rc <<= 8;
+ rc |= c & 0xFF;
+ }
+
+ for(c = rc; c; c >>= 8){
+ buf.push(this.channames[this.TYPE(c)]);
+ buf.push(this.NBITS(c));
+ }
+
+ return buf.join("");
+ },
+ strtochan: function(s){
+ throw("strtochan not implemented");
+ },
+ chantodepth: function(c){
+ var n;
+
+ for(n = 0; c; c >>= 8){
+ if(this.TYPE(c) >= this.chans.NChan ||
+ this.NBITS(c) > 8 ||
+ this.NBITS(c) <= 0){
+ return 0;
+ }
+ n += this.NBITS(c);
+ }
+ if(n == 0 || (n > 8 && n % 8) || (n < 8 && 8 % n)){
+ return 0;
+ }
+ return n;
+ }
+}
+
+Chan.fmts = (function(c){
+ with(c){ return {
+ GREY1: c1(CGrey, 1),
+ GREY2: c1(CGrey, 2),
+ GREY4: c1(CGrey, 4),
+ GREY8: c1(CGrey, 8),
+ CMAP8: c1(CMap, 8),
+ RGB15: c4(CIgnore, 1, CRed, 5, CGreen, 5, CBlue, 5),
+ RGB16: c3(CRed, 5, CGreen, 6, CBlue, 5),
+ RGB24: c3(CRed, 8, CGreen, 8, CBlue, 8),
+ RGBA32: c4(CRed, 8, CGreen, 8, CBlue, 8, CAlpha, 8),
+ ARGB32: c4(CAlpha, 8, CRed, 8, CGreen, 8, CBlue, 8), /* stupid VGAs */
+ XRGB32: c4(CIgnore, 8, CRed, 8, CGreen, 8, CBlue, 8),
+ BGR24: c3(CBlue, 8, CGreen, 8, CRed, 8),
+ ABGR32: c4(CAlpha, 8, CBlue, 8, CGreen, 8, CRed, 8),
+ XBGR32: c4(CIgnore, 8, CBlue, 8, CGreen, 8, CRed, 8),
+ }}
+}(Chan.chans))
--- /usr/web/9wd/js/draw Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/draw Mon Feb 17 21:44:57 2014
@@ -0,0 +1,54 @@
+/* I don't understand what's going on here. */
+/* Cribbed from /sys/src/libdraw/rgb.c */
+
+Cmap = {
+ rbg2cmap: function(red, green, blue){
+ var i, r, g, b, sq;
+ var rgb;
+ var best, bestq;
+
+ best = 0;
+ bestsq = 0x7FFFFFFF;
+ for(i = 0; i < 256; i++){
+ rgb = cmap2rgb(i);
+ r = (rgb >> 16) & 0xFF;
+ g = (rgb >> 8) & 0xFF;
+ b = (rgb >> 0) & 0xFF;
+ sq = (r-cr)*(r-cr)+(g-cg)*(g-cg)+(b-cb)*(b-cb);
+ if(sq < bestq){
+ bestq = sq;
+ best = i;
+ }
+ }
+ return best;
+ },
+ cmap2rgb: function(c){
+ var j, num, den, r, g, b, v, rgb;
+
+ r = c >> 6;
+ v = (c >> 4) & 3;
+ j = (c - v + r) & 15;
+ g = j >> 2;
+ b = j & 3;
+ den = r;
+ if(g > den){
+ den = g;
+ }
+ if(b > den){
+ den = b;
+ }
+ if(den == 0){
+ v *= 17;
+ rgb = (v << 16) | (v << 8) | v;
+ }else{
+ num = 17 * (4 * den + v);
+ rgb = (Math.floor(r * num / den) << 16) |
+ (Math.floor(g * num / den) << 8) |
+ Math.floor(b * num / den);
+ }
+ return rgb;
+ },
+ cmap2rgba: function(c){
+ return (this.cmap2rgb(c) << 8) | 0xFF;
+ }
+}
--- /usr/web/9wd/js/draw Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/draw Mon Feb 17 21:44:58 2014
@@ -0,0 +1,48 @@
+var mka11 = function(padder){
+ var buffer = [];
+ return function(data){
+ return buffer = buffer.concat(padder(data));
+ }
+}
+
+Draw9p.readdrawctl = function(fid, offset){
+ cons.log("readdrawctl");
+ var dd = this.drawdir(fid.qid.path);
+
+ var conn = this.conns[dd.drawdir];
+ if(conn == undefined){
+ throw("invalid draw connection");
+ }
+ var img = conn.imgs[conn.imgid];
+ if(img == undefined){
+ throw("invalid image");
+ }
+
+ if(offset == 0){
+ var a11 = mka11(pad11);
+ a11(conn.id);
+ a11(conn.imgid);
+ a11(img.chan);
+ a11(0); /* what is this? */
+ a11(img.r.min.x);
+ a11(img.r.min.y);
+ a11(img.r.max.x);
+ a11(img.r.max.y);
+ a11(img.clipr.min.x);
+ a11(img.clipr.min.y);
+ a11(img.clipr.max.x);
+ return(a11(img.clipr.max.y));
+ }else{
+ return [];
+ }
+}
+
+Draw9p.writedrawctl = function(connid, offset, data){
+ cons.log("writedrawctl");
+ var conn = this.conns[connid];
+ if(conn == undefined){
+ throw("invalid draw connection");
+ }
+
+ conn.imgid = (new ArrayIterator(data)).getLong();
+}
--- /usr/web/9wd/js/draw Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/draw Mon Feb 17 21:45:00 2014
@@ -0,0 +1,505 @@
+Draw9p.writedrawdata = function(connid, offset, data){
+ var conn = this.conns[connid];
+ if(conn == undefined){
+ throw("invalid draw connection");
+ }
+
+ var length = data.length;
+ var ai = new ArrayIterator(data);
+ while(ai.hasRemainingBytes()){
+ var c = String.fromCharCode(ai.getChar());
+ cons.log("writedrawdata: " + c);
+ if(this.drawdatahandlers[c] == undefined){
+ throw("bad draw command");
+ }else{
+ this.drawdatahandlers[c](conn, offset, ai);
+ }
+ }
+ return length;
+}
+
+var drawcoord = function(ai, old){
+ var b, x;
+
+ b = ai.getChar();
+ x = b & 0x7F;
+ if(b & 0x80){
+ x |= ai.getChar() << 7;
+ x |= ai.getChar() << 15;
+ if(x & (1<<22)){
+ /* Not sure how ~0 would work in Javascript. */
+ x |= ((1<<31)|((1<<31)-1))<<23;
+ }
+ }else{
+ if(b & 0x40){
+ x |= ((1<<31)|((1<<31)-1))<<7;
+ }
+ x += old;
+ }
+ return x;
+}
+
+with(Draw9p){
+Draw9p.drawdatahandlers = {
+ "A": function(conn, offset, ai){
+ try{
+ var id = ai.getLong();
+ var imageid = ai.getLong();
+ var fillid = ai.getLong();
+ var public = ai.getChar();
+ }catch(e){
+ throw("short draw message");
+ }
+ if(conn.screens[id] != undefined){
+ throw("screen id in use");
+ }
+ var image = conn.imgs[imageid];
+ if(image == undefined){
+ throw("invalid image id");
+ }
+ var fill = conn.imgs[fillid];
+ if(fill == undefined){
+ throw("invalid image id");
+ }
+ conn.screens[id] = new Draw9p.Screen(id, image, fill, public);
+ },
+ "b": function(conn, offset, ai){
+ try{
+ var id = ai.getLong();
+ var screenid = ai.getLong();
+ var refresh = ai.getChar();
+ var chan = ai.getLong();
+ var repl = ai.getChar();
+ var r = ai.getRect();
+ var clipr = ai.getRect();
+ var color = ai.getLong();
+ }catch(e){
+ throw("short draw message");
+ }
+ if(conn.imgs[id] != undefined){
+ throw("image id in use");
+ }
+ if(screenid){
+ if(conn.screens[screenid] == undefined){
+ throw("invalid screen id");
+ }
+ conn.screens[screenid].imgs[id] = conn.imgs[id] =
+ new ScreenImage(conn.screens[screenid],
+ refresh, chan, repl, r, clipr, color);
+ }else{
+ conn.imgs[id] = new Image(refresh, chan, repl, r, clipr, color);
+ }
+ },
+ "c": function(conn, offset, ai){
+ try{
+ var dstid = ai.getLong();
+ var repl = ai.getChar();
+ var clipr = ai.getRect();
+ }catch(e){
+ throw("short draw message");
+ }
+ },
+ "d": function(conn, offset, ai){
+ try{
+ var dstid = ai.getLong();
+ var srcid = ai.getLong();
+ var maskid = ai.getLong();
+ var dstr = ai.getRect();
+ var srcp = ai.getPoint();
+ var maskp = ai.getPoint();
+ }catch(e){
+ throw("short draw message");
+ }
+ var dst = conn.imgs[dstid];
+ if(dst == undefined){
+ throw("invalid image id");
+ }
+ var src = conn.imgs[srcid];
+ if(src == undefined){
+ throw("invalid image id");
+ }
+ var mask = conn.imgs[maskid];
+ if(mask == undefined){
+ throw("invalid image id");
+ }
+ /* XXX should be drawmasked() */
+ /* XXX calling unintentionally global draw(). */
+ draw(dst, dstr, src, srcp, conn.op);
+ },
+ "D": function(conn, offset, ai){
+ try{
+ var debugon = ai.getChar();
+ }catch(e){
+ throw("short draw message");
+ }
+ },
+ "e": function(conn, offset, ai){
+ try{
+ var dstid = ai.getLong();
+ var srcid = ai.getLong();
+ var c = ai.getPoint();
+ var a = ai.getLong();
+ var b = ai.getLong();
+ var thick = ai.getLong();
+ var sp = ai.getPoint();
+ var alpha = ai.getLong();
+ var phi = ai.getLong();
+ }catch(e){
+ throw("short draw message");
+ }
+ },
+ "E": function(conn, offset, ai){
+ try{
+ var dstid = ai.getLong();
+ var srcid = ai.getLong();
+ var center = ai.getPoint();
+ var a = ai.getLong();
+ var b = ai.getLong();
+ var thick = ai.getLong();
+ var sp = ai.getPoint();
+ var alpha = ai.getLong();
+ var phi = ai.getLong();
+ }catch(e){
+ throw("short draw message");
+ }
+ var dst = conn.imgs[dstid];
+ if(dst == undefined){
+ throw("invalid destination image");
+ }
+ var src = conn.imgs[srcid];
+ if(src == undefined){
+ throw("invalid source image");
+ }
+ Memdraw.fillellipse(dst, center, a, b, alpha, phi, src,sp, conn.op);
+ },
+ "f": function(conn, offset, ai){
+ try{
+ var id = ai.getLong();
+ }catch(e){
+ throw("short draw message");
+ }
+ },
+ "F": function(conn, offset, ai){
+ try{
+ var id = ai.getLong();
+ }catch(e){
+ throw("short draw message");
+ }
+ },
+ "i": function(conn, offset, ai){
+ try{
+ var id = ai.getLong();
+ var n = ai.getLong();
+ var ascent = ai.getChar();
+ }catch(e){
+ throw("short draw message");
+ }
+ var img = conn.imgs[id];
+ if(img == undefined){
+ throw("invalid image id");
+ }
+ img.nchar = n;
+ img.ascent = ascent;
+ img.fchar = [];
+ /* document.body.appendChild(img.canvas); */
+ },
+ "l": function(conn, offset, ai){
+ try{
+ var cacheid = ai.getLong();
+ var srcid = ai.getLong();
+ var index = ai.getShort();
+ var r = ai.getRect();
+ var sp = ai.getPoint();
+ var left = ai.getChar();
+ var width = ai.getChar();
+ }catch(e){
+ throw("short draw message");
+ }
+ var cache = conn.imgs[cacheid];
+ if(cache == undefined){
+ throw("invalid image id");
+ }
+ var src = conn.imgs[srcid];
+ if(src == undefined){
+ throw("invalid image id");
+ }
+ cache.fchar[index] = {
+ r: r,
+ left: left,
+ width: width
+ };
+ /* XXX draw() is meant to be private to Memdraw! */
+ draw(cache, r, src, sp, Memdraw.Opdefs.SoverD.key);
+ },
+ "L": function(conn, offset, ai){
+ try{
+ var dstid = ai.getLong();
+ var p0 = ai.getPoint();
+ var p1 = ai.getPoint();
+ var end0 = ai.getLong();
+ var end1 = ai.getLong();
+ var thick = ai.getLong();
+ var srcid = ai.getLong();
+ var sp = ai.getPoint();
+ }catch(e){
+ throw("short draw message");
+ }
+ var dst = conn.imgs[dstid];
+ if(dst == undefined){
+ throw("invalid image id");
+ }
+ var src = conn.imgs[srcid];
+ if(src == undefined){
+ throw("invalid image id");
+ }
+ Memdraw.line(dst, p0, p1, end0, end1, thick,
+ src, sp, conn.op);
+ },
+ "N": function(conn, offset, ai){
+ try{
+ var id = ai.getLong();
+ var inp = ai.getChar();
+ var j = ai.getChar();
+ var name = String.fromUTF8Array(ai.getBytes(j));
+ }catch(e){
+ throw("short draw message");
+ }
+ if(inp){
+ if(conn.imgs[id] == undefined){
+ throw("invalid image id");
+ }
+ /* XXX Silently overwrites conflicting name. */
+ imgnames[name] = conn.imgs[id];
+ }else{
+ /* XXX Should check if this is the right image. */
+ delete imgnames[name];
+ }
+ },
+ "n": function(conn, offset, ai){
+ try{
+ var id = ai.getLong();
+ var j = ai.getChar();
+ var name = String.fromUTF8Array(ai.getBytes(j));
+ }catch(e){
+ throw("short draw message");
+ }
+ if(conn.imgs[id] != undefined){
+ throw("image id in use");
+ }
+ if(imgnames[name] == undefined){
+ throw("no image by name " + name);
+ }
+ conn.imgs[id] = imgnames[name];
+ },
+ "o": function(conn, offset, ai){
+ try{
+ var id = ai.getLong();
+ var rmin = ai.getPoint();
+ var scr = ai.getPoint();
+ }catch(e){
+ throw("short draw message");
+ }
+ },
+ "O": function(conn, offset, ai){
+ try{
+ var op = ai.getChar();
+ }catch(e){
+ throw("short draw message");
+ }
+ conn.op = op;
+ },
+ "p": function(conn, offset, ai){
+ try{
+ var dstid = ai.getLong();
+ var n = ai.getShort() + 1;
+ var end0 = ai.getLong();
+ var end1 = ai.getLong();
+ var thick = ai.getLong();
+ var srcid = ai.getLong();
+ var sp = ai.getPoint();
+ var dp = [];
+ var o = {x: 0, y: 0};
+ for(var i = 0; i < n; ++i){
+ dp[i] = {
+ x: drawcoord(ai, o.x),
+ y: drawcoord(ai, o.y)
+ }
+ o = dp[i];
+ }
+ }catch(e){
+ throw("short draw message");
+ }
+ var dst = conn.imgs[dstid];
+ if(dst == undefined){
+ throw("invalid image id");
+ }
+ var src = conn.imgs[srcid];
+ if(src == undefined){
+ throw("invalid image id");
+ }
+ Memdraw.poly(dst, dp, end0, end1, thick, src, sp, conn.op);
+ },
+ "P": function(conn, offset, ai){
+ try{
+ var dstid = ai.getLong();
+ var n = ai.getShort() + 1;
+ var wind = ai.getLong();
+ var ignore = ai.getPoint();
+ var srcid = ai.getLong();
+ var sp = ai.getPoint();
+ var dp = [];
+ var o = {x: 0, y: 0};
+ for(var i = 0; i < n; ++i){
+ var p = {
+ x: drawcoord(ai, o.x),
+ y: drawcoord(ai, o.y)
+ }
+ dp[i] = o = p;
+ }
+ }catch(e){
+ throw("short draw message");
+ }
+ var dst = conn.imgs[dstid];
+ if(dst == undefined){
+ throw("invalid image id");
+ }
+ var src = conn.imgs[srcid];
+ if(src == undefined){
+ throw("invalid image id");
+ }
+ Memdraw.fillpoly(dst, dp, wind, src, sp, conn.op);
+ },
+ "r": function(conn, offset, ai){
+ try{
+ var id = ai.getLong();
+ var r = ai.getRect();
+ }catch(e){
+ throw("short draw message");
+ }
+ },
+ "s": function(conn, offset, ai){
+ try{
+ var dstid = ai.getLong();
+ var srcid = ai.getLong();
+ var fontid = ai.getLong();
+ var p = ai.getPoint();
+ var clipr = ai.getRect();
+ var sp = ai.getPoint();
+ var n = ai.getShort();
+ var index = [];
+ for(var i = 0; i < n; ++i){
+ index[i] = ai.getShort();
+ }
+ }catch(e){
+ throw("short draw message");
+ }
+ var dst = conn.imgs[dstid];
+ if(dst == undefined){
+ throw("invalid image id");
+ }
+ var src = conn.imgs[srcid];
+ if(src == undefined){
+ throw("invalid image id");
+ }
+ var font = conn.imgs[fontid];
+ if(font == undefined){
+ throw("invalid image id");
+ }
+ if(font.fchar == undefined){
+ throw("not a font");
+ }
+ Memdraw.string(dst, src, font, p, clipr, sp, null, null, index, conn.op);
+ },
+ "x": function(conn, offset, ai){
+ try{
+ var dstid = ai.getLong();
+ var srcid = ai.getLong();
+ var fontid = ai.getLong();
+ var dp = ai.getPoint();
+ var clipr = ai.getRect();
+ var sp = ai.getPoint();
+ var n = ai.getShort();
+ var bgid = ai.getLong();
+ var bp = ai.getPoint();
+ var index = [];
+ for(var i = 0; i < n; ++i){
+ index[i] = ai.getShort();
+ }
+ }catch(e){
+ throw("short draw message");
+ }
+ var dst = conn.imgs[dstid];
+ if(dst == undefined){
+ throw("invalid image id");
+ }
+ var src = conn.imgs[srcid];
+ if(src == undefined){
+ throw("invalid image id");
+ }
+ var font = conn.imgs[fontid];
+ if(font == undefined){
+ throw("invalid image id");
+ }
+ if(font.fchar == undefined){
+ throw("not a font");
+ }
+ var bg = conn.imgs[bgid];
+ if(bg == undefined){
+ throw("invalid image id");
+ }
+ Memdraw.string(dst, src, font, dp, clipr, sp, bg, bp, index, conn.op);
+ },
+ "S": function(conn, offset, ai){
+ try{
+ var id = ai.getLong();
+ var chan = ai.getLong();
+ }catch(e){
+ throw("short draw message");
+ }
+ },
+ "t": function(conn, offset, ai){
+ try{
+ var top = ai.getChar();
+ var n = ai.getShort();
+ var ids = [];
+ for(var i = 0; i < n; ++i){
+ ids[i] = ai.getShort();
+ }
+ }catch(e){
+ throw("short draw message");
+ }
+ },
+ "v": function(conn, offset, ai){
+ },
+ "y": function(conn, offset, ai){
+ try{
+ var id = ai.getLong();
+ var r = ai.getRect();
+ var buf = ai.peekRemainingBytes();
+ }catch(e){
+ throw("short draw message");
+ }
+ var img = conn.imgs[id];
+ if(img == undefined){
+ throw("invalid image id");
+ }
+ var seek = Memdraw.load(img, r, buf, false);
+ ai.advanceBytes(seek);
+ },
+ "Y": function(conn, offset, ai){
+ try{
+ var id = ai.getLong();
+ var r = ai.getRect();
+ var buf = ai.peekRemainingBytes();
+ }catch(e){
+ throw("short draw message");
+ }
+ var img = conn.imgs[id];
+ if(img == undefined){
+ throw("invalid image id");
+ }
+ var seek = Memdraw.load(img, r, buf, true);
+ ai.advanceBytes(seek);
+ },
+}
+}
--- /usr/web/9wd/js/draw Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/draw Mon Feb 17 21:45:01 2014
@@ -0,0 +1,393 @@
+Draw9p = {};
+
+Draw9p.BPSHORT = function(p, v){
+ p[0] = (v) & 0xFF;
+ p[1] = (v >> 8) & 0xFF;
+ return p;
+}
+Draw9p.BPLONG = function(p, v){
+ p[0] = (v) & 0xFF;
+ p[1] = (v >> 8) & 0xFF;
+ p[2] = (v >>16) & 0xFF;
+ p[3] = (v >> 24) & 0xFF;
+ return p;
+}
+
+Draw9p.Qids = {
+ QROOT: 0,
+ QCONS: 1,
+ QCONSCTL: 2,
+ QMOUSE: 3,
+ QCURSOR: 4,
+ QWINNAME: 5,
+ QLABEL: 6,
+ QDRAW: 98,
+ QDRAWNEW: 99,
+ QDRAWBASE: 100,
+ QDRAWCTL: 1,
+ QDRAWDATA: 2,
+ QDRAWCOLORMAP: 3,
+ QDRAWREFRESH: 4,
+ QDRAWSTEP: 10
+}
+
+Draw9p.drawdir = function(path){
+ with(this.Qids){
+ return {
+ drawfile: (path - QDRAWBASE) % QDRAWSTEP,
+ drawdir: Math.floor((path - QDRAWBASE) / QDRAWSTEP)
+ }
+ }
+}
+
+
+Draw9p.conns = [];
+Draw9p.nextconn = 1;
+Draw9p.imgnames = {};
+
+Draw9p.Conn = function(connid){
+ this.id = connid;
+ this.imgs = [Draw9p.RootImage()];
+ this.screens = [];
+ this.imgid = 0;
+ this.op = Memdraw.Opdefs.SoverD.key;
+}
+
+Draw9p.connqids = function(){
+ var qids = [];
+ for(var i = 0; i < this.conns.length; ++i){
+ if(this.conns[i] != undefined){
+ qids.push(this.Qids.QDRAWBASE + (i * this.Qids.QDRAWSTEP));
+ }
+ }
+ return qids;
+}
+
+Draw9p.walk1 = function(qid, name){
+ with(this.Qids){
+ var path = qid.path;
+ if(path == QROOT){
+ if(name == ".."){
+ return new NineP.Qid(path, 0, NineP.QTDIR);
+ }else if(name == "cons"){
+ return new NineP.Qid(QCONS, 0, 0);
+ }else if(name == "consctl"){
+ return new NineP.Qid(QCONSCTL, 0, 0);
+ }else if(name == "mouse"){
+ return new NineP.Qid(QMOUSE, 0, 0);
+ }else if(name == "cursor"){
+ return new NineP.Qid(QCURSOR, 0, 0);
+ }else if(name == "winname"){
+ return new NineP.Qid(QWINNAME, 0, 0);
+ }else if(name == "label"){
+ return new NineP.Qid(QLABEL, 0, 0);
+ }else if(name == "draw"){
+ return new NineP.Qid(QDRAW, 0, NineP.QTDIR);
+ }else{
+ throw("file not found");
+ }
+ }else if(path == QDRAW){
+ if(name == ".."){
+ return new NineP.Qid(QROOT, 0, NineP.QTDIR);
+ }else if(name == "new"){
+ return new NineP.Qid(QDRAWNEW, 0, 0);
+ }else if(!/\D/.test(name)){
+ return new NineP.Qid(
+ QDRAWBASE + (
+ parseInt(name, 10) * QDRAWSTEP),
+ 0, NineP.QTDIR
+ );
+ }else{
+ throw("file not found");
+ }
+ }else if(path >= QDRAWBASE){
+ return this.walk1drawdir(path, name);
+ }else{
+ throw("file not found");
+ }
+ }
+}
+
+Draw9p.walk1drawdir = function(path, name){
+ with(this.Qids){
+ var dd = this.drawdir(path);
+
+ if(path < QDRAWBASE){
+ throw("could not walk");
+ }
+
+ if(this.conns[dd.drawdir] == undefined){
+ throw("file not found");
+ }
+
+ if(dd.drawfile == 0){
+ if(name == ".."){
+ return new NineP.Qid(QDRAW, 0, NineP.QTDIR);
+ }else if(name == "ctl"){
+ return new NineP.Qid(QDRAWCTL + path, 0, 0);
+ }else if(name == "data"){
+ return new NineP.Qid(QDRAWDATA + path, 0, 0);
+ }else if(name == "colormap"){
+ return new NineP.Qid(QDRAWCOLORMAP + path,
+ 0, 0);
+ }else if(name == "refresh"){
+ return new NineP.Qid(QDRAWREFRESH + path,
+ 0, 0);
+ }else{
+ throw("file not found");
+ }
+ }else{
+ throw("cannot walk from non-directory");
+ }
+ }
+}
+
+Draw9p.open = function(fid, mode){
+ with(this.Qids){
+ if(fid.qid.path == QDRAWNEW){
+ this.conns[this.nextconn] = new this.Conn(this.nextconn);
+ fid.drawconn = this.nextconn;
+ this.nextconn += 1;
+ }
+ }
+}
+
+Draw9p.create = function(name, perm, mode){
+ throw("creation not implemented");
+}
+
+Draw9p.read = function(fid, offset, count, callback){
+ with(this.Qids){
+ if(fid.qid.path == QDRAWNEW){
+ if(offset == 0){
+ try{
+ return callback.read(this.readdrawnew(fid.drawconn));
+ }catch(e){
+ return callback.error(e.toString());
+ }
+ }else{
+ return callback.read([]);
+ }
+ }else if(fid.qid.path >= QDRAWBASE){
+ var dd = this.drawdir(fid.qid.path);
+ if(dd.drawfile == QDRAWCTL){
+ try{
+ return callback.read(this.readdrawctl(fid, offset));
+ }catch(e){
+ return callback.error(e.toString());
+ }
+ }else if(dd.drawfile == QDRAWREFRESH){
+ return this.readdrawrefresh(dd, offset, callback);
+ }else{
+ return callback.read([]);
+ }
+ }else{
+ if(fid.qid.path == QCONS){
+ return cons.addcallback(callback);
+ }else if(fid.qid.path == QMOUSE){
+ return mouse.addcallback(callback);
+ }else if(fid.qid.path == QWINNAME){
+ if(offset == 0){
+ return callback.read("webdraw".toUTF8Array());
+ }else{
+ return callback.read([]);
+ }
+ }else if(fid.qid.path == QLABEL){
+ if(offset == 0){
+ return callback.read(this.label);
+ }else{
+ return callback.read([]);
+ }
+ }else{
+ return callback.read([]);
+ }
+ }
+ }
+}
+
+Draw9p.dirent = function(qid, offset){
+ with(this.Qids){
+ try{
+ if(qid.path == QROOT){
+ return this.stat([
+ QCONS,
+ QCONSCTL,
+ QMOUSE,
+ QCURSOR,
+ QWINNAME,
+ QLABEL,
+ QDRAW
+ ][offset]);
+ }else if(qid.path == QDRAW){
+ return this.stat([QDRAWNEW].concat(this.connqids())[offset]);
+ }else if(qid.path >= QDRAWBASE){
+ var dd = this.drawdir(qid.path);
+
+ if(dd.drawfile == 0){
+ return this.stat([
+ QDRAWCTL, QDRAWDATA,
+ QDRAWCOLORMAP, QDRAWREFRESH
+ ][offset] + (dd.drawdir * QDRAWSTEP) + QDRAWBASE);
+ }
+ }
+ return undefined;
+ }catch(e){
+ return undefined;
+ }
+ }
+}
+
+Draw9p.write = function(qid, offset, data){
+ with(this.Qids){
+ if(qid.path >= QDRAWBASE){
+ var dd = this.drawdir(qid.path);
+ if(dd.drawfile == QDRAWDATA){
+ return this.writedrawdata(dd.drawdir, offset, data);
+ }else if(dd.drawfile == QDRAWCTL){
+ return this.writedrawctl(dd.drawdir, offset, data);
+ }else{
+ throw("writing impermissible");
+ }
+ }else{
+ if(qid.path == QCONSCTL){
+ return;
+ }else if(qid.path == QCURSOR){
+ return mouse.cursor.write(data);
+ }else if(qid.path == QLABEL){
+ this.label = data;
+ return data.length;
+ }else{
+ throw("cannot write");
+ }
+ }
+ }
+}
+
+Draw9p.clunk = function(fid){
+ with(this.Qids){
+ if(fid.qid.path == QDRAWNEW){
+ delete this.conns[fid.drawconn];
+ }
+ }
+}
+
+Draw9p.remove = function(qid){
+ throw("cannot remove");
+}
+
+Draw9p.stat = function(qid){
+ with(this.Qids){
+ if(qid == QROOT){
+ return new NineP.Stat({
+ qid: new NineP.Qid(QROOT, 0, NineP.QTDIR),
+ mode: NineP.DMDIR|NineP.DMREAD|NineP.DMEXEC,
+ name: "/"
+ });
+ }else if(qid == QCONS){
+ return new NineP.Stat({
+ qid: new NineP.Qid(QCONS, 0, 0),
+ mode: 0,
+ name: "cons"
+ });
+ }else if(qid == QCONSCTL){
+ return new NineP.Stat({
+ qid: new NineP.Qid(QCONSCTL, 0, 0),
+ mode: 0,
+ name: "consctl"
+ });
+ }else if(qid == QMOUSE){
+ return new NineP.Stat({
+ qid: new NineP.Qid(QMOUSE, 0, 0),
+ mode: NineP.DMAPPEND,
+ length: 49,
+ name: "mouse"
+ });
+ }else if(qid == QCURSOR){
+ return new NineP.Stat({
+ qid: new NineP.Qid(QCURSOR, 0, 0),
+ mode: 0,
+ length: 72,
+ name: "cursor"
+ });
+ }else if(qid == QWINNAME){
+ return new NineP.Stat({
+ qid: new NineP.Qid(QWINNAME, 0, 0),
+ length: "webdraw".length,
+ name: "winname"
+ });
+ }else if(qid == QLABEL){
+ return new NineP.Stat({
+ qid: new NineP.Qid(QLABEL, 0, 0),
+ length: this.label.length,
+ name: "label"
+ });
+ }else if(qid == QDRAW){
+ return new NineP.Stat({
+ qid: new NineP.Qid(QDRAW, 0, NineP.QTDIR),
+ mode: NineP.DMDIR,
+ name: "draw"
+ });
+ }else if(qid == QDRAWNEW){
+ return new NineP.Stat({
+ qid: new NineP.Qid(QDRAWNEW, this.nextconn, 0),
+ mode: 0,
+ length: 144,
+ name: "new"
+ });
+ }else if(qid >= QDRAWBASE){
+ return this.statdrawdir(qid);
+ }else{
+ throw("invalid qid");
+ }
+ }
+}
+
+Draw9p.statdrawdir = function(qid){
+ with(this.Qids){
+ var dd = this.drawdir(qid);
+
+ if(qid < QDRAWBASE){
+ throw("could not stat");
+ }
+
+ if(this.conns[dd.drawdir] == undefined){
+ throw("file not found");
+ }
+
+ if(dd.drawfile == 0){
+ return new NineP.Stat({
+ qid: new NineP.Qid(qid, 0, NineP.QTDIR),
+ mode: 0,
+ name: String(dd.drawdir)
+ });
+ }
+
+ if(dd.drawfile == QDRAWCTL){
+ return new NineP.Stat({
+ qid: new NineP.Qid(qid, 0, 0),
+ mode: 0,
+ name: "ctl"
+ });
+ }else if(dd.drawfile == QDRAWDATA){
+ return new NineP.Stat({
+ qid: new NineP.Qid(qid, 0, 0),
+ mode: 0,
+ name: "data"
+ });
+ }else if(dd.drawfile == QDRAWCOLORMAP){
+ return new NineP.Stat({
+ qid: new NineP.Qid(qid, 0, 0),
+ mode: 0,
+ name: "colormap"
+ });
+ }else if(dd.drawfile == QDRAWREFRESH){
+ return new NineP.Stat({
+ qid: new NineP.Qid(qid, 0, 0),
+ mode: 0,
+ name: "refresh"
+ });
+ }else{
+ throw("could not stat");
+ }
+ }
+}
--- /usr/web/9wd/js/draw Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/draw Mon Feb 17 21:45:02 2014
@@ -0,0 +1,73 @@
+Draw9p.Image = function(refresh, chan, repl, r, clipr, color){
+ this.refresh = refresh;
+ this.chan = chan;
+ this.repl = repl;
+ this.r = r;
+ this.clipr = clipr;
+ this.canvas = document.createElement("canvas");
+ this.canvas.width = r.max.x - r.min.x;
+ this.canvas.height = r.max.y - r.min.y;
+ this.ctx = this.canvas.getContext("2d");
+
+ var red = (color >> 24) & 0xFF;
+ var green = (color >> 16) & 0xFF;
+ var blue = (color >> 8) & 0xFF;
+ var alpha = (color) & 0xFF;
+
+ var data = this.ctx.createImageData(this.canvas.width, this.canvas.height);
+ for(var i = 0; i < data.data.length; i += 4){
+ data.data[i + 0] = red;
+ data.data[i + 1] = green;
+ data.data[i + 2] = blue;
+ data.data[i + 3] = alpha;
+ }
+
+ this.ctx.putImageData(data, 0, 0);
+}
+
+/* XXX ScreenImage will not work as a drawing source */
+/* due to the assumption that the canvas maps 1-1 to the image data. */
+Draw9p.ScreenImage = function(screen, refresh, chan, repl, r, clipr, color){
+ if(screen == undefined || screen.backimg == undefined){
+ throw("invalid screen");
+ }
+ this.screen = screen;
+
+ /* if(chan != this.screen.backimg.chan){ */
+ /* throw("chan mismatch between image and screen"); */
+ /* } */
+
+ this.refresh = refresh;
+ this.chan = chan;
+ this.repl = repl;
+ this.r = r;
+ this.clipr = clipr;
+
+ /* XXX We should create a backing store, depending on refresh value. */
+ this.canvas = this.screen.backimg.canvas;
+ this.ctx = this.canvas.getContext("2d");
+ this.ctx.beginPath();
+ this.ctx.moveTo(r.min.x, r.min.y);
+ this.ctx.lineTo(r.max.x, r.min.y);
+ this.ctx.lineTo(r.max.x, r.max.y);
+ this.ctx.lineTo(r.min.x, r.max.y);
+ this.ctx.lineTo(r.min.x, r.min.y);
+ this.ctx.clip();
+
+ /* XXX Fill ScreenImage with background colour. */
+}
+
+/* XXX Creating a new rootwindow object for each connection will probably */
+/* break once we start doing more advanced things. */
+/* XXX These parameters should not be hardcoded. */
+Draw9p.RootImage = function(){
+ var image = new this.Image(0, "r8g8b8", 0,
+ {min: {x: 0, y: 0}, max: {x: 640, y: 480}},
+ {min: {x: 0, y: 0}, max: {x: 640, y: 480}},
+ 0xFFFFFFFF);
+
+ image.canvas = Draw9p.rootcanvas;
+ image.ctx = image.canvas.getContext("2d");
+
+ return image;
+}
--- /usr/web/9wd/js/draw Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/draw Mon Feb 17 21:45:03 2014
@@ -0,0 +1,211 @@
+var decompress = function(data, w, h, bpl, cdata){
+ var cdoff = 0; /* cdata offset */
+ var doff = 0; /* data offset */
+ var odoff = 0; /* offset data offset */
+ var c, cnt;
+ var offs, offlen;
+
+ for(;;){
+ if(doff >= data.length){
+ return cdoff;
+ }
+ if(cdoff >= cdata.length){
+ throw("buffer too small");
+ }
+
+ c = cdata[cdoff++];
+ if(c >= 128){
+ for(cnt = c-128+1; cnt > 0; --cnt){
+ data[doff++] = cdata[cdoff++];
+ }
+ }else{
+ offs = cdata[cdoff++] + ((c&3)<<8)+1;
+ odoff = doff - offs;
+ for(cnt = (c>>2) + 3; cnt > 0; --cnt){
+ data[doff++] = data[odoff++];
+ }
+ }
+ }
+}
+
+var bytesperline = function(w, depth){
+ var bytesperpix = Math.ceil(depth / 8);
+ var pixperbyte = Math.floor(8 / depth);
+
+ if(depth < 8){
+ return Math.ceil(w / pixperbyte);
+ }else{
+ return w * bytesperpix;
+ }
+}
+
+var getpixel = function(data, depth, w, h, line, col){
+ var bytesperpix = Math.ceil(depth / 8);
+ var pixperbyte = Math.floor(8 / depth);
+ var bytesperline = Math.ceil((w * pixperbyte) / 8);
+ var pixordinbyte = col % pixperbyte;
+
+ if(depth < 8){
+ bytesperline = Math.ceil(w / pixperbyte);
+ var offset = (line * bytesperline) + Math.floor((col * pixperbyte) / 8);
+ }else{
+ bytesperline = w * bytesperpix;
+ var offset = (line * bytesperline) + (col * bytesperpix);
+ }
+
+ if(line > h){
+ throw("pixel line index out of bounds");
+ }
+ if(col > w){
+ throw("pixel column index out of bounds");
+ }
+
+ if(depth < 8){
+ /* XXX remember to shift the whole mess back down! */
+ var mask = (1 << depth) - 1;
+ return (data[offset] >> (depth * pixordinbyte)) & mask;
+ }else{
+ /* XXX THIS IS WRONG! */
+ /* possibly: for(var i = bpp; i > 0; --i) */
+ var pixel = 0;
+ for(var i = 0; i < bytesperpix; ++i){
+ pixel |= data[offset + i] << (8 * i);
+ }
+ return pixel;
+ }
+}
+
+var canvaspos = function(w, h, line, col){
+ return ((line * w) + col) * 4;
+}
+
+var scalepixel = function(pixel, from, to){
+ if(from < to){
+ return Math.floor((pixel * ((1<> (from - to);
+ }
+}
+
+var loader = {
+ generic: function(arr, w, h, chan, data){
+ var depth = Chan.chantodepth(chan);
+
+ for(var line = 0; line < h; ++line){
+ for(var col = 0; col < w; ++col){
+ var pixel = getpixel(data, depth, w, h, line, col);
+ var cp = canvaspos(w, h, line, col);
+ arr[cp + 3] = 0xFF; /* Default to 100% alpha. */
+ for(var c = chan; c; c >>= 8){
+ var nbits = Chan.NBITS(c);
+ var px = pixel & ((1<>= nbits;
+ }
+ }
+ }
+ return arr;
+ },
+ grey: function(canvas, w, h, chan, data){
+ if((Chan.TYPE(chan) != Chan.chans.CGrey) || (chan >> 8)){
+ throw("not a grey-only image");
+ }
+ var depth = Chan.NBITS(chan);
+ var pixperbyte = Math.floor(8 / depth);
+ var bytesperline = Math.ceil(w / pixperbyte);
+
+ var cp = 0; /* canvas offset */
+ var dp = 0; /* data offset */
+
+ for(var line = 0; line < h; ++line, dp += bytesperline){
+ for(var b = 0; b < bytesperline; ++b){
+ for(var p = 1; p <= pixperbyte; ++p){
+ if((b * pixperbyte) + p > w) break;
+
+ var px = (data[dp + b] >> ((pixperbyte - p) * depth)) & ((1<> 16) & 0xFF;
+ canvas[cp++] = (px >> 8) & 0xFF;
+ canvas[cp++] = (px >> 0) & 0xFF;
+ canvas[cp++] = 0xFF;
+ }
+ }
+ }
+}
+
+Memdraw.Load = function(canvas, w, h, chan, data, iscompressed){
+ var depth = Chan.chantodepth(chan);
+ var bpl = Math.ceil((w * depth) / 8);
+ var len;
+
+ if(iscompressed){
+ var cdata = data;
+ data = new Uint8Array(h * bpl);
+ len = decompress(data, w, h, bpl, cdata);
+ }else{
+ len = bpl * h;
+ }
+
+ if(!(chan>>8)){
+ switch(Chan.TYPE(chan)){
+ case Chan.chans.CGrey:
+ loader.grey(canvas, w, h, chan, data);
+ return len;
+ case Chan.chans.CMap:
+ if(Chan.NBITS(chan) == 8){
+ loader.cmap8(canvas, w, h, chan, data);
+ return len;
+ }
+ }
+ }
+
+ loader.generic(canvas, w, h, chan, data);
+ return len;
+}
--- /usr/web/9wd/js/draw Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/draw Mon Feb 17 21:45:05 2014
@@ -0,0 +1,295 @@
+var icossin2 = function(dx, dy){
+ var theta = Math.atan2(dx, dy);
+ return {
+ cos: Math.cos(theta),
+ sin: Math.sin(theta),
+ theta: theta
+ }
+}
+
+/* XXX Seems to misbehave on non-(0,0) src.r.min. */
+var draw = function(dst, r, src, sp, op){
+ dst.ctx.save();
+ dst.ctx.globalCompositeOperation = Memdraw.Ops[op];
+
+ /* XXX what about clipping on src? */
+
+ dst.ctx.beginPath();
+ dst.ctx.rect(r.min.x-dst.r.min.x, r.min.y-dst.r.min.y,
+ r.max.x-r.min.x, r.max.y-r.min.y);
+ dst.ctx.clip();
+
+ if(src.repl){
+ dst.ctx.fillStyle = dst.ctx.createPattern(src.canvas, "repeat");
+ dst.ctx.fill();
+ }else{
+ dst.ctx.drawImage(src.canvas, r.min.x-sp.x+src.r.min.x-dst.r.min.x,
+ r.min.y-sp.y+src.r.min.y-dst.r.min.y);
+ }
+ dst.ctx.restore();
+ return;
+}
+
+var maskalpha = function(img){
+ var data = img.ctx.getImageData(0, 0, img.canvas.width, img.canvas.height);
+ for(var i = 0; i < data.data.length; i += 4){
+ data.data[i + 3] = (data.data[i + 0] + data.data[i + 1] + data.data[i + 2]) /3;
+ }
+ img.ctx.putImageData(data, 0, 0);
+}
+
+var drawmasked = function(dst, r, src, sp, mask, mp, op){
+ if(r.max.x == r.min.x || r.max.y == r.min.y){
+ return;
+ }
+
+ /* XXX Hack: draw() doesn't seem to handle offset images properly. */
+ var rdelta = {
+ min: {x: 0, y: 0},
+ max: {
+ x: r.max.x - r.min.x,
+ y: r.max.y - r.min.y
+ }
+ }
+ var img = new Draw9p.Image(0, Chan.fmts.CMAP8, 0, rdelta, rdelta, 0xFF00FFFF);
+
+ /* XXX Hack; we should have a way to create blank images. */
+ img.ctx.clearRect(0, 0, r.max.x, r.max.y);
+
+ draw(img, rdelta, mask, mp, Memdraw.Opdefs.SoverD.key);
+ maskalpha(img);
+ draw(img, rdelta, src, sp, Memdraw.Opdefs.SinD.key);
+ //document.body.appendChild(img.canvas);
+ draw(dst, r, img, {x: 0, y: 0}, op);
+}
+
+var load = function(dst, r, data, iscompressed){
+ var img = new Draw9p.Image(0, dst.chan, 0, r, r, 0);
+ var w = r.max.x - r.min.x;
+ var h = r.max.y - r.min.y;
+ var arr = img.ctx.createImageData(w, h);
+
+ var offset = Memdraw.Load(arr.data, w, h, img.chan, data, iscompressed);
+ img.ctx.putImageData(arr, 0, 0);
+ draw(dst, r, img, r.min, Memdraw.Opdefs.SoverD.key);
+ /* Append canvas for debugging. */
+ //document.body.appendChild(dst.canvas);
+ return offset;
+}
+
+var arrowend = function(tip, points, pp, end, sin, cos, radius){
+ var x1, x2, x3;
+
+ if(end == Memdraw.End.arrow){
+ x1 = 8;
+ x2 = 10;
+ x3 = 3;
+ }else{
+ x1 = (end >> 5) & 0x1FF;
+ x2 = (end >>14) & 0x1FF;
+ x3 = (end >> 23) & 0x1FF;
+ }
+
+ points[pp] = { /* upper side of shaft */
+ x: tip.x+((2*radius+1)*sin/2-x1*cos),
+ y: tip.y-((2*radius+1)*cos/2+x1*sin)
+ };
+ ++pp;
+ points[pp] = { /* upper barb */
+ x: tip.x+((2*radius+2*x3+1)*sin/2-x2*cos),
+ y: tip.y-((2*radius+2*x3+1)*cos/2+x2*sin)
+ };
+ ++pp;
+ points[pp] = {
+ x: tip.x,
+ y: tip.y
+ };
+ ++pp;
+ points[pp] = { /* lower barb */
+ x: tip.x+(-(2*radius+2*x3+1)*sin/2-x2*cos),
+ y: tip.y-(-(2*radius+2*x3+1)*cos/2+x2*sin)
+ };
+ ++pp;
+ points[pp] = { /* lower side of shaft */
+ x: tip.x+(-(2*radius+1)*sin/2-x1*cos),
+ y: tip.y+((2*radius+1)*cos/2-x1*sin)
+ };
+}
+
+var discend = function(p, radius, dst, src, dsrc, op){
+ Memdraw.fillellipse(dst, p, radius, radius, 0, 2 * Math.PI, src, dsrc, op);
+}
+
+var drawchar = function(dst, p, src, sp, bg, bp, font, fc, op){
+ var r = {
+ min: {
+ x: p.x + fc.left,
+ y: p.y - (font.ascent - fc.r.min.y)
+ },
+ max: {
+ x: (p.x + fc.left) + (fc.r.max.x - fc.r.min.x),
+ y: (p.y - (font.ascent - fc.r.min.y)) + (fc.r.max.y - fc.r.min.y)
+ }
+ }
+ var sp1 = {
+ x: sp.x + fc.left,
+ y: sp.y + fc.r.min.y
+ }
+
+ if(bg){
+ draw(dst, r, bg, bp, op);
+ }
+ drawmasked(dst, r, src, sp1, font, fc.r.min, op);
+ p.x += fc.width;
+ sp.x += fc.width;
+ return p;
+}
+
+Memdraw = {
+ line: function(dst, p0, p1, end0, end1, radius, src, sp, op){
+ var angle = icossin2(p1.y - p0.y, p1.x - p0.x);
+ var dx = (angle.sin * (2 * radius + 1))/2;
+ var dy = (angle.cos * (2 * radius + 1))/2;
+
+ var q = {
+ /* 1/2 is cargo-cult from /sys/src/libmemdraw/line.c ; why? */
+ x: p0.x + 1/2 + angle.cos/2,
+ y: p0.y + 1/2 + angle.sin/2
+ }
+
+ var points = [];
+ var pp = 0;
+
+ switch(end0 & 0x1F){
+ case Memdraw.End.disc:
+ discend(p0, radius, dst, src, sp, op);
+ /* fall through */
+ case Memdraw.End.square:
+ default:
+ points[pp] = {x: q.x-dx, y: q.y+dy};
+ ++pp;
+ points[pp] = {x: q.x+dx, y: q.y-dy};
+ ++pp;
+ break;
+ case Memdraw.End.arrow:
+ arrowend(q, points, pp, end0, -angle.sin, -angle.cos, radius);
+ this.fillpoly(dst, points.slice(0, 5), 0, src, sp, op);
+ points[pp+1] = points[pp+4];
+ pp += 2;
+ }
+ q = {
+ x: p1.x + 1/2 + angle.cos/2,
+ y: p1.y + 1/2 + angle.sin/2
+ }
+
+ switch(end1 & 0x1F){
+ case Memdraw.End.disc:
+ discend(p1, radius, dst, src, sp, op);
+ /* fall through */
+ case Memdraw.End.square:
+ default:
+ points[pp] = {x: q.x+dx, y: q.y-dy};
+ ++pp;
+ points[pp] = {x: q.x-dx, y: q.y+dy};
+ ++pp;
+ break;
+ case Memdraw.End.arrow:
+ arrowend(q, points, pp, end1, angle.sin, angle.cos, radius);
+ this.fillpoly(dst, points.slice(pp, pp+5), 0, src, sp, op);
+ points[pp+1] = points[pp+4];
+ pp += 2;
+ }
+ /* XXX setting w incorrectly! */
+ return this.fillpoly(dst, points.slice(0, pp), 0, src, sp, op);
+ },
+ /* XXX behaves incorrectly for incomplete (non 2pi) ellipses. */
+ fillellipse: function(dst, c, horiz, vert, alpha, phi, src, sp, op){
+ dst.ctx.save();
+ dst.ctx.save();
+ dst.ctx.beginPath();
+ dst.ctx.translate(c.x, c.y);
+ dst.ctx.scale(horiz, vert);
+ dst.ctx.arc(0, 0, 1, alpha, phi, false);
+ dst.ctx.restore();
+ dst.ctx.clip();
+ draw(dst, dst.clipr, src, sp, op);
+ dst.ctx.restore();
+ },
+ fillpoly: function(dst, vertices, w, src, sp, op){
+ if(vertices.length < 1){
+ return;
+ }
+ dst.ctx.save();
+ dst.ctx.beginPath();
+ dst.ctx.moveTo(vertices[0].x, vertices[0].y);
+ for(var i = 1; i < vertices.length; ++i){
+ dst.ctx.lineTo(vertices[i].x, vertices[i].y);
+ }
+ dst.ctx.clip();
+ /* XXX fill background here */
+ draw(dst, {min: {x: 0, y: 0}, max: {x: 500, y: 500}}, src, sp, op);
+ dst.ctx.restore();
+ return;
+ },
+ poly: function(dst, points, end0, end1, radius, src, sp, op){
+ if(points.length < 2){
+ return;
+ }
+ for(var i = 1; i < points.length; ++i){
+ /* XXX calculate ends here; see C source. */
+ /* XXX calculate change in sp; requires point operations. */
+ this.line(dst, points[i-1], points[i],
+ Memdraw.End.disc, Memdraw.End.disc,
+ radius, src, sp, op);
+ }
+ },
+ load: function(dst, r, data, iscompressed){
+ return load(dst, r, data, iscompressed);
+ },
+ string: function(dst, src, font, p, clipr, sp, bg, bp, index, op){
+ for(var i = 0; i < index.length; ++i){
+ if(index[i] == 0 || index[i] >= font.nchar){
+ throw("font cache index out of bounds");
+ }cons.log("char: " + index[i]);
+ drawchar(dst, p, src, sp, bg, bp, font, font.fchar[index[i]], op);
+ }
+ },
+ Opdefs: {
+ Clear: {key: 0, op: undefined},
+ SinD: {key: 8, op: "source-in"},
+ DinS: {key: 4, op: "destination-in"},
+ SoutD: {key: 2, op: "source-out"},
+ DoutS: {key: 1, op: "destination-out"},
+
+ S: {key: 10, op: "copy"}, /* SinD | SoutD */
+ SoverD: {key: 11, op: "source-over"}, /* SinD | SoutD | DoutS */
+ SatopD: {key: 9, op: "source-atop"}, /* SinD | DoutS */
+ SxorD: {key: 3, op: "xor"}, /* SoutD | DoutS */
+
+ D: {key: 5, op: undefined}, /* DinS | DoutS */
+ DoverS: {key: 7, op: "destination-over"}, /* DinS | DoutS | SoutD */
+ DatopS: {key: 6, op: "destination-atop"}, /* DinS | SoutD */
+ DxorS: {key: 3, op: "xor"}, /* DoutS | SoutD */
+
+ /* Ncomp: 12 */
+ }
+}
+
+Memdraw.Ops = (function(o){
+ var ops = [];
+ for(var k in o){
+ ops[o[k].key] = o[k].op;
+ }
+ return ops;
+})(Memdraw.Opdefs);
+
+Memdraw.End = {
+ square: 0,
+ disc: 1,
+ arrow: 2,
+ mask: 0x1F
+}
+
+Memdraw.ARROW = function(a, b, c){
+ return Memdraw.End.arrow | (a << 5) | (b << 14) | (c << 23);
+}
--- /usr/web/9wd/js/draw Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/draw Mon Feb 17 21:45:06 2014
@@ -0,0 +1,19 @@
+Draw9p.readdrawnew = function(conn){
+ cons.log("readdrawnew");
+ var buf = [];
+
+ buf = buf.concat(pad11(conn));
+ buf = buf.concat(pad11(0));
+ buf = buf.concat(pad11("r8g8b8"));
+ buf = buf.concat(pad11(0));
+ buf = buf.concat(pad11(0));
+ buf = buf.concat(pad11(0));
+ buf = buf.concat(pad11(640));
+ buf = buf.concat(pad11(480));
+ buf = buf.concat(pad11(0));
+ buf = buf.concat(pad11(0));
+ buf = buf.concat(pad11(640));
+ buf = buf.concat(pad11(480));
+
+ return buf;
+}
--- /usr/web/9wd/js/draw Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/draw Mon Feb 17 21:45:07 2014
@@ -0,0 +1,19 @@
+Draw9p.readdrawrefresh = function(dd, offset, callback){
+ cons.log("readdrawrefresh");
+
+ var conn = this.conns[dd.drawdir];
+ if(conn == undefined){
+ return callback.error("invalid draw connection");
+ }
+
+ /* XXX This breaks if multiple reads are outstanding on the */
+ /* refresh file! Should we permit this and use some sort of */
+ /* array of refreshcallbacks, or should we be setting (and */
+ /* obeying!) the exclusive-use bit on the relevant [qf]id? */
+ if(conn.refreshcallback != undefined){
+
+ return callback.error("multiple reads are not allowed");
+ }
+
+ conn.refreshcallback = callback;
+}
--- /usr/web/9wd/js/draw Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/draw Mon Feb 17 21:45:08 2014
@@ -0,0 +1,7 @@
+Draw9p.Screen = function(id, backimg, fillimg, public){
+ this.id = id;
+ this.public = public;
+ this.backimg = backimg;
+ /* Memdraw.draw(this.backimg, this.fillimg); */
+ this.imgs = []; /* not sure how this should be represented? */
+}
--- /usr/web/9wd/js/draw/chan.js Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/draw/chan.js Mon Feb 17 21:45:10 2014
@@ -0,0 +1,94 @@
+var dc = function(type, nbits){
+ return ((type & 0xF) << 4) | (nbits & 0xF);
+}
+var c1 = function(a,b){
+ return dc(a,b);
+}
+var c2 = function(a,b,c,d){
+ return c1(a,b)<<8 | dc(c,d);
+}
+var c3 = function(a,b,c,d,e,f){
+ return c2(a,b,c,d)<<8 | dc(e,f);
+}
+var c4 = function(a,b,c,d,e,f,g,h){
+ return c3(a,b,c,d,e,f)<<8 | dc(g,h);
+}
+
+Chan = {
+ NBITS: function(c){
+ return c & 0xF;
+ },
+ TYPE: function(c){
+ return (c >> 4) & 0xF;
+ },
+ channames: ['r', 'g', 'b', 'k', 'a', 'm', 'x'],
+ chans: {
+ CRed: 0,
+ CGreen: 1,
+ CBlue: 2,
+ CGrey: 3,
+ CAlpha: 4,
+ CMap: 5,
+ CIgnore: 6,
+ NChan: 7
+ },
+ chantostr: function(cc){
+ var buf = [];
+ var c, rc;
+
+ if(chantodepth(cc) == 0){
+ return undefined;
+ }
+
+ rc = 0;
+ for(c = cc; c; c >>= 8){
+ rc <<= 8;
+ rc |= c & 0xFF;
+ }
+
+ for(c = rc; c; c >>= 8){
+ buf.push(this.channames[this.TYPE(c)]);
+ buf.push(this.NBITS(c));
+ }
+
+ return buf.join("");
+ },
+ strtochan: function(s){
+ throw("strtochan not implemented");
+ },
+ chantodepth: function(c){
+ var n;
+
+ for(n = 0; c; c >>= 8){
+ if(this.TYPE(c) >= this.chans.NChan ||
+ this.NBITS(c) > 8 ||
+ this.NBITS(c) <= 0){
+ return 0;
+ }
+ n += this.NBITS(c);
+ }
+ if(n == 0 || (n > 8 && n % 8) || (n < 8 && 8 % n)){
+ return 0;
+ }
+ return n;
+ }
+}
+
+Chan.fmts = (function(c){
+ with(c){ return {
+ GREY1: c1(CGrey, 1),
+ GREY2: c1(CGrey, 2),
+ GREY4: c1(CGrey, 4),
+ GREY8: c1(CGrey, 8),
+ CMAP8: c1(CMap, 8),
+ RGB15: c4(CIgnore, 1, CRed, 5, CGreen, 5, CBlue, 5),
+ RGB16: c3(CRed, 5, CGreen, 6, CBlue, 5),
+ RGB24: c3(CRed, 8, CGreen, 8, CBlue, 8),
+ RGBA32: c4(CRed, 8, CGreen, 8, CBlue, 8, CAlpha, 8),
+ ARGB32: c4(CAlpha, 8, CRed, 8, CGreen, 8, CBlue, 8), /* stupid VGAs */
+ XRGB32: c4(CIgnore, 8, CRed, 8, CGreen, 8, CBlue, 8),
+ BGR24: c3(CBlue, 8, CGreen, 8, CRed, 8),
+ ABGR32: c4(CAlpha, 8, CBlue, 8, CGreen, 8, CRed, 8),
+ XBGR32: c4(CIgnore, 8, CBlue, 8, CGreen, 8, CRed, 8),
+ }}
+}(Chan.chans))
--- /usr/web/9wd/js/draw/cmap.js Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/draw/cmap.js Mon Feb 17 21:45:11 2014
@@ -0,0 +1,54 @@
+/* I don't understand what's going on here. */
+/* Cribbed from /sys/src/libdraw/rgb.c */
+
+Cmap = {
+ rbg2cmap: function(red, green, blue){
+ var i, r, g, b, sq;
+ var rgb;
+ var best, bestq;
+
+ best = 0;
+ bestsq = 0x7FFFFFFF;
+ for(i = 0; i < 256; i++){
+ rgb = cmap2rgb(i);
+ r = (rgb >> 16) & 0xFF;
+ g = (rgb >> 8) & 0xFF;
+ b = (rgb >> 0) & 0xFF;
+ sq = (r-cr)*(r-cr)+(g-cg)*(g-cg)+(b-cb)*(b-cb);
+ if(sq < bestq){
+ bestq = sq;
+ best = i;
+ }
+ }
+ return best;
+ },
+ cmap2rgb: function(c){
+ var j, num, den, r, g, b, v, rgb;
+
+ r = c >> 6;
+ v = (c >> 4) & 3;
+ j = (c - v + r) & 15;
+ g = j >> 2;
+ b = j & 3;
+ den = r;
+ if(g > den){
+ den = g;
+ }
+ if(b > den){
+ den = b;
+ }
+ if(den == 0){
+ v *= 17;
+ rgb = (v << 16) | (v << 8) | v;
+ }else{
+ num = 17 * (4 * den + v);
+ rgb = (Math.floor(r * num / den) << 16) |
+ (Math.floor(g * num / den) << 8) |
+ Math.floor(b * num / den);
+ }
+ return rgb;
+ },
+ cmap2rgba: function(c){
+ return (this.cmap2rgb(c) << 8) | 0xFF;
+ }
+}
--- /usr/web/9wd/js/draw/ctl.js Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/draw/ctl.js Mon Feb 17 21:45:13 2014
@@ -0,0 +1,48 @@
+var mka11 = function(padder){
+ var buffer = [];
+ return function(data){
+ return buffer = buffer.concat(padder(data));
+ }
+}
+
+Draw9p.readdrawctl = function(fid, offset){
+ cons.log("readdrawctl");
+ var dd = this.drawdir(fid.qid.path);
+
+ var conn = this.conns[dd.drawdir];
+ if(conn == undefined){
+ throw("invalid draw connection");
+ }
+ var img = conn.imgs[conn.imgid];
+ if(img == undefined){
+ throw("invalid image");
+ }
+
+ if(offset == 0){
+ var a11 = mka11(pad11);
+ a11(conn.id);
+ a11(conn.imgid);
+ a11(img.chan);
+ a11(0); /* what is this? */
+ a11(img.r.min.x);
+ a11(img.r.min.y);
+ a11(img.r.max.x);
+ a11(img.r.max.y);
+ a11(img.clipr.min.x);
+ a11(img.clipr.min.y);
+ a11(img.clipr.max.x);
+ return(a11(img.clipr.max.y));
+ }else{
+ return [];
+ }
+}
+
+Draw9p.writedrawctl = function(connid, offset, data){
+ cons.log("writedrawctl");
+ var conn = this.conns[connid];
+ if(conn == undefined){
+ throw("invalid draw connection");
+ }
+
+ conn.imgid = (new ArrayIterator(data)).getLong();
+}
--- /usr/web/9wd/js/draw/data.js Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/draw/data.js Mon Feb 17 21:45:15 2014
@@ -0,0 +1,505 @@
+Draw9p.writedrawdata = function(connid, offset, data){
+ var conn = this.conns[connid];
+ if(conn == undefined){
+ throw("invalid draw connection");
+ }
+
+ var length = data.length;
+ var ai = new ArrayIterator(data);
+ while(ai.hasRemainingBytes()){
+ var c = String.fromCharCode(ai.getChar());
+ cons.log("writedrawdata: " + c);
+ if(this.drawdatahandlers[c] == undefined){
+ throw("bad draw command");
+ }else{
+ this.drawdatahandlers[c](conn, offset, ai);
+ }
+ }
+ return length;
+}
+
+var drawcoord = function(ai, old){
+ var b, x;
+
+ b = ai.getChar();
+ x = b & 0x7F;
+ if(b & 0x80){
+ x |= ai.getChar() << 7;
+ x |= ai.getChar() << 15;
+ if(x & (1<<22)){
+ /* Not sure how ~0 would work in Javascript. */
+ x |= ((1<<31)|((1<<31)-1))<<23;
+ }
+ }else{
+ if(b & 0x40){
+ x |= ((1<<31)|((1<<31)-1))<<7;
+ }
+ x += old;
+ }
+ return x;
+}
+
+with(Draw9p){
+Draw9p.drawdatahandlers = {
+ "A": function(conn, offset, ai){
+ try{
+ var id = ai.getLong();
+ var imageid = ai.getLong();
+ var fillid = ai.getLong();
+ var public = ai.getChar();
+ }catch(e){
+ throw("short draw message");
+ }
+ if(conn.screens[id] != undefined){
+ throw("screen id in use");
+ }
+ var image = conn.imgs[imageid];
+ if(image == undefined){
+ throw("invalid image id");
+ }
+ var fill = conn.imgs[fillid];
+ if(fill == undefined){
+ throw("invalid image id");
+ }
+ conn.screens[id] = new Draw9p.Screen(id, image, fill, public);
+ },
+ "b": function(conn, offset, ai){
+ try{
+ var id = ai.getLong();
+ var screenid = ai.getLong();
+ var refresh = ai.getChar();
+ var chan = ai.getLong();
+ var repl = ai.getChar();
+ var r = ai.getRect();
+ var clipr = ai.getRect();
+ var color = ai.getLong();
+ }catch(e){
+ throw("short draw message");
+ }
+ if(conn.imgs[id] != undefined){
+ throw("image id in use");
+ }
+ if(screenid){
+ if(conn.screens[screenid] == undefined){
+ throw("invalid screen id");
+ }
+ conn.screens[screenid].imgs[id] = conn.imgs[id] =
+ new ScreenImage(conn.screens[screenid],
+ refresh, chan, repl, r, clipr, color);
+ }else{
+ conn.imgs[id] = new Image(refresh, chan, repl, r, clipr, color);
+ }
+ },
+ "c": function(conn, offset, ai){
+ try{
+ var dstid = ai.getLong();
+ var repl = ai.getChar();
+ var clipr = ai.getRect();
+ }catch(e){
+ throw("short draw message");
+ }
+ },
+ "d": function(conn, offset, ai){
+ try{
+ var dstid = ai.getLong();
+ var srcid = ai.getLong();
+ var maskid = ai.getLong();
+ var dstr = ai.getRect();
+ var srcp = ai.getPoint();
+ var maskp = ai.getPoint();
+ }catch(e){
+ throw("short draw message");
+ }
+ var dst = conn.imgs[dstid];
+ if(dst == undefined){
+ throw("invalid image id");
+ }
+ var src = conn.imgs[srcid];
+ if(src == undefined){
+ throw("invalid image id");
+ }
+ var mask = conn.imgs[maskid];
+ if(mask == undefined){
+ throw("invalid image id");
+ }
+ /* XXX should be drawmasked() */
+ /* XXX calling unintentionally global draw(). */
+ draw(dst, dstr, src, srcp, conn.op);
+ },
+ "D": function(conn, offset, ai){
+ try{
+ var debugon = ai.getChar();
+ }catch(e){
+ throw("short draw message");
+ }
+ },
+ "e": function(conn, offset, ai){
+ try{
+ var dstid = ai.getLong();
+ var srcid = ai.getLong();
+ var c = ai.getPoint();
+ var a = ai.getLong();
+ var b = ai.getLong();
+ var thick = ai.getLong();
+ var sp = ai.getPoint();
+ var alpha = ai.getLong();
+ var phi = ai.getLong();
+ }catch(e){
+ throw("short draw message");
+ }
+ },
+ "E": function(conn, offset, ai){
+ try{
+ var dstid = ai.getLong();
+ var srcid = ai.getLong();
+ var center = ai.getPoint();
+ var a = ai.getLong();
+ var b = ai.getLong();
+ var thick = ai.getLong();
+ var sp = ai.getPoint();
+ var alpha = ai.getLong();
+ var phi = ai.getLong();
+ }catch(e){
+ throw("short draw message");
+ }
+ var dst = conn.imgs[dstid];
+ if(dst == undefined){
+ throw("invalid destination image");
+ }
+ var src = conn.imgs[srcid];
+ if(src == undefined){
+ throw("invalid source image");
+ }
+ Memdraw.fillellipse(dst, center, a, b, alpha, phi, src,sp, conn.op);
+ },
+ "f": function(conn, offset, ai){
+ try{
+ var id = ai.getLong();
+ }catch(e){
+ throw("short draw message");
+ }
+ },
+ "F": function(conn, offset, ai){
+ try{
+ var id = ai.getLong();
+ }catch(e){
+ throw("short draw message");
+ }
+ },
+ "i": function(conn, offset, ai){
+ try{
+ var id = ai.getLong();
+ var n = ai.getLong();
+ var ascent = ai.getChar();
+ }catch(e){
+ throw("short draw message");
+ }
+ var img = conn.imgs[id];
+ if(img == undefined){
+ throw("invalid image id");
+ }
+ img.nchar = n;
+ img.ascent = ascent;
+ img.fchar = [];
+ /* document.body.appendChild(img.canvas); */
+ },
+ "l": function(conn, offset, ai){
+ try{
+ var cacheid = ai.getLong();
+ var srcid = ai.getLong();
+ var index = ai.getShort();
+ var r = ai.getRect();
+ var sp = ai.getPoint();
+ var left = ai.getChar();
+ var width = ai.getChar();
+ }catch(e){
+ throw("short draw message");
+ }
+ var cache = conn.imgs[cacheid];
+ if(cache == undefined){
+ throw("invalid image id");
+ }
+ var src = conn.imgs[srcid];
+ if(src == undefined){
+ throw("invalid image id");
+ }
+ cache.fchar[index] = {
+ r: r,
+ left: left,
+ width: width
+ };
+ /* XXX draw() is meant to be private to Memdraw! */
+ draw(cache, r, src, sp, Memdraw.Opdefs.SoverD.key);
+ },
+ "L": function(conn, offset, ai){
+ try{
+ var dstid = ai.getLong();
+ var p0 = ai.getPoint();
+ var p1 = ai.getPoint();
+ var end0 = ai.getLong();
+ var end1 = ai.getLong();
+ var thick = ai.getLong();
+ var srcid = ai.getLong();
+ var sp = ai.getPoint();
+ }catch(e){
+ throw("short draw message");
+ }
+ var dst = conn.imgs[dstid];
+ if(dst == undefined){
+ throw("invalid image id");
+ }
+ var src = conn.imgs[srcid];
+ if(src == undefined){
+ throw("invalid image id");
+ }
+ Memdraw.line(dst, p0, p1, end0, end1, thick,
+ src, sp, conn.op);
+ },
+ "N": function(conn, offset, ai){
+ try{
+ var id = ai.getLong();
+ var inp = ai.getChar();
+ var j = ai.getChar();
+ var name = String.fromUTF8Array(ai.getBytes(j));
+ }catch(e){
+ throw("short draw message");
+ }
+ if(inp){
+ if(conn.imgs[id] == undefined){
+ throw("invalid image id");
+ }
+ /* XXX Silently overwrites conflicting name. */
+ imgnames[name] = conn.imgs[id];
+ }else{
+ /* XXX Should check if this is the right image. */
+ delete imgnames[name];
+ }
+ },
+ "n": function(conn, offset, ai){
+ try{
+ var id = ai.getLong();
+ var j = ai.getChar();
+ var name = String.fromUTF8Array(ai.getBytes(j));
+ }catch(e){
+ throw("short draw message");
+ }
+ if(conn.imgs[id] != undefined){
+ throw("image id in use");
+ }
+ if(imgnames[name] == undefined){
+ throw("no image by name " + name);
+ }
+ conn.imgs[id] = imgnames[name];
+ },
+ "o": function(conn, offset, ai){
+ try{
+ var id = ai.getLong();
+ var rmin = ai.getPoint();
+ var scr = ai.getPoint();
+ }catch(e){
+ throw("short draw message");
+ }
+ },
+ "O": function(conn, offset, ai){
+ try{
+ var op = ai.getChar();
+ }catch(e){
+ throw("short draw message");
+ }
+ conn.op = op;
+ },
+ "p": function(conn, offset, ai){
+ try{
+ var dstid = ai.getLong();
+ var n = ai.getShort() + 1;
+ var end0 = ai.getLong();
+ var end1 = ai.getLong();
+ var thick = ai.getLong();
+ var srcid = ai.getLong();
+ var sp = ai.getPoint();
+ var dp = [];
+ var o = {x: 0, y: 0};
+ for(var i = 0; i < n; ++i){
+ dp[i] = {
+ x: drawcoord(ai, o.x),
+ y: drawcoord(ai, o.y)
+ }
+ o = dp[i];
+ }
+ }catch(e){
+ throw("short draw message");
+ }
+ var dst = conn.imgs[dstid];
+ if(dst == undefined){
+ throw("invalid image id");
+ }
+ var src = conn.imgs[srcid];
+ if(src == undefined){
+ throw("invalid image id");
+ }
+ Memdraw.poly(dst, dp, end0, end1, thick, src, sp, conn.op);
+ },
+ "P": function(conn, offset, ai){
+ try{
+ var dstid = ai.getLong();
+ var n = ai.getShort() + 1;
+ var wind = ai.getLong();
+ var ignore = ai.getPoint();
+ var srcid = ai.getLong();
+ var sp = ai.getPoint();
+ var dp = [];
+ var o = {x: 0, y: 0};
+ for(var i = 0; i < n; ++i){
+ var p = {
+ x: drawcoord(ai, o.x),
+ y: drawcoord(ai, o.y)
+ }
+ dp[i] = o = p;
+ }
+ }catch(e){
+ throw("short draw message");
+ }
+ var dst = conn.imgs[dstid];
+ if(dst == undefined){
+ throw("invalid image id");
+ }
+ var src = conn.imgs[srcid];
+ if(src == undefined){
+ throw("invalid image id");
+ }
+ Memdraw.fillpoly(dst, dp, wind, src, sp, conn.op);
+ },
+ "r": function(conn, offset, ai){
+ try{
+ var id = ai.getLong();
+ var r = ai.getRect();
+ }catch(e){
+ throw("short draw message");
+ }
+ },
+ "s": function(conn, offset, ai){
+ try{
+ var dstid = ai.getLong();
+ var srcid = ai.getLong();
+ var fontid = ai.getLong();
+ var p = ai.getPoint();
+ var clipr = ai.getRect();
+ var sp = ai.getPoint();
+ var n = ai.getShort();
+ var index = [];
+ for(var i = 0; i < n; ++i){
+ index[i] = ai.getShort();
+ }
+ }catch(e){
+ throw("short draw message");
+ }
+ var dst = conn.imgs[dstid];
+ if(dst == undefined){
+ throw("invalid image id");
+ }
+ var src = conn.imgs[srcid];
+ if(src == undefined){
+ throw("invalid image id");
+ }
+ var font = conn.imgs[fontid];
+ if(font == undefined){
+ throw("invalid image id");
+ }
+ if(font.fchar == undefined){
+ throw("not a font");
+ }
+ Memdraw.string(dst, src, font, p, clipr, sp, null, null, index, conn.op);
+ },
+ "x": function(conn, offset, ai){
+ try{
+ var dstid = ai.getLong();
+ var srcid = ai.getLong();
+ var fontid = ai.getLong();
+ var dp = ai.getPoint();
+ var clipr = ai.getRect();
+ var sp = ai.getPoint();
+ var n = ai.getShort();
+ var bgid = ai.getLong();
+ var bp = ai.getPoint();
+ var index = [];
+ for(var i = 0; i < n; ++i){
+ index[i] = ai.getShort();
+ }
+ }catch(e){
+ throw("short draw message");
+ }
+ var dst = conn.imgs[dstid];
+ if(dst == undefined){
+ throw("invalid image id");
+ }
+ var src = conn.imgs[srcid];
+ if(src == undefined){
+ throw("invalid image id");
+ }
+ var font = conn.imgs[fontid];
+ if(font == undefined){
+ throw("invalid image id");
+ }
+ if(font.fchar == undefined){
+ throw("not a font");
+ }
+ var bg = conn.imgs[bgid];
+ if(bg == undefined){
+ throw("invalid image id");
+ }
+ Memdraw.string(dst, src, font, dp, clipr, sp, bg, bp, index, conn.op);
+ },
+ "S": function(conn, offset, ai){
+ try{
+ var id = ai.getLong();
+ var chan = ai.getLong();
+ }catch(e){
+ throw("short draw message");
+ }
+ },
+ "t": function(conn, offset, ai){
+ try{
+ var top = ai.getChar();
+ var n = ai.getShort();
+ var ids = [];
+ for(var i = 0; i < n; ++i){
+ ids[i] = ai.getShort();
+ }
+ }catch(e){
+ throw("short draw message");
+ }
+ },
+ "v": function(conn, offset, ai){
+ },
+ "y": function(conn, offset, ai){
+ try{
+ var id = ai.getLong();
+ var r = ai.getRect();
+ var buf = ai.peekRemainingBytes();
+ }catch(e){
+ throw("short draw message");
+ }
+ var img = conn.imgs[id];
+ if(img == undefined){
+ throw("invalid image id");
+ }
+ var seek = Memdraw.load(img, r, buf, false);
+ ai.advanceBytes(seek);
+ },
+ "Y": function(conn, offset, ai){
+ try{
+ var id = ai.getLong();
+ var r = ai.getRect();
+ var buf = ai.peekRemainingBytes();
+ }catch(e){
+ throw("short draw message");
+ }
+ var img = conn.imgs[id];
+ if(img == undefined){
+ throw("invalid image id");
+ }
+ var seek = Memdraw.load(img, r, buf, true);
+ ai.advanceBytes(seek);
+ },
+}
+}
--- /usr/web/9wd/js/draw/draw9p.js Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/draw/draw9p.js Mon Feb 17 21:45:16 2014
@@ -0,0 +1,393 @@
+Draw9p = {};
+
+Draw9p.BPSHORT = function(p, v){
+ p[0] = (v) & 0xFF;
+ p[1] = (v >> 8) & 0xFF;
+ return p;
+}
+Draw9p.BPLONG = function(p, v){
+ p[0] = (v) & 0xFF;
+ p[1] = (v >> 8) & 0xFF;
+ p[2] = (v >>16) & 0xFF;
+ p[3] = (v >> 24) & 0xFF;
+ return p;
+}
+
+Draw9p.Qids = {
+ QROOT: 0,
+ QCONS: 1,
+ QCONSCTL: 2,
+ QMOUSE: 3,
+ QCURSOR: 4,
+ QWINNAME: 5,
+ QLABEL: 6,
+ QDRAW: 98,
+ QDRAWNEW: 99,
+ QDRAWBASE: 100,
+ QDRAWCTL: 1,
+ QDRAWDATA: 2,
+ QDRAWCOLORMAP: 3,
+ QDRAWREFRESH: 4,
+ QDRAWSTEP: 10
+}
+
+Draw9p.drawdir = function(path){
+ with(this.Qids){
+ return {
+ drawfile: (path - QDRAWBASE) % QDRAWSTEP,
+ drawdir: Math.floor((path - QDRAWBASE) / QDRAWSTEP)
+ }
+ }
+}
+
+
+Draw9p.conns = [];
+Draw9p.nextconn = 1;
+Draw9p.imgnames = {};
+
+Draw9p.Conn = function(connid){
+ this.id = connid;
+ this.imgs = [Draw9p.RootImage()];
+ this.screens = [];
+ this.imgid = 0;
+ this.op = Memdraw.Opdefs.SoverD.key;
+}
+
+Draw9p.connqids = function(){
+ var qids = [];
+ for(var i = 0; i < this.conns.length; ++i){
+ if(this.conns[i] != undefined){
+ qids.push(this.Qids.QDRAWBASE + (i * this.Qids.QDRAWSTEP));
+ }
+ }
+ return qids;
+}
+
+Draw9p.walk1 = function(qid, name){
+ with(this.Qids){
+ var path = qid.path;
+ if(path == QROOT){
+ if(name == ".."){
+ return new NineP.Qid(path, 0, NineP.QTDIR);
+ }else if(name == "cons"){
+ return new NineP.Qid(QCONS, 0, 0);
+ }else if(name == "consctl"){
+ return new NineP.Qid(QCONSCTL, 0, 0);
+ }else if(name == "mouse"){
+ return new NineP.Qid(QMOUSE, 0, 0);
+ }else if(name == "cursor"){
+ return new NineP.Qid(QCURSOR, 0, 0);
+ }else if(name == "winname"){
+ return new NineP.Qid(QWINNAME, 0, 0);
+ }else if(name == "label"){
+ return new NineP.Qid(QLABEL, 0, 0);
+ }else if(name == "draw"){
+ return new NineP.Qid(QDRAW, 0, NineP.QTDIR);
+ }else{
+ throw("file not found");
+ }
+ }else if(path == QDRAW){
+ if(name == ".."){
+ return new NineP.Qid(QROOT, 0, NineP.QTDIR);
+ }else if(name == "new"){
+ return new NineP.Qid(QDRAWNEW, 0, 0);
+ }else if(!/\D/.test(name)){
+ return new NineP.Qid(
+ QDRAWBASE + (
+ parseInt(name, 10) * QDRAWSTEP),
+ 0, NineP.QTDIR
+ );
+ }else{
+ throw("file not found");
+ }
+ }else if(path >= QDRAWBASE){
+ return this.walk1drawdir(path, name);
+ }else{
+ throw("file not found");
+ }
+ }
+}
+
+Draw9p.walk1drawdir = function(path, name){
+ with(this.Qids){
+ var dd = this.drawdir(path);
+
+ if(path < QDRAWBASE){
+ throw("could not walk");
+ }
+
+ if(this.conns[dd.drawdir] == undefined){
+ throw("file not found");
+ }
+
+ if(dd.drawfile == 0){
+ if(name == ".."){
+ return new NineP.Qid(QDRAW, 0, NineP.QTDIR);
+ }else if(name == "ctl"){
+ return new NineP.Qid(QDRAWCTL + path, 0, 0);
+ }else if(name == "data"){
+ return new NineP.Qid(QDRAWDATA + path, 0, 0);
+ }else if(name == "colormap"){
+ return new NineP.Qid(QDRAWCOLORMAP + path,
+ 0, 0);
+ }else if(name == "refresh"){
+ return new NineP.Qid(QDRAWREFRESH + path,
+ 0, 0);
+ }else{
+ throw("file not found");
+ }
+ }else{
+ throw("cannot walk from non-directory");
+ }
+ }
+}
+
+Draw9p.open = function(fid, mode){
+ with(this.Qids){
+ if(fid.qid.path == QDRAWNEW){
+ this.conns[this.nextconn] = new this.Conn(this.nextconn);
+ fid.drawconn = this.nextconn;
+ this.nextconn += 1;
+ }
+ }
+}
+
+Draw9p.create = function(name, perm, mode){
+ throw("creation not implemented");
+}
+
+Draw9p.read = function(fid, offset, count, callback){
+ with(this.Qids){
+ if(fid.qid.path == QDRAWNEW){
+ if(offset == 0){
+ try{
+ return callback.read(this.readdrawnew(fid.drawconn));
+ }catch(e){
+ return callback.error(e.toString());
+ }
+ }else{
+ return callback.read([]);
+ }
+ }else if(fid.qid.path >= QDRAWBASE){
+ var dd = this.drawdir(fid.qid.path);
+ if(dd.drawfile == QDRAWCTL){
+ try{
+ return callback.read(this.readdrawctl(fid, offset));
+ }catch(e){
+ return callback.error(e.toString());
+ }
+ }else if(dd.drawfile == QDRAWREFRESH){
+ return this.readdrawrefresh(dd, offset, callback);
+ }else{
+ return callback.read([]);
+ }
+ }else{
+ if(fid.qid.path == QCONS){
+ return cons.addcallback(callback);
+ }else if(fid.qid.path == QMOUSE){
+ return mouse.addcallback(callback);
+ }else if(fid.qid.path == QWINNAME){
+ if(offset == 0){
+ return callback.read("webdraw".toUTF8Array());
+ }else{
+ return callback.read([]);
+ }
+ }else if(fid.qid.path == QLABEL){
+ if(offset == 0){
+ return callback.read(this.label);
+ }else{
+ return callback.read([]);
+ }
+ }else{
+ return callback.read([]);
+ }
+ }
+ }
+}
+
+Draw9p.dirent = function(qid, offset){
+ with(this.Qids){
+ try{
+ if(qid.path == QROOT){
+ return this.stat([
+ QCONS,
+ QCONSCTL,
+ QMOUSE,
+ QCURSOR,
+ QWINNAME,
+ QLABEL,
+ QDRAW
+ ][offset]);
+ }else if(qid.path == QDRAW){
+ return this.stat([QDRAWNEW].concat(this.connqids())[offset]);
+ }else if(qid.path >= QDRAWBASE){
+ var dd = this.drawdir(qid.path);
+
+ if(dd.drawfile == 0){
+ return this.stat([
+ QDRAWCTL, QDRAWDATA,
+ QDRAWCOLORMAP, QDRAWREFRESH
+ ][offset] + (dd.drawdir * QDRAWSTEP) + QDRAWBASE);
+ }
+ }
+ return undefined;
+ }catch(e){
+ return undefined;
+ }
+ }
+}
+
+Draw9p.write = function(qid, offset, data){
+ with(this.Qids){
+ if(qid.path >= QDRAWBASE){
+ var dd = this.drawdir(qid.path);
+ if(dd.drawfile == QDRAWDATA){
+ return this.writedrawdata(dd.drawdir, offset, data);
+ }else if(dd.drawfile == QDRAWCTL){
+ return this.writedrawctl(dd.drawdir, offset, data);
+ }else{
+ throw("writing impermissible");
+ }
+ }else{
+ if(qid.path == QCONSCTL){
+ return;
+ }else if(qid.path == QCURSOR){
+ return mouse.cursor.write(data);
+ }else if(qid.path == QLABEL){
+ this.label = data;
+ return data.length;
+ }else{
+ throw("cannot write");
+ }
+ }
+ }
+}
+
+Draw9p.clunk = function(fid){
+ with(this.Qids){
+ if(fid.qid.path == QDRAWNEW){
+ delete this.conns[fid.drawconn];
+ }
+ }
+}
+
+Draw9p.remove = function(qid){
+ throw("cannot remove");
+}
+
+Draw9p.stat = function(qid){
+ with(this.Qids){
+ if(qid == QROOT){
+ return new NineP.Stat({
+ qid: new NineP.Qid(QROOT, 0, NineP.QTDIR),
+ mode: NineP.DMDIR|NineP.DMREAD|NineP.DMEXEC,
+ name: "/"
+ });
+ }else if(qid == QCONS){
+ return new NineP.Stat({
+ qid: new NineP.Qid(QCONS, 0, 0),
+ mode: 0,
+ name: "cons"
+ });
+ }else if(qid == QCONSCTL){
+ return new NineP.Stat({
+ qid: new NineP.Qid(QCONSCTL, 0, 0),
+ mode: 0,
+ name: "consctl"
+ });
+ }else if(qid == QMOUSE){
+ return new NineP.Stat({
+ qid: new NineP.Qid(QMOUSE, 0, 0),
+ mode: NineP.DMAPPEND,
+ length: 49,
+ name: "mouse"
+ });
+ }else if(qid == QCURSOR){
+ return new NineP.Stat({
+ qid: new NineP.Qid(QCURSOR, 0, 0),
+ mode: 0,
+ length: 72,
+ name: "cursor"
+ });
+ }else if(qid == QWINNAME){
+ return new NineP.Stat({
+ qid: new NineP.Qid(QWINNAME, 0, 0),
+ length: "webdraw".length,
+ name: "winname"
+ });
+ }else if(qid == QLABEL){
+ return new NineP.Stat({
+ qid: new NineP.Qid(QLABEL, 0, 0),
+ length: this.label.length,
+ name: "label"
+ });
+ }else if(qid == QDRAW){
+ return new NineP.Stat({
+ qid: new NineP.Qid(QDRAW, 0, NineP.QTDIR),
+ mode: NineP.DMDIR,
+ name: "draw"
+ });
+ }else if(qid == QDRAWNEW){
+ return new NineP.Stat({
+ qid: new NineP.Qid(QDRAWNEW, this.nextconn, 0),
+ mode: 0,
+ length: 144,
+ name: "new"
+ });
+ }else if(qid >= QDRAWBASE){
+ return this.statdrawdir(qid);
+ }else{
+ throw("invalid qid");
+ }
+ }
+}
+
+Draw9p.statdrawdir = function(qid){
+ with(this.Qids){
+ var dd = this.drawdir(qid);
+
+ if(qid < QDRAWBASE){
+ throw("could not stat");
+ }
+
+ if(this.conns[dd.drawdir] == undefined){
+ throw("file not found");
+ }
+
+ if(dd.drawfile == 0){
+ return new NineP.Stat({
+ qid: new NineP.Qid(qid, 0, NineP.QTDIR),
+ mode: 0,
+ name: String(dd.drawdir)
+ });
+ }
+
+ if(dd.drawfile == QDRAWCTL){
+ return new NineP.Stat({
+ qid: new NineP.Qid(qid, 0, 0),
+ mode: 0,
+ name: "ctl"
+ });
+ }else if(dd.drawfile == QDRAWDATA){
+ return new NineP.Stat({
+ qid: new NineP.Qid(qid, 0, 0),
+ mode: 0,
+ name: "data"
+ });
+ }else if(dd.drawfile == QDRAWCOLORMAP){
+ return new NineP.Stat({
+ qid: new NineP.Qid(qid, 0, 0),
+ mode: 0,
+ name: "colormap"
+ });
+ }else if(dd.drawfile == QDRAWREFRESH){
+ return new NineP.Stat({
+ qid: new NineP.Qid(qid, 0, 0),
+ mode: 0,
+ name: "refresh"
+ });
+ }else{
+ throw("could not stat");
+ }
+ }
+}
--- /usr/web/9wd/js/draw/image.js Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/draw/image.js Mon Feb 17 21:45:17 2014
@@ -0,0 +1,73 @@
+Draw9p.Image = function(refresh, chan, repl, r, clipr, color){
+ this.refresh = refresh;
+ this.chan = chan;
+ this.repl = repl;
+ this.r = r;
+ this.clipr = clipr;
+ this.canvas = document.createElement("canvas");
+ this.canvas.width = r.max.x - r.min.x;
+ this.canvas.height = r.max.y - r.min.y;
+ this.ctx = this.canvas.getContext("2d");
+
+ var red = (color >> 24) & 0xFF;
+ var green = (color >> 16) & 0xFF;
+ var blue = (color >> 8) & 0xFF;
+ var alpha = (color) & 0xFF;
+
+ var data = this.ctx.createImageData(this.canvas.width, this.canvas.height);
+ for(var i = 0; i < data.data.length; i += 4){
+ data.data[i + 0] = red;
+ data.data[i + 1] = green;
+ data.data[i + 2] = blue;
+ data.data[i + 3] = alpha;
+ }
+
+ this.ctx.putImageData(data, 0, 0);
+}
+
+/* XXX ScreenImage will not work as a drawing source */
+/* due to the assumption that the canvas maps 1-1 to the image data. */
+Draw9p.ScreenImage = function(screen, refresh, chan, repl, r, clipr, color){
+ if(screen == undefined || screen.backimg == undefined){
+ throw("invalid screen");
+ }
+ this.screen = screen;
+
+ /* if(chan != this.screen.backimg.chan){ */
+ /* throw("chan mismatch between image and screen"); */
+ /* } */
+
+ this.refresh = refresh;
+ this.chan = chan;
+ this.repl = repl;
+ this.r = r;
+ this.clipr = clipr;
+
+ /* XXX We should create a backing store, depending on refresh value. */
+ this.canvas = this.screen.backimg.canvas;
+ this.ctx = this.canvas.getContext("2d");
+ this.ctx.beginPath();
+ this.ctx.moveTo(r.min.x, r.min.y);
+ this.ctx.lineTo(r.max.x, r.min.y);
+ this.ctx.lineTo(r.max.x, r.max.y);
+ this.ctx.lineTo(r.min.x, r.max.y);
+ this.ctx.lineTo(r.min.x, r.min.y);
+ this.ctx.clip();
+
+ /* XXX Fill ScreenImage with background colour. */
+}
+
+/* XXX Creating a new rootwindow object for each connection will probably */
+/* break once we start doing more advanced things. */
+/* XXX These parameters should not be hardcoded. */
+Draw9p.RootImage = function(){
+ var image = new this.Image(0, "r8g8b8", 0,
+ {min: {x: 0, y: 0}, max: {x: 640, y: 480}},
+ {min: {x: 0, y: 0}, max: {x: 640, y: 480}},
+ 0xFFFFFFFF);
+
+ image.canvas = Draw9p.rootcanvas;
+ image.ctx = image.canvas.getContext("2d");
+
+ return image;
+}
--- /usr/web/9wd/js/draw/load.js Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/draw/load.js Mon Feb 17 21:45:19 2014
@@ -0,0 +1,211 @@
+var decompress = function(data, w, h, bpl, cdata){
+ var cdoff = 0; /* cdata offset */
+ var doff = 0; /* data offset */
+ var odoff = 0; /* offset data offset */
+ var c, cnt;
+ var offs, offlen;
+
+ for(;;){
+ if(doff >= data.length){
+ return cdoff;
+ }
+ if(cdoff >= cdata.length){
+ throw("buffer too small");
+ }
+
+ c = cdata[cdoff++];
+ if(c >= 128){
+ for(cnt = c-128+1; cnt > 0; --cnt){
+ data[doff++] = cdata[cdoff++];
+ }
+ }else{
+ offs = cdata[cdoff++] + ((c&3)<<8)+1;
+ odoff = doff - offs;
+ for(cnt = (c>>2) + 3; cnt > 0; --cnt){
+ data[doff++] = data[odoff++];
+ }
+ }
+ }
+}
+
+var bytesperline = function(w, depth){
+ var bytesperpix = Math.ceil(depth / 8);
+ var pixperbyte = Math.floor(8 / depth);
+
+ if(depth < 8){
+ return Math.ceil(w / pixperbyte);
+ }else{
+ return w * bytesperpix;
+ }
+}
+
+var getpixel = function(data, depth, w, h, line, col){
+ var bytesperpix = Math.ceil(depth / 8);
+ var pixperbyte = Math.floor(8 / depth);
+ var bytesperline = Math.ceil((w * pixperbyte) / 8);
+ var pixordinbyte = col % pixperbyte;
+
+ if(depth < 8){
+ bytesperline = Math.ceil(w / pixperbyte);
+ var offset = (line * bytesperline) + Math.floor((col * pixperbyte) / 8);
+ }else{
+ bytesperline = w * bytesperpix;
+ var offset = (line * bytesperline) + (col * bytesperpix);
+ }
+
+ if(line > h){
+ throw("pixel line index out of bounds");
+ }
+ if(col > w){
+ throw("pixel column index out of bounds");
+ }
+
+ if(depth < 8){
+ /* XXX remember to shift the whole mess back down! */
+ var mask = (1 << depth) - 1;
+ return (data[offset] >> (depth * pixordinbyte)) & mask;
+ }else{
+ /* XXX THIS IS WRONG! */
+ /* possibly: for(var i = bpp; i > 0; --i) */
+ var pixel = 0;
+ for(var i = 0; i < bytesperpix; ++i){
+ pixel |= data[offset + i] << (8 * i);
+ }
+ return pixel;
+ }
+}
+
+var canvaspos = function(w, h, line, col){
+ return ((line * w) + col) * 4;
+}
+
+var scalepixel = function(pixel, from, to){
+ if(from < to){
+ return Math.floor((pixel * ((1<> (from - to);
+ }
+}
+
+var loader = {
+ generic: function(arr, w, h, chan, data){
+ var depth = Chan.chantodepth(chan);
+
+ for(var line = 0; line < h; ++line){
+ for(var col = 0; col < w; ++col){
+ var pixel = getpixel(data, depth, w, h, line, col);
+ var cp = canvaspos(w, h, line, col);
+ arr[cp + 3] = 0xFF; /* Default to 100% alpha. */
+ for(var c = chan; c; c >>= 8){
+ var nbits = Chan.NBITS(c);
+ var px = pixel & ((1<>= nbits;
+ }
+ }
+ }
+ return arr;
+ },
+ grey: function(canvas, w, h, chan, data){
+ if((Chan.TYPE(chan) != Chan.chans.CGrey) || (chan >> 8)){
+ throw("not a grey-only image");
+ }
+ var depth = Chan.NBITS(chan);
+ var pixperbyte = Math.floor(8 / depth);
+ var bytesperline = Math.ceil(w / pixperbyte);
+
+ var cp = 0; /* canvas offset */
+ var dp = 0; /* data offset */
+
+ for(var line = 0; line < h; ++line, dp += bytesperline){
+ for(var b = 0; b < bytesperline; ++b){
+ for(var p = 1; p <= pixperbyte; ++p){
+ if((b * pixperbyte) + p > w) break;
+
+ var px = (data[dp + b] >> ((pixperbyte - p) * depth)) & ((1<> 16) & 0xFF;
+ canvas[cp++] = (px >> 8) & 0xFF;
+ canvas[cp++] = (px >> 0) & 0xFF;
+ canvas[cp++] = 0xFF;
+ }
+ }
+ }
+}
+
+Memdraw.Load = function(canvas, w, h, chan, data, iscompressed){
+ var depth = Chan.chantodepth(chan);
+ var bpl = Math.ceil((w * depth) / 8);
+ var len;
+
+ if(iscompressed){
+ var cdata = data;
+ data = new Uint8Array(h * bpl);
+ len = decompress(data, w, h, bpl, cdata);
+ }else{
+ len = bpl * h;
+ }
+
+ if(!(chan>>8)){
+ switch(Chan.TYPE(chan)){
+ case Chan.chans.CGrey:
+ loader.grey(canvas, w, h, chan, data);
+ return len;
+ case Chan.chans.CMap:
+ if(Chan.NBITS(chan) == 8){
+ loader.cmap8(canvas, w, h, chan, data);
+ return len;
+ }
+ }
+ }
+
+ loader.generic(canvas, w, h, chan, data);
+ return len;
+}
--- /usr/web/9wd/js/draw/memdraw.js Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/draw/memdraw.js Mon Feb 17 21:45:20 2014
@@ -0,0 +1,295 @@
+var icossin2 = function(dx, dy){
+ var theta = Math.atan2(dx, dy);
+ return {
+ cos: Math.cos(theta),
+ sin: Math.sin(theta),
+ theta: theta
+ }
+}
+
+/* XXX Seems to misbehave on non-(0,0) src.r.min. */
+var draw = function(dst, r, src, sp, op){
+ dst.ctx.save();
+ dst.ctx.globalCompositeOperation = Memdraw.Ops[op];
+
+ /* XXX what about clipping on src? */
+
+ dst.ctx.beginPath();
+ dst.ctx.rect(r.min.x-dst.r.min.x, r.min.y-dst.r.min.y,
+ r.max.x-r.min.x, r.max.y-r.min.y);
+ dst.ctx.clip();
+
+ if(src.repl){
+ dst.ctx.fillStyle = dst.ctx.createPattern(src.canvas, "repeat");
+ dst.ctx.fill();
+ }else{
+ dst.ctx.drawImage(src.canvas, r.min.x-sp.x+src.r.min.x-dst.r.min.x,
+ r.min.y-sp.y+src.r.min.y-dst.r.min.y);
+ }
+ dst.ctx.restore();
+ return;
+}
+
+var maskalpha = function(img){
+ var data = img.ctx.getImageData(0, 0, img.canvas.width, img.canvas.height);
+ for(var i = 0; i < data.data.length; i += 4){
+ data.data[i + 3] = (data.data[i + 0] + data.data[i + 1] + data.data[i + 2]) /3;
+ }
+ img.ctx.putImageData(data, 0, 0);
+}
+
+var drawmasked = function(dst, r, src, sp, mask, mp, op){
+ if(r.max.x == r.min.x || r.max.y == r.min.y){
+ return;
+ }
+
+ /* XXX Hack: draw() doesn't seem to handle offset images properly. */
+ var rdelta = {
+ min: {x: 0, y: 0},
+ max: {
+ x: r.max.x - r.min.x,
+ y: r.max.y - r.min.y
+ }
+ }
+ var img = new Draw9p.Image(0, Chan.fmts.CMAP8, 0, rdelta, rdelta, 0xFF00FFFF);
+
+ /* XXX Hack; we should have a way to create blank images. */
+ img.ctx.clearRect(0, 0, r.max.x, r.max.y);
+
+ draw(img, rdelta, mask, mp, Memdraw.Opdefs.SoverD.key);
+ maskalpha(img);
+ draw(img, rdelta, src, sp, Memdraw.Opdefs.SinD.key);
+ //document.body.appendChild(img.canvas);
+ draw(dst, r, img, {x: 0, y: 0}, op);
+}
+
+var load = function(dst, r, data, iscompressed){
+ var img = new Draw9p.Image(0, dst.chan, 0, r, r, 0);
+ var w = r.max.x - r.min.x;
+ var h = r.max.y - r.min.y;
+ var arr = img.ctx.createImageData(w, h);
+
+ var offset = Memdraw.Load(arr.data, w, h, img.chan, data, iscompressed);
+ img.ctx.putImageData(arr, 0, 0);
+ draw(dst, r, img, r.min, Memdraw.Opdefs.SoverD.key);
+ /* Append canvas for debugging. */
+ //document.body.appendChild(dst.canvas);
+ return offset;
+}
+
+var arrowend = function(tip, points, pp, end, sin, cos, radius){
+ var x1, x2, x3;
+
+ if(end == Memdraw.End.arrow){
+ x1 = 8;
+ x2 = 10;
+ x3 = 3;
+ }else{
+ x1 = (end >> 5) & 0x1FF;
+ x2 = (end >>14) & 0x1FF;
+ x3 = (end >> 23) & 0x1FF;
+ }
+
+ points[pp] = { /* upper side of shaft */
+ x: tip.x+((2*radius+1)*sin/2-x1*cos),
+ y: tip.y-((2*radius+1)*cos/2+x1*sin)
+ };
+ ++pp;
+ points[pp] = { /* upper barb */
+ x: tip.x+((2*radius+2*x3+1)*sin/2-x2*cos),
+ y: tip.y-((2*radius+2*x3+1)*cos/2+x2*sin)
+ };
+ ++pp;
+ points[pp] = {
+ x: tip.x,
+ y: tip.y
+ };
+ ++pp;
+ points[pp] = { /* lower barb */
+ x: tip.x+(-(2*radius+2*x3+1)*sin/2-x2*cos),
+ y: tip.y-(-(2*radius+2*x3+1)*cos/2+x2*sin)
+ };
+ ++pp;
+ points[pp] = { /* lower side of shaft */
+ x: tip.x+(-(2*radius+1)*sin/2-x1*cos),
+ y: tip.y+((2*radius+1)*cos/2-x1*sin)
+ };
+}
+
+var discend = function(p, radius, dst, src, dsrc, op){
+ Memdraw.fillellipse(dst, p, radius, radius, 0, 2 * Math.PI, src, dsrc, op);
+}
+
+var drawchar = function(dst, p, src, sp, bg, bp, font, fc, op){
+ var r = {
+ min: {
+ x: p.x + fc.left,
+ y: p.y - (font.ascent - fc.r.min.y)
+ },
+ max: {
+ x: (p.x + fc.left) + (fc.r.max.x - fc.r.min.x),
+ y: (p.y - (font.ascent - fc.r.min.y)) + (fc.r.max.y - fc.r.min.y)
+ }
+ }
+ var sp1 = {
+ x: sp.x + fc.left,
+ y: sp.y + fc.r.min.y
+ }
+
+ if(bg){
+ draw(dst, r, bg, bp, op);
+ }
+ drawmasked(dst, r, src, sp1, font, fc.r.min, op);
+ p.x += fc.width;
+ sp.x += fc.width;
+ return p;
+}
+
+Memdraw = {
+ line: function(dst, p0, p1, end0, end1, radius, src, sp, op){
+ var angle = icossin2(p1.y - p0.y, p1.x - p0.x);
+ var dx = (angle.sin * (2 * radius + 1))/2;
+ var dy = (angle.cos * (2 * radius + 1))/2;
+
+ var q = {
+ /* 1/2 is cargo-cult from /sys/src/libmemdraw/line.c ; why? */
+ x: p0.x + 1/2 + angle.cos/2,
+ y: p0.y + 1/2 + angle.sin/2
+ }
+
+ var points = [];
+ var pp = 0;
+
+ switch(end0 & 0x1F){
+ case Memdraw.End.disc:
+ discend(p0, radius, dst, src, sp, op);
+ /* fall through */
+ case Memdraw.End.square:
+ default:
+ points[pp] = {x: q.x-dx, y: q.y+dy};
+ ++pp;
+ points[pp] = {x: q.x+dx, y: q.y-dy};
+ ++pp;
+ break;
+ case Memdraw.End.arrow:
+ arrowend(q, points, pp, end0, -angle.sin, -angle.cos, radius);
+ this.fillpoly(dst, points.slice(0, 5), 0, src, sp, op);
+ points[pp+1] = points[pp+4];
+ pp += 2;
+ }
+ q = {
+ x: p1.x + 1/2 + angle.cos/2,
+ y: p1.y + 1/2 + angle.sin/2
+ }
+
+ switch(end1 & 0x1F){
+ case Memdraw.End.disc:
+ discend(p1, radius, dst, src, sp, op);
+ /* fall through */
+ case Memdraw.End.square:
+ default:
+ points[pp] = {x: q.x+dx, y: q.y-dy};
+ ++pp;
+ points[pp] = {x: q.x-dx, y: q.y+dy};
+ ++pp;
+ break;
+ case Memdraw.End.arrow:
+ arrowend(q, points, pp, end1, angle.sin, angle.cos, radius);
+ this.fillpoly(dst, points.slice(pp, pp+5), 0, src, sp, op);
+ points[pp+1] = points[pp+4];
+ pp += 2;
+ }
+ /* XXX setting w incorrectly! */
+ return this.fillpoly(dst, points.slice(0, pp), 0, src, sp, op);
+ },
+ /* XXX behaves incorrectly for incomplete (non 2pi) ellipses. */
+ fillellipse: function(dst, c, horiz, vert, alpha, phi, src, sp, op){
+ dst.ctx.save();
+ dst.ctx.save();
+ dst.ctx.beginPath();
+ dst.ctx.translate(c.x, c.y);
+ dst.ctx.scale(horiz, vert);
+ dst.ctx.arc(0, 0, 1, alpha, phi, false);
+ dst.ctx.restore();
+ dst.ctx.clip();
+ draw(dst, dst.clipr, src, sp, op);
+ dst.ctx.restore();
+ },
+ fillpoly: function(dst, vertices, w, src, sp, op){
+ if(vertices.length < 1){
+ return;
+ }
+ dst.ctx.save();
+ dst.ctx.beginPath();
+ dst.ctx.moveTo(vertices[0].x, vertices[0].y);
+ for(var i = 1; i < vertices.length; ++i){
+ dst.ctx.lineTo(vertices[i].x, vertices[i].y);
+ }
+ dst.ctx.clip();
+ /* XXX fill background here */
+ draw(dst, {min: {x: 0, y: 0}, max: {x: 500, y: 500}}, src, sp, op);
+ dst.ctx.restore();
+ return;
+ },
+ poly: function(dst, points, end0, end1, radius, src, sp, op){
+ if(points.length < 2){
+ return;
+ }
+ for(var i = 1; i < points.length; ++i){
+ /* XXX calculate ends here; see C source. */
+ /* XXX calculate change in sp; requires point operations. */
+ this.line(dst, points[i-1], points[i],
+ Memdraw.End.disc, Memdraw.End.disc,
+ radius, src, sp, op);
+ }
+ },
+ load: function(dst, r, data, iscompressed){
+ return load(dst, r, data, iscompressed);
+ },
+ string: function(dst, src, font, p, clipr, sp, bg, bp, index, op){
+ for(var i = 0; i < index.length; ++i){
+ if(index[i] == 0 || index[i] >= font.nchar){
+ throw("font cache index out of bounds");
+ }cons.log("char: " + index[i]);
+ drawchar(dst, p, src, sp, bg, bp, font, font.fchar[index[i]], op);
+ }
+ },
+ Opdefs: {
+ Clear: {key: 0, op: undefined},
+ SinD: {key: 8, op: "source-in"},
+ DinS: {key: 4, op: "destination-in"},
+ SoutD: {key: 2, op: "source-out"},
+ DoutS: {key: 1, op: "destination-out"},
+
+ S: {key: 10, op: "copy"}, /* SinD | SoutD */
+ SoverD: {key: 11, op: "source-over"}, /* SinD | SoutD | DoutS */
+ SatopD: {key: 9, op: "source-atop"}, /* SinD | DoutS */
+ SxorD: {key: 3, op: "xor"}, /* SoutD | DoutS */
+
+ D: {key: 5, op: undefined}, /* DinS | DoutS */
+ DoverS: {key: 7, op: "destination-over"}, /* DinS | DoutS | SoutD */
+ DatopS: {key: 6, op: "destination-atop"}, /* DinS | SoutD */
+ DxorS: {key: 3, op: "xor"}, /* DoutS | SoutD */
+
+ /* Ncomp: 12 */
+ }
+}
+
+Memdraw.Ops = (function(o){
+ var ops = [];
+ for(var k in o){
+ ops[o[k].key] = o[k].op;
+ }
+ return ops;
+})(Memdraw.Opdefs);
+
+Memdraw.End = {
+ square: 0,
+ disc: 1,
+ arrow: 2,
+ mask: 0x1F
+}
+
+Memdraw.ARROW = function(a, b, c){
+ return Memdraw.End.arrow | (a << 5) | (b << 14) | (c << 23);
+}
--- /usr/web/9wd/js/draw/new.js Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/draw/new.js Mon Feb 17 21:45:21 2014
@@ -0,0 +1,19 @@
+Draw9p.readdrawnew = function(conn){
+ cons.log("readdrawnew");
+ var buf = [];
+
+ buf = buf.concat(pad11(conn));
+ buf = buf.concat(pad11(0));
+ buf = buf.concat(pad11("r8g8b8"));
+ buf = buf.concat(pad11(0));
+ buf = buf.concat(pad11(0));
+ buf = buf.concat(pad11(0));
+ buf = buf.concat(pad11(640));
+ buf = buf.concat(pad11(480));
+ buf = buf.concat(pad11(0));
+ buf = buf.concat(pad11(0));
+ buf = buf.concat(pad11(640));
+ buf = buf.concat(pad11(480));
+
+ return buf;
+}
--- /usr/web/9wd/js/draw/refresh.js Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/draw/refresh.js Mon Feb 17 21:45:22 2014
@@ -0,0 +1,19 @@
+Draw9p.readdrawrefresh = function(dd, offset, callback){
+ cons.log("readdrawrefresh");
+
+ var conn = this.conns[dd.drawdir];
+ if(conn == undefined){
+ return callback.error("invalid draw connection");
+ }
+
+ /* XXX This breaks if multiple reads are outstanding on the */
+ /* refresh file! Should we permit this and use some sort of */
+ /* array of refreshcallbacks, or should we be setting (and */
+ /* obeying!) the exclusive-use bit on the relevant [qf]id? */
+ if(conn.refreshcallback != undefined){
+
+ return callback.error("multiple reads are not allowed");
+ }
+
+ conn.refreshcallback = callback;
+}
--- /usr/web/9wd/js/draw/screen.js Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/draw/screen.js Mon Feb 17 21:45:24 2014
@@ -0,0 +1,7 @@
+Draw9p.Screen = function(id, backimg, fillimg, public){
+ this.id = id;
+ this.public = public;
+ this.backimg = backimg;
+ /* Memdraw.draw(this.backimg, this.fillimg); */
+ this.imgs = []; /* not sure how this should be represented? */
+}
--- /usr/web/9wd/js/init.js Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/init.js Mon Feb 17 21:45:25 2014
@@ -0,0 +1,74 @@
+function elem(name){
+ return document.getElementById(name);
+}
+
+function addevent(elem, evt, handler){
+ elem.addEventListener(evt, handler, true);
+}
+
+/* this should not be necessary, but */
+/* addevent does not seem to let me */
+/* keep the F3 key event from propagating */
+/* on Firefox 20. */
+function setevent(elem, evt, handler){
+ elem["on" + evt] = handler;
+}
+
+var basetime;
+var cons;
+var mouse;
+var settings;
+var ninep;
+
+window.onload = function(){
+ //var wsurl = Socket.wsurl(window.location.toString());
+ var wsurl = "ws://172.16.0.17/magic/websocket";
+ var webdraw = elem("webdraw");
+
+ basetime = Date.now();
+ cons = new Cons();
+ mouse = new Mouse(elem("cursor"));
+ settings = new Settings();
+ ninep = new NineP(wsurl, Draw9p, cons);
+
+ /* XXX Draw9p should be instantiated and have a constructor. */
+ Draw9p.rootcanvas = webdraw;
+ Draw9p.imgnames["webdraw"] = Draw9p.RootImage();
+ Draw9p.label = "webdraw".toUTF8Array();
+
+ addevent(webdraw, "mousedown", function(e){
+ return mouse.handlebutton(e, 1);
+ });
+ addevent(webdraw, "mouseup", function(e){
+ return mouse.handlebutton(e, 0);
+ });
+ addevent(webdraw, "mousemove", function(e){
+ return mouse.handlemove(e);
+ });
+ setevent(window, "keydown", function(e){
+ return cons.handlekeys(e, cons.kbd.down);
+ });
+ setevent(window, "keypress", function(e){
+ return cons.handlekeys(e, cons.kbd.press);
+ });
+ setevent(window, "keyup", function(e){
+ return cons.handlekeys(e, cons.kbd.up);
+ });
+
+ setevent(webdraw, "click", function(e){
+ if(
+ document.pointerLockElement !== webdraw &&
+ document.mozPointerLockElement !== webdraw &&
+ document.webkitPointerLockElement !== webdraw
+ ){
+ webdraw.requestPointerLock =
+ webdraw.requestPointerLock ||
+ webdraw.mozRequestPointerLock ||
+ webdraw.webkitRequestPointerLock;
+ webdraw.requestPointerLock();
+ return false;
+ }else{
+ return true;
+ }
+ });
+}
--- /usr/web/9wd/js/lib9p Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/lib9p Mon Feb 17 21:45:29 2014
@@ -0,0 +1,8 @@
+NineP.Fid = function(fid, qid){
+ this.fid = fid;
+ this.qid = qid;
+}
+
+NineP.Fid.prototype.toString = function(){
+ return "{ fid: " + this.fid + " qid: " + this.qid + " }";
+}
--- /usr/web/9wd/js/lib9p Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/lib9p Mon Feb 17 21:45:31 2014
@@ -0,0 +1,509 @@
+NineP = function(path, callbacks, cons){
+ var that = this;
+ this.socket = new Socket(path, function(e){that.rawpktin(e);});
+
+ this.maxbufsz = 32768;
+ this.buffer = [];
+ this.fids = [];
+
+ this.local = callbacks;
+ this.log = new NineP.Log(cons);
+};
+
+NineP.NOTAG = (~0) & 0xFFFF;
+
+NineP.OREAD = 0;
+NineP.OWRITE = 1;
+NineP.ORDWR = 2;
+NineP.OEXEC = 3;
+NineP.ORCLOSE = 0x40;
+
+NineP.packets = {
+ Tversion: 100,
+ Rversion: 101,
+ Tauth: 102,
+ Rauth: 103,
+ Tattach: 104,
+ Rattach: 105,
+ Terror: 106,
+ Rerror: 107,
+ Tflush: 108,
+ Rflush: 109,
+ Twalk: 110,
+ Rwalk: 111,
+ Topen: 112,
+ Ropen: 113,
+ Tcreate: 114,
+ Rcreate: 115,
+ Tread: 116,
+ Rread: 117,
+ Twrite: 118,
+ Rwrite: 119,
+ Tclunk: 120,
+ Rclunk: 121,
+ Tremove: 122,
+ Rremove: 123,
+ Tstat: 124,
+ Rstat: 125,
+ Twstat: 126,
+ Rwstat: 127,
+ Tmax: 128
+}
+
+NineP.GBIT8 = function(p){ return (p[0]); };
+NineP.GBIT16 = function(p){ return (p[0])|(p[1]<<8); };
+NineP.GBIT32 = function(p){ return (p[0])|(p[1]<<8)|(p[2]<<16)|(p[3]<<24); };
+/* XXX Javascript will do unpleasant things to integers over 32 bits! */
+NineP.GBIT64 = function(p){
+ /* throw("JAVASCRIPT CANNOT INTO INTEGERS!"); */
+ return (p[0]) | (p[1]<<8) | (p[2]<<16) | (p[3]<<24) |
+ (p[4]<<32) | (p[5]<<40) | (p[6]<<48) | (p[7]<<56);
+};
+NineP.PBIT8 = function(p,v){
+ p[0] = (v)&0xFF;
+ return p;
+};
+NineP.PBIT16 = function(p,v){
+ p[0] = (v)&0xFF;
+ p[1] = (v>>8)&0xFF;
+ return p;
+};
+NineP.PBIT32 = function(p,v){
+ p[0] = (v)&0xFF;
+ p[1] = (v>>8)&0xFF;
+ p[2] = (v>>16)&0xFF;
+ p[3] = (v>>24)&0xFF;
+ return p;
+}
+/* XXX Javascript will do unpleasant things to integers over 32 bits! */
+NineP.PBIT64 = function(p,v){
+ p[0] = (v) & 0xFF;
+ p[1] = (v>>8) & 0xFF;
+ p[2] = (v>>16) & 0xFF;
+ p[3] = (v>>24) & 0xFF;
+ p[4] = (v>>32) & 0xFF;
+ p[5] = (v>>40) & 0xFF;
+ p[6] = (v>>48) & 0xFF;
+ p[7] = (v>>56) & 0xFF;
+ return p;
+};
+
+NineP.getpktsize = function(buf){ return NineP.GBIT32(buf.slice(0,4)); };
+NineP.getpkttype = function(buf){ return buf[4]; };
+NineP.getpkttag = function(buf){ return buf.slice(5, 7); };
+
+NineP.mkwirebuf = function(buf){
+ return NineP.PBIT16([], buf.length).concat(buf);
+}
+
+NineP.mkwirestring = function(str){
+ var arr = str.toUTF8Array();
+ var len = NineP.PBIT16([], arr.length);
+ arr = len.concat(arr);
+ return arr;
+}
+
+NineP.getwirestring = function(pkt){
+ var len = NineP.GBIT16(pkt.splice(0,2));
+ return String.fromUTF8Array(pkt.splice(0,len));
+}
+
+NineP.prototype.rawpktin = function(pkt){
+ var pktarr = new Uint8Array(pkt);
+
+ this.buffer.push.apply(this.buffer, pktarr);
+ this.log.buf(this.buffer);
+
+ for(;;){
+ if(this.buffer.length < 4){
+ break;
+ }
+
+ var size = NineP.getpktsize(this.buffer);
+
+ if(this.buffer.length >= size){
+ this.processpkt(this.buffer.splice(0, size));
+ }else{
+ break;
+ }
+ }
+}
+
+NineP.prototype.processpkt = function(pkt){
+ var tag = NineP.getpkttag(pkt);
+ switch(NineP.getpkttype(pkt)){
+ case NineP.packets.Tversion:
+ return this.Rversion(pkt, tag);
+ case NineP.packets.Tauth:
+ return this.Rerror(tag, "no authentication required");
+ case NineP.packets.Tattach:
+ return this.Tattach(pkt, tag);
+ case NineP.packets.Terror:
+ return this.Rerror(tag, "terror");
+ case NineP.packets.Tflush:
+ return this.Tflush(pkt, tag);
+ case NineP.packets.Twalk:
+ return this.Twalk(pkt, tag);
+ case NineP.packets.Topen:
+ return this.Topen(pkt, tag);
+ case NineP.packets.Tcreate:
+ return this.Tcreate(pkt, tag);
+ case NineP.packets.Tread:
+ return this.Tread(pkt, tag);
+ case NineP.packets.Twrite:
+ return this.Twrite(pkt, tag);
+ case NineP.packets.Tclunk:
+ return this.Tclunk(pkt, tag);
+ case NineP.packets.Tremove:
+ return this.Tremove(pkt, tag);
+ case NineP.packets.Tstat:
+ return this.Tstat(pkt, tag);
+ case NineP.packets.Twstat:
+ case NineP.packets.Tmax:
+ default:
+ return this.Rerror(tag, "request not supported");
+ }
+}
+
+NineP.prototype.Rversion = function(pkt, tag){
+ var buf = [0, 0, 0, 0, NineP.packets.Rversion];
+ buf.push.apply(buf, tag);
+ var msize = NineP.GBIT32(pkt.slice(7));
+ this.maxbufsz = Math.min(msize, this.maxbufsz);
+ buf = buf.concat(NineP.PBIT32([], this.maxbufsz));
+ buf = buf.concat(NineP.mkwirestring("9P2000"));
+ NineP.PBIT32(buf, buf.length);
+ this.log.buf(buf);
+ this.socket.write(buf);
+}
+
+NineP.prototype.Tattach = function(pkt, tag){
+ var fid = NineP.GBIT32(pkt.slice(7));
+
+ if(this.fids[fid] != undefined){
+ this.Rerror(tag, "fid already in use");
+ }else{
+ this.fids[fid] = new NineP.Fid(fid, new NineP.Qid(0, 0, NineP.QTDIR));
+ this.Rattach(tag, fid);
+ }
+}
+
+NineP.prototype.Rattach = function(tag, fid){
+ var buf = [0, 0, 0, 0, NineP.packets.Rattach];
+ buf = buf.concat(tag);
+ buf = buf.concat(this.fids[fid].qid.toWireQid());
+ NineP.PBIT32(buf, buf.length);
+ this.log.buf(buf);
+ this.socket.write(buf);
+}
+
+
+NineP.prototype.Rerror = function(tag, msg){
+ var buf = [0,0,0,0, NineP.packets.Rerror];
+ buf.push.apply(buf, tag);
+ buf = buf.concat(NineP.mkwirestring(msg));
+ NineP.PBIT32(buf, buf.length);
+ this.log.buf(buf);
+ this.log.txt("error: " + msg);
+ this.socket.write(buf);
+}
+
+NineP.prototype.Tflush = function(pkt, tag){
+ this.Rerror(tag, "flush not implemented");
+}
+
+NineP.prototype.Twalk = function(pkt, tag){
+ pkt.splice(0, 7);
+ var oldfid = NineP.GBIT32(pkt.splice(0, 4));
+ var newfid = NineP.GBIT32(pkt.splice(0, 4));
+ var nwname = NineP.GBIT16(pkt.splice(0, 2));
+ var names = [];
+ var i;
+ for(i = 0; i < nwname; ++i){
+ names.push(NineP.getwirestring(pkt));
+ }
+ this.log.txt("twalk components: " + names + " (" + nwname + ")");
+
+ if(this.fids[oldfid] == undefined){
+ return this.Rerror(tag, "invalid fid");
+ }
+ if(this.fids[newfid] != undefined){
+ return this.Rerror(tag, "newfid in use");
+ }
+
+ var fakeqid = this.fids[oldfid].qid;
+ var interqids = [];
+
+ try{
+ for(i = 0; i < nwname; ++i){
+ fakeqid = this.local.walk1(fakeqid, names[i]);
+ interqids.push(fakeqid);
+ }
+ this.fids[newfid] = new NineP.Fid(newfid, fakeqid);
+ }catch(e){
+ if(i == 0){
+ return this.Rerror(tag, "could not walk");
+ }
+ }
+ this.Rwalk(tag, interqids.length, interqids);
+}
+
+NineP.prototype.Rwalk = function(tag, nwqid, qids){
+ var pkt = [0, 0, 0, 0, NineP.packets.Rwalk];
+ var i;
+
+ pkt = pkt.concat(tag);
+ pkt = pkt.concat(NineP.PBIT16([], nwqid));
+
+ for(i = 0; i < nwqid; ++i){
+ pkt = pkt.concat(qids[i].toWireQid());
+ }
+
+ NineP.PBIT32(pkt, pkt.length);
+ this.log.buf(pkt);
+ this.socket.write(pkt);
+}
+
+NineP.prototype.Topen = function(pkt, tag){
+ pkt.splice(0, 7);
+ var fid = NineP.GBIT32(pkt.splice(0, 4));
+ var mode = NineP.GBIT8(pkt.splice(0, 1));
+
+ if(this.fids[fid] == undefined){
+ return this.Rerror(tag, "invalid fid");
+ }
+
+ var qid = this.fids[fid].qid;
+
+ if(qid.type & NineP.QTDIR){
+ if(mode != NineP.OREAD){
+ return this.Rerror(tag, "cannot write to directory");
+ }
+ }
+
+ try{
+ this.local.open(this.fids[fid], mode);
+ }catch(e){
+ return this.Rerror(tag, e.toString());
+ }
+
+ this.fids[fid].mode = mode;
+
+ return this.Ropen(tag, fid);
+}
+
+NineP.prototype.Ropen = function(tag, fid){
+ var buf = [0, 0, 0, 0, NineP.packets.Ropen].concat(tag);
+
+ buf = buf.concat(this.fids[fid].qid.toWireQid());
+ buf = buf.concat(NineP.PBIT32([], 0));
+
+ NineP.PBIT32(buf, buf.length);
+ this.log.buf(buf);
+ this.socket.write(buf);
+}
+
+NineP.prototype.Tcreate = function(pkt, tag){
+ pkt.splice(0, 7);
+ var fid = NineP.GBIT32(pkt.splice(0, 4));
+ var name = NineP.getwirestring(pkt);
+ var perm = NineP.GBIT32(pkt.splice(0, 4));
+ var mode = NineP.GBIT8(pkt.splice(0, 1));
+
+ if(this.fids[fid] == undefined){
+ return this.Rerror(tag, "invalid fid");
+ }else if(!(this.fids[fid].qid.type & NineP.QTDIR)){
+ return this.Rerror(tag, "cannot create in non-directory");
+ }
+
+ try{
+ var qid = this.local.create(name, perm, mode);
+ this.fids[fid] = new NineP.Fid(fid, qid);
+ this.fids[fid].mode = mode;
+ }catch(e){
+ return this.Rerror(tag, e.toString());
+ }
+
+ return this.Rcreate(tag, qid);
+}
+
+NineP.prototype.Rcreate = function(tag, qid){
+ var buf = [0, 0, 0, 0, NineP.packets.Rcreate].concat(tag);
+ var buf = buf.concat(qid.toWireQid());
+ var buf = buf.concat(NineP.PBIT32([], 0));
+
+ NineP.PBIT32(buf, buf.length);
+
+ this.log.buf(buf);
+ this.socket.write(buf);
+}
+
+NineP.prototype.Tread = function(pkt, tag){
+ pkt.splice(0, 7);
+ var fid = NineP.GBIT32(pkt.splice(0, 4));
+ var offset = NineP.GBIT64(pkt.splice(0, 8));
+ var count = NineP.GBIT32(pkt.splice(0, 4));
+
+ if(this.fids[fid] == undefined){
+ return this.Rerror(tag, "invalid fid");
+ }
+ var mode = this.fids[fid].mode;
+ if(mode != NineP.OREAD && mode != NineP.ORDWR){
+ return this.Rerror(tag, "fid not opened for reading");
+ }
+
+ if(this.fids[fid].qid.type & NineP.QTDIR){
+ return this.Rread(tag, this.dirread(this.fids[fid], offset, count));
+ }else{
+ var that = this;
+ return this.local.read(this.fids[fid], offset, count, {
+ read: function(data){
+ return that.Rread.call(that, tag, data);
+ },
+ error: function(data){
+ return that.Rerror.call(that, tag, data);
+ }
+ });
+ }
+}
+
+NineP.prototype.dirread = function(fid, offset, count){
+ var dirindex;
+ var buf = [];
+ if(offset == 0){
+ dirindex = 0;
+ }else{
+ dirindex = fid.dirindex;
+ }
+
+ while(buf.length < count){
+ var tmpstat = this.local.dirent(fid.qid, dirindex);
+ if(tmpstat == undefined){
+ break;
+ }
+ var tmpbuf = tmpstat.toWireStat();
+ if((buf.length + tmpbuf.length) > count){
+ break;
+ }
+ buf = buf.concat(tmpbuf);
+ dirindex += 1;
+ }
+ fid.dirindex = dirindex;
+ return buf;
+}
+
+NineP.prototype.Rread = function(tag, data){
+ var buf = [0, 0, 0, 0, NineP.packets.Rread].concat(tag);
+
+ buf = buf.concat(NineP.PBIT32([], data.length)).concat(data);
+ NineP.PBIT32(buf, buf.length)
+
+ this.log.buf(buf);
+ this.socket.write(buf);
+}
+
+NineP.prototype.Twrite = function(pkt, tag){
+ pkt.splice(0, 7);
+ var fid = NineP.GBIT32(pkt.splice(0, 4));
+ var offset = NineP.GBIT64(pkt.splice(0, 8));
+ var count = NineP.GBIT32(pkt.splice(0, 4));
+ var data = pkt.splice(0, count);
+
+ if(this.fids[fid] == undefined){
+ return this.Rerror(tag, "invalid fid");
+ }
+ var mode = this.fids[fid].mode;
+ if(!(mode & NineP.OWRITE) && mode != NineP.ORDWR){
+ return this.Rerror(tag, "file not opened for writing");
+ }
+
+ try{
+ var bytes = this.local.write(this.fids[fid].qid, offset, data);
+ }catch(e){
+ return this.Rerror(tag, e.toString());
+ }
+
+ return this.Rwrite(tag, bytes);
+}
+
+NineP.prototype.Rwrite = function(tag, count){
+ var buf = [0, 0, 0, 0, NineP.packets.Rwrite].concat(tag);
+ buf = buf.concat(NineP.PBIT32([], count));
+
+ NineP.PBIT32(buf, buf.length);
+ this.log.buf(buf);
+ this.socket.write(buf);
+}
+
+
+NineP.prototype.Tclunk = function(pkt, tag){
+ pkt.splice(0, 7);
+ var fid = NineP.GBIT32(pkt);
+
+ if(this.fids[fid] == undefined){
+ return this.Rerror(tag, "fid not in use");
+ }
+
+ this.local.clunk(this.fids[fid]);
+
+ delete this.fids[fid];
+
+ return this.Rclunk(tag);
+}
+
+NineP.prototype.Rclunk = function(tag){
+ var buf = [0, 0, 0, 0, NineP.packets.Rclunk].concat(tag);
+
+ buf = NineP.PBIT32(buf, buf.length);
+ this.log.buf(buf);
+ this.socket.write(buf);
+}
+
+NineP.prototype.Tremove = function(pkt, tag){
+ pkt.splice(0, 7);
+ var fid = NineP.GBIT32(pkt);
+
+ if(this.fids[fid] == undefined){
+ return this.Rerror(tag, "fid not in use");
+ }
+
+ try{
+ this.local.remove(this.fids[fid].qid);
+ }catch(e){
+ return this.Rerror(tag, e.toString());
+ }
+
+ delete this.fids[fid];
+ return this.Rremove(tag);
+}
+
+NineP.prototype.Rremove = function(tag){
+ var buf = [0, 0, 0, 0, NineP.packets.Rremove].concat(tag);
+
+ NineP.PBIT32(buf, buf.length);
+ this.log.buf(buf);
+ this.socket.write(buf);
+}
+
+NineP.prototype.Tstat = function(pkt, tag){
+ pkt.splice(0, 7);
+ var fid = NineP.GBIT32(pkt);
+ if(this.fids[fid] == undefined){
+ return this.Rerror(tag, "invalid fid");
+ }
+
+ try{
+ return this.Rstat(tag, this.local.stat(this.fids[fid].qid.path));
+ }catch(e){
+ return this.Rerror(tag, e.toString());
+ }
+}
+
+NineP.prototype.Rstat = function(tag, stat){
+ var pkt = [0, 0, 0, 0, NineP.packets.Rstat].concat(tag);
+ pkt = pkt.concat(NineP.mkwirebuf(stat.toWireStat()));
+
+ NineP.PBIT32(pkt, pkt.length);
+ this.log.buf(pkt);
+ this.socket.write(pkt);
+}
--- /usr/web/9wd/js/lib9p Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/lib9p Mon Feb 17 21:45:32 2014
@@ -0,0 +1,11 @@
+NineP.Log = function(cons){
+ this.cons = cons;
+}
+
+NineP.Log.prototype.txt = function(s){
+ this.cons.log(s);
+}
+
+NineP.Log.prototype.buf = function(s){
+ this.cons.log(s);
+}
--- /usr/web/9wd/js/lib9p Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/lib9p Mon Feb 17 21:45:33 2014
@@ -0,0 +1,31 @@
+NineP.QTDIR = 0x80;
+NineP.QTAPPEND = 0x40;
+NineP.QTEXCL = 0x20;
+NineP.QTMOUNT = 0x10;
+NineP.QTAUTH = 0x08;
+NineP.QTTMP = 0x04;
+NineP.QTFILE = 0x00;
+
+NineP.DMDIR = 0x80000000;
+NineP.DMAPPEND = 0x40000000;
+NineP.DMEXCL = 0x20000000;
+NineP.DMTMP = 0x04000000;
+
+NineP.Qid = function(path, vers, type, callback){
+ this.path = path;
+ this.vers = vers;
+ this.type = type;
+ this.callback = callback;
+}
+
+NineP.Qid.prototype.toString = function(){
+ return "{path: " + this.path + "; vers: " + this.vers + "; type: " + this.type + ";}";
+}
+
+NineP.Qid.prototype.toWireQid = function(){
+ var buf = [];
+ buf = buf.concat(NineP.PBIT8([], this.type));
+ buf = buf.concat(NineP.PBIT32([], this.vers));
+ buf = buf.concat(NineP.PBIT64([], this.path));
+ return buf;
+}
--- /usr/web/9wd/js/lib9p Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/lib9p Mon Feb 17 21:45:34 2014
@@ -0,0 +1,17 @@
+function Socket(path, read){
+ this.sock = new WebSocket(path, "9p");
+ this.sock.binaryType = "arraybuffer";
+
+ this.write = function(data){
+ var u8 = new Uint8Array(data);
+ this.sock.send(u8.buffer);
+ }
+
+ this.sock.onmessage = function(e){
+ read(e.data);
+ }
+}
+/* I couldn't find a better way to get a Websocket to the same server. */
+Socket.wsurl = function(url){
+ return url.replace(/^http/, "ws").concat("9p");
+}
--- /usr/web/9wd/js/lib9p Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/lib9p Mon Feb 17 21:45:35 2014
@@ -0,0 +1,60 @@
+NineP.Stat = function(stat){
+ for(var elem in stat){
+ this[elem] = stat[elem];
+ }
+}
+
+NineP.Stat.fromWireStat = function(buf){
+ var size = NineP.GBIT16(buf.splice(0, 2));
+ buf.splice(0, 6); /* type, dev */
+
+ var proto = {
+ qid: NineP.Qid.fromWireQid(buf.slice(0, 13)),
+ mode: NineP.GBIT32(buf.slice(0, 4)),
+ atime: NineP.GBIT32(buf.slice(0, 4)),
+ mtime: NineP.GBIT32(buf.slice(0, 4)),
+ length: NineP.GBIT64(buf.slice(0, 8)),
+ name: NineP.getwirestring(buf),
+ uid: NineP.getwirestring(buf),
+ gid: NineP.getwirestring(buf),
+ muid: NineP.getwirestring(buf)
+ }
+
+ /* XXX This is wasteful! I just want to bless it with ``is-a''. */
+ return new NineP.Stat(proto);
+}
+
+NineP.Stat.prototype = {
+ qid: new NineP.Qid(0, 0, 0),
+ mode: 0,
+ atime: 0,
+ mtime: 0,
+ length: 0,
+ name: "",
+ uid: "",
+ gid: "",
+ muid: ""
+}
+
+NineP.Stat.prototype.toWireStat = function(){
+ var stat = [];
+ stat = stat.concat([0, 0]);
+ stat = stat.concat([0, 0, 0, 0]);
+ stat = stat.concat(this.qid.toWireQid());
+ stat = stat.concat(NineP.PBIT32([], this.mode));
+ stat = stat.concat(NineP.PBIT32([], this.atime));
+ stat = stat.concat(NineP.PBIT32([], this.mtime));
+ stat = stat.concat(NineP.PBIT64([], this.length));
+ stat = stat.concat(NineP.mkwirestring(this.name));
+ stat = stat.concat(NineP.mkwirestring(this.uid));
+ stat = stat.concat(NineP.mkwirestring(this.gid));
+ stat = stat.concat(NineP.mkwirestring(this.muid));
+
+ stat = NineP.PBIT16([], stat.length).concat(stat);
+
+ return stat;
+}
+
+NineP.Stat.prototype.toString = function(){
+ return JSON.stringify(this);
+}
--- /usr/web/9wd/js/lib9p Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/lib9p Mon Feb 17 21:45:36 2014
@@ -0,0 +1,76 @@
+String.prototype.toUTF8Array = function(){
+ var arr = [];
+
+ for(var i = 0; i < this.length; ++i){
+ var c = this.charCodeAt(i);
+
+ /* Convert from UTF-16 to Unicode code point. */
+ if((c & 0xDC00) == 0xD800){
+ var lead = c;
+ var tail = this.charCodeAt(++i);
+
+ c = 0x10000 | ((lead & 0x3FF) << 10) | ((tail & 0x3FF));
+ }
+
+ /* Convert from code point to UTF-8. */
+ if(c > 0x10000){ /* 4 bytes */
+ arr.push(0xF0 | ((c & 0x1C0000) >> 18));
+ arr.push(0x80 | ((c & 0x3F000) >> 12));
+ arr.push(0x80 | ((c & 0xFC0) >> 6));
+ arr.push(0x80 | ((c & 0x3F)));
+ }else if(c > 0x0800){ /* 3 bytes */
+ arr.push(0xE0 | ((c & 0xF000) >> 12));
+ arr.push(0x80 | ((c & 0xFC0) >> 6));
+ arr.push(0x80 | ((c & 0x3F)));
+ }else if(c > 0x0080){ /* 2 bytes */
+ arr.push(0xC0 | ((c & 0xFC0) >> 6));
+ arr.push(0x80 | ((c & 0x3F)));
+ }else{ /* 1 byte */
+ arr.push(0x00 | ((c & 0x7F)));
+ }
+ }
+
+ return arr;
+}
+
+String.fromUTF8Array = function(arr){
+ var units = [];
+
+ for(var i = 0; i < arr.length; ++i){
+ var codepoint = 0;
+
+ /* Convert from UTF-8 to Unicode codepoint. */
+ if((arr[i] & 0x80) == 0){ /* one byte */
+ codepoint = arr[i] & 0x7F;
+ }else if((arr[i] & 0xE0) == 0xC0){ /* two bytes */
+ codepoint = (
+ ((arr[i] & 0x1F) << 6) |
+ ((arr[++i] & 0x3F))
+ );
+ }else if((arr[i] & 0xF0) == 0xE0){ /* three bytes */
+ codepoint = (
+ ((arr[i] & 0x0F) << 12) |
+ ((arr[++i] & 0x3F) << 6) |
+ ((arr[++i] & 0x3F))
+ );
+ }else if((arr[i] & 0xF8) == 0xF0){ /* four bytes */
+ codepoint = (
+ ((arr[i] & 0x07) << 18) |
+ ((arr[++i] & 0x3F) << 12) |
+ ((arr[++i] & 0x3F) << 6) |
+ ((arr[++i] & 0x3F))
+ );
+ }else{
+ /* five- and six- byte UTF-8 are now illegal. */
+ }
+
+ /* Convert from Unicode codepoint to UTF-16. */
+ if(codepoint & 0x10000){
+ units.push(0xD800 | ((codepoint >> 10) & 0x3FF));
+ units.push(0xDC00 | (codepoint & 0x3FF));
+ }else{
+ units.push(codepoint);
+ }
+ }
+ return String.fromCharCode.apply(null, units);
+}
--- /usr/web/9wd/js/lib9p/fid.js Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/lib9p/fid.js Mon Feb 17 21:45:38 2014
@@ -0,0 +1,8 @@
+NineP.Fid = function(fid, qid){
+ this.fid = fid;
+ this.qid = qid;
+}
+
+NineP.Fid.prototype.toString = function(){
+ return "{ fid: " + this.fid + " qid: " + this.qid + " }";
+}
--- /usr/web/9wd/js/lib9p/lib9p.js Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/lib9p/lib9p.js Mon Feb 17 21:45:39 2014
@@ -0,0 +1,509 @@
+NineP = function(path, callbacks, cons){
+ var that = this;
+ this.socket = new Socket(path, function(e){that.rawpktin(e);});
+
+ this.maxbufsz = 32768;
+ this.buffer = [];
+ this.fids = [];
+
+ this.local = callbacks;
+ this.log = new NineP.Log(cons);
+};
+
+NineP.NOTAG = (~0) & 0xFFFF;
+
+NineP.OREAD = 0;
+NineP.OWRITE = 1;
+NineP.ORDWR = 2;
+NineP.OEXEC = 3;
+NineP.ORCLOSE = 0x40;
+
+NineP.packets = {
+ Tversion: 100,
+ Rversion: 101,
+ Tauth: 102,
+ Rauth: 103,
+ Tattach: 104,
+ Rattach: 105,
+ Terror: 106,
+ Rerror: 107,
+ Tflush: 108,
+ Rflush: 109,
+ Twalk: 110,
+ Rwalk: 111,
+ Topen: 112,
+ Ropen: 113,
+ Tcreate: 114,
+ Rcreate: 115,
+ Tread: 116,
+ Rread: 117,
+ Twrite: 118,
+ Rwrite: 119,
+ Tclunk: 120,
+ Rclunk: 121,
+ Tremove: 122,
+ Rremove: 123,
+ Tstat: 124,
+ Rstat: 125,
+ Twstat: 126,
+ Rwstat: 127,
+ Tmax: 128
+}
+
+NineP.GBIT8 = function(p){ return (p[0]); };
+NineP.GBIT16 = function(p){ return (p[0])|(p[1]<<8); };
+NineP.GBIT32 = function(p){ return (p[0])|(p[1]<<8)|(p[2]<<16)|(p[3]<<24); };
+/* XXX Javascript will do unpleasant things to integers over 32 bits! */
+NineP.GBIT64 = function(p){
+ /* throw("JAVASCRIPT CANNOT INTO INTEGERS!"); */
+ return (p[0]) | (p[1]<<8) | (p[2]<<16) | (p[3]<<24) |
+ (p[4]<<32) | (p[5]<<40) | (p[6]<<48) | (p[7]<<56);
+};
+NineP.PBIT8 = function(p,v){
+ p[0] = (v)&0xFF;
+ return p;
+};
+NineP.PBIT16 = function(p,v){
+ p[0] = (v)&0xFF;
+ p[1] = (v>>8)&0xFF;
+ return p;
+};
+NineP.PBIT32 = function(p,v){
+ p[0] = (v)&0xFF;
+ p[1] = (v>>8)&0xFF;
+ p[2] = (v>>16)&0xFF;
+ p[3] = (v>>24)&0xFF;
+ return p;
+}
+/* XXX Javascript will do unpleasant things to integers over 32 bits! */
+NineP.PBIT64 = function(p,v){
+ p[0] = (v) & 0xFF;
+ p[1] = (v>>8) & 0xFF;
+ p[2] = (v>>16) & 0xFF;
+ p[3] = (v>>24) & 0xFF;
+ p[4] = (v>>32) & 0xFF;
+ p[5] = (v>>40) & 0xFF;
+ p[6] = (v>>48) & 0xFF;
+ p[7] = (v>>56) & 0xFF;
+ return p;
+};
+
+NineP.getpktsize = function(buf){ return NineP.GBIT32(buf.slice(0,4)); };
+NineP.getpkttype = function(buf){ return buf[4]; };
+NineP.getpkttag = function(buf){ return buf.slice(5, 7); };
+
+NineP.mkwirebuf = function(buf){
+ return NineP.PBIT16([], buf.length).concat(buf);
+}
+
+NineP.mkwirestring = function(str){
+ var arr = str.toUTF8Array();
+ var len = NineP.PBIT16([], arr.length);
+ arr = len.concat(arr);
+ return arr;
+}
+
+NineP.getwirestring = function(pkt){
+ var len = NineP.GBIT16(pkt.splice(0,2));
+ return String.fromUTF8Array(pkt.splice(0,len));
+}
+
+NineP.prototype.rawpktin = function(pkt){
+ var pktarr = new Uint8Array(pkt);
+
+ this.buffer.push.apply(this.buffer, pktarr);
+ this.log.buf(this.buffer);
+
+ for(;;){
+ if(this.buffer.length < 4){
+ break;
+ }
+
+ var size = NineP.getpktsize(this.buffer);
+
+ if(this.buffer.length >= size){
+ this.processpkt(this.buffer.splice(0, size));
+ }else{
+ break;
+ }
+ }
+}
+
+NineP.prototype.processpkt = function(pkt){
+ var tag = NineP.getpkttag(pkt);
+ switch(NineP.getpkttype(pkt)){
+ case NineP.packets.Tversion:
+ return this.Rversion(pkt, tag);
+ case NineP.packets.Tauth:
+ return this.Rerror(tag, "no authentication required");
+ case NineP.packets.Tattach:
+ return this.Tattach(pkt, tag);
+ case NineP.packets.Terror:
+ return this.Rerror(tag, "terror");
+ case NineP.packets.Tflush:
+ return this.Tflush(pkt, tag);
+ case NineP.packets.Twalk:
+ return this.Twalk(pkt, tag);
+ case NineP.packets.Topen:
+ return this.Topen(pkt, tag);
+ case NineP.packets.Tcreate:
+ return this.Tcreate(pkt, tag);
+ case NineP.packets.Tread:
+ return this.Tread(pkt, tag);
+ case NineP.packets.Twrite:
+ return this.Twrite(pkt, tag);
+ case NineP.packets.Tclunk:
+ return this.Tclunk(pkt, tag);
+ case NineP.packets.Tremove:
+ return this.Tremove(pkt, tag);
+ case NineP.packets.Tstat:
+ return this.Tstat(pkt, tag);
+ case NineP.packets.Twstat:
+ case NineP.packets.Tmax:
+ default:
+ return this.Rerror(tag, "request not supported");
+ }
+}
+
+NineP.prototype.Rversion = function(pkt, tag){
+ var buf = [0, 0, 0, 0, NineP.packets.Rversion];
+ buf.push.apply(buf, tag);
+ var msize = NineP.GBIT32(pkt.slice(7));
+ this.maxbufsz = Math.min(msize, this.maxbufsz);
+ buf = buf.concat(NineP.PBIT32([], this.maxbufsz));
+ buf = buf.concat(NineP.mkwirestring("9P2000"));
+ NineP.PBIT32(buf, buf.length);
+ this.log.buf(buf);
+ this.socket.write(buf);
+}
+
+NineP.prototype.Tattach = function(pkt, tag){
+ var fid = NineP.GBIT32(pkt.slice(7));
+
+ if(this.fids[fid] != undefined){
+ this.Rerror(tag, "fid already in use");
+ }else{
+ this.fids[fid] = new NineP.Fid(fid, new NineP.Qid(0, 0, NineP.QTDIR));
+ this.Rattach(tag, fid);
+ }
+}
+
+NineP.prototype.Rattach = function(tag, fid){
+ var buf = [0, 0, 0, 0, NineP.packets.Rattach];
+ buf = buf.concat(tag);
+ buf = buf.concat(this.fids[fid].qid.toWireQid());
+ NineP.PBIT32(buf, buf.length);
+ this.log.buf(buf);
+ this.socket.write(buf);
+}
+
+
+NineP.prototype.Rerror = function(tag, msg){
+ var buf = [0,0,0,0, NineP.packets.Rerror];
+ buf.push.apply(buf, tag);
+ buf = buf.concat(NineP.mkwirestring(msg));
+ NineP.PBIT32(buf, buf.length);
+ this.log.buf(buf);
+ this.log.txt("error: " + msg);
+ this.socket.write(buf);
+}
+
+NineP.prototype.Tflush = function(pkt, tag){
+ this.Rerror(tag, "flush not implemented");
+}
+
+NineP.prototype.Twalk = function(pkt, tag){
+ pkt.splice(0, 7);
+ var oldfid = NineP.GBIT32(pkt.splice(0, 4));
+ var newfid = NineP.GBIT32(pkt.splice(0, 4));
+ var nwname = NineP.GBIT16(pkt.splice(0, 2));
+ var names = [];
+ var i;
+ for(i = 0; i < nwname; ++i){
+ names.push(NineP.getwirestring(pkt));
+ }
+ this.log.txt("twalk components: " + names + " (" + nwname + ")");
+
+ if(this.fids[oldfid] == undefined){
+ return this.Rerror(tag, "invalid fid");
+ }
+ if(this.fids[newfid] != undefined){
+ return this.Rerror(tag, "newfid in use");
+ }
+
+ var fakeqid = this.fids[oldfid].qid;
+ var interqids = [];
+
+ try{
+ for(i = 0; i < nwname; ++i){
+ fakeqid = this.local.walk1(fakeqid, names[i]);
+ interqids.push(fakeqid);
+ }
+ this.fids[newfid] = new NineP.Fid(newfid, fakeqid);
+ }catch(e){
+ if(i == 0){
+ return this.Rerror(tag, "could not walk");
+ }
+ }
+ this.Rwalk(tag, interqids.length, interqids);
+}
+
+NineP.prototype.Rwalk = function(tag, nwqid, qids){
+ var pkt = [0, 0, 0, 0, NineP.packets.Rwalk];
+ var i;
+
+ pkt = pkt.concat(tag);
+ pkt = pkt.concat(NineP.PBIT16([], nwqid));
+
+ for(i = 0; i < nwqid; ++i){
+ pkt = pkt.concat(qids[i].toWireQid());
+ }
+
+ NineP.PBIT32(pkt, pkt.length);
+ this.log.buf(pkt);
+ this.socket.write(pkt);
+}
+
+NineP.prototype.Topen = function(pkt, tag){
+ pkt.splice(0, 7);
+ var fid = NineP.GBIT32(pkt.splice(0, 4));
+ var mode = NineP.GBIT8(pkt.splice(0, 1));
+
+ if(this.fids[fid] == undefined){
+ return this.Rerror(tag, "invalid fid");
+ }
+
+ var qid = this.fids[fid].qid;
+
+ if(qid.type & NineP.QTDIR){
+ if(mode != NineP.OREAD){
+ return this.Rerror(tag, "cannot write to directory");
+ }
+ }
+
+ try{
+ this.local.open(this.fids[fid], mode);
+ }catch(e){
+ return this.Rerror(tag, e.toString());
+ }
+
+ this.fids[fid].mode = mode;
+
+ return this.Ropen(tag, fid);
+}
+
+NineP.prototype.Ropen = function(tag, fid){
+ var buf = [0, 0, 0, 0, NineP.packets.Ropen].concat(tag);
+
+ buf = buf.concat(this.fids[fid].qid.toWireQid());
+ buf = buf.concat(NineP.PBIT32([], 0));
+
+ NineP.PBIT32(buf, buf.length);
+ this.log.buf(buf);
+ this.socket.write(buf);
+}
+
+NineP.prototype.Tcreate = function(pkt, tag){
+ pkt.splice(0, 7);
+ var fid = NineP.GBIT32(pkt.splice(0, 4));
+ var name = NineP.getwirestring(pkt);
+ var perm = NineP.GBIT32(pkt.splice(0, 4));
+ var mode = NineP.GBIT8(pkt.splice(0, 1));
+
+ if(this.fids[fid] == undefined){
+ return this.Rerror(tag, "invalid fid");
+ }else if(!(this.fids[fid].qid.type & NineP.QTDIR)){
+ return this.Rerror(tag, "cannot create in non-directory");
+ }
+
+ try{
+ var qid = this.local.create(name, perm, mode);
+ this.fids[fid] = new NineP.Fid(fid, qid);
+ this.fids[fid].mode = mode;
+ }catch(e){
+ return this.Rerror(tag, e.toString());
+ }
+
+ return this.Rcreate(tag, qid);
+}
+
+NineP.prototype.Rcreate = function(tag, qid){
+ var buf = [0, 0, 0, 0, NineP.packets.Rcreate].concat(tag);
+ var buf = buf.concat(qid.toWireQid());
+ var buf = buf.concat(NineP.PBIT32([], 0));
+
+ NineP.PBIT32(buf, buf.length);
+
+ this.log.buf(buf);
+ this.socket.write(buf);
+}
+
+NineP.prototype.Tread = function(pkt, tag){
+ pkt.splice(0, 7);
+ var fid = NineP.GBIT32(pkt.splice(0, 4));
+ var offset = NineP.GBIT64(pkt.splice(0, 8));
+ var count = NineP.GBIT32(pkt.splice(0, 4));
+
+ if(this.fids[fid] == undefined){
+ return this.Rerror(tag, "invalid fid");
+ }
+ var mode = this.fids[fid].mode;
+ if(mode != NineP.OREAD && mode != NineP.ORDWR){
+ return this.Rerror(tag, "fid not opened for reading");
+ }
+
+ if(this.fids[fid].qid.type & NineP.QTDIR){
+ return this.Rread(tag, this.dirread(this.fids[fid], offset, count));
+ }else{
+ var that = this;
+ return this.local.read(this.fids[fid], offset, count, {
+ read: function(data){
+ return that.Rread.call(that, tag, data);
+ },
+ error: function(data){
+ return that.Rerror.call(that, tag, data);
+ }
+ });
+ }
+}
+
+NineP.prototype.dirread = function(fid, offset, count){
+ var dirindex;
+ var buf = [];
+ if(offset == 0){
+ dirindex = 0;
+ }else{
+ dirindex = fid.dirindex;
+ }
+
+ while(buf.length < count){
+ var tmpstat = this.local.dirent(fid.qid, dirindex);
+ if(tmpstat == undefined){
+ break;
+ }
+ var tmpbuf = tmpstat.toWireStat();
+ if((buf.length + tmpbuf.length) > count){
+ break;
+ }
+ buf = buf.concat(tmpbuf);
+ dirindex += 1;
+ }
+ fid.dirindex = dirindex;
+ return buf;
+}
+
+NineP.prototype.Rread = function(tag, data){
+ var buf = [0, 0, 0, 0, NineP.packets.Rread].concat(tag);
+
+ buf = buf.concat(NineP.PBIT32([], data.length)).concat(data);
+ NineP.PBIT32(buf, buf.length)
+
+ this.log.buf(buf);
+ this.socket.write(buf);
+}
+
+NineP.prototype.Twrite = function(pkt, tag){
+ pkt.splice(0, 7);
+ var fid = NineP.GBIT32(pkt.splice(0, 4));
+ var offset = NineP.GBIT64(pkt.splice(0, 8));
+ var count = NineP.GBIT32(pkt.splice(0, 4));
+ var data = pkt.splice(0, count);
+
+ if(this.fids[fid] == undefined){
+ return this.Rerror(tag, "invalid fid");
+ }
+ var mode = this.fids[fid].mode;
+ if(!(mode & NineP.OWRITE) && mode != NineP.ORDWR){
+ return this.Rerror(tag, "file not opened for writing");
+ }
+
+ try{
+ var bytes = this.local.write(this.fids[fid].qid, offset, data);
+ }catch(e){
+ return this.Rerror(tag, e.toString());
+ }
+
+ return this.Rwrite(tag, bytes);
+}
+
+NineP.prototype.Rwrite = function(tag, count){
+ var buf = [0, 0, 0, 0, NineP.packets.Rwrite].concat(tag);
+ buf = buf.concat(NineP.PBIT32([], count));
+
+ NineP.PBIT32(buf, buf.length);
+ this.log.buf(buf);
+ this.socket.write(buf);
+}
+
+
+NineP.prototype.Tclunk = function(pkt, tag){
+ pkt.splice(0, 7);
+ var fid = NineP.GBIT32(pkt);
+
+ if(this.fids[fid] == undefined){
+ return this.Rerror(tag, "fid not in use");
+ }
+
+ this.local.clunk(this.fids[fid]);
+
+ delete this.fids[fid];
+
+ return this.Rclunk(tag);
+}
+
+NineP.prototype.Rclunk = function(tag){
+ var buf = [0, 0, 0, 0, NineP.packets.Rclunk].concat(tag);
+
+ buf = NineP.PBIT32(buf, buf.length);
+ this.log.buf(buf);
+ this.socket.write(buf);
+}
+
+NineP.prototype.Tremove = function(pkt, tag){
+ pkt.splice(0, 7);
+ var fid = NineP.GBIT32(pkt);
+
+ if(this.fids[fid] == undefined){
+ return this.Rerror(tag, "fid not in use");
+ }
+
+ try{
+ this.local.remove(this.fids[fid].qid);
+ }catch(e){
+ return this.Rerror(tag, e.toString());
+ }
+
+ delete this.fids[fid];
+ return this.Rremove(tag);
+}
+
+NineP.prototype.Rremove = function(tag){
+ var buf = [0, 0, 0, 0, NineP.packets.Rremove].concat(tag);
+
+ NineP.PBIT32(buf, buf.length);
+ this.log.buf(buf);
+ this.socket.write(buf);
+}
+
+NineP.prototype.Tstat = function(pkt, tag){
+ pkt.splice(0, 7);
+ var fid = NineP.GBIT32(pkt);
+ if(this.fids[fid] == undefined){
+ return this.Rerror(tag, "invalid fid");
+ }
+
+ try{
+ return this.Rstat(tag, this.local.stat(this.fids[fid].qid.path));
+ }catch(e){
+ return this.Rerror(tag, e.toString());
+ }
+}
+
+NineP.prototype.Rstat = function(tag, stat){
+ var pkt = [0, 0, 0, 0, NineP.packets.Rstat].concat(tag);
+ pkt = pkt.concat(NineP.mkwirebuf(stat.toWireStat()));
+
+ NineP.PBIT32(pkt, pkt.length);
+ this.log.buf(pkt);
+ this.socket.write(pkt);
+}
--- /usr/web/9wd/js/lib9p/log.js Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/lib9p/log.js Mon Feb 17 21:45:41 2014
@@ -0,0 +1,11 @@
+NineP.Log = function(cons){
+ this.cons = cons;
+}
+
+NineP.Log.prototype.txt = function(s){
+ this.cons.log(s);
+}
+
+NineP.Log.prototype.buf = function(s){
+ this.cons.log(s);
+}
--- /usr/web/9wd/js/lib9p/qid.js Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/lib9p/qid.js Mon Feb 17 21:45:42 2014
@@ -0,0 +1,31 @@
+NineP.QTDIR = 0x80;
+NineP.QTAPPEND = 0x40;
+NineP.QTEXCL = 0x20;
+NineP.QTMOUNT = 0x10;
+NineP.QTAUTH = 0x08;
+NineP.QTTMP = 0x04;
+NineP.QTFILE = 0x00;
+
+NineP.DMDIR = 0x80000000;
+NineP.DMAPPEND = 0x40000000;
+NineP.DMEXCL = 0x20000000;
+NineP.DMTMP = 0x04000000;
+
+NineP.Qid = function(path, vers, type, callback){
+ this.path = path;
+ this.vers = vers;
+ this.type = type;
+ this.callback = callback;
+}
+
+NineP.Qid.prototype.toString = function(){
+ return "{path: " + this.path + "; vers: " + this.vers + "; type: " + this.type + ";}";
+}
+
+NineP.Qid.prototype.toWireQid = function(){
+ var buf = [];
+ buf = buf.concat(NineP.PBIT8([], this.type));
+ buf = buf.concat(NineP.PBIT32([], this.vers));
+ buf = buf.concat(NineP.PBIT64([], this.path));
+ return buf;
+}
--- /usr/web/9wd/js/lib9p/socket.js Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/lib9p/socket.js Mon Feb 17 21:45:43 2014
@@ -0,0 +1,17 @@
+function Socket(path, read){
+ this.sock = new WebSocket(path, "9p");
+ this.sock.binaryType = "arraybuffer";
+
+ this.write = function(data){
+ var u8 = new Uint8Array(data);
+ this.sock.send(u8.buffer);
+ }
+
+ this.sock.onmessage = function(e){
+ read(e.data);
+ }
+}
+/* I couldn't find a better way to get a Websocket to the same server. */
+Socket.wsurl = function(url){
+ return url.replace(/^http/, "ws").concat("9p");
+}
--- /usr/web/9wd/js/lib9p/stat.js Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/lib9p/stat.js Mon Feb 17 21:45:44 2014
@@ -0,0 +1,60 @@
+NineP.Stat = function(stat){
+ for(var elem in stat){
+ this[elem] = stat[elem];
+ }
+}
+
+NineP.Stat.fromWireStat = function(buf){
+ var size = NineP.GBIT16(buf.splice(0, 2));
+ buf.splice(0, 6); /* type, dev */
+
+ var proto = {
+ qid: NineP.Qid.fromWireQid(buf.slice(0, 13)),
+ mode: NineP.GBIT32(buf.slice(0, 4)),
+ atime: NineP.GBIT32(buf.slice(0, 4)),
+ mtime: NineP.GBIT32(buf.slice(0, 4)),
+ length: NineP.GBIT64(buf.slice(0, 8)),
+ name: NineP.getwirestring(buf),
+ uid: NineP.getwirestring(buf),
+ gid: NineP.getwirestring(buf),
+ muid: NineP.getwirestring(buf)
+ }
+
+ /* XXX This is wasteful! I just want to bless it with ``is-a''. */
+ return new NineP.Stat(proto);
+}
+
+NineP.Stat.prototype = {
+ qid: new NineP.Qid(0, 0, 0),
+ mode: 0,
+ atime: 0,
+ mtime: 0,
+ length: 0,
+ name: "",
+ uid: "",
+ gid: "",
+ muid: ""
+}
+
+NineP.Stat.prototype.toWireStat = function(){
+ var stat = [];
+ stat = stat.concat([0, 0]);
+ stat = stat.concat([0, 0, 0, 0]);
+ stat = stat.concat(this.qid.toWireQid());
+ stat = stat.concat(NineP.PBIT32([], this.mode));
+ stat = stat.concat(NineP.PBIT32([], this.atime));
+ stat = stat.concat(NineP.PBIT32([], this.mtime));
+ stat = stat.concat(NineP.PBIT64([], this.length));
+ stat = stat.concat(NineP.mkwirestring(this.name));
+ stat = stat.concat(NineP.mkwirestring(this.uid));
+ stat = stat.concat(NineP.mkwirestring(this.gid));
+ stat = stat.concat(NineP.mkwirestring(this.muid));
+
+ stat = NineP.PBIT16([], stat.length).concat(stat);
+
+ return stat;
+}
+
+NineP.Stat.prototype.toString = function(){
+ return JSON.stringify(this);
+}
--- /usr/web/9wd/js/lib9p/utf8.js Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/lib9p/utf8.js Mon Feb 17 21:45:46 2014
@@ -0,0 +1,76 @@
+String.prototype.toUTF8Array = function(){
+ var arr = [];
+
+ for(var i = 0; i < this.length; ++i){
+ var c = this.charCodeAt(i);
+
+ /* Convert from UTF-16 to Unicode code point. */
+ if((c & 0xDC00) == 0xD800){
+ var lead = c;
+ var tail = this.charCodeAt(++i);
+
+ c = 0x10000 | ((lead & 0x3FF) << 10) | ((tail & 0x3FF));
+ }
+
+ /* Convert from code point to UTF-8. */
+ if(c > 0x10000){ /* 4 bytes */
+ arr.push(0xF0 | ((c & 0x1C0000) >> 18));
+ arr.push(0x80 | ((c & 0x3F000) >> 12));
+ arr.push(0x80 | ((c & 0xFC0) >> 6));
+ arr.push(0x80 | ((c & 0x3F)));
+ }else if(c > 0x0800){ /* 3 bytes */
+ arr.push(0xE0 | ((c & 0xF000) >> 12));
+ arr.push(0x80 | ((c & 0xFC0) >> 6));
+ arr.push(0x80 | ((c & 0x3F)));
+ }else if(c > 0x0080){ /* 2 bytes */
+ arr.push(0xC0 | ((c & 0xFC0) >> 6));
+ arr.push(0x80 | ((c & 0x3F)));
+ }else{ /* 1 byte */
+ arr.push(0x00 | ((c & 0x7F)));
+ }
+ }
+
+ return arr;
+}
+
+String.fromUTF8Array = function(arr){
+ var units = [];
+
+ for(var i = 0; i < arr.length; ++i){
+ var codepoint = 0;
+
+ /* Convert from UTF-8 to Unicode codepoint. */
+ if((arr[i] & 0x80) == 0){ /* one byte */
+ codepoint = arr[i] & 0x7F;
+ }else if((arr[i] & 0xE0) == 0xC0){ /* two bytes */
+ codepoint = (
+ ((arr[i] & 0x1F) << 6) |
+ ((arr[++i] & 0x3F))
+ );
+ }else if((arr[i] & 0xF0) == 0xE0){ /* three bytes */
+ codepoint = (
+ ((arr[i] & 0x0F) << 12) |
+ ((arr[++i] & 0x3F) << 6) |
+ ((arr[++i] & 0x3F))
+ );
+ }else if((arr[i] & 0xF8) == 0xF0){ /* four bytes */
+ codepoint = (
+ ((arr[i] & 0x07) << 18) |
+ ((arr[++i] & 0x3F) << 12) |
+ ((arr[++i] & 0x3F) << 6) |
+ ((arr[++i] & 0x3F))
+ );
+ }else{
+ /* five- and six- byte UTF-8 are now illegal. */
+ }
+
+ /* Convert from Unicode codepoint to UTF-16. */
+ if(codepoint & 0x10000){
+ units.push(0xD800 | ((codepoint >> 10) & 0x3FF));
+ units.push(0xDC00 | (codepoint & 0x3FF));
+ }else{
+ units.push(codepoint);
+ }
+ }
+ return String.fromCharCode.apply(null, units);
+}
--- /usr/web/9wd/js/mouse.js Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/mouse.js Mon Feb 17 21:45:47 2014
@@ -0,0 +1,167 @@
+var mouse;
+
+function Mouse(cursorelem){
+ var State = function(position, buttons){
+ this.position = position;
+ this.buttons = buttons;
+ this.timestamp = Date.now() - basetime;
+ }
+ State.prototype.copy = function(){
+ return new State(this.position, this.buttons);
+ }
+ State.prototype.toWireFormat = function(){
+ var buf = "m".toUTF8Array();
+ buf = buf.concat(pad11(this.position.x));
+ buf = buf.concat(pad11(this.position.y));
+ buf = buf.concat(pad11(this.buttons));
+ buf = buf.concat(pad11(this.timestamp));
+ return buf;
+ }
+
+ this.states = {down: 1, up: 0};
+ this.state = new State({x: 0, y: 0}, 0);
+
+ this.usefkeys = false;
+ this.callbacks = [];
+ this.buf = [];
+
+ this.handlefkeys = function(e, state){
+ if(!this.usefkeys){
+ return true;
+ }
+ switch(e.keyCode){
+ case 112:
+ this.state.buttons = (this.state.buttons& ~1) | state<<0;
+ break;
+ case 113:
+ this.state.buttons = (this.state.buttons& ~2) | state<<1;
+ break;
+ case 114:
+ this.state.buttons = (this.state.buttons& ~4) | state<<2;
+ break;
+ default:
+ return true;
+ }
+ this.generatemovement(this.state);
+ return false;
+ }
+
+ this.handlebutton = function(e, state){
+ this.state.buttons = (this.state.buttons& ~(1< Draw9p.rootcanvas.width){
+ this.state.position.x = Draw9p.rootcanvas.width;
+ }
+ if(this.state.position.x < 0){
+ this.state.position.x = 0;
+ }
+ this.state.position.y +=
+ e.movementY ||
+ e.mozMovementY ||
+ e.webkitMovementY ||
+ 0;
+ if(this.state.position.y > Draw9p.rootcanvas.height){
+ this.state.position.y = Draw9p.rootcanvas.height;
+ }
+ if(this.state.position.y < 0){
+ this.state.position.y = 0;
+ }
+
+ this.cursor.goto(this.state.position);
+ this.generatemovement(this.state);
+ return false;
+}
+
+ this.generatemovement = function(state){
+ cons.write("m " + state.position.x + ", " + state.position.y +
+ " : " + state.buttons);
+ this.buf.push(this.state.copy());
+ this.flushcallbacks();
+ }
+
+ this.addcallback = function(callback){
+ this.callbacks.push(callback);
+ this.flushcallbacks();
+ }
+
+ this.flushcallbacks = function(){
+ while(this.callbacks.length > 0 && this.buf.length > 0){
+ this.callbacks.shift().read(this.buf.shift().toWireFormat());
+ }
+ }
+
+ this.cursor = {
+ arrow: [
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+
+ 0xFF, 0xFF, 0x80, 0x01, 0x80, 0x02, 0x80, 0x0C,
+ 0x80, 0x10, 0x80, 0x10, 0x80, 0x08, 0x80, 0x04,
+ 0x80, 0x02, 0x80, 0x01, 0x80, 0x02, 0x8C, 0x04,
+ 0x92, 0x08, 0x91, 0x10, 0xA0, 0xA0, 0xC0, 0x40,
+
+ 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFC, 0x7F, 0xF0,
+ 0x7F, 0xE0, 0x7F, 0xE0, 0x7F, 0xF0, 0x7F, 0xF8,
+ 0x7F, 0xFC, 0x7F, 0xFE, 0x7F, 0xFC, 0x73, 0xF8,
+ 0x61, 0xF0, 0x60, 0xE0, 0x40, 0x40, 0x00, 0x00,
+ ],
+ img: (function(elem){
+ var c = elem;
+ c.width = c.height = 16;
+ return {
+ canvas: c,
+ ctx: c.getContext("2d"),
+ clear: function(){
+ this.ctx.clearRect(0, 0, 16, 16);
+ },
+ fill: function(data, px){
+ var id = this.ctx.getImageData(0, 0, 16, 16);
+ var cp = 0; /* canvas pointer */
+ for(var i=0; i<32; ++i){
+ for(var b=7; b>=0; --b){
+ var p = (data[i]>>b) & 1;
+ if(p){
+ id.data[cp++] = px;
+ id.data[cp++] = px;
+ id.data[cp++] = px;
+ id.data[cp++] = 0xFF;
+ }else{
+ cp += 4;
+ }
+ }
+ }
+ this.ctx.putImageData(id, 0, 0);
+ }
+ };
+ })(cursorelem),
+ offset: {x: 0, y: 0},
+ write: function(data){
+ if(data.length != 72){
+ data = this.arrow;
+ }
+ this.img.clear();
+ var ai = new ArrayIterator(data);
+ this.offset = ai.getPoint();
+ this.img.fill(ai.getBytes(32), 0xFF);
+ this.img.fill(ai.getBytes(32), 0x00);
+ return data.length;
+ },
+ goto: function(pos){
+ var x = pos.x - this.offset.x;
+ var y = pos.y - this.offset.y;
+ this.img.canvas.style.left = x + "px";
+ this.img.canvas.style.top = y + "px";
+ }
+ }
+ this.cursor.write([]);
+
+}
--- /usr/web/9wd/js/settings.js Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/settings.js Mon Feb 17 21:45:48 2014
@@ -0,0 +1,31 @@
+function Settings(){
+
+ this.settings = ["mousefkeys", "showcons"];
+
+ this.addsetting = function(setting){
+ setevent(elem(setting), "click", function(){
+ return settings.set(setting, this.checked? true: false);
+ });
+ }
+
+ this.set = function(name, value){
+
+ switch(name){
+ case "mousefkeys":
+ mouse.usefkeys = value;
+ return false;
+ case "showcons":
+ cons.showhide(value);
+ return true;
+ default:
+ return true;
+ }
+ localStorage.setItem(name, value);
+
+ }
+
+ for(var setting in this.settings){
+ this.set(this.settings[setting], localStorage.getItem(this.settings[setting]) == "true"? true: false);
+ this.addsetting(this.settings[setting]);
+ }
+}
--- /usr/web/9wd/js/test Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/test Mon Feb 17 21:45:53 2014
@@ -0,0 +1,99 @@
+Testdraw = {};
+
+Testdraw.line = function(){
+ var root = Draw9p.RootImage();
+
+ var src = new Draw9p.Image(0, "r8g8b8", 1,
+ {min: {x: 0, y: 0}, max: {x: 1, y: 1}},
+ {min: {x: 0, y: 0}, max: {x: 1, y: 1}},
+ 0x00FF00FF);
+
+ var img = new Draw9p.Image(0, "r8g8b8", 0,
+ {min: {x: 0, y: 0}, max: {x: 100, y: 100}},
+ {min: {x: 0, y: 0}, max: {x: 100, y: 100}},
+ 0xFF00FFFF);
+
+ Memdraw.line(img, {x: 45, y: 15}, {x: 0, y: 100}, 0, 0, 10, src, {x: 0, y: 0}, 0);
+
+ Memdraw.line(root, {x: 15, y: 15}, {x: 100, y: 100}, 0, 0, 10, img, {x: 0, y: 0}, 0);
+
+ var cap = Memdraw.ARROW(25, 25, 10);
+ Memdraw.line(root, {x: 100, y: 15}, {x: 200, y: 200}, cap, cap, 10, src, {x:0,y:0}, 0);
+ Memdraw.line(root, {x: 200, y: 15}, {x: 400, y: 200},
+ Memdraw.End.disc, Memdraw.End.disc, 15, src, {x:0, y:0}, 0);
+}
+
+Testdraw.fillpoly = function(){
+ var root = Draw9p.RootImage();
+
+ var src = new Draw9p.Image(0, "r8g8b8", 1,
+ {min: {x: 0, y: 0}, max: {x: 1, y: 1}},
+ {min: {x: 0, y: 0}, max: {x: 1, y: 1}},
+ 0x00FF00FF);
+
+ var pts = [
+ {x: 20, y: 20},
+ {x: 20, y: 80},
+ {x: 40, y: 40},
+ {x: 20, y: 20}
+ ]
+
+ Memdraw.fillpoly(root, pts, 0, src, {x: 0, y: 0}, 0);
+}
+
+Testdraw.poly = function(){
+ var root = Draw9p.RootImage();
+
+ var src = new Draw9p.Image(0, "r8g8b8", 1,
+ {min: {x: 0, y: 0}, max: {x: 1, y: 1}},
+ {min: {x: 0, y: 0}, max: {x: 1, y: 1}},
+ 0x00FF00FF);
+
+ var pts = [
+ {x: 20, y: 20},
+ {x: 20, y: 200},
+ {x: 200, y: 200},
+ {x: 200, y: 20},
+ {x: 20, y: 20}
+ ];
+
+ Memdraw.poly(root, pts, 0, 0, 15, src, {x:0, y:0}, 0);
+}
+
+/* XXX Doesn't work, and would fail if it did. */
+Testdraw.mask = function(){
+ var root = Draw9p.RootImage();
+
+ var alpha = new Draw9p.Image(0, "r8g8b8a8", 1,
+ {min: {x: 0, y: 0}, max: {x: 1, y: 1}},
+ {min: {x: 0, y: 0}, max: {x: 1, y: 1}},
+ 0x000000FF);
+
+ var red = new Draw9p.Image(0, "r8g8b8a8", 1,
+ {min: {x: 0, y: 0}, max: {x: 1, y: 1}},
+ {min: {x: 0, y: 0}, max: {x: 1, y: 1}},
+ 0xFF0000FF);
+
+ var mask = new Draw9p.Image(0, "r8g8b8a8", 0,
+ {min: {x: 0, y: 0}, max: {x: 100, y: 100}},
+ {min: {x: 0, y: 0}, max: {x: 100, y: 100}},
+ 0x000000FF);
+
+ Memdraw.line(mask, {x: 10, y: 10}, {x: 90, y: 90},
+ 0, 0, 10, alpha, {x: 0, y: 0}, 0);
+
+ Memdraw.drawmasked(root, mask.clipr, red, {x: 0, y: 0},
+ mask, {x: 0, y: 0}, 0);
+}
+
+Testdraw.ellipse = function(){
+ var root = Draw9p.RootImage();
+
+ var src = new Draw9p.Image(0, "r8g8b8", 1,
+ {min: {x: 0, y: 0}, max: {x: 1, y: 1}},
+ {min: {x: 0, y: 0}, max: {x: 1, y: 1}},
+ 0x44AA77FF);
+
+ Memdraw.fillellipse(root, {x: 100, y: 100},
+ 75, 25, 0, 2*Math.PI, src, {x: 0, y: 0});
+}
--- /usr/web/9wd/js/test/draw.js Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/test/draw.js Mon Feb 17 21:45:55 2014
@@ -0,0 +1,99 @@
+Testdraw = {};
+
+Testdraw.line = function(){
+ var root = Draw9p.RootImage();
+
+ var src = new Draw9p.Image(0, "r8g8b8", 1,
+ {min: {x: 0, y: 0}, max: {x: 1, y: 1}},
+ {min: {x: 0, y: 0}, max: {x: 1, y: 1}},
+ 0x00FF00FF);
+
+ var img = new Draw9p.Image(0, "r8g8b8", 0,
+ {min: {x: 0, y: 0}, max: {x: 100, y: 100}},
+ {min: {x: 0, y: 0}, max: {x: 100, y: 100}},
+ 0xFF00FFFF);
+
+ Memdraw.line(img, {x: 45, y: 15}, {x: 0, y: 100}, 0, 0, 10, src, {x: 0, y: 0}, 0);
+
+ Memdraw.line(root, {x: 15, y: 15}, {x: 100, y: 100}, 0, 0, 10, img, {x: 0, y: 0}, 0);
+
+ var cap = Memdraw.ARROW(25, 25, 10);
+ Memdraw.line(root, {x: 100, y: 15}, {x: 200, y: 200}, cap, cap, 10, src, {x:0,y:0}, 0);
+ Memdraw.line(root, {x: 200, y: 15}, {x: 400, y: 200},
+ Memdraw.End.disc, Memdraw.End.disc, 15, src, {x:0, y:0}, 0);
+}
+
+Testdraw.fillpoly = function(){
+ var root = Draw9p.RootImage();
+
+ var src = new Draw9p.Image(0, "r8g8b8", 1,
+ {min: {x: 0, y: 0}, max: {x: 1, y: 1}},
+ {min: {x: 0, y: 0}, max: {x: 1, y: 1}},
+ 0x00FF00FF);
+
+ var pts = [
+ {x: 20, y: 20},
+ {x: 20, y: 80},
+ {x: 40, y: 40},
+ {x: 20, y: 20}
+ ]
+
+ Memdraw.fillpoly(root, pts, 0, src, {x: 0, y: 0}, 0);
+}
+
+Testdraw.poly = function(){
+ var root = Draw9p.RootImage();
+
+ var src = new Draw9p.Image(0, "r8g8b8", 1,
+ {min: {x: 0, y: 0}, max: {x: 1, y: 1}},
+ {min: {x: 0, y: 0}, max: {x: 1, y: 1}},
+ 0x00FF00FF);
+
+ var pts = [
+ {x: 20, y: 20},
+ {x: 20, y: 200},
+ {x: 200, y: 200},
+ {x: 200, y: 20},
+ {x: 20, y: 20}
+ ];
+
+ Memdraw.poly(root, pts, 0, 0, 15, src, {x:0, y:0}, 0);
+}
+
+/* XXX Doesn't work, and would fail if it did. */
+Testdraw.mask = function(){
+ var root = Draw9p.RootImage();
+
+ var alpha = new Draw9p.Image(0, "r8g8b8a8", 1,
+ {min: {x: 0, y: 0}, max: {x: 1, y: 1}},
+ {min: {x: 0, y: 0}, max: {x: 1, y: 1}},
+ 0x000000FF);
+
+ var red = new Draw9p.Image(0, "r8g8b8a8", 1,
+ {min: {x: 0, y: 0}, max: {x: 1, y: 1}},
+ {min: {x: 0, y: 0}, max: {x: 1, y: 1}},
+ 0xFF0000FF);
+
+ var mask = new Draw9p.Image(0, "r8g8b8a8", 0,
+ {min: {x: 0, y: 0}, max: {x: 100, y: 100}},
+ {min: {x: 0, y: 0}, max: {x: 100, y: 100}},
+ 0x000000FF);
+
+ Memdraw.line(mask, {x: 10, y: 10}, {x: 90, y: 90},
+ 0, 0, 10, alpha, {x: 0, y: 0}, 0);
+
+ Memdraw.drawmasked(root, mask.clipr, red, {x: 0, y: 0},
+ mask, {x: 0, y: 0}, 0);
+}
+
+Testdraw.ellipse = function(){
+ var root = Draw9p.RootImage();
+
+ var src = new Draw9p.Image(0, "r8g8b8", 1,
+ {min: {x: 0, y: 0}, max: {x: 1, y: 1}},
+ {min: {x: 0, y: 0}, max: {x: 1, y: 1}},
+ 0x44AA77FF);
+
+ Memdraw.fillellipse(root, {x: 100, y: 100},
+ 75, 25, 0, 2*Math.PI, src, {x: 0, y: 0});
+}
--- /usr/web/9wd/js/util Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/util Mon Feb 17 21:45:59 2014
@@ -0,0 +1,84 @@
+function ArrayIterator(array){
+ this.array = array;
+ this.index = 0;
+}
+
+ArrayIterator.prototype.getChar = function(){
+ if(this.array.length < this.index + 1){
+ throw("array too short");
+ }
+ return ((this.array[this.index++] & 0xFF) << 0);
+}
+
+ArrayIterator.prototype.getShort = function(){
+ if(this.array.length < this.index + 2){
+ throw("array too short");
+ }
+ return (
+ ((this.array[this.index++] & 0xFF) << 0) |
+ ((this.array[this.index++] & 0xFF) << 8)
+ );
+}
+
+ArrayIterator.prototype.getLong = function(){
+ if(this.array.length < this.index + 4){
+ throw("array too short");
+ }
+ return (
+ ((this.array[this.index++] & 0xFF) << 0) |
+ ((this.array[this.index++] & 0xFF) << 8) |
+ ((this.array[this.index++] & 0xFF) << 16) |
+ ((this.array[this.index++] & 0xFF) << 24)
+ );
+}
+
+ArrayIterator.prototype.getBytes = function(bytes){
+ if(this.array.length < this.index + bytes){
+ throw("array too short");
+ }
+ var begin = this.index;
+ this.index += bytes;
+ return this.array.slice(begin, this.index);
+}
+
+ArrayIterator.prototype.peekBytes = function(bytes){
+ if(this.array.length < this.index + bytes){
+ throw("array too short");
+ }
+ var begin = this.index;
+ var end = begin + bytes;
+ return this.array.slice(begin, end);
+}
+
+ArrayIterator.prototype.advanceBytes = function(bytes){
+ if(this.array.length < this.index + bytes){
+ throw("array too short");
+ }
+ this.index += bytes;
+}
+
+ArrayIterator.prototype.getRemainingBytes = function(){
+ return this.getBytes(this.array.length - this.index);
+}
+
+ArrayIterator.prototype.peekRemainingBytes = function(){
+ return this.peekBytes(this.array.length - this.index);
+}
+
+ArrayIterator.prototype.hasRemainingBytes = function(){
+ return this.index < this.array.length;
+}
+
+ArrayIterator.prototype.getPoint = function(){
+ return {
+ x: this.getLong(),
+ y: this.getLong()
+ }
+}
+
+ArrayIterator.prototype.getRect = function(){
+ return {
+ min: this.getPoint(),
+ max: this.getPoint()
+ }
+}
--- /usr/web/9wd/js/util Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/util Mon Feb 17 21:46:00 2014
@@ -0,0 +1,21 @@
+pad11 = function(x){
+ var buf = [];
+ var s = String(x);
+ var i;
+
+ //do{
+ // buf[i] = Math.floor(x % 10);
+ // i += 1;
+ //}while(x = Math.floor(x / 10));
+
+ for(i = 0; i < 11 - s.length; ++i){
+ buf[i] = " ".charCodeAt(0);
+ }
+
+ for(i; i < 11; ++i){
+ buf[i] = s.charCodeAt(i - (11 - s.length));
+ }
+
+ buf[11] = " ".charCodeAt(0);
+ return buf;
+}
--- /usr/web/9wd/js/util/array.js Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/util/array.js Mon Feb 17 21:46:02 2014
@@ -0,0 +1,84 @@
+function ArrayIterator(array){
+ this.array = array;
+ this.index = 0;
+}
+
+ArrayIterator.prototype.getChar = function(){
+ if(this.array.length < this.index + 1){
+ throw("array too short");
+ }
+ return ((this.array[this.index++] & 0xFF) << 0);
+}
+
+ArrayIterator.prototype.getShort = function(){
+ if(this.array.length < this.index + 2){
+ throw("array too short");
+ }
+ return (
+ ((this.array[this.index++] & 0xFF) << 0) |
+ ((this.array[this.index++] & 0xFF) << 8)
+ );
+}
+
+ArrayIterator.prototype.getLong = function(){
+ if(this.array.length < this.index + 4){
+ throw("array too short");
+ }
+ return (
+ ((this.array[this.index++] & 0xFF) << 0) |
+ ((this.array[this.index++] & 0xFF) << 8) |
+ ((this.array[this.index++] & 0xFF) << 16) |
+ ((this.array[this.index++] & 0xFF) << 24)
+ );
+}
+
+ArrayIterator.prototype.getBytes = function(bytes){
+ if(this.array.length < this.index + bytes){
+ throw("array too short");
+ }
+ var begin = this.index;
+ this.index += bytes;
+ return this.array.slice(begin, this.index);
+}
+
+ArrayIterator.prototype.peekBytes = function(bytes){
+ if(this.array.length < this.index + bytes){
+ throw("array too short");
+ }
+ var begin = this.index;
+ var end = begin + bytes;
+ return this.array.slice(begin, end);
+}
+
+ArrayIterator.prototype.advanceBytes = function(bytes){
+ if(this.array.length < this.index + bytes){
+ throw("array too short");
+ }
+ this.index += bytes;
+}
+
+ArrayIterator.prototype.getRemainingBytes = function(){
+ return this.getBytes(this.array.length - this.index);
+}
+
+ArrayIterator.prototype.peekRemainingBytes = function(){
+ return this.peekBytes(this.array.length - this.index);
+}
+
+ArrayIterator.prototype.hasRemainingBytes = function(){
+ return this.index < this.array.length;
+}
+
+ArrayIterator.prototype.getPoint = function(){
+ return {
+ x: this.getLong(),
+ y: this.getLong()
+ }
+}
+
+ArrayIterator.prototype.getRect = function(){
+ return {
+ min: this.getPoint(),
+ max: this.getPoint()
+ }
+}
--- /usr/web/9wd/js/util/pad.js Thu Jan 1 00:00:00 1970
+++ /usr/web/9wd/js/util/pad.js Mon Feb 17 21:46:03 2014
@@ -0,0 +1,21 @@
+pad11 = function(x){
+ var buf = [];
+ var s = String(x);
+ var i;
+
+ //do{
+ // buf[i] = Math.floor(x % 10);
+ // i += 1;
+ //}while(x = Math.floor(x / 10));
+
+ for(i = 0; i < 11 - s.length; ++i){
+ buf[i] = " ".charCodeAt(0);
+ }
+
+ for(i; i < 11; ++i){
+ buf[i] = s.charCodeAt(i - (11 - s.length));
+ }
+
+ buf[11] = " ".charCodeAt(0);
+ return buf;
+}