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