1implement Wmexport; 2 3# 4# Copyright © 2003 Vita Nuova Holdings Limited. 5# 6 7include "sys.m"; 8 sys: Sys; 9include "draw.m"; 10 draw: Draw; 11 Wmcontext, Image: import draw; 12include "wmlib.m"; 13 wmlib: Wmlib; 14include "styx.m"; 15 styx: Styx; 16 Rmsg, Tmsg: import styx; 17include "styxservers.m"; 18 styxservers: Styxservers; 19 Styxserver, Fid, Navigator, Navop: import styxservers; 20 Enotdir, Enotfound: import Styxservers; 21 22Wmexport: module { 23 init: fn(nil: ref Draw->Context, argv: list of string); 24}; 25 26# filesystem looks like: 27# clone 28# 1 29# wmctl 30# keyboard 31# pointer 32# winname 33 34badmodule(p: string) 35{ 36 sys->fprint(sys->fildes(2), "wmexport: cannot load %s: %r\n", p); 37 raise "fail:bad module"; 38} 39 40user := "me"; 41qidseq := 1; 42imgseq := 0; 43 44pidregister: chan of (int, int); 45flush: chan of (int, int, chan of int); 46 47makeconn: chan of chan of (ref Conn, string); 48delconn: chan of ref Conn; 49reqpool: list of chan of (ref Tmsg, ref Conn, ref Fid); 50reqidle: int; 51reqdone: chan of chan of (ref Tmsg, ref Conn, ref Fid); 52 53srv: ref Styxserver; 54ctxt: ref Draw->Context; 55 56conns: array of ref Conn; 57nconns := 0; 58 59Qerror, Qroot, Qdir, Qclone, Qwmctl, Qptr, Qkbd, Qwinname: con iota; 60Shift: con 4; 61Mask: con 16rf; 62 63Maxreqidle: con 3; 64Maxreplyidle: con 3; 65 66Conn: adt { 67 wm: ref Wmcontext; 68 iname: string; # name of image 69 n: int; 70 nreads: int; 71}; 72 73# initial connection provides base-name (fid?) for images. 74# full name could be: 75# window.fid.tag 76 77init(drawctxt: ref Draw->Context, nil: list of string) 78{ 79 sys = load Sys Sys->PATH; 80 ctxt = drawctxt; 81 if(ctxt == nil || ctxt.wm == nil){ 82 sys->fprint(sys->fildes(2), "wmexport: no window manager context\n"); 83 raise "fail:no wm"; 84 } 85 draw = load Draw Draw->PATH; 86 styx = load Styx Styx->PATH; 87 if (styx == nil) 88 badmodule(Styx->PATH); 89 styx->init(); 90 styxservers = load Styxservers Styxservers->PATH; 91 if (styxservers == nil) 92 badmodule(Styxservers->PATH); 93 styxservers->init(styx); 94 95 wmlib = load Wmlib Wmlib->PATH; 96 if(wmlib == nil) 97 badmodule(Wmlib->PATH); 98 wmlib->init(); 99 100 sys->pctl(Sys->FORKNS|Sys->NEWPGRP, nil); # fork pgrp? 101 102 ctxt = drawctxt; 103 navops := chan of ref Navop; 104 spawn navigator(navops); 105 tchan: chan of ref Tmsg; 106 (tchan, srv) = Styxserver.new(sys->fildes(0), Navigator.new(navops), big Qroot); 107 srv.replychan = chan of ref Styx->Rmsg; 108 spawn replymarshal(srv.replychan); 109 spawn serve(tchan, navops); 110} 111 112serve(tchan: chan of ref Tmsg, navops: chan of ref Navop) 113{ 114 pidregister = chan of (int, int); 115 makeconn = chan of chan of (ref Conn, string); 116 delconn = chan of ref Conn; 117 flush = chan of (int, int, chan of int); 118 reqdone = chan of chan of (ref Tmsg, ref Conn, ref Fid); 119 spawn flushproc(flush); 120 121Serve: 122 for(;;)alt{ 123 gm := <-tchan => 124 if(gm == nil) 125 break Serve; 126 pick m := gm { 127 Readerror => 128 sys->fprint(sys->fildes(2), "wmexport: fatal read error: %s\n", m.error); 129 break Serve; 130 Open => 131 (fid, mode, d, err) := srv.canopen(m); 132 if(err != nil) 133 srv.reply(ref Rmsg.Error(m.tag, err)); 134 else if(fid.qtype & Sys->QTDIR) 135 srv.default(m); 136 else 137 request(ctxt, m, fid); 138 Read => 139 (fid, err) := srv.canread(m); 140 if(err != nil) 141 srv.reply(ref Rmsg.Error(m.tag, err)); 142 else if(fid.qtype & Sys->QTDIR) 143 srv.read(m); 144 else 145 request(ctxt, m, fid); 146 Write => 147 (fid, err) := srv.canwrite(m); 148 if(err != nil) 149 srv.reply(ref Rmsg.Error(m.tag, err)); 150 else 151 request(ctxt, m, fid); 152 Flush => 153 done := chan of int; 154 flush <-= (m.tag, m.oldtag, done); 155 <-done; 156 Clunk => 157 request(ctxt, m, srv.clunk(m)); 158 * => 159 srv.default(gm); 160 } 161 rc := <-makeconn => 162 if(nconns >= len conns) 163 conns = (array[len conns + 5] of ref Conn)[0:] = conns; 164 wm := wmlib->connect(ctxt); 165 if(wm == nil) # XXX this can't happen - give wmlib->connect an error return 166 rc <-= (nil, "cannot connect"); 167 else{ 168 c := ref Conn(wm, nil, qidseq++, 0); 169 conns[nconns++] = c; 170 rc <-= (c, nil); 171 } 172 c := <-delconn => 173 for(i := 0; i < nconns; i++) 174 if(conns[i] == c) 175 break; 176 nconns--; 177 if(i < nconns) 178 conns[i] = conns[nconns]; 179 conns[nconns] = nil; 180 reqpool = <-reqdone :: reqpool => 181 if(reqidle++ > Maxreqidle){ 182 hd reqpool <-= (nil, nil, nil); 183 reqpool = tl reqpool; 184 reqidle--; 185 } 186 } 187 navops <-= nil; 188 kill(sys->pctl(0, nil), "killgrp"); 189} 190 191nameimage(nil: ref Conn, img: ref Draw->Image): string 192{ 193 if(img.iname != nil) 194 return img.iname; 195 for(i := 0; i < 100; i++){ 196 s := "inferno." + string imgseq++; 197 if(img.name(s, 1) > 0) 198 return s; 199 if(img.iname != nil) 200 return img.iname; # a competing process has done it for us. 201 } 202sys->print("wmexport: no image names: %r\n"); 203raise "panic"; 204} 205 206request(nil: ref Draw->Context, m: ref Styx->Tmsg, fid: ref Fid) 207{ 208 n := int fid.path >> Shift; 209 conn: ref Conn; 210 for(i := 0; i < nconns; i++){ 211 if(conns[i].n == n){ 212 conn = conns[i]; 213 break; 214 } 215 } 216 c: chan of (ref Tmsg, ref Conn, ref Fid); 217 if(reqpool == nil){ 218 c = chan of (ref Tmsg, ref Conn, ref Fid); 219 spawn requestproc(c); 220 }else{ 221 (c, reqpool) = (hd reqpool, tl reqpool); 222 reqidle--; 223 } 224 c <-= (m, conn, fid); 225} 226 227requestproc(req: chan of (ref Tmsg, ref Conn, ref Fid)) 228{ 229 pid := sys->pctl(0, nil); 230 for(;;){ 231 (gm, c, fid) := <-req; 232 if(gm == nil) 233 break; 234 pidregister <-= (pid, gm.tag); 235 path := int fid.path; 236 pick m := gm { 237 Read => 238 if(c == nil) 239 srv.replydirect(ref Rmsg.Error(m.tag, "connection is dead")); 240 case path & Mask { 241 Qwmctl => 242 # first read gets number of connection. 243 m.offset = big 0; 244 if(c.nreads++ == 0) 245 srv.replydirect(styxservers->readstr(m, string c.n)); 246 else 247 srv.replydirect(styxservers->readstr(m, <-c.wm.ctl)); 248 Qptr => 249 m.offset = big 0; 250 p := <-c.wm.ptr; 251 srv.replydirect(styxservers->readbytes(m, 252 sys->aprint("m%11d %11d %11d %11ud ", p.xy.x, p.xy.y, p.buttons, p.msec))); 253 Qkbd => 254 m.offset = big 0; 255 s := ""; 256 s[0] = <-c.wm.kbd; 257 srv.replydirect(styxservers->readstr(m, s)); 258 Qwinname => 259 m.offset = big 0; 260 srv.replydirect(styxservers->readstr(m, c.iname)); 261 * => 262 srv.replydirect(ref Rmsg.Error(m.tag, "what was i thinking1?")); 263 } 264 Write => 265 if(c == nil) 266 srv.replydirect(ref Rmsg.Error(m.tag, "connection is dead")); 267 case path & Mask { 268 Qwmctl => 269 if(sys->write(c.wm.connfd, m.data, len m.data) == -1){ 270 srv.replydirect(ref Rmsg.Error(m.tag, sys->sprint("%r"))); 271 break; 272 } 273 if(len m.data > 0 && int m.data[0] == '!'){ 274 i := <-c.wm.images; 275 if(i == nil) 276 i = <-c.wm.images; 277 c.iname = nameimage(c, i); 278 } 279 srv.replydirect(ref Rmsg.Write(m.tag, len m.data)); 280 * => 281 srv.replydirect(ref Rmsg.Error(m.tag, "what was i thinking2?")); 282 } 283 Open => 284 if(c == nil && path != Qclone) 285 srv.replydirect(ref Rmsg.Error(m.tag, "connection is dead")); 286 err: string; 287 q := qid(path); 288 case path & Mask { 289 Qclone => 290 cch := chan of (ref Conn, string); 291 makeconn <-= cch; 292 (c, err) = <-cch; 293 if(c != nil) 294 q = qid(Qwmctl | (c.n << Shift)); 295 Qptr => 296 if(sys->fprint(c.wm.connfd, "start ptr") == -1) 297 err = sys->sprint("%r"); 298 Qkbd => 299 if(sys->fprint(c.wm.connfd, "start kbd") == -1) 300 err = sys->sprint("%r"); 301 Qwmctl => 302 ; 303 Qwinname => 304 ; 305 * => 306 err = "what was i thinking3?"; 307 } 308 if(err != nil) 309 srv.replydirect(ref Rmsg.Error(m.tag, err)); 310 else{ 311 srv.replydirect(ref Rmsg.Open(m.tag, q, 0)); 312 fid.open(m.mode, q); 313 } 314 Clunk => 315 case path & Mask { 316 Qwmctl => 317 if(c != nil) 318 delconn <-= c; 319 } 320 * => 321 srv.replydirect(ref Rmsg.Error(gm.tag, "oh dear")); 322 } 323 pidregister <-= (pid, -1); 324 reqdone <-= req; 325 } 326} 327 328qid(path: int): Sys->Qid 329{ 330 return dirgen(path).t0.qid; 331} 332 333replyproc(c: chan of ref Rmsg, replydone: chan of chan of ref Rmsg) 334{ 335 # hmm, this could still send a reply out-of-order with a flush 336 while((m := <-c) != nil){ 337 srv.replydirect(m); 338 replydone <-= c; 339 } 340} 341 342# deal with reply messages coming from styxservers. 343replymarshal(c: chan of ref Styx->Rmsg) 344{ 345 replypool: list of chan of ref Rmsg; 346 n := 0; 347 replydone := chan of chan of ref Rmsg; 348 for(;;) alt{ 349 m := <-c => 350 c: chan of ref Rmsg; 351 if(replypool == nil){ 352 c = chan of ref Rmsg; 353 spawn replyproc(c, replydone); 354 }else{ 355 (c, replypool) = (hd replypool, tl replypool); 356 n--; 357 } 358 c <-= m; 359 replypool = <-replydone :: replypool => 360 if(++n > Maxreplyidle){ 361 hd replypool <-= nil; 362 replypool = tl replypool; 363 n--; 364 } 365 } 366} 367 368navigator(navops: chan of ref Navop) 369{ 370 while((m := <-navops) != nil){ 371 path := int m.path; 372 pick n := m { 373 Stat => 374 n.reply <-= dirgen(int n.path); 375 Walk => 376 name := n.name; 377 case path & Mask { 378 Qdir => 379 dp := path & ~Mask; 380 case name { 381 ".." => 382 path = Qroot; 383 "wmctl" => 384 path = Qwmctl | dp; 385 "pointer" => 386 path = Qptr | dp; 387 "keyboard" => 388 path = Qkbd | dp; 389 "winname" => 390 path = Qwinname | dp; 391 * => 392 path = Qerror; 393 } 394 Qroot => 395 case name{ 396 "clone" => 397 path = Qclone; 398 * => 399 x := int name; 400 path = Qerror; 401 if(string x == name){ 402 for(i := 0; i < nconns; i++) 403 if(conns[i].n == x){ 404 path = (x << Shift) | Qdir; 405 break; 406 } 407 } 408 } 409 } 410 n.reply <-= dirgen(path); 411 Readdir => 412 err := ""; 413 d: array of int; 414 case path & Mask { 415 Qdir => 416 d = array[] of {Qwmctl, Qptr, Qkbd, Qwinname}; 417 for(i := 0; i < len d; i++) 418 d[i] |= path & ~Mask; 419 Qroot => 420 d = array[nconns + 1] of int; 421 d[0] = Qclone; 422 for(i := 0; i < nconns; i++) 423 d[i + 1] = (conns[i].n<<Shift) | Qdir; 424 } 425 if(d == nil){ 426 n.reply <-= (nil, Enotdir); 427 break; 428 } 429 for (i := n.offset; i < len d; i++) 430 n.reply <-= dirgen(d[i]); 431 n.reply <-= (nil, nil); 432 } 433 } 434} 435 436dirgen(path: int): (ref Sys->Dir, string) 437{ 438 name: string; 439 perm: int; 440 case path & Mask { 441 Qroot => 442 name = "."; 443 perm = 8r555|Sys->DMDIR; 444 Qdir => 445 name = string (path >> Shift); 446 perm = 8r555|Sys->DMDIR; 447 Qclone => 448 name = "clone"; 449 perm = 8r666; 450 Qwmctl => 451 name = "wmctl"; 452 perm = 8r666; 453 Qptr => 454 name = "pointer"; 455 perm = 8r444; 456 Qkbd => 457 name = "keyboard"; 458 perm = 8r444; 459 Qwinname => 460 name = "winname"; 461 perm = 8r444; 462 * => 463 return (nil, Enotfound); 464 } 465 return (dir(path, name, perm), nil); 466} 467 468dir(path: int, name: string, perm: int): ref Sys->Dir 469{ 470 d := ref sys->zerodir; 471 d.qid.path = big path; 472 if(perm & Sys->DMDIR) 473 d.qid.qtype = Sys->QTDIR; 474 d.mode = perm; 475 d.name = name; 476 d.uid = user; 477 d.gid = user; 478 return d; 479} 480 481flushproc(flush: chan of (int, int, chan of int)) 482{ 483 a: array of (int, int); # (pid, tag) 484 n := 0; 485 for(;;)alt{ 486 (pid, tag) := <-pidregister => 487 if(tag == -1){ 488 for(i := 0; i < n; i++) 489 if(a[i].t0 == pid) 490 break; 491 n--; 492 if(i < n) 493 a[i] = a[n]; 494 }else{ 495 if(n >= len a){ 496 na := array[n + 5] of (int, int); 497 na[0:] = a; 498 a = na; 499 } 500 a[n++] = (pid, tag); 501 } 502 (tag, oldtag, done) := <-flush => 503 for(i := 0; i < n; i++) 504 if(a[i].t1 == oldtag){ 505 spawn doflush(tag, a[i].t0, done); 506 break; 507 } 508 if(i == n) 509 spawn doflush(tag, -1, done); 510 } 511} 512 513doflush(tag: int, pid: int, done: chan of int) 514{ 515 if(pid != -1){ 516 kill(pid, "kill"); 517 pidregister <-= (pid, -1); 518 } 519 srv.replydirect(ref Rmsg.Flush(tag)); 520 done <-= 1; 521} 522 523# return number of characters from s that will fit into 524# max bytes when encoded as utf-8. 525fullutf(s: string, max: int): int 526{ 527 Bit1: con 7; 528 Bitx: con 6; 529 Bit2: con 5; 530 Bit3: con 4; 531 Bit4: con 3; 532 Rune1: con (1<<(Bit1+0*Bitx))-1; # 0000 0000 0111 1111 533 Rune2: con (1<<(Bit2+1*Bitx))-1; # 0000 0111 1111 1111 534 Rune3: con (1<<(Bit3+2*Bitx))-1; # 1111 1111 1111 1111 535 nb := 0; 536 for(i := 0; i < len s; i++){ 537 c := s[i]; 538 if(c <= Rune1) 539 nb += 1; 540 else if(c <= Rune2) 541 nb += 2; 542 else 543 nb += 3; 544 if(nb > max) 545 break; 546 } 547 return i; 548} 549 550kill(pid: int, note: string): int 551{ 552 fd := sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE); 553 if(fd == nil || sys->fprint(fd, "%s", note) < 0) 554 return -1; 555 return 0; 556} 557