shithub: lola

ref: fb7cc13fe8c7e198e6130f973448b13ac8614470
dir: /main.c/

View raw version
#include "inc.h"

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, Window *w)
{
	if(w) send(w->mc.c, &mc->Mouse);
	while(mc->buttons){
		readmouse(mc);
		/* stop sending once focus changes.
		 * buttons released in wfocus() */
		if(w != focused) w = nil;
		if(w) send(w->mc.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);
			wfocus(w);
		}else{
			new(r);
		}
		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 + MAXWINDOWS] = {
	"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);
		break;
	case Paste:
		xpaste(x);
		break;
	case Snarf:
		xsnarf(x);
		break;
	case Plumb:
		if(xplumb(x, "lola", 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)
{
	static Window *hidden[MAXWINDOWS];
	int nhidden;
	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);
			if(mctl->buttons & 2){
				incref(w);
				btn2menu(w);
				wrelease(w);
			}
			if(mctl->buttons & 4)
				btn3menu();
		}else{
			wsetcursor(w);
			drainmouse(mctl, w);
		}
	}
}

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)
			wresize(w, rectaddpt(w->img->r, delta));

		flushimage(display, 1);
	}
}

/*
 *    kbd    -----+-------> to tap
 *                 \
 *                  \
 * from tap  --------+----> window
 */

Channel *opentap;	/* open fromtap or totap */
Channel *closetap;	/* close fromtap or totap */
Channel *fromtap;	/* input from kbd tap program to window */
Channel *totap;		/* our keyboard input to tap program */

void
keyboardtap(void*)
{
	char *s, *z;
	Channel *fschan, *chan;
	int n;
	Stringpair pair;
	Window *cur, *prev;
	Queue tapq;

	threadsetname("keyboardtap");

	fschan = chancreate(sizeof(Stringpair), 0);
	enum { Akbd, Afromtap, Atotap, Aopen, Aclose,  NALT };
	Alt alts[NALT+1] = {
		[Akbd]		{.c = kbctl->c, .v = &s, .op = CHANRCV},
		[Afromtap]	{.c = nil, .v = &s, .op = CHANNOP},
		[Atotap]	{.c = nil, .v = &fschan, .op = CHANNOP},
		[Aopen]		{.c = opentap, .v = &chan, .op = CHANRCV},
		[Aclose]	{.c = closetap, .v = &chan, .op = CHANRCV},
		[NALT]		{.op = CHANEND},
	};

	memset(&tapq, 0, sizeof(tapq));
	cur = nil;
	for(;;){
		if(alts[Atotap].c && !qempty(&tapq))
			alts[Atotap].op = CHANSND;
		else
			alts[Atotap].op = CHANNOP;
		switch(alt(alts)){
		case Akbd:
			/* from keyboard to tap or to window */
			if(*s == 'k' || *s == 'K')
				shiftdown = utfrune(s+1, Kshift) != nil;
			prev = cur;
			cur = focused;
			if(totap){
				if(cur != prev && cur){
					/* notify tap of focus change */
					z = smprint("z%d", cur->id);
					if(!qadd(&tapq, z))
						free(z);
				}
				/* send to tap */
				if(qadd(&tapq, s))
					break;
				/* tap is wedged, send directly instead */
			}
			if(cur)
				sendp(cur->kbd, s);
			else
				free(s);
			break;

		case Afromtap:
			/* from tap to window */
			if(cur && cur == focused)
				sendp(cur->kbd, s);
			else
				free(s);
			break;

		case Atotap:
			/* send queued up messages */
			recv(fschan, &pair);
			s = qget(&tapq);
			n = strlen(s)+1;
			pair.ns = min(n, pair.ns);
			memmove(pair.s, s, pair.ns);
			free(s);
			send(fschan, &pair);
			break;

		case Aopen:
			if(chan == fromtap){
				alts[Afromtap].c = fromtap;
				alts[Afromtap].op = CHANRCV;
			}
			if(chan == totap)
				alts[Atotap].c = totap;
			break;

		case Aclose:
			if(chan == fromtap){
				fromtap = nil;
				alts[Afromtap].c = nil;
				alts[Afromtap].op = CHANNOP;
				// TODO: empty chan
			}
			if(chan == totap){
				totap = nil;
				alts[Atotap].c = nil;
				alts[Atotap].op = CHANNOP;
				while(!qempty(&tapq))
					free(qget(&tapq));
			}
			chanfree(chan);
			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");
	opentap = chancreate(sizeof(Channel*), 0);
	closetap = chancreate(sizeof(Channel*), 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);
	threadcreate(keyboardtap, nil, mainstacksize);

	flushimage(display, 1);

	fs();
	// not reached
}