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

+
+ cursor + /dev/draw +
+
+
+ + +
+ + --- /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

+
+ cursor + /dev/draw +
+
+
+ + +
+ + --- /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; +}