diff options
author | cinap_lenrek <cinap_lenrek@centraldogma> | 2011-09-04 19:16:30 +0200 |
---|---|---|
committer | cinap_lenrek <cinap_lenrek@centraldogma> | 2011-09-04 19:16:30 +0200 |
commit | 6842f8712508262d0ea27692f13caa686419601e (patch) | |
tree | 8c074ca18dc70c02392fc951f6e8e21db00d7cdb | |
parent | f6e73a6a22db00925f2f447815287bf2a086054b (diff) |
add mothra
54 files changed, 9240 insertions, 0 deletions
diff --git a/sys/src/cmd/mothra/auth.c b/sys/src/cmd/mothra/auth.c new file mode 100644 index 000000000..3e99e6324 --- /dev/null +++ b/sys/src/cmd/mothra/auth.c @@ -0,0 +1,68 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <panel.h> +#include <bio.h> +#include "mothra.h" + +static int +basicauth(char *arg, char *str, int n) +{ + int i; + char *p; + char buf[1024]; + Biobuf *b; + + if(strncmp(arg, "realm=", 6) == 0) + arg += 6; + if(*arg == '"'){ + arg++; + for(p = arg; *p && *p != '"'; p++); + *p = 0; + } else { + for(p = arg; *p && *p != ' ' && *p != '\t'; p++); + *p = 0; + } + + p = getenv("home"); + if(p == 0){ + werrstr("$home not set"); + return -1; + } + snprint(buf, sizeof(buf), "%s/lib/mothra/insecurity", p); + b = Bopen(buf, OREAD); + if(b == 0){ + werrstr("www password file %s: %r", buf); + return -1; + } + + i = strlen(arg); + while(p = Brdline(b, '\n')) + if(strncmp(arg, p, i) == 0 && p[i] == '\t') + break; + if(p == 0){ + Bterm(b); + werrstr("no basic password for domain `%s'", arg); + return -1; + } + + p[Blinelen(b)-1] = 0; + for(p += i; *p == '\t'; p++); + if (enc64(buf, sizeof buf, (uchar*)p, strlen(p)) < 0) { + Bterm(b); + werrstr("password too long: %s", p); + return -1; + } + snprint(str, n, "Authorization: Basic %s\r\n", buf); + return 0; +} + +int +auth(Url *url, char *str, int n) +{ + if(cistrcmp(url->authtype, "basic") == 0) + return basicauth(url->autharg, str, n); + werrstr("unknown auth method %s", url->authtype); + return -1; +} diff --git a/sys/src/cmd/mothra/cistr.c b/sys/src/cmd/mothra/cistr.c new file mode 100644 index 000000000..f6aec8681 --- /dev/null +++ b/sys/src/cmd/mothra/cistr.c @@ -0,0 +1,29 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <panel.h> +#include <ctype.h> +#include "mothra.h" +int cistrcmp(char *s1, char *s2){ + int c1, c2; + + for(; *s1; s1++, s2++){ + c1 = isupper(*s1) ? tolower(*s1) : *s1; + c2 = isupper(*s2) ? tolower(*s2) : *s2; + if (c1 < c2) return -1; + if (c1 > c2) return 1; + } + return 0; +} +int cistrncmp(char *s1, char *s2, int n){ + int c1, c2; + + for(; *s1 && n!=0; s1++, s2++, --n){ + c1 = isupper(*s1) ? tolower(*s1) : *s1; + c2 = isupper(*s2) ? tolower(*s2) : *s2; + if (c1 < c2) return -1; + if (c1 > c2) return 1; + } + return 0; +} diff --git a/sys/src/cmd/mothra/crackurl.c b/sys/src/cmd/mothra/crackurl.c new file mode 100644 index 000000000..f04f42575 --- /dev/null +++ b/sys/src/cmd/mothra/crackurl.c @@ -0,0 +1,188 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <panel.h> +#include <ctype.h> +#include "mothra.h" +enum{ + IP=1, /* url can contain //ipaddress[:port] */ + REL=2, /* fill in ip address & root of name from current, if necessary */ + SSL=4, /* use SSL/TLS encryption */ +}; +Scheme scheme[]={ + "http:", HTTP, IP|REL, 80, + "https:", HTTP, IP|REL|SSL, 443, + "ftp:", FTP, IP|REL, 21, + "file:", FILE, REL, 0, + "telnet:", TELNET, IP, 0, + "mailto:", MAILTO, 0, 0, + "gopher:", GOPHER, IP, 70, + 0, HTTP, IP|REL, 80, +}; +int endaddr(int c){ + return c=='/' || c==':' || c=='?' || c=='#' || c=='\0'; +} +/* + * Remove ., mu/.. and empty components from path names. + * Empty last components of urls are significant, and + * therefore preserved. + */ +void urlcanon(char *name){ + char *s, *t; + char **comp, **p, **q; + int rooted; + rooted=name[0]=='/'; + /* + * Break the name into a list of components + */ + comp=emalloc((strlen(name)+2)*sizeof(char *)); + p=comp; + *p++=name; + for(s=name;;s++){ + if(*s=='/'){ + *p++=s+1; + *s='\0'; + } + else if(*s=='\0' || *s=='?') + break; + } + *p=0; + /* + * go through the component list, deleting components that are empty (except + * the last component) or ., and any .. and its non-.. predecessor. + */ + p=q=comp; + while(*p){ + if(strcmp(*p, "")==0 && p[1]!=0 + || strcmp(*p, ".")==0) + p++; + else if(strcmp(*p, "..")==0 && q!=comp && strcmp(q[-1], "..")!=0){ + --q; + p++; + } + else + *q++=*p++; + } + *q=0; + /* + * rebuild the path name + */ + s=name; + if(rooted) *s++='/'; + for(p=comp;*p;p++){ + t=*p; + while(*t) *s++=*t++; + if(p[1]!=0) *s++='/'; + } + *s='\0'; + free(comp); +} +/* + * True url parsing is a nightmare. + * This assumes that there are two basic syntaxes + * for url's -- with and without an ip address. + * If the type identifier or the ip address and port number + * or the relative address is missing from urlname or is empty, + * it is copied from cur. + */ +void crackurl(Url *url, char *urlname, Url *cur){ + char *relp, *tagp, *httpname; + int len; + Scheme *up; + char buf[30]; + /* + * The following lines `fix' the most egregious urlname syntax errors + */ + while(*urlname==' ' || *urlname=='\t' || *urlname=='\n') urlname++; + relp=strchr(urlname, '\n'); + if(relp) *relp='\0'; + /* + * In emulation of Netscape, attach a free "http://" + * to names beginning with "www.". + */ + if(strncmp(urlname, "www.", 4)==0){ + httpname=emalloc(strlen(urlname)+8); + strcpy(httpname, "http://"); + strcat(httpname, urlname); + crackurl(url, httpname, cur); + free(httpname); + return; + } + url->port=cur->port; + strncpy(url->ipaddr, cur->ipaddr, sizeof(url->ipaddr)); + strncpy(url->reltext, cur->reltext, sizeof(url->reltext)); + if(strchr(urlname, ':')==0){ + up=cur->scheme; + if(up==0){ + up=&scheme[0]; + cur->scheme=up; + } + } + else{ + for(up=scheme;up->name;up++){ + len=strlen(up->name); + if(strncmp(urlname, up->name, len)==0){ + urlname+=len; + break; + } + } + if(up->name==0) up=&scheme[0]; /* default to http: */ + } + url->access=up->type; + url->scheme=up; + if(up!=cur->scheme) + url->reltext[0]='\0'; + if(up->flags&IP && strncmp(urlname, "//", 2)==0){ + urlname+=2; + for(relp=urlname;!endaddr(*relp);relp++); + len=relp-urlname; + strncpy(url->ipaddr, urlname, len); + url->ipaddr[len]='\0'; + urlname=relp; + if(*urlname==':'){ + urlname++; + url->port=atoi(urlname); + while(!endaddr(*urlname)) urlname++; + } + else + url->port=up->port; + if(*urlname=='\0') urlname="/"; + } + url->ssl = up->flags&SSL; + + tagp=strchr(urlname, '#'); + if(tagp){ + *tagp='\0'; + strncpy(url->tag, tagp+1, sizeof(url->tag)); + } + else + url->tag[0]='\0'; + if(!(up->flags&REL) || *urlname=='/') + strncpy(url->reltext, urlname, sizeof(url->reltext)); + else if(urlname[0]){ + relp=strrchr(url->reltext, '/'); + if(relp==0) + strncpy(url->reltext, urlname, sizeof(url->reltext)); + else + strcpy(relp+1, urlname); + } + urlcanon(url->reltext); + if(tagp) *tagp='#'; + /* + * The following mess of strcpys and strcats + * can't be changed to a few sprints because + * urls are not necessarily composed of legal utf + */ + strcpy(url->fullname, up->name); + if(up->flags&IP){ + strncat(url->fullname, "//", sizeof(url->fullname)); + strncat(url->fullname, url->ipaddr, sizeof(url->fullname)); + if(url->port!=up->port){ + snprint(buf, sizeof(buf), ":%d", url->port); + strncat(url->fullname, buf, sizeof(url->fullname)); + } + } + strcat(url->fullname, url->reltext); + url->map=0; +} diff --git a/sys/src/cmd/mothra/file.c b/sys/src/cmd/mothra/file.c new file mode 100644 index 000000000..50ae60f25 --- /dev/null +++ b/sys/src/cmd/mothra/file.c @@ -0,0 +1,48 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <panel.h> +#include "mothra.h" +/* + * fd is the result of a successful open(name, OREAD), + * where name is the name of a directory. We convert + * this into an html page containing links to the files + * in the directory. + */ +int dir2html(char *name, int fd){ + int p[2], first; + Dir *dir; + int i, n; + if(pipe(p)==-1){ + close(fd); + return -1; + } + switch(rfork(RFFDG|RFPROC|RFNOWAIT)){ + case -1: + close(fd); + return -1; + case 0: + close(p[1]); + fprint(p[0], "<head>\n"); + fprint(p[0], "<title>Directory %s</title>\n", name); + fprint(p[0], "</head>\n"); + fprint(p[0], "<body>\n"); + fprint(p[0], "<h1>%s</h1>\n", name); + fprint(p[0], "<ul>\n"); + first=1; + while((n = dirread(fd, &dir)) > 0) { + for (i = 0; i < n; i++) + fprint(p[0], "<li><a href=\"%s/%s\">%s%s</a>\n", name, dir[i].name, dir[i].name, + dir[i].mode&DMDIR?"/":""); + free(dir); + } + fprint(p[0], "</ul>\n"); + fprint(p[0], "</body>\n"); + _exits(0); + default: + close(fd); + close(p[0]); + return p[1]; + } +} diff --git a/sys/src/cmd/mothra/filetype.c b/sys/src/cmd/mothra/filetype.c new file mode 100644 index 000000000..71ae92bed --- /dev/null +++ b/sys/src/cmd/mothra/filetype.c @@ -0,0 +1,106 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <panel.h> +#include <ctype.h> +#include "mothra.h" +typedef struct Kind Kind; +struct Kind{ + char *name; + int kind; +}; +int klook(char *s, Kind *k){ + while(k->name && cistrcmp(k->name, s)!=0) + k++; + return k->kind; +} +Kind suffix[]={ + ".html", HTML, + ".htm", HTML, + "/", HTML, + ".gif", GIF, + ".jpe", JPEG, + ".jpg", JPEG, + ".jpeg", JPEG, + ".png", PNG, + ".pic", PIC, + ".au", AUDIO, + ".tif", TIFF, + ".tiff", TIFF, + ".xbm", XBM, + ".txt", PLAIN, + ".text", PLAIN, + ".ai", POSTSCRIPT, + ".eps", POSTSCRIPT, + ".ps", POSTSCRIPT, + ".pdf", PDF, + ".zip", ZIP, + 0, HTML +}; +int suflook(char *s, int len, Kind *k){ + int l; + while(k->name){ + l=strlen(k->name); + if(l<=len && cistrcmp(k->name, s+len-l)==0) return k->kind; + k++; + } + return k->kind; +} +int suffix2type(char *name){ + int len, kind, restore; + char *s; + len=strlen(name); + if(len>=2 && cistrcmp(name+len-2, ".Z")==0){ + kind=COMPRESS; + len-=2; + } + else if(len>=3 && cistrcmp(name+len-3, ".gz")==0){ + kind=GUNZIP; + len-=3; + } + else + kind=0; + restore=name[len]; + name[len]='\0'; + for(s=name+len;s!=name && *s!='.';--s); + kind|=suflook(name, len, suffix); + name[len]=restore; + return kind; +} +Kind content[]={ + "text/html", HTML, + "text/x-html", HTML, + "application/html", HTML, + "application/x-html", HTML, + "text/plain", PLAIN, + "image/gif", GIF, + "image/jpeg", JPEG, + "image/pjpeg", JPEG, + "image/png", PNG, + "image/tiff", TIFF, + "image/x-xbitmap", XBM, + "image/x-bitmap", XBM, + "image/xbitmap", XBM, + "application/postscript", POSTSCRIPT, + "application/pdf", PDF, + "application/octet-stream", SUFFIX, + "application/zip", ZIP, + 0, HTML +}; +int content2type(char *s, char *name){ + int type; + type=klook(s, content); + if(type==SUFFIX) type=suffix2type(name); + return type; +} +Kind encoding[]={ + "x-compress", COMPRESS, + "compress", COMPRESS, + "x-gzip", GUNZIP, + "gzip", GUNZIP, + 0, 0 +}; +int encoding2type(char *s){ + return klook(s, encoding); +} diff --git a/sys/src/cmd/mothra/forms.c b/sys/src/cmd/mothra/forms.c new file mode 100644 index 000000000..5e08a3242 --- /dev/null +++ b/sys/src/cmd/mothra/forms.c @@ -0,0 +1,661 @@ +/* + * type=image is treated like submit + */ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <panel.h> +#include "mothra.h" +#include "html.h" +typedef struct Field Field; +typedef struct Option Option; +struct Form{ + int method; + Url *action; + Field *fields, *efields; + Form *next; +}; +struct Field{ + Field *next; + Form *form; + char *name; + char *value; + int checked; + int size; /* should be a point, but that feature is deprecated */ + int maxlength; + int type; + int rows, cols; + Option *options; + int multiple; + int state; /* is the button marked? */ + Panel *p; + Panel *pulldown; + Panel *textwin; +}; +/* + * Field types + */ +enum{ + TYPEIN=1, + CHECK, + PASSWD, + RADIO, + SUBMIT, + RESET, + SELECT, + TEXTWIN, + HIDDEN, + INDEX, +}; +struct Option{ + int selected; + int def; + char label[NLABEL+1]; + char *value; + Option *next; +}; +void h_checkinput(Panel *, int, int); +void h_radioinput(Panel *, int, int); +void h_submitinput(Panel *, int); +void h_submittype(Panel *, char *); +void h_submitindex(Panel *, char *); +void h_resetinput(Panel *, int); +void h_select(Panel *, int, int); +void h_cut(Panel *, int); +void h_paste(Panel *, int); +void h_snarf(Panel *, int); +void h_edit(Panel *); +char *selgen(Panel *, int); +char *nullgen(Panel *, int); +Field *newfield(Form *form){ + Field *f; + f=emallocz(sizeof(Field), 1); + if(form->efields==0) + form->fields=f; + else + form->efields->next=f; + form->efields=f; + f->next=0; + f->form=form; + return f; +} +/* + * Called by rdhtml on seeing a forms-related tag + */ +void rdform(Hglob *g){ + char *s; + Field *f; + Option *o, **op; + Form *form; + switch(g->tag){ + default: + fprint(2, "Bad tag <%s> in rdform (Can't happen!)\n", g->token); + return; + case Tag_form: + if(g->form){ + htmlerror(g->name, g->lineno, "nested forms illegal\n"); + break; + } + g->form=emallocz(sizeof(Form), 1); + g->form->action=emalloc(sizeof(Url)); + s=pl_getattr(g->attr, "action"); + if(s==0) + *g->form->action=*g->dst->url; + else + crackurl(g->form->action, s, g->dst->base); + s=pl_getattr(g->attr, "method"); + if(s==0) + g->form->method=GET; + else if(cistrcmp(s, "post")==0) + g->form->method=POST; + else{ + if(cistrcmp(s, "get")!=0) + htmlerror(g->name, g->lineno, + "unknown form method %s\n", s); + g->form->method=GET; + } + g->form->fields=0; + + g->form->next = g->dst->form; + g->dst->form = g->form; + + break; + case Tag_input: + if(g->form==0){ + BadTag: + htmlerror(g->name, g->lineno, "<%s> not in form, ignored\n", + tag[g->tag].name); + break; + } + f=newfield(g->form); + s=pl_getattr(g->attr, "name"); + if(s==0) + f->name=0; + else + f->name=strdup(s); + s=pl_getattr(g->attr, "value"); + if(s==0) + f->value=strdup(""); + else + f->value=strdup(s); + f->checked=pl_hasattr(g->attr, "checked"); + s=pl_getattr(g->attr, "size"); + if(s==0) + f->size=20; + else + f->size=atoi(s); + s=pl_getattr(g->attr, "maxlength"); + if(s==0) + f->maxlength=0x3fffffff; + else + f->maxlength=atoi(s); + s=pl_getattr(g->attr, "type"); + /* bug -- password treated as text */ + if(s==0 || cistrcmp(s, "text")==0 || cistrcmp(s, "password")==0 || cistrcmp(s, "int")==0){ + s=pl_getattr(g->attr, "name"); + if(s!=0 && strcmp(s, "isindex")==0) + f->type=INDEX; + else + f->type=TYPEIN; + /* + * If there's exactly one attribute, use its value as the name, + * regardless of the attribute name. This makes + * http://linus.att.com/ias/puborder.html work. + */ + if(s==0){ + if(g->attr[0].name && g->attr[1].name==0) + f->name=strdup(g->attr[0].value); + else + f->name=strdup("no-name"); + } + } + else if(cistrcmp(s, "checkbox")==0) + f->type=CHECK; + else if(cistrcmp(s, "radio")==0) + f->type=RADIO; + else if(cistrcmp(s, "submit")==0) + f->type=SUBMIT; + else if(cistrcmp(s, "image")==0){ + /* presotto's egregious hack to make image submits do something */ + if(f->name){ + free(f->name); + f->name=0; + } + f->type=SUBMIT; + } else if(cistrcmp(s, "reset")==0) + f->type=RESET; + else if(cistrcmp(s, "hidden")==0) + f->type=HIDDEN; + else{ + htmlerror(g->name, g->lineno, "bad field type %s, ignored", s); + break; + } + if((f->type==CHECK || f->type==RADIO) && !pl_hasattr(g->attr, "value")){ + free(f->value); + f->value=strdup("on"); + } + if(f->type!=HIDDEN) + pl_htmloutput(g, g->nsp, f->value[0]?f->value:"blank field", f); + break; + case Tag_select: + if(g->form==0) goto BadTag; + f=newfield(g->form); + s=pl_getattr(g->attr, "name"); + if(s==0){ + f->name=strdup("select"); + htmlerror(g->name, g->lineno, "select has no name=\n"); + } + else + f->name=strdup(s); + s=pl_getattr(g->attr, "size"); + if(s==0) f->size=4; + else{ + f->size=atoi(s); + if(f->size<=0) f->size=1; + } + f->multiple=pl_hasattr(g->attr, "multiple"); + f->type=SELECT; + f->options=0; + g->text=g->token; + g->tp=g->text; + g->etext=g->text; + break; + case Tag_option: + if(g->form==0) goto BadTag; + f=g->form->efields; + o=emallocz(sizeof(Option), 1); + for(op=&f->options;*op;op=&(*op)->next); + *op=o; + o->next=0; + g->text=o->label; + g->tp=o->label; + g->etext=o->label+NLABEL; + memset(o->label, 0, NLABEL+1); + *g->tp++=' '; + o->def=pl_hasattr(g->attr, "selected"); + o->selected=o->def; + s=pl_getattr(g->attr, "value"); + if(s==0) + o->value=o->label+1; + else + o->value=strdup(s); + break; + case Tag_textarea: + if(g->form==0) goto BadTag; + f=newfield(g->form); + s=pl_getattr(g->attr, "name"); + if(s==0){ + f->name=strdup("enter text"); + htmlerror(g->name, g->lineno, "select has no name=\n"); + } + else + f->name=strdup(s); + s=pl_getattr(g->attr, "rows"); + f->rows=s?atoi(s):8; + s=pl_getattr(g->attr, "cols"); + f->cols=s?atoi(s):30; + f->type=TEXTWIN; + /* suck up initial text */ + pl_htmloutput(g, g->nsp, f->name, f); + break; + case Tag_isindex: + /* + * Make up a form with one tag, of type INDEX + * I have seen a page with <ISINDEX PROMPT="Enter a title here ">, + * which is nonstandard and not handled here. + */ + form=emalloc(sizeof(Form)); + form->fields=0; + form->efields=0; + form->action=emalloc(sizeof(Url)); + s=pl_getattr(g->attr, "action"); + if(s==0) + *form->action=*g->dst->url; + else + crackurl(form->action, s, g->dst->base); + form->method=GET; + form->fields=0; + f=newfield(form); + f->name=0; + f->value=strdup(""); + f->size=20; + f->maxlength=0x3fffffff; + f->type=INDEX; + pl_htmloutput(g, g->nsp, f->value[0]?f->value:"blank field", f); + break; + } +} +/* + * Called by rdhtml on seeing a forms-related end tag + */ +void endform(Hglob *g){ + switch(g->tag){ + case Tag_form: + g->form=0; + break; + case Tag_select: + if(g->form==0) + htmlerror(g->name, g->lineno, "</select> not in form, ignored\n"); + else + pl_htmloutput(g, g->nsp, g->form->efields->name,g->form->efields); + break; + case Tag_textarea: + break; + } +} +char *nullgen(Panel *, int ){ + return 0; +} +char *selgen(Panel *p, int index){ + Option *a; + Field *f; + f=p->userp; + if(f==0) return 0; + for(a=f->options;index!=0 && a!=0;--index,a=a->next); + if(a==0) return 0; + a->label[0]=a->selected?'*':' '; + return a->label; +} +char *seloption(Field *f){ + Option *a; + for(a=f->options;a!=0;a=a->next) + if(a->selected) + return a->label+1; + return f->name; +} +void mkfieldpanel(Rtext *t){ + Action *a; + Panel *win, *scrl, *menu, *pop, *button; + Field *f; + + if((a = t->user) == nil) + return; + if((f = a->field) == nil) + return; + + f->p=0; + switch(f->type){ + case TYPEIN: + f->p=plentry(0, 0, f->size*chrwidth, f->value, h_submittype); + break; + case CHECK: + f->p=plcheckbutton(0, 0, "", h_checkinput); + f->state=f->checked; + plsetbutton(f->p, f->checked); + break; + case RADIO: + f->p=plradiobutton(0, 0, "", h_radioinput); + f->state=f->checked; + plsetbutton(f->p, f->checked); + break; + case SUBMIT: + f->p=plbutton(0, 0, f->value[0]?f->value:"submit", h_submitinput); + break; + case RESET: + f->p=plbutton(0, 0, f->value[0]?f->value:"reset", h_resetinput); + break; + case SELECT: + f->pulldown=plgroup(0,0); + scrl=plscrollbar(f->pulldown, PACKW|FILLY); + win=pllist(f->pulldown, PACKN, nullgen, f->size, h_select); + win->userp=f; + plinitlist(win, PACKN, selgen, f->size, h_select); + plscroll(win, 0, scrl); + plpack(f->pulldown, Rect(0,0,1024,1024)); + f->p=plpulldown(0, FIXEDX, seloption(f), f->pulldown, PACKS); + f->p->fixedsize.x=f->pulldown->r.max.x-f->pulldown->r.min.x; + break; + case TEXTWIN: + menu=plgroup(0,0); + f->p=plframe(0,0); + pllabel(f->p, PACKN|FILLX, f->name); + scrl=plscrollbar(f->p, PACKW|FILLY); + pop=plpopup(f->p, PACKN|FILLX, 0, menu, 0); + f->textwin=pledit(pop, EXPAND, Pt(f->cols*chrwidth, f->rows*font->height), + 0, 0, h_edit); + f->textwin->userp=f; + button=plbutton(menu, PACKN|FILLX, "cut", h_cut); + button->userp=f->textwin; + button=plbutton(menu, PACKN|FILLX, "paste", h_paste); + button->userp=f->textwin; + button=plbutton(menu, PACKN|FILLX, "snarf", h_snarf); + button->userp=f->textwin; + plscroll(f->textwin, 0, scrl); + break; + case INDEX: + f->p=plentry(0, 0, f->size*chrwidth, f->value, h_submitindex); + break; + } + if(f->p){ + f->p->userp=f; + free(t->text); + t->text=0; + t->p=f->p; + t->hot=1; + } +} +void h_checkinput(Panel *p, int, int v){ + ((Field *)p->userp)->state=v; +} +void h_radioinput(Panel *p, int, int v){ + Field *f, *me; + me=p->userp; + me->state=v; + if(v){ + for(f=me->form->fields;f;f=f->next) + if(f->type==RADIO && f!=me && strcmp(f->name, me->name)==0){ + plsetbutton(f->p, 0); + f->state=0; + pldraw(f->p, screen); + } + } +} +void h_select(Panel *p, int, int index){ + Option *a; + Field *f; + f=p->userp; + if(f==0) return; + if(!f->multiple) for(a=f->options;a;a=a->next) a->selected=0; + for(a=f->options;index!=0 && a!=0;--index,a=a->next); + if(a==0) return; + a->selected=!a->selected; + plinitpulldown(f->p, FIXEDX, seloption(f), f->pulldown, PACKS); + pldraw(f->p, screen); +} +void h_resetinput(Panel *p, int){ + Field *f; + Option *o; + for(f=((Field *)p->userp)->form->fields;f;f=f->next) switch(f->type){ + case TYPEIN: + case PASSWD: + plinitentry(f->p, 0, f->size*chrwidth, f->value, 0); + break; + case CHECK: + case RADIO: + f->state=f->checked; + plsetbutton(f->p, f->checked); + break; + case SELECT: + for(o=f->options;o;o=o->next) + o->selected=o->def; + break; + } + pldraw(text, screen); +} +void h_edit(Panel *p){ + plgrabkb(p); +} +Rune *snarfbuf=0; +int nsnarfbuf=0; +void h_snarf(Panel *p, int){ + int s0, s1; + Rune *text; + p=p->userp; + plegetsel(p, &s0, &s1); + if(s0==s1) return; + text=pleget(p); + if(snarfbuf) free(snarfbuf); + nsnarfbuf=s1-s0; + snarfbuf=malloc(nsnarfbuf*sizeof(Rune)); + if(snarfbuf==0){ + fprint(2, "No mem\n"); + exits("no mem"); + } + memmove(snarfbuf, text+s0, nsnarfbuf*sizeof(Rune)); +} +void h_cut(Panel *p, int b){ + h_snarf(p, b); + plepaste(p->userp, 0, 0); +} +void h_paste(Panel *p, int){ + plepaste(p->userp, snarfbuf, nsnarfbuf); +} +int ulen(char *s){ + int len; + len=0; + for(;*s;s++){ + if(strchr("/$-_@.!*'(), ", *s) + || 'a'<=*s && *s<='z' + || 'A'<=*s && *s<='Z' + || '0'<=*s && *s<='9') + len++; + else + len+=3; + } + return len; +} +int hexdigit(int v){ + return 0<=v && v<=9?'0'+v:'A'+v-10; +} +char *ucpy(char *buf, char *s){ + for(;*s;s++){ + if(strchr("/$-_@.!*'(),", *s) + || 'a'<=*s && *s<='z' + || 'A'<=*s && *s<='Z' + || '0'<=*s && *s<='9') + *buf++=*s; + else if(*s==' ') + *buf++='+'; + else{ + *buf++='%'; + *buf++=hexdigit((*s>>4)&15); + *buf++=hexdigit(*s&15); + } + } + *buf='\0'; + return buf; +} +char *runetou(char *buf, Rune r){ + char rbuf[2]; + if(r<=255){ + rbuf[0]=r; + rbuf[1]='\0'; + buf=ucpy(buf, rbuf); + } + return buf; +} +/* + * If there's exactly one button with type=text, then + * a CR in the button is supposed to submit the form. + */ +void h_submittype(Panel *p, char *){ + int ntype; + Field *f; + ntype=0; + for(f=((Field *)p->userp)->form->fields;f;f=f->next) if(f->type==TYPEIN) ntype++; + if(ntype==1) h_submitinput(p, 0); +} +void h_submitindex(Panel *p, char *){ + h_submitinput(p, 0); +} +void h_submitinput(Panel *p, int){ + Form *form; + int size, nrune; + char *buf, *bufp, sep; + Rune *rp; + Field *f; + Option *o; + form=((Field *)p->userp)->form; + if(form->method==GET) size=ulen(form->action->fullname)+1; + else size=1; + for(f=form->fields;f;f=f->next) switch(f->type){ + case TYPEIN: + case PASSWD: + size+=ulen(f->name)+1+ulen(plentryval(f->p))+1; + break; + case INDEX: + size+=ulen(plentryval(f->p))+1; + break; + case CHECK: + case RADIO: + if(!f->state) break; + case HIDDEN: + size+=ulen(f->name)+1+ulen(f->value)+1; + break; + case SELECT: + for(o=f->options;o;o=o->next) + if(o->selected) + size+=ulen(f->name)+1+ulen(o->value)+1; + break; + case TEXTWIN: + size+=ulen(f->name)+1+plelen(f->textwin)*3+1; + break; + } + buf=emalloc(size); + if(form->method==GET){ + strcpy(buf, form->action->fullname); + sep='?'; + } + else{ + buf[0]='\0'; + sep=0; + } + bufp=buf+strlen(buf); + if(form->method==GET && bufp!=buf && bufp[-1]=='?') *--bufp='\0'; /* spurious ? */ + for(f=form->fields;f;f=f->next) switch(f->type){ + case TYPEIN: + case PASSWD: + if(sep) *bufp++=sep; + sep='&'; + bufp=ucpy(bufp, f->name); + *bufp++='='; + bufp=ucpy(bufp, plentryval(f->p)); + break; + case INDEX: + if(sep) *bufp++=sep; + sep='&'; + bufp=ucpy(bufp, plentryval(f->p)); + break; + case CHECK: + case RADIO: + if(!f->state) break; + case HIDDEN: + if(sep) *bufp++=sep; + sep='&'; + bufp=ucpy(bufp, f->name); + *bufp++='='; + bufp=ucpy(bufp, f->value); + break; + case SELECT: + for(o=f->options;o;o=o->next) + if(o->selected){ + if(sep) *bufp++=sep; + sep='&'; + bufp=ucpy(bufp, f->name); + *bufp++='='; + bufp=ucpy(bufp, o->value); + } + break; + case TEXTWIN: + if(sep) *bufp++=sep; + sep='&'; + bufp=ucpy(bufp, f->name); + *bufp++='='; + rp=pleget(f->textwin); + for(nrune=plelen(f->textwin);nrune!=0;--nrune) + bufp=runetou(bufp, *rp++); + *bufp='\0'; + break; + } + if(form->method==GET){ +fprint(2, "GET %s\n", buf); + geturl(buf, GET, 0, 0, 0); + } + else{ +fprint(2, "POST %s: %s\n", form->action->fullname, buf); + geturl(form->action->fullname, POST, buf, 0, 0); + } + free(buf); +} + +void freeform(void *p) +{ + Form *form; + Field *f; + Option *o; + + while(form = p){ + p = form->next; + free(form->action); + while(f = form->fields){ + form->fields = f->next; + + if(f->p!=0) + plfree(f->p); + + free(f->name); + free(f->value); + + while(o = f->options){ + f->options = o->next; + if(o->value != o->label+1) + free(o->value); + free(o); + } + + free(f); + } + free(form); + } +} diff --git a/sys/src/cmd/mothra/ftp.c b/sys/src/cmd/mothra/ftp.c new file mode 100644 index 000000000..78c3ab5da --- /dev/null +++ b/sys/src/cmd/mothra/ftp.c @@ -0,0 +1,428 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <panel.h> +#include <bio.h> +#include <ndb.h> +#include <ctype.h> +#include <ip.h> +#include "mothra.h" + +enum +{ + /* return codes */ + Extra= 1, + Success= 2, + Incomplete= 3, + TempFail= 4, + PermFail= 5, + + NAMELEN= 28, + Nnetdir= 3*NAMELEN, /* max length of network directory paths */ + Ndialstr= 64, /* max length of dial strings */ +}; + +typedef struct Ftp Ftp; +struct Ftp +{ + char net[Nnetdir]; + Biobuf *ftpctl; + Url *u; +}; + +static int ftpdebug; + + +/* + * read from biobuf turning cr/nl into nl + */ +char* +getcrnl(Biobuf *b) +{ + char *p, *ep; + + p = Brdline(b, '\n'); + if(p == nil) + return nil; + ep = p + Blinelen(b) - 1; + if(*(ep-1) == '\r') + ep--; + *ep = 0; + return p; +} + +char* +readfile(char *file, char *buf, int len) +{ + int n, fd; + + fd = open(file, OREAD); + if(fd < 0) + return nil; + n = read(fd, buf, len-1); + close(fd); + if(n <= 0) + return nil; + buf[n] = 0; + return buf; +} + +char* +sysname(void) +{ + static char sys[Ndbvlen]; + char *p; + + p = readfile("/dev/sysname", sys, sizeof(sys)); + if(p == nil) + return "unknown"; + return p; +} + +char* +domainname(void) +{ + static char domain[Ndbvlen]; + Ndbtuple *t; + + if(*domain) + return domain; + + t = csgetval(0, "sys", sysname(), "dom", domain); + if(t){ + ndbfree(t); + return domain; + } else + return sysname(); +} + +static int +sendrequest(Biobuf *b, char *fmt, ...) +{ + char buf[2*1024], *s; + va_list args; + + va_start(args, fmt); + s = buf + vsnprint(buf, (sizeof(buf)-4) / sizeof(*buf), fmt, args); + va_end(args); + *s++ = '\r'; + *s++ = '\n'; + if(write(Bfildes(b), buf, s - buf) != s - buf) + return -1; + if(ftpdebug) + write(2, buf, s - buf); + return 0; +} + +static int +getreply(Biobuf *b, char *msg, int len) +{ + char *line; + int rv; + int i, n; + + while(line = getcrnl(b)){ + /* add line to message buffer, strip off \r */ + n = Blinelen(b); + if(ftpdebug) + write(2, line, n); + if(n > len - 1) + i = len - 1; + else + i = n; + if(i > 0){ + memmove(msg, line, i); + msg += i; + len -= i; + *msg = 0; + } + + /* stop if not a continuation */ + rv = atoi(line); + if(rv >= 100 && rv < 600 && (n == 4 || (n > 4 && line[3] == ' '))) + return rv/100; + } + + return -1; +} + +int +terminateftp(Ftp *d) +{ + if(d->ftpctl){ + close(Bfildes(d->ftpctl)); + Bterm(d->ftpctl); + free(d->ftpctl); + d->ftpctl = nil; + } + free(d); + return -1; +} + +Biobuf* +hello(Ftp *d) +{ + int fd; + char *p; + Biobuf *b; + char msg[1024]; + char ndir[Nnetdir]; + + snprint(msg, sizeof msg, "tcp!%s!%d", d->u->ipaddr, d->u->port); + fd = dial(msg, 0, ndir, 0); + if(fd < 0){ + d->ftpctl = nil; + return nil; + } + b = emalloc(sizeof(Biobuf)); + Binit(b, fd, OREAD); + d->ftpctl = b; + + /* remember network for the data connections */ + p = strrchr(ndir, '/'); + if(p == 0){ + fprint(2, "dial is out of date\n"); + return nil; + } + *p = 0; + strcpy(d->net, ndir); + + /* wait for hello from other side */ + if(getreply(b, msg, sizeof(msg)) != Success){ + fprint(2, "instead of hello: %s\n", msg); + return nil; + } + return b; +} + +int +logon(Ftp *d) +{ + char msg[1024]; + + /* login anonymous */ + sendrequest(d->ftpctl, "USER anonymous"); + switch(getreply(d->ftpctl, msg, sizeof(msg))){ + case Success: + return 0; + case Incomplete: + break; /* need password */ + default: + fprint(2, "login failed: %s\n", msg); + werrstr(msg); + return -1; + } + + /* send user id as password */ + sprint(msg, "%s@", getuser()); + sendrequest(d->ftpctl, "PASS %s", msg); + if(getreply(d->ftpctl, msg, sizeof(msg)) != Success){ + fprint(2, "login failed: %s\n", msg); + werrstr(msg); + return -1; + } + + return 0; +} + +int +xfertype(Ftp *d, char *t) +{ + char msg[1024]; + + sendrequest(d->ftpctl, "TYPE %s", t); + if(getreply(d->ftpctl, msg, sizeof(msg)) != Success){ + fprint(2, "can't set type %s: %s\n", t, msg); + werrstr(msg); + return -1; + } + return 0; +} + +int +passive(Ftp *d) +{ + char msg[1024]; + char dialstr[Ndialstr]; + char *f[6]; + char *p; + int fd; + + sendrequest(d->ftpctl, "PASV"); + if(getreply(d->ftpctl, msg, sizeof(msg)) != Success) + return -1; + + /* get address and port number from reply, this is AI */ + p = strchr(msg, '('); + if(p == nil){ + for(p = msg+3; *p; p++) + if(isdigit(*p)) + break; + } else + p++; + if(getfields(p, f, 6, 0, ",") < 6){ + fprint(2, "passive mode protocol botch: %s\n", msg); + werrstr("ftp protocol botch"); + return -1; + } + snprint(dialstr, sizeof(dialstr), "%s!%s.%s.%s.%s!%d", d->net, + f[0], f[1], f[2], f[3], + ((atoi(f[4])&0xff)<<8) + (atoi(f[5])&0xff)); + + + /* open data connection */ + fd = dial(dialstr, 0, 0, 0); + if(fd < 0){ + fprint(2, "passive mode connect to %s failed: %r\n", dialstr); + return -1; + } + + /* tell remote to send a file */ + sendrequest(d->ftpctl, "RETR %s", d->u->reltext); + if(getreply(d->ftpctl, msg, sizeof(msg)) != Extra){ + fprint(2, "passive mode retrieve failed: %s\n", msg); + werrstr(msg); + return -1; + } + return fd; +} + +int +active(Ftp *d) +{ + char msg[1024]; + char buf[Ndialstr]; + char netdir[Nnetdir]; + char newdir[Nnetdir]; + uchar ipaddr[4]; + int dfd, cfd, listenfd; + char *p; + int port; + + /* get a channel to listen on, let kernel pick the port number */ + sprint(buf, "%s!*!0", d->net); + listenfd = announce(buf, netdir); + if(listenfd < 0){ + fprint(2, "can't listen for ftp callback: %r\n", buf); + return -1; + } + + /* get the local address and port number */ + sprint(newdir, "%s/local", netdir); + readfile(newdir, buf, sizeof buf); + p = strchr(buf, '!')+1; + parseip(ipaddr, buf); + port = atoi(p); + + /* tell remote side address and port*/ + sendrequest(d->ftpctl, "PORT %d,%d,%d,%d,%d,%d", ipaddr[0], ipaddr[1], ipaddr[2], + ipaddr[3], port>>8, port&0xff); + if(getreply(d->ftpctl, msg, sizeof(msg)) != Success){ + close(listenfd); + werrstr("ftp protocol botch"); + fprint(2, "active mode connect failed %s\n", msg); + return -1; + } + + /* tell remote to send a file */ + sendrequest(d->ftpctl, "RETR %s", d->u->reltext); + if(getreply(d->ftpctl, msg, sizeof(msg)) != Extra){ + close(listenfd); + fprint(2, "active mode connect failed: %s\n", msg); + werrstr(msg); + return -1; + } + + /* wait for a new call */ + cfd = listen(netdir, newdir); + close(listenfd); + if(cfd < 0){ + fprint(2, "active mode connect failed: %r\n"); + return -1; + } + + /* open the data connection and close the control connection */ + dfd = accept(cfd, newdir); + close(cfd); + if(dfd < 0){ + fprint(2, "active mode connect failed: %r\n"); + werrstr("ftp protocol botch"); + return -1; + } + + return dfd; +} + +/* + * Given a url, return a file descriptor on which caller can + * read an ftp document. + * The caller is responsible for processing redirection loops. + */ +int +ftp(Url *url) +{ + int n; + int data; + Ftp *d; + int pfd[2]; + char buf[2048]; + + if(url->type == 0) + url->type = PLAIN; + + d = (Ftp*)emalloc(sizeof(Ftp)); + d->u = url; + d->ftpctl = nil; + + if(hello(d) == nil) + return terminateftp(d); + if(logon(d) < 0) + return terminateftp(d); + + switch(url->type){ + case PLAIN: + case HTML: + if(xfertype(d, "A") < 0) + return terminateftp(d); + break; + default: + if(xfertype(d, "I") < 0) + return terminateftp(d); + break; + } + + /* first try passive mode, then active */ + data = passive(d); + if(data < 0){ + if(d->ftpctl == nil) + return -1; + data = active(d); + if(data < 0) + return -1; + } + + if(pipe(pfd) < 0) + return -1; + + switch(rfork(RFFDG|RFPROC|RFNOWAIT)){ + case -1: + werrstr("Can't fork"); + close(pfd[0]); + close(pfd[1]); + return terminateftp(d); + case 0: + close(pfd[0]); + while((n=read(data, buf, sizeof(buf)))>0) + write(pfd[1], buf, n); + if(n<0) + fprint(2, "ftp: %s: %r\n", url->fullname); + _exits(0); + default: + close(pfd[1]); + close(data); + terminateftp(d); + return pfd[0]; + } + return -1; +} diff --git a/sys/src/cmd/mothra/getpix.c b/sys/src/cmd/mothra/getpix.c new file mode 100644 index 000000000..19e2b8b23 --- /dev/null +++ b/sys/src/cmd/mothra/getpix.c @@ -0,0 +1,126 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <panel.h> +#include "mothra.h" + +typedef struct Pix Pix; +struct Pix{ + Pix *next; + Image *b; + int width; + int height; + char name[NNAME]; +}; + +char *pixcmd[]={ +[GIF] "gif -9t", +[JPEG] "jpg -9t", +[PNG] "png -9t", +[PIC] "fb/3to1 /lib/fb/cmap/rgbv", +[TIFF] "/sys/lib/mothra/tiffcvt", +[XBM] "fb/xbm2pic", +}; + +void storebitmap(Rtext *t, Image *b){ + t->b=b; + free(t->text); + t->text=0; +} + +void getimage(Rtext *t, Www *w){ + int pfd[2]; + Action *ap; + Url url; + Image *b; + int fd; + char err[512]; + Pix *p; + + ap=t->user; + crackurl(&url, ap->image, w->base); + for(p=w->pix;p!=nil; p=p->next) + if(strcmp(ap->image, p->name)==0 && ap->width==p->width && ap->height==p->height){ + storebitmap(t, p->b); + free(ap->image); + ap->image=0; + w->changed=1; + return; + } + fd=urlopen(&url, GET, 0); + if(fd==-1){ + Err: + snprint(err, sizeof(err), "[%s: %r]", url.fullname); + free(t->text); + t->text=strdup(err); + free(ap->image); + ap->image=0; + w->changed=1; + close(fd); + return; + } + if(url.type!=GIF + && url.type!=JPEG + && url.type!=PNG + && url.type!=PIC + && url.type!=TIFF + && url.type!=XBM){ + werrstr("unknown image type"); + goto Err; + } + + if((fd = pipeline(pixcmd[url.type], fd)) < 0) + goto Err; + if(ap->width>0 || ap->height>0){ + char buf[80]; + char *p; + + p = buf; + p += sprint(p, "resize"); + if(ap->width>0) + p += sprint(p, " -x %d", ap->width); + if(ap->height>0) + p += sprint(p, " -y %d", ap->height); + if((fd = pipeline(buf, fd)) < 0) + goto Err; + } + b=readimage(display, fd, 1); + if(b==0){ + werrstr("can't read image"); + goto Err; + } + close(fd); + p = emallocz(sizeof(Pix), 1); + strncpy(p->name, ap->image, sizeof(p->name)); + p->b=b; + p->width=ap->width; + p->height=ap->height; + p->next=w->pix; + w->pix=p; + storebitmap(t, b); + free(ap->image); + ap->image=0; + w->changed=1; +} + +void getpix(Rtext *t, Www *w){ + Action *ap; + + for(;t!=0;t=t->next){ + ap=t->user; + if(ap && ap->image) + getimage(t, w); + } +} + +void freepix(void *p) +{ + Pix *x, *xx; + xx = p; + while(x = xx){ + xx = x->next; + freeimage(x->b); + free(x); + } +} diff --git a/sys/src/cmd/mothra/gopher.c b/sys/src/cmd/mothra/gopher.c new file mode 100644 index 000000000..2e231eae6 --- /dev/null +++ b/sys/src/cmd/mothra/gopher.c @@ -0,0 +1,38 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <panel.h> +#include "mothra.h" +void httpheader(Url *, char *); +/* + * Given a url, return a file descriptor on which caller can + * read a gopher document. + */ +int gopher(Url *url){ + int pfd[2]; + char port[30]; + if(pipe(pfd)==-1) return -1; + switch(rfork(RFFDG|RFPROC|RFNOWAIT)){ + case -1: + close(pfd[0]); + close(pfd[1]); + return -1; + case 0: + dup(pfd[1], 1); + close(pfd[0]); + close(pfd[1]); + sprint(port, "%d", url->port); + execl("/bin/aux/gopher2html", + "gopher2html", url->ipaddr, port, url->reltext+1, 0); + fprint(2, "Can't exec aux/gopher2html!\n"); + print("<head><title>Mothra error</title></head>\n"); + print("<body><h1>Mothra error</h1>\n"); + print("Can't exec aux/gopher2html!</body>\n"); + exits("no exec"); + default: + close(pfd[1]); + url->type=HTML; + return pfd[0]; + } +} diff --git a/sys/src/cmd/mothra/gopher2html.c b/sys/src/cmd/mothra/gopher2html.c new file mode 100644 index 000000000..e0c57d736 --- /dev/null +++ b/sys/src/cmd/mothra/gopher2html.c @@ -0,0 +1,230 @@ +/* + * Reads gopher output from a TCP port, outputs + * html on standard output. + * Usage: gopher2html gopher-string + * where gopher-string is the string sent to + * the gopher server to get the document. + * + * Gopher protocol is described in rfc1436 + */ +#include <u.h> +#include <libc.h> +char *cmd; +int ifd; +void errexit(char *s, ...){ + static char buf[1024]; + char *out; + va_list args; + va_start(args, s); + out = doprint(buf, buf+sizeof(buf), s, args); + va_end(args); + *out='\0'; + print("<head><title>%s error</title></head>\n", cmd); + print("<body><h1>%s error</h1>\n", cmd); + print("%s</body>\n", buf); + exits("gopher error"); +} +void wtext(char *buf, char *ebuf){ + char *bp; + for(bp=buf;bp!=ebuf;bp++){ + if(*bp=='<' || *bp=='>' || *bp=='&' || *bp=='"'){ + if(bp!=buf) write(1, buf, bp-buf); + buf=bp+1; + switch(*bp){ + case '<': print("<"); break; + case '>': print(">"); break; + case '&': print("&"); break; + case '"': print("""); break; + } + } + } + if(bp!=buf) write(1, buf, bp-buf); +} +void savefile(char *name, char *type){ + int fd, n; + char save[30], buf[1024]; + for(n=1;;n++){ + if(n==100) errexit("can't save binary file %s: %r", name); + sprint(save, "gopher.save.%d", n); + fd=create(save, OWRITE, 0444); + if(fd!=-1) break; + } + print("<head><title>%s</title></head\n", name); + print("<body><h1>%s</h1><p>\n", name); + print("Saving %s file %s in <tt>%s</tt>...\n", type, name, save); + while((n=read(ifd, buf, sizeof buf))>0) write(fd, buf, n); + close(fd); + print("done</body>\n"); +} +void copyfile(char *title){ + char buf[1024]; + int n; + print("<head><title>%s</title></head>\n", title); + print("<body><h1>%s</h1><pre>\n", title); + while((n=read(ifd, buf, sizeof buf))>0) wtext(buf, buf+n); + print("</pre></body>\n"); +} +/* + * A directory entry contains + * type name selector host port + * all tab separated, except type and name (type is one character) + */ +char ibuf[1024], *ibp, *eibuf; +#define EOF (-1) +int get(void){ + int n; +Again: + if(ibp==eibuf){ + n=read(ifd, ibuf, sizeof(ibuf)); + if(n<=0) return EOF; + eibuf=ibuf+n; + ibp=ibuf; + } + if(*ibp=='\r'){ + ibp++; + goto Again; + } + return *ibp++&255; +} +char *escape(char *in){ + static char out[516]; + char *op, *eop; + eop=out+512; + op=out; + for(;*in;in++){ + if(op<eop){ + if(strchr("/$-_@.&!*'(),", *in) + || 'a'<=*in && *in<='z' + || 'A'<=*in && *in<='Z' + || '0'<=*in && *in<='9') + *op++=*in; + else{ + sprint(op, "%%%.2X", *in&255); + op+=3; + } + } + } + *op='\0'; + return out; +} +void copydir(char *title){ + int type, c; + char name[513], *ename; + char selector[513]; + char host[513]; + char port[513]; + char *bp; + print("<head><title>%s</title></head>\n", title); + print("<body><h1>%s</h1><ul>\n", title); + for(;;){ + type=get(); + if(type==EOF || type=='.') break; + bp=name; + while((c=get())!=EOF && c!='\t') if(bp!=&name[512]) *bp++=c; + ename=bp; + bp=selector; + while((c=get())!=EOF && c!='\t') if(bp!=&selector[512]) *bp++=c; + *bp='\0'; + bp=host; + while((c=get())!=EOF && c!='\t') if(bp!=&host[512]) *bp++=c; + *bp='\0'; + bp=port; + while((c=get())!=EOF && c!='\t' && c!='\n') if(bp!=&port[512]) *bp++=c; + while(c!=EOF && c!='\n') c=get(); + *bp='\0'; + switch(type){ + case '3': + print("<li>"); + wtext(name, ename); + break; + case '7': + print("<li><isindex action=\"gopher://%s:%s/%c%s\">", + host, port, type, escape(selector)); + wtext(name, ename); + break; + default: + print("<li><a href=\"gopher://%s:%s/%c%s\">", + host, port, type, escape(selector)); + wtext(name, ename); + print("</a>\n"); + break; + } + } + print("</ul></body>\n"); +} +int hexdigit(int c){ + if('0'<=c && c<='9') return c-'0'; + if('a'<=c && c<='f') return c-'a'+10; + if('A'<=c && c<='F') return c-'A'+10; + return -1; +} +void unescape(char *s){ + char *t; + int hi, lo; + t=s; + while(*s){ + if(*s=='%' + && (hi=hexdigit(s[1]))>=0 + && (lo=hexdigit(s[2]))>=0){ + *t++=hi*16+lo; + s+=3; + } + else *t++=*s++; + } + *t='\0'; +} +void main(int argc, char *argv[]){ + char dialstr[1024]; + char *name; + cmd=argv[0]; + if(argc!=4) errexit("Usage: %s host port selector", argv[0]); + sprint(dialstr, "tcp!%s!%s", argv[1], argv[2]); + ifd=dial(dialstr, 0, 0, 0); + if(ifd==-1) errexit("can't call %s:%s", argv[1], argv[2]); + unescape(argv[3]); + switch(argv[3][0]){ + case '/': + fprint(ifd, "\r\n"); + copydir(argv[3]); + break; + case '\0': + fprint(ifd, "\r\n"); + copydir(argv[1]); + break; + case '7': /* index query */ + name=strchr(argv[3], '?'); + if(name!=0){ + if(name==argv[3]+1){ + argv[3][1]=argv[3][0]; + argv[3]++; + } + else + *name='\t'; + name++; + } + else + name=argv[3]; + fprint(ifd, "%s\r\n", argv[3]+1); + copydir(name); + break; + default: + fprint(ifd, "%s\r\n", argv[3]+1); + name=strrchr(argv[3], '/'); + if(name==0) name=argv[3]; + else name++; + switch(argv[3][0]){ + default: errexit("sorry, can't handle %s (type %c)", + argv[3]+1, argv[3][0]); + case '0': copyfile(name); break; + case '1': copydir(name); break; + case '4': savefile(name, "Macintosh BINHEX"); break; + case '5': savefile(name, "DOS binary"); break; + case '6': savefile(name, "uuencoded"); break; + case '9': savefile(name, "binary"); break; + case 'g': savefile(name, "GIF"); break; + case 'I': savefile(name, "some sort of image"); break; + } + break; + } + exits(0); +} diff --git a/sys/src/cmd/mothra/help.html b/sys/src/cmd/mothra/help.html new file mode 100644 index 000000000..771f4b138 --- /dev/null +++ b/sys/src/cmd/mothra/help.html @@ -0,0 +1,78 @@ +<html><head><title>Mothra help</title> +</head> +<body> +<H1>Mothra Help</H1> +<p> +Mothra is a World-wide Web browser. Its display looks like this: +<p><img src="file:display.pic" alt="[mothra display]"> +<p>The display's regions, from top to bottom, are: +<ul> +<li>Error messages and other information. +<li>A text input window in which <a href="#commands">commands</a> can be typed. +<li>A scrollable list of titles of previously visited documents, with the most recent first. +Pointing at one of these lines with mouse button 1 revisits the document. +<li>The title of the currently-displayed document. +<li>The URL of the currently-displayed document. +<li>The scrollable document display. Underlined text and +images surrounded by boxes may be pointed at with button 1 to +visit the files that they refer to. Files that are not +HTML documents (for example images or mailto: urls) cause +<i>9v</i> or <i>mail</i> to pop up in a new 8½ window. +</ul> +<h4>Mouse Action</H4> +<p>Pointing with button +2 instead of button 1 selects a url without following it; +the url will be displayed in the selection: area and commands +will refer to the url, but it will not be drawn in the document display. +Button 3 pops up a command menu that contains +<ul> +<li><b>alt display</b><br>switches to (or from) the alternate display, which shows only +the scrollable document display area. This might be useful when running mothra +in a small window. +<li><b>snarf url</b><br>copies the selected url into the snarf buffer. +<li><b>paste</b><br>appends the snarf buffer to the command window. +<li><b>inline pix</b><br>turn off/on loading of inline images. Image maps cannot be disabled. +<li><b>fix cmap</b><br>reload the default plan 9 colormap +<li><b>save hit</b><br>appends the selected url to file:$home/lib/hit.html +<li><b>hit list</b><br>displays file:$home/lib/hit.html +<li><b>exit</b> +</ul> +<a name="#commands"><h4>Commands</h4></a> +<p>The commands you can type are: +<ul> +<li>g [url]<br>get the page with the given url (default, the selection.) +<li>r [url]<br>refresh the display if the URL changes. +Otherwise, you will probably see a cached version. +<li>s file<br>save the current page in the given file. +<li>w file<br>write a bitmap image of the document display area in the given file. +<li>q<br>exit. +<li>?<br>get help. +<li>h<br>get help. +</ul> +<p> +<h4>Configuration</h4> +Mothra gets configuration information from the environment. +<ul> +<li>$url<br>The default <i>url</i> displayed when mothra starts. +A <i>url</i> given on the command line overrides this. +The default is <b>/sys/lib/mothra/start.html</b> +<li>$httpproxy<br>The network address of an http proxy server, +in the format expected by dial(2). If $httpproxy is not set +or is null, no proxy server is used. +</ul> +<h4>Command line</h4> +If the mothra command has an argument, it is the name of a <i>url</i> to visit +instead of the startup page. Giving mothra the <b>-i</b> flag disables loading +of inline images. The <b>inline pix</b> menu item will reset this option. +<h4>Files</h4> +Mothra creates several files in $home/lib/mothra. +<ul> +<li>mothra.log<br>a list of all the url's visited +<li>mothra.err<br>a log of error messages, mostly uninteresting +<li>hit.html<br>the hit list used by the <b>save hit</b> +and <b>hit list</b> commands. Since <b>save hit</b> only +adds new urls to the end of this file, it is safe to edit it +to add annotation or sort the saved urls. +</ul> +</body> +</html> diff --git a/sys/src/cmd/mothra/html.h b/sys/src/cmd/mothra/html.h new file mode 100644 index 000000000..f5462ceea --- /dev/null +++ b/sys/src/cmd/mothra/html.h @@ -0,0 +1,201 @@ +/* + * Parameters + */ +#define NSTACK 100 /* html grammar is not recursive, so 30 or so should do */ +#define NHBUF 8192 /* Input buffer size */ +#define NPEEKC 3 /* Maximum lookahead */ +#define NTOKEN 1024 /* Maximum token length */ +#define NATTR 512 /* Maximum number of attributes of a tag */ +typedef struct Pair Pair; +typedef struct Tag Tag; +typedef struct Stack Stack; +typedef struct Hglob Hglob; +typedef struct Form Form; +typedef struct Entity Entity; +struct Pair{ + char *name; + char *value; +}; +struct Entity{ + char *name; + Rune value; +}; +struct Tag{ + char *name; + int action; +}; +struct Stack{ + int tag; /* html tag being processed */ + int pre; /* in preformatted text? */ + int font; /* typeface */ + int size; /* point size of text */ + int margin; /* left margin position */ + int indent; /* extra indent at paragraph start */ + int number; /* paragraph number */ + int ismap; /* flag of <img> */ + int width; /* size of image */ + int height; + int table; /* depth of table nesting */ + char image[NNAME]; /* arg of <img> */ + char link[NNAME]; /* arg of <a href=...> */ + char name[NNAME]; /* arg of <a name=...> */ +}; + +/* + * Globals -- these are packed up into a struct that gets passed around + * so that multiple parsers can run concurrently + */ +struct Hglob{ + char *tp; /* pointer in text buffer */ + char *name; /* input file name */ + int hfd; /* input file descriptor */ + char hbuf[NHBUF]; /* input buffer */ + char *hbufp; /* next character in buffer */ + char *ehbuf; /* end of good characters in buffer */ + int heof; /* end of file flag */ + int peekc[NPEEKC]; /* characters to re-read */ + int npeekc; /* # of characters to re-read */ + char token[NTOKEN]; /* if token type is TEXT */ + Pair attr[NATTR]; /* tag attribute/value pairs */ + int nsp; /* # of white-space characters before TEXT token */ + int spacc; /* place to accumulate more spaces */ + /* if negative, won't accumulate! */ + int tag; /* if token type is TAG or END */ + Stack stack[NSTACK]; /* parse stack */ + Stack *state; /* parse stack pointer */ + int lineno; /* input line number */ + int linebrk; /* flag set if we require a line-break in output */ + int para; /* flag set if we need an indent at the break */ + char *text; /* text buffer */ + char *etext; /* end of text buffer */ + Form *form; /* data for form under construction */ + Www *dst; /* where the text goes */ + char charset[NNAME]; +}; + +/* + * Token types + */ +enum{ + TAG=1, + ENDTAG, + TEXT, +}; + +/* + * Magic characters corresponding to + * literal < followed by / ! or alpha, + * literal > and + * end of file + */ +#define STAG 65536 +#define ETAG 65537 +#define EOF -1 + +/* + * fonts + */ +enum{ + ROMAN, + ITALIC, + BOLD, + CWIDTH, +}; + +/* + * font sizes + */ +enum{ + SMALL, + NORMAL, + LARGE, + ENORMOUS, +}; + +/* + * Token names for the html parser. + * Tag_end corresponds to </end> tags. + * Tag_text tags text not in a tag. + * Those two must follow the others. + */ +enum{ + Tag_comment, + Tag_a, + Tag_address, + Tag_b, + Tag_base, + Tag_blockquot, + Tag_body, + Tag_br, + Tag_center, + Tag_cite, + Tag_code, + Tag_dd, + Tag_dfn, + Tag_dir, + Tag_dl, + Tag_dt, + Tag_em, + Tag_font, + Tag_form, + Tag_h1, + Tag_h2, + Tag_h3, + Tag_h4, + Tag_h5, + Tag_h6, + Tag_head, + Tag_hr, + Tag_html, + Tag_i, + Tag_img, + Tag_input, + Tag_isindex, + Tag_kbd, + Tag_key, + Tag_li, + Tag_link, + Tag_listing, + Tag_menu, + Tag_meta, + Tag_nextid, + Tag_ol, + Tag_option, + Tag_p, + Tag_plaintext, + Tag_pre, + Tag_samp, + Tag_select, + Tag_strong, + Tag_textarea, + Tag_title, + Tag_tt, + Tag_u, + Tag_ul, + Tag_var, + Tag_xmp, + Tag_frame, /* rm 5.8.97 */ + Tag_table, /* rm 3.8.00 */ + Tag_td, + Tag_tr, + Tag_script, + Tag_style, + Tag_end, /* also used to indicate unrecognized start tag */ + + Tag_text, +}; +enum{ + NTAG=Tag_end, + END=1, /* tag must have a matching end tag */ + NOEND, /* tag must not have a matching end tag */ + OPTEND, /* tag may have a matching end tag */ + ERR, /* tag must not occur */ +}; +Tag tag[]; +Entity pl_entity[]; +int pl_entities; +void rdform(Hglob *); +void endform(Hglob *); +char *pl_getattr(Pair *, char *); +int pl_hasattr(Pair *, char *); +void pl_htmloutput(Hglob *, int, char *, Field *); diff --git a/sys/src/cmd/mothra/html.syntax.c b/sys/src/cmd/mothra/html.syntax.c new file mode 100644 index 000000000..3001245ef --- /dev/null +++ b/sys/src/cmd/mothra/html.syntax.c @@ -0,0 +1,340 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <panel.h> +#include "mothra.h" +#include "html.h" +Tag tag[]={ +[Tag_comment] "!--", NOEND, +[Tag_a] "a", END, +[Tag_address] "address", END, +[Tag_b] "b", END, +[Tag_base] "base", NOEND, +[Tag_blockquot] "blockquote", END, +[Tag_body] "body", END, /* OPTEND */ +[Tag_br] "br", NOEND, +[Tag_center] "center", END, +[Tag_cite] "cite", END, +[Tag_code] "code", END, +[Tag_dd] "dd", NOEND, /* OPTEND */ +[Tag_dfn] "dfn", END, +[Tag_dir] "dir", END, +[Tag_dl] "dl", END, +[Tag_dt] "dt", NOEND, /* OPTEND */ +[Tag_em] "em", END, +[Tag_font] "font", END, +[Tag_form] "form", END, +[Tag_h1] "h1", END, +[Tag_h2] "h2", END, +[Tag_h3] "h3", END, +[Tag_h4] "h4", END, +[Tag_h5] "h5", END, +[Tag_h6] "h6", END, +[Tag_head] "head", END, /* OPTEND */ +[Tag_hr] "hr", NOEND, +[Tag_html] "html", END, /* OPTEND */ +[Tag_i] "i", END, +[Tag_input] "input", NOEND, +[Tag_img] "img", NOEND, +[Tag_isindex] "isindex", NOEND, +[Tag_kbd] "kbd", END, +[Tag_key] "key", END, +[Tag_li] "li", NOEND, /* OPTEND */ +[Tag_link] "link", NOEND, +[Tag_listing] "listing", END, +[Tag_menu] "menu", END, +[Tag_meta] "meta", NOEND, +[Tag_nextid] "nextid", NOEND, +[Tag_ol] "ol", END, +[Tag_option] "option", NOEND, /* OPTEND */ +[Tag_p] "p", NOEND, /* OPTEND */ +[Tag_plaintext] "plaintext", NOEND, +[Tag_pre] "pre", END, +[Tag_samp] "samp", END, +[Tag_script] "script", END, +[Tag_style] "style", END, +[Tag_select] "select", END, +[Tag_strong] "strong", END, +[Tag_table] "table", END, +[Tag_td] "td", END, +[Tag_textarea] "textarea", END, +[Tag_title] "title", END, +[Tag_tr] "tr", END, +[Tag_tt] "tt", END, +[Tag_u] "u", END, +[Tag_ul] "ul", END, +[Tag_var] "var", END, +[Tag_xmp] "xmp", END, +[Tag_frame] "frame", NOEND, +[Tag_end] 0, ERR, +}; +Entity pl_entity[]={ +"AElig", L'Æ', +"Aacute", L'Á', +"Acirc", L'Â', +"Agrave", L'À', +"Alpha", L'Α', +"Aring", L'Å', +"Atilde", L'Ã', +"Auml", L'Ä', +"Beta", L'Β', +"Ccedil", L'Ç', +"Chi", L'Χ', +"Dagger", L'‡', +"Delta", L'Δ', +"ETH", L'Ð', +"Eacute", L'É', +"Ecirc", L'Ê', +"Egrave", L'È', +"Epsilon", L'Ε', +"Eta", L'Η', +"Euml", L'Ë', +"Gamma", L'Γ', +"Iacute", L'Í', +"Icirc", L'Î', +"Igrave", L'Ì', +"Iota", L'Ι', +"Iuml", L'Ï', +"Kappa", L'Κ', +"Lambda", L'Λ', +"Mu", L'Μ', +"Ntilde", L'Ñ', +"Nu", L'Ν', +"OElig", L'Œ', +"Oacute", L'Ó', +"Ocirc", L'Ô', +"Ograve", L'Ò', +"Omega", L'Ω', +"Omicron", L'Ο', +"Oslash", L'Ø', +"Otilde", L'Õ', +"Ouml", L'Ö', +"Phi", L'Φ', +"Pi", L'Π', +"Prime", L'″', +"Psi", L'Ψ', +"Rho", L'Ρ', +"Scaron", L'Š', +"Sigma", L'Σ', +"THORN", L'Þ', +"Tau", L'Τ', +"Theta", L'Θ', +"Uacute", L'Ú', +"Ucirc", L'Û', +"Ugrave", L'Ù', +"Upsilon", L'Υ', +"Uuml", L'Ü', +"Xi", L'Ξ', +"Yacute", L'Ý', +"Yuml", L'Ÿ', +"Zeta", L'Ζ', +"aacute", L'á', +"acirc", L'â', +"acute", L'´', +"aelig", L'æ', +"agrave", L'à', +"alefsym", L'ℵ', +"alpha", L'α', +"amp", L'&', +"and", L'∧', +"ang", L'∠', +"aring", L'å', +"asymp", L'≈', +"atilde", L'ã', +"auml", L'ä', +"bdquo", L'„', +"beta", L'β', +"brvbar", L'¦', +"bull", L'•', +"cap", L'∩', +"ccedil", L'ç', +"cdots", L'⋯', +"cedil", L'¸', +"cent", L'¢', +"chi", L'χ', +"circ", L'ˆ', +"clubs", L'♣', +"cong", L'≅', +"copy", L'©', +"crarr", L'↵', +"cup", L'∪', +"curren", L'¤', +"dArr", L'⇓', +"dagger", L'†', +"darr", L'↓', +"ddots", L'⋱', +"deg", L'°', +"delta", L'δ', +"diams", L'♦', +"divide", L'÷', +"eacute", L'é', +"ecirc", L'ê', +"egrave", L'è', +"emdash", L'—', +"empty", L'∅', +"emsp", L' ', +"endash", L'–', +"ensp", L' ', +"epsilon", L'ε', +"equiv", L'≡', +"eta", L'η', +"eth", L'ð', +"euml", L'ë', +"euro", L'€', +"exist", L'∃', +"fnof", L'ƒ', +"forall", L'∀', +"frac12", L'½', +"frac14", L'¼', +"frac34", L'¾', +"frasl", L'⁄', +"gamma", L'γ', +"ge", L'≥', +"gt", L'>', +"hArr", L'⇔', +"harr", L'↔', +"hearts", L'♥', +"hellip", L'…', +"iacute", L'í', +"icirc", L'î', +"iexcl", L'¡', +"igrave", L'ì', +"image", L'ℑ', +"infin", L'∞', +"int", L'∫', +"iota", L'ι', +"iquest", L'¿', +"isin", L'∈', +"iuml", L'ï', +"kappa", L'κ', +"lArr", L'⇐', +"lambda", L'λ', +"lang", L'〈', +"laquo", L'«', +"larr", L'←', +"lceil", L'⌈', +"ldots", L'…', +"ldquo", L'“', +"le", L'≤', +"lfloor", L'⌊', +"lowast", L'∗', +"loz", L'◊', +"lrm", L'', +"lsaquo", L'‹', +"lsquo", L'‘', +"lt", L'<', +"macr", L'¯', +"mdash", L'—', +"micro", L'µ', +"middot", L'·', +"minus", L'−', +"mu", L'μ', +"nabla", L'∇', +"nbsp", L' ', +"ndash", L'–', +"ne", L'≠', +"ni", L'∋', +"not", L'¬', +"notin", L'∉', +"nsub", L'⊄', +"ntilde", L'ñ', +"nu", L'ν', +"oacute", L'ó', +"ocirc", L'ô', +"oelig", L'œ', +"ograve", L'ò', +"oline", L'‾', +"omega", L'ω', +"omicron", L'ο', +"oplus", L'⊕', +"or", L'∨', +"ordf", L'ª', +"ordm", L'º', +"oslash", L'ø', +"otilde", L'õ', +"otimes", L'⊗', +"ouml", L'ö', +"para", L'¶', +"part", L'∂', +"permil", L'‰', +"perp", L'⊥', +"phi", L'φ', +"pi", L'π', +"piv", L'ϖ', +"plusmn", L'±', +"pound", L'£', +"prime", L'′', +"prod", L'∏', +"prop", L'∝', +"psi", L'ψ', +"quad", L' ', +"quot", L'"', +"rArr", L'⇒', +"radic", L'√', +"rang", L'〉', +"raquo", L'»', +"rarr", L'→', +"rceil", L'⌉', +"rdquo", L'”', +"real", L'ℜ', +"reg", L'®', +"rfloor", L'⌋', +"rho", L'ρ', +"rlm", L'', +"rsaquo", L'›', +"rsquo", L'’', +"sbquo", L'‚', +"scaron", L'š', +"sdot", L'⋅', +"sect", L'§', +"shy", L'', +"sigma", L'σ', +"sigmaf", L'ς', +"sim", L'∼', +"sp", L' ', +"spades", L'♠', +"sub", L'⊂', +"sube", L'⊆', +"sum", L'∑', +"sup", L'⊃', +"sup1", L'¹', +"sup2", L'²', +"sup3", L'³', +"supe", L'⊇', +"szlig", L'ß', +"tau", L'τ', +"there4", L'∴', +"theta", L'θ', +"thetasym", L'ϑ', +"thinsp", L' ', +"thorn", L'þ', +"tilde", L'˜', +"times", L'×', +"trade", L'™', +"uArr", L'⇑', +"uacute", L'ú', +"uarr", L'↑', +"ucirc", L'û', +"ugrave", L'ù', +"uml", L'¨', +"upsih", L'ϒ', +"upsilon", L'υ', +"uuml", L'ü', +"varepsilon", L'∈', +"varphi", L'ϕ', +"varpi", L'ϖ', +"varrho", L'ϱ', +"vdots", L'⋮', +"vsigma", L'ς', +"vtheta", L'ϑ', +"weierp", L'℘', +"xi", L'ξ', +"yacute", L'ý', +"yen", L'¥', +"yuml", L'ÿ', +"zeta", L'ζ', +"zwj", L'', +"zwnj", L'', +}; +int pl_entities = nelem(pl_entity); diff --git a/sys/src/cmd/mothra/http.c b/sys/src/cmd/mothra/http.c new file mode 100644 index 000000000..deb36f391 --- /dev/null +++ b/sys/src/cmd/mothra/http.c @@ -0,0 +1,486 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <panel.h> + +#include <libsec.h> /* tlsClient */ + +#include "mothra.h" +typedef struct Cache Cache; +struct Cache{ + int fd; /* file descriptor on which to write cached data */ + ulong hash; /* hash of url, used to compute cache file name */ + int modtime; /* time at which cache entry was created */ + int type; /* url->type of cached entry */ +}; +void httpheader(Url *, char *); +int httpresponse(char *); +static char *proxyserver; /* name of proxy server */ +void exitnow(void*, char*){ + noted(NDFLT); +} +void hashname(char *name, int n, char *stem, Cache *c){ + snprint(name, n, "/sys/lib/mothra/cache/%s.%.8lux", stem, c->hash); +} +// #define CacheEnabled +/* + * Returns fd of cached file, if found (else -1) + * Fills in Cache data structure for caller + * If stale is set, caller has determined that the existing + * cache entry for this url is stale, so we shouldn't bother re-examining it. + */ +int cacheopen(Url *url, Cache *c, int stale){ +#ifdef CacheEnabled + int fd, n; + char name[NNAME+1], *s, *l; + /* + * If we're using a proxy server or the url contains a ? or =, + * don't even bother. + */ + if(proxyserver || strchr(url->reltext, '?')!=0 || strchr(url->reltext, '=')!=0){ + c->fd=-1; + return -1; + } + c->hash=0; + for(s=url->fullname,n=0;*s;s++,n++) c->hash=c->hash*n+(*s&255); + if(stale) + fd=-1; + else{ + hashname(name, sizeof(name), "cache", c); + fd=open(name, OREAD); + } + if(fd==-1){ + hashname(name, sizeof(name), "write", c); + c->fd=create(name, OWRITE, 0444); + if(c->fd!=-1) + fprint(c->fd, "%s %10ld\n", url->fullname, time(0)); + return -1; + } + c->fd=-1; + for(l=name;l!=&name[NNAME];l+=n){ + n=&name[NNAME]-l; + n=read(fd, l, n); + if(n<=0) break; + } + *l='\0'; + s=strchr(name, ' '); + if(s==0){ + close(fd); + return -1; + } + *s='\0'; + if(strcmp(url->fullname, name)!=0){ + close(fd); + return -1; + } + c->modtime=atol(++s); + s=strchr(s, '\n'); + if(s==0){ + close(fd); + return -1; + } + s++; + if(strncmp(s, "type ", 5)!=0){ + close(fd); + return -1; + } + c->type=atoi(s+5); + s=strchr(s+5, '\n'); + if(s==0){ + close(fd); + return -1; + } + + seek(fd, s-name+1, 0); + return fd; +#else + c->fd=-1; + return -1; +#endif +} +/* + * Close url->fd and either rename the cache file or + * remove it, depending on success + */ +void cacheclose(Cache *c, int success){ + char wname[NNAME+1], cname[NNAME+1], *celem; + Dir *wdir; + if(c->fd==-1) return; + close(c->fd); + hashname(wname, sizeof(wname), "write", c); + if(!success){ + remove(wname); + return; + } + if((wdir = dirstat(wname)) == 0) + return; + hashname(cname, sizeof(cname), "cache", c); + if(access(cname, 0) == 0){ + if(remove(cname)==-1){ + remove(wname); + free(wdir); + return; + } + /* + * This looks implausible, but it's what the mv command does + */ + do; while(remove(cname)!=-1); + } + celem=strrchr(cname, '/'); + if(celem==0) celem=cname; + else celem++; + strcpy(wdir->name, celem); + if(dirwstat(wname, wdir)==-1) + remove(wname); + free(wdir); +} +static char *wkday[]={ + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" +}; +static char *month[]={ + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; +/* + * Sun, 06 Nov 1994 08:49:38 GMT + * 123456789 123456789 123456789 + */ +char *rfc1123date(long time){ + static char buf[50]; + Tm *t; + t=gmtime(time); + snprint(buf, sizeof(buf), "%s, %2.2d %s %4.4d %2.2d:%2.2d:%2.2d GMT", + wkday[t->wday], t->mday, month[t->mon], t->year+1900, + t->hour, t->min, t->sec); + return buf; +} +/* + * Given a url, return a file descriptor on which caller can + * read an http document. As a side effect, we parse the + * http header and fill in some fields in the url. + * The caller is responsible for processing redirection loops. + * Method can be either GET or POST. If method==post, body + * is the text to be posted. + */ +int http(Url *url, int method, char *body){ + char *addr, *com; + int fd, n, nnl, len; + int ncom, m; + int pfd[2]; + char buf[1024], *bp, *ebp; + char line[1024+1], *lp, *elp; + char authstr[NAUTH], *urlname; + int gotresponse; + int response; + Cache cache; + int cfd, cookiefd; + static int firsttime=1; + static int gotcookies; + + if(firsttime){ + proxyserver=getenv("httpproxy"); + gotcookies=(access("/mnt/webcookies/http", AREAD|AWRITE)==0); + firsttime=0; + } + *authstr = 0; +Authorize: + cfd=-1; + cookiefd=-1; + if(proxyserver && proxyserver[0]!='\0'){ + addr=strdup(proxyserver); + urlname=url->fullname; + } + else{ + addr=emalloc(strlen(url->ipaddr)+100); + sprint(addr, "tcp!%s!%d", url->ipaddr, url->port); + urlname=url->reltext; + } + fd=dial(addr, 0, 0, 0); + free(addr); + if(fd==-1) goto ErrReturn; + if(url->ssl){ + int tfd; + TLSconn conn; + + memset(&conn, 0, sizeof conn); + tfd = tlsClient(fd, &conn); + if(tfd < 0){ + close(fd); + goto ErrReturn; + } + /* BUG: check cert here? */ + if(conn.cert) + free(conn.cert); + close(fd); + fd = tfd; + } + ncom=strlen(urlname)+sizeof(buf); + com=emalloc(ncom+2); + cache.fd=-1; + switch(method){ + case GET: + cfd=cacheopen(url, &cache, 0); + if(cfd==-1) + n=sprint(com, + "GET %s HTTP/1.0\r\n%s" + "Accept: */*\r\n" + "User-agent: mothra/%s\r\n" + "Host: %s\r\n", + urlname, authstr, version, url->ipaddr); + else + n=sprint(com, + "GET %s HTTP/1.0\r\n%s" + "If-Modified-since: %s\r\n" + "Accept: */*\r\n" + "User-agent: mothra/%s\r\n" + "Host: %s\r\n", + urlname, authstr, rfc1123date(cache.modtime), version, url->ipaddr); + break; + case POST: + len=strlen(body); + n=sprint(com, + "POST %s HTTP/1.0\r\n%s" + "Content-type: application/x-www-form-urlencoded\r\n" + "Content-length: %d\r\n" + "User-agent: mothra/%s\r\n", + urlname, authstr, len, version); + break; + } + if(gotcookies && (cookiefd=open("/mnt/webcookies/http", ORDWR)) >= 0){ + if(fprint(cookiefd, "%s", url->fullname) > 0){ + while((m=read(cookiefd, buf, sizeof buf)) > 0){ + if(m+n>ncom){ + if(write(fd, com, n)!= n){ + free(com); + goto fdErrReturn; + } + n=0; + com[0] = '\0'; + } + strncat(com, buf, m); + n += m; + } + }else{ + close(cookiefd); + cookiefd=-1; + } + } + strcat(com, "\r\n"); + n += 2; + switch(method){ + case GET: + if(write(fd, com, n)!=n){ + free(com); + goto fdErrReturn; + } + break; + case POST: + if(write(fd, com, n)!=n + || write(fd, body, len)!=len){ + free(com); + goto fdErrReturn; + } + break; + } + free(com); + if(pipe(pfd)==-1) goto fdErrReturn; + n=read(fd, buf, 1024); + if(n<=0){ + EarlyEof: + if(n==0){ + fprint(2, "%s: EOF in header\n", url->fullname); + werrstr("EOF in header"); + } + pfdErrReturn: + close(pfd[0]); + close(pfd[1]); + fdErrReturn: + close(fd); + ErrReturn: + if(cookiefd>=0) + close(cookiefd); + cacheclose(&cache, 0); + return -1; + } + bp=buf; + ebp=buf+n; + url->type=0; + if(strncmp(buf, "HTTP/", 5)==0){ /* hack test for presence of header */ + SET(response); + gotresponse=0; + url->redirname[0]='\0'; + nnl=0; + lp=line; + elp=line+1024; + while(nnl!=2){ + if(bp==ebp){ + n=read(fd, buf, 1024); + if(n<=0) goto EarlyEof; + ebp=buf+n; + bp=buf; + } + if(*bp!='\r'){ + if(nnl==1 && (!gotresponse || (*bp!=' ' && *bp!='\t'))){ + *lp='\0'; + if(gotresponse){ + if(cookiefd>=0 && cistrncmp(line, "Set-Cookie:", 11) == 0) + fprint(cookiefd, "%s\n", line); + httpheader(url, line); + }else{ + response=httpresponse(line); + gotresponse=1; + } + lp=line; + } + if(*bp=='\n') nnl++; + else{ + nnl=0; + if(lp!=elp) *lp++=*bp; + } + } + bp++; + } + if(gotresponse) switch(response){ + case 200: /* OK */ + case 201: /* Created */ + case 202: /* Accepted */ + break; + case 204: /* No Content */ + werrstr("URL has no content"); + goto pfdErrReturn; + case 301: /* Moved Permanently */ + case 302: /* Moved Temporarily */ + if(url->redirname[0]){ + url->type=FORWARD; + werrstr("URL forwarded"); + goto pfdErrReturn; + } + break; + case 304: /* Not Modified */ + if(cfd!=-1){ + url->type=cache.type; + close(pfd[0]); + close(pfd[1]); + close(fd); + if(cookiefd>=0) + close(cookiefd); + return cfd; + } + werrstr("Not modified!"); + goto pfdErrReturn; + case 400: /* Bad Request */ + werrstr("Bad Request to server"); + goto pfdErrReturn; + case 401: /* Unauthorized */ + case 402: /* ??? */ + if(*authstr == 0){ + close(pfd[0]); + close(pfd[1]); + close(fd); + if(auth(url, authstr, sizeof(authstr)) == 0){ + if(cfd!=-1) + close(cfd); + goto Authorize; + } + goto ErrReturn; + } + break; + case 403: /* Forbidden */ + werrstr("Forbidden by server"); + goto pfdErrReturn; + case 404: /* Not Found */ + werrstr("Not found on server"); + goto pfdErrReturn; + case 500: /* Internal server error */ + werrstr("Server choked"); + goto pfdErrReturn; + case 501: /* Not implemented */ + werrstr("Server can't do it!"); + goto pfdErrReturn; + case 502: /* Bad gateway */ + werrstr("Bad gateway"); + goto pfdErrReturn; + case 503: /* Service unavailable */ + werrstr("Service unavailable"); + goto pfdErrReturn; + } + } + if(cfd!=-1){ + close(cfd); + cfd=cacheopen(url, &cache, 1); + } + if(cookiefd>=0){ + close(cookiefd); + cookiefd=-1; + } + if(url->type==0) + url->type=suffix2type(url->fullname); + if(cache.fd!=-1) fprint(cache.fd, "type %d\n", url->type); + switch(rfork(RFFDG|RFPROC|RFNOWAIT)){ + case -1: + werrstr("Can't fork"); + goto pfdErrReturn; + case 0: + notify(exitnow); /* otherwise write on closed pipe below may cause havoc */ + close(pfd[0]); + if(bp!=ebp){ + write(pfd[1], bp, ebp-bp); + if(cache.fd!=-1) write(cache.fd, bp, ebp-bp); + } + while((n=read(fd, buf, 1024))>0){ + write(pfd[1], buf, n); + if(cache.fd!=-1) write(cache.fd, buf, n); + } + cacheclose(&cache, 1); + _exits(0); + default: + if(cache.fd!=-1) close(cache.fd); + close(pfd[1]); + close(fd); + return pfd[0]; + } +} +/* + * Process a header line for this url + */ +void httpheader(Url *url, char *line){ + char *name, *arg, *s, *arg2; + name=line; + while(*name==' ' || *name=='\t') name++; + for(s=name;*s!=':';s++) if(*s=='\0') return; + *s++='\0'; + while(*s==' ' || *s=='\t') s++; + arg=s; + while(*s!=' ' && *s!='\t' && *s!=';' && *s!='\0') s++; + while(*s == ' ' || *s == '\t' || *s == ';') + *s++ = '\0'; + arg2 = s; + if(cistrcmp(name, "Content-Type")==0){ + url->type|=content2type(arg, url->reltext); + if(cistrncmp(arg2, "charset=", 8) == 0){ + strncpy(url->charset, arg2+8, sizeof(url->charset)); + } else { + url->charset[0] = '\0'; + } + } + else if(cistrcmp(name, "Content-Encoding")==0) + url->type|=encoding2type(arg); + else if(cistrcmp(name, "WWW-authenticate")==0){ + strncpy(url->authtype, arg, sizeof(url->authtype)); + strncpy(url->autharg, arg2, sizeof(url->autharg)); + } + else if(cistrcmp(name, "URI")==0){ + if(*arg!='<') return; + ++arg; + for(s=arg;*s!='>';s++) if(*s=='\0') return; + *s='\0'; + strncpy(url->redirname, arg, sizeof(url->redirname)); + } + else if(cistrcmp(name, "Location")==0) + strncpy(url->redirname, arg, sizeof(url->redirname)); +} +int httpresponse(char *line){ + while(*line!=' ' && *line!='\t' && *line!='\0') line++; + return atoi(line); +} diff --git a/sys/src/cmd/mothra/libpanel/button.c b/sys/src/cmd/mothra/libpanel/button.c new file mode 100644 index 000000000..d6ac4076c --- /dev/null +++ b/sys/src/cmd/mothra/libpanel/button.c @@ -0,0 +1,189 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <panel.h> +#include "pldefs.h" +typedef struct Button Button; +struct Button{ + int btype; /* button type */ + Icon *icon; /* what to write on the button */ + int check; /* for check/radio buttons */ + void (*hit)(Panel *, int, int); /* call back user code on check/radio hit */ + void (*menuhit)(int, int); /* call back user code on menu item hit */ + void (*pl_buttonhit)(Panel *, int); /* call back user code on button hit */ + int index; /* arg to menuhit */ + int buttons; +}; +/* + * Button types + */ +#define BUTTON 1 +#define CHECK 2 +#define RADIO 3 +void pl_drawbutton(Panel *p){ + Rectangle r; + Button *bp; + bp=p->data; + r=pl_box(p->b, p->r, p->state); + switch(bp->btype){ + case CHECK: + r=pl_check(p->b, r, bp->check); + break; + case RADIO: + r=pl_radio(p->b, r, bp->check); + break; + } + pl_drawicon(p->b, r, PLACECEN, p->flags, bp->icon); +} +int pl_hitbutton(Panel *p, Mouse *m){ + int oldstate, hitme; + Panel *sib; + Button *bp; + bp=p->data; + oldstate=p->state; + if(m->buttons&OUT){ + hitme=0; + p->state=UP; + } + else if(m->buttons&7){ + hitme=0; + p->state=DOWN; + bp->buttons=m->buttons; + } + else{ /* mouse inside, but no buttons down */ + hitme=p->state==DOWN; + p->state=UP; + } + if(hitme) switch(bp->btype){ + case CHECK: + if(hitme) bp->check=!bp->check; + break; + case RADIO: + if(bp->check) bp->check=0; + else{ + if(p->parent){ + for(sib=p->parent->child;sib;sib=sib->next){ + if(sib->hit==pl_hitbutton + && ((Button *)sib->data)->btype==RADIO + && ((Button *)sib->data)->check){ + ((Button *)sib->data)->check=0; + pldraw(sib, p->b); + } + } + } + bp->check=1; + } + break; + } + if(hitme || oldstate!=p->state) pldraw(p, p->b); + if(hitme && bp->hit){ + bp->hit(p, bp->buttons, bp->check); + p->state=UP; + } + return 0; +} +void pl_typebutton(Panel *g, Rune c){ + USED(g, c); +} +Point pl_getsizebutton(Panel *p, Point children){ + Point s; + int ckw; + Button *bp; + USED(children); /* shouldn't have any children */ + bp=p->data; + s=pl_iconsize(p->flags, bp->icon); + if(bp->btype!=BUTTON){ + ckw=pl_ckwid(); + if(s.y<ckw){ + s.x+=ckw; + s.y=ckw; + } + else s.x+=s.y; + } + return pl_boxsize(s, p->state); +} +void pl_childspacebutton(Panel *g, Point *ul, Point *size){ + USED(g, ul, size); +} +void pl_initbtype(Panel *v, int flags, Icon *icon, void (*hit)(Panel *, int, int), int btype){ + Button *bp; + bp=v->data; + v->flags=flags|LEAF; + v->state=UP; + v->draw=pl_drawbutton; + v->hit=pl_hitbutton; + v->type=pl_typebutton; + v->getsize=pl_getsizebutton; + v->childspace=pl_childspacebutton; + bp->btype=btype; + bp->check=0; + bp->hit=hit; + bp->icon=icon; + switch(btype){ + case BUTTON: v->kind="button"; break; + case CHECK: v->kind="checkbutton"; break; + case RADIO: v->kind="radiobutton"; break; + } +} +void pl_buttonhit(Panel *p, int buttons, int check){ + USED(check); + if(((Button *)p->data)->pl_buttonhit) ((Button *)p->data)->pl_buttonhit(p, buttons); +} +void plinitbutton(Panel *p, int flags, Icon *icon, void (*hit)(Panel *, int)){ + ((Button *)p->data)->pl_buttonhit=hit; + pl_initbtype(p, flags, icon, pl_buttonhit, BUTTON); +} +void plinitcheckbutton(Panel *p, int flags, Icon *icon, void (*hit)(Panel *, int, int)){ + pl_initbtype(p, flags, icon, hit, CHECK); +} +void plinitradiobutton(Panel *p, int flags, Icon *icon, void (*hit)(Panel *, int, int)){ + pl_initbtype(p, flags, icon, hit, RADIO); +} +Panel *plbutton(Panel *parent, int flags, Icon *icon, void (*hit)(Panel *, int)){ + Panel *p; + p=pl_newpanel(parent, sizeof(Button)); + plinitbutton(p, flags, icon, hit); + return p; +} +Panel *plcheckbutton(Panel *parent, int flags, Icon *icon, void (*hit)(Panel *, int, int)){ + Panel *p; + p=pl_newpanel(parent, sizeof(Button)); + plinitcheckbutton(p, flags, icon, hit); + return p; +} +Panel *plradiobutton(Panel *parent, int flags, Icon *icon, void (*hit)(Panel *, int, int)){ + Panel *p; + p=pl_newpanel(parent, sizeof(Button)); + plinitradiobutton(p, flags, icon, hit); + return p; +} +void pl_hitmenu(Panel *p, int buttons){ + void (*hit)(int, int); + hit=((Button *)p->data)->menuhit; + if(hit) hit(buttons, ((Button *)p->data)->index); +} +void plinitmenu(Panel *v, int flags, Icon **item, int cflags, void (*hit)(int, int)){ + Panel *b; + int i; + v->flags=flags; + v->kind="menu"; + if(v->child){ + plfree(v->child); + v->child=0; + } + for(i=0;item[i];i++){ + b=plbutton(v, cflags, item[i], pl_hitmenu); + ((Button *)b->data)->menuhit=hit; + ((Button *)b->data)->index=i; + } +} +Panel *plmenu(Panel *parent, int flags, Icon **item, int cflags, void (*hit)(int, int)){ + Panel *v; + v=plgroup(parent, flags); + plinitmenu(v, flags, item, cflags, hit); + return v; +} +void plsetbutton(Panel *p, int val){ + ((Button *)p->data)->check=val; +} diff --git a/sys/src/cmd/mothra/libpanel/canvas.c b/sys/src/cmd/mothra/libpanel/canvas.c new file mode 100644 index 000000000..c0ebbc793 --- /dev/null +++ b/sys/src/cmd/mothra/libpanel/canvas.c @@ -0,0 +1,51 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <panel.h> +#include "pldefs.h" +typedef struct Canvas Canvas; +struct Canvas{ + void (*draw)(Panel *); + void (*hit)(Panel *, Mouse *); +}; +void pl_drawcanvas(Panel *p){ + Canvas *c; + c=p->data; + if(c->draw) c->draw(p); +} +int pl_hitcanvas(Panel *p, Mouse *m){ + Canvas *c; + c=p->data; + if(c->hit) c->hit(p, m); + return 0; +} +void pl_typecanvas(Panel *p, Rune c){ + USED(p, c); +} +Point pl_getsizecanvas(Panel *p, Point children){ + USED(p, children); + return Pt(0,0); +} +void pl_childspacecanvas(Panel *p, Point *ul, Point *size){ + USED(p, ul, size); +} +void plinitcanvas(Panel *v, int flags, void (*draw)(Panel *), void (*hit)(Panel *, Mouse *)){ + Canvas *c; + v->flags=flags|LEAF; + v->draw=pl_drawcanvas; + v->hit=pl_hitcanvas; + v->type=pl_typecanvas; + v->getsize=pl_getsizecanvas; + v->childspace=pl_childspacecanvas; + v->kind="canvas"; + c=v->data; + c->draw=draw; + c->hit=hit; +} +Panel *plcanvas(Panel *parent, int flags, void (*draw)(Panel *), void (*hit)(Panel *, Mouse *)){ + Panel *p; + p=pl_newpanel(parent, sizeof(Canvas)); + plinitcanvas(p, flags, draw, hit); + return p; +} diff --git a/sys/src/cmd/mothra/libpanel/draw.c b/sys/src/cmd/mothra/libpanel/draw.c new file mode 100644 index 000000000..a399024c1 --- /dev/null +++ b/sys/src/cmd/mothra/libpanel/draw.c @@ -0,0 +1,286 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <panel.h> +#include "pldefs.h" +#define PWID 1 /* width of label border */ +#define BWID 1 /* width of button relief */ +#define FWID 2 /* width of frame relief */ +#define SPACE 1 /* space inside relief of button or frame */ +#define CKSIZE 3 /* size of check mark */ +#define CKSPACE 2 /* space around check mark */ +#define CKWID 1 /* width of frame around check mark */ +#define CKINSET 1 /* space around check mark frame */ +#define CKBORDER 2 /* space around X inside frame */ +static int plldepth; +static Image *pl_white, *pl_light, *pl_dark, *pl_black, *pl_hilit; +int pl_drawinit(int ldepth){ + plldepth=ldepth; +/* + pl_white=allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xFFFFFFFF); + pl_light=allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xAAAAAAFF); + pl_dark =allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x555555FF); + pl_black=allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x000000FF); + pl_hilit=allocimage(display, Rect(0,0,1,1), CHAN1(CAlpha,8), 1, 0x80); +*/ + + pl_white=allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xFFFFFFFF); + pl_light=allocimagemix(display, DPalebluegreen, DWhite); + pl_dark =allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPurpleblue); + pl_black=allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x000000FF); + pl_hilit=allocimage(display, Rect(0,0,1,1), CHAN1(CAlpha,8), 1, 0x80); + + + if(pl_white==0 || pl_light==0 || pl_black==0 || pl_dark==0) return 0; + return 1; +} +void pl_relief(Image *b, Image *ul, Image *lr, Rectangle r, int wid){ + int x, y; + draw(b, Rect(r.min.x, r.max.y-wid, r.max.x, r.max.y), lr, 0, ZP); /* bottom */ + draw(b, Rect(r.max.x-wid, r.min.y, r.max.x, r.max.y), lr, 0, ZP); /* right */ + draw(b, Rect(r.min.x, r.min.y, r.min.x+wid, r.max.y), ul, 0, ZP); /* left */ + draw(b, Rect(r.min.x, r.min.y, r.max.x, r.min.y+wid), ul, 0, ZP); /* top */ + for(x=0;x!=wid;x++) for(y=wid-1-x;y!=wid;y++){ + draw(b, rectaddpt(Rect(0,0,1,1), Pt(x+r.max.x-wid, y+r.min.y)), lr, 0, ZP); + draw(b, rectaddpt(Rect(0,0,1,1), Pt(x+r.min.x, y+r.max.y-wid)), lr, 0, ZP); + } +} +Rectangle pl_boxoutline(Image *b, Rectangle r, int style, int fill){ + if(plldepth==0) switch(style){ + case UP: + pl_relief(b, pl_black, pl_black, r, BWID); + r=insetrect(r, BWID); + if(fill) draw(b, r, pl_white, 0, ZP); + else border(b, r, SPACE, pl_white, ZP); + break; + case DOWN: + case DOWN1: + case DOWN2: + case DOWN3: + pl_relief(b, pl_black, pl_black, r, BWID); + r=insetrect(r, BWID); + if(fill) draw(b, r, pl_black, 0, ZP); + border(b, r, SPACE, pl_black, ZP); + break; + case PASSIVE: + if(fill) draw(b, r, pl_white, 0, ZP); + r=insetrect(r, PWID); + if(!fill) border(b, r, SPACE, pl_white, ZP); + break; + case FRAME: + pl_relief(b, pl_white, pl_black, r, FWID); + r=insetrect(r, FWID); + pl_relief(b, pl_black, pl_white, r, FWID); + r=insetrect(r, FWID); + if(fill) draw(b, r, pl_white, 0, ZP); + else border(b, r, SPACE, pl_white, ZP); + break; + } + else switch(style){ + case UP: + pl_relief(b, pl_white, pl_black, r, BWID); + r=insetrect(r, BWID); + if(fill) draw(b, r, pl_light, 0, ZP); + else border(b, r, SPACE, pl_white, ZP); + break; + case DOWN: + case DOWN1: + case DOWN2: + case DOWN3: + pl_relief(b, pl_black, pl_white, r, BWID); + r=insetrect(r, BWID); + if(fill) draw(b, r, pl_dark, 0, ZP); + else border(b, r, SPACE, pl_black, ZP); + break; + case PASSIVE: + if(fill) draw(b, r, pl_light, 0, ZP); + r=insetrect(r, PWID); + if(!fill) border(b, r, SPACE, pl_white, ZP); + break; + case FRAME: + pl_relief(b, pl_white, pl_black, r, FWID); + r=insetrect(r, FWID); + pl_relief(b, pl_black, pl_white, r, FWID); + r=insetrect(r, FWID); + if(fill) draw(b, r, pl_light, 0, ZP); + else border(b, r, SPACE, pl_white, ZP); + break; + } + return insetrect(r, SPACE); +} +Rectangle pl_outline(Image *b, Rectangle r, int style){ + return pl_boxoutline(b, r, style, 0); +} +Rectangle pl_box(Image *b, Rectangle r, int style){ + return pl_boxoutline(b, r, style, 1); +} +Point pl_boxsize(Point interior, int state){ + switch(state){ + case UP: + case DOWN: + case DOWN1: + case DOWN2: + case DOWN3: + return addpt(interior, Pt(2*(BWID+SPACE), 2*(BWID+SPACE))); + case PASSIVE: + return addpt(interior, Pt(2*(PWID+SPACE), 2*(PWID+SPACE))); + case FRAME: + return addpt(interior, Pt(4*FWID+2*SPACE, 4*FWID+2*SPACE)); + } +} +void pl_interior(int state, Point *ul, Point *size){ + switch(state){ + case UP: + case DOWN: + case DOWN1: + case DOWN2: + case DOWN3: + *ul=addpt(*ul, Pt(BWID+SPACE, BWID+SPACE)); + *size=subpt(*size, Pt(2*(BWID+SPACE), 2*(BWID+SPACE))); + break; + case PASSIVE: + *ul=addpt(*ul, Pt(PWID+SPACE, PWID+SPACE)); + *size=subpt(*size, Pt(2*(PWID+SPACE), 2*(PWID+SPACE))); + break; + case FRAME: + *ul=addpt(*ul, Pt(2*FWID+SPACE, 2*FWID+SPACE)); + *size=subpt(*size, Pt(4*FWID+2*SPACE, 4*FWID+2*SPACE)); + } +} +void pl_drawicon(Image *b, Rectangle r, int stick, int flags, Icon *s){ + Rectangle save; + Point ul, offs; + save=b->clipr; + replclipr(b, b->repl, r); + ul=r.min; + offs=subpt(subpt(r.max, r.min), pl_iconsize(flags, s)); + switch(stick){ + case PLACENW: break; + case PLACEN: ul.x+=offs.x/2; break; + case PLACENE: ul.x+=offs.x; break; + case PLACEW: ul.y+=offs.y/2; break; + case PLACECEN: ul.x+=offs.x/2; ul.y+=offs.y/2; break; + case PLACEE: ul.x+=offs.x; break; + case PLACESW: ul.y+=offs.y; break; + case PLACES: ul.x+=offs.x/2; ul.y+=offs.y; break; + case PLACESE: ul.x+=offs.x; ul.y+=offs.y; break; + } + if(flags&BITMAP) draw(b, Rpt(ul, addpt(ul, pl_iconsize(flags, s))), s, 0, ZP); + else string(b, ul, pl_black, ZP, font, s); + replclipr(b, b->repl, save); +} +/* + * Place a check mark at the left end of r. Return the unused space. + * Caller must guarantee that r.max.x-r.min.x>=r.max.y-r.min.y! + */ +Rectangle pl_radio(Image *b, Rectangle r, int val){ + Rectangle remainder; + remainder=r; + r.max.x=r.min.x+r.max.y-r.min.y; + remainder.min.x=r.max.x; + r=insetrect(r, CKINSET); + if(plldepth==0) + pl_relief(b, pl_black, pl_black, r, CKWID); + else + pl_relief(b, pl_black, pl_white, r, CKWID); + r=insetrect(r, CKWID); + if(plldepth==0) + draw(b, r, pl_white, 0, ZP); + else + draw(b, r, pl_light, 0, ZP); + if(val) draw(b, insetrect(r, CKSPACE), pl_black, 0, ZP); + return remainder; +} +Rectangle pl_check(Image *b, Rectangle r, int val){ + Rectangle remainder; + remainder=r; + r.max.x=r.min.x+r.max.y-r.min.y; + remainder.min.x=r.max.x; + r=insetrect(r, CKINSET); + if(plldepth==0) + pl_relief(b, pl_black, pl_black, r, CKWID); + else + pl_relief(b, pl_black, pl_white, r, CKWID); + r=insetrect(r, CKWID); + if(plldepth==0) + draw(b, r, pl_white, 0, ZP); + else + draw(b, r, pl_light, 0, ZP); + r=insetrect(r, CKBORDER); + if(val){ + line(b, Pt(r.min.x, r.min.y+1), Pt(r.max.x-1, r.max.y ), Endsquare, Endsquare, 0, pl_black, ZP); + line(b, Pt(r.min.x, r.min.y ), Pt(r.max.x, r.max.y ), Endsquare, Endsquare, 0, pl_black, ZP); + line(b, Pt(r.min.x+1, r.min.y ), Pt(r.max.x, r.max.y-1), Endsquare, Endsquare, 0, pl_black, ZP); + line(b, Pt(r.min.x , r.max.y-2), Pt(r.max.x-1, r.min.y-1), Endsquare, Endsquare, 0, pl_black, ZP); + line(b, Pt(r.min.x, r.max.y-1), Pt(r.max.x, r.min.y-1), Endsquare, Endsquare, 0, pl_black, ZP); + line(b, Pt(r.min.x+1, r.max.y-1), Pt(r.max.x, r.min.y ), Endsquare, Endsquare, 0, pl_black, ZP); + } + return remainder; +} +int pl_ckwid(void){ + return 2*(CKINSET+CKSPACE+CKWID)+CKSIZE; +} +void pl_sliderupd(Image *b, Rectangle r1, int dir, int lo, int hi){ + Rectangle r2, r3; + r2=r1; + r3=r1; + if(lo<0) lo=0; + if(hi<=lo) hi=lo+1; + switch(dir){ + case HORIZ: + r1.max.x=r1.min.x+lo; + r2.min.x=r1.max.x; + r2.max.x=r1.min.x+hi; + if(r2.max.x>r3.max.x) r2.max.x=r3.max.x; + r3.min.x=r2.max.x; + break; + case VERT: + r1.max.y=r1.min.y+lo; + r2.min.y=r1.max.y; + r2.max.y=r1.min.y+hi; + if(r2.max.y>r3.max.y) r2.max.y=r3.max.y; + r3.min.y=r2.max.y; + break; + } + draw(b, r1, pl_light, 0, ZP); + draw(b, r2, pl_dark, 0, ZP); + draw(b, r3, pl_light, 0, ZP); +} +void pl_draw1(Panel *p, Image *b); +void pl_drawall(Panel *p, Image *b){ + if(p->flags&INVIS) return; + p->b=b; + p->draw(p); + for(p=p->child;p;p=p->next) pl_draw1(p, b); +} +void pl_draw1(Panel *p, Image *b){ + if(b!=0) + pl_drawall(p, b); +} +void pldraw(Panel *p, Image *b){ + pl_draw1(p, b); + flushimage(display, 1); +} +void pl_invis(Panel *p, int v){ + for(;p;p=p->next){ + if(v) p->flags|=INVIS; else p->flags&=~INVIS; + pl_invis(p->child, v); + } +} +Point pl_iconsize(int flags, Icon *p){ + if(flags&BITMAP) return subpt(((Image *)p)->r.max, ((Image *)p)->r.min); + return stringsize(font, (char *)p); +} +void pl_highlight(Image *b, Rectangle r){ + draw(b, r, pl_dark, pl_hilit, ZP); +} +void pl_clr(Image *b, Rectangle r){ + draw(b, r, display->white, 0, ZP); +} +void pl_fill(Image *b, Rectangle r){ + draw(b, r, plldepth==0? pl_white : pl_light, 0, ZP); +} +void pl_cpy(Image *b, Point dst, Rectangle src){ + draw(b, Rpt(dst, addpt(dst, subpt(src.max, src.min))), b, 0, src.min); +} diff --git a/sys/src/cmd/mothra/libpanel/edit.c b/sys/src/cmd/mothra/libpanel/edit.c new file mode 100644 index 000000000..f570ff5da --- /dev/null +++ b/sys/src/cmd/mothra/libpanel/edit.c @@ -0,0 +1,239 @@ +/* + * Interface includes: + * void plescroll(Panel *p, int top); + * move the given character position onto the top line + * void plegetsel(Panel *p, int *sel0, int *sel1); + * read the selection back + * int plelen(Panel *p); + * read the length of the text back + * Rune *pleget(Panel *p); + * get a pointer to the text + * void plesel(Panel *p, int sel0, int sel1); + * set the selection -- adjusts hiliting + * void plepaste(Panel *p, Rune *text, int ntext); + * replace the selection with the given text + */ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <panel.h> +#include "pldefs.h" +typedef struct Edit Edit; +struct Edit{ + Point minsize; + int sel0, sel1; + Textwin *t; + void (*hit)(Panel *); + Rune *text; + int ntext; +}; +void pl_drawedit(Panel *p){ + Edit *ep; + Panel *sb; + ep=p->data; + if(ep->t==0){ + ep->t=twnew(p->b, font, ep->text, ep->ntext); + if(ep->t==0){ + fprint(2, "pl_drawedit: can't allocate\n"); + exits("no mem"); + } + } + ep->t->b=p->b; + twreshape(ep->t, p->r); + twhilite(ep->t, ep->sel0, ep->sel1, 1); + sb=p->yscroller; + if(sb && sb->setscrollbar) + sb->setscrollbar(sb, ep->t->top, ep->t->bot, ep->t->etext-ep->t->text); +} +/* + * Should do double-clicks: + * If ep->sel0==ep->sel1 on entry and the + * call to twselect returns the same selection, then + * expand selections (| marks possible selection points, ... is expanded selection) + * <|...|> <> must nest + * (|...|) () must nest + * [|...|] [] must nest + * {|...|} {} must nest + * '|...|' no ' in ... + * "|...|" no " in ... + * \n|...|\n either newline may be the corresponding end of text + * include the trailing newline in the selection + * ...|I... I and ... are characters satisfying pl_idchar(I) + * ...I| + */ +int pl_hitedit(Panel *p, Mouse *m){ + Edit *ep; + if(m->buttons&7){ + ep=p->data; + ep->t->b=p->b; + twhilite(ep->t, ep->sel0, ep->sel1, 0); + twselect(ep->t, m); + ep->sel0=ep->t->sel0; + ep->sel1=ep->t->sel1; + if(ep->hit) ep->hit(p); + } + return 0; +} +void pl_scrolledit(Panel *p, int dir, int buttons, int num, int den){ + Edit *ep; + Textwin *t; + Panel *sb; + int index, nline; + if(dir!=VERT) return; + ep=p->data; + t=ep->t; + t->b=p->b; + switch(buttons){ + default: + return; + case 1: /* top line moves to mouse position */ + nline=(t->r.max.y-t->r.min.y)/t->hgt*num/den; + index=t->top; + while(index!=0 && nline!=0) + if(t->text[--index]=='\n') --nline; + break; + case 2: /* absolute */ + index=(t->etext-t->text)*num/den; + break; + case 4: /* mouse points at new top line */ + index=twpt2rune(t, + Pt(t->r.min.x, t->r.min.y+(t->r.max.y-t->r.min.y)*num/den)); + break; + } + while(index!=0 && t->text[index-1]!='\n') --index; + if(index!=t->top){ + twhilite(ep->t, ep->sel0, ep->sel1, 0); + twscroll(t, index); + p->scr.pos.y=t->top; + twhilite(ep->t, ep->sel0, ep->sel1, 1); + sb=p->yscroller; + if(sb && sb->setscrollbar) + sb->setscrollbar(sb, t->top, t->bot, t->etext-t->text); + } +} +void pl_typeedit(Panel *p, Rune c){ + Edit *ep; + Textwin *t; + int bot, scrolled; + Panel *sb; + ep=p->data; + t=ep->t; + t->b=p->b; + twhilite(t, ep->sel0, ep->sel1, 0); + switch(c){ + case '\b': + if(ep->sel0!=0) --ep->sel0; + twreplace(t, ep->sel0, ep->sel1, 0, 0); + break; + case '\025': /* ctrl-u */ + while(ep->sel0!=0 && t->text[ep->sel0-1]!='\n') --ep->sel0; + twreplace(t, ep->sel0, ep->sel1, 0, 0); + break; + case '\027': /* ctrl-w */ + while(ep->sel0!=0 && !pl_idchar(t->text[ep->sel0-1])) --ep->sel0; + while(ep->sel0!=0 && pl_idchar(t->text[ep->sel0-1])) --ep->sel0; + twreplace(t, ep->sel0, ep->sel1, 0, 0); + break; + default: + twreplace(t, ep->sel0, ep->sel1, &c, 1); + ++ep->sel0; + break; + } + ep->sel1=ep->sel0; + /* + * Scroll up until ep->sel0 is above t->bot. + */ + scrolled=0; + do{ + bot=t->bot; + if(ep->sel0<=bot) break; + twscroll(t, twpt2rune(t, Pt(t->r.min.x, t->r.min.y+font->height))); + scrolled++; + }while(bot!=t->bot); + if(scrolled){ + sb=p->yscroller; + if(sb && sb->setscrollbar) + sb->setscrollbar(sb, t->top, t->bot, t->etext-t->text); + } + twhilite(t, ep->sel0, ep->sel1, 1); +} +Point pl_getsizeedit(Panel *p, Point children){ + USED(children); + return pl_boxsize(((Edit *)p->data)->minsize, p->state); +} +void pl_childspaceedit(Panel *g, Point *ul, Point *size){ + USED(g, ul, size); +} +void plinitedit(Panel *v, int flags, Point minsize, Rune *text, int ntext, void (*hit)(Panel *)){ + Edit *ep; + ep=v->data; + v->flags=flags|LEAF; + v->state=UP; + v->draw=pl_drawedit; + v->hit=pl_hitedit; + v->type=pl_typeedit; + v->getsize=pl_getsizeedit; + v->childspace=pl_childspaceedit; + v->kind="edit"; + ep->hit=hit; + ep->minsize=minsize; + ep->text=text; + ep->ntext=ntext; + if(ep->t!=0) twfree(ep->t); + ep->t=0; + ep->sel0=-1; + ep->sel1=-1; + v->scroll=pl_scrolledit; + v->scr.pos=Pt(0,0); + v->scr.size=Pt(ntext,0); +} +Panel *pledit(Panel *parent, int flags, Point minsize, Rune *text, int ntext, void (*hit)(Panel *)){ + Panel *v; + v=pl_newpanel(parent, sizeof(Edit)); + ((Edit *)v->data)->t=0; + plinitedit(v, flags, minsize, text, ntext, hit); + return v; +} +void plescroll(Panel *p, int top){ + twscroll(((Edit *)p->data)->t, top); +} +void plegetsel(Panel *p, int *sel0, int *sel1){ + Edit *ep; + ep=p->data; + *sel0=ep->sel0; + *sel1=ep->sel1; +} +int plelen(Panel *p){ + Textwin *t; + t=((Edit *)p->data)->t; + return t->etext-t->text; +} +Rune *pleget(Panel *p){ + return ((Edit *)p->data)->t->text; +} +void plesel(Panel *p, int sel0, int sel1){ + Edit *ep; + ep=p->data; + ep->t->b=p->b; + twhilite(ep->t, ep->sel0, ep->sel1, 0); + ep->sel0=sel0; + ep->sel1=sel1; + twhilite(ep->t, ep->sel0, ep->sel1, 1); +} +void plepaste(Panel *p, Rune *text, int ntext){ + Edit *ep; + ep=p->data; + ep->t->b=p->b; + twhilite(ep->t, ep->sel0, ep->sel1, 0); + twreplace(ep->t, ep->sel0, ep->sel1, text, ntext); + ep->sel1=ep->sel0+ntext; + twhilite(ep->t, ep->sel0, ep->sel1, 1); + p->scr.size.y=ep->t->etext-ep->t->text; + p->scr.pos.y=ep->t->top; +} +void plemove(Panel *p, Point d){ + Edit *ep; + ep=p->data; + if(ep->t && !eqpt(d, Pt(0,0))) twmove(ep->t, d); +} diff --git a/sys/src/cmd/mothra/libpanel/entry.c b/sys/src/cmd/mothra/libpanel/entry.c new file mode 100644 index 000000000..a8e2c86c8 --- /dev/null +++ b/sys/src/cmd/mothra/libpanel/entry.c @@ -0,0 +1,130 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <panel.h> +#include "pldefs.h" +typedef struct Entry Entry; +struct Entry{ + char *entry; + char *entp; + char *eent; + void (*hit)(Panel *, char *); + Point minsize; +}; +#define SLACK 7 /* enough for one extra rune and ◀ and a nul */ +void pl_drawentry(Panel *p){ + Rectangle r; + Entry *ep; + ep=p->data; + r=pl_box(p->b, p->r, p->state); + if(stringwidth(font, ep->entry)<=r.max.x-r.min.x) + pl_drawicon(p->b, r, PLACEW, 0, ep->entry); + else + pl_drawicon(p->b, r, PLACEE, 0, ep->entry); +} +int pl_hitentry(Panel *p, Mouse *m){ + int oldstate; + oldstate=p->state; + if(m->buttons&OUT) + p->state=UP; + else if(m->buttons&7) + p->state=DOWN; + else{ /* mouse inside, but no buttons down */ + if(p->state==DOWN) plgrabkb(p); + p->state=UP; + } + if(p->state!=oldstate) pldraw(p, p->b); + return 0; +} +void pl_typeentry(Panel *p, Rune c){ + int n; + Entry *ep; + ep=p->data; + switch(c){ + case '\n': + case '\r': + *ep->entp='\0'; + if(ep->hit) ep->hit(p, ep->entry); + return; + case 025: /* ctrl-u */ + ep->entp=ep->entry; + *ep->entp='\0'; + break; + case '\b': + while(ep->entp!=ep->entry && !pl_rune1st(ep->entp[-1])) *--ep->entp='\0'; + if(ep->entp!=ep->entry) *--ep->entp='\0'; + break; + case 027: /* ctrl-w */ + while(ep->entp!=ep->entry && !pl_idchar(ep->entp[-1])) + --ep->entp; + while(ep->entp!=ep->entry && pl_idchar(ep->entp[-1])) + --ep->entp; + *ep->entp='\0'; + break; + default: + ep->entp+=runetochar(ep->entp, &c); + if(ep->entp>ep->eent){ + n=ep->entp-ep->entry; + ep->entry=realloc(ep->entry, n+100+SLACK); + if(ep->entry==0){ + fprint(2, "can't realloc in pl_typeentry\n"); + exits("no mem"); + } + ep->entp=ep->entry+n; + ep->eent=ep->entp+100; + } + break; + } + memset(ep->entp, 0, SLACK); + + /* strcpy(ep->entp, "◀"); */ + pldraw(p, p->b); +} +Point pl_getsizeentry(Panel *p, Point children){ + USED(children); + return pl_boxsize(((Entry *)p->data)->minsize, p->state); +} +void pl_childspaceentry(Panel *p, Point *ul, Point *size){ + USED(p, ul, size); +} +void pl_freeentry(Panel *p){ + free(((Entry *)p->data)->entry); +} +void plinitentry(Panel *v, int flags, int wid, char *str, void (*hit)(Panel *, char *)){ + int elen; + Entry *ep; + ep=v->data; + v->flags=flags|LEAF; + v->state=UP; + v->draw=pl_drawentry; + v->hit=pl_hitentry; + v->type=pl_typeentry; + v->getsize=pl_getsizeentry; + v->childspace=pl_childspaceentry; + ep->minsize=Pt(wid, font->height); + v->free=pl_freeentry; + elen=100; + if(str) elen+=strlen(str); + ep->entry=pl_emalloc(elen+SLACK); + ep->eent=ep->entry+elen; + if(str) + strcpy(ep->entry, str); + else ep->entry[0]='\0'; + ep->entp=ep->entry+strlen(ep->entry); + /* strcat(ep->entry, "◀"); */ + ep->hit=hit; + v->kind="entry"; +} +Panel *plentry(Panel *parent, int flags, int wid, char *str, void (*hit)(Panel *, char *)){ + Panel *v; + v=pl_newpanel(parent, sizeof(Entry)); + plinitentry(v, flags, wid, str, hit); + return v; +} +char *plentryval(Panel *p){ + Entry *ep; + ep=p->data; + *ep->entp='\0'; + return ep->entry; +} diff --git a/sys/src/cmd/mothra/libpanel/event.c b/sys/src/cmd/mothra/libpanel/event.c new file mode 100644 index 000000000..bd34ace15 --- /dev/null +++ b/sys/src/cmd/mothra/libpanel/event.c @@ -0,0 +1,50 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <panel.h> +#include "pldefs.h" +Panel *pl_kbfocus; +void plgrabkb(Panel *g){ + pl_kbfocus=g; +} +void plkeyboard(Rune c){ + if(pl_kbfocus){ + pl_kbfocus->type(pl_kbfocus, c); + flushimage(display, 1); + } +} +/* + * Return the most leafward, highest priority panel containing p + */ +Panel *pl_ptinpanel(Point p, Panel *g){ + Panel *v; + for(;g;g=g->next) if(ptinrect(p, g->r)){ + v=pl_ptinpanel(p, g->child); + if(v && v->pri(v, p)>=g->pri(g, p)) return v; + return g; + } + return 0; +} +void plmouse(Panel *g, Mouse mouse){ + Panel *hit, *last; + if(g->flags&REMOUSE) + hit=g->lastmouse; + else{ + hit=pl_ptinpanel(mouse.xy, g); + last=g->lastmouse; + if(last && last!=hit){ + mouse.buttons|=OUT; + last->hit(last, &mouse); + mouse.buttons&=~OUT; + } + } + if(hit){ + if(hit->hit(hit, &mouse)) + g->flags|=REMOUSE; + else + g->flags&=~REMOUSE; + g->lastmouse=hit; + } + flushimage(display, 1); +} diff --git a/sys/src/cmd/mothra/libpanel/frame.c b/sys/src/cmd/mothra/libpanel/frame.c new file mode 100644 index 000000000..cbfac12d7 --- /dev/null +++ b/sys/src/cmd/mothra/libpanel/frame.c @@ -0,0 +1,39 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <panel.h> +#include "pldefs.h" +void pl_drawframe(Panel *p){ + pl_box(p->b, p->r, FRAME); +} +int pl_hitframe(Panel *p, Mouse *m){ + USED(p, m); + return 0; +} +void pl_typeframe(Panel *p, Rune c){ + USED(p, c); +} +Point pl_getsizeframe(Panel *p, Point children){ + USED(p); + return pl_boxsize(children, FRAME); +} +void pl_childspaceframe(Panel *p, Point *ul, Point *size){ + USED(p); + pl_interior(FRAME, ul, size); +} +void plinitframe(Panel *v, int flags){ + v->flags=flags; + v->draw=pl_drawframe; + v->hit=pl_hitframe; + v->type=pl_typeframe; + v->getsize=pl_getsizeframe; + v->childspace=pl_childspaceframe; + v->kind="frame"; +} +Panel *plframe(Panel *parent, int flags){ + Panel *p; + p=pl_newpanel(parent, 0); + plinitframe(p, flags); + return p; +} diff --git a/sys/src/cmd/mothra/libpanel/group.c b/sys/src/cmd/mothra/libpanel/group.c new file mode 100644 index 000000000..7a0fa7e74 --- /dev/null +++ b/sys/src/cmd/mothra/libpanel/group.c @@ -0,0 +1,38 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <panel.h> +#include "pldefs.h" +void pl_drawgroup(Panel *p){ + USED(p); +} +int pl_hitgroup(Panel *p, Mouse *m){ + USED(p, m); + return 0; +} +void pl_typegroup(Panel *p, Rune c){ + USED(p, c); +} +Point pl_getsizegroup(Panel *p, Point children){ + USED(p); + return children; +} +void pl_childspacegroup(Panel *p, Point *ul, Point *size){ + USED(p, ul, size); +} +void plinitgroup(Panel *v, int flags){ + v->flags=flags; + v->draw=pl_drawgroup; + v->hit=pl_hitgroup; + v->type=pl_typegroup; + v->getsize=pl_getsizegroup; + v->childspace=pl_childspacegroup; + v->kind="group"; +} +Panel *plgroup(Panel *parent, int flags){ + Panel *p; + p=pl_newpanel(parent, 0); + plinitgroup(p, flags); + return p; +} diff --git a/sys/src/cmd/mothra/libpanel/init.c b/sys/src/cmd/mothra/libpanel/init.c new file mode 100644 index 000000000..452909722 --- /dev/null +++ b/sys/src/cmd/mothra/libpanel/init.c @@ -0,0 +1,13 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <panel.h> +#include "pldefs.h" +/* + * Just a wrapper for all the initialization routines + */ +int plinit(int ldepth){ + if(!pl_drawinit(ldepth)) return 0; + return 1; +} diff --git a/sys/src/cmd/mothra/libpanel/label.c b/sys/src/cmd/mothra/libpanel/label.c new file mode 100644 index 000000000..c7f16be54 --- /dev/null +++ b/sys/src/cmd/mothra/libpanel/label.c @@ -0,0 +1,50 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <panel.h> +#include "pldefs.h" +typedef struct Label Label; +struct Label{ + int placement; + Icon *icon; +}; +void pl_drawlabel(Panel *p){ + Label *l; + l=p->data; + pl_drawicon(p->b, pl_box(p->b, p->r, PASSIVE), l->placement, p->flags, l->icon); +} +int pl_hitlabel(Panel *p, Mouse *m){ + USED(p, m); + return 0; +} +void pl_typelabel(Panel *p, Rune c){ + USED(p, c); +} +Point pl_getsizelabel(Panel *p, Point children){ + USED(children); /* shouldn't have any children */ + return pl_boxsize(pl_iconsize(p->flags, ((Label *)p->data)->icon), PASSIVE); +} +void pl_childspacelabel(Panel *g, Point *ul, Point *size){ + USED(g, ul, size); +} +void plinitlabel(Panel *v, int flags, Icon *icon){ + v->flags=flags|LEAF; + ((Label *)(v->data))->icon=icon; + v->draw=pl_drawlabel; + v->hit=pl_hitlabel; + v->type=pl_typelabel; + v->getsize=pl_getsizelabel; + v->childspace=pl_childspacelabel; + v->kind="label"; +} +Panel *pllabel(Panel *parent, int flags, Icon *icon){ + Panel *p; + p=pl_newpanel(parent, sizeof(Label)); + plinitlabel(p, flags, icon); + plplacelabel(p, PLACECEN); + return p; +} +void plplacelabel(Panel *p, int placement){ + ((Label *)(p->data))->placement=placement; +} diff --git a/sys/src/cmd/mothra/libpanel/list.c b/sys/src/cmd/mothra/libpanel/list.c new file mode 100644 index 000000000..fb9b676f7 --- /dev/null +++ b/sys/src/cmd/mothra/libpanel/list.c @@ -0,0 +1,190 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <panel.h> +#include "pldefs.h" +typedef struct List List; +struct List{ + void (*hit)(Panel *, int, int); /* call user back on hit */ + char *(*gen)(Panel *, int); /* return text given index or 0 if out of range */ + int lo; /* indices of first, last items displayed */ + int sel; /* index of hilited item */ + int len; /* # of items in list */ + Rectangle listr; + Point minsize; + int buttons; +}; +#define MAXHGT 12 +void pl_listsel(Panel *p, int sel, int on){ + List *lp; + int hi; + Rectangle r; + lp=p->data; + hi=lp->lo+(lp->listr.max.y-lp->listr.min.y)/font->height; + if(lp->lo<=sel && sel<hi && sel<lp->len){ + r=lp->listr; + r.min.y+=(sel-lp->lo)*font->height; + r.max.y=r.min.y+font->height; + if(on) + pl_highlight(p->b, r); + else{ + pl_fill(p->b, r); + pl_drawicon(p->b, r, PLACEW, 0, lp->gen(p, sel)); + } + } +} +void pl_liststrings(Panel *p, int lo, int hi, Rectangle r){ + Panel *sb; + List *lp; + char *s; + int i; + lp=p->data; + for(i=lo;i!=hi && (s=lp->gen(p, i));i++){ + r.max.y=r.min.y+font->height; + pl_drawicon(p->b, r, PLACEW, 0, s); + r.min.y+=font->height; + } + if(lo<=lp->sel && lp->sel<hi) pl_listsel(p, lp->sel, 1); + sb=p->yscroller; + if(sb && sb->setscrollbar) + sb->setscrollbar(sb, lp->lo, + lp->lo+(lp->listr.max.y-lp->listr.min.y)/font->height, lp->len); +} +void pl_drawlist(Panel *p){ + List *lp; + lp=p->data; + lp->listr=pl_box(p->b, p->r, UP); + pl_liststrings(p, lp->lo, lp->lo+(lp->listr.max.y-lp->listr.min.y)/font->height, + lp->listr); +} +int pl_hitlist(Panel *p, Mouse *m){ + int oldsel, hitme; + Point ul, size; + List *lp; + lp=p->data; + hitme=0; + ul=p->r.min; + size=subpt(p->r.max, p->r.min); + pl_interior(p->state, &ul, &size); + oldsel=lp->sel; + if(m->buttons&OUT){ + p->state=UP; + if(m->buttons&~OUT) lp->sel=-1; + } + else if(p->state==DOWN || m->buttons&7){ + lp->sel=(m->xy.y-ul.y)/font->height+lp->lo; + if(m->buttons&7){ + lp->buttons=m->buttons; + p->state=DOWN; + } + else{ + hitme=1; + p->state=UP; + } + } + if(oldsel!=lp->sel){ + pl_listsel(p, oldsel, 0); + pl_listsel(p, lp->sel, 1); + } + if(hitme && 0<=lp->sel && lp->sel<lp->len && lp->hit) + lp->hit(p, lp->buttons, lp->sel); + return 0; +} +void pl_scrolllist(Panel *p, int dir, int buttons, int val, int len){ + Point ul, size; + int nlist, oldlo, hi, nline, y; + List *lp; + Rectangle r; + lp=p->data; + ul=p->r.min; + size=subpt(p->r.max, p->r.min); + pl_interior(p->state, &ul, &size); + nlist=size.y/font->height; + oldlo=lp->lo; + if(dir==VERT) switch(buttons){ + case 1: lp->lo-=nlist*val/len; break; + case 2: lp->lo=lp->len*val/len; break; + case 4: lp->lo+=nlist*val/len; break; + } + if(lp->lo<0) lp->lo=0; + if(lp->lo>=lp->len) lp->lo=lp->len-1; + if(lp->lo==oldlo) return; + p->scr.pos.y=lp->lo; + r=lp->listr; + nline=(r.max.y-r.min.y)/font->height; + hi=lp->lo+nline; + if(hi<=oldlo || lp->lo>=oldlo+nline){ + pl_box(p->b, r, PASSIVE); + pl_liststrings(p, lp->lo, hi, r); + } + else if(lp->lo<oldlo){ + y=r.min.y+(oldlo-lp->lo)*font->height; + pl_cpy(p->b, Pt(r.min.x, y), + Rect(r.min.x, r.min.y, r.max.x, r.min.y+(hi-oldlo)*font->height)); + r.max.y=y; + pl_box(p->b, r, PASSIVE); + pl_liststrings(p, lp->lo, oldlo, r); + } + else{ + pl_cpy(p->b, r.min, Rect(r.min.x, r.min.y+(lp->lo-oldlo)*font->height, + r.max.x, r.max.y)); + r.min.y=r.min.y+(oldlo+nline-lp->lo)*font->height; + pl_box(p->b, r, PASSIVE); + pl_liststrings(p, oldlo+nline, hi, r); + } +} +void pl_typelist(Panel *g, Rune c){ + USED(g, c); +} +Point pl_getsizelist(Panel *p, Point children){ + USED(children); + return pl_boxsize(((List *)p->data)->minsize, p->state); +} +void pl_childspacelist(Panel *g, Point *ul, Point *size){ + USED(g, ul, size); +} +void plinitlist(Panel *v, int flags, char *(*gen)(Panel *, int), int nlist, void (*hit)(Panel *, int, int)){ + List *lp; + int wid, max; + char *str; + lp=v->data; + v->flags=flags|LEAF; + v->state=UP; + v->draw=pl_drawlist; + v->hit=pl_hitlist; + v->type=pl_typelist; + v->getsize=pl_getsizelist; + v->childspace=pl_childspacelist; + lp->gen=gen; + lp->hit=hit; + max=0; + for(lp->len=0;str=gen(v, lp->len);lp->len++){ + wid=stringwidth(font, str); + if(wid>max) max=wid; + } + if(flags&(FILLX|EXPAND)){ + for(lp->len=0;gen(v, lp->len);lp->len++); + lp->minsize=Pt(0, nlist*font->height); + } + else{ + max=0; + for(lp->len=0;str=gen(v, lp->len);lp->len++){ + wid=stringwidth(font, str); + if(wid>max) max=wid; + } + lp->minsize=Pt(max, nlist*font->height); + } + lp->sel=-1; + lp->lo=0; + v->scroll=pl_scrolllist; + v->scr.pos=Pt(0,0); + v->scr.size=Pt(0,lp->len); + v->kind="list"; +} +Panel *pllist(Panel *parent, int flags, char *(*gen)(Panel *, int), int nlist, void (*hit)(Panel *, int, int)){ + Panel *v; + v=pl_newpanel(parent, sizeof(List)); + plinitlist(v, flags, gen, nlist, hit); + return v; +} diff --git a/sys/src/cmd/mothra/libpanel/mem.c b/sys/src/cmd/mothra/libpanel/mem.c new file mode 100644 index 000000000..0155cc12b --- /dev/null +++ b/sys/src/cmd/mothra/libpanel/mem.c @@ -0,0 +1,108 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <panel.h> +#include "pldefs.h" +void *pl_emalloc(int n){ + void *v; + v=malloc(n); + if(v==0){ + fprint(2, "Can't malloc!\n"); + exits("no mem"); + } + return v; +} +void pl_unexpected(Panel *g, char *rou){ + fprint(2, "%s called unexpectedly (%s %lux)\n", rou, g->kind, (ulong)g); + abort(); +} +void pl_drawerror(Panel *g){ + pl_unexpected(g, "draw"); +} +int pl_hiterror(Panel *g, Mouse *m){ + USED(m); + pl_unexpected(g, "hit"); + return 0; +} +void pl_typeerror(Panel *g, Rune c){ + USED(c); + pl_unexpected(g, "type"); +} +Point pl_getsizeerror(Panel *g, Point childsize){ + pl_unexpected(g, "getsize"); + return childsize; +} +void pl_childspaceerror(Panel *g, Point *ul, Point *size){ + USED(ul, size); + pl_unexpected(g, "childspace"); +} +void pl_scrollerror(Panel *g, int dir, int button, int num, int den){ + USED(dir, button, num, den); + pl_unexpected(g, "scroll"); +} +void pl_setscrollbarerror(Panel *g, int top, int bot, int den){ + USED(top, bot, den); + pl_unexpected(g, "setscrollbar"); +} +int pl_prinormal(Panel *, Point){ + return PRI_NORMAL; +} +Panel *pl_newpanel(Panel *parent, int ndata){ + Panel *v; + if(parent && parent->flags&LEAF){ + fprint(2, "newpanel: can't create child of %s %lux\n", parent->kind, (ulong)parent); + exits("bad newpanel"); + } + v=pl_emalloc(sizeof(Panel)); + v->r=Rect(0,0,0,0); + v->flags=0; + v->ipad=Pt(0,0); + v->pad=Pt(0,0); + v->size=Pt(0,0); + v->sizereq=Pt(0,0); + v->lastmouse=0; + v->next=0; + v->child=0; + v->echild=0; + v->b=0; + v->pri=pl_prinormal; + v->scrollee=0; + v->xscroller=0; + v->yscroller=0; + v->parent=parent; + v->scr.pos=Pt(0,0); + v->scr.size=Pt(0,0); + if(parent){ + if(parent->child==0) + parent->child=v; + else + parent->echild->next=v; + parent->echild=v; + } + v->draw=pl_drawerror; + v->hit=pl_hiterror; + v->type=pl_typeerror; + v->getsize=pl_getsizeerror; + v->childspace=pl_childspaceerror; + v->scroll=pl_scrollerror; + v->setscrollbar=pl_setscrollbarerror; + v->free=0; + if(ndata) + v->data=pl_emalloc(ndata); + else + v->data=0; + return v; +} +void plfree(Panel *p){ + Panel *cp, *ncp; + if(p==0) + return; + for(cp=p->child;cp;cp=ncp){ + ncp=cp->next; + plfree(cp); + } + if(p->free) p->free(p); + if(p->data) free(p->data); + free(p); +} diff --git a/sys/src/cmd/mothra/libpanel/message.c b/sys/src/cmd/mothra/libpanel/message.c new file mode 100644 index 000000000..7c3dbed07 --- /dev/null +++ b/sys/src/cmd/mothra/libpanel/message.c @@ -0,0 +1,104 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <panel.h> +#include "pldefs.h" +typedef struct Message Message; +struct Message{ + char *text; + Point minsize; +}; +void pl_textmsg(Image *b, Rectangle r, Font *f, char *s){ + char *start, *end; /* of line */ + Point where; + int lwid, c, wid; + where=r.min; + wid=r.max.x-r.min.x; + do{ + start=s; + lwid=0; + end=s; + do{ + for(;*s!=' ' && *s!='\0';s=pl_nextrune(s)) lwid+=pl_runewidth(f, s); + if(lwid>wid) break; + end=s; + for(;*s==' ';s=pl_nextrune(s)) lwid+=pl_runewidth(f, s); + }while(*s!='\0'); + if(end==start) /* can't even fit one word on line! */ + end=s; + c=*end; + *end='\0'; + string(b, where, display->black, ZP, f, start); + *end=c; + where.y+=font->height; + s=end; + while(*s==' ') s=pl_nextrune(s); + }while(*s!='\0'); +} +Point pl_foldsize(Font *f, char *s, int wid){ + char *start, *end; /* of line */ + Point size; + int lwid, ewid; + size=Pt(0,0); + do{ + start=s; + lwid=0; + end=s; + ewid=lwid; + do{ + for(;*s!=' ' && *s!='\0';s=pl_nextrune(s)) lwid+=pl_runewidth(f, s); + if(lwid>wid) break; + end=s; + ewid=lwid; + for(;*s==' ';s=pl_nextrune(s)) lwid+=pl_runewidth(f, s); + }while(*s!='\0'); + if(end==start){ /* can't even fit one word on line! */ + ewid=lwid; + end=s; + } + if(ewid>size.x) size.x=ewid; + size.y+=font->height; + s=end; + while(*s==' ') s=pl_nextrune(s); + }while(*s!='\0'); + return size; +} +void pl_drawmessage(Panel *p){ + pl_textmsg(p->b, pl_box(p->b, p->r, PASSIVE), font, ((Message *)p->data)->text); +} +int pl_hitmessage(Panel *g, Mouse *m){ + USED(g, m); + return 0; +} +void pl_typemessage(Panel *g, Rune c){ + USED(g, c); +} +Point pl_getsizemessage(Panel *p, Point children){ + Message *mp; + USED(children); + mp=p->data; + return pl_boxsize(pl_foldsize(font, mp->text, mp->minsize.x), PASSIVE); +} +void pl_childspacemessage(Panel *p, Point *ul, Point *size){ + USED(p, ul, size); +} +void plinitmessage(Panel *v, int flags, int wid, char *msg){ + Message *mp; + mp=v->data; + v->flags=flags|LEAF; + v->draw=pl_drawmessage; + v->hit=pl_hitmessage; + v->type=pl_typemessage; + v->getsize=pl_getsizemessage; + v->childspace=pl_childspacemessage; + mp->text=msg; + mp->minsize=Pt(wid, font->height); + v->kind="message"; +} +Panel *plmessage(Panel *parent, int flags, int wid, char *msg){ + Panel *v; + v=pl_newpanel(parent, sizeof(Message)); + plinitmessage(v, flags, wid, msg); + return v; +} diff --git a/sys/src/cmd/mothra/libpanel/mkfile b/sys/src/cmd/mothra/libpanel/mkfile new file mode 100644 index 000000000..9fbbe59b0 --- /dev/null +++ b/sys/src/cmd/mothra/libpanel/mkfile @@ -0,0 +1,33 @@ +</$objtype/mkfile + +LIB=libpanel.$O.a +OFILES=\ + button.$O\ + canvas.$O\ + draw.$O\ + edit.$O\ + entry.$O\ + event.$O\ + frame.$O\ + group.$O\ +# idollist.$O\ + init.$O\ + label.$O\ + list.$O\ + mem.$O\ + message.$O\ + pack.$O\ + popup.$O\ + print.$O\ + pulldown.$O\ + rtext.$O\ + scroll.$O\ + scrollbar.$O\ + slider.$O\ + textview.$O\ + textwin.$O\ + utf.$O + +HFILES=panel.h pldefs.h rtext.h + +</sys/src/cmd/mklib diff --git a/sys/src/cmd/mothra/libpanel/pack.c b/sys/src/cmd/mothra/libpanel/pack.c new file mode 100644 index 000000000..fef854c64 --- /dev/null +++ b/sys/src/cmd/mothra/libpanel/pack.c @@ -0,0 +1,167 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <panel.h> +#include "pldefs.h" +int pl_max(int a, int b){ + return a>b?a:b; +} +Point pl_sizesibs(Panel *p){ + Point s; + if(p==0) return Pt(0,0); + s=pl_sizesibs(p->next); + switch(p->flags&PACK){ + case PACKN: + case PACKS: + s.x=pl_max(s.x, p->sizereq.x); + s.y+=p->sizereq.y; + break; + case PACKE: + case PACKW: + s.x+=p->sizereq.x; + s.y=pl_max(s.y, p->sizereq.y); + break; + } + return s; +} +/* + * Compute the requested size of p and its descendants. + */ +void pl_sizereq(Panel *p){ + Panel *cp; + Point maxsize; + maxsize=Pt(0,0); + for(cp=p->child;cp;cp=cp->next){ + pl_sizereq(cp); + if(cp->sizereq.x>maxsize.x) maxsize.x=cp->sizereq.x; + if(cp->sizereq.y>maxsize.y) maxsize.y=cp->sizereq.y; + } + for(cp=p->child;cp;cp=cp->next){ + if(cp->flags&MAXX) cp->sizereq.x=maxsize.x; + if(cp->flags&MAXY) cp->sizereq.y=maxsize.y; + } + p->childreq=pl_sizesibs(p->child); + p->sizereq=addpt(addpt(p->getsize(p, p->childreq), p->ipad), p->pad); + if(p->flags&FIXEDX) p->sizereq.x=p->fixedsize.x; + if(p->flags&FIXEDY) p->sizereq.y=p->fixedsize.y; +} +Point pl_getshare(Panel *p){ + Point share; + if(p==0) return Pt(0,0); + share=pl_getshare(p->next); + if(p->flags&EXPAND) switch(p->flags&PACK){ + case PACKN: + case PACKS: + if(share.x==0) share.x=1; + share.y++; + break; + case PACKE: + case PACKW: + share.x++; + if(share.y==0) share.y=1; + break; + } + return share; +} +/* + * Set the sizes and rectangles of p and its descendants, given their requested sizes. + * Returns 1 if everything fit, 0 otherwise. + * For now we punt in the case that the children don't all fit. + * Possibly we should shrink all the children's sizereqs to fit, + * by the same means used to do EXPAND, except clamping at some minimum size, + * but that smacks of AI. + */ +Panel *pl_toosmall; +int pl_setrect(Panel *p, Point ul, Point avail){ + Point space, newul, newspace, slack, share; + int l; + Panel *c; + p->size=subpt(p->sizereq, p->pad); + ul=addpt(ul, divpt(p->pad, 2)); + avail=subpt(avail, p->pad); + if(p->size.x>avail.x || p->size.y>avail.y){ + pl_toosmall=p; + return 0; /* not enough space! */ + } + if(p->flags&(FILLX|EXPAND)) p->size.x=avail.x; + if(p->flags&(FILLY|EXPAND)) p->size.y=avail.y; + switch(p->flags&PLACE){ + case PLACECEN: ul.x+=(avail.x-p->size.x)/2; ul.y+=(avail.y-p->size.y)/2; break; + case PLACES: ul.x+=(avail.x-p->size.x)/2; ul.y+= avail.y-p->size.y ; break; + case PLACEE: ul.x+= avail.x-p->size.x ; ul.y+=(avail.y-p->size.y)/2; break; + case PLACEW: ul.y+=(avail.y-p->size.y)/2; break; + case PLACEN: ul.x+=(avail.x-p->size.x)/2; break; + case PLACENE: ul.x+= avail.x-p->size.x ; break; + case PLACENW: break; + case PLACESE: ul.x+= avail.x-p->size.x ; ul.y+= avail.y-p->size.y ; break; + case PLACESW: ul.y+= avail.y-p->size.y ; break; + } + p->r=Rpt(ul, addpt(ul, p->size)); + space=p->size; + p->childspace(p, &ul, &space); + slack=subpt(space, p->childreq); + share=pl_getshare(p->child); + for(c=p->child;c;c=c->next){ + if(c->flags&EXPAND){ + switch(c->flags&PACK){ + case PACKN: + case PACKS: + c->sizereq.x+=slack.x; + l=slack.y/share.y; + c->sizereq.y+=l; + slack.y-=l; + --share.y; + break; + case PACKE: + case PACKW: + l=slack.x/share.x; + c->sizereq.x+=l; + slack.x-=l; + --share.x; + c->sizereq.y+=slack.y; + break; + } + } + switch(c->flags&PACK){ + case PACKN: + newul=Pt(ul.x, ul.y+c->sizereq.y); + newspace=Pt(space.x, space.y-c->sizereq.y); + if(!pl_setrect(c, ul, Pt(space.x, c->sizereq.y))) return 0; + break; + case PACKW: + newul=Pt(ul.x+c->sizereq.x, ul.y); + newspace=Pt(space.x-c->sizereq.x, space.y); + if(!pl_setrect(c, ul, Pt(c->sizereq.x, space.y))) return 0; + break; + case PACKS: + newul=ul; + newspace=Pt(space.x, space.y-c->sizereq.y); + if(!pl_setrect(c, Pt(ul.x, ul.y+space.y-c->sizereq.y), + Pt(space.x, c->sizereq.y))) return 0; + break; + case PACKE: + newul=ul; + newspace=Pt(space.x-c->sizereq.x, space.y); + if(!pl_setrect(c, Pt(ul.x+space.x-c->sizereq.x, ul.y), + Pt(c->sizereq.x, space.y))) return 0; + break; + } + ul=newul; + space=newspace; + } + return 1; +} +int plpack(Panel *p, Rectangle where){ + pl_sizereq(p); + return pl_setrect(p, where.min, subpt(where.max, where.min)); +} +/* + * move an already-packed panel so that p->r=raddp(p->r, d) + */ +void plmove(Panel *p, Point d){ + if(strcmp(p->kind, "edit") == 0) /* sorry */ + plemove(p, d); + p->r=rectaddpt(p->r, d); + for(p=p->child;p;p=p->next) plmove(p, d); +} diff --git a/sys/src/cmd/mothra/libpanel/panel.h b/sys/src/cmd/mothra/libpanel/panel.h new file mode 100644 index 000000000..beb75c0d7 --- /dev/null +++ b/sys/src/cmd/mothra/libpanel/panel.h @@ -0,0 +1,174 @@ +//#pragma src "/sys/src/libpanel" +//#pragma lib "libpanel.a" +typedef struct Scroll Scroll; +typedef struct Panel Panel; /* a Graphical User Interface element */ +typedef struct Rtext Rtext; /* formattable text */ +typedef void Icon; /* Always used as Icon * -- Image or char */ +typedef struct Idol Idol; /* A picture/text combo */ +struct Scroll{ + Point pos, size; +}; +struct Rtext{ + int hot; /* responds to hits? */ + void *user; /* user data */ + int space; /* how much space before, if no break */ + int indent; /* how much space before, after a break */ + Image *b; /* what to display, if nonzero */ + Panel *p; /* what to display, if nonzero and b==0 */ + Font *font; /* font in which to draw text */ + char *text; /* what to display, if b==0 and p==0 */ + Rtext *next; /* next piece */ + /* private below */ + Rtext *nextline; /* links line to line */ + Rtext *last; /* last, for append */ + Rectangle r; /* where to draw, if origin were Pt(0,0) */ + int topy; /* y coord of top of line */ + int wid; /* not including space */ +}; +struct Panel{ + Point ipad, pad; /* extra space inside and outside */ + Point fixedsize; /* size of Panel, if FIXED */ + int user; /* available for user */ + void *userp; /* available for user */ + Rectangle r; /* where the Panel goes */ + /* private below */ + Panel *next; /* It's a list! */ + Panel *child, *echild, *parent; /* No, it's a tree! */ + Image *b; /* where we're drawn */ + int flags; /* position flags, see below */ + char *kind; /* what kind of panel? */ + int state; /* for hitting & drawing purposes */ + Point size; /* space for this Panel */ + Point sizereq; /* size requested by this Panel */ + Point childreq; /* total size needed by children */ + Panel *lastmouse; /* who got the last mouse event? */ + Panel *scrollee; /* pointer to scrolled window */ + Panel *xscroller, *yscroller; /* pointers to scroll bars */ + Scroll scr; /* scroll data */ + void *data; /* kind-specific data */ + void (*draw)(Panel *); /* draw panel and children */ + int (*pri)(Panel *, Point); /* priority for hitting */ + int (*hit)(Panel *, Mouse *); /* process mouse event */ + void (*type)(Panel *, Rune); /* process keyboard event */ + Point (*getsize)(Panel *, Point); /* return size, given child size */ + void (*childspace)(Panel *, Point *, Point *); /* child ul & size given our size */ + void (*scroll)(Panel *, int, int, int, int); /* scroll bar to scrollee */ + void (*setscrollbar)(Panel *, int, int, int); /* scrollee to scroll bar */ + void (*free)(Panel *); /* free fields of data when done */ +}; +/* + * Panel flags -- there are more private flags in panelprivate.h + * that need to be kept synchronized with these! + */ +#define PACK 0x0007 /* which side of the parent is the Panel attached to? */ +#define PACKN 0x0000 +#define PACKE 0x0001 +#define PACKS 0x0002 +#define PACKW 0x0003 +#define PACKCEN 0x0004 /* only used by pulldown */ +#define FILLX 0x0008 /* grow horizontally to fill the available space */ +#define FILLY 0x0010 /* grow vertically to fill the available space */ +#define PLACE 0x01e0 /* which side of its space should the Panel adhere to? */ +#define PLACECEN 0x0000 +#define PLACES 0x0020 +#define PLACEE 0x0040 +#define PLACEW 0x0060 +#define PLACEN 0x0080 +#define PLACENE 0x00a0 +#define PLACENW 0x00c0 +#define PLACESE 0x00e0 +#define PLACESW 0x0100 +#define EXPAND 0x0200 /* use up all extra space in the parent */ +#define FIXED 0x0c00 /* don't pass children's size requests through to parent */ +#define FIXEDX 0x0400 +#define FIXEDY 0x0800 +#define MAXX 0x1000 /* make x size as big as biggest sibling's */ +#define MAXY 0x2000 /* make y size as big as biggest sibling's */ +#define BITMAP 0x4000 /* text argument is a bitmap, not a string */ +/* + * An extra bit in Mouse.buttons + */ +#define OUT 8 /* Mouse.buttons bit, set when mouse leaves Panel */ +/* + * Priorities + */ +#define PRI_NORMAL 0 /* ordinary panels */ +#define PRI_POPUP 1 /* popup menus */ +#define PRI_SCROLLBAR 2 /* scroll bars */ +int plinit(int); /* initialization */ +int plpack(Panel *, Rectangle); /* figure out where to put the Panel & children */ +void plmove(Panel *, Point); /* move an already-packed panel to a new location */ +void pldraw(Panel *, Image *); /* display the panel on the bitmap */ +void plfree(Panel *); /* give back space */ +void plgrabkb(Panel *); /* this Panel should receive keyboard events */ +void plkeyboard(Rune); /* send a keyboard event to the appropriate Panel */ +void plmouse(Panel *, Mouse); /* send a Mouse event to a Panel tree */ +void plscroll(Panel *, Panel *, Panel *); /* link up scroll bars */ +char *plentryval(Panel *); /* entry delivers its value */ +void plsetbutton(Panel *, int); /* set or clear the mark on a button */ +void plsetslider(Panel *, int, int); /* set the value of a slider */ +Rune *pleget(Panel *); /* get the text from an edit window */ +int plelen(Panel *); /* get the length of the text from an edit window */ +void plegetsel(Panel *, int *, int *); /* get the selection from an edit window */ +void plepaste(Panel *, Rune *, int); /* paste in an edit window */ +void plesel(Panel *, int, int); /* set the selection in an edit window */ +void plescroll(Panel *, int); /* scroll an edit window */ +Scroll plgetscroll(Panel *); /* get scrolling information from panel */ +void plsetscroll(Panel *, Scroll); /* set scrolling information */ +void plplacelabel(Panel *, int); /* label placement */ +/* + * Panel creation & reinitialization functions + */ +Panel *plbutton(Panel *pl, int, Icon *, void (*)(Panel *pl, int)); +Panel *plcanvas(Panel *pl, int, void (*)(Panel *), void (*)(Panel *pl, Mouse *)); +Panel *plcheckbutton(Panel *pl, int, Icon *, void (*)(Panel *pl, int, int)); +Panel *pledit(Panel *, int, Point, Rune *, int, void (*)(Panel *)); +Panel *plentry(Panel *pl, int, int, char *, void (*)(Panel *pl, char *)); +Panel *plframe(Panel *pl, int); +Panel *plgroup(Panel *pl, int); +Panel *plidollist(Panel*, int, Point, Font*, Idol*, void (*)(Panel*, int, void*)); +Panel *pllabel(Panel *pl, int, Icon *); +Panel *pllist(Panel *pl, int, char *(*)(Panel *, int), int, void(*)(Panel *pl, int, int)); +Panel *plmenu(Panel *pl, int, Icon **, int, void (*)(int, int)); +Panel *plmenubar(Panel *pl, int, int, Icon *, Panel *pl, Icon *, ...); +Panel *plmessage(Panel *pl, int, int, char *); +Panel *plpopup(Panel *pl, int, Panel *pl, Panel *pl, Panel *pl); +Panel *plpulldown(Panel *pl, int, Icon *, Panel *pl, int); +Panel *plradiobutton(Panel *pl, int, Icon *, void (*)(Panel *pl, int, int)); +Panel *plscrollbar(Panel *plparent, int flags); +Panel *plslider(Panel *pl, int, Point, void(*)(Panel *pl, int, int, int)); +Panel *pltextview(Panel *, int, Point, Rtext *, void (*)(Panel *, int, Rtext *)); +void plinitbutton(Panel *, int, Icon *, void (*)(Panel *, int)); +void plinitcanvas(Panel *, int, void (*)(Panel *), void (*)(Panel *, Mouse *)); +void plinitcheckbutton(Panel *, int, Icon *, void (*)(Panel *, int, int)); +void plinitedit(Panel *, int, Point, Rune *, int, void (*)(Panel *)); +void plinitentry(Panel *, int, int, char *, void (*)(Panel *, char *)); +void plinitframe(Panel *, int); +void plinitgroup(Panel *, int); +void plinitidollist(Panel*, int, Point, Font*, Idol*, void (*)(Panel*, int, void*)); +void plinitlabel(Panel *, int, Icon *); +void plinitlist(Panel *, int, char *(*)(Panel *, int), int, void(*)(Panel *, int, int)); +void plinitmenu(Panel *, int, Icon **, int, void (*)(int, int)); +void plinitmessage(Panel *, int, int, char *); +void plinitpopup(Panel *, int, Panel *, Panel *, Panel *); +void plinitpulldown(Panel *, int, Icon *, Panel *, int); +void plinitradiobutton(Panel *, int, Icon *, void (*)(Panel *, int, int)); +void plinitscrollbar(Panel *parent, int flags); +void plinitslider(Panel *, int, Point, void(*)(Panel *, int, int, int)); +void plinittextview(Panel *, int, Point, Rtext *, void (*)(Panel *, int, Rtext *)); +/* + * Rtext constructors & destructor + */ +Rtext *plrtstr(Rtext **, int, int, Font *, char *, int, void *); +Rtext *plrtbitmap(Rtext **, int, int, Image *, int, void *); +Rtext *plrtpanel(Rtext **, int, int, Panel *, void *); +void plrtfree(Rtext *); +int plgetpostextview(Panel *); +void plsetpostextview(Panel *, int); +/* + * Idols + */ +Idol *plmkidol(Idol**, Image*, Image*, char*, void*); +void plfreeidol(Idol*); +Point plidolsize(Idol*, Font*, int); +void *plidollistgetsel(Panel*); diff --git a/sys/src/cmd/mothra/libpanel/panel.pdf b/sys/src/cmd/mothra/libpanel/panel.pdf Binary files differnew file mode 100644 index 000000000..7c84c14e0 --- /dev/null +++ b/sys/src/cmd/mothra/libpanel/panel.pdf diff --git a/sys/src/cmd/mothra/libpanel/pldefs.h b/sys/src/cmd/mothra/libpanel/pldefs.h new file mode 100644 index 000000000..52a7d6814 --- /dev/null +++ b/sys/src/cmd/mothra/libpanel/pldefs.h @@ -0,0 +1,103 @@ +/* + * Definitions for internal use only + */ +/* + * Variable-font text routines + * These could make a separate library. + */ +int pl_rtfmt(Rtext *, int); +void pl_rtdraw(Image *, Rectangle, Rtext *, int); +void pl_rtredraw(Image *, Rectangle, Rtext *, int, int); +Rtext *pl_rthit(Rtext *, int, Point, Point); +#define HITME 0x08000 /* tells ptinpanel not to look at children */ +#define LEAF 0x10000 /* newpanel will refuse to attach children */ +#define INVIS 0x20000 /* don't draw this */ +#define REMOUSE 0x40000 /* send next mouse event here, even if not inside */ +/* + * States, also styles + */ +enum{ + UP, + DOWN1, + DOWN2, + DOWN3, + DOWN, + PASSIVE, + FRAME +}; +/* + * Scroll flags + */ +enum{ + SCROLLUP, + SCROLLDOWN, + SCROLLABSY, + SCROLLLEFT, + SCROLLRIGHT, + SCROLLABSX, +}; +/* + * Scrollbar, slider orientations + */ +enum{ + HORIZ, + VERT +}; +Panel *pl_newpanel(Panel *, int); /* make a new Panel, given parent & data size */ +void *pl_emalloc(int); /* allocate some space, exit on error */ +void pl_print(Panel *); /* print a Panel tree */ +Panel *pl_ptinpanel(Point, Panel *); /* highest-priority subpanel containing point */ +/* + * Drawing primitives + */ +int pl_drawinit(int); +Rectangle pl_box(Image *, Rectangle, int); +Rectangle pl_outline(Image *, Rectangle, int); +Point pl_boxsize(Point, int); +void pl_interior(int, Point *, Point *); +void pl_drawicon(Image *, Rectangle, int, int, Icon *); +Rectangle pl_check(Image *, Rectangle, int); +Rectangle pl_radio(Image *, Rectangle, int); +int pl_ckwid(void); +void pl_sliderupd(Image *, Rectangle, int, int, int); +void pl_invis(Panel *, int); +Point pl_iconsize(int, Icon *); +void pl_highlight(Image *, Rectangle); +void pl_clr(Image *, Rectangle); +void pl_fill(Image *, Rectangle); +void pl_cpy(Image *, Point, Rectangle); + +/* + * Rune mangling functions + */ +int pl_idchar(int); +int pl_rune1st(int); +char *pl_nextrune(char *); +int pl_runewidth(Font *, char *); +/* + * Fixed-font Text-window routines + * These could be separated out into a separate library. + */ +typedef struct Textwin Textwin; +struct Textwin{ + Rune *text, *etext, *eslack; /* text, with some slack off the end */ + int top, bot; /* range of runes visible on screen */ + int sel0, sel1; /* selection */ + Point *loc, *eloc; /* ul corners of visible runes (+1 more at end!) */ + Image *b; /* bitmap the text is drawn in */ + Rectangle r; /* rectangle the text is drawn in */ + Font *font; /* font text is drawn in */ + int hgt; /* same as font->height */ + int tabstop; /* tab settings are every tabstop pixels */ + int mintab; /* the minimum size of a tab */ +}; +Textwin *twnew(Image *, Font *, Rune *, int); +void twfree(Textwin *); +void twhilite(Textwin *, int, int, int); +void twselect(Textwin *, Mouse *); +void twreplace(Textwin *, int, int, Rune *, int); +void twscroll(Textwin *, int); +int twpt2rune(Textwin *, Point); +void twreshape(Textwin *, Rectangle); +void twmove(Textwin *, Point); +void plemove(Panel *, Point); diff --git a/sys/src/cmd/mothra/libpanel/popup.c b/sys/src/cmd/mothra/libpanel/popup.c new file mode 100644 index 000000000..345ca4ca5 --- /dev/null +++ b/sys/src/cmd/mothra/libpanel/popup.c @@ -0,0 +1,113 @@ +/* + * popup + * looks like a group, except diverts hits on certain buttons to + * panels that it temporarily pops up. + */ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <panel.h> +#include "pldefs.h" +typedef struct Popup Popup; +struct Popup{ + Image *save; /* where to save what the popup covers */ + Panel *pop[3]; /* what to pop up */ +}; +void pl_drawpopup(Panel *p){ + USED(p); +} +int pl_hitpopup(Panel *g, Mouse *m){ + Panel *p; + Point d; + Popup *pp; + pp=g->data; + if(g->state==UP){ + switch(m->buttons&7){ + case 0: p=g->child; break; + case 1: p=pp->pop[0]; g->state=DOWN1; break; + case 2: p=pp->pop[1]; g->state=DOWN2; break; + case 4: p=pp->pop[2]; g->state=DOWN3; break; + default: p=0; break; + } + if(p==0){ + p=g->child; + g->state=DOWN; + } + else if(g->state!=UP){ + plpack(p, screen->clipr); + if(p->lastmouse) + d=subpt(m->xy, divpt(addpt(p->lastmouse->r.min, + p->lastmouse->r.max), 2)); + else + d=subpt(m->xy, divpt(addpt(p->r.min, p->r.max), 2)); + if(p->r.min.x+d.x<g->r.min.x) d.x=g->r.min.x-p->r.min.x; + if(p->r.max.x+d.x>g->r.max.x) d.x=g->r.max.x-p->r.max.x; + if(p->r.min.y+d.y<g->r.min.y) d.y=g->r.min.y-p->r.min.y; + if(p->r.max.y+d.y>g->r.max.y) d.y=g->r.max.y-p->r.max.y; + plmove(p, d); + pp->save=allocimage(display, p->r, g->b->chan, 0, DNofill); + if(pp->save!=0) draw(pp->save, p->r, g->b, 0, p->r.min); + pl_invis(p, 0); + pldraw(p, g->b); + } + } + else{ + switch(g->state){ + default: SET(p); break; /* can't happen! */ + case DOWN1: p=pp->pop[0]; break; + case DOWN2: p=pp->pop[1]; break; + case DOWN3: p=pp->pop[2]; break; + case DOWN: p=g->child; break; + } + if((m->buttons&7)==0){ + if(g->state!=DOWN){ + if(pp->save!=0){ + draw(g->b, p->r, pp->save, 0, p->r.min); + flushimage(display, 1); + freeimage(pp->save); + } + pl_invis(p, 1); + } + g->state=UP; + } + } + plmouse(p, *m); + return (m->buttons&7)!=0; +} +void pl_typepopup(Panel *g, Rune c){ + USED(g, c); +} +Point pl_getsizepopup(Panel *g, Point children){ + USED(g); + return children; +} +void pl_childspacepopup(Panel *g, Point *ul, Point *size){ + USED(g, ul, size); +} +int pl_pripopup(Panel *, Point){ + return PRI_POPUP; +} +void plinitpopup(Panel *v, int flags, Panel *pop0, Panel *pop1, Panel *pop2){ + Popup *pp; + pp=v->data; + v->flags=flags; + v->pri=pl_pripopup; + v->state=UP; + v->draw=pl_drawpopup; + v->hit=pl_hitpopup; + v->type=pl_typepopup; + v->getsize=pl_getsizepopup; + v->childspace=pl_childspacepopup; + pp->pop[0]=pop0; + pp->pop[1]=pop1; + pp->pop[2]=pop2; + pp->save=0; + v->kind="popup"; +} +Panel *plpopup(Panel *parent, int flags, Panel *pop0, Panel *pop1, Panel *pop2){ + Panel *v; + v=pl_newpanel(parent, sizeof(Popup)); + plinitpopup(v, flags, pop0, pop1, pop2); + return v; +} diff --git a/sys/src/cmd/mothra/libpanel/print.c b/sys/src/cmd/mothra/libpanel/print.c new file mode 100644 index 000000000..a3bd70210 --- /dev/null +++ b/sys/src/cmd/mothra/libpanel/print.c @@ -0,0 +1,56 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <panel.h> +#include "pldefs.h" +void pl_iprint(int indent, char *fmt, ...){ + char buf[8192]; + va_list arg; + memset(buf, '\t', indent); + va_start(arg, fmt); + write(1, buf, vsnprint(buf+indent, sizeof(buf)-indent, fmt, arg)); + va_end(arg); +} +void pl_ipprint(Panel *p, int n){ + Panel *c; + char *place, *stick; + pl_iprint(n, "%s (0x%.8x)\n", p->kind, p); + pl_iprint(n, " r=(%d %d, %d %d)\n", + p->r.min.x, p->r.min.y, p->r.max.x, p->r.max.y); + switch(p->flags&PACK){ + default: SET(place); break; + case PACKN: place="n"; break; + case PACKE: place="e"; break; + case PACKS: place="s"; break; + case PACKW: place="w"; break; + } + switch(p->flags&PLACE){ + default: SET(stick); break; + case PLACECEN: stick=""; break; + case PLACES: stick=" stick s"; break; + case PLACEE: stick=" stick e"; break; + case PLACEW: stick=" stick w"; break; + case PLACEN: stick=" stick n"; break; + case PLACENE: stick=" stick ne"; break; + case PLACENW: stick=" stick nw"; break; + case PLACESE: stick=" stick se"; break; + case PLACESW: stick=" stick sw"; break; + } + pl_iprint(n, " place %s%s%s%s%s%s\n", + place, + p->flags&FILLX?" fill x":"", + p->flags&FILLY?" fill y":"", + stick, + p->flags&EXPAND?" expand":"", + p->flags&FIXED?" fixed":""); + if(!eqpt(p->pad, Pt(0, 0))) pl_iprint(n, " pad=%d,%d)\n", p->pad.x, p->pad.y); + if(!eqpt(p->ipad, Pt(0, 0))) pl_iprint(n, " ipad=%d,%d)\n", p->ipad.x, p->ipad.y); + pl_iprint(n, " size=(%d,%d), sizereq=(%d,%d)\n", + p->size.x, p->size.y, p->sizereq.x, p->sizereq.y); + for(c=p->child;c;c=c->next) + pl_ipprint(c, n+1); +} +void pl_print(Panel *p){ + pl_ipprint(p, 0); +} diff --git a/sys/src/cmd/mothra/libpanel/pulldown.c b/sys/src/cmd/mothra/libpanel/pulldown.c new file mode 100644 index 000000000..828fce42c --- /dev/null +++ b/sys/src/cmd/mothra/libpanel/pulldown.c @@ -0,0 +1,160 @@ +/* + * pulldown + * makes a button that pops up a panel when hit + */ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <panel.h> +#include "pldefs.h" +typedef struct Pulldown Pulldown; +struct Pulldown{ + Icon *icon; /* button label */ + Panel *pull; /* Panel to pull down */ + int side; /* which side of the button to put the panel on */ + Image *save; /* where to save what we draw the panel on */ +}; +void pl_drawpulldown(Panel *p){ + pl_drawicon(p->b, pl_box(p->b, p->r, p->state), PLACECEN, + p->flags, ((Pulldown *)p->data)->icon); +} +int pl_hitpulldown(Panel *g, Mouse *m){ + int oldstate, passon; + Rectangle r; + Panel *p, *hitme; + Pulldown *pp; + pp=g->data; + oldstate=g->state; + p=pp->pull; + hitme=0; + switch(g->state){ + case UP: + if(!ptinrect(m->xy, g->r)) + g->state=UP; + else if(m->buttons&7){ + r=g->b->r; + p->flags&=~PLACE; + switch(pp->side){ + case PACKN: + r.min.x=g->r.min.x; + r.max.y=g->r.min.y; + p->flags|=PLACESW; + break; + case PACKS: + r.min.x=g->r.min.x; + r.min.y=g->r.max.y; + p->flags|=PLACENW; + break; + case PACKE: + r.min.x=g->r.max.x; + r.min.y=g->r.min.y; + p->flags|=PLACENW; + break; + case PACKW: + r.max.x=g->r.min.x; + r.min.y=g->r.min.y; + p->flags|=PLACENE; + break; + case PACKCEN: + r.min=g->r.min; + p->flags|=PLACENW; + break; + } + plpack(p, r); + pp->save=allocimage(display, p->r, g->b->chan, 0, DNofill); + if(pp->save!=0) draw(pp->save, p->r, g->b, 0, p->r.min); + pl_invis(p, 0); + pldraw(p, g->b); + g->state=DOWN; + } + break; + case DOWN: + if(!ptinrect(m->xy, g->r)){ + switch(pp->side){ + default: SET(passon); break; /* doesn't happen */ + case PACKN: passon=m->xy.y<g->r.min.y; break; + case PACKS: passon=m->xy.y>=g->r.max.y; break; + case PACKE: passon=m->xy.x>=g->r.max.x; break; + case PACKW: passon=m->xy.x<g->r.min.x; break; + case PACKCEN: passon=1; break; + } + if(passon){ + hitme=p; + if((m->buttons&7)==0) g->state=UP; + } + else g->state=UP; + } + else if((m->buttons&7)==0) g->state=UP; + else hitme=p; + if(g->state!=DOWN && pp->save){ + draw(g->b, p->r, pp->save, 0, p->r.min); + freeimage(pp->save); + pp->save=0; + pl_invis(p, 1); + hitme=p; + } + } + if(g->state!=oldstate) pldraw(g, g->b); + if(hitme) plmouse(hitme, *m); + return g->state==DOWN; +} +void pl_typepulldown(Panel *p, Rune c){ + USED(p, c); +} +Point pl_getsizepulldown(Panel *p, Point children){ + USED(p, children); + return pl_boxsize(pl_iconsize(p->flags, ((Pulldown *)p->data)->icon), p->state); +} +void pl_childspacepulldown(Panel *p, Point *ul, Point *size){ + USED(p, ul, size); +} +void plinitpulldown(Panel *v, int flags, Icon *icon, Panel *pullthis, int side){ + Pulldown *pp; + pp=v->data; + v->flags=flags|LEAF; + v->draw=pl_drawpulldown; + v->hit=pl_hitpulldown; + v->type=pl_typepulldown; + v->getsize=pl_getsizepulldown; + v->childspace=pl_childspacepulldown; + pp->pull=pullthis; + pp->side=side; + pp->icon=icon; + v->kind="pulldown"; +} +Panel *plpulldown(Panel *parent, int flags, Icon *icon, Panel *pullthis, int side){ + Panel *v; + v=pl_newpanel(parent, sizeof(Pulldown)); + v->state=UP; + ((Pulldown *)v->data)->save=0; + plinitpulldown(v, flags, icon, pullthis, side); + return v; +} +Panel *plmenubar(Panel *parent, int flags, int cflags, Icon *l1, Panel *m1, Icon *l2, ...){ + Panel *v; + va_list arg; + Icon *s; + int pulldir; + switch(cflags&PACK){ + default: + SET(pulldir); + break; + case PACKE: + case PACKW: + pulldir=PACKS; + break; + case PACKN: + case PACKS: + pulldir=PACKE; + break; + } + v=plgroup(parent, flags); + va_start(arg, cflags); + while((s=va_arg(arg, Icon *))!=0) + plpulldown(v, cflags, s, va_arg(arg, Panel *), pulldir); + va_end(arg); + USED(l1, m1, l2); + v->kind="menubar"; + return v; +} diff --git a/sys/src/cmd/mothra/libpanel/rtext.c b/sys/src/cmd/mothra/libpanel/rtext.c new file mode 100644 index 000000000..0f03c5212 --- /dev/null +++ b/sys/src/cmd/mothra/libpanel/rtext.c @@ -0,0 +1,230 @@ +/* + * Rich text with images. + * Should there be an offset field, to do subscripts & kerning? + */ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <panel.h> +#include "pldefs.h" +#include "rtext.h" +#define LEAD 4 /* extra space between lines */ +Rtext *pl_rtnew(Rtext **t, int space, int indent, Image *b, Panel *p, Font *f, char *s, int hot, void *user){ + Rtext *new; + new=malloc(sizeof(Rtext)); + if(new==0) return 0; + new->hot=hot; + new->user=user; + new->space=space; + new->indent=indent; + new->b=b; + new->p=p; + new->font=f; + new->text=s; + new->next=0; + new->r=Rect(0,0,0,0); + if(*t) + (*t)->last->next=new; + else + *t=new; + (*t)->last=new; + return new; +} +Rtext *plrtpanel(Rtext **t, int space, int indent, Panel *p, void *user){ + return pl_rtnew(t, space, indent, 0, p, 0, 0, 1, user); +} +Rtext *plrtstr(Rtext **t, int space, int indent, Font *f, char *s, int hot, void *user){ + return pl_rtnew(t, space, indent, 0, 0, f, s, hot, user); +} +Rtext *plrtbitmap(Rtext **t, int space, int indent, Image *b, int hot, void *user){ + return pl_rtnew(t, space, indent, b, 0, 0, 0, hot, user); +} +void plrtfree(Rtext *t){ + Rtext *next; + while(t){ + next=t->next; + free(t); + t=next; + } +} +int pl_tabmin, pl_tabsize; +void pltabsize(int min, int size){ + pl_tabmin=min; + pl_tabsize=size; +} +int pl_space(int space, int pos, int indent){ + if(space>=0) return space; + switch(PL_OP(space)){ + default: + return 0; + case PL_TAB: + return ((pos-indent+pl_tabmin)/pl_tabsize+PL_ARG(space))*pl_tabsize+indent-pos; + } +} +/* + * initialize rectangles & nextlines of text starting at t, + * galley width is wid. Returns the total length of the text + */ +int pl_rtfmt(Rtext *t, int wid){ + Rtext *tp, *eline; + int ascent, descent, x, space, a, d, w, topy, indent; + Point p; + p=Pt(0,0); + eline=t; + while(t){ + ascent=0; + descent=0; + indent=space=pl_space(t->indent, 0, 0); + x=0; + tp=t; + for(;;){ + if(tp->b){ + a=tp->b->r.max.y-tp->b->r.min.y+2; + d=0; + w=tp->b->r.max.x-tp->b->r.min.x+4; + } + else if(tp->p){ + /* what if plpack fails? */ + plpack(tp->p, Rect(0,0,wid,wid)); + plmove(tp->p, subpt(Pt(0,0), tp->p->r.min)); + a=tp->p->r.max.y-tp->p->r.min.y; + d=0; + w=tp->p->r.max.x-tp->p->r.min.x; + } + else{ + a=tp->font->ascent; + d=tp->font->height-a; + w=tp->wid=stringwidth(tp->font, tp->text); + } + if(x+w+space>wid) break; + if(a>ascent) ascent=a; + if(d>descent) descent=d; + x+=w+space; + tp=tp->next; + if(tp==0){ + eline=0; + break; + } + space=pl_space(tp->space, x, indent); + if(space) eline=tp; + } + if(eline==t){ /* No progress! Force fit the first block! */ + if(a>ascent) ascent=a; + if(d>descent) descent=d; + if(tp==t) + eline=tp->next; + else + eline=tp; + } + topy=p.y; + p.y+=ascent; + p.x=indent=pl_space(t->indent, 0, 0); + for(;;){ + t->topy=topy; + t->r.min.x=p.x; + if(t->b){ + t->r.max.y=p.y; + t->r.min.y=p.y-(t->b->r.max.y-t->b->r.min.y); + p.x+=t->b->r.max.x-t->b->r.min.x+2; + t->r=rectaddpt(t->r, Pt(2, 2)); + } + else if(t->p){ + t->r.max.y=p.y; + t->r.min.y=p.y-t->p->r.max.y; + p.x+=t->p->r.max.x; + } + else{ + t->r.min.y=p.y-t->font->ascent; + t->r.max.y=t->r.min.y+t->font->height; + p.x+=t->wid; + } + t->r.max.x=p.x; + t->nextline=eline; + t=t->next; + if(t==eline) break; + p.x+=pl_space(t->space, p.x, indent); + } + p.y+=descent+LEAD; + } + return p.y; +} +void pl_rtdraw(Image *b, Rectangle r, Rtext *t, int yoffs){ + Point offs; + Rectangle dr; + Rectangle cr; + cr=b->clipr; + replclipr(b, b->repl, r); + pl_clr(b, r); + offs=subpt(r.min, Pt(0, yoffs)); + for(;t;t=t->next) if(!eqrect(t->r, Rect(0,0,0,0))){ + dr=rectaddpt(t->r, offs); + if(dr.max.y>r.min.y + && dr.min.y<r.max.y){ + if(t->b){ +// bitblt(b, dr.min, t->b, t->b->r, S|D); + draw(b, Rpt(dr.min, addpt(dr.min, subpt(t->b->r.max, t->b->r.min))), t->b, 0, t->b->r.min); + if(t->hot) border(b, insetrect(dr, -2), 1, display->black, ZP); + } + else if(t->p){ + plmove(t->p, subpt(dr.min, t->p->r.min)); + pldraw(t->p, b); + } + else{ + string(b, dr.min, display->black, ZP, t->font, t->text); + if(t->hot) + line(b, Pt(dr.min.x, dr.max.y-1), + Pt(dr.max.x, dr.max.y-1), + Endsquare, Endsquare, 0, + display->black, ZP); + } + } + } + replclipr(b, b->repl, cr); +} +/* + * Reposition text already drawn in the window. + * We just move the pixels and update the positions of any + * enclosed panels + */ +void pl_reposition(Rtext *t, Image *b, Point p, Rectangle r){ + Point offs; + pl_cpy(b, p, r); + offs=subpt(p, r.min); + for(;t;t=t->next) + if(!eqrect(t->r, Rect(0,0,0,0)) && !t->b && t->p) + plmove(t->p, offs); +} +/* + * Rectangle r of Image b contains an image of Rtext t, offset by oldoffs. + * Redraw the text to have offset yoffs. + */ +void pl_rtredraw(Image *b, Rectangle r, Rtext *t, int yoffs, int oldoffs){ + int dy, size; + dy=oldoffs-yoffs; + size=r.max.y-r.min.y; + if(dy>=size || -dy>=size) + pl_rtdraw(b, r, t, yoffs); + else if(dy<0){ + pl_reposition(t, b, r.min, + Rect(r.min.x, r.min.y-dy, r.max.x, r.max.y)); + pl_rtdraw(b, Rect(r.min.x, r.max.y+dy, r.max.x, r.max.y), + t, yoffs+size+dy); + } + else if(dy>0){ + pl_reposition(t, b, Pt(r.min.x, r.min.y+dy), + Rect(r.min.x, r.min.y, r.max.x, r.max.y-dy)); + pl_rtdraw(b, Rect(r.min.x, r.min.y, r.max.x, r.min.y+dy), t, yoffs); + } +} +Rtext *pl_rthit(Rtext *t, int yoffs, Point p, Point ul){ + if(t==0) return 0; + p.x-=ul.x; + p.y+=yoffs-ul.y; + while(t->nextline && t->nextline->topy<=p.y) t=t->nextline; + for(;t!=0;t=t->next){ + if(t->topy>p.y) return 0; + if(ptinrect(p, t->r)) return t; + } + return 0; +} diff --git a/sys/src/cmd/mothra/libpanel/rtext.h b/sys/src/cmd/mothra/libpanel/rtext.h new file mode 100644 index 000000000..78a4b0ce9 --- /dev/null +++ b/sys/src/cmd/mothra/libpanel/rtext.h @@ -0,0 +1,11 @@ +/* + * Rtext definitions + */ +#define PL_NOPBIT 4 +#define PL_NARGBIT 12 +#define PL_ARGMASK ((1<<PL_NARGBIT)-1) +#define PL_SPECIAL(op) (((-1<<PL_NOPBIT)|op)<<PL_NARGBIT) +#define PL_OP(t) ((t)&~PL_ARGMASK) +#define PL_ARG(t) ((t)&PL_ARGMASK) +#define PL_TAB PL_SPECIAL(0) /* # of tab stops before text */ +void pltabsize(int, int); /* set min tab and tab size */ diff --git a/sys/src/cmd/mothra/libpanel/scrltest.c b/sys/src/cmd/mothra/libpanel/scrltest.c new file mode 100644 index 000000000..4e43b18e6 --- /dev/null +++ b/sys/src/cmd/mothra/libpanel/scrltest.c @@ -0,0 +1,65 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <panel.h> +Panel *root, *list; +char *genlist(Panel *, int which){ + static char buf[7]; + if(which<0 || 26<=which) return 0; + sprint(buf, "item %c", which+'a'); + return buf; +} +void hitgen(Panel *p, int buttons, int sel){ + USED(p, buttons, sel); +} +void ereshaped(Rectangle r){ + screen.r=r; + r=inset(r, 4); + plpack(root, r); + bitblt(&screen, screen.r.min, &screen, screen.r, Zero); + pldraw(root, &screen); +} +void done(Panel *p, int buttons){ + USED(p, buttons); + bitblt(&screen, screen.r.min, &screen, screen.r, Zero); + exits(0); +} +Panel *msg; +void message(char *s, ...){ + char buf[1024], *out; + va_list arg; + va_start(arg, s); + out = doprint(buf, buf+sizeof(buf), s, arg); + va_end(arg); + *out='\0'; + plinitlabel(msg, PACKN|FILLX, buf); + pldraw(msg, &screen); +} +Scroll s; +void save(Panel *p, int buttons){ + USED(p, buttons); + s=plgetscroll(list); + message("save %d %d %d %d", s); +} +void revert(Panel *p, int buttons){ + USED(p, buttons); + plsetscroll(list, s, &screen); + message("revert %d %d %d %d", s); +} +void main(void){ + Panel *g; + binit(0,0,0); + einit(Emouse); + plinit(screen.ldepth); + root=plgroup(0, 0); + g=plgroup(root, PACKN|EXPAND); + list=pllist(g, PACKE|EXPAND, genlist, 8, hitgen); + plscroll(list, 0, plscrollbar(g, PACKW)); + msg=pllabel(root, PACKN|FILLX, ""); + plbutton(root, PACKW, "save", save); + plbutton(root, PACKW, "revert", revert); + plbutton(root, PACKE, "done", done); + ereshaped(screen.r); + for(;;) plmouse(root, emouse(), &screen); +} diff --git a/sys/src/cmd/mothra/libpanel/scroll.c b/sys/src/cmd/mothra/libpanel/scroll.c new file mode 100644 index 000000000..2c8489e5b --- /dev/null +++ b/sys/src/cmd/mothra/libpanel/scroll.c @@ -0,0 +1,21 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <panel.h> +#include "pldefs.h" +void plscroll(Panel *scrollee, Panel *xscroller, Panel *yscroller){ + scrollee->xscroller=xscroller; + scrollee->yscroller=yscroller; + if(xscroller) xscroller->scrollee=scrollee; + if(yscroller) yscroller->scrollee=scrollee; +} +Scroll plgetscroll(Panel *p){ + return p->scr; +} +void plsetscroll(Panel *p, Scroll s){ + if(p->scroll){ + if(s.size.x) p->scroll(p, HORIZ, 2, s.pos.x, s.size.x); + if(s.size.y) p->scroll(p, VERT, 2, s.pos.y, s.size.y); + } +} diff --git a/sys/src/cmd/mothra/libpanel/scrollbar.c b/sys/src/cmd/mothra/libpanel/scrollbar.c new file mode 100644 index 000000000..f47417afc --- /dev/null +++ b/sys/src/cmd/mothra/libpanel/scrollbar.c @@ -0,0 +1,148 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <panel.h> +#include "pldefs.h" +typedef struct Scrollbar Scrollbar; +struct Scrollbar{ + int dir; /* HORIZ or VERT */ + int lo, hi; /* setting, in screen coordinates */ + int buttons; /* saved mouse buttons for transmittal to scrollee */ + Rectangle interior; + Point minsize; +}; +#define SBWID 15 /* should come from draw.c? */ +void pl_drawscrollbar(Panel *p){ + Scrollbar *sp; + sp=p->data; + sp->interior=pl_outline(p->b, p->r, p->state); + pl_sliderupd(p->b, sp->interior, sp->dir, sp->lo, sp->hi); +} +int pl_hitscrollbar(Panel *g, Mouse *m){ + int oldstate, pos, len, dy; + Point ul, size; + Scrollbar *sp; + sp=g->data; + ul=g->r.min; + size=subpt(g->r.max, g->r.min); + pl_interior(g->state, &ul, &size); + oldstate=g->state; + if(m->buttons&OUT && m->buttons&7){ + if(m->xy.y<g->r.min.y) m->xy.y=g->r.min.y; + if(m->xy.y>=g->r.max.y) m->xy.y=g->r.max.y-1; + if(ptinrect(m->xy, g->r)) + m->buttons&=~OUT; + } + if(sp->dir==HORIZ){ + pos=m->xy.x-ul.x; + len=size.x; + } + else{ + pos=m->xy.y-ul.y; + len=size.y; + } + if(pos<0) pos=0; + else if(pos>len) pos=len; + if(m->buttons&7){ + g->state=DOWN; + if(g->r.min.x<=m->xy.x && m->xy.x<g->r.max.x){ + sp->buttons=m->buttons; + switch(m->buttons){ + case 1: + dy=pos*(sp->hi-sp->lo)/len; + pl_sliderupd(g->b, sp->interior, sp->dir, sp->lo-dy, + sp->hi-dy); + break; + case 2: + if(g->scrollee && g->scrollee->scroll) + g->scrollee->scroll(g->scrollee, sp->dir, + m->buttons, pos, len); + break; + case 4: + dy=pos*(sp->hi-sp->lo)/len; + pl_sliderupd(g->b, sp->interior, sp->dir, sp->lo+dy, + sp->hi+dy); + break; + } + } + } + else{ + if(!(sp->buttons&2) && g->state==DOWN && g->scrollee && g->scrollee->scroll) + g->scrollee->scroll(g->scrollee, sp->dir, sp->buttons, + pos, len); + g->state=UP; + } + if(oldstate!=g->state) pldraw(g, g->b); + return g->state==DOWN; +} +void pl_typescrollbar(Panel *p, Rune c){ + USED(p, c); +} +Point pl_getsizescrollbar(Panel *p, Point children){ + USED(children); + return pl_boxsize(((Scrollbar *)p->data)->minsize, p->state); +} +void pl_childspacescrollbar(Panel *p, Point *ul, Point *size){ + USED(p, ul, size); +} +/* + * Arguments lo, hi and len are in the scrollee's natural coordinates + */ +void pl_setscrollbarscrollbar(Panel *p, int lo, int hi, int len){ + Point ul, size; + int mylen; + Scrollbar *sp; + sp=p->data; + ul=p->r.min; + size=subpt(p->r.max, p->r.min); + pl_interior(p->state, &ul, &size); + mylen=sp->dir==HORIZ?size.x:size.y; + if(len==0) len=1; + sp->lo=lo*mylen/len; + sp->hi=hi*mylen/len; + if(sp->lo<0) sp->lo=0; + if(sp->lo>=mylen) sp->hi=mylen-1; + if(sp->hi<=sp->lo) sp->hi=sp->lo+1; + if(sp->hi>mylen) sp->hi=mylen; + pldraw(p, p->b); +} +int pl_priscrollbar(Panel *, Point){ + return PRI_SCROLLBAR; +} +void plinitscrollbar(Panel *v, int flags){ + Scrollbar *sp; + sp=v->data; + v->flags=flags|LEAF; + v->pri=pl_priscrollbar; + v->state=UP; + v->draw=pl_drawscrollbar; + v->hit=pl_hitscrollbar; + v->type=pl_typescrollbar; + v->getsize=pl_getsizescrollbar; + v->childspace=pl_childspacescrollbar; + v->setscrollbar=pl_setscrollbarscrollbar; + switch(flags&PACK){ + case PACKN: + case PACKS: + sp->dir=HORIZ; + sp->minsize=Pt(0, SBWID); + v->flags|=FILLX; + break; + case PACKE: + case PACKW: + sp->dir=VERT; + sp->minsize=Pt(SBWID, 0); + v->flags|=FILLY; + break; + } + sp->lo=0; + sp->hi=0; + v->kind="scrollbar"; +} +Panel *plscrollbar(Panel *parent, int flags){ + Panel *v; + v=pl_newpanel(parent, sizeof(Scrollbar)); + plinitscrollbar(v, flags); + return v; +} diff --git a/sys/src/cmd/mothra/libpanel/slider.c b/sys/src/cmd/mothra/libpanel/slider.c new file mode 100644 index 000000000..150a7feff --- /dev/null +++ b/sys/src/cmd/mothra/libpanel/slider.c @@ -0,0 +1,97 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <panel.h> +#include "pldefs.h" +typedef struct Slider Slider; +struct Slider{ + int dir; /* HORIZ or VERT */ + int val; /* setting, in screen coordinates */ + Point minsize; + void (*hit)(Panel *, int, int, int); /* call back to user when slider changes */ + int buttons; +}; +void pl_drawslider(Panel *p){ + Rectangle r; + Slider *sp; + sp=p->data; + r=pl_box(p->b, p->r, UP); + switch(sp->dir){ + case HORIZ: pl_sliderupd(p->b, r, sp->dir, 0, sp->val); break; + case VERT: pl_sliderupd(p->b, r, sp->dir, r.max.y-sp->val, r.max.y); break; + } +} +int pl_hitslider(Panel *p, Mouse *m){ + int oldstate, oldval, len; + Point ul, size; + Slider *sp; + sp=p->data; + ul=p->r.min; + size=subpt(p->r.max, p->r.min); + pl_interior(p->state, &ul, &size); + oldstate=p->state; + oldval=sp->val; + SET(len); + if(m->buttons&OUT) + p->state=UP; + else if(m->buttons&7){ + p->state=DOWN; + sp->buttons=m->buttons; + if(sp->dir==HORIZ){ + sp->val=m->xy.x-ul.x; + len=size.x; + } + else{ + sp->val=ul.y+size.y-m->xy.y; + len=size.y; + } + if(sp->val<0) sp->val=0; + else if(sp->val>len) sp->val=len; + } + else /* mouse inside, but no buttons down */ + p->state=UP; + if(oldval!=sp->val || oldstate!=p->state) pldraw(p, p->b); + if(oldval!=sp->val && sp->hit) sp->hit(p, sp->buttons, sp->val, len); + return 0; +} +void pl_typeslider(Panel *p, Rune c){ + USED(p, c); +} +Point pl_getsizeslider(Panel *p, Point children){ + USED(children); + return pl_boxsize(((Slider *)p->data)->minsize, p->state); +} +void pl_childspaceslider(Panel *g, Point *ul, Point *size){ + USED(g, ul, size); +} +void plinitslider(Panel *v, int flags, Point size, void (*hit)(Panel *, int, int, int)){ + Slider *sp; + sp=v->data; + v->r=Rect(0,0,size.x,size.y); + v->flags=flags|LEAF; + v->state=UP; + v->draw=pl_drawslider; + v->hit=pl_hitslider; + v->type=pl_typeslider; + v->getsize=pl_getsizeslider; + v->childspace=pl_childspaceslider; + sp->minsize=size; + sp->dir=size.x>size.y?HORIZ:VERT; + sp->hit=hit; + v->kind="slider"; +} +Panel *plslider(Panel *parent, int flags, Point size, void (*hit)(Panel *, int, int, int)){ + Panel *p; + p=pl_newpanel(parent, sizeof(Slider)); + plinitslider(p, flags, size, hit); + return p; +} +void plsetslider(Panel *p, int value, int range){ + Slider *sp; + sp=p->data; + if(value<0) value=0; + else if(value>range) value=range; + if(sp->dir==HORIZ) sp->val=value*(p->r.max.x-p->r.min.x)/range; + else sp->val=value*(p->r.max.y-p->r.min.y)/range; +} diff --git a/sys/src/cmd/mothra/libpanel/textview.c b/sys/src/cmd/mothra/libpanel/textview.c new file mode 100644 index 000000000..e3120862b --- /dev/null +++ b/sys/src/cmd/mothra/libpanel/textview.c @@ -0,0 +1,246 @@ +/* + * Fonted text viewer, calls out to code in rtext.c + * + * Should redo this to copy the already-visible parts on scrolling & only + * update the newly appearing stuff -- then the offscreen assembly bitmap can go away. + */ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <panel.h> +#include "pldefs.h" +typedef struct Textview Textview; +struct Textview{ + void (*hit)(Panel *, int, Rtext *); /* call back to user on hit */ + Rtext *text; /* text */ + int yoffs; /* offset of top of screen */ + Rtext *hitword; /* text to hilite */ + Image *hitsave; /* for restoring hilit text */ + int twid; /* text width */ + int thgt; /* text height */ + Point minsize; /* smallest acceptible window size */ + int buttons; +}; +void pl_hiliteword(Panel *p, Rtext *w, int on){ + Point ul, size; + Rectangle r; + Textview *tp; + if(w==0 || (w->b==0 && w->p!=0)) return; + tp=p->data; + ul=p->r.min; + size=subpt(p->r.max, p->r.min); + pl_interior(UP, &ul, &size); + ul.y-=tp->yoffs; + r=rectaddpt(w->r, ul); + if(rectclip(&r, p->r)){ + if(on){ + if(tp->hitsave) freeimage(tp->hitsave); + tp->hitsave = allocimage(display, r, screen->chan, 0, DNofill); + if(tp->hitsave) draw(tp->hitsave, r, p->b, 0, r.min); + pl_highlight(p->b, r); + }else{ + if(tp->hitsave){ + draw(p->b, r, tp->hitsave, 0, r.min); + freeimage(tp->hitsave); + tp->hitsave = 0; + } + } + } +} +void pl_stuffbitmap(Panel *p, Image *b){ + p->b=b; + for(p=p->child;p;p=p->next) + pl_stuffbitmap(p, b); +} +/* + * If we draw the text in a backup bitmap and copy it onto the screen, + * the bitmap pointers in all the subpanels point to the wrong bitmap. + * This code fixes them. + */ +void pl_drawnon(Rtext *rp, Image *b){ + for(;rp!=0;rp=rp->next) + if(rp->b==0 && rp->p!=0) + pl_stuffbitmap(rp->p, b); +} +/* + * Mark the hilite and update the scroll bar + */ +void pl_fixtextview(Panel *p, Textview *tp, Rectangle r){ + Panel *sb; + int lo, hi; + pl_hiliteword(p, tp->hitword, 1); + lo=tp->yoffs; + hi=lo+r.max.y-r.min.y; /* wrong? */ + sb=p->yscroller; + if(sb && sb->setscrollbar) sb->setscrollbar(sb, lo, hi, tp->thgt); +} +void pl_drawtextview(Panel *p){ + int twid; + Rectangle r; + Textview *tp; + Image *b; + tp=p->data; + b=allocimage(display, p->r, screen->chan, 0, DNofill); + if(b==0) b=p->b; + r=pl_outline(b, p->r, p->state); + twid=r.max.x-r.min.x; + if(twid!=tp->twid){ + tp->twid=twid; + tp->thgt=pl_rtfmt(tp->text, tp->twid); + p->scr.size.y=tp->thgt; + } + p->scr.pos.y=tp->yoffs; + pl_rtdraw(b, r, tp->text, tp->yoffs); + if(b!=p->b){ + draw(p->b, p->r, b, 0, b->r.min); + freeimage(b); + pl_drawnon(tp->text, p->b); + } + pl_fixtextview(p, tp, r); +} +/* + * If t is a panel word, pass the mouse event on to it + */ +void pl_passon(Rtext *t, Mouse *m){ + if(t && t->b==0 && t->p!=0) plmouse(t->p, *m); +} +int pl_hittextview(Panel *p, Mouse *m){ + Rtext *oldhitword; + int hitme; + Point ul, size; + Textview *tp; + tp=p->data; + oldhitword=tp->hitword; + hitme=0; + pl_passon(oldhitword, m); + if(m->buttons&OUT) + p->state=UP; + else if(m->buttons&7){ + tp->buttons=m->buttons; + p->state=DOWN; + if(oldhitword==0 + || oldhitword->b!=0 + || oldhitword->p==0 + || (oldhitword->p->flags&REMOUSE)==0){ + ul=p->r.min; + size=subpt(p->r.max, p->r.min); + pl_interior(p->state, &ul, &size); + tp->hitword=pl_rthit(tp->text, tp->yoffs, m->xy, ul); + if(tp->hitword!=0 && tp->hitword->hot==0) tp->hitword=0; + } + } + else{ + if(p->state==DOWN) hitme=1; + p->state=UP; + } + if(tp->hitword!=oldhitword){ + pl_hiliteword(p, oldhitword, 0); + pl_hiliteword(p, tp->hitword, 1); + pl_passon(tp->hitword, m); + } + if(hitme && tp->hit && tp->hitword){ + pl_hiliteword(p, tp->hitword, 0); + if(tp->hitword->b!=0 || tp->hitword->p==0) + tp->hit(p, tp->buttons, tp->hitword); + tp->hitword=0; + } + return 0; +} +void pl_scrolltextview(Panel *p, int dir, int buttons, int num, int den){ + int yoffs; + Point ul, size; + Textview *tp; + Rectangle r; + if(dir!=VERT) return; + tp=p->data; + ul=p->r.min; + size=subpt(p->r.max, p->r.min); + pl_interior(p->state, &ul, &size); + switch(buttons){ + default: + SET(yoffs); + break; + case 1: /* left -- top moves to pointer */ + yoffs=tp->yoffs-num*size.y/den; + if(yoffs<0) yoffs=0; + break; + case 2: /* middle -- absolute index of file */ + yoffs=tp->thgt*num/den; + break; + case 4: /* right -- line pointed at moves to top */ + yoffs=tp->yoffs+num*size.y/den; + if(yoffs>tp->thgt) yoffs=tp->thgt; + break; + } + if(yoffs!=tp->yoffs){ + pl_hiliteword(p, tp->hitword, 0); + r=pl_outline(p->b, p->r, p->state); + pl_rtredraw(p->b, r, tp->text, yoffs, tp->yoffs); + tp->yoffs=yoffs; + pl_fixtextview(p, tp, r); + } +} +void pl_typetextview(Panel *g, Rune c){ + USED(g, c); +} +Point pl_getsizetextview(Panel *p, Point children){ + USED(children); + return pl_boxsize(((Textview *)p->data)->minsize, p->state); +} +void pl_childspacetextview(Panel *g, Point *ul, Point *size){ + USED(g, ul, size); +} +/* + * Priority depends on what thing inside the panel we're pointing at. + */ +int pl_pritextview(Panel *p, Point xy){ + Point ul, size; + Textview *tp; + Rtext *h; + tp=p->data; + ul=p->r.min; + size=subpt(p->r.max, p->r.min); + pl_interior(p->state, &ul, &size); + h=pl_rthit(tp->text, tp->yoffs, xy, ul); + if(h && h->b==0 && h->p!=0){ + p=pl_ptinpanel(xy, h->p); + if(p) return p->pri(p, xy); + } + return PRI_NORMAL; +} +void plinittextview(Panel *v, int flags, Point minsize, Rtext *t, void (*hit)(Panel *, int, Rtext *)){ + Textview *tp; + tp=v->data; + v->flags=flags|LEAF; + v->state=UP; + v->draw=pl_drawtextview; + v->hit=pl_hittextview; + v->type=pl_typetextview; + v->getsize=pl_getsizetextview; + v->childspace=pl_childspacetextview; + v->kind="textview"; + v->pri=pl_pritextview; + tp->hit=hit; + tp->minsize=minsize; + tp->text=t; + tp->yoffs=0; + tp->hitword=0; + v->scroll=pl_scrolltextview; + tp->twid=-1; + v->scr.pos=Pt(0,0); + v->scr.size=Pt(0,1); +} +Panel *pltextview(Panel *parent, int flags, Point minsize, Rtext *t, void (*hit)(Panel *, int, Rtext *)){ + Panel *v; + v=pl_newpanel(parent, sizeof(Textview)); + plinittextview(v, flags, minsize, t, hit); + return v; +} +int plgetpostextview(Panel *p){ + return ((Textview *)p->data)->yoffs; +} +void plsetpostextview(Panel *p, int yoffs){ + ((Textview *)p->data)->yoffs=yoffs; + pldraw(p, p->b); +} diff --git a/sys/src/cmd/mothra/libpanel/textwin.c b/sys/src/cmd/mothra/libpanel/textwin.c new file mode 100644 index 000000000..7a0629ad1 --- /dev/null +++ b/sys/src/cmd/mothra/libpanel/textwin.c @@ -0,0 +1,488 @@ +/* + * Text windows + * void twhilite(Textwin *t, int sel0, int sel1, int on) + * hilite (on=1) or unhilite (on=0) a range of characters + * void twselect(Textwin *t, Mouse *m) + * set t->sel0, t->sel1 from mouse input. + * Also hilites selection. + * Caller should first unhilite previous selection. + * void twreplace(Textwin *t, int r0, int r1, Rune *ins, int nins) + * Replace the given range of characters with the given insertion. + * Caller should unhilite selection while this is called. + * void twscroll(Textwin *t, int top) + * Character with index top moves to the top line of the screen. + * int twpt2rune(Textwin *t, Point p) + * which character is displayed at point p? + * void twreshape(Textwin *t, Rectangle r) + * save r and redraw the text + * Textwin *twnew(Bitmap *b, Font *f, Rune *text, int ntext) + * create a new text window + * void twfree(Textwin *t) + * get rid of a surplus Textwin + */ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <panel.h> +#include "pldefs.h" +/* + * Is text at point a before or after that at point b? + */ +int tw_before(Textwin *t, Point a, Point b){ + return a.y<b.y || a.y<b.y+t->hgt && a.x<b.x; +} +/* + * Return the character index indicated by point p, or -1 + * if its off-screen. The screen must be up-to-date. + * + * Linear search should be binary search. + */ +int twpt2rune(Textwin *t, Point p){ + Point *el, *lp; + el=t->loc+(t->bot-t->top); + for(lp=t->loc;lp!=el;lp++) + if(tw_before(t, p, *lp)){ + if(lp==t->loc) return t->top; + return lp-t->loc+t->top-1; + } + return t->bot; +} +/* + * Return ul corner of the character with the given index + */ +Point tw_rune2pt(Textwin *t, int i){ + if(i<t->top) return t->r.min; + if(i>t->bot) return t->r.max; + return t->loc[i-t->top]; +} +/* + * Store p at t->loc[l], extending t->loc if necessary + */ +void tw_storeloc(Textwin *t, int l, Point p){ + int nloc; + if(l>t->eloc-t->loc){ + nloc=l+100; + t->loc=realloc(t->loc, nloc*sizeof(Point)); + if(t->loc==0){ + fprint(2, "No mem in tw_storeloc\n"); + exits("no mem"); + } + t->eloc=t->loc+nloc; + } + t->loc[l]=p; +} +/* + * Set the locations at which the given runes should appear. + * Returns the index of the first rune not set, which might not + * be last because we reached the bottom of the window. + * + * N.B. this zaps the loc of r[last], so that value should be saved first, + * if it's important. + */ +int tw_setloc(Textwin *t, int first, int last, Point ul){ + Rune *r, *er; + int x, dt, lp; + char buf[UTFmax+1]; + er=t->text+last; + for(r=t->text+first,lp=first-t->top;r!=er && ul.y+t->hgt<=t->r.max.y;r++,lp++){ + tw_storeloc(t, lp, ul); + switch(*r){ + case '\n': + ul.x=t->r.min.x; + ul.y+=t->hgt; + break; + case '\t': + x=ul.x-t->r.min.x+t->mintab+t->tabstop; + x-=x%t->tabstop; + ul.x=x+t->r.min.x; + if(ul.x>t->r.max.x){ + ul.x=t->r.min.x; + ul.y+=t->hgt; + tw_storeloc(t, lp, ul); + if(ul.y+t->hgt>t->r.max.y) return r-t->text; + ul.x+=+t->tabstop; + } + break; + default: + buf[runetochar(buf, r)]='\0'; + dt=stringwidth(t->font, buf); + ul.x+=dt; + if(ul.x>t->r.max.x){ + ul.x=t->r.min.x; + ul.y+=t->hgt; + tw_storeloc(t, lp, ul); + if(ul.y+t->hgt>t->r.max.y) return r-t->text; + ul.x+=dt; + } + break; + } + } + tw_storeloc(t, lp, ul); + return r-t->text; +} +/* + * Draw the given runes at their locations. + * Bug -- saving up multiple characters would + * reduce the number of calls to string, + * and probably make this a lot faster. + */ +void tw_draw(Textwin *t, int first, int last){ + Rune *r, *er; + Point *lp, ul, ur; + char buf[UTFmax+1]; + if(first<t->top) first=t->top; + if(last>t->bot) last=t->bot; + if(last<=first) return; + er=t->text+last; + for(r=t->text+first,lp=t->loc+(first-t->top);r!=er;r++,lp++){ + if(lp->y+t->hgt>t->r.max.y){ + fprint(2, "chr %C, index %ld of %d, loc %d %d, off bottom\n", + *r, lp-t->loc, t->bot-t->top, lp->x, lp->y); + return; + } + switch(*r){ + case '\n': + ur=*lp; + break; + case '\t': + ur=*lp; + if(lp[1].y!=lp[0].y) + ul=Pt(t->r.min.x, lp[1].y); + else + ul=*lp; + pl_clr(t->b, Rpt(ul, Pt(lp[1].x, ul.y+t->hgt))); + break; + default: + buf[runetochar(buf, r)]='\0'; + /***/ pl_clr(t->b, Rpt(*lp, addpt(*lp, stringsize(t->font, buf)))); + ur=string(t->b, *lp, display->black, ZP, t->font, buf); + break; + } + if(lp[1].y!=lp[0].y) + /***/ pl_clr(t->b, Rpt(ur, Pt(t->r.max.x, ur.y+t->hgt))); + } +} +/* + * Hilight the characters with tops between ul and ur + */ +void tw_hilitep(Textwin *t, Point ul, Point ur){ + Point swap; + int y; + if(tw_before(t, ur, ul)){ swap=ul; ul=ur; ur=swap;} + y=ul.y+t->hgt; + if(y>t->r.max.y) y=t->r.max.y; + if(ul.y==ur.y) + pl_highlight(t->b, Rpt(ul, Pt(ur.x, y))); + else{ + pl_highlight(t->b, Rpt(ul, Pt(t->r.max.x, y))); + ul=Pt(t->r.min.x, y); + pl_highlight(t->b, Rpt(ul, Pt(t->r.max.x, ur.y))); + ul=Pt(t->r.min.x, ur.y); + y=ur.y+t->hgt; + if(y>t->r.max.y) y=t->r.max.y; + pl_highlight(t->b, Rpt(ul, Pt(ur.x, y))); + } +} +/* + * Hilite/unhilite the given range of characters + */ +void twhilite(Textwin *t, int sel0, int sel1, int on){ + Point ul, ur; + int swap, y; + if(sel1<sel0){ swap=sel0; sel0=sel1; sel1=swap; } + if(sel1<t->top || t->bot<sel0) return; + if(sel0<t->top) sel0=t->top; + if(sel1>t->bot) sel1=t->bot; + if(!on){ + if(sel1==sel0){ + ul=t->loc[sel0-t->top]; + y=ul.y+t->hgt; + if(y>t->r.max.y) y=t->r.max.y; + pl_clr(t->b, Rpt(ul, Pt(ul.x+1, y))); + }else + tw_draw(t, sel0, sel1); + return; + } + ul=t->loc[sel0-t->top]; + if(sel1==sel0) + ur=addpt(ul, Pt(1, 0)); + else + ur=t->loc[sel1-t->top]; + tw_hilitep(t, ul, ur); +} +/* + * Set t->sel[01] from mouse input. + * Also hilites the selection. + * Caller should unhilite the previous + * selection before calling this. + */ +void twselect(Textwin *t, Mouse *m){ + int sel0, sel1, newsel; + Point p0, p1, newp; + sel0=sel1=twpt2rune(t, m->xy); + p0=tw_rune2pt(t, sel0); + p1=addpt(p0, Pt(1, 0)); + twhilite(t, sel0, sel1, 1); + for(;;){ + *m=emouse(); + if(m->buttons==0) break; + newsel=twpt2rune(t, m->xy); + newp=tw_rune2pt(t, newsel); + if(eqpt(newp, p0)) newp=addpt(newp, Pt(1, 0)); + if(!eqpt(newp, p1)){ + if((sel0<=sel1 && sel1<newsel) || (newsel<sel1 && sel1<sel0)) + tw_hilitep(t, p1, newp); + else if((sel0<=newsel && newsel<sel1) || (sel1<newsel && newsel<=sel0)){ + twhilite(t, sel1, newsel, 0); + if(newsel==sel0) + tw_hilitep(t, p0, newp); + }else if((newsel<sel0 && sel0<=sel1) || (sel1<sel0 && sel0<=newsel)){ + twhilite(t, sel0, sel1, 0); + tw_hilitep(t, p0, newp); + } + sel1=newsel; + p1=newp; + } + } + if(sel0<=sel1){ + t->sel0=sel0; + t->sel1=sel1; + } + else{ + t->sel0=sel1; + t->sel1=sel0; + } +} +/* + * Clear the area following the last displayed character + */ +void tw_clrend(Textwin *t){ + Point ul; + int y; + ul=t->loc[t->bot-t->top]; + y=ul.y+t->hgt; + if(y>t->r.max.y) y=t->r.max.y; + pl_clr(t->b, Rpt(ul, Pt(t->r.max.x, y))); + ul=Pt(t->r.min.x, y); + pl_clr(t->b, Rpt(ul, t->r.max)); +} +/* + * Move part of a line of text, truncating the source or padding + * the destination on the right if necessary. + */ +void tw_moverect(Textwin *t, Point uld, Point urd, Point uls, Point urs){ + int sw, dw, d; + if(urs.y!=uls.y) urs=Pt(t->r.max.x, uls.y); + if(urd.y!=uld.y) urd=Pt(t->r.max.x, uld.y); + sw=uls.x-urs.x; + dw=uld.x-urd.x; + if(dw>sw){ + d=dw-sw; + pl_clr(t->b, Rect(urd.x-d, urd.y, urd.x, urd.y+t->hgt)); + dw=sw; + } + pl_cpy(t->b, uld, Rpt(uls, Pt(uls.x+dw, uls.y+t->hgt))); +} +/* + * Move a block of characters up or to the left: + * Identify contiguous runs of characters whose width doesn't change, and + * move them in one bitblt per run. + * If we get to a point where source and destination are x-aligned, + * they will remain x-aligned for the rest of the block. + * Then, if they are y-aligned, they're already in the right place. + * Otherwise, we can move them in three bitblts; one if all the + * remaining characters are on one line. + */ +void tw_moveup(Textwin *t, Point *dp, Point *sp, Point *esp){ + Point uld, uls; /* upper left of destination/source */ + int y; + while(sp!=esp && sp->x!=dp->x){ + uld=*dp; + uls=*sp; + while(sp!=esp && sp->y==uls.y && dp->y==uld.y && sp->x-uls.x==dp->x-uld.x){ + sp++; + dp++; + } + tw_moverect(t, uld, *dp, uls, *sp); + } + if(sp==esp || esp->y==dp->y) return; + if(esp->y==sp->y){ /* one line only */ + pl_cpy(t->b, *dp, Rpt(*sp, Pt(esp->x, sp->y+t->hgt))); + return; + } + y=sp->y+t->hgt; + pl_cpy(t->b, *dp, Rpt(*sp, Pt(t->r.max.x, y))); + pl_cpy(t->b, Pt(t->r.min.x, dp->y+t->hgt), + Rect(t->r.min.x, y, t->r.max.x, esp->y)); + y=dp->y+esp->y-sp->y; + pl_cpy(t->b, Pt(t->r.min.x, y), + Rect(t->r.min.x, esp->y, esp->x, esp->y+t->hgt)); +} +/* + * Same as above, but moving down and in reverse order, so as not to overwrite stuff + * not moved yet. + */ +void tw_movedn(Textwin *t, Point *dp, Point *bsp, Point *esp){ + Point *sp, urs, urd; + int dy; + dp+=esp-bsp; + sp=esp; + dy=dp->y-sp->y; + while(sp!=bsp && dp[-1].x==sp[-1].x){ + --dp; + --sp; + } + if(dy!=0){ + if(sp->y==esp->y) + pl_cpy(t->b, *dp, Rect(sp->x, sp->y, esp->x, esp->y+t->hgt)); + else{ + pl_cpy(t->b, Pt(t->r.min.x, sp->x+dy), + Rect(t->r.min.x, sp->y, esp->x, esp->y+t->hgt)); + pl_cpy(t->b, Pt(t->r.min.x, dp->y+t->hgt), + Rect(t->r.min.x, sp->y+t->hgt, t->r.max.x, esp->y)); + pl_cpy(t->b, *dp, + Rect(sp->x, sp->y, t->r.max.x, sp->y+t->hgt)); + } + } + while(sp!=bsp){ + urd=*dp; + urs=*sp; + while(sp!=bsp && sp[-1].y==sp[0].y && dp[-1].y==dp[0].y + && sp[-1].x-sp[0].x==dp[-1].x-dp[0].x){ + --sp; + --dp; + } + tw_moverect(t, *dp, urd, *sp, urs); + } +} +/* + * Move the given range of characters, already drawn on + * the given textwin, to the given location. + * Start and end must both index characters that are initially on-screen. + */ +void tw_relocate(Textwin *t, int first, int last, Point dst){ + Point *srcloc; + int nbyte; + if(first<t->top || last<first || t->bot<last) return; + nbyte=(last-first+1)*sizeof(Point); + srcloc=malloc(nbyte); + if(srcloc==0) return; + memmove(srcloc, &t->loc[first-t->top], nbyte); + tw_setloc(t, first, last, dst); + if(tw_before(t, dst, srcloc[0])) + tw_moveup(t, t->loc+first-t->top, srcloc, srcloc+(last-first)); + else + tw_movedn(t, t->loc+first-t->top, srcloc, srcloc+(last-first)); +} +/* + * Replace the runes with indices from r0 to r1-1 with the text + * pointed to by text, and with length ntext. + * Open up a hole in t->text, t->loc. + * Insert new text, calculate their locs (save the extra loc that's overwritten first) + * (swap saved & overwritten locs) + * move tail. + * calc locs and draw new text after tail, if necessary. + * draw new text, if necessary + */ +void twreplace(Textwin *t, int r0, int r1, Rune *ins, int nins){ + int olen, nlen, tlen, dtop; + Rune *ntext; + olen=t->etext-t->text; + nlen=olen+r0-r1+nins; + tlen=t->eslack-t->text; + if(nlen>tlen){ + tlen=nlen+100; + ntext=malloc(tlen*sizeof(Rune)); + memmove(ntext, t->text, r0*sizeof(Rune)); + memmove(ntext+r0+nins, t->text+r1, (olen-r1)*sizeof(Rune)); + t->text=ntext; + t->eslack=ntext+tlen; + } + else if(olen!=nlen) + memmove(t->text+r0+nins, t->text+r1, (olen-r1)*sizeof(Rune)); + if(nins!=0) /* ins can be 0 if nins==0 */ + memmove(t->text+r0, ins, nins*sizeof(Rune)); + t->etext=t->text+nlen; + if(r0>t->bot) /* insertion is completely below visible text */ + return; + if(r1<t->top){ /* insertion is completely above visible text */ + dtop=nlen-olen; + t->top+=dtop; + t->bot+=dtop; + return; + } + if(1 || t->bot<=r0+nins){ /* no useful text on screen below r0 */ + if(r0<=t->top) /* no useful text above, either */ + t->top=r0; + t->bot=tw_setloc(t, r0, nlen, t->loc[r0-t->top]); + tw_draw(t, r0, t->bot); + tw_clrend(t); + return; + } + /* + * code for case where there is useful text below is missing (see `1 ||' above) + */ +} +/* + * This works but is stupid. + */ +void twscroll(Textwin *t, int top){ + while(top!=0 && t->text[top-1]!='\n') --top; + t->top=top; + t->bot=tw_setloc(t, top, t->etext-t->text, t->r.min); + tw_draw(t, t->top, t->bot); + tw_clrend(t); +} +void twreshape(Textwin *t, Rectangle r){ + t->r=r; + t->bot=tw_setloc(t, t->top, t->etext-t->text, t->r.min); + tw_draw(t, t->top, t->bot); + tw_clrend(t); +} +Textwin *twnew(Image *b, Font *f, Rune *text, int ntext){ + Textwin *t; + t=malloc(sizeof(Textwin)); + if(t==0) return 0; + t->text=malloc((ntext+100)*sizeof(Rune)); + if(t->text==0){ + free(t); + return 0; + } + t->loc=malloc(100*sizeof(Point)); + if(t->loc==0){ + free(t->text); + free(t); + return 0; + } + t->eloc=t->loc+100; + t->etext=t->text+ntext; + t->eslack=t->etext+100; + if(ntext) memmove(t->text, text, ntext*sizeof(Rune)); + t->top=0; + t->bot=0; + t->sel0=0; + t->sel1=0; + t->b=b; + t->font=f; + t->hgt=f->height; + t->mintab=stringwidth(f, "0"); + t->tabstop=8*t->mintab; + return t; +} +void twfree(Textwin *t){ + free(t->loc); + free(t->text); + free(t); +} +/* + * Correct the character locations in a textwin after the panel is moved. + * This horrid hack would not be necessary if loc values were relative + * to the panel, rather than absolute. + */ +void twmove(Textwin *t, Point d){ + Point *lp; + t->r = rectaddpt(t->r, d); + for(lp=t->loc; lp<t->eloc; lp++) + *lp = addpt(*lp, d); +} diff --git a/sys/src/cmd/mothra/libpanel/utf.c b/sys/src/cmd/mothra/libpanel/utf.c new file mode 100644 index 000000000..30cb221cb --- /dev/null +++ b/sys/src/cmd/mothra/libpanel/utf.c @@ -0,0 +1,30 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <panel.h> +#include "pldefs.h" +/* + * This is the same definition that 8½ uses + */ +int pl_idchar(int c){ + if(c<=' ' + || 0x7F<=c && c<=0xA0 + || utfrune("!\"#$%&'()*+,-./:;<=>?@`[\\]^{|}~", c)) + return 0; + return 1; +} +int pl_rune1st(int c){ + return (c&0xc0)!=0x80; +} +char *pl_nextrune(char *s){ + do s++; while(!pl_rune1st(*s)); + return s; +} +int pl_runewidth(Font *f, char *s){ + char r[4], *t; + t=r; + do *t++=*s++; while(!pl_rune1st(*s)); + *t='\0'; + return stringwidth(f, r); +} diff --git a/sys/src/cmd/mothra/mkfile b/sys/src/cmd/mothra/mkfile new file mode 100644 index 000000000..1b8542ce8 --- /dev/null +++ b/sys/src/cmd/mothra/mkfile @@ -0,0 +1,35 @@ +</$objtype/mkfile + +TARG=mothra +LIB=libpanel/libpanel.$O.a +CFILES= \ + cistr.c \ + crackurl.c \ + file.c \ + filetype.c \ + forms.c \ + ftp.c \ + getpix.c \ + gopher.c \ + html.syntax.c \ + http.c \ + mothra.c \ + rdhtml.c \ + auth.c \ + +OFILES=${CFILES:%.c=%.$O} version.$O +HFILES=mothra.h html.h tcs.h libpanel/panel.h libpanel/rtext.h +BIN=/$objtype/bin +</sys/src/cmd/mkone + +CFLAGS=-Dplan9 -Ilibpanel +version.c: $CFILES + date|sed 's/^... //;s/ +/-/g;s/.*/char version[]="&";/' >version.c + +$LIB:V: + cd libpanel + mk + +clean nuke:V: + @{ cd libpanel; mk $target } + rm -f *.[$OS] [$OS].out $TARG diff --git a/sys/src/cmd/mothra/mothra.c b/sys/src/cmd/mothra/mothra.c new file mode 100644 index 000000000..cbb3ee7a1 --- /dev/null +++ b/sys/src/cmd/mothra/mothra.c @@ -0,0 +1,1072 @@ +/* + * Trivial web browser + */ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <keyboard.h> +#include <plumb.h> +#include <cursor.h> +#include <panel.h> +#include "mothra.h" +#include "rtext.h" +int verbose=0; /* -v flag causes html errors to appear in error log */ +int defdisplay=1; /* is the default (initial) display visible? */ +Panel *root; /* the whole display */ +Panel *alt; /* the alternate display */ +Panel *alttext; /* the alternate text window */ +Panel *cmd; /* command entry */ +Panel *curttl; /* label giving the title of the visible text */ +Panel *cururl; /* label giving the url of the visible text */ +Panel *list; /* list of previously acquired www pages */ +Panel *msg; /* message display */ +Panel *menu3; /* button 3 menu */ +Mouse mouse; /* current mouse data */ +char helpfile[] = "file:/sys/lib/mothra/help.html"; +char mothra[] = "mothra!"; +Url defurl={ + "http://plan9.bell-labs.com/", + 0, + "plan9.bell-labs.com", + "/", + "", + "", "", "", + 80, + HTTP, + HTML +}; +Url badurl={ + "No file loaded", + 0, + "", + "/dev/null", + "", "", "", + "", + 0, + FILE, + HTML +}; +Cursor patientcurs={ + 0, 0, + 0x01, 0x80, 0x03, 0xC0, 0x07, 0xE0, 0x07, 0xe0, + 0x07, 0xe0, 0x07, 0xe0, 0x03, 0xc0, 0x0F, 0xF0, + 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, + 0x0F, 0xF0, 0x1F, 0xF8, 0x3F, 0xFC, 0x3F, 0xFC, + + 0x01, 0x80, 0x03, 0xC0, 0x07, 0xE0, 0x04, 0x20, + 0x04, 0x20, 0x06, 0x60, 0x02, 0x40, 0x0C, 0x30, + 0x10, 0x08, 0x14, 0x08, 0x14, 0x28, 0x12, 0x28, + 0x0A, 0x50, 0x16, 0x68, 0x20, 0x04, 0x3F, 0xFC, +}; +Cursor confirmcurs={ + 0, 0, + 0x0F, 0xBF, 0x0F, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, + 0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x03, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC, + + 0x00, 0x0E, 0x07, 0x1F, 0x03, 0x17, 0x73, 0x6F, + 0xFB, 0xCE, 0xDB, 0x8C, 0xDB, 0xC0, 0xFB, 0x6C, + 0x77, 0xFC, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, + 0x94, 0xA6, 0x63, 0x3C, 0x63, 0x18, 0x94, 0x90 +}; +Cursor readingcurs={ + -10, -3, + 0x00, 0x00, 0x00, 0x00, 0x0F, 0xF0, 0x0F, 0xF0, + 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x1F, 0xF0, + 0x3F, 0xF0, 0x7F, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFB, 0xFF, 0xF3, 0xFF, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xE0, + 0x07, 0xE0, 0x01, 0xE0, 0x03, 0xE0, 0x07, 0x60, + 0x0E, 0x60, 0x1C, 0x00, 0x38, 0x00, 0x71, 0xB6, + 0x61, 0xB6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; +Www *current=0; +Url *selection=0; +int logfile; +void docmd(Panel *, char *); +void doprev(Panel *, int, int); +void selurl(char *); +void setcurrent(int, char *); +char *genwww(Panel *, int); +void updtext(Www *); +void dolink(Panel *, int, Rtext *); +void hit3(int, int); +char *buttons[]={ + "alt display", + "snarf url", + "paste", + "save hit", + "hit list", + "exit", + 0 +}; + +int wwwtop=0; +Www *www(int index){ + static Www a[1+NWWW]; + return &a[1+(index % NWWW)]; +} +int nwww(void){ + return wwwtop<NWWW ? wwwtop : NWWW; +} + +void err(Display *, char *msg){ + fprint(2, "err: %s (%r)\n", msg); + abort(); +} +int subpanel(Panel *obj, Panel *subj){ + if(obj==0) return 0; + if(obj==subj) return 1; + for(obj=obj->child;obj;obj=obj->next) + if(subpanel(obj, subj)) return 1; + return 0; +} +/* + * Make sure that the keyboard focus is on-screen, by adjusting it to + * be the cmd entry if necessary. + */ +void adjkb(void){ + Rtext *t; + int yoffs; + extern Panel *pl_kbfocus; /* this is a secret panel library name */ + yoffs=text->r.min.y-plgetpostextview(text); + for(t=current->text;t;t=t->next) if(!eqrect(t->r, Rect(0,0,0,0))){ + if(t->r.max.y+yoffs>text->r.max.y) break; + if(t->r.min.y+yoffs>=text->r.min.y + && t->b==0 + && subpanel(t->p, pl_kbfocus)) return; + } + plgrabkb(cmd); +} + +void scrolltext(int dy) +{ + Scroll s; + + s = plgetscroll(text); + s.pos.y += dy; + if(s.pos.y < 0) + s.pos.y = 0; + if(s.pos.y > s.size.y) + s.pos.y = s.size.y; + plsetscroll(text, s); + pldraw(root, screen); +} + +void mkpanels(void){ + Panel *p, *bar; + menu3=plmenu(0, 0, buttons, PACKN|FILLX, hit3); + root=plpopup(root, EXPAND, 0, 0, menu3); + p=plgroup(root, PACKN|FILLX); + msg=pllabel(p, PACKN|FILLX, mothra); + plplacelabel(msg, PLACEW); + pllabel(p, PACKW, "Go:"); + cmd=plentry(p, PACKN|FILLX, 0, "", docmd); + p=plgroup(root, PACKN|FILLX); + bar=plscrollbar(p, PACKW); + list=pllist(p, PACKN|FILLX, genwww, 8, doprev); + plscroll(list, 0, bar); + p=plgroup(root, PACKN|FILLX); + pllabel(p, PACKW, "Title:"); + curttl=pllabel(p, PACKE|EXPAND, "Initializing"); + plplacelabel(curttl, PLACEW); + p=plgroup(root, PACKN|FILLX); + pllabel(p, PACKW, "Url:"); + cururl=pllabel(p, PACKE|EXPAND, "---"); + plplacelabel(cururl, PLACEW); + p=plgroup(root, PACKN|EXPAND); + bar=plscrollbar(p, PACKW); + text=pltextview(p, PACKE|EXPAND, Pt(0, 0), 0, dolink); + plscroll(text, 0, bar); + plgrabkb(cmd); + alt=plpopup(0, PACKE|EXPAND, 0, 0, menu3); + bar=plscrollbar(alt, PACKW); + alttext=pltextview(alt, PACKE|EXPAND, Pt(0, 0), 0, dolink); + plscroll(alttext, 0, bar); +} +void killcohort(void){ + int i; + for(i=0;i!=3;i++){ /* It's a long way to the kitchen */ + postnote(PNGROUP, getpid(), "kill\n"); + sleep(1); + } +} +void dienow(void*, char*){ + noted(NDFLT); +} +int mkmfile(char *stem, int mode){ + char *henv; + char filename[NNAME]; + int f; + if(home[0]=='\0'){ + henv=getenv("home"); + if(henv){ + sprint(home, "%s/lib", henv); + f=create(home, OREAD, DMDIR|0777); + if(f!=-1) close(f); + sprint(home, "%s/lib/mothra", henv); + f=create(home, OREAD, DMDIR|0777); + if(f!=-1) close(f); + } + else + strcpy(home, "/tmp"); + } + snprint(filename, sizeof(filename), "%s/%s", home, stem); + f=create(filename, OWRITE, mode); + if(f==-1) + f=create(stem, OWRITE, mode); + return f; +} +void main(int argc, char *argv[]){ + Event e; + enum { Eplumb = 128 }; + Plumbmsg *pm; + Www *new; + char *url; + int errfile; + int i; + ARGBEGIN{ + case 'd': debug++; break; + case 'v': verbose=1; break; + default: goto Usage; + }ARGEND + + /* + * so that we can stop all subprocesses with a note, + * and to isolate rendezvous from other processes + */ + rfork(RFNOTEG|RFNAMEG|RFREND); + atexit(killcohort); + switch(argc){ + default: + Usage: + fprint(2, "Usage: %s [-d] [url]\n", argv[0]); + exits("usage"); + case 0: + url=getenv("url"); + if(url==0 || url[0]=='\0') + url="file:/sys/lib/mothra/start.html"; + break; + case 1: url=argv[0]; break; + } + errfile=mkmfile("mothra.err", 0666); + if(errfile!=-1){ + dup(errfile, 2); + close(errfile); + } + logfile=mkmfile("mothra.log", 0666|DMAPPEND); + + initdraw(err,0,"mothra"); + display->locking = 1; + chrwidth=stringwidth(font, "0"); + pltabsize(chrwidth, 8*chrwidth); + einit(Emouse|Ekeyboard); + eplumb(Eplumb, "web"); + etimer(0, 1000); + plinit(screen->depth); + if(debug) notify(dienow); + getfonts(); + hrule=allocimage(display, Rect(0, 0, 2048, 5), screen->chan, 0, DWhite); + if(hrule==0){ + fprint(2, "%s: can't allocimage!\n", argv[0]); + exits("no mem"); + } + draw(hrule, Rect(0,1,1280,3), display->black, 0, ZP); + linespace=allocimage(display, Rect(0, 0, 2048, 5), screen->chan, 0, DWhite); + if(linespace==0){ + fprint(2, "%s: can't allocimage!\n", argv[0]); + exits("no mem"); + } + bullet=allocimage(display, Rect(0,0,25, 8), screen->chan, 0, DWhite); + fillellipse(bullet, Pt(4,4), 3, 3, display->black, ZP); + new = www(-1); + new->url=&badurl; + new->base=&badurl; + strcpy(new->title, "See error message above"); + plrtstr(&new->text, 0, 0, font, "See error message above", 0, 0); + new->alldone=1; + mkpanels(); + + unlockdisplay(display); + eresized(0); + lockdisplay(display); + + geturl(url, GET, 0, 1, 0); + + if(logfile==-1) message("Can't open log file"); + mouse.buttons=0; + for(;;){ + if(mouse.buttons==0 && current){ + if(current->finished){ + updtext(current); + current->finished=0; + current->changed=0; + current->alldone=1; + message(mothra); + esetcursor(0); + } + else if(current->changed){ + updtext(current); + current->changed=0; + } + } + + unlockdisplay(display); + i=event(&e); + lockdisplay(display); + + switch(i){ + case Ekeyboard: + switch(e.kbdc){ + default: + adjkb(); + plkeyboard(e.kbdc); + break; + case Kup: + scrolltext(-text->size.y/4); + break; + case Kdown: + scrolltext(text->size.y/4); + break; + } + break; + case Emouse: + mouse=e.mouse; + plmouse(root, e.mouse); + break; + case Eplumb: + pm=e.v; + if(pm->ndata > 0) + geturl(pm->data, GET, 0, 1, 0); + plumbfree(pm); + break; + } + } +} +void message(char *s, ...){ + static char buf[1024]; + char *out; + va_list args; + va_start(args, s); + out = buf + vsnprint(buf, sizeof(buf), s, args); + va_end(args); + *out='\0'; + plinitlabel(msg, PACKN|FILLX, buf); + if(defdisplay) pldraw(msg, screen); +} +void htmlerror(char *name, int line, char *m, ...){ + static char buf[1024]; + char *out; + va_list args; + if(verbose){ + va_start(args, m); + out=buf+snprint(buf, sizeof(buf), "%s: line %d: ", name, line); + out+=vsnprint(out, sizeof(buf)-(out-buf)-1, m, args); + va_end(args); + *out='\0'; + fprint(2, "%s\n", buf); + } +} +void eresized(int new){ + Rectangle r; + + lockdisplay(display); + if(new && getwindow(display, Refnone) == -1) { + fprint(2, "getwindow: %r\n"); + exits("getwindow"); + } + r=screen->r; + plinitlabel(curttl, PACKE|EXPAND, "---"); + plinitlabel(cururl, PACKE|EXPAND, "---"); + plpack(root, r); + if(current){ + plinitlabel(curttl, PACKE|EXPAND, current->title); + plinitlabel(cururl, PACKE|EXPAND, current->url->fullname); + } + draw(screen, r, display->white, 0, ZP); + pldraw(root, screen); + unlockdisplay(display); +} +void *emalloc(int n){ + void *v; + v=malloc(n); + if(v==0){ + fprint(2, "out of space\n"); + exits("no mem"); + } + return v; +} +void *emallocz(int n, int z){ + void *v; + v = emalloc(n); + if(z) + memset(v, 0, n); + return v; +} + +char *genwww(Panel *, int index){ + static char buf[1024]; + int i; + + if(index >= nwww()) + return 0; + i = wwwtop-index-1; + snprint(buf, sizeof(buf), "%2d %s", i+1, www(i)->title); + return buf; +} + +void donecurs(void){ + esetcursor(current && current->alldone?0:&readingcurs); +} +/* + * selected text should be a url. + * get the document, scroll to the given tag + */ +void setcurrent(int index, char *tag){ + Www *new; + Rtext *tp; + Action *ap; + int i; + new=www(index); + if(new==current && (tag==0 || tag[0]==0)) return; + if(current) + current->yoffs=plgetpostextview(text); + current=new; + plinitlabel(curttl, PACKE|EXPAND, current->title); + if(defdisplay) pldraw(curttl, screen); + plinitlabel(cururl, PACKE|EXPAND, current->url->fullname); + if(defdisplay) pldraw(cururl, screen); + plinittextview(text, PACKE|EXPAND, Pt(0, 0), current->text, dolink); + if(tag && tag[0]){ + for(tp=current->text;tp;tp=tp->next){ + ap=tp->user; + if(ap && ap->name && strcmp(ap->name, tag)==0){ + current->yoffs=tp->topy; + break; + } + } + } + plsetpostextview(text, current->yoffs); + donecurs(); + flushimage(display, 1); +} +char *arg(char *s){ + do ++s; while(*s==' ' || *s=='\t'); + return s; +} +void save(Url *url, char *name){ + int ofd, ifd, n; + char buf[4096]; + ofd=create(name, OWRITE, 0666); + if(ofd==-1){ + message("save: %s: %r", name); + return; + } + esetcursor(&patientcurs); + ifd=urlopen(url, GET, 0); + donecurs(); + if(ifd==-1){ + message("save: %s: %r", selection->fullname); + close(ofd); + } + switch(rfork(RFNOTEG|RFFDG|RFPROC|RFNOWAIT)){ + case -1: + message("Can't fork -- please wait"); + esetcursor(&patientcurs); + while((n=read(ifd, buf, 4096))>0) + write(ofd, buf, n); + donecurs(); + break; + case 0: + while((n=read(ifd, buf, 4096))>0) + write(ofd, buf, n); + if(n==-1) fprint(2, "save: %s: %r\n", url->fullname); + _exits(0); + } + close(ifd); + close(ofd); +} +void screendump(char *name, int full){ + Image *b; + int fd; + fd=create(name, OWRITE|OTRUNC, 0666); + if(fd==-1){ + message("can't create %s", name); + return; + } + if(full){ + writeimage(fd, screen, 0); + } else { + if((b=allocimage(display, text->r, screen->chan, 0, DNofill)) == nil){ + message("can't allocate image"); + close(fd); + return; + } + draw(b, b->r, screen, 0, b->r.min); + writeimage(fd, b, 0); + freeimage(b); + } + close(fd); +} + +/* + * user typed a command. + */ +void docmd(Panel *p, char *s){ + USED(p); + while(*s==' ' || *s=='\t') s++; + /* + * Non-command does a get on the url + */ + if(s[0]!='\0' && s[1]!='\0' && s[1]!=' ') + geturl(s, GET, 0, 1, 0); + else switch(s[0]){ + default: + message("Unknown command %s, type h for help", s); + break; + case '?': + case 'h': + geturl(helpfile, GET, 0, 1, 0); + break; + case 'g': + s=arg(s); + if(*s=='\0'){ + if(selection) + geturl(selection->fullname, GET, 0, 1, 0); + else + message("no url selected"); + } + else geturl(s, GET, 0, 1, 0); + break; + case 'r': + s = arg(s); + if(*s == '\0') + s = selection ? selection->fullname : helpfile; + geturl(s, GET, 0, 0, 0); + break; + case 'W': + s=arg(s); + if(s=='\0'){ + message("Usage: W file"); + break; + } + screendump(s, 1); + break; + case 'w': + s=arg(s); + if(s=='\0'){ + message("Usage: w file"); + break; + } + screendump(s, 0); + break; + case 's': + s=arg(s); + if(*s=='\0'){ + if(selection){ + s=strrchr(selection->fullname, '/'); + if(s) s++; + } + if(s==0 || *s=='\0'){ + message("Usage: s file"); + break; + } + } + save(selection, s); + break; + case 'q': + draw(screen, screen->r, display->white, 0, ZP); + exits(0); + } + plinitentry(cmd, EXPAND, 0, "", docmd); + if(defdisplay) pldraw(cmd, screen); +} +void hiturl(int buttons, char *url, int map){ + switch(buttons){ + case 1: geturl(url, GET, 0, 1, map); break; + case 2: selurl(url); break; + case 4: message("Button 3 hit on url can't happen!"); break; + } +} +/* + * user selected from the list of available pages + */ +void doprev(Panel *p, int buttons, int index){ + int i; + USED(p); + if(index >= nwww()) + return; + i = wwwtop-index-1; + switch(buttons){ + case 1: setcurrent(i, 0); /* no break ... */ + case 2: selurl(www(i)->url->fullname); break; + case 4: message("Button 3 hit on page can't happen!"); break; + } +} + +/* + * Follow an html link + */ +void dolink(Panel *p, int buttons, Rtext *word){ + char mapurl[NNAME]; + Action *a; + Point coord; + int yoffs; + USED(p); + a=word->user; + if(a && a->link){ + if(a->ismap){ + yoffs=plgetpostextview(p); + coord=subpt(subpt(mouse.xy, word->r.min), p->r.min); + snprint(mapurl, sizeof(mapurl), "%s?%d,%d", a->link, coord.x, coord.y+yoffs); + hiturl(buttons, mapurl, 1); + } + else + hiturl(buttons, a->link, 0); + } +} +void filter(char *cmd, int fd){ + flushimage(display, 1); + switch(rfork(RFFDG|RFPROC|RFNOWAIT)){ + case -1: + message("Can't fork!"); + break; + case 0: + close(0); + dup(fd, 0); + close(fd); + execl("/bin/rc", "rc", "-c", cmd, 0); + message("Can't exec /bin/rc!"); + _exits(0); + default: + break; + } + close(fd); +} +void gettext(Www *w, int fd, int type){ + flushimage(display, 1); + switch(rfork(RFFDG|RFPROC|RFNOWAIT|RFMEM)){ + case -1: + message("Can't fork, please wait"); + if(type==HTML) + plrdhtml(w->url->fullname, fd, w); + else + plrdplain(w->url->fullname, fd, w); + break; + case 0: + if(type==HTML) + plrdhtml(w->url->fullname, fd, w); + else + plrdplain(w->url->fullname, fd, w); + _exits(0); + } + close(fd); +} + +void freetext(Rtext *t){ + Rtext *tt; + Action *a; + + tt = t; + for(; t!=0; t = t->next){ + t->b=0; + free(t->text); + t->text=0; + if(a = t->user){ + t->user=0; + free(a->image); + free(a->link); + free(a->name); + free(a); + } + } + plrtfree(tt); +} + +void popwin(char *cmd){ + flushimage(display, 1); + switch(rfork(RFFDG|RFPROC|RFNOWAIT)){ + case -1: + message("sorry, can't fork to %s", cmd); + break; + case 0: + execl("/bin/window", "window", "100 100 800 800", "rc", "-c", cmd, 0); + _exits(0); + } +} +int urlopen(Url *url, int method, char *body){ + int fd; + Url prev; + int nredir; + Dir *dir; + nredir=0; +Again: + if(++nredir==NREDIR){ + werrstr("redir loop"); + return -1; + } + seek(logfile, 0, 2); + fprint(logfile, "%s\n", url->fullname); + switch(url->access){ + default: + werrstr("unknown access type"); + return -1; + case FTP: + url->type = suffix2type(url->reltext); + return ftp(url); + case HTTP: + fd=http(url, method, body); + if(url->type==FORWARD){ + prev=*url; + crackurl(url, prev.redirname, &prev); + + /* + * I'm not convinced that the following two lines are right, + * but once I got a redir loop because they were missing. + */ + method=GET; + body=0; + goto Again; + } + return fd; + case FILE: + url->type=suffix2type(url->reltext); + fd=open(url->reltext, OREAD); + if(fd!=-1){ + dir=dirfstat(fd); + if(dir->mode&DMDIR){ + url->type=HTML; + free(dir); + return dir2html(url->reltext, fd); + } + free(dir); + } + return fd; + case GOPHER: + return gopher(url); + } +} + +int pipeline(char *cmd, int fd) +{ + int pfd[2]; + + if(pipe(pfd)==-1){ +Err: + close(fd); + werrstr("pipeline for %s failed: %r", cmd); + return -1; + } + switch(fork()){ + case -1: + close(pfd[0]); + close(pfd[1]); + goto Err; + case 0: + dup(fd, 0); + dup(pfd[0], 1); + close(pfd[0]); + close(pfd[1]); + execl("/bin/rc", "rc", "-c", cmd, 0); + _exits(0); + } + close(pfd[0]); + close(fd); + return pfd[1]; +} + +/* + * select the file at the given url + */ +void selurl(char *urlname){ + Url *cur; + static Url url; + if(current){ + cur=current->base; + /* + * I believe that the following test should never succeed + */ + if(cur==0){ + cur=current->url; + if(cur==0){ + fprint(2, "bad base & url, getting %s\n", urlname); + cur=&defurl; + } + else + fprint(2, "bad base, current %s, getting %s\n", + current->url->fullname, urlname); + } + } + else cur=&defurl; + crackurl(&url, urlname, cur); + selection=&url; + message("selected: %s", selection->fullname); +} +Url *copyurl(Url *u){ + Url *v; + v=emalloc(sizeof(Url)); + *v=*u; + return v; +} +void freeurl(Url *u){ + if(u!=&defurl && u!=&badurl) + free(u); +} + +/* + * get the file at the given url + */ +void geturl(char *urlname, int method, char *body, int cache, int map){ + int i, fd; + char cmd[NNAME]; + int pfd[2]; + Www *w; + + selurl(urlname); + selection->map=map; + + message("getting %s", selection->fullname); + esetcursor(&patientcurs); + switch(selection->access){ + default: + message("unknown access %d", selection->access); + break; + case TELNET: + sprint(cmd, "telnet %s", selection->reltext); + popwin(cmd); + break; + case MAILTO: + if(body){ + /* + * Undocumented Mozilla feature + */ + pipe(pfd); + switch(rfork(RFFDG|RFPROC|RFNOWAIT)){ + case -1: + message("Can't fork!"); + break; + case 0: + close(0); + dup(pfd[1], 0); + close(pfd[1]); + close(pfd[0]); + execl("/bin/upas/send", + "sendmail", selection->reltext, 0); + message("Can't exec sendmail"); + _exits(0); + default: + close(pfd[1]); + fprint(pfd[0], + "Content-type: application/x-www-form-urlencoded\n" + "Subject: Form posted from Mothra\n" + "\n" + "%s\n", body); + close(pfd[0]); + break; + } + } + else{ + snprint(cmd, sizeof(cmd), "mail %s", selection->reltext); + popwin(cmd); + } + break; + case FTP: + case HTTP: + case FILE: + case GOPHER: + fd=urlopen(selection, method, body); + if(fd==-1){ + message("%r"); + setcurrent(-1, 0); + break; + } + if(selection->type&COMPRESS) + fd=pipeline("/bin/uncompress", fd); + else if(selection->type&GUNZIP) + fd=pipeline("/bin/gunzip", fd); + switch(selection->type&~COMPRESSION){ + default: + message("Bad type %x in geturl", selection->type); + break; + case PLAIN: + case HTML: + w = www(i = wwwtop++); + if(i >= NWWW){ + extern void freeform(void *p); + extern void freepix(void *p); + + /* wait for the reader to finish the document */ + while(!w->finished && !w->alldone){ + unlockdisplay(display); + sleep(10); + lockdisplay(display); + } + + freetext(w->text); + freeform(w->form); + freepix(w->pix); + if(w->base != w->url) + freeurl(w->base); + freeurl(w->url); + memset(w, 0, sizeof(*w)); + } + if(selection->map){ + if(current && current->base) /* always succeeds */ + w->url=copyurl(current->base); + else{ + fprint(2, "no base for map!\n"); + w->url=copyurl(selection); + } + } + else + w->url=copyurl(selection); + w->finished = 0; + w->alldone = 0; + gettext(w, fd, selection->type&~COMPRESSION); + plinitlist(list, PACKN|FILLX, genwww, 8, doprev); + if(defdisplay) pldraw(list, screen); + setcurrent(i, selection->tag); + break; + case POSTSCRIPT: + case GIF: + case JPEG: + case PNG: + case PDF: + filter("page -w", fd); + break; + case TIFF: + filter("/sys/lib/mothra/tiffview", fd); + break; + case XBM: + filter("fb/xbm2pic|fb/9v", fd); + break; + } + } + donecurs(); +} +void updtext(Www *w){ + Rtext *t; + Action *a; + for(t=w->text;t;t=t->next){ + a=t->user; + if(a){ + if(a->field) + mkfieldpanel(t); + a->field=0; + } + } + w->yoffs=plgetpostextview(text); + plinittextview(text, PACKE|EXPAND, Pt(0, 0), w->text, dolink); + plsetpostextview(text, w->yoffs); + pldraw(root, screen); +} +Cursor confirmcursor={ + 0, 0, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + + 0x00, 0x0E, 0x07, 0x1F, 0x03, 0x17, 0x73, 0x6F, + 0xFB, 0xCE, 0xDB, 0x8C, 0xDB, 0xC0, 0xFB, 0x6C, + 0x77, 0xFC, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, + 0x94, 0xA6, 0x63, 0x3C, 0x63, 0x18, 0x94, 0x90, +}; +int confirm(int b){ + Mouse down, up; + esetcursor(&confirmcursor); + do down=emouse(); while(!down.buttons); + do up=emouse(); while(up.buttons); + donecurs(); + return down.buttons==(1<<(b-1)); +} +void snarf(Panel *p){ + int fd; + fd=create("/dev/snarf", OWRITE, 0666); + if(fd>=0){ + fprint(fd, "%s", selection->fullname); + close(fd); + } +} +void paste(Panel *p){ + char buf[1024]; + int n, len, fd; + fd=open("/dev/snarf", OREAD); + strncpy(buf, plentryval(p), sizeof(buf)); + len=strlen(buf); + n=read(fd, buf+len, 1023-len); + if(n>0){ + buf[len+n]='\0'; + plinitentry(cmd, PACKE|EXPAND, 0, buf, docmd); + pldraw(cmd, screen); + } + close(fd); +} +void hit3(int button, int item){ + char name[100], *home; + Panel *swap; + int fd; + USED(button); + switch(item){ + case 0: + swap=root; + root=alt; + alt=swap; + current->yoffs=plgetpostextview(text); + swap=text; + text=alttext; + alttext=swap; + defdisplay=!defdisplay; + plpack(root, screen->r); + plinittextview(text, PACKE|EXPAND, Pt(0, 0), current->text, dolink); + plsetpostextview(text, current->yoffs); + pldraw(root, screen); + break; + case 1: + snarf(cmd); + break; + case 2: + paste(cmd); + break; + case 3: + home=getenv("home"); + if(home==0){ + message("no $home"); + return; + } + snprint(name, sizeof(name), "%s/lib/mothra/hit.html", home); + fd=open(name, OWRITE); + if(fd==-1){ + fd=create(name, OWRITE, 0666); + if(fd==-1){ + message("can't open %s", name); + return; + } + fprint(fd, "<head><title>Hit List</title></head>\n"); + fprint(fd, "<body><h1>Hit list</h1>\n"); + } + seek(fd, 0, 2); + fprint(fd, "<p><a href=\"%s\">%s</a>\n", + selection->fullname, selection->fullname); + close(fd); + break; + case 4: + home=getenv("home"); + if(home==0){ + message("no $home"); + return; + } + snprint(name, sizeof(name), "file:%s/lib/mothra/hit.html", home); + geturl(name, GET, 0, 1, 0); + break; + case 5: + if(confirm(3)){ + draw(screen, screen->r, display->white, 0, ZP); + exits(0); + } + break; + } +} diff --git a/sys/src/cmd/mothra/mothra.gif b/sys/src/cmd/mothra/mothra.gif Binary files differnew file mode 100644 index 000000000..652e16e4b --- /dev/null +++ b/sys/src/cmd/mothra/mothra.gif diff --git a/sys/src/cmd/mothra/mothra.h b/sys/src/cmd/mothra/mothra.h new file mode 100644 index 000000000..3fb97f87f --- /dev/null +++ b/sys/src/cmd/mothra/mothra.h @@ -0,0 +1,143 @@ +enum{ + NWWW=64, /* # of pages we hold in the log */ + NNAME=512, + NLINE=256, + NAUTH=128, + NTITLE=81, /* length of title (including nul at end) */ + NLABEL=50, /* length of option name in forms */ + NREDIR=10, /* # of redirections we'll tolerate before declaring a loop */ +}; + +typedef struct Action Action; +typedef struct Url Url; +typedef struct Www Www; +typedef struct Scheme Scheme; +typedef struct Field Field; +struct Scheme{ + char *name; + int type; + int flags; + int port; +}; +struct Action{ + char *image; + Field *field; + char *link; + char *name; + int ismap; + int width; + int height; +}; +struct Url{ + char fullname[NNAME]; + Scheme *scheme; + char ipaddr[NNAME]; + char reltext[NNAME]; + char tag[NNAME]; + char redirname[NNAME]; + char autharg[NAUTH]; + char authtype[NTITLE]; + char charset[NNAME]; + int port; + int access; + int type; + int map; /* is this an image map? */ + int ssl; +}; +struct Www{ + Url *url; + Url *base; + void *pix; + void *form; + char title[NTITLE]; + Rtext *text; + int yoffs; + int changed; /* reader sets this every time it updates page */ + int finished; /* reader sets this when done */ + int alldone; /* page will not change further -- used to adjust cursor */ +}; + +/* + * url reference types -- COMPRESS and GUNZIP are flags that can modify any other type + * Changing these in a non-downward compatible way spoils cache entries + */ +enum{ + GIF=1, + HTML, + JPEG, + PIC, + TIFF, + AUDIO, + PLAIN, + XBM, + POSTSCRIPT, + FORWARD, + PDF, + SUFFIX, + ZIP, + PNG, + + COMPRESS=16, + GUNZIP=32, + COMPRESSION=16+32, +}; + +/* + * url access types + */ +enum{ + HTTP=1, + FTP, + FILE, + TELNET, + MAILTO, + GOPHER, +}; + +/* + * authentication types + */ +enum{ + ANONE, + ABASIC, +}; + +Image *hrule, *bullet, *linespace; +char home[512]; /* where to put files */ +int chrwidth; /* nominal width of characters in font */ +Panel *text; /* Panel displaying the current www page */ +int debug; /* command line flag */ + +/* + * HTTP methods + */ +enum{ + GET=1, + POST, +}; + +void plrdhtml(char *, int, Www *); +void plrdplain(char *, int, Www *); +void htmlerror(char *, int, char *, ...); /* user-supplied routine */ +void crackurl(Url *, char *, Url *); +void getpix(Rtext *, Www *); +int pipeline(char *, int); +int urlopen(Url *, int, char *); +void getfonts(void); +void *emalloc(int); +void *emallocz(int, int); +void setbitmap(Rtext *); +void message(char *, ...); +int ftp(Url *); +int http(Url *, int, char *); +int gopher(Url *); +int cistrcmp(char *, char *); +int cistrncmp(char *, char *, int); +int suffix2type(char *); +int content2type(char *, char *); +int encoding2type(char *); +void mkfieldpanel(Rtext *); +void geturl(char *, int, char *, int, int); +int dir2html(char *, int); +int auth(Url*, char*, int); +char version[]; diff --git a/sys/src/cmd/mothra/mothracompat.gif b/sys/src/cmd/mothra/mothracompat.gif Binary files differnew file mode 100644 index 000000000..3ec3731ab --- /dev/null +++ b/sys/src/cmd/mothra/mothracompat.gif diff --git a/sys/src/cmd/mothra/mothraenhanced.gif b/sys/src/cmd/mothra/mothraenhanced.gif Binary files differnew file mode 100644 index 000000000..e86de22ba --- /dev/null +++ b/sys/src/cmd/mothra/mothraenhanced.gif diff --git a/sys/src/cmd/mothra/rdhtml.c b/sys/src/cmd/mothra/rdhtml.c new file mode 100644 index 000000000..7940f0309 --- /dev/null +++ b/sys/src/cmd/mothra/rdhtml.c @@ -0,0 +1,1091 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <panel.h> +#include "mothra.h" +#include "html.h" +#include "rtext.h" +#include "tcs.h" + +typedef struct Fontdata Fontdata; +struct Fontdata{ + char *name; + Font *font; + int space; +}fontlist[4][4]={ + "lucidasans/unicode.7", 0, 0, + "lucidasans/unicode.8", 0, 0, + "lucidasans/unicode.10", 0, 0, + "lucidasans/unicode.13", 0, 0, + + "lucidasans/italicunicode.7", 0, 0, + "lucidasans/italicunicode.8", 0, 0, + "lucidasans/italicunicode.10", 0, 0, + "lucidasans/italicunicode.13", 0, 0, + + "lucidasans/boldunicode.7", 0, 0, + "lucidasans/boldunicode.8", 0, 0, + "lucidasans/boldunicode.10", 0, 0, + "lucidasans/boldunicode.13", 0, 0, + + "lucidasans/typeunicode.7", 0, 0, + "pelm/unicode.8", 0, 0, + "lucidasans/typeunicode.12", 0, 0, + "lucidasans/typeunicode.16", 0, 0, +}; +Fontdata *pl_whichfont(int f, int s){ + char name[100]; + if(fontlist[f][s].font==0){ + snprint(name, sizeof(name), "/lib/font/bit/%s.font", + fontlist[f][s].name); + fontlist[f][s].font=openfont(display, name); + if(fontlist[f][s].font==0) fontlist[f][s].font=font; + fontlist[f][s].space=stringwidth(fontlist[f][s].font, "0"); + } + return &fontlist[f][s]; + +} +void getfonts(void){ + int f, s; + for(f=0;f!=4;f++) + for(s=0;s!=4;s++) + pl_whichfont(f, s); +} +void pl_pushstate(Hglob *g, int t){ + ++g->state; + if(g->state==&g->stack[NSTACK]){ + htmlerror(g->name, g->lineno, "stack overflow at <%s>", tag[t].name); + --g->state; + } + g->state[0]=g->state[-1]; + g->state->tag=t; +} +void pl_linespace(Hglob *g){ + plrtbitmap(&g->dst->text, 1000000, 0, linespace, 0, 0); + g->para=0; + g->linebrk=0; +} +enum{ + HORIZ, + VERT, +}; +int strtolength(Hglob *g, int dir, char *str) +{ + double f; + + f = atof(str); + if(cistrstr(str, "px")) + return floor(f); + if(cistrstr(str, "%")) + return floor(f*((dir==HORIZ) ? Dx(g->dst->text->r) : Dy(g->dst->text->r))/100); + if(cistrstr(str, "em")){ + Point z; + z = stringsize(g->dst->text->font, "M"); + return floor(f*((dir==HORIZ) ? z.x : z.y)); + } + return floor(f); +} + +void pl_htmloutput(Hglob *g, int nsp, char *s, Field *field){ + Fontdata *f; + int space, indent; + Action *ap; + if(g->state->tag==Tag_title +/* || g->state->tag==Tag_textarea */ + || g->state->tag==Tag_select){ + if(s){ + if(g->tp!=g->text && g->tp!=g->etext && g->tp[-1]!=' ') + *g->tp++=' '; + while(g->tp!=g->etext && *s) *g->tp++=*s++; + if(g->state->tag==Tag_title) g->dst->changed=1; + *g->tp='\0'; + } + return; + } + f=pl_whichfont(g->state->font, g->state->size); + space=f->space; + indent=g->state->margin; + if(g->para){ + space=1000000; + indent+=g->state->indent; + } + else if(g->linebrk) + space=1000000; + else if(nsp<=0) + space=0; + if(g->state->image[0]==0 && g->state->link[0]==0 && g->state->name[0]==0 && field==0) + ap=0; + else{ + ap=mallocz(sizeof(Action), 1); + if(ap!=0){ + if(g->state->image[0]) + ap->image = strdup(g->state->image); + if(g->state->link[0]) + ap->link = strdup(g->state->link); + if(g->state->name[0]) + ap->name = strdup(g->state->name); + ap->ismap=g->state->ismap; + ap->width=g->state->width; + ap->height=g->state->height; + ap->field=field; + } + } + if(space<0) space=0; + if(indent<0) indent=0; + if(g->state->pre && s[0]=='\t'){ + space=0; + while(s[0]=='\t'){ + space++; + s++; + } + space=PL_TAB|space; + if(g->linebrk){ + indent=space; + space=1000000; + } + } + plrtstr(&g->dst->text, space, indent, f->font, strdup(s), g->state->link[0]!=0, ap); + g->para=0; + g->linebrk=0; + g->dst->changed=1; +} + +void pl_applycharset(Hglob *g) +{ + int fd, pfd[2], n; + char buf[NHBUF]; + char **cs, *charset; + + charset = nil; + for(cs = tcs; *cs; cs += 2){ + if(cistrcmp(cs[0], g->charset) == 0){ + charset = cs[1]; + break; + } + } + /* make sure we dont convet multiple times */ + g->charset[0]=0; + + /* no match, dont convert */ + if(charset == nil) + return; + + fd = g->hfd; + n = g->ehbuf - g->hbufp; + memcpy(buf, g->hbufp, n); + + if(pipe(pfd)==-1) + return; + switch(rfork(RFFDG|RFPROC|RFNOWAIT)){ + case -1: + close(pfd[0]); + close(pfd[1]); + return; + case 0: + dup(fd, 0); + dup(pfd[1], 1); + close(pfd[0]); + close(pfd[1]); + close(fd); + + write(1, buf, n); + while((n=read(0, buf, sizeof(buf)))>0) + write(1, buf, n); + _exits("no exec!"); + } + dup(pfd[0], fd); + close(pfd[0]); + close(pfd[1]); + g->hbufp = g->ehbuf; + snprint(buf, sizeof(buf), "tcs -s -f %s -t utf", charset); + if((fd=pipeline(buf, fd)) >= 0) + g->hfd = fd; +} + +/* + * Buffered read, no translation + * Save in cache. + */ +int pl_bread(Hglob *g){ + int n, c; + char err[1024]; + if(g->hbufp==g->ehbuf){ + n=read(g->hfd, g->hbuf, NHBUF); + if(n<=0){ + if(n<0){ + snprint(err, sizeof(err), "%r reading %s", g->name); + pl_htmloutput(g, 1, err, 0); + } + g->heof=1; + return EOF; + } + g->hbufp=g->hbuf; + g->ehbuf=g->hbuf+n; + } + c=*g->hbufp++&255; + if(c=='\n') g->lineno++; + return c; +} +/* + * Read a character, translating \r\n, \n\r, \r and \n into \n + */ +int pl_readc(Hglob *g){ + int c; + static int peek=-1; + if(peek!=-1){ + c=peek; + peek=-1; + } + else + c=pl_bread(g); + if(c=='\r'){ + c=pl_bread(g); + if(c!='\n') peek=c; + return '\n'; + } + if(c=='\n'){ + c=pl_bread(g); + if(c!='\r') peek=c; + return '\n'; + } + return c; +} +void pl_putback(Hglob *g, int c){ + if(g->npeekc==NPEEKC) htmlerror(g->name, g->lineno, "too much putback!"); + else if(c!=EOF) g->peekc[g->npeekc++]=c; +} +int pl_nextc(Hglob *g){ + int c; + int n; + Rune r; + char crune[4]; + if(g->heof) return EOF; + if(g->npeekc!=0) return g->peekc[--g->npeekc]; + c=pl_readc(g); + if(c=='<'){ + c=pl_readc(g); + if(c=='/'){ + c=pl_readc(g); + pl_putback(g, c); + pl_putback(g, '/'); + if('a'<=c && c<='z' || 'A'<=c && c<='Z') return STAG; + return '<'; + } + pl_putback(g, c); + if(c=='!' || 'a'<=c && c<='z' || 'A'<=c && c<='Z' || c=='?') return STAG; + return '<'; + } + if(c=='>') return ETAG; + if(c==EOF) return c; + n=0; + for (;;){ + crune[n++]=c; + if(fullrune(crune, n)){ + chartorune(&r, crune); + return r; + } + c=pl_readc(g); + if(c==EOF) + return EOF; + } + return c; +} +int entchar(int c){ + return c=='#' || 'a'<=c && c<='z' || 'A'<=c && c<='Z' || '0'<=c && c<='9'; +} +Entity *entsearch(char *s){ + int i, m, n, r; + i=0; + n=pl_entities; + while ((n-i) > 0) { + m=i+(n-i)/2; + r=strcmp(s, pl_entity[m].name); + if (r > 0) + i=m+1; + else if (r < 0) + n=m; + else + return &pl_entity[m]; + } + return 0; +} +/* + * remove entity references, in place. + * Potential bug: + * This doesn't work if removing an entity reference can lengthen the string! + * Fortunately, this doesn't happen. + */ +void pl_rmentities(Hglob *g, char *s){ + char *t, *u, c, svc; + Entity *ep; + Rune r; + t=s; + do{ + c=*s++; + if(c=='&' + && ((*s=='#' && strchr("0123456789Xx", s[1])) + || 'a'<=*s && *s<='z' + || 'A'<=*s && *s<='Z')){ + u=s; + while(entchar(*s)) s++; + svc=*s; + if(svc!=';') + htmlerror(g->name, g->lineno, "entity syntax error"); + *s++='\0'; + if(*u=='#'){ + if (u[1]=='X' || u[1]=='x') + r=strtol(u+2, 0, 16); + else + r=atoi(u+1); + t+=runetochar(t, &r); + if(svc!=';') *--s=svc; + } + else{ + ep=entsearch(u); + if(ep && ep->name){ + t+=runetochar(t, &ep->value); + if(svc!=';') *--s=svc; + } + else{ + htmlerror(g->name, g->lineno, + "unknown entity %s", u); + s[-1]=svc; + s=u; + *t++='&'; + } + } + } + else *t++=c; + }while(c); +} +/* + * Skip over white space + */ +char *pl_white(char *s){ + while(*s==' ' || *s=='\t' || *s=='\n' || *s=='\r') s++; + return s; +} +/* + * Skip over HTML word + */ +char *pl_word(char *s){ + if ('a'<=*s && *s<='z' || 'A'<=*s && *s<='Z') { + s++; + while('a'<=*s && *s<='z' || 'A'<=*s && *s<='Z' || '0'<=*s && *s<='9' || *s=='-' || *s=='.') s++; + } + return s; +} +/* + * Skip to matching quote + */ +char *pl_quote(char *s){ + char q; + q=*s++; + while(*s!=q && *s!='\0') s++; + return s; +} +void pl_dnl(char *s){ + char *t; + for(t=s;*s;s++) if(*s!='\r' && *s!='\n') *t++=*s; + *t='\0'; +} +void pl_tagparse(Hglob *g, char *str){ + char *s, *t, *name, c; + Pair *ap; + Tag *tagp; + g->tag=Tag_end; + ap=g->attr; + if(str[0]=='!'){ /* test should be strncmp(str, "!--", 3)==0 */ + g->tag=Tag_comment; + ap->name=0; + return; + } + if(str[0]=='/') str++; + name=str; + s=pl_word(str); + if(*s!=' ' && *s!='\n' && *s!='\t' && *s!='\0'){ + htmlerror(g->name, g->lineno, "bad tag name in %s", str); + ap->name=0; + return; + } + if(*s!='\0') *s++='\0'; + for(t=name;t!=s;t++) if('A'<=*t && *t<='Z') *t+='a'-'A'; + /* + * Binary search would be faster here + */ + for(tagp=tag;tagp->name;tagp++) if(strcmp(name, tagp->name)==0) break; + g->tag=tagp-tag; + if(g->tag==Tag_end) htmlerror(g->name, g->lineno, "no tag %s", name); + for(;;){ + s=pl_white(s); + if(*s=='\0'){ + ap->name=0; + return; + } + ap->name=s; + s=pl_word(s); + t=pl_white(s); + c=*t; + *s='\0'; + for(s=ap->name;*s;s++) if('A'<=*s && *s<='Z') *s+='a'-'A'; + if(c=='='){ + s=pl_white(t+1); + if(*s=='\'' || *s=='"'){ + ap->value=s+1; + s=pl_quote(s); + if(*s=='\0'){ + htmlerror(g->name, g->lineno, + "No terminating quote in rhs of attribute %s", + ap->name); + ap->name=0; + return; + } + *s++='\0'; + pl_dnl(ap->value); + } + else{ + /* read up to white space or > */ + ap->value=s; + while(*s!=' ' && *s!='\t' && *s!='\n' && *s!='\0') s++; + if(*s!='\0') *s++='\0'; + } + pl_rmentities(g, ap->value); + } + else{ + if(c!='\0') s++; + ap->value=""; + } + if(ap==&g->attr[NATTR-1]) + htmlerror(g->name, g->lineno, "too many attributes!"); + else ap++; + } +} +int pl_getcomment(Hglob *g){ + int c; + if((c=pl_nextc(g))=='-' && (c=pl_nextc(g))=='-'){ + /* <!-- eats everything until --> or EOF */ + for(;;){ + while((c=pl_nextc(g))!='-' && c!=EOF) + ; + if(c==EOF) + break; + if((c=pl_nextc(g))=='-'){ + while((c=pl_nextc(g))=='-') + ; + if(c==ETAG || c==EOF) + break; + } + } + } else { + /* <! eats everything until > or EOF */ + while(c!=ETAG && c!=EOF) + c=pl_nextc(g); + } + if(c==EOF) + htmlerror(g->name, g->lineno, "EOF in comment"); + g->tag=Tag_comment; + g->attr->name=0; + g->token[0]='\0'; + return TAG; +} +int lrunetochar(char *p, int v) +{ + Rune r; + + r=v; + return runetochar(p, &r); +} + +/* + * Read a start or end tag -- the caller has read the initial < + */ +int pl_gettag(Hglob *g){ + char *tokp; + int c; + tokp=g->token; + if((c=pl_nextc(g))=='!' || c=='?') + return pl_getcomment(g); + pl_putback(g, c); + while((c=pl_nextc(g))!=ETAG && c!=EOF) + if(tokp!=&g->token[NTOKEN-3]) tokp += lrunetochar(tokp, c); + *tokp='\0'; + if(c==EOF) htmlerror(g->name, g->lineno, "EOF in tag"); + pl_tagparse(g, g->token); + if(g->token[0]!='/') return TAG; + if(g->attr[0].name!=0) + htmlerror(g->name, g->lineno, "end tag should not have attributes"); + return ENDTAG; +} +/* + * The next token is a tag, an end tag or a sequence of + * non-white characters. + * If inside <pre>, newlines are converted to <br> and spaces are preserved. + * Otherwise, spaces and newlines are noted and discarded. + */ +int pl_gettoken(Hglob *g){ + char *tokp; + int c; + if(g->state->pre) switch(c=pl_nextc(g)){ + case STAG: return pl_gettag(g); + case EOF: return EOF; + case '\n': + pl_tagparse(g, "br"); + return TAG; + default: + tokp=g->token; + while(c=='\t'){ + if(tokp!=&g->token[NTOKEN-3]) tokp += lrunetochar(tokp, c); + c=pl_nextc(g); + } + while(c!='\t' && c!='\n' && c!=STAG && c!=EOF){ + if(c==ETAG) c='>'; + if(tokp!=&g->token[NTOKEN-3]) tokp += lrunetochar(tokp, c); + c=pl_nextc(g); + } + *tokp='\0'; + pl_rmentities(g, g->token); + pl_putback(g, c); + g->nsp=0; + g->spacc=0; + return TEXT; + } + while((c=pl_nextc(g))==' ' || c=='\t' || c=='\n') + if(g->spacc!=-1) + g->spacc++; + switch(c){ + case STAG: return pl_gettag(g); + case EOF: return EOF; + default: + tokp=g->token; + do{ + if(c==ETAG) c='>'; + if(tokp!=&g->token[NTOKEN-3]) tokp += lrunetochar(tokp, c); + c=pl_nextc(g); + }while(c!=' ' && c!='\t' && c!='\n' && c!=STAG && c!=EOF); + *tokp='\0'; + pl_rmentities(g, g->token); + pl_putback(g, c); + g->nsp=g->spacc; + g->spacc=0; + return TEXT; + } +} +char *pl_getattr(Pair *attr, char *name){ + for(;attr->name;attr++) + if(strcmp(attr->name, name)==0) + return attr->value; + return 0; +} +int pl_hasattr(Pair *attr, char *name){ + for(;attr->name;attr++) + if(strcmp(attr->name, name)==0) + return 1; + return 0; +} +void plaintext(Hglob *g){ + char line[NLINE]; + char *lp, *elp; + int c; + g->state->font=CWIDTH; + g->state->size=NORMAL; + elp=&line[NLINE+1]; + lp=line; + for(;;){ + c=pl_readc(g); + if(c==EOF) break; + if(c=='\n' || lp==elp){ + *lp='\0'; + g->linebrk=1; + pl_htmloutput(g, 0, line, 0); + lp=line; + } + if(c=='\t'){ + do *lp++=' '; while(lp!=elp && utfnlen(line, lp-line)%8!=0); + } + else if(c!='\n') + lp += lrunetochar(lp, c); + } + if(lp!=line){ + *lp='\0'; + g->linebrk=1; + pl_htmloutput(g, 0, line, 0); + } +} +void plrdplain(char *name, int fd, Www *dst){ + Hglob g; + g.state=g.stack; + g.state->tag=Tag_html; + g.state->font=CWIDTH; + g.state->size=NORMAL; + g.state->pre=0; + g.state->image[0]=0; + g.state->link[0]=0; + g.state->name[0]=0; + g.state->margin=0; + g.state->indent=20; + g.state->ismap=0; + g.state->table=0; + g.dst=dst; + g.hfd=fd; + g.name=name; + g.ehbuf=g.hbufp=g.hbuf; + g.npeekc=0; + g.heof=0; + g.lineno=1; + g.linebrk=1; + g.para=0; + g.text=dst->title; + g.tp=g.text; + g.etext=g.text+NTITLE-1; + g.spacc=0; + g.form=0; + strncpy(g.text, name, NTITLE); + pl_applycharset(&g); + plaintext(&g); + dst->finished=1; +} +void plrdhtml(char *name, int fd, Www *dst){ + Stack *sp; + char buf[20]; + char *str; + Hglob g; + int t; + int tagerr; + g.state=g.stack; + g.state->tag=Tag_html; + g.state->font=ROMAN; + g.state->size=NORMAL; + g.state->pre=0; + g.state->image[0]=0; + g.state->link[0]=0; + g.state->name[0]=0; + g.state->margin=0; + g.state->indent=25; + g.state->ismap=0; + g.state->width=0; + g.state->height=0; + g.state->table=0; + g.dst=dst; + g.hfd=fd; + g.name=name; + g.ehbuf=g.hbufp=g.hbuf; + g.npeekc=0; + g.heof=0; + g.lineno=1; + g.linebrk=1; + g.para=0; + g.text=dst->title; + g.tp=g.text; + g.etext=g.text+NTITLE-1; + dst->title[0]='\0'; + dst->base=dst->url; + g.spacc=0; + g.form=0; + g.charset[0] = '\0'; + strncpy(g.charset, dst->url->charset, sizeof(g.charset)); + + for(;;) switch(pl_gettoken(&g)){ + case TAG: + switch(tag[g.tag].action){ + case OPTEND: + for(sp=g.state;sp!=g.stack && sp->tag!=g.tag;--sp); + if(sp->tag!=g.tag) + pl_pushstate(&g, g.tag); + else + for(;g.state!=sp;--g.state) + if(tag[g.state->tag].action!=OPTEND) + htmlerror(g.name, g.lineno, + "end tag </%s> missing", + tag[g.state->tag].name); + break; + case END: + pl_pushstate(&g, g.tag); + break; + } + switch(g.tag){ + default: + htmlerror(g.name, g.lineno, + "unimplemented tag <%s>", tag[g.tag].name); + break; + case Tag_end: /* unrecognized start tag */ + break; + case Tag_meta: + if((str=pl_getattr(g.attr, "http-equiv")) && + (cistrcmp(str, "content-type"))==0 && + (str=pl_getattr(g.attr, "content")) && + (str=cistrstr(str, "charset="))){ + strncpy(g.charset, str+8, sizeof(g.charset)); + pl_applycharset(&g); + } + break; + case Tag_img: + if(str=pl_getattr(g.attr, "src")) + strncpy(g.state->image, str, sizeof(g.state->image)); + g.state->ismap=pl_hasattr(g.attr, "ismap"); + if(str=pl_getattr(g.attr, "width")) + g.state->width = strtolength(&g, HORIZ, str); + if(str=pl_getattr(g.attr, "height")) + g.state->height = strtolength(&g, VERT, str); + str=pl_getattr(g.attr, "alt"); + if(str==0){ + if(g.state->image[0]) + str=g.state->image; + else + str="[[image]]"; + } + pl_htmloutput(&g, 0, str, 0); + g.state->image[0]=0; + g.state->ismap=0; + g.state->width=0; + g.state->height=0; + break; + case Tag_plaintext: + g.spacc=0; + plaintext(&g); + break; + case Tag_comment: + case Tag_html: + case Tag_link: + case Tag_nextid: + break; + case Tag_table: + g.state->table++; + break; + case Tag_tr: + if(g.state->table==1){ + g.spacc=0; + g.linebrk=1; + } else + g.spacc++; + break; + case Tag_td: + g.spacc++; + break; + case Tag_a: + if(str=pl_getattr(g.attr, "href")) + strncpy(g.state->link, str, sizeof(g.state->link)); + if(str=pl_getattr(g.attr, "name")){ + strncpy(g.state->name, str, sizeof(g.state->name)); + pl_htmloutput(&g, 0, "", 0); + } + break; + case Tag_frame: + pl_htmloutput(&g, 0, "FRAME: ", 0); + if(str=pl_getattr(g.attr, "src")) + strncpy(g.state->link, str, sizeof(g.state->link)); + if(str=pl_getattr(g.attr, "name")) + strncpy(g.state->name, str, sizeof(g.state->name)); + else + str = g.state->link; + pl_htmloutput(&g, 0, str, 0); + g.state->link[0]=0; + g.state->name[0] =0; + g.spacc=0; + g.linebrk=1; + break; + case Tag_address: + g.spacc=0; + g.linebrk=1; + g.state->font=ROMAN; + g.state->size=NORMAL; + g.state->margin=300; + g.state->indent=50; + break; + case Tag_b: + case Tag_strong: + g.state->font=BOLD; + break; + case Tag_base: + if(str=pl_getattr(g.attr, "href")){ + dst->base=emalloc(sizeof(Url)); + crackurl(dst->base, str, dst->url); + } + break; + case Tag_blockquot: + g.spacc=0; + g.linebrk=1; + g.state->margin+=50; + g.state->indent=20; + break; + case Tag_body: + pl_applycharset(&g); + case Tag_head: + g.state->font=ROMAN; + g.state->size=NORMAL; + g.state->margin=0; + g.state->indent=20; + g.spacc=0; + break; + case Tag_br: + g.spacc=0; + g.linebrk=1; + break; + case Tag_center: + /* more to come */ + break; + case Tag_cite: + g.state->font=ITALIC; + g.state->size=NORMAL; + break; + case Tag_code: + g.state->font=CWIDTH; + g.state->size=NORMAL; + break; + case Tag_dd: + g.linebrk=1; + g.state->indent=0; + g.state->font=ROMAN; + g.spacc=0; + break; + case Tag_dfn: + htmlerror(g.name, g.lineno, "<dfn> deprecated"); + g.state->font=BOLD; + g.state->size=NORMAL; + break; + case Tag_dl: + g.state->font=BOLD; + g.state->size=NORMAL; + g.state->margin+=40; + g.spacc=0; + break; + case Tag_dt: + g.para=1; + g.state->indent=-40; + g.state->font=BOLD; + g.spacc=0; + break; + case Tag_font: + /* more to come */ + break; + case Tag_u: + htmlerror(g.name, g.lineno, "<u> deprecated"); + case Tag_em: + case Tag_i: + case Tag_var: + g.state->font=ITALIC; + break; + case Tag_h1: + g.linebrk=1; + g.state->font=BOLD; + g.state->size=ENORMOUS; + g.state->margin+=100; + g.spacc=0; + break; + case Tag_h2: + pl_linespace(&g); + g.state->font=BOLD; + g.state->size=ENORMOUS; + g.spacc=0; + break; + case Tag_h3: + g.linebrk=1; + pl_linespace(&g); + g.state->font=ITALIC; + g.state->size=ENORMOUS; + g.state->margin+=20; + g.spacc=0; + break; + case Tag_h4: + pl_linespace(&g); + g.state->font=BOLD; + g.state->size=LARGE; + g.state->margin+=10; + g.spacc=0; + break; + case Tag_h5: + pl_linespace(&g); + g.state->font=ITALIC; + g.state->size=LARGE; + g.state->margin+=10; + g.spacc=0; + break; + case Tag_h6: + pl_linespace(&g); + g.state->font=BOLD; + g.state->size=LARGE; + g.spacc=0; + break; + case Tag_hr: + g.spacc=0; + plrtbitmap(&g.dst->text, 1000000, g.state->margin, hrule, 0, 0); + break; + case Tag_key: + htmlerror(g.name, g.lineno, "<key> deprecated"); + case Tag_kbd: + g.state->font=CWIDTH; + break; + case Tag_dir: + case Tag_menu: + case Tag_ol: + case Tag_ul: + g.state->number=0; + g.linebrk=1; + g.state->margin+=25; + g.state->indent=-25; + g.spacc=0; + break; + case Tag_li: + g.spacc=0; + switch(g.state->tag){ + default: + htmlerror(g.name, g.lineno, "can't have <li> in <%s>", + tag[g.state->tag].name); + case Tag_dir: /* supposed to be multi-columns, can't do! */ + case Tag_menu: + g.linebrk=1; + break; + case Tag_ol: + g.para=1; + snprint(buf, sizeof(buf), "%2d ", ++g.state->number); + pl_htmloutput(&g, 0, buf, 0); + break; + case Tag_ul: + g.para=0; + g.linebrk=0; + g.spacc=-1; + plrtbitmap(&g.dst->text, 100000, + g.state->margin+g.state->indent, bullet, 0, 0); + break; + } + break; + case Tag_p: + g.para=1; + g.spacc=0; + break; + case Tag_listing: + case Tag_xmp: + htmlerror(g.name, g.lineno, "<%s> deprecated", tag[g.tag].name); + case Tag_pre: + case Tag_samp: + g.state->indent=0; + g.state->pre=1; + g.state->font=CWIDTH; + g.state->size=NORMAL; + pl_linespace(&g); + break; + case Tag_tt: + g.state->font=CWIDTH; + g.state->size=NORMAL; + break; + case Tag_title: + g.text=dst->title+strlen(dst->title); + g.tp=g.text; + g.etext=dst->title+NTITLE-1; + break; + case Tag_form: + case Tag_input: + case Tag_select: + case Tag_option: + case Tag_textarea: + case Tag_isindex: + rdform(&g); + break; + case Tag_script: + case Tag_style: + /* + * ignore the content of these tags, eat tokens until we + * reach a matching endtag. + */ + t = g.tag; + for(;;){ + switch(pl_gettoken(&g)){ + default: + continue; + case ENDTAG: + if(g.tag != t) + continue; + case EOF: + break; + } + break; + } + break; + } + break; + + case ENDTAG: + /* + * If the end tag doesn't match the top, we try to uncover a match + * on the stack. + */ + if(g.state->tag!=g.tag){ + tagerr=0; + for(sp=g.state;sp!=g.stack;--sp){ + if(sp->tag==g.tag) + break; + if(tag[g.state->tag].action!=OPTEND) tagerr++; + } + if(sp==g.stack){ + if(tagerr) + htmlerror(g.name, g.lineno, + "end tag mismatch <%s>...</%s>, ignored", + tag[g.state->tag].name, tag[g.tag].name); + } + else{ + if(tagerr) + htmlerror(g.name, g.lineno, + "end tag mismatch <%s>...</%s>, " + "intervening tags popped", + tag[g.state->tag].name, tag[g.tag].name); + g.state=sp-1; + } + } + else if(g.state==g.stack) + htmlerror(g.name, g.lineno, "end tag </%s> at stack bottom", + tag[g.tag].name); + else + --g.state; + switch(g.tag){ + case Tag_select: + case Tag_form: + case Tag_textarea: + endform(&g); + break; + case Tag_h1: + case Tag_h2: + case Tag_h3: + case Tag_h4: + pl_linespace(&g); + break; + case Tag_address: + case Tag_blockquot: + case Tag_body: + case Tag_dir: + case Tag_dl: + case Tag_dt: + case Tag_h5: + case Tag_h6: + case Tag_listing: + case Tag_menu: + case Tag_ol: + case Tag_samp: + case Tag_title: + case Tag_ul: + case Tag_xmp: + g.linebrk=1; + break; + case Tag_table: + if(g.state->table==0) + g.linebrk=1; + break; + case Tag_pre: + pl_linespace(&g); + break; + } + break; + case TEXT: + pl_htmloutput(&g, g.nsp, g.token, 0); + break; + case EOF: + for(;g.state!=g.stack;--g.state) + if(tag[g.state->tag].action!=OPTEND) + htmlerror(g.name, g.lineno, + "missing </%s> at EOF", tag[g.state->tag].name); + *g.tp='\0'; + dst->changed=1; + getpix(dst->text, dst); + dst->finished=1; + return; + } +} diff --git a/sys/src/cmd/mothra/tcs.h b/sys/src/cmd/mothra/tcs.h new file mode 100644 index 000000000..752c0548d --- /dev/null +++ b/sys/src/cmd/mothra/tcs.h @@ -0,0 +1,172 @@ +/* mapping from web charset to tcs */ +char *tcs[] ={ +"iso_8859-1:1987", "8859-1", +"iso-ir-100", "8859-1", +"iso_8859-1", "8859-1", +"iso-8859-1", "8859-1", +"latin1", "8859-1", +"l1", "8859-1", +"ibm819", "8859-1", +"cp819", "8859-1", +"csisolatin1", "8859-1", +"iso_8859-2:1987", "8859-2", +"iso-ir-101", "8859-2", +"iso_8859-2", "8859-2", +"iso-8859-2", "8859-2", +"latin2", "8859-2", +"l2", "8859-2", +"csisolatin2", "8859-2", +"iso_8859-3:1988", "8859-3", +"iso-ir-109", "8859-3", +"iso_8859-3", "8859-3", +"iso-8859-3", "8859-3", +"latin3", "8859-3", +"l3", "8859-3", +"csisolatin3", "8859-3", +"iso_8859-4:1988", "8859-4", +"iso-ir-110", "8859-4", +"iso_8859-4", "8859-4", +"iso-8859-4", "8859-4", +"latin4", "8859-4", +"l4", "8859-4", +"csisolatin4", "8859-4", +"iso_8859-5:1988", "8859-5", +"iso-ir-144", "8859-5", +"iso_8859-5", "8859-5", +"iso-8859-5", "8859-5", +"cyrillic", "8859-5", +"csisolatincyrillic", "8859-5", +"iso_8859-6:1987", "8859-6", +"iso-ir-127", "8859-6", +"iso_8859-6", "8859-6", +"iso-8859-6", "8859-6", +"ecma-114", "8859-6", +"asmo-708", "8859-6", +"arabic", "8859-6", +"csisolatinarabic", "8859-6", +"iso_8859-7:1987", "8859-7", +"iso-ir-126", "8859-7", +"iso_8859-7", "8859-7", +"iso-8859-7", "8859-7", +"elot_928", "8859-7", +"ecma-118", "8859-7", +"greek", "8859-7", +"greek8", "8859-7", +"csisolatingreek", "8859-7", +"iso_8859-8:1988", "8859-8", +"iso-ir-138", "8859-8", +"iso_8859-8", "8859-8", +"iso-8859-8", "8859-8", +"hebrew", "8859-8", +"csisolatinhebrew", "8859-8", +"iso_8859-9:1989", "8859-9", +"iso-ir-148", "8859-9", +"iso_8859-9", "8859-9", +"iso-8859-9", "8859-9", +"latin5", "8859-9", +"l5", "8859-9", +"csisolatin5", "8859-9", +"iso-8859-15", "8859-15", +"iso_8859-15", "8859-15", +"latin-9", "8859-15", +"ansi_x3.4-1968", "ascii", +"iso-ir-6", "ascii", +"ansi_x3.4-1986", "ascii", +"iso_646.irv:1991", "ascii", +"ascii", "ascii", +"iso646-us", "ascii", +"us-ascii", "ascii", +"us", "ascii", +"ibm367", "ascii", +"cp367", "ascii", +"csascii", "ascii", +"big5", "big5", +"csbig5", "big5", +"ibm437", "ibm437", +"cp437", "ibm437", +"437", "ibm437", +"cspc8codepage437", "ibm437", +"ibm850", "ibm850", +"cp850", "ibm850", +"850", "ibm850", +"cspc850multilingual", "ibm850", +"ibm852", "ibm852", +"cp852", "ibm852", +"852", "ibm852", +"cspcp852", "ibm852", +"ibm855", "ibm855", +"cp855", "ibm855", +"855", "ibm855", +"csibm855", "ibm855", +"ibm857", "ibm857", +"cp857", "ibm857", +"857", "ibm857", +"csibm857", "ibm857", +"ibm862", "ibm862", +"cp862", "ibm862", +"862", "ibm862", +"cspc862latinhebrew", "ibm862", +"ibm866", "ibm866", +"cp866", "ibm866", +"866", "ibm866", +"csibm866", "ibm866", +"windows-1250", "windows-1250", +"windows-1251", "windows-1251", +"windows-1252", "windows-1252", +"windows-1253", "windows-1253", +"windows-1254", "windows-1254", +"windows-1255", "windows-1255", +"windows-1256", "windows-1256", +"windows-1257", "windows-1257", +"windows-1258", "windows-1258", +"ks_c_5601-1987", "euc-k", +"iso-ir-149", "euc-k", +"ks_c_5601-1989", "euc-k", +"ksc_5601", "euc-k", +"korean", "euc-k", +"csksc56011987", "euc-k", +"euc-kr", "euc-k", +"cseuckr", "euc-k", +"gb2312", "gb", +"gbk", "gbk", +"csgb2312", "gb", +"gb_2312-80", "gb", +"iso-ir-58", "gb", +"chinese", "gb", +"csiso58gb231280", "gb", +"iso-2022-jp", "jis-kanji", +"csiso2022jp", "jis-kanji", +"koi8-r", "koi8", +"cskoi8r", "koi8", +"macintosh", "macrom", +"mac", "macrom", +"csmacintosh", "macrom", +"ibm865", "msdos2", +"cp865", "msdos2", +"865", "msdos2", +"csibm865", "msdos2", +"shift_jis", "ms-kanji", +"ms_kanji", "ms-kanji", +"csshiftjis", "ms-kanji", +"sen_850200_b", "sf1", +"iso-ir-10", "sf1", +"fi", "sf1", +"iso646-fi", "sf1", +"iso646-se", "sf1", +"se", "sf1", +"csiso10swedish", "sf1", +"sen_850200_c", "sf2", +"iso-ir-11", "sf2", +"iso646-se2", "sf2", +"se2", "sf2", +"csiso11swedishfornames", "sf2", +"tis-620", "tis", +"extended_unix_code_packed_format_for_japanese", "ujis", +"cseucpkdfmtjapanese", "ujis", +"euc-jp", "ujis", +"iso-10646-utf-1", "utf1", +"csiso10646utf1", "utf1", +"viscii", "viscii", +"csviscii", "viscii", +nil, nil, +}; diff --git a/sys/src/cmd/mothra/urlcanon.c b/sys/src/cmd/mothra/urlcanon.c new file mode 100644 index 000000000..2ffaeb750 --- /dev/null +++ b/sys/src/cmd/mothra/urlcanon.c @@ -0,0 +1,70 @@ +#include <u.h> +#include <libc.h> +void *emalloc(int n){ + void *p; + p=malloc(n); + if(p==0){ + fprint(2, "can't malloc\n"); + exits("no mem"); + } + return p; +} +void urlcanon(char *name){ + char *s, *t; + char **comp, **p, **q; + int rooted; + rooted=name[0]=='/'; + /* + * Break the name into a list of components + */ + comp=emalloc(strlen(name)*sizeof(char *)); + p=comp; + *p++=name; + for(s=name;;s++){ + if(*s=='/'){ + *p++=s+1; + *s='\0'; + } + else if(*s=='\0') + break; + } + *p=0; + /* + * go through the component list, deleting components that are empty (except + * the last component) or ., and any .. and its non-.. predecessor. + */ + p=q=comp; + while(*p){ + if(strcmp(*p, "")==0 && p[1]!=0 + || strcmp(*p, ".")==0) + p++; + else if(strcmp(*p, "..")==0 && q!=comp && strcmp(q[-1], "..")!=0){ + --q; + p++; + } + else + *q++=*p++; + } + *q=0; + /* + * rebuild the path name + */ + s=name; + if(rooted) *s++='/'; + for(p=comp;*p;p++){ + t=*p; + while(*t) *s++=*t++; + if(p[1]!=0) *s++='/'; + } + *s='\0'; + free(comp); +} +void main(int argc, char *argv[]){ + int i; + for(i=1;i!=argc;i++){ + print("%s: ", argv[i]); + urlcanon(argv[i]); + print("%s\n", argv[i]); + } + exits(0); +} diff --git a/sys/src/cmd/mothra/version.c b/sys/src/cmd/mothra/version.c new file mode 100644 index 000000000..1e8feb792 --- /dev/null +++ b/sys/src/cmd/mothra/version.c @@ -0,0 +1 @@ +char version[]="Sep-4-19:07:30-CET-2011"; |