ref: 58363833ce933924ef340eb53f0caa4d0d1f6ae5
dir: /main.c/
#include <u.h>
#include <libc.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
#include <draw.h>
#include <mouse.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"
#define Einuse "file in use"
QLock guilock = 0;
char *username;
enum {
Qdir,
Qclone,
Qevent,
Qtype,
Qprops,
Qprop,
};
enum {
Fclone,
Fevent,
Ftype,
Fprops,
Fmax,
};
void fsattach(Req *);
char *fswalk1(Fid *, char *, Qid *);
char *fsclone(Fid *, Fid *);
void fsopen(Req *);
void fsstat(Req *);
void fsread(Req *);
void fswrite(Req *);
void fsforker(void (*)(void*), void *, int);
void fsdestroyfid(Fid *);
Srv fs = {
.attach = fsattach,
.walk1 = fswalk1,
.clone = fsclone,
.open = fsopen,
.stat = fsstat,
.read = fsread,
.write = fswrite,
.destroyfid = fsdestroyfid,
.forker = fsforker,
};
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
settype(GuiElement *g, int type)
{
GuiSpec spec = guispecs[type];
int nprops = spec.nprops + nbaseprops;
/* Allocate the props before locking the gui element, as some of the
* allocations might cause this thread to block
*/
Prop *props = emalloc(sizeof(Prop) * nprops);
for(int i = 0; i < nbaseprops; i++){
int tag = baseprops[i];
props[i].tag = tag;
props[i].val = propspecs[tag].def(type, tag);
props[i].qid = mkpropqid(tag);
}
for(int i = 0; i < spec.nprops; i++){
int tag = spec.proptags[i];
props[i+nbaseprops].tag = tag;
props[i+nbaseprops].val = propspecs[tag].def(type, tag);
props[i+nbaseprops].qid = mkpropqid(tag);
}
wlock(&g->lock);
/* TODO: free old propvals */
free(g->props);
g->type = type;
g->nprops = nprops;
g->props = props;
wunlock(&g->lock);
updategui(0); /* redraw everything */
}
GuiElement *
newgui(GuiElement *parent)
{
GuiElement *g = emalloc(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);
g->events = chancreate(sizeof(char *), 0);
if(parent){
g->id = parent->nchildren;
wlock(&parent->lock);
parent->nchildren++;
parent->children = erealloc(parent->children, parent->nchildren * sizeof(GuiElement *));
parent->children[g->id] = g;
wunlock(&parent->lock);
}
settype(g, Gcontainer);
return g;
}
GuiElement *
findchild(GuiElement *g, char *name)
{
char *r;
uvlong id = strtoull(name, &r, 10);
if(*r != 0){
return nil;
}
rlock(&g->lock);
GuiElement *child = (id < g->nchildren) ? g->children[id] : nil;
runlock(&g->lock);
return child;
}
void
fsattach(Req *r)
{
if(root == nil){
GuiElement *g = newgui(nil);
root = g;
settype(g, Gcontainer);
updategui(1);
}
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;
char *err = nil;
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;
}
}else if(strcmp(name, "clone") == 0){
rlock(&g->lock);
if(guispecs[g->type].leafnode)
err = Eexist;
else
*qid = g->qclone;
runlock(&g->lock);
}else if(strcmp(name, "event") == 0)
*qid = g->qevent;
else if(strcmp(name, "type") == 0)
*qid = g->qtype;
else if(strcmp(name, "props") == 0)
*qid = g->qprops;
else if(child = findchild(g, name)){
fid->aux = child;
*qid = child->qid;
}else
err = Eexist;
break;
case Qprops:
if(strcmp(name, "..") == 0)
*qid = g->qid;
else{
rlock(&g->lock);
int ok = 0;
for(int i = 0; i < g->nprops && ok == 0; i++){
PropSpec spec = propspecs[g->props[i].tag];
if(strcmp(name, spec.name) == 0){
*qid = g->props[i].qid;
ok = 1;
}
}
runlock(&g->lock);
if(!ok)
err = Eexist;
}
break;
default:
err = Enodir;
break;
}
return err;
}
char *
fsclone(Fid *old, Fid *new)
{
new->aux = old->aux;
return nil;
}
void
fsopen(Req *r)
{
GuiElement *g = r->fid->aux;
char *err = nil;
switch(QID_TYPE(r->fid->qid)){
case Qdir:
case Qevent:
case Qclone:
case Qprops:
if(r->ifcall.mode != OREAD){
err = Eperm;
goto Lend;
}
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;
}
if(QID_TYPE(r->fid->qid) == Qevent){
wlock(&g->lock);
if(g->listening){
err = Einuse;
r->fid->aux = nil;
}else
g->listening = 1;
wunlock(&g->lock);
}
Lend:
respond(r, err);
}
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);
rlock(&g->lock);
if(guispecs[g->type].leafnode)
n++;
runlock(&g->lock);
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|DMEXCL;
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;
rlock(&g->lock);
int done = n >= g->nchildren;
if(!done){
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);
}
runlock(&g->lock);
return done ? -1 : 0;
}
int
proptreegen(int n, Dir *d, void *aux)
{
GuiElement *g = aux;
d->uid = estrdup9p(username);
d->gid = estrdup9p(username);
d->muid = estrdup9p(username);
int done;
rlock(&g->lock);
done = n >= g->nprops;
if(!done){
PropSpec spec = propspecs[g->props[n].tag];
d->mode = 0666;
d->name = estrdup9p(spec.name);
d->qid = g->props[n].qid;
}
runlock(&g->lock);
return done ? -1 : 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:
{
/* get all the messages we can, and add them to g->currentevents */
char *event;
int mustrecv = 0;
ulong currentsize;
ulong eventsize;
Lretry:
srvrelease(&fs);
if(mustrecv){
recv(g->events, &event);
goto Lgotevent;
}
while(nbrecv(g->events, &event)){
Lgotevent: currentsize = g->currentevents ? strlen(g->currentevents) : 0;
eventsize = strlen(event);
wlock(&g->lock);
g->currentevents = erealloc(g->currentevents, currentsize+eventsize+1);
memcpy(g->currentevents+currentsize, event, eventsize);
g->currentevents[currentsize+eventsize] = 0;
wunlock(&g->lock);
free(event);
}
rlock(&g->lock);
if(g->currentevents == nil){
runlock(&g->lock);
recv(g->events, &event);
goto Lgotevent;
}else{
srvacquire(&fs);
readstr(r, g->currentevents);
if(r->ofcall.count == 0){
runlock(&g->lock);
mustrecv = 1;
goto Lretry;
}
}
runlock(&g->lock);
}
break;
case Qtype:
rlock(&g->lock);
snprint(buf, sizeof(buf), "%s\n", guispecs[g->type].name);
runlock(&g->lock);
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, 1);
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:
{
char *buf = emalloc(r->ifcall.count + 1);
buf[r->ifcall.count] = 0;
memcpy(buf, r->ifcall.data, r->ifcall.count);
int type;
for(type = 0; type < Gmax; type++){
char *name = guispecs[type].name;
int len = strlen(name);
if(strncmp(buf, name, len) == 0 && allspace(buf+len)){
rlock(&g->lock);
int canswitch = (g->nchildren == 0 || guispecs[type].leafnode == 0);
runlock(&g->lock);
if(canswitch)
settype(g, type);
else
err = "old node has children, and new node type can't";
break;
}
}
if(type == Gmax)
err = "no such gui element type";
free(buf);
}
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, 1);
free(buf);
}
}
respond(r, err);
}
void
fsforker(void (*fn)(void*), void *arg, int rflag)
{
/* same as threadsrvforker, but stay in the same note group */
rflag &= ~RFNOTEG;
procrfork(fn, arg, 32*1024, rflag);
}
void
fsdestroyfid(Fid *fid)
{
GuiElement *g = fid->aux;
if(g != nil && QID_TYPE(fid->qid) == Qevent){
wlock(&g->lock);
g->listening = 0;
free(g->currentevents);
wunlock(&g->lock);
}
}
void
usage(void)
{
fprint(2, "usage: %s [-D] [-m mountpoint] [-s srvname] command\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 == 0)
usage();
username = getuser();
initgraphics();
threadpostmountsrv(&fs, srvname, mtpt, MREPL);
int pid = fork();
switch(pid){
case 0: /* child process */
exec(argv[0], argv);
sysfatal("exec: %r");
break;
case -1: /* error */
sysfatal("fork: %r");
break;
}
/* parent process */
int wpid;
do
wpid = waitpid();
while(wpid != -1 && wpid != pid);
postnote(PNGROUP, getpid(), "interrupt");
exits(nil);
}