1implement Ninewin;
2include "sys.m";
3	sys: Sys;
4include "draw.m";
5	draw: Draw;
6	Image, Display, Pointer: import draw;
7include "arg.m";
8include "keyboard.m";
9include "tk.m";
10include "wmclient.m";
11	wmclient: Wmclient;
12	Window: import wmclient;
13include "sh.m";
14	sh: Sh;
15
16# run a p9 graphics program (default rio) under inferno wm,
17# making available to it:
18# /dev/winname - naming the current inferno window (changing on resize)
19# /dev/mouse - pointer file + resize events; write to change position
20# /dev/cursor - change appearance of cursor.
21# /dev/draw - inferno draw device
22# /dev/cons - read keyboard events, write to 9win stdout.
23
24Ninewin: module {
25	init: fn(ctxt: ref Draw->Context, argv: list of string);
26};
27winname: string;
28
29init(ctxt: ref Draw->Context, argv: list of string)
30{
31	size := Draw->Point(500, 500);
32	sys = load Sys Sys->PATH;
33	draw = load Draw Draw->PATH;
34	wmclient = load Wmclient Wmclient->PATH;
35	wmclient->init();
36	sh = load Sh Sh->PATH;
37
38	buts := Wmclient->Resize;
39	if(ctxt == nil){
40		ctxt = wmclient->makedrawcontext();
41		buts = Wmclient->Plain;
42	}
43	arg := load Arg Arg->PATH;
44	arg->init(argv);
45	arg->setusage("9win [-s] [-x width] [-y height]");
46	exportonly := 0;
47	while(((opt := arg->opt())) != 0){
48		case opt {
49		's' =>
50			exportonly = 1;
51		'x' =>
52			size.x = int arg->earg();
53		'y' =>
54			size.y = int arg->earg();
55		* =>
56			arg->usage();
57		}
58	}
59	if(size.x < 1 || size.y < 1)
60		arg->usage();
61	argv = arg->argv();
62	if(argv != nil && hd argv == "-s"){
63		exportonly = 1;
64		argv = tl argv;
65	}
66	if(argv == nil && !exportonly)
67		argv = "rio" :: nil;
68	if(argv != nil && exportonly){
69		sys->fprint(sys->fildes(2), "9win: no command allowed with -s flag\n");
70		raise "fail:usage";
71	}
72	title := "9win";
73	if(!exportonly)
74		title += " " + hd argv;
75	w := wmclient->window(ctxt, title, buts);
76	w.reshape(((0, 0), size));
77	w.onscreen(nil);
78	if(w.image == nil){
79		sys->fprint(sys->fildes(2), "9win: cannot get image to draw on\n");
80		raise "fail:no window";
81	}
82
83	sys->pctl(Sys->FORKNS|Sys->NEWPGRP, nil);
84	ld := "/n/9win";
85	if(sys->bind("#s", ld, Sys->MREPL) == -1 &&
86			sys->bind("#s", ld = "/n/local", Sys->MREPL) == -1){
87		sys->fprint(sys->fildes(2), "9win: cannot bind files: %r\n");
88		raise "fail:error";
89	}
90	w.startinput("kbd" :: "ptr" :: nil);
91	spawn ptrproc(rq := chan of Sys->Rread, ptr := chan[10] of ref Pointer, reshape := chan[1] of int);
92
93
94	fwinname := sys->file2chan(ld, "winname");
95	fconsctl := sys->file2chan(ld, "consctl");
96	fcons := sys->file2chan(ld, "cons");
97	fmouse := sys->file2chan(ld, "mouse");
98	fcursor := sys->file2chan(ld, "cursor");
99	if(!exportonly){
100		spawn run(sync := chan of string, w.ctl, ld, argv);
101		if((e := <-sync) != nil){
102			sys->fprint(sys->fildes(2), "9win: %s", e);
103			raise "fail:error";
104		}
105	}
106	spawn serveproc(w, rq, fwinname, fconsctl, fcons, fmouse, fcursor);
107	if(!exportonly){
108		# handle events synchronously so that we don't get a "killed" message
109		# from the shell.
110		handleevents(w, ptr, reshape);
111	}else{
112		spawn handleevents(w, ptr, reshape);
113		sys->bind(ld, "/dev", Sys->MBEFORE);
114		export(sys->fildes(0), w.ctl);
115	}
116}
117
118handleevents(w: ref Window, ptr: chan of ref Pointer, reshape: chan of int)
119{
120	for(;;)alt{
121	c := <-w.ctxt.ctl or
122	c = <-w.ctl =>
123		e := w.wmctl(c);
124		if(e != nil)
125			sys->fprint(sys->fildes(2), "9win: ctl error: %s\n", e);
126		if(e == nil && c != nil && c[0] == '!'){
127			alt{
128			reshape <-= 1 =>
129				;
130			* =>
131				;
132			}
133			winname = nil;
134		}
135	p := <-w.ctxt.ptr =>
136		if(w.pointer(*p) == 0){
137			# XXX would block here if client isn't reading mouse... but we do want to
138			# extert back-pressure, which conflicts.
139			alt{
140			ptr <-= p =>
141				;
142			* =>
143				; # sys->fprint(sys->fildes(2), "9win: discarding mouse event\n");
144			}
145		}
146	}
147}
148
149serveproc(w: ref Window, mouserq: chan of Sys->Rread, fwinname, fconsctl, fcons, fmouse, fcursor: ref Sys->FileIO)
150{
151	winid := 0;
152	krc: list of Sys->Rread;
153	ks: string;
154
155	for(;;)alt {
156	c := <-w.ctxt.kbd =>
157		ks[len ks] = inf2p9key(c);
158		if(krc != nil){
159			hd krc <-= (array of byte ks, nil);
160			ks = nil;
161			krc = tl krc;
162		}
163	(nil, d, nil, wc) := <-fcons.write =>
164		if(wc != nil){
165			sys->write(sys->fildes(1), d, len d);
166			wc <-= (len d, nil);
167		}
168	(nil, nil, nil, rc) := <-fcons.read =>
169		if(rc != nil){
170			if(ks != nil){
171				rc <-= (array of byte ks, nil);
172				ks = nil;
173			}else
174				krc = rc :: krc;
175		}
176	(offset, nil, nil, rc) := <-fwinname.read =>
177		if(rc != nil){
178			if(winname == nil){
179				winname = sys->sprint("noborder.9win.%d", winid++);
180				if(w.image.name(winname, 1) == -1){
181					sys->fprint(sys->fildes(2), "9win: namewin %q failed: %r", winname);
182					rc <-= (nil, "namewin failure");
183					break;
184				}
185			}
186			d := array of byte winname;
187			if(offset < len d)
188				d = d[offset:];
189			else
190				d = nil;
191			rc <-= (d, nil);
192		}
193	(nil, nil, nil, wc) := <-fwinname.write =>
194		if(wc != nil)
195			wc <-= (-1, "permission denied");
196	(nil, nil, nil, rc) := <-fconsctl.read =>
197		if(rc != nil)
198			rc <-= (nil, "permission denied");
199	(nil, d, nil, wc) := <-fconsctl.write =>
200		if(wc != nil){
201			if(string d != "rawon")
202				wc <-= (-1, "cannot change console mode");
203			else
204				wc <-= (len d, nil);
205		}
206	(nil, nil, nil, rc) := <-fmouse.read =>
207		if(rc != nil)
208			mouserq <-= rc;
209	(nil, d, nil, wc) := <-fmouse.write =>
210		if(wc != nil){
211			e := cursorset(w, string d);
212			if(e == nil)
213				wc <-= (len d, nil);
214			else
215				wc <-= (-1, e);
216		}
217	(nil, nil, nil, rc) := <-fcursor.read =>
218		if(rc != nil)
219			rc <-= (nil, "permission denied");
220	(nil, d, nil, wc) := <-fcursor.write =>
221		if(wc != nil){
222			e := cursorswitch(w, d);
223			if(e == nil)
224				wc <-= (len d, nil);
225			else
226				wc <-= (-1, e);
227		}
228	}
229}
230
231ptrproc(rq: chan of Sys->Rread, ptr: chan of ref Pointer, reshape: chan of int)
232{
233	rl: list of Sys->Rread;
234	c := ref Pointer(0, (0, 0), 0);
235	for(;;){
236		ch: int;
237		alt{
238		p := <-ptr =>
239			ch = 'm';
240			c = p;
241		<-reshape =>
242			ch = 'r';
243		rc := <-rq =>
244			rl  = rc :: rl;
245			continue;
246		}
247		if(rl == nil)
248			rl = <-rq :: rl;
249		hd rl <-= (sys->aprint("%c%11d %11d %11d %11d ", ch, c.xy.x, c.xy.y, c.buttons, c.msec), nil);
250		rl = tl rl;
251	}
252}
253
254cursorset(w: ref Window, m: string): string
255{
256	if(m == nil || m[0] != 'm')
257		return "invalid mouse message";
258	x := int m[1:];
259	for(i := 1; i < len m; i++)
260		if(m[i] == ' '){
261			while(m[i] == ' ')
262				i++;
263			break;
264		}
265	if(i == len m)
266		return "invalid mouse message";
267	y := int m[i:];
268	return w.wmctl(sys->sprint("ptr %d %d", x, y));
269}
270
271cursorswitch(w: ref Window, d: array of byte): string
272{
273	Hex: con "0123456789abcdef";
274	if(len d != 2*4+64)
275		return w.wmctl("cursor");
276	hot := Draw->Point(bglong(d, 0*4), bglong(d, 1*4));
277	s := sys->sprint("cursor %d %d 16 32 ", hot.x, hot.y);
278	for(i := 2*4; i < len d; i++){
279		c := int d[i];
280		s[len s] = Hex[c >> 4];
281		s[len s] = Hex[c & 16rf];
282	}
283	return w.wmctl(s);
284}
285
286run(sync, ctl: chan of string, ld: string, argv: list of string)
287{
288	Rcmeta: con "|<>&^*[]?();";
289	sys->pctl(Sys->FORKNS, nil);
290	if(sys->bind("#₪", "/srv", Sys->MCREATE) == -1){
291		sync <-= sys->sprint("cannot bind srv device: %r");
292		exit;
293	}
294	srvname := "/srv/9win."+string sys->pctl(0, nil);	# XXX do better.
295	fd := sys->create(srvname, Sys->ORDWR, 8r600);
296	if(fd == nil){
297		sync <-= sys->sprint("cannot create %s: %r", srvname);
298		exit;
299	}
300	sync <-= nil;
301	spawn export(fd, ctl);
302	sh->run(nil, "os" ::
303		"rc" :: "-c" ::
304			"mount "+srvname+" /mnt/term;"+
305			"rm "+srvname+";"+
306			"bind -b /mnt/term"+ld+" /dev;"+
307			"bind /mnt/term/dev/draw /dev/draw ||"+
308				"bind -a /mnt/term/dev /dev;"+
309			quotedc("cd"::"/mnt/term"+cwd()::nil, Rcmeta)+";"+
310			quotedc(argv, Rcmeta)+";"::
311			nil
312		);
313}
314
315export(fd: ref Sys->FD, ctl: chan of string)
316{
317	sys->export(fd, "/", Sys->EXPWAIT);
318	ctl <-= "exit";
319}
320
321inf2p9key(c: int): int
322{
323	KF: import Keyboard;
324
325	P9KF: con	16rF000;
326	Spec: con	16rF800;
327	Khome: con	P9KF|16r0D;
328	Kup: con	P9KF|16r0E;
329	Kpgup: con	P9KF|16r0F;
330	Kprint: con	P9KF|16r10;
331	Kleft: con	P9KF|16r11;
332	Kright: con	P9KF|16r12;
333	Kdown: con	Spec|16r00;
334	Kview: con	Spec|16r00;
335	Kpgdown: con	P9KF|16r13;
336	Kins: con	P9KF|16r14;
337	Kend: con	P9KF|16r18;
338	Kalt: con		P9KF|16r15;
339	Kshift: con	P9KF|16r16;
340	Kctl: con		P9KF|16r17;
341
342	case c {
343	Keyboard->LShift =>
344		return Kshift;
345	Keyboard->LCtrl =>
346		return Kctl;
347	Keyboard->LAlt =>
348		return Kalt;
349	Keyboard->Home =>
350		return Khome;
351	Keyboard->End =>
352		return Kend;
353	Keyboard->Up =>
354		return Kup;
355	Keyboard->Down =>
356		return Kdown;
357	Keyboard->Left =>
358		return Kleft;
359	Keyboard->Right =>
360		return Kright;
361	Keyboard->Pgup =>
362		return Kpgup;
363	Keyboard->Pgdown =>
364		return Kpgdown;
365	Keyboard->Ins =>
366		return Kins;
367
368	# function keys
369	KF|1 or
370	KF|2 or
371	KF|3 or
372	KF|4 or
373	KF|5 or
374	KF|6 or
375	KF|7 or
376	KF|8 or
377	KF|9 or
378	KF|10 or
379	KF|11 or
380	KF|12 =>
381		return (c - KF) + P9KF;
382	}
383	return c;
384}
385
386cwd(): string
387{
388	return sys->fd2path(sys->open(".", Sys->OREAD));
389}
390
391# from string.b, waiting for declaration to be uncommented.
392quotedc(argv: list of string, cl: string): string
393{
394	s := "";
395	while (argv != nil) {
396		arg := hd argv;
397		for (i := 0; i < len arg; i++) {
398			c := arg[i];
399			if (c == ' ' || c == '\t' || c == '\n' || c == '\'' || in(c, cl))
400				break;
401		}
402		if (i < len arg || arg == nil) {
403			s += "'" + arg[0:i];
404			for (; i < len arg; i++) {
405				if (arg[i] == '\'')
406					s[len s] = '\'';
407				s[len s] = arg[i];
408			}
409			s[len s] = '\'';
410		} else
411			s += arg;
412		if (tl argv != nil)
413			s[len s] = ' ';
414		argv = tl argv;
415	}
416	return s;
417}
418
419in(c: int, s: string): int
420{
421	n := len s;
422	if(n == 0)
423		return 0;
424	ans := 0;
425	negate := 0;
426	if(s[0] == '^') {
427		negate = 1;
428		s = s[1:];
429		n--;
430	}
431	for(i := 0; i < n; i++) {
432		if(s[i] == '-' && i > 0 && i < n-1)  {
433			if(c >= s[i-1] && c <= s[i+1]) {
434				ans = 1;
435				break;
436			}
437			i++;
438		}
439		else
440		if(c == s[i]) {
441			ans = 1;
442			break;
443		}
444	}
445	if(negate)
446		ans = !ans;
447
448	# just to showcase labels
449skip:
450	return ans;
451}
452
453bglong(d: array of byte, i: int): int
454{
455	return int d[i] | (int d[i+1]<<8) | (int d[i+2]<<16) | (int d[i+3]<<24);
456}
457