ref: f75e1bb49e5b3a979d716b05b1d815f8b5a9590c
dir: /main.c/
#include "inc.h"
typedef struct RKeyboardctl RKeyboardctl;
struct RKeyboardctl
{
Keyboardctl;
int kbdfd;
};
RKeyboardctl *kbctl;
Mousectl *mctl;
int scrolling = 1;
char *startdir;
int shiftdown;
int gotscreen;
int servekbd;
Screen *wscreen;
void
killprocs(void)
{
int i;
for(i = 0; i < nwindows; i++)
if(windows[i]->notefd >= 0)
write(windows[i]->notefd, "hangup", 6);
}
/*
* /dev/snarf updates when the file is closed, so we must open our own
* fd here rather than use snarffd
*/
void
putsnarf(void)
{
int fd, i, n;
if(snarffd<0 || nsnarf==0)
return;
fd = open("/dev/snarf", OWRITE|OCEXEC);
if(fd < 0)
return;
/* snarf buffer could be huge, so fprint will truncate; do it in blocks */
for(i=0; i<nsnarf; i+=n){
n = nsnarf-i;
if(n >= 256)
n = 256;
if(fprint(fd, "%.*S", n, snarf+i) < 0)
break;
}
close(fd);
}
void
setsnarf(char *s, int ns)
{
free(snarf);
snarf = runesmprint("%.*s", ns, s);
nsnarf = runestrlen(snarf);
snarfversion++;
}
void
getsnarf(void)
{
int i, n;
char *s, *sn;
if(snarffd < 0)
return;
sn = nil;
i = 0;
seek(snarffd, 0, 0);
for(;;){
if(i > MAXSNARF)
break;
if((s = realloc(sn, i+1024+1)) == nil)
break;
sn = s;
if((n = read(snarffd, sn+i, 1024)) <= 0)
break;
i += n;
}
if(i == 0)
return;
sn[i] = 0;
setsnarf(sn, i);
free(sn);
}
static int overridecursor;
static Cursor *ovcursor;
static Cursor *normalcursor;
Cursor *cursor;
void
setmousecursor(Cursor *c)
{
if(cursor == c)
return;
cursor = c;
setcursor(mctl, c);
}
void
setcursoroverride(Cursor *c, int ov)
{
overridecursor = ov;
ovcursor = c;
setmousecursor(overridecursor ? ovcursor : normalcursor);
}
void
setcursornormal(Cursor *c)
{
normalcursor = c;
setmousecursor(overridecursor ? ovcursor : normalcursor);
}
char *rcargv[] = { "rc", "-i", nil };
Window*
new(Rectangle r)
{
Window *w;
w = wcreate(r, FALSE, scrolling);
assert(w);
if(wincmd(w, 0, nil, rcargv) == 0)
return nil;
return w;
}
void
drainmouse(Mousectl *mc, Channel *c)
{
if(c) send(c, &mc->Mouse);
while(mc->buttons){
readmouse(mc);
if(c) send(c, &mc->Mouse);
}
}
Window*
clickwindow(int but, Mousectl *mc)
{
Window *w;
but = 1<<(but-1);
setcursoroverride(&sightcursor, TRUE);
drainmouse(mc, nil);
while(!(mc->buttons & but)){
readmouse(mc);
if(mc->buttons & (7^but)){
setcursoroverride(nil, FALSE);
drainmouse(mc, nil);
return nil;
}
}
w = wpointto(mc->xy);
return w;
}
Rectangle
dragrect(int but, Rectangle r, Mousectl *mc)
{
Rectangle rc;
Point start, end;
but = 1<<(but-1);
setcursoroverride(&boxcursor, TRUE);
start = mc->xy;
end = mc->xy;
do{
rc = rectaddpt(r, subpt(end, start));
drawgetrect(rc, 1);
readmouse(mc);
drawgetrect(rc, 0);
end = mc->xy;
}while(mc->buttons == but);
setcursoroverride(nil, FALSE);
if(mc->buttons & (7^but)){
rc.min.x = rc.max.x = 0;
rc.min.y = rc.max.y = 0;
drainmouse(mc, nil);
}
return rc;
}
Rectangle
sweeprect(int but, Mousectl *mc)
{
Rectangle r, rc;
but = 1<<(but-1);
setcursoroverride(&crosscursor, TRUE);
drainmouse(mc, nil);
while(!(mc->buttons & but)){
readmouse(mc);
if(mc->buttons & (7^but))
goto Return;
}
r.min = mc->xy;
r.max = mc->xy;
do{
rc = canonrect(r);
drawgetrect(rc, 1);
readmouse(mc);
drawgetrect(rc, 0);
r.max = mc->xy;
}while(mc->buttons == but);
Return:
setcursoroverride(nil, FALSE);
if(mc->buttons & (7^but)){
rc.min.x = rc.max.x = 0;
rc.min.y = rc.max.y = 0;
drainmouse(mc, nil);
}
return rc;
}
int
whichside(int x, int lo, int hi)
{
return x < lo+20 ? 0 :
x > hi-20 ? 2 :
1;
}
/* 0 1 2
* 3 5
* 6 7 8 */
int
whichcorner(Rectangle r, Point p)
{
int i, j;
i = whichside(p.x, r.min.x, r.max.x);
j = whichside(p.y, r.min.y, r.max.y);
return 3*j+i;
}
/* replace corner or edge of rect with point */
Rectangle
changerect(Rectangle r, int corner, Point p)
{
switch(corner){
case 0: return Rect(p.x, p.y, r.max.x, r.max.y);
case 1: return Rect(r.min.x, p.y, r.max.x, r.max.y);
case 2: return Rect(r.min.x, p.y, p.x+1, r.max.y);
case 3: return Rect(p.x, r.min.y, r.max.x, r.max.y);
case 5: return Rect(r.min.x, r.min.y, p.x+1, r.max.y);
case 6: return Rect(p.x, r.min.y, r.max.x, p.y+1);
case 7: return Rect(r.min.x, r.min.y, r.max.x, p.y+1);
case 8: return Rect(r.min.x, r.min.y, p.x+1, p.y+1);
}
return r;
}
Rectangle
bandrect(Rectangle r, int but, Mousectl *mc)
{
Rectangle or, nr;
int corner, ncorner;
or = r;
corner = whichcorner(r, mc->xy);
setcursornormal(corners[corner]);
do{
drawgetrect(r, 1);
readmouse(mc);
drawgetrect(r, 0);
nr = canonrect(changerect(r, corner, mc->xy));
if(goodrect(nr))
r = nr;
ncorner = whichcorner(r, mc->xy);
/* can switch from edge to corner, but not vice versa */
if(ncorner != corner && ncorner != 4 && (corner|~ncorner) & 1){
corner = ncorner;
setcursornormal(corners[corner]);
}
}while(mc->buttons == but);
if(mc->buttons){
drainmouse(mctl, nil);
return or;
}
setcursornormal(nil);
return r;
}
Window*
pick(void)
{
Window *w1, *w2;
w1 = clickwindow(3, mctl);
drainmouse(mctl, nil);
setcursoroverride(nil, FALSE);
w2 = wpointto(mctl->xy);
if(w1 != w2)
return nil;
return w1;
}
void
grab(Window *w)
{
if(w == nil)
w = clickwindow(3, mctl);
if(w == nil)
setcursoroverride(nil, FALSE);
else{
Rectangle r = dragrect(3, w->img->r, mctl);
if(Dx(r) > 0 || Dy(r) > 0){
wmove(w, r.min);
wfocus(w);
flushimage(display, 1);
}
}
}
void
sweep(Window *w)
{
Rectangle r = sweeprect(3, mctl);
if(goodrect(r)){
if(w){
wresize(w, r);
wraise(w);
}else{
w = new(r);
}
wfocus(w);
flushimage(display, 1);
}
}
void
bandresize(Window *w)
{
Rectangle r;
r = bandrect(w->img->r, mctl->buttons, mctl);
if(!eqrect(r, w->img->r)){
wresize(w, r);
flushimage(display, 1);
}
}
int
obscured(Window *w, Rectangle r, Window *t)
{
if(Dx(r) < font->height || Dy(r) < font->height)
return 1;
if(!rectclip(&r, screen->r))
return 1;
for(; t; t = t->higher){
if(t->hidden || Dx(t->img->r) == 0 || Dy(t->img->r) == 0 || rectXrect(r, t->img->r) == 0)
continue;
if(r.min.y < t->img->r.min.y)
if(!obscured(w, Rect(r.min.x, r.min.y, r.max.x, t->img->r.min.y), t))
return 0;
if(r.min.x < t->img->r.min.x)
if(!obscured(w, Rect(r.min.x, r.min.y, t->img->r.min.x, r.max.y), t))
return 0;
if(r.max.y > t->img->r.max.y)
if(!obscured(w, Rect(r.min.x, t->img->r.max.y, r.max.x, r.max.y), t))
return 0;
if(r.max.x > t->img->r.max.x)
if(!obscured(w, Rect(t->img->r.max.x, r.min.y, r.max.x, r.max.y), t))
return 0;
return 1;
}
return 0;
}
/* Check that newly created window will be of manageable size */
int
goodrect(Rectangle r)
{
if(badrect(r) || !eqrect(canonrect(r), r))
return 0;
/* reasonable sizes only please */
if(Dx(r) > BIG*Dx(screen->r))
return 0;
if(Dy(r) > BIG*Dy(screen->r))
return 0;
/*
* the height has to be big enough to fit one line of text.
* that includes the border on each side with an extra pixel
* so that the text is still drawn
*/
if(Dx(r) < 100 || Dy(r) < 2*(Borderwidth+1)+font->height)
return 0;
/* window must be on screen */
if(!rectXrect(screen->r, r))
return 0;
/* must have some screen and border visible so we can move it out of the way */
if(rectinrect(screen->r, insetrect(r, Borderwidth)))
return 0;
return 1;
}
/* Rectangle for new window */
Rectangle
newrect(void)
{
static int i = 0;
int minx, miny, dx, dy;
dx = min(600, Dx(screen->r) - 2*Borderwidth);
dy = min(400, Dy(screen->r) - 2*Borderwidth);
minx = 32 + 16*i;
miny = 32 + 16*i;
i++;
i %= 10;
return Rect(minx, miny, minx+dx, miny+dy);
}
enum {
Cut,
Paste,
Snarf,
Plumb,
Look,
Send,
Scroll
};
char *menu2str[] = {
"cut",
"paste",
"snarf",
"plumb",
"look",
"send",
"scroll",
nil
};
Menu menu2 = { menu2str };
enum {
New,
Reshape,
Move,
Delete,
Hide,
Exit
};
int Hidden = Exit+1;
char *menu3str[7 + nelem(hidden)] = {
"New",
"Resize",
"Move",
"Delete",
"Hide",
"Exit",
nil
};
Menu menu3 = { menu3str };
void
btn2menu(Window *w)
{
int sel;
Text *x;
Cursor *c;
x = &w->text;
menu2str[Scroll] = w->scrolling ? "noscroll" : "scroll";
sel = menuhit(2, mctl, &menu2, wscreen);
switch(sel){
case Cut:
xsnarf(x);
xcut(x);
xscrdraw(x); // TODO let cut handle this?
break;
case Paste:
xpaste(x);
break;
case Snarf:
xsnarf(x);
xscrdraw(x); // TODO let snarf handle this?
break;
case Plumb:
if(xplumb(x, w->dir, fsys.msize-1024)){
c = cursor;
setcursoroverride(&query, TRUE);
sleep(300);
setcursoroverride(c, FALSE);
}
break;
case Look:
xlook(x);
break;
case Send:
xsend(x);
break;
case Scroll:
w->scrolling = !w->scrolling;
if(w->scrolling)
xshow(x, x->nr);
break;
}
wsendmsg(w, Wakeup);
}
void
btn3menu(void)
{
Window *w, *t;
int i, sel;
nhidden = 0;
for(i = 0; i < nwindows; i++){
t = windows[i];
if(t->hidden || obscured(t, t->img->r, t->higher)){
hidden[nhidden] = windows[i];
menu3str[nhidden+Hidden] = windows[i]->label;
nhidden++;
}
}
menu3str[nhidden+Hidden] = nil;
sel = menuhit(3, mctl, &menu3, wscreen);
switch(sel){
case New:
sweep(nil);
break;
case Reshape:
w = pick();
if(w) sweep(w);
break;
case Move:
grab(nil);
break;
case Delete:
w = pick();
if(w) wdelete(w);
break;
case Hide:
w = pick();
if(w) whide(w);
break;
case Exit:
killprocs();
threadexitsall(nil);
default:
if(sel >= Hidden){
w = hidden[sel-Hidden];
if(w->hidden)
wunhide(w);
else{
wraise(w);
wfocus(w);
}
}
break;
}
}
void
mthread(void*)
{
Window *w;
threadsetname("mousethread");
while(readmouse(mctl) != -1){
w = wpointto(mctl->xy);
cursorwin = w;
again:
if(w == nil){
setcursornormal(nil);
if(mctl->buttons & 4)
btn3menu();
}else if(!ptinrect(mctl->xy, w->contrect)){
/* border */
setcursornormal(corners[whichcorner(w->img->r, mctl->xy)]);
if(mctl->buttons & 7){
wraise(w);
wfocus(w);
if(mctl->buttons & 4)
grab(w);
if(mctl->buttons & 3)
bandresize(w);
}
}else if(w != focused){
wsetcursor(w);
if(mctl->buttons & 7 ||
mctl->buttons & (8|16) && focused->mouseopen){
wraise(w);
wfocus(w);
if(mctl->buttons & 1)
drainmouse(mctl, nil);
else
goto again;
}
}else if(!w->mouseopen){
wsetcursor(w);
if(mctl->buttons && topwin != w)
wraise(w);
if(mctl->buttons & (1|8|16) || ptinrect(mctl->xy, w->text.scrollr))
drainmouse(mctl, w->mc.c);
if(mctl->buttons & 2){
incref(w);
btn2menu(w);
wrelease(w);
}
if(mctl->buttons & 4)
btn3menu();
}else{
wsetcursor(w);
drainmouse(mctl, w->mc.c);
}
}
}
void
resthread(void*)
{
Window *w;
Rectangle or, nr;
Point delta;
threadsetname("resizethread");
for(;;){
recvul(mctl->resizec);
or = screen->clipr;
if(getwindow(display, Refnone) < 0)
sysfatal("resize failed: %r");
nr = screen->clipr;
freescrtemps();
freescreen(wscreen);
wscreen = allocscreen(screen, background, 0);
draw(screen, screen->r, background, nil, ZP);
delta = subpt(nr.min, or.min);
for(w = bottomwin; w; w = w->higher){
Rectangle r = w->img->r;
freeimage(w->img);
w->img = nil;
wresize(w, rectaddpt(r, delta));
if(w->hidden)
originwindow(w->img, w->img->r.min, screen->r.max);
}
flushimage(display, 1);
}
}
static void
_ioproc(void *arg)
{
int m, n, nerr;
char buf[1024], *e, *p;
Rune r;
RKeyboardctl *kc;
kc = arg;
threadsetname("kbdproc");
n = 0;
nerr = 0;
if(kc->kbdfd >= 0){
while(kc->kbdfd >= 0){
m = read(kc->kbdfd, buf, sizeof(buf)-1);
if(m <= 0){
yield(); /* if error is due to exiting, we'll exit here */
if(kc->kbdfd < 0)
break;
fprint(2, "keyboard: short read: %r\n");
if(m<0 || ++nerr>10)
threadexits("read error");
continue;
}
/* one read can return multiple messages, delimited by NUL
* split them up for sending on the channel */
e = buf+m;
e[-1] = 0;
e[0] = 0;
for(p = buf; p < e; p += strlen(p)+1)
chanprint(kc->c, "%s", p);
}
}else{
while(kc->consfd >= 0){
m = read(kc->consfd, buf+n, sizeof buf-n);
if(m <= 0){
yield(); /* if error is due to exiting, we'll exit here */
if(kc->consfd < 0)
break;
fprint(2, "keyboard: short read: %r\n");
if(m<0 || ++nerr>10)
threadexits("read error");
continue;
}
nerr = 0;
n += m;
while(n>0 && fullrune(buf, n)){
m = chartorune(&r, buf);
n -= m;
memmove(buf, buf+m, n);
if(chanprint(kc->c, "c%C", r) < 0)
break;
}
}
}
chanfree(kc->c);
free(kc->file);
free(kc);
}
RKeyboardctl*
initkbd(char *file, char *kbdfile)
{
RKeyboardctl *kc;
char *t;
if(file == nil)
file = "/dev/cons";
if(kbdfile == nil)
kbdfile = "/dev/kbd";
kc = mallocz(sizeof(RKeyboardctl), 1);
if(kc == nil)
return nil;
kc->file = strdup(file);
// TODO: handle file == nil
kc->consfd = open(file, ORDWR|OCEXEC);
t = malloc(strlen(file)+16);
if(kc->consfd<0 || t==nil)
goto Error1;
sprint(t, "%sctl", file);
kc->ctlfd = open(t, OWRITE|OCEXEC);
if(kc->ctlfd < 0){
fprint(2, "initkeyboard: can't open %s: %r\n", t);
goto Error2;
}
if(ctlkeyboard(kc, "rawon") < 0){
fprint(2, "initkeyboard: can't turn on raw mode on %s: %r\n", t);
close(kc->ctlfd);
goto Error2;
}
free(t);
kc->kbdfd = open(kbdfile, OREAD|OCEXEC);
kc->c = chancreate(sizeof(char*), 20);
kc->pid = proccreate(_ioproc, kc, 4096);
return kc;
Error2:
close(kc->consfd);
Error1:
free(t);
free(kc->file);
free(kc);
return nil;
}
/*
* kbd -----+-------> to tap
* \
* \
* from tap --------+----> window
*/
Channel *ctltap; /* open/close */
Channel *resptap; /* open/close err */
Channel *fromtap; /* input from kbd tap program to window */
Channel *totap; /* our keyboard input to tap program */
Channel *wintap; /* tell the tapthread which Window to send to */
static int tapseats[] = { [OREAD] Tapoff, [OWRITE] Tapoff };
char*
tapctlmsg(char *msg)
{
int perm;
perm = msg[1];
switch(msg[0]){
case Tapoff:
if(perm == ORDWR)
tapseats[OREAD] = Tapoff, tapseats[OWRITE] = Tapoff;
else
tapseats[perm] = Tapoff;
break;
case Tapon:
switch(perm){
case ORDWR:
if(tapseats[OREAD] != Tapoff || tapseats[OWRITE] != Tapoff)
return "seat taken";
tapseats[OREAD] = Tapon, tapseats[OWRITE] = Tapon;
break;
case OREAD: case OWRITE:
if(tapseats[perm] != Tapoff)
return "seat taken";
tapseats[perm] = Tapon;
break;
}
break;
}
return nil;
}
void
keyboardtap(void*)
{
char *s, *ctl;
char *e;
char *watched;
Channel *fschan;
int n;
Stringpair pair;
Window *w, *cur;
threadsetname("keyboardtap");
fschan = chancreate(sizeof(Stringpair), 0);
enum { Awin, Actl, Afrom, Adev, Ato, Ainp, Awatch, NALT };
Alt alts[NALT+1] = {
[Awin] {.c = wintap, .v = &w, .op = CHANRCV},
[Actl] {.c = ctltap, .v = &ctl, .op = CHANRCV},
[Afrom] {.c = fromtap, .v = &s, .op = CHANRCV},
[Adev] {.c = kbctl->c, .v = &s, .op = CHANRCV},
[Ato] {.c = totap, .v = &fschan, .op = CHANNOP},
[Ainp] {.c = nil, .v = &s, .op = CHANNOP},
[Awatch]{.c = totap, .v = &fschan, .op = CHANNOP},
[NALT] {.op = CHANEND},
};
cur = nil;
watched = nil;
for(;;)
switch(alt(alts)){
case Awin:
cur = w;
if(cur != nil){
alts[Ainp].c = cur->kbd;
if(tapseats[OREAD] == Tapoff)
goto Reset;
if(alts[Awatch].op == CHANSND)
free(watched);
watched = smprint("%c%d", Tapfocus, cur->id);
alts[Awatch].op = CHANSND;
}
if(alts[Ainp].op != CHANNOP || alts[Ato].op != CHANNOP)
free(s);
goto Reset;
case Actl:
e = tapctlmsg(ctl);
sendp(resptap, e);
if(e != nil || *ctl != Tapoff){
free(ctl);
break;
}
free(ctl);
goto Reset;
case Afrom:
if(cur == nil){
free(s);
break;
}
alts[Afrom].op = CHANNOP;
alts[Adev].op = CHANNOP;
alts[Ato].op = CHANNOP;
alts[Ainp].op = CHANSND;
break;
case Adev:
if(tapseats[OWRITE] == Tapoff && cur == nil){
free(s);
break;
}
alts[Afrom].op = CHANNOP;
alts[Adev].op = CHANNOP;
if(tapseats[OWRITE] == Tapoff)
alts[Ainp].op = CHANSND;
else
alts[Ato].op = CHANSND;
break;
/* These two do the xreq channel dance
* ugly... */
case Ato:
recv(fschan, &pair);
n = strlen(s)+1;
pair.ns = min(n, pair.ns);
memmove(pair.s, s, pair.ns);
free(s);
send(fschan, &pair);
goto Reset;
case Awatch:
recv(fschan, &pair);
n = strlen(watched)+1;
pair.ns = min(n, pair.ns);
memmove(pair.s, watched, pair.ns);
free(watched);
send(fschan, &pair);
alts[Awatch].op = CHANNOP;
break;
case Ainp:
if(*s == 'k' || *s == 'K')
shiftdown = utfrune(s+1, Kshift) != nil;
Reset:
alts[Ainp].op = CHANNOP;
alts[Ato].op = CHANNOP;
alts[Afrom].op = CHANRCV;
alts[Adev].op = CHANRCV;
break;
}
}
void
threadmain(int, char *[])
{
char buf[256];
//rfork(RFENVG);
//newwindow("-dx 1280 -dy 800");
if(getwd(buf, sizeof(buf)) == nil)
startdir = estrdup(".");
else
startdir = estrdup(buf);
if(initdraw(nil, nil, "lola") < 0)
sysfatal("initdraw: %r");
kbctl = initkbd(nil, nil);
if(kbctl == nil)
sysfatal("inikeyboard: %r");
mctl = initmouse(nil, screen);
if(mctl == nil)
sysfatal("initmouse: %r");
totap = chancreate(sizeof(Channel**), 0);
fromtap = chancreate(sizeof(char*), 32);
wintap = chancreate(sizeof(Window*), 0);
ctltap = chancreate(sizeof(char*), 0);
resptap = chancreate(sizeof(char*), 0);
servekbd = kbctl->kbdfd >= 0;
snarffd = open("/dev/snarf", OREAD|OCEXEC);
gotscreen = access("/dev/screen", AEXIST)==0;
initdata();
wscreen = allocscreen(screen, background, 0);
draw(screen, screen->r, background, nil, ZP);
timerinit();
threadcreate(mthread, nil, mainstacksize);
threadcreate(resthread, nil, mainstacksize);
/* proc so mouse keeps working if tap program crashes */
proccreate(keyboardtap, nil, mainstacksize);
flushimage(display, 1);
fs();
// not reached
}