#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include <cursor.h>
#include <mouse.h>
#include <keyboard.h>
#include <frame.h>
#include <fcall.h>
#include <plumb.h>
#include "dat.h"
#include "fns.h"
/* for generating syms in mkfile only: */
#include <bio.h>
#include "edit.h"
void mousethread(void*);
void keyboardthread(void*);
void waitthread(void*);
void xfidallocthread(void*);
void newwindowthread(void*);
void plumbproc(void*);
Reffont **fontcache;
int nfontcache;
char wdir[512] = ".";
Reffont *reffonts[2];
int snarffd = -1;
int mainpid;
int plumbsendfd;
int plumbeditfd;
enum{
NSnarf = 1000 /* less than 1024, I/O buffer size */
};
Rune snarfrune[NSnarf+1];
char *fontnames[2] =
{
"/lib/font/bit/lucidasans/euro.8.font",
"/lib/font/bit/lucm/unicode.9.font",
};
Command *command;
void acmeerrorinit(void);
void readfile(Column*, char*);
int shutdown(void*, char*);
void
derror(Display*, char *errorstr)
{
error(errorstr);
}
void
threadmain(int argc, char *argv[])
{
int i;
char *p, *loadfile;
char buf[256];
Column *c;
int ncol;
Display *d;
static void *arg[1];
rfork(RFENVG|RFNAMEG);
ncol = -1;
loadfile = nil;
ARGBEGIN{
case 'a':
globalautoindent = TRUE;
break;
case 'b':
bartflag = TRUE;
break;
case 'c':
p = ARGF();
if(p == nil)
goto Usage;
ncol = atoi(p);
if(ncol <= 0)
goto Usage;
break;
case 'f':
fontnames[0] = ARGF();
if(fontnames[0] == nil)
goto Usage;
break;
case 'F':
fontnames[1] = ARGF();
if(fontnames[1] == nil)
goto Usage;
break;
case 'l':
loadfile = ARGF();
if(loadfile == nil)
goto Usage;
break;
default:
Usage:
fprint(2, "usage: acme -a -c ncol -f fontname -F fixedwidthfontname -l loadfile\n");
exits("usage");
}ARGEND
quotefmtinstall();
cputype = getenv("cputype");
objtype = getenv("objtype");
home = getenv("home");
p = getenv("tabstop");
if(p != nil){
maxtab = strtoul(p, nil, 0);
free(p);
}
if(maxtab == 0)
maxtab = 4;
if(loadfile)
rowloadfonts(loadfile);
putenv("font", fontnames[0]);
snarffd = open("/dev/snarf", OREAD|OCEXEC);
if(cputype){
sprint(buf, "/acme/bin/%s", cputype);
bind(buf, "/bin", MBEFORE);
}
bind("/acme/bin", "/bin", MBEFORE);
getwd(wdir, sizeof wdir);
if(geninitdraw(nil, derror, fontnames[0], "acme", nil, Refnone) < 0){
fprint(2, "acme: can't open display: %r\n");
exits("geninitdraw");
}
d = display;
font = d->defaultfont;
reffont.f = font;
reffonts[0] = &reffont;
incref(&reffont); /* one to hold up 'font' variable */
incref(&reffont); /* one to hold up reffonts[0] */
fontcache = emalloc(sizeof(Reffont*));
nfontcache = 1;
fontcache[0] = &reffont;
iconinit();
timerinit();
rxinit();
cwait = threadwaitchan();
ccommand = chancreate(sizeof(Command**), 0);
ckill = chancreate(sizeof(Rune*), 0);
cxfidalloc = chancreate(sizeof(Xfid*), 0);
cxfidfree = chancreate(sizeof(Xfid*), 0);
cnewwindow = chancreate(sizeof(Channel*), 0);
cerr = chancreate(sizeof(char*), 0);
cedit = chancreate(sizeof(int), 0);
cexit = chancreate(sizeof(int), 0);
cwarn = chancreate(sizeof(void*), 1);
if(cwait==nil || ccommand==nil || ckill==nil || cxfidalloc==nil || cxfidfree==nil || cerr==nil || cexit==nil || cwarn==nil){
fprint(2, "acme: can't create initial channels: %r\n");
exits("channels");
}
mousectl = initmouse(nil, screen);
if(mousectl == nil){
fprint(2, "acme: can't initialize mouse: %r\n");
exits("mouse");
}
mouse = mousectl;
keyboardctl = initkeyboard(nil);
if(keyboardctl == nil){
fprint(2, "acme: can't initialize keyboard: %r\n");
exits("keyboard");
}
mainpid = getpid();
plumbeditfd = plumbopen("edit", OREAD|OCEXEC);
if(plumbeditfd >= 0){
cplumb = chancreate(sizeof(Plumbmsg*), 0);
proccreate(plumbproc, nil, STACK);
}
plumbsendfd = plumbopen("send", OWRITE|OCEXEC);
fsysinit();
#define WPERCOL 8
disk = diskinit();
if(!loadfile || !rowload(&row, loadfile, TRUE)){
rowinit(&row, screen->clipr);
if(ncol < 0){
if(argc == 0)
ncol = 2;
else{
ncol = (argc+(WPERCOL-1))/WPERCOL;
if(ncol < 2)
ncol = 2;
}
}
if(ncol == 0)
ncol = 2;
for(i=0; i<ncol; i++){
c = rowadd(&row, nil, -1);
if(c==nil && i==0)
error("initializing columns");
}
c = row.col[row.ncol-1];
if(argc == 0)
readfile(c, wdir);
else
for(i=0; i<argc; i++){
p = utfrrune(argv[i], '/');
if((p!=nil && strcmp(p, "/guide")==0) || i/WPERCOL>=row.ncol)
readfile(c, argv[i]);
else
readfile(row.col[i/WPERCOL], argv[i]);
}
}
flushimage(display, 1);
acmeerrorinit();
threadcreate(keyboardthread, nil, STACK);
threadcreate(mousethread, nil, STACK);
threadcreate(waitthread, nil, STACK);
threadcreate(xfidallocthread, nil, STACK);
threadcreate(newwindowthread, nil, STACK);
threadnotify(shutdown, 1);
recvul(cexit);
killprocs();
threadexitsall(nil);
}
void
readfile(Column *c, char *s)
{
Window *w;
Rune rb[256];
int nb, nr;
Runestr rs;
w = coladd(c, nil, nil, -1);
cvttorunes(s, strlen(s), rb, &nb, &nr, nil);
rs = cleanrname((Runestr){rb, nr});
winsetname(w, rs.r, rs.nr);
textload(&w->body, 0, s, 1);
w->body.file->mod = FALSE;
w->dirty = FALSE;
winsettag(w);
textscrdraw(&w->body);
textsetselect(&w->tag, w->tag.file->nc, w->tag.file->nc);
}
char *oknotes[] ={
"delete",
"hangup",
"kill",
"exit",
nil
};
int dumping;
int
shutdown(void*, char *msg)
{
int i;
killprocs();
if(!dumping && strcmp(msg, "kill")!=0 && strcmp(msg, "exit")!=0 && getpid()==mainpid){
dumping = TRUE;
rowdump(&row, nil);
}
for(i=0; oknotes[i]; i++)
if(strncmp(oknotes[i], msg, strlen(oknotes[i])) == 0)
threadexitsall(msg);
print("acme: %s\n", msg);
abort();
return 0;
}
void
killprocs(void)
{
Command *c;
fsysclose();
// if(display)
// flushimage(display, 1);
for(c=command; c; c=c->next)
postnote(PNGROUP, c->pid, "hangup");
remove(acmeerrorfile);
}
static int errorfd;
void
acmeerrorproc(void *)
{
char *buf;
int n;
threadsetname("acmeerrorproc");
buf = emalloc(8192+1);
while((n=read(errorfd, buf, 8192)) >= 0){
buf[n] = '\0';
sendp(cerr, estrdup(buf));
}
}
void
acmeerrorinit(void)
{
int fd, pfd[2];
char buf[64];
if(pipe(pfd) < 0)
error("can't create pipe");
sprint(acmeerrorfile, "/srv/acme.%s.%d", getuser(), mainpid);
fd = create(acmeerrorfile, OWRITE, 0666);
if(fd < 0){
remove(acmeerrorfile);
fd = create(acmeerrorfile, OWRITE, 0666);
if(fd < 0)
error("can't create acmeerror file");
}
sprint(buf, "%d", pfd[0]);
write(fd, buf, strlen(buf));
close(fd);
/* reopen pfd[1] close on exec */
sprint(buf, "/fd/%d", pfd[1]);
errorfd = open(buf, OREAD|OCEXEC);
if(errorfd < 0)
error("can't re-open acmeerror file");
close(pfd[1]);
close(pfd[0]);
proccreate(acmeerrorproc, nil, STACK);
}
void
plumbproc(void *)
{
Plumbmsg *m;
threadsetname("plumbproc");
for(;;){
m = plumbrecv(plumbeditfd);
if(m == nil)
threadexits(nil);
sendp(cplumb, m);
}
}
void
keyboardthread(void *)
{
Rune r;
Timer *timer;
Text *t;
enum { KTimer, KKey, NKALT };
static Alt alts[NKALT+1];
alts[KTimer].c = nil;
alts[KTimer].v = nil;
alts[KTimer].op = CHANNOP;
alts[KKey].c = keyboardctl->c;
alts[KKey].v = &r;
alts[KKey].op = CHANRCV;
alts[NKALT].op = CHANEND;
timer = nil;
typetext = nil;
threadsetname("keyboardthread");
for(;;){
switch(alt(alts)){
case KTimer:
timerstop(timer);
t = typetext;
if(t!=nil && t->what==Tag){
winlock(t->w, 'K');
wincommit(t->w, t);
winunlock(t->w);
flushimage(display, 1);
}
alts[KTimer].c = nil;
alts[KTimer].op = CHANNOP;
break;
case KKey:
casekeyboard:
typetext = rowtype(&row, r, mouse->xy);
t = typetext;
if(t!=nil && t->col!=nil && !(r==Kdown || r==Kleft || r==Kright)) /* scrolling doesn't change activecol */
activecol = t->col;
if(t!=nil && t->w!=nil)
t->w->body.file->curtext = &t->w->body;
if(timer != nil)
timercancel(timer);
if(t!=nil && t->what==Tag) {
timer = timerstart(500);
alts[KTimer].c = timer->c;
alts[KTimer].op = CHANRCV;
}else{
timer = nil;
alts[KTimer].c = nil;
alts[KTimer].op = CHANNOP;
}
if(nbrecv(keyboardctl->c, &r) > 0)
goto casekeyboard;
flushimage(display, 1);
break;
}
}
}
void
mousethread(void *)
{
Text *t, *argt;
int but;
uint q0, q1;
Window *w;
Plumbmsg *pm;
Mouse m;
char *act;
enum { MResize, MMouse, MPlumb, MWarnings, NMALT };
static Alt alts[NMALT+1];
threadsetname("mousethread");
alts[MResize].c = mousectl->resizec;
alts[MResize].v = nil;
alts[MResize].op = CHANRCV;
alts[MMouse].c = mousectl->c;
alts[MMouse].v = &mousectl->Mouse;
alts[MMouse].op = CHANRCV;
alts[MPlumb].c = cplumb;
alts[MPlumb].v = ±
alts[MPlumb].op = CHANRCV;
alts[MWarnings].c = cwarn;
alts[MWarnings].v = nil;
alts[MWarnings].op = CHANRCV;
if(cplumb == nil)
alts[MPlumb].op = CHANNOP;
alts[NMALT].op = CHANEND;
for(;;){
qlock(&row);
flushwarnings();
qunlock(&row);
flushimage(display, 1);
switch(alt(alts)){
case MResize:
if(getwindow(display, Refnone) < 0)
error("attach to window");
scrlresize();
rowresize(&row, screen->clipr);
break;
case MPlumb:
if(strcmp(pm->type, "text") == 0){
act = plumblookup(pm->attr, "action");
if(act==nil || strcmp(act, "showfile")==0)
plumblook(pm);
else if(strcmp(act, "showdata")==0)
plumbshow(pm);
}
plumbfree(pm);
break;
case MWarnings:
break;
case MMouse:
/*
* Make a copy so decisions are consistent; mousectl changes
* underfoot. Can't just receive into m because this introduces
* another race; see /sys/src/libdraw/mouse.c.
*/
m = mousectl->Mouse;
qlock(&row);
t = rowwhich(&row, m.xy);
if(t!=mousetext && mousetext!=nil && mousetext->w!=nil){
winlock(mousetext->w, 'M');
mousetext->eq0 = ~0;
wincommit(mousetext->w, mousetext);
winunlock(mousetext->w);
}
mousetext = t;
if(t == nil)
goto Continue;
w = t->w;
if(t==nil || m.buttons==0)
goto Continue;
but = 0;
if(m.buttons == 1)
but = 1;
else if(m.buttons == 2)
but = 2;
else if(m.buttons == 4)
but = 3;
barttext = t;
if(t->what==Body && ptinrect(m.xy, t->scrollr)){
if(but){
winlock(w, 'M');
t->eq0 = ~0;
textscroll(t, but);
winunlock(w);
}
goto Continue;
}
/* scroll buttons, wheels, etc. */
if(t->what==Body && w != nil && (m.buttons & (8|16))){
if(m.buttons & 8)
but = Kscrolloneup;
else
but = Kscrollonedown;
winlock(w, 'M');
t->eq0 = ~0;
texttype(t, but);
winunlock(w);
goto Continue;
}
if(ptinrect(m.xy, t->scrollr)){
if(but){
if(t->what == Columntag)
rowdragcol(&row, t->col, but);
else if(t->what == Tag){
coldragwin(t->col, t->w, but);
if(t->w)
barttext = &t->w->body;
}
if(t->col)
activecol = t->col;
}
goto Continue;
}
if(m.buttons){
if(w)
winlock(w, 'M');
t->eq0 = ~0;
if(w)
wincommit(w, t);
else
textcommit(t, TRUE);
if(m.buttons & 1){
textselect(t);
if(w)
winsettag(w);
argtext = t;
seltext = t;
if(t->col)
activecol = t->col; /* button 1 only */
if(t->w!=nil && t==&t->w->body)
activewin = t->w;
}else if(m.buttons & 2){
if(textselect2(t, &q0, &q1, &argt))
execute(t, q0, q1, FALSE, argt);
}else if(m.buttons & 4){
if(textselect3(t, &q0, &q1))
look3(t, q0, q1, FALSE);
}
if(w)
winunlock(w);
goto Continue;
}
Continue:
qunlock(&row);
break;
}
}
}
/*
* There is a race between process exiting and our finding out it was ever created.
* This structure keeps a list of processes that have exited we haven't heard of.
*/
typedef struct Pid Pid;
struct Pid
{
int pid;
char msg[ERRMAX];
Pid *next;
};
void
waitthread(void *)
{
Waitmsg *w;
Command *c, *lc;
uint pid;
int found, ncmd;
Rune *cmd;
char *err;
Text *t;
Pid *pids, *p, *lastp;
enum { WErr, WKill, WWait, WCmd, NWALT };
Alt alts[NWALT+1];
threadsetname("waitthread");
pids = nil;
alts[WErr].c = cerr;
alts[WErr].v = &err;
alts[WErr].op = CHANRCV;
alts[WKill].c = ckill;
alts[WKill].v = &cmd;
alts[WKill].op = CHANRCV;
alts[WWait].c = cwait;
alts[WWait].v = &w;
alts[WWait].op = CHANRCV;
alts[WCmd].c = ccommand;
alts[WCmd].v = &c;
alts[WCmd].op = CHANRCV;
alts[NWALT].op = CHANEND;
command = nil;
for(;;){
switch(alt(alts)){
case WErr:
qlock(&row);
warning(nil, "%s", err);
free(err);
flushimage(display, 1);
qunlock(&row);
break;
case WKill:
found = FALSE;
ncmd = runestrlen(cmd);
for(c=command; c; c=c->next){
/* -1 for blank */
if(runeeq(c->name, c->nname-1, cmd, ncmd) == TRUE){
if(postnote(PNGROUP, c->pid, "kill") < 0)
warning(nil, "kill %S: %r\n", cmd);
found = TRUE;
}
}
if(!found)
warning(nil, "Kill: no process %S\n", cmd);
free(cmd);
break;
case WWait:
pid = w->pid;
lc = nil;
for(c=command; c; c=c->next){
if(c->pid == pid){
if(lc)
lc->next = c->next;
else
command = c->next;
break;
}
lc = c;
}
qlock(&row);
t = &row.tag;
textcommit(t, TRUE);
if(c == nil){
/* helper processes use this exit status */
if(strncmp(w->msg, "libthread", 9) != 0){
p = emalloc(sizeof(Pid));
p->pid = pid;
strncpy(p->msg, w->msg, sizeof(p->msg));
p->next = pids;
pids = p;
}
}else{
if(search(t, c->name, c->nname)){
textdelete(t, t->q0, t->q1, TRUE);
textsetselect(t, 0, 0);
}
if(w->msg[0])
warning(c->md, "%s\n", w->msg);
flushimage(display, 1);
}
qunlock(&row);
free(w);
Freecmd:
if(c){
if(c->iseditcmd)
sendul(cedit, 0);
free(c->text);
free(c->name);
fsysdelid(c->md);
free(c);
}
break;
case WCmd:
/* has this command already exited? */
lastp = nil;
for(p=pids; p!=nil; p=p->next){
if(p->pid == c->pid){
if(p->msg[0])
warning(c->md, "%s\n", p->msg);
if(lastp == nil)
pids = p->next;
else
lastp->next = p->next;
free(p);
goto Freecmd;
}
lastp = p;
}
c->next = command;
command = c;
qlock(&row);
t = &row.tag;
textcommit(t, TRUE);
textinsert(t, 0, c->name, c->nname, TRUE);
textsetselect(t, 0, 0);
flushimage(display, 1);
qunlock(&row);
break;
}
}
}
void
xfidallocthread(void*)
{
Xfid *xfree, *x;
enum { Alloc, Free, N };
static Alt alts[N+1];
threadsetname("xfidallocthread");
alts[Alloc].c = cxfidalloc;
alts[Alloc].v = nil;
alts[Alloc].op = CHANRCV;
alts[Free].c = cxfidfree;
alts[Free].v = &x;
alts[Free].op = CHANRCV;
alts[N].op = CHANEND;
xfree = nil;
for(;;){
switch(alt(alts)){
case Alloc:
x = xfree;
if(x)
xfree = x->next;
else{
x = emalloc(sizeof(Xfid));
x->c = chancreate(sizeof(void(*)(Xfid*)), 0);
x->arg = x;
threadcreate(xfidctl, x->arg, STACK);
}
sendp(cxfidalloc, x);
break;
case Free:
x->next = xfree;
xfree = x;
break;
}
}
}
/* this thread, in the main proc, allows fsysproc to get a window made without doing graphics */
void
newwindowthread(void*)
{
Window *w;
threadsetname("newwindowthread");
for(;;){
/* only fsysproc is talking to us, so synchronization is trivial */
recvp(cnewwindow);
w = makenewwindow(nil);
winsettag(w);
sendp(cnewwindow, w);
}
}
Reffont*
rfget(int fix, int save, int setfont, char *name)
{
Reffont *r;
Font *f;
int i;
r = nil;
if(name == nil){
name = fontnames[fix];
r = reffonts[fix];
}
if(r == nil){
for(i=0; i<nfontcache; i++)
if(strcmp(name, fontcache[i]->f->name) == 0){
r = fontcache[i];
goto Found;
}
f = openfont(display, name);
if(f == nil){
warning(nil, "can't open font file %s: %r\n", name);
return nil;
}
r = emalloc(sizeof(Reffont));
r->f = f;
fontcache = realloc(fontcache, (nfontcache+1)*sizeof(Reffont*));
fontcache[nfontcache++] = r;
}
Found:
if(save){
incref(r);
if(reffonts[fix])
rfclose(reffonts[fix]);
reffonts[fix] = r;
fontnames[fix] = name;
}
if(setfont){
reffont.f = r->f;
incref(r);
rfclose(reffonts[0]);
font = r->f;
reffonts[0] = r;
incref(r);
iconinit();
}
incref(r);
return r;
}
void
rfclose(Reffont *r)
{
int i;
if(decref(r) == 0){
for(i=0; i<nfontcache; i++)
if(r == fontcache[i])
break;
if(i >= nfontcache)
warning(nil, "internal error: can't find font in cache\n");
else{
nfontcache--;
memmove(fontcache+i, fontcache+i+1, (nfontcache-i)*sizeof(Reffont*));
}
freefont(r->f);
free(r);
}
}
Cursor boxcursor = {
{-7, -7},
{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F,
0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
{0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE,
0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0x00}
};
void
iconinit(void)
{
Rectangle r;
Image *tmp;
/* Blue */
tagcols[BACK] = allocimagemix(display, DPalebluegreen, DWhite);
tagcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPalegreygreen);
tagcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPurpleblue);
tagcols[TEXT] = display->black;
tagcols[HTEXT] = display->black;
/* Yellow */
textcols[BACK] = allocimagemix(display, DPaleyellow, DWhite);
textcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DDarkyellow);
textcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DYellowgreen);
textcols[TEXT] = display->black;
textcols[HTEXT] = display->black;
if(button){
freeimage(button);
freeimage(modbutton);
freeimage(colbutton);
}
r = Rect(0, 0, Scrollwid+2, font->height+1);
button = allocimage(display, r, screen->chan, 0, DNofill);
draw(button, r, tagcols[BACK], nil, r.min);
r.max.x -= 2;
border(button, r, 2, tagcols[BORD], ZP);
r = button->r;
modbutton = allocimage(display, r, screen->chan, 0, DNofill);
draw(modbutton, r, tagcols[BACK], nil, r.min);
r.max.x -= 2;
border(modbutton, r, 2, tagcols[BORD], ZP);
r = insetrect(r, 2);
tmp = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DMedblue);
draw(modbutton, r, tmp, nil, ZP);
freeimage(tmp);
r = button->r;
colbutton = allocimage(display, r, screen->chan, 0, DPurpleblue);
but2col = allocimage(display, r, screen->chan, 1, 0xAA0000FF);
but3col = allocimage(display, r, screen->chan, 1, 0x006600FF);
}
/*
* /dev/snarf updates when the file is closed, so we must open our own
* fd here rather than use snarffd
*/
/* rio truncates larges snarf buffers, so this avoids using the
* service if the string is huge */
#define MAXSNARF 100*1024
void
putsnarf(void)
{
int fd, i, n;
if(snarffd<0 || snarfbuf.nc==0)
return;
if(snarfbuf.nc > MAXSNARF)
return;
fd = open("/dev/snarf", OWRITE);
if(fd < 0)
return;
for(i=0; i<snarfbuf.nc; i+=n){
n = snarfbuf.nc-i;
if(n >= NSnarf)
n = NSnarf;
bufread(&snarfbuf, i, snarfrune, n);
if(fprint(fd, "%.*S", n, snarfrune) < 0)
break;
}
close(fd);
}
void
getsnarf()
{
int nulls;
if(snarfbuf.nc > MAXSNARF)
return;
if(snarffd < 0)
return;
seek(snarffd, 0, 0);
bufreset(&snarfbuf);
bufload(&snarfbuf, 0, snarffd, &nulls);
}
|