shithub: guifs

Download patch

ref: 7c6a945996a1d5510ff1412320ac7d07a0f82851
parent: b5a96b97ee2f3fd30e5935d2b3c2c6e0d96c4640
author: Peter Mikkelsen <[email protected]>
date: Sat Feb 10 19:04:31 EST 2024

Start working on it

--- /dev/null
+++ b/graphics.c
@@ -1,0 +1,123 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+#include <mouse.h>
+#include <keyboard.h>
+
+#include "guifs.h"
+
+Mousectl *mouse;
+Keyboardctl *keyboard;
+Channel *updatechan;
+
+void
+drawgui(GuiElement *g)
+{
+	GuiSpec spec = guispecs[g->type];
+	spec.draw(g);
+	for(int i = 0; i < g->nchildren; i++)
+		drawgui(g->children[i]);
+}
+
+void
+drawnone(GuiElement *g)
+{
+	Image *bg = getprop(g, Pbackground).colour->image;
+	draw(screen, g->rect, bg, nil, ZP);
+}
+
+void
+drawcontainer(GuiElement *g)
+{
+	Image *bg = getprop(g, Pbackground).colour->image;
+	draw(screen, g->rect, bg, nil, ZP);
+}
+
+Colour *
+mkcolour(ulong c)
+{
+	lockdisplay(display);
+	Colour *col = emalloc(sizeof(Colour));
+	col->image = allocimage(display, Rect(0,0,1,1), screen->chan, 1, c);
+	col->code = c;
+	unlockdisplay(display);
+
+	return col;
+}
+
+void
+updategui(int new)
+{
+	/* Trigger a call to resized by sending a message */
+	send(updatechan, &new);
+}
+
+void
+resized(int new)
+{
+	if(new && getwindow(display, Refnone) < 0)
+		sysfatal("can't reattach to window: %r");
+
+	if(root != nil){
+		layout(root, screen->r);
+		drawgui(root);
+		flushimage(display, 1);
+	}
+}
+
+void
+guiproc(void *)
+{
+	int i;
+	if(initdraw(nil, nil, "guifs") < 0)
+		sysfatal("initdraw failed");
+	display->locking = 1;
+
+	if((mouse = initmouse(nil, screen)) == nil)
+		sysfatal("initmouse failed");
+	if((keyboard = initkeyboard(nil)) == nil)
+		sysfatal("initkeyboard failed");
+
+	enum {
+		Aupdategui,
+		Aresize,
+		Amouse,
+		Aaltend,
+	};
+	Alt a[] = {
+		[Aupdategui] =
+			{updatechan, &i, CHANRCV},
+		[Aresize] =
+			{mouse->resizec, nil, CHANRCV},
+		[Amouse] =
+			{mouse->c, &mouse->Mouse, CHANRCV},
+		[Aaltend] =
+			{nil, nil, CHANEND},
+	};
+
+	while(1){
+		unlockdisplay(display);
+		int which = alt(a);
+		lockdisplay(display);
+
+		switch(which){
+		case Aupdategui:
+			resized(i);
+			break;
+		case Aresize:
+			resized(1);
+			break;
+		case Amouse:
+			break;
+		}
+	}
+}
+
+void
+initgraphics(void)
+{
+	updatechan = chancreate(sizeof(int), 0);
+	proccreate(guiproc, nil, mainstacksize);
+	updategui(1);
+}
--- /dev/null
+++ b/guifs.h
@@ -1,0 +1,87 @@
+enum {
+	Pbackground,
+	Pmax,
+};
+
+enum {
+	Gnone,
+	Gcontainer,
+	Gmax,
+};
+typedef struct Colour Colour;
+typedef union PropVal PropVal;
+typedef struct PropSpec PropSpec;
+typedef struct Prop Prop;
+typedef struct GuiSpec GuiSpec;
+typedef struct GuiElement GuiElement;
+
+struct Colour {
+	Image *image;
+	ulong code;
+};
+
+union PropVal {
+	Colour *colour;
+};
+
+struct PropSpec {
+	char *name;
+	PropVal (*def)(void);
+	char *(*print)(PropVal);
+	char *(*parse)(char *, PropVal *);
+};
+
+struct Prop {
+	int tag;
+	PropVal val;
+	Qid qid;
+};
+
+struct GuiSpec {
+	char *name;
+	void (*draw)(GuiElement *);
+	void (*layout)(GuiElement *, Rectangle);
+	int nprops;
+	int proptags[];
+};
+
+struct GuiElement {
+	int type;
+	int id;
+
+	Qid qid;
+	Qid qclone;
+	Qid qevent;
+	Qid qtype;
+	Qid qprops;
+
+	int nchildren;
+	GuiElement **children;
+
+	GuiElement *parent;
+
+	int nprops;
+	Prop *props;
+
+	Rectangle rect;
+	
+};
+
+extern GuiElement *root;
+extern PropSpec propspecs[Pmax];
+extern GuiSpec guispecs[Gmax];
+
+void *emalloc(ulong);
+
+Colour *mkcolour(ulong);
+void initgraphics(void);
+void layout(GuiElement *, Rectangle);
+void updategui(int);
+void drawnone(GuiElement *);
+void drawcontainer(GuiElement *);
+
+void layoutnone(GuiElement *, Rectangle);
+void layoutcontainer(GuiElement *, Rectangle);
+
+PropVal getprop(GuiElement *, int);
+void setprop(GuiElement *, int, PropVal);
\ No newline at end of file
--- /dev/null
+++ b/guispec.c
@@ -1,0 +1,10 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+#include "guifs.h"
+
+GuiSpec guispecs[Gmax] = {
+	[Gnone]	=	{ "none",	drawnone,	layoutnone,	1,	{Pbackground}},
+	[Gcontainer] =	{ "container",	drawcontainer,	layoutcontainer,	1,	{Pbackground}},
+};
\ No newline at end of file
--- /dev/null
+++ b/layout.c
@@ -1,0 +1,43 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+#include "guifs.h"
+
+void
+layout(GuiElement *g, Rectangle r)
+{
+	GuiSpec spec = guispecs[g->type];
+
+	g->rect = r;
+	spec.layout(g, r);
+}
+
+void
+layoutnone(GuiElement *g, Rectangle r)
+{
+	USED(g);
+	USED(r);
+}
+
+void
+layoutcontainer(GuiElement *g, Rectangle r)
+{
+	USED(g);
+	USED(r);
+
+	if(g->nchildren == 0)
+		return;
+
+	int margin = 10;
+
+	r = insetrect(r, 10);
+	int width = Dx(r) - (margin * (g->nchildren - 1));
+	width = width / g->nchildren;
+	r.max.x = r.min.x + width;
+
+	for(int i = 0; i < g->nchildren; i++){
+		layout(g->children[i], r);
+		r = rectaddpt(r, Pt(width+margin, 0));
+	}
+}
\ No newline at end of file
--- a/main.c
+++ b/main.c
@@ -1,8 +1,460 @@
 #include <u.h>
 #include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include <draw.h>
 
+#include "guifs.h"
+
+#define Eexist	"file does not exist"
+#define Enodir	"not a directory"
+#define Eperm	"permission denied"
+#define Eoffset	"can't write to this file at non-zero offset"
+#define Ebadctl	"bad ctl message"
+
+char *username;
+
+enum {
+	Qdir,
+	Qclone,
+	Qevent,
+	Qtype,
+	Qprops,
+
+	Qprop,
+};
+
+enum {
+	Fclone,
+	Fevent,
+	Ftype,
+	Fprops,
+	Fmax,
+};
+
+GuiElement *root;
+
+#define QID_TYPE(q)	((q.path) & 0xFF)
+#define QID_PROP(q)	(((q.path) >> 8) & 0xFF)
+
+void *
+emalloc(ulong size)
+{
+	void *p = mallocz(size, 1);
+	if(!p)
+		sysfatal("malloc failed");
+	return p;
+}
+
+void *
+erealloc(void *p, ulong size)
+{
+	p = realloc(p, size);
+	if(!p)
+		sysfatal("realloc failed");
+	return p;
+}
+
+Qid
+mkqid(int type)
+{
+	static int id = 0;
+
+	Qid q;
+	q.vers = 0;
+	q.path = (type & 0xFFFF) | (id << 16);
+	id++;
+	switch(type){
+	case Qdir:
+	case Qprops:
+		q.type = QTDIR;
+		break;
+	case Qclone:
+	case Qevent:
+	case Qtype:
+		q.type = QTFILE;
+		break;
+	}
+	return q;
+}
+
+Qid
+mkpropqid(int proptag)
+{
+	return mkqid(Qprop | ((proptag & 0xFF) << 8));
+}
+
 void
-main(void)
+settype(GuiElement *g, int type)
 {
-	print("hello\n");
+	GuiSpec spec = guispecs[type];
+	free(g->props);
+	g->type = type;
+
+	g->nprops = spec.nprops;
+	g->props = emalloc(sizeof(Prop) * spec.nprops);
+	for(int i = 0; i < spec.nprops; i++){
+		int tag = spec.proptags[i];
+		g->props[i].tag = tag;
+		g->props[i].val = propspecs[tag].def();
+		g->props[i].qid = mkpropqid(tag);
+	}
+
+	updategui(0); /* redraw everything */
+}
+
+GuiElement *
+newgui(GuiElement *parent)
+{
+	GuiElement *g = emalloc(sizeof(GuiElement));
+	memset(g, 0, sizeof(GuiElement));
+	g->parent = parent;
+	g->qid = mkqid(Qdir);
+	g->qclone = mkqid(Qclone);
+	g->qevent = mkqid(Qevent);
+	g->qtype = mkqid(Qtype);
+	g->qprops = mkqid(Qprops);
+
+	if(parent){
+		g->id = parent->nchildren;
+		parent->nchildren++;
+		parent->children = erealloc(parent->children, parent->nchildren * sizeof(GuiElement *));
+		parent->children[g->id] = g;
+	}
+
+	settype(g, Gcontainer);
+
+	return g;
+}
+
+GuiElement *
+findchild(GuiElement *g, char *name)
+{
+	char *r;
+	uvlong id = strtoull(name, &r, 10);
+	if(*r != 0){
+		return nil;
+	}
+
+	if(id < g->nchildren)
+		return g->children[id];
+
+	return nil;
+}
+
+void
+fsattach(Req *r)
+{
+	if(root == nil){
+		GuiElement *g = newgui(nil);
+		root = g;
+		settype(g, Gcontainer);
+	}
+
+	r->fid->aux = root;
+	r->fid->qid = root->qid;
+	r->ofcall.qid = r->fid->qid;
+
+	respond(r, nil);
+}
+
+char *
+fswalk1(Fid *fid, char *name, Qid *qid)
+{
+	GuiElement *g = fid->aux;
+	GuiElement *child;
+
+	switch(QID_TYPE(fid->qid)){
+	case Qdir:
+		if(strcmp(name, "..") == 0){
+			if(g->parent == nil) // Root element
+				*qid = g->qid;
+			else{
+				fid->aux = g->parent;
+				*qid = g->parent->qid;
+			}
+			return nil;
+		}else if(strcmp(name, "clone") == 0){
+			*qid = g->qclone;
+			return nil;
+		}else if(strcmp(name, "event") == 0){
+			*qid = g->qevent;
+			return nil;
+		}else if(strcmp(name, "type") == 0){
+			*qid = g->qtype;
+			return nil;
+		}else if(strcmp(name, "props") == 0){
+			*qid = g->qprops;
+			return nil;
+		}else if(child = findchild(g, name)){
+			fid->aux = child;
+			*qid = child->qid;
+			return nil;
+		}
+		return Eexist;
+	case Qprops:
+		if(strcmp(name, "..") == 0){
+			*qid = g->qid;
+			return nil;
+		}
+		for(int i = 0; i < g->nprops; i++){
+			PropSpec spec = propspecs[g->props[i].tag];
+			if(strcmp(name, spec.name) == 0){
+				*qid = g->props[i].qid;
+				return nil;
+			}
+		}
+		return Eexist;
+	default:
+		return Enodir;
+	}
+}
+
+char *
+fsclone(Fid *old, Fid *new)
+{
+	new->aux = old->aux;
+	return nil;
+}
+
+void
+fsopen(Req *r)
+{
+	GuiElement *g = r->fid->aux;
+
+	switch(QID_TYPE(r->fid->qid)){
+	case Qdir:
+	case Qevent:
+	case Qclone:
+	case Qprops:
+		if(r->ifcall.mode != OREAD){
+			respond(r, Eperm);
+			return;
+		}
+		break;
+	}
+
+	if(QID_TYPE(r->fid->qid) == Qclone){
+		/* Create a new child gui element */
+		GuiElement *child = newgui(g);
+
+		assert(r->fid->qid.vers == child->id);
+
+		/* Update qid version, so a read reports the correct child id */
+		assert(memcmp(&r->fid->qid, &g->qclone, sizeof(Qid)) == 0);
+		g->qclone.vers++;
+		r->fid->qid = g->qclone;
+		r->ofcall.qid = g->qclone;
+	}
+
+	respond(r, nil);
+}
+
+void
+fsstat(Req *r)
+{
+	r->d.qid = r->fid->qid;
+	r->d.uid = estrdup9p(username);
+	r->d.gid = estrdup9p(username);
+	r->d.muid = estrdup9p(username);
+	switch(QID_TYPE(r->fid->qid)){
+	case Qdir:
+		r->d.name = estrdup9p("/");
+		r->d.mode = 0555|DMDIR;
+		break;
+	case Qprops:
+		r->d.name = estrdup9p("/");
+		r->d.mode = 0555|DMDIR;
+		break;
+	}
+
+	respond(r, nil);
+}
+
+int
+dirtreegen(int n, Dir *d, void *aux)
+{
+	GuiElement *g = aux;
+
+	d->uid = estrdup9p(username);
+	d->gid = estrdup9p(username);
+	d->muid = estrdup9p(username);
+
+	if(n < Fmax){
+		d->length = 0;
+
+		switch(n){
+		case Fclone:
+			d->mode = 0444;
+			d->name = estrdup9p("clone");
+			d->qid = g->qclone;
+			break;
+		case Fevent:
+			d->mode = 0444;
+			d->name = estrdup9p("event");
+			d->qid = g->qevent;
+			break;
+		case Ftype:
+			d->mode = 0666;
+			d->name = estrdup9p("type");
+			d->qid = g->qtype;
+			break;
+		case Fprops:
+			d->mode = 0555|DMDIR;
+			d->name = estrdup9p("props");
+			d->qid = g->qprops;
+			break;
+		}
+		return 0;
+	}else
+		n -= Fmax;
+
+	if(g && n < g->nchildren){
+		GuiElement *child = g->children[n];
+
+		d->mode = DMDIR|0555;
+		d->qid = child->qid;
+
+		char buf[64];
+		snprint(buf, sizeof(buf), "%d", child->id);
+		d->name = estrdup9p(buf);
+		return 0;
+	}
+
+	return -1;
+}
+
+int
+proptreegen(int n, Dir *d, void *aux)
+{
+	GuiElement *g = aux;
+
+	d->uid = estrdup9p(username);
+	d->gid = estrdup9p(username);
+	d->muid = estrdup9p(username);
+
+	if(n >= g->nprops)
+		return -1;
+
+	PropSpec spec = propspecs[g->props[n].tag];
+	d->mode = 0666;
+	d->name = estrdup9p(spec.name);
+	d->qid = g->props[n].qid;
+	return 0;
+}
+
+void
+fsread(Req *r)
+{
+	GuiElement *g = r->fid->aux;
+	char buf[256];
+
+	switch(QID_TYPE(r->fid->qid)){
+	case Qdir:
+		dirread9p(r, dirtreegen, g);
+		break;
+	case Qclone:
+		snprint(buf, sizeof(buf), "%uld\n", r->fid->qid.vers-1);
+		readstr(r, buf);
+		break;
+	case Qevent:
+		/* in another thread, wait for events on a channel
+		 * and call readstr on each of them individually.
+		 */
+		readstr(r, "eveeent\n");
+		break;
+	case Qtype:
+		snprint(buf, sizeof(buf), "%s\n", guispecs[g->type].name);
+		readstr(r, buf);
+		break;
+	case Qprops:
+		dirread9p(r, proptreegen, g);
+		break;
+	case Qprop:
+		{
+			int tag = QID_PROP(r->fid->qid);
+			PropSpec spec = propspecs[tag];
+			PropVal val = getprop(g, tag);
+			char *str = spec.print(val);
+			readstr(r, str);
+			free(str);
+		}
+		break;
+	}
+	respond(r, nil);
+}
+
+void
+fswrite(Req *r)
+{
+	GuiElement *g = r->fid->aux;
+	char *err = nil;
+	
+	switch(QID_TYPE(r->fid->qid)){
+	case Qtype:
+		err = "Can't switch type";
+		break;
+	case Qprop:
+		{
+			int tag = QID_PROP(r->fid->qid);
+			PropSpec spec = propspecs[tag];
+			PropVal val;
+
+			char *buf = emalloc(r->ifcall.count + 1);
+			buf[r->ifcall.count] = 0;
+			memcpy(buf, r->ifcall.data, r->ifcall.count);
+			err = spec.parse(buf, &val);
+			if(err == nil)
+				setprop(g, tag, val);
+			free(buf);
+		}
+	}
+
+	respond(r, err);
+}
+
+Srv fs = {
+	.attach = fsattach,
+	.walk1 = fswalk1,
+	.clone = fsclone,
+	.open = fsopen,
+	.stat = fsstat,
+	.read = fsread,
+	.write = fswrite,
+};
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [-D] [-m mountpoint] [-s srvname] \n", argv0);
+	exits("usage");
+}
+
+void
+threadmain(int argc, char **argv)
+{
+	char *mtpt = "/mnt/gui";
+	char *srvname = nil;
+	ARGBEGIN{
+	case 'D':
+		chatty9p++;
+		break;
+	case 'm':
+		mtpt = EARGF(usage());
+		break;
+	case 's':
+		srvname = EARGF(usage());
+		break;
+	default:
+		usage();
+	}ARGEND;
+
+	if(argc > 1)
+		usage();
+
+	username = getuser();
+	initgraphics();
+	threadpostmountsrv(&fs, srvname, mtpt, MREPL);
+	exits(nil);
 }
--- a/mkfile
+++ b/mkfile
@@ -1,7 +1,13 @@
 </$objtype/mkfile
 
 TARG=guifs
-OFILES=main.$O
-BIN=$home/bin/$objtype/bin
+OFILES=\
+	main.$O\
+	props.$O\
+	guispec.$O\
+	graphics.$O\
+	layout.$O\
+
+BIN=$home/bin/$objtype
 
 </sys/src/cmd/mkone
--- /dev/null
+++ b/props.c
@@ -1,0 +1,60 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+#include "guifs.h"
+
+#define Eparse "could not parse property"
+
+PropVal
+defbackground(void)
+{
+	PropVal v;
+	v.colour = mkcolour(DBlack);
+	return v;
+}
+
+char *
+printcolour(PropVal p)
+{
+	int bufsize = 64;
+	char *buf = emalloc(bufsize);
+	snprint(buf, bufsize, "%08ulX\n", p.colour->code);
+	return buf;
+}
+
+char *
+parsecolour(char *str, PropVal *p)
+{
+	char *r;
+	ulong c = strtoul(str, &r, 16);
+	if((r - str) != 8)
+		return Eparse;
+	(*p).colour = mkcolour(c);
+	return nil;
+}
+
+PropVal
+getprop(GuiElement *g, int tag)
+{
+	for(int i = 0; i < g->nprops; i++)
+		if(g->props[i].tag == tag)
+			return g->props[i].val;
+	sysfatal("invalid prop for this gui element");
+}
+
+void
+setprop(GuiElement *g, int tag, PropVal val)
+{
+	/* TODO: free old propval */
+	for(int i = 0; i < g->nprops; i++)
+		if(g->props[i].tag == tag){
+			g->props[i].val = val;
+			updategui(0);
+			return;
+		}
+}
+
+PropSpec propspecs[Pmax] = {
+	[Pbackground] = {"background",	defbackground,	printcolour,	parsecolour},
+};
\ No newline at end of file
--- /dev/null
+++ b/test.rc
@@ -1,0 +1,26 @@
+#!/bin/rc
+
+rfork n
+
+guifs -s testgui
+
+dir=/mnt/gui
+sleep 1
+
+colours=(FF0000 00FF00 0000FF FFFF00 00FFFF FF00FF FFFFFF)
+for(colour in $colours){
+	dir=$dir/`{cat $dir/clone}			# Create a new sub element in the gui (for now, always a "container" with a small margin)
+	echo $colour^FF >> $dir/props/background	# Set the background
+	sleep 1						# Wait a bit
+}
+
+# Now do the same, but don't nest the elements
+for(colour in $colours){
+	subdir=$dir/`{cat $dir/clone}
+	echo $colour^FF >> $subdir/props/background
+	sleep 1
+}
+
+# when the script ends, the old text window draws over the gui :joyd: I will fix that later.
+# For now, i just make the script sleep for a long time
+sleep 1000000