ref: bb604c5cdaa9a6c85d06cb9555e3e428d57b4315
author: demyxology <spicycoldnoodles@gmail.com>
date: Thu Jan 2 17:26:40 EST 2025
"just
--- /dev/null
+++ b/data.c
@@ -1,0 +1,248 @@
+#include "inc.h"
+
+Image *background;
+Image *colors[NumColors];
+
+Cursor whitearrow = {
+ {0, 0},
+ {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC,
+ 0xFF, 0xF0, 0xFF, 0xF0, 0xFF, 0xF8, 0xFF, 0xFC,
+ 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC,
+ 0xF3, 0xF8, 0xF1, 0xF0, 0xE0, 0xE0, 0xC0, 0x40, },
+ {0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x06, 0xC0, 0x1C,
+ 0xC0, 0x30, 0xC0, 0x30, 0xC0, 0x38, 0xC0, 0x1C,
+ 0xC0, 0x0E, 0xC0, 0x07, 0xCE, 0x0E, 0xDF, 0x1C,
+ 0xD3, 0xB8, 0xF1, 0xF0, 0xE0, 0xE0, 0xC0, 0x40, }
+};
+
+Cursor query = {
+ {-7,-7},
+ {0x0f, 0xf0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe,
+ 0x7c, 0x7e, 0x78, 0x7e, 0x00, 0xfc, 0x01, 0xf8,
+ 0x03, 0xf0, 0x07, 0xe0, 0x07, 0xc0, 0x07, 0xc0,
+ 0x07, 0xc0, 0x07, 0xc0, 0x07, 0xc0, 0x07, 0xc0, },
+ {0x00, 0x00, 0x0f, 0xf0, 0x1f, 0xf8, 0x3c, 0x3c,
+ 0x38, 0x1c, 0x00, 0x3c, 0x00, 0x78, 0x00, 0xf0,
+ 0x01, 0xe0, 0x03, 0xc0, 0x03, 0x80, 0x03, 0x80,
+ 0x00, 0x00, 0x03, 0x80, 0x03, 0x80, 0x00, 0x00, }
+};
+
+Cursor crosscursor = {
+ {-7, -7},
+ {0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0,
+ 0x03, 0xC0, 0x03, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0xC0, 0x03, 0xC0,
+ 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0, },
+ {0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80,
+ 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x7F, 0xFE,
+ 0x7F, 0xFE, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80,
+ 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x00, 0x00, }
+};
+
+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, }
+};
+
+Cursor sightcursor = {
+ {-7, -7},
+ {0x1F, 0xF8, 0x3F, 0xFC, 0x7F, 0xFE, 0xFB, 0xDF,
+ 0xF3, 0xCF, 0xE3, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xC7, 0xF3, 0xCF,
+ 0x7B, 0xDF, 0x7F, 0xFE, 0x3F, 0xFC, 0x1F, 0xF8, },
+ {0x00, 0x00, 0x0F, 0xF0, 0x31, 0x8C, 0x21, 0x84,
+ 0x41, 0x82, 0x41, 0x82, 0x41, 0x82, 0x7F, 0xFE,
+ 0x7F, 0xFE, 0x41, 0x82, 0x41, 0x82, 0x41, 0x82,
+ 0x21, 0x84, 0x31, 0x8C, 0x0F, 0xF0, 0x00, 0x00, }
+};
+
+Cursor tl = {
+ {-4, -4},
+ {0xfe, 0x00, 0x82, 0x00, 0x8c, 0x00, 0x87, 0xff,
+ 0xa0, 0x01, 0xb0, 0x01, 0xd0, 0x01, 0x11, 0xff,
+ 0x11, 0x00, 0x11, 0x00, 0x11, 0x00, 0x11, 0x00,
+ 0x11, 0x00, 0x11, 0x00, 0x11, 0x00, 0x1f, 0x00, },
+ {0x00, 0x00, 0x7c, 0x00, 0x70, 0x00, 0x78, 0x00,
+ 0x5f, 0xfe, 0x4f, 0xfe, 0x0f, 0xfe, 0x0e, 0x00,
+ 0x0e, 0x00, 0x0e, 0x00, 0x0e, 0x00, 0x0e, 0x00,
+ 0x0e, 0x00, 0x0e, 0x00, 0x0e, 0x00, 0x00, 0x00, }
+};
+
+Cursor t = {
+ {-7, -8},
+ {0x00, 0x00, 0x00, 0x00, 0x03, 0x80, 0x06, 0xc0,
+ 0x1c, 0x70, 0x10, 0x10, 0x0c, 0x60, 0xfc, 0x7f,
+ 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
+ 0x03, 0x80, 0x0f, 0xe0, 0x03, 0x80, 0x03, 0x80,
+ 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }
+};
+
+Cursor tr = {
+ {-11, -4},
+ {0x00, 0x7f, 0x00, 0x41, 0x00, 0x31, 0xff, 0xe1,
+ 0x80, 0x05, 0x80, 0x0d, 0x80, 0x0b, 0xff, 0x88,
+ 0x00, 0x88, 0x0, 0x88, 0x00, 0x88, 0x00, 0x88,
+ 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0xf8, },
+ {0x00, 0x00, 0x00, 0x3e, 0x00, 0x0e, 0x00, 0x1e,
+ 0x7f, 0xfa, 0x7f, 0xf2, 0x7f, 0xf0, 0x00, 0x70,
+ 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70,
+ 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x00, }
+};
+
+Cursor r = {
+ {-8, -7},
+ {0x07, 0xc0, 0x04, 0x40, 0x04, 0x40, 0x04, 0x58,
+ 0x04, 0x68, 0x04, 0x6c, 0x04, 0x06, 0x04, 0x02,
+ 0x04, 0x06, 0x04, 0x6c, 0x04, 0x68, 0x04, 0x58,
+ 0x04, 0x40, 0x04, 0x40, 0x04, 0x40, 0x07, 0xc0, },
+ {0x00, 0x00, 0x03, 0x80, 0x03, 0x80, 0x03, 0x80,
+ 0x03, 0x90, 0x03, 0x90, 0x03, 0xf8, 0x03, 0xfc,
+ 0x03, 0xf8, 0x03, 0x90, 0x03, 0x90, 0x03, 0x80,
+ 0x03, 0x80, 0x03, 0x80, 0x03, 0x80, 0x00, 0x00, }
+};
+
+Cursor br = {
+ {-11, -11},
+ {0x00, 0xf8, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88,
+ 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88,
+ 0xff, 0x88, 0x80, 0x0b, 0x80, 0x0d, 0x80, 0x05,
+ 0xff, 0xe1, 0x00, 0x31, 0x00, 0x41, 0x00, 0x7f, },
+ {0x00, 0x00, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70,
+ 0x0, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70,
+ 0x00, 0x70, 0x7f, 0xf0, 0x7f, 0xf2, 0x7f, 0xfa,
+ 0x00, 0x1e, 0x00, 0x0e, 0x00, 0x3e, 0x00, 0x00, }
+};
+
+Cursor b = {
+ {-7, -7},
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xff, 0xff, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01,
+ 0xfc, 0x7f, 0x0c, 0x60, 0x10, 0x10, 0x1c, 0x70,
+ 0x06, 0xc0, 0x03, 0x80, 0x00, 0x00, 0x00, 0x00, },
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe,
+ 0x03, 0x80, 0x03, 0x80, 0x0f, 0xe0, 0x03, 0x80,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }
+};
+
+Cursor bl = {
+ {-4, -11},
+ {0x1f, 0x00, 0x11, 0x00, 0x11, 0x00, 0x11, 0x00,
+ 0x11, 0x00, 0x11, 0x00, 0x11, 0x00, 0x11, 0x00,
+ 0x11, 0xff, 0xd0, 0x01, 0xb0, 0x01, 0xa0, 0x01,
+ 0x87, 0xff, 0x8c, 0x00, 0x82, 0x00, 0xfe, 0x00, },
+ {0x00, 0x00, 0x0e, 0x00, 0x0e, 0x00, 0x0e, 0x00,
+ 0x0e, 0x00, 0x0e, 0x00, 0x0e, 0x00, 0x0e, 0x00,
+ 0x0e, 0x00, 0x0f, 0xfe, 0x4f, 0xfe, 0x5f, 0xfe,
+ 0x78, 0x00, 0x70, 0x00, 0x7c, 0x00, 0x00, 0x0, }
+};
+
+Cursor l = {
+ {-7, -7},
+ {0x03, 0xe0, 0x02, 0x20, 0x02, 0x20, 0x1a, 0x20,
+ 0x16, 0x20, 0x36, 0x20, 0x60, 0x20, 0x40, 0x20,
+ 0x60, 0x20, 0x36, 0x20, 0x16, 0x20, 0x1a, 0x20,
+ 0x02, 0x20, 0x02, 0x20, 0x02, 0x20, 0x03, 0xe0, },
+ {0x00, 0x00, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0,
+ 0x09, 0xc0, 0x09, 0xc0, 0x1f, 0xc0, 0x3f, 0xc0,
+ 0x1f, 0xc0, 0x09, 0xc0, 0x09, 0xc0, 0x01, 0xc0,
+ 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x00, 0x00, }
+};
+
+Cursor *corners[9] = {
+ &tl, &t, &tr,
+ &l, nil, &r,
+ &bl, &b, &br,
+};
+
+Image*
+mkcolor(ulong col)
+{
+// TODO: what if we can't get RGBA32?
+// return allocimage(display, Rect(0,0,1,1), screen->chan, 1, col);
+ return allocimage(display, Rect(0,0,1,1), RGBA32, 1, col);
+}
+
+Image*
+getcolor(char *name, ulong col)
+{
+ Image *img;
+
+ if(name == nil)
+ return mkcolor(col);
+
+ name = smprint("th_%s", name);
+
+ img = namedimage(display, name);
+ if(img == nil){
+ img = mkcolor(col);
+ if(nameimage(img, name, 1) == 0)
+ panic("couldn't name image\n");
+ }
+ free(name);
+ return img;
+}
+
+Image *pal[8];
+
+Image*
+mkicon(char *px, int w, int h)
+{
+ int i, j, idx;
+ Image *img;
+
+ img = allocimage(display, Rect(0,0,w,h), RGBA32, 1, 0x00000000);
+ for(i = 0; i < h; i++)
+ for(j = 0; j < w; j++) {
+ idx = px[i*w + j];
+ if(idx)
+ draw(img, Rect(j,i,j+1,i+1), pal[idx], nil, ZP);
+ }
+ return img;
+}
+
+void
+initdata(void)
+{
+ // this is probably dumb.
+ pal[0] = mkcolor(0x00000000);
+ pal[1] = mkcolor(0x000000FF);
+ pal[2] = mkcolor(0xFFFFFFFF);
+ pal[3] = mkcolor(0xC0C0C0FF);
+ pal[4] = mkcolor(0x808080FF);
+ pal[5] = mkcolor(0x0000FFFF);
+ pal[6] = mkcolor(0x87888FFF);
+ pal[7] = mkcolor(0xC0C7C8FF);
+
+ inittheme();
+
+ /* these might have already been set by the theme */
+ if(background == nil)
+ background = getcolor("background", 0x777777FF);
+ if(colors[BACK] == nil)
+ colors[BACK] = getcolor("back", 0xFFFFFFFF);
+ if(colors[HIGH] == nil)
+ colors[HIGH] = getcolor("high", 0xCCCCCCFF);
+ if(colors[BORD] == nil)
+ colors[BORD] = getcolor("bord", 0x999999FF);
+ if(colors[TEXT] == nil)
+ colors[TEXT] = getcolor("text", 0x000000FF);
+ if(colors[HTEXT] == nil)
+ colors[HTEXT] = getcolor("htext", 0x000000FF);
+ if(colors[PALETEXT] == nil)
+ colors[PALETEXT] = getcolor("paletext", 0x666666FF);
+ if(colors[HOLDTEXT] == nil)
+ colors[HOLDTEXT] = getcolor("holdtext", DMedblue);
+ if(colors[PALEHOLDTEXT] == nil)
+ colors[PALEHOLDTEXT] = getcolor("paleholdtext", DGreyblue);
+}
--- /dev/null
+++ b/deskmenu.c
@@ -1,0 +1,169 @@
+#include "inc.h"
+
+static Image *menutxt;
+static Image *back;
+static Image *high;
+static Image *bord;
+static Image *text;
+static Image *htext;
+
+enum
+{
+ Border = 2,
+ ItemBorder = 1,
+ Itemwidth = 40,
+ Itemheight = 30
+};
+
+static
+void
+menucolors(void)
+{
+ /* Main tone is greenish, with negative selection */
+ back = getcolor("menuback", 0xEAFFEAFF);
+ high = getcolor("menuhigh", 0x448844FF); /* dark green */
+ bord = getcolor("menubord", 0x88CC88FF); /* not as dark green */
+ text = getcolor("menutext", 0x000000FF);
+ htext = getcolor("menuhtext", 0xEAFFEAFF);
+ if(back==nil || high==nil || bord==nil || text==nil || htext==nil)
+ goto Error;
+ return;
+
+ Error:
+ freeimage(back);
+ freeimage(high);
+ freeimage(bord);
+ freeimage(text);
+ freeimage(htext);
+ back = display->white;
+ high = display->black;
+ bord = display->black;
+ text = display->black;
+ htext = display->white;
+}
+
+static Rectangle
+menurect(Rectangle r, int i, int j)
+{
+ if(i < 0 || j < 0)
+ return Rect(0, 0, 0, 0);
+ return rectaddpt(Rect(0, 0, Itemwidth, Itemheight),
+ Pt(r.min.x+i*Itemwidth, r.min.y+j*Itemheight));
+}
+
+static void
+paintitem(Image *m, Rectangle contr, int i, int j, int highlight)
+{
+ Rectangle r;
+
+ if(i < 0 || j < 0)
+ return;
+ r = menurect(contr, i, j);
+ draw(m, r, highlight? high : back, nil, ZP);
+ border(m, r, ItemBorder, bord, ZP);
+}
+
+static Point
+menusel(Rectangle r, Point p)
+{
+ if(!ptinrect(p, r))
+ return Pt(-1,-1);
+ return Pt((p.x-r.min.x)/Itemwidth, (p.y-r.min.y)/Itemheight);
+}
+
+
+static Point
+menuscan(Image *m, int but, Mousectl *mc, Rectangle contr)
+{
+ Point ij, lastij;
+
+ lastij = menusel(contr, mc->xy);
+ paintitem(m, contr, lastij.x, lastij.y, 1);
+ readmouse(mc);
+ while(mc->buttons & (1<<(but-1))){
+ ij = menusel(contr, mc->xy);
+ if(!eqpt(ij, lastij))
+ paintitem(m, contr, lastij.x, lastij.y, 0);
+ if(ij.x == -1 || ij.y == -1)
+ return Pt(-1,-1);
+ lastij = ij;
+ paintitem(m, contr, lastij.x, lastij.y, 1);
+
+ readmouse(mc);
+ }
+ return lastij;
+}
+
+static void
+menupaint(Image *m, Rectangle contr, int nx, int ny)
+{
+ int i, j;
+
+ draw(m, contr, back, nil, ZP);
+ for(i = 0; i < nx; i++)
+ for(j = 0; j < ny; j++)
+ paintitem(m, contr, i, j, 0);
+}
+
+static Point
+clampscreen(Rectangle r)
+{
+ Point pt;
+
+ pt = ZP;
+ if(r.max.x>screen->r.max.x)
+ pt.x = screen->r.max.x-r.max.x;
+ if(r.max.y>screen->r.max.y)
+ pt.y = screen->r.max.y-r.max.y;
+ if(r.min.x<screen->r.min.x)
+ pt.x = screen->r.min.x-r.min.x;
+ if(r.min.y<screen->r.min.y)
+ pt.y = screen->r.min.y-r.min.y;
+ return pt;
+}
+
+Point
+dmenuhit(int but, Mousectl *mc, int nx, int ny, Point last)
+{
+ Rectangle r, menur, contr;
+ Point delta;
+ Point sel;
+
+ if(back == nil)
+ menucolors();
+
+ if(last.x < 0) last.x = 0;
+ if(last.x >= nx) last.x = nx-1;
+ if(last.y < 0) last.y = 0;
+ if(last.y >= ny) last.y = ny-1;
+
+ r = insetrect(Rect(0, 0, nx*Itemwidth, ny*Itemheight), -Border);
+ r = rectsubpt(r, Pt(last.x*Itemwidth+Itemwidth/2, last.y*Itemheight+Itemheight/2));
+ menur = rectaddpt(r, mc->xy);
+ delta = clampscreen(menur);
+ menur = rectaddpt(menur, delta);
+ contr = insetrect(menur, Border);
+
+ Image *b, *backup;
+ {
+ b = screen;
+ backup = allocimage(display, menur, screen->chan, 0, -1);
+ draw(backup, menur, screen, nil, menur.min);
+ }
+ draw(b, menur, back, nil, ZP);
+ border(b, menur, Border, bord, ZP);
+ menupaint(b, contr, nx, ny);
+
+ sel = Pt(-1, -1);
+ while(mc->buttons & (1<<(but-1))){
+ sel = menuscan(b, but, mc, contr);
+ }
+
+ if(backup){
+ draw(screen, menur, backup, nil, menur.min);
+ freeimage(backup);
+ }
+ flushimage(display, 1);
+
+ return sel;
+}
--- /dev/null
+++ b/flat.c
@@ -1,0 +1,111 @@
+#include "inc.h"
+
+int bordersz = 4;
+int titlesz = 18;
+int tabsz = 18;
+
+enum {
+ TITLE,
+ LTITLE,
+ TITLEHOLD,
+ LTITLEHOLD,
+ TITLETEXT,
+ LTITLETEXT,
+ TITLEHOLDTEXT,
+ LTITLEHOLDTEXT,
+
+ NumWinColors
+};
+
+Image *wincolors[NumWinColors];
+Image *shadecol;
+
+void
+wdecor(Window *w)
+{
+ if(w->frame == nil)
+ return;
+ int c = wcolsel(w);
+ int tc = TITLE + wcolsel(w);
+
+ Rectangle r;
+ int margin;
+
+ if(!w->noborder){
+ r = w->rect;
+ border(w->frame, r, bordersz, wincolors[tc], ZP);
+ }
+
+ if(!w->notitle){
+ r = w->titlerect;
+ r.max.y = r.min.y + titlesz;
+ draw(w->frame, r, wincolors[tc], nil, ZP);
+
+ margin = w->noborder ? titlesz : titlesz + bordersz;
+ margin = (margin - font->height)/2;
+ Point pt = Pt(r.min.x, w->rect.min.y + margin + 1);
+ if(w->cur)
+ string(w->frame, pt, wincolors[TITLETEXT+c], pt, font, w->cur->label);
+ }
+
+ r = rectsubpt(w->tabrect, Pt(0,1));
+ draw(w->frame, r, wincolors[c], nil, ZP);
+
+ int n = w->ref;
+ if(n > 1){
+ int wd = Dx(r)/n;
+ int xxx = r.max.x;
+ r.max.x = r.min.x + wd;
+ for(WinTab *t = w->tab; t; t = t->next){
+ if(t->next == nil)
+ r.max.x = xxx;
+ if(t != w->cur)
+ draw(w->frame, r, shadecol, nil, ZP);
+ margin = (tabsz - font->height)/2;
+ Point pt = Pt(r.min.x+bordersz/2, r.min.y + margin);
+ string(w->frame, pt, wincolors[TITLETEXT+c], pt, font, t->label);
+ r = rectaddpt(r, Pt(wd,0));
+ }
+ }
+}
+
+void
+wtitlectl(Window *w)
+{
+ if(mctl->buttons & 7){
+ wraise(w);
+ wfocus(w);
+ if(mctl->buttons & 1) {
+ if(!w->maximized)
+ grab(w, 1);
+ }
+ if(mctl->buttons & 4){
+ Window *ww = pick();
+ if(ww){
+ tmigrate(w->cur, ww);
+ wraise(ww);
+ wfocus(ww);
+ }
+ }
+ }
+}
+
+void
+inittheme(void)
+{
+ wincolors[TITLE] = getcolor("title", DGreygreen);
+ wincolors[LTITLE] = getcolor("ltitle", DPalegreygreen);
+// wincolors[TITLE] = getcolor("title", 0x2F78EDFF);
+// wincolors[LTITLE] = getcolor("ltitle", 0x7C9DE3FF);
+
+ wincolors[TITLEHOLD] = getcolor("titlehold", DMedblue);
+ wincolors[LTITLEHOLD] = getcolor("ltitlehold", DPalegreyblue);
+
+
+ wincolors[TITLETEXT] = getcolor("titletext", 0xFFFFFFFF);
+ wincolors[LTITLETEXT] = getcolor("ltitletext", 0x808080FF);
+ wincolors[TITLEHOLDTEXT] = getcolor("titleholdtext", 0xFFFFFFFF);
+ wincolors[LTITLEHOLDTEXT] = getcolor("ltitleholdtext", 0xC0C0C0FF);
+
+ shadecol = getcolor(nil, 0x00000020);
+}
--- /dev/null
+++ b/fs.c
@@ -1,0 +1,1067 @@
+#include "inc.h"
+
+enum {
+ Qroot,
+ Qwsys,
+ Qscreen,
+ Qsnarf,
+ Qwctl,
+ Qtap,
+ Qpick,
+ Qglobal = Qpick, /* last global one */
+
+ /* these need a window */
+ Qcons,
+ Qconsctl,
+ Qcursor,
+ Qwinid,
+ Qwinname,
+ Qlabel,
+ Qkbd,
+ Qmouse,
+ Qtext,
+ Qwdir,
+ Qwindow,
+
+ NQids,
+};
+
+typedef struct Dirent Dirent;
+struct Dirent
+{
+ int path;
+ int type;
+ char *name;
+ uint mode;
+};
+
+Dirent dirents[] = {
+ { Qroot, QTDIR, ".", 0500|DMDIR },
+ { Qwsys, QTDIR, "wsys", 0500|DMDIR },
+ { Qwinid, QTFILE, "winid", 0400 },
+ { Qwinname, QTFILE, "winname", 0400 },
+ { Qwdir, QTFILE, "wdir", 0600 },
+ { Qlabel, QTFILE, "label", 0600 },
+ { Qsnarf, QTFILE, "snarf", 0600 },
+ { Qtext, QTFILE, "text", 0600 },
+ { Qcons, QTFILE, "cons", 0600 },
+ { Qconsctl, QTFILE, "consctl", 0200 },
+ { Qkbd, QTFILE, "kbd", 0600 },
+ { Qmouse, QTFILE, "mouse", 0600 },
+ { Qcursor, QTFILE, "cursor", 0600 },
+ { Qscreen, QTFILE, "screen", 0400 },
+ { Qwindow, QTFILE, "window", 0400 },
+ { Qwctl, QTFILE, "wctl", 0600 },
+ { Qpick, QTFILE, "pick", 0400 },
+ { Qtap, QTFILE, "kbdtap", 0660 }
+};
+
+char Eperm[] = "permission denied";
+char Eexist[] = "file does not exist"; // XXX
+char Enotdir[] = "not a directory"; // XXX
+char Ebadfcall[] = "bad fcall type"; // XXX
+char Eoffset[] = "illegal offset";
+char Enomem[] = "out of memory";
+
+char Eflush[] = "interrupted";
+char Einuse[] = "file in use";
+char Edeleted[] = "window deleted";
+char Etooshort[] = "buffer too small";
+char Eshort[] = "short i/o request";
+char Elong[] = "snarf buffer too long";
+char Eunkid[] = "unknown id in attach";
+char Ebadrect[] = "bad rectangle in attach"; // XXX
+char Ewindow[] = "cannot make window";
+char Enowindow[] = "window has no image"; // XXX
+char Ebadmouse[] = "bad format on /dev/mouse";
+
+int fsysfd;
+char srvpipe[64];
+char *user;
+
+/* Extension of a Req, req->aux. also has a thread. */
+typedef struct Xreq Xreq;
+struct Xreq
+{
+ Req *req;
+ Channel *xc;
+ Channel *flush; /* cancel read/write */
+ Xreq *next;
+};
+#define XR(req) ((Xreq*)(req)->aux)
+static Xreq *xreqfree;
+
+/* Extension of a Fid, fid->aux */
+typedef struct Xfid Xfid;
+struct Xfid
+{
+ WinTab *w;
+ RuneConvBuf cnv;
+};
+#define XF(fid) ((Xfid*)(fid)->aux)
+
+typedef struct XreqMsg XreqMsg;
+struct XreqMsg
+{
+ Req *r;
+ void (*f)(Req*);
+};
+
+static void
+xreqthread(void *a)
+{
+ Xreq *xr = a;
+ XreqMsg xm;
+
+ threadsetname("xreg.%p", xr);
+ for(;;){
+ recv(xr->xc, &xm);
+ xr->req = xm.r;
+ xm.r->aux = xr;
+ (*xm.f)(xm.r);
+ /* return to pool */
+ xr->req = nil;
+ xr->next = xreqfree;
+ xreqfree = xr;
+ }
+}
+
+static Xreq*
+getxreq(void)
+{
+ Xreq *xr;
+ if(xreqfree){
+ xr = xreqfree;
+ xreqfree = xr->next;
+ }else{
+ xr = emalloc(sizeof(Xreq));
+ xr->xc = chancreate(sizeof(XreqMsg), 0);
+ xr->flush = chancreate(sizeof(int), 0);
+ threadcreate(xreqthread, xr, mainstacksize);
+ }
+ xr->next = nil;
+ return xr;
+}
+
+static void
+toxreq(Req *r, void (*f)(Req*))
+{
+ Xreq *xr;
+ XreqMsg xm;
+
+ xr = getxreq();
+ xm.r = r;
+ xm.f = f;
+ send(xr->xc, &xm);
+}
+
+static Xfid*
+getxfid(WinTab *w)
+{
+ Xfid *xf;
+ xf = emalloc(sizeof(Xfid));
+ memset(&xf->cnv, 0, sizeof(xf->cnv));
+ xf->w = w;
+ if(w)
+ incref(w);
+ return xf;
+}
+
+#define QID(w, q) ((w)<<8|(q))
+#define QWIN(q) ((q)>>8)
+#define QFILE(q) ((q)&0xFF)
+#define ID(w) ((w) ? (w)->id : 0)
+
+static void
+fsattach(Req *r)
+{
+ WinTab *w;
+ char *end;
+ int id;
+ Wctlcmd cmd;
+
+ if(strcmp(r->ifcall.uname, user) != 0){
+ respond(r, Eperm);
+ return;
+ }
+
+ if(strncmp(r->ifcall.aname, "new", 3) == 0){
+ cmd = parsewctl(r->ifcall.aname, ZR);
+ if(cmd.error){
+ respond(r, cmd.error);
+ return;
+ }
+ if(cmd.id > 0){
+ w = wfind(cmd.id);
+ if(w == nil){
+ respond(r, Eunkid);
+ return;
+ }
+ w = tcreate(w->w, cmd.scrolling);
+ }else
+ w = wtcreate(cmd.r, cmd.hidden, cmd.scrolling);
+ if(w == nil){
+ respond(r, Ewindow);
+ return;
+ }
+ wincmd(w, cmd.pid, cmd.dir, nil);
+ flushimage(display, 1);
+ decref(w); /* don't delete, xfid will take it */
+ }else if(strncmp(r->ifcall.aname, "none", 4) == 0){
+ w = nil;
+ }else if(id = strtol(r->ifcall.aname, &end, 10), *end == '\0'){
+ w = wfind(id);
+ if(w == nil){
+ respond(r, Eunkid);
+ return;
+ }
+ }else{
+ respond(r, Eunkid);
+ return;
+ }
+
+ r->fid->aux = getxfid(w);
+ r->fid->qid = (Qid){QID(ID(w),Qroot),0,QTDIR};
+ r->ofcall.qid = r->fid->qid;
+ respond(r, nil);
+}
+
+static char*
+fsclone(Fid *fid, Fid *newfid)
+{
+ if(XF(fid))
+ newfid->aux = getxfid(XF(fid)->w);
+ return nil;
+}
+
+int
+skipfile(char *name)
+{
+ return gotscreen && strcmp(name, "screen") == 0 ||
+ snarffd >= 0 && strcmp(name, "snarf") == 0 ||
+ !servekbd && strcmp(name, "kbd") == 0;
+}
+
+static char*
+fswalk1(Fid *fid, char *name, Qid *qid)
+{
+ int i;
+ Dirent *d;
+ Xfid *xf;
+ WinTab *w;
+ int dir;
+
+ xf = fid->aux;
+ w = xf->w;
+ dir = QFILE(fid->qid.path);
+ if(dir == Qroot){
+ if(strcmp(name, "..") == 0){
+ /* This sucks because we don't know which window we came from
+ * error out for now */
+ return "vorwärts immer, rückwärts nimmer";
+ }
+ for(i = 0; i < nelem(dirents); i++){
+ d = &dirents[i];
+ if((w || d->path <= Qglobal) &&
+ !skipfile(d->name) && strcmp(name, d->name) == 0){
+ fid->qid = (Qid){QID(ID(w),d->path), 0, d->type};
+ *qid = fid->qid;
+ return nil;
+ }
+ }
+ }else if(dir == Qwsys){
+ char *end;
+ int id;
+ if(strcmp(name, "..") == 0){
+ fid->qid = (Qid){QID(ID(w),Qroot), 0, QTDIR};
+ *qid = fid->qid;
+ return nil;
+ }
+ if(id = strtol(name, &end, 10), *end == '\0'){
+ w = wfind(id);
+ if(w || id == 0){
+ if(w)
+ incref(w);
+ wrelease(xf->w);
+ xf->w = w;
+ fid->qid = (Qid){QID(ID(w),Qroot), 0, QTDIR};
+ *qid = fid->qid;
+ return nil;
+ }
+ }
+ }
+ return "no such file";
+}
+
+static int
+genrootdir(int n, Dir *d, void *a)
+{
+ WinTab *w = a;
+ int i;
+
+ n++; /* -1 is root dir */
+ i = 0;
+ while(n--){
+ i++;
+ if(i >= nelem(dirents))
+ return -1;
+ /* we know the last file is never skipped */
+ while(w == nil && dirents[i].path > Qglobal ||
+ skipfile(dirents[i].name))
+ i++;
+ }
+
+ d->atime = time(nil);
+ d->mtime = d->atime;
+ d->uid = estrdup9p(user);
+ d->gid = estrdup9p(d->uid);
+ d->muid = estrdup9p(d->uid);
+ d->qid = (Qid){QID(ID(w),dirents[i].path), 0, dirents[i].type};
+ if(dirents[i].path == Qsnarf)
+ d->qid.vers = snarfversion;
+ d->mode = dirents[i].mode;
+ d->name = estrdup9p(dirents[i].name);
+ d->length = 0;
+ return 0;
+}
+
+static int
+genwsysdir(int n, Dir *d, void*)
+{
+ WinTab *w;
+
+ if(n == -1){
+ genrootdir(0, d, nil);
+ free(d->name);
+ d->name = estrdup9p("wsys");
+ return 0;
+ }
+ if(n < nwintabs){
+ w = wintabs[n];
+ genrootdir(-1, d, w);
+ free(d->name);
+ d->name = smprint("%d", w->id);
+ return 0;
+ }
+ return -1;
+}
+
+static int ntsnarf;
+static char *tsnarf;
+
+static void
+fsopen(Req *r)
+{
+ Xfid *xf;
+ WinTab *w;
+ int rd, wr;
+
+ xf = XF(r->fid);
+ w = xf->w;
+
+ /* TODO: check and sanitize mode */
+
+ if(w && w->deleted){
+ respond(r, Edeleted);
+ return;
+ }
+
+ /* only text can be truncated (not implemented yet) */
+ if(QFILE(r->fid->qid.path) != Qtext)
+ r->ifcall.mode &= (OREAD|OWRITE|ORDWR);
+
+ rd = r->ifcall.mode==ORDWR || r->ifcall.mode==OREAD;
+ wr = r->ifcall.mode==ORDWR || r->ifcall.mode==OWRITE;
+ switch(QFILE(r->fid->qid.path)){
+ case Qtext:
+ if(r->ifcall.mode & OTRUNC)
+ xdelete(&w->text, 0, w->text.nr);
+ break;
+
+ case Qsnarf:
+ if(wr)
+ ntsnarf = 0;
+ break;
+
+ case Qconsctl:
+ if(w->consctlopen){
+ respond(r, Einuse);
+ return;
+ }
+ w->consctlopen = TRUE;
+ break;
+
+ case Qkbd:
+ if(w->kbdopen){
+ respond(r, Einuse);
+ return;
+ }
+ w->kbdopen = TRUE;
+ break;
+
+ case Qmouse:
+ if(w->mouseopen){
+ respond(r, Einuse);
+ return;
+ }
+ w->resized = FALSE;
+ w->mouseopen = TRUE;
+ break;
+
+ case Qwctl:
+ if(w && rd){
+ /* can only have one reader of wctl */
+ if(w->wctlopen){
+ respond(r, Einuse);
+ return;
+ }
+ w->wctlopen = TRUE;
+ w->wctlready = TRUE;
+ wsendmsg(w, Wakeup);
+ }
+ break;
+
+ case Qpick:
+ if(xf->w){
+ wrelease(xf->w);
+ xf->w = nil;
+ }
+ /* pick window from main thread.
+ * TODO: this may not be optimal because
+ * it might block this thread. */
+ Channel *wc = chancreate(sizeof(WinTab*), 0);
+ sendp(pickchan, wc);
+ w = recvp(wc);
+ /* actually want the current tab */
+ if(w) w = ((Window*)w)->cur;
+ xf->w = w;
+ if(w)
+ incref(w);
+ chanfree(wc);
+ break;
+
+ case Qtap:
+ if(rd && totap || wr && fromtap){
+ respond(r, Einuse);
+ return;
+ }
+ if(rd){
+ totap = chancreate(sizeof(Channel**), 0);
+ sendp(opentap, totap);
+ }
+ if(wr){
+ fromtap = chancreate(sizeof(char*), 32);
+ sendp(opentap, fromtap);
+ }
+ break;
+ }
+
+ respond(r, nil);
+}
+
+static void
+fsclose(Fid *fid)
+{
+ Xfid *xf;
+ WinTab *w;
+ Text *x;
+ int rd, wr;
+
+ xf = XF(fid);
+ if(xf == nil)
+ return;
+ w = xf->w;
+ x = &w->text;
+
+ rd = fid->omode==ORDWR || fid->omode==OREAD;
+ wr = fid->omode==ORDWR || fid->omode==OWRITE;
+ if(fid->omode != -1)
+ switch(QFILE(fid->qid.path)){
+ /* replace snarf buffer when /dev/snarf is closed */
+ case Qsnarf:
+ if(wr){
+ setsnarf(tsnarf, ntsnarf);
+ ntsnarf = 0;
+ }
+ break;
+
+ case Qconsctl:
+ if(x->rawmode){
+ x->rawmode = 0;
+ wsendmsg(w, Rawoff);
+ }
+ if(w->holdmode > 0){
+ w->holdmode = 1;
+ wsendmsg(w, Holdoff);
+ }
+ w->consctlopen = FALSE;
+ break;
+
+ case Qkbd:
+ w->kbdopen = FALSE;
+ break;
+
+ case Qmouse:
+ w->mouseopen = FALSE;
+ w->resized = FALSE;
+ wsendmsg(w, Refresh);
+ break;
+
+ case Qcursor:
+ w->cursorp = nil;
+ wsetcursor(w);
+ break;
+
+ case Qwctl:
+ if(w && rd)
+ w->wctlopen = FALSE;
+ break;
+
+ case Qtap:
+ if(wr && fromtap)
+ sendp(closetap, fromtap);
+ if(rd && totap)
+ sendp(closetap, totap);
+ break;
+ }
+
+ if(xf->w)
+ wrelease(xf->w);
+ free(xf->cnv.buf);
+ free(xf);
+ fid->aux = nil;
+}
+
+static int
+readimgdata(Image *i, char *t, Rectangle r, int offset, int n)
+{
+ int ww, oo, y, m;
+ uchar *tt;
+
+ ww = bytesperline(r, i->depth);
+ r.min.y += offset/ww;
+ if(r.min.y >= r.max.y)
+ return 0;
+ y = r.min.y + (n + ww-1)/ww;
+ if(y < r.max.y)
+ r.max.y = y;
+ m = ww * Dy(r);
+ oo = offset % ww;
+ if(oo == 0 && n >= m)
+ return unloadimage(i, r, (uchar*)t, n);
+ if((tt = malloc(m)) == nil)
+ return -1;
+ m = unloadimage(i, r, tt, m) - oo;
+ if(m > 0){
+ if(n < m) m = n;
+ memmove(t, tt + oo, m);
+ }
+ free(tt);
+ return m;
+}
+
+/* Fill request from image,
+ * returns only either header or data */
+char*
+readimg(Req *r, Image *img)
+{
+ char *head;
+ char cbuf[30];
+ Rectangle rect;
+ int n;
+
+ rect = img->r;
+ if(r->ifcall.offset < 5*12){
+ head = smprint("%11s %11d %11d %11d %11d ",
+ chantostr(cbuf, img->chan),
+ rect.min.x, rect.min.y, rect.max.x, rect.max.y);
+ readstr(r, head);
+ free(head);
+ }else{
+ /* count is unsigned, so check with n */
+ n = readimgdata(img, r->ofcall.data, rect, r->ifcall.offset-5*12, r->ifcall.count);
+ if(n < 0)
+ return Enomem;
+ r->ofcall.count = n;
+ }
+ return nil;
+}
+
+static char*
+waitblocking(Req *r, Channel *waitchan, Channel **replychan)
+{
+ WinTab *w;
+ enum { Adata, Agone, Aflush, NALT };
+ Alt alts[NALT+1];
+
+ w = XF(r->fid)->w;
+
+ *replychan = nil;
+ alts[Adata] = ALT(waitchan, replychan, CHANRCV);
+ alts[Agone] = w ? ALT(w->gone, nil, CHANRCV)
+ : ALT(nil, nil, CHANNOP);
+ alts[Aflush] = ALT(XR(r)->flush, nil, CHANRCV);
+ alts[NALT].op = CHANEND;
+ switch(alt(alts)){
+ case Adata: return nil;
+ case Agone: return Edeleted;
+ case Aflush: return Eflush;
+ }
+ assert(0); /* can't happen */
+ return nil;
+}
+
+static char*
+readblocking(Req *r, Channel *readchan)
+{
+ Channel *chan;
+ Stringpair pair;
+ char *err;
+
+ if(err = waitblocking(r, readchan, &chan))
+ return err;
+ pair.s = r->ofcall.data;
+ pair.ns = r->ifcall.count;
+ send(chan, &pair);
+ recv(chan, &pair);
+ r->ofcall.count = min(r->ifcall.count, pair.ns);
+ return nil;
+}
+
+static void
+xread(Req *r)
+{
+ WinTab *w;
+ char *data;
+
+ w = XF(r->fid)->w;
+
+ if(w && w->deleted){
+ respond(r, Edeleted);
+ return;
+ }
+
+ switch(QFILE(r->fid->qid.path)){
+ case Qwinid:
+ data = smprint("%11d ", w->id);
+ readstr(r, data);
+ free(data);
+ break;
+ case Qwinname:
+ readstr(r, w->name);
+ break;
+ case Qlabel:
+ readstr(r, w->label);
+ break;
+ case Qsnarf:
+ data = smprint("%.*S", nsnarf, snarf);
+ readstr(r, data);
+ free(data);
+ break;
+ case Qtext:
+ data = smprint("%.*S", w->text.nr, w->text.r);
+ readstr(r, data);
+ free(data);
+ break;
+ case Qcons:
+ respond(r, readblocking(r, w->consread));
+ return;
+ case Qkbd:
+ respond(r, readblocking(r, w->kbdread));
+ return;
+ case Qmouse:
+ respond(r, readblocking(r, w->mouseread));
+ return;
+ case Qcursor:
+ respond(r, "cursor read not implemented");
+ return;
+ case Qscreen:
+ respond(r, readimg(r, screen));
+ return;
+ case Qwindow:
+ respond(r, readimg(r, w->w->frame));
+ return;
+ case Qwctl:
+/* TODO: what's with the Etooshort conditions?? */
+ if(w == nil){
+ if(r->ifcall.count < 4*12){
+ respond(r, Etooshort);
+ return;
+ }
+// data = smprint("%11d %11d %11d %11d %11s %11s ",
+ data = smprint("%11d %11d %11d %11d %11d %11d ",
+ screen->r.min.x, screen->r.min.y, screen->r.max.x, screen->r.max.y,
+// "nowindow", "nowindow");
+ screenoff.x, screenoff.y);
+ readstr(r, data);
+ free(data);
+ }else{
+ if(r->ifcall.count < 4*12){
+ respond(r, Etooshort);
+ return;
+ }
+ respond(r, readblocking(r, w->wctlread));
+ return;
+ }
+ break;
+ case Qpick:
+ data = smprint("%11d ", w ? w->id : -1);
+ readstr(r, data);
+ free(data);
+ break;
+ case Qtap:
+ respond(r, readblocking(r, totap));
+ return;
+ default:
+ respond(r, "cannot read");
+ return;
+ }
+ respond(r, nil);
+}
+
+static void
+xwrite(Req *r)
+{
+ Xfid *xf;
+ WinTab *w;
+ Text *x;
+ vlong offset;
+ u32int count;
+ char *data, *p, *e, *err;
+ Point pt;
+ Channel *chan;
+ Stringpair pair;
+
+ xf = XF(r->fid);
+ w = xf->w;
+ x = &w->text;
+ offset = r->ifcall.offset;
+ count = r->ifcall.count;
+ data = r->ifcall.data;
+ r->ofcall.count = count;
+
+ /* custom emalloc9p allows us this */
+ data[count] = '\0';
+
+ if(w && w->deleted){
+ respond(r, Edeleted);
+ return;
+ }
+ int f = QFILE(r->fid->qid.path);
+ switch(f){
+ case Qtext:
+ case Qcons:
+ if(err = waitblocking(r, w->conswrite, &chan)){
+ respond(r, err);
+ return;
+ }
+ cnvsize(&xf->cnv, count);
+ memmove(xf->cnv.buf+xf->cnv.n, data, count);
+ xf->cnv.n += count;
+ pair = b2r(&xf->cnv);
+ send(chan, &pair);
+ break;
+
+ case Qconsctl:
+ if(strncmp(data, "holdon", 6) == 0){
+ wsendmsg(w, Holdon);
+ break;
+ }
+ if(strncmp(data, "holdoff", 7) == 0){
+ wsendmsg(w, Holdoff);
+ break;
+ }
+ if(strncmp(data, "rawon", 5) == 0){
+ if(w->holdmode){
+ w->holdmode = 1;
+ wsendmsg(w, Holdoff);
+ }
+ if(x->rawmode++ == 0)
+ wsendmsg(w, Rawon);
+ break;
+ }
+ if(strncmp(data, "rawoff", 6) == 0){
+ if(--x->rawmode == 0)
+ wsendmsg(w, Rawoff);
+ break;
+ }
+ respond(r, "unknown control message");
+ return;
+
+ case Qmouse:
+ if(data[0] != 'm' && data[0] != 'M'){
+ respond(r, Ebadmouse);
+ return;
+ }
+ p = nil;
+ pt.x = strtoul(data+1, &p, 0);
+ if(p == nil){
+ respond(r, Eshort);
+ return;
+ }
+ pt.y = strtoul(p, nil, 0);
+ wmovemouse(w->w, pt, data[0] == 'M');
+ break;
+
+ case Qcursor:
+ if(count < 2*4+2*2*16)
+ w->cursorp = nil;
+ else{
+ w->cursor.offset.x = BGLONG(data+0*4);
+ w->cursor.offset.y = BGLONG(data+1*4);
+ memmove(w->cursor.clr, data+2*4, 2*2*16);
+ w->cursorp = &w->cursor;
+ }
+ cursor = (void*)(uintptr)~0; /* invalide cache */
+ wsetcursor(w);
+ break;
+
+ case Qlabel:
+ if(offset != 0){
+ respond(r, "non-zero offset writing label");
+ return;
+ }
+ wsetlabel(w, data);
+ break;
+
+ case Qsnarf:
+ if(count == 0)
+ break;
+ /* always append only */
+ if(ntsnarf > MAXSNARF){ /* avoid thrashing when people cut huge text */
+ respond(r, Elong);
+ return;
+ }
+ p = realloc(tsnarf, ntsnarf+count);
+ if(p == nil){
+ respond(r, Enomem);
+ return;
+ }
+ tsnarf = p;
+ memmove(tsnarf+ntsnarf, data, count);
+ ntsnarf += count;
+ break;
+
+ case Qwdir:
+ if(count > 0 && data[count-1] == '\n')
+ data[--count] = '\0';
+ if(count == 0)
+ break;
+ /* assume data comes in a single write */
+ if(data[0] == '/')
+ p = smprint("%.*s", count, data);
+ else
+ p = smprint("%s/%.*s", w->dir, count, data);
+ if(p == nil){
+ respond(r, Enomem);
+ return;
+ }
+ free(w->dir);
+ w->dir = cleanname(p);
+ break;
+
+ case Qwctl:
+ respond(r, writewctl(w, data));
+ return;
+
+ case Qtap:
+ if(count < 2){
+ respond(r, "malformed key");
+ return;
+ }
+ e = data + count;
+ for(p = data; p < e; p += strlen(p)+1){
+ switch(*p){
+ case '\0':
+ r->ofcall.count = p - data;
+ respond(r, "null message type");
+ return;
+ case 'z':
+ /* ignore context change */
+ break;
+ default:
+ chanprint(fromtap, "%s", p);
+ break;
+ }
+ }
+ break;
+
+ default:
+ respond(r, "cannot write");
+ return;
+ }
+ respond(r, nil);
+}
+
+static void
+fsread(Req *r)
+{
+ if((r->fid->qid.type & QTDIR) == 0){
+ toxreq(r, xread);
+ return;
+ }
+
+ switch(QFILE(r->fid->qid.path)){
+ case Qroot:
+ dirread9p(r, genrootdir, XF(r->fid)->w);
+ break;
+ case Qwsys:
+ dirread9p(r, genwsysdir, nil);
+ break;
+ }
+ respond(r, nil);
+}
+
+static void
+fswrite(Req *r)
+{
+ toxreq(r, xwrite);
+}
+
+static void
+fsflush(Req *r)
+{
+ Xreq *xr;
+ int dummy = 0;
+
+ xr = XR(r->oldreq);
+ assert(xr);
+
+ /* TODO: not entirely sure this is right.
+ * is it possible no-one is listening? */
+ send(xr->flush, &dummy);
+ respond(r, nil);
+}
+
+static void
+fsstat(Req *r)
+{
+ int f;
+
+ f = QFILE(r->fid->qid.path);
+ genrootdir(f-1, &r->d, XF(r->fid)->w);
+ respond(r, nil);
+}
+
+Srv fsys = {
+ .attach fsattach,
+ .open fsopen,
+ .read fsread,
+ .write fswrite,
+ .stat fsstat,
+ .flush fsflush,
+ .walk1 fswalk1,
+ .clone fsclone,
+ .destroyfid fsclose,
+ nil
+};
+
+static Ioproc *io9p;
+
+/* copy & paste from /sys/src/libc/9sys/read9pmsg.c
+ * changed to use ioreadn instead of readn */
+int
+read9pmsg(int fd, void *abuf, uint n)
+{
+ int m, len;
+ uchar *buf;
+
+ buf = abuf;
+
+ /* read count */
+ m = ioreadn(io9p, fd, buf, BIT32SZ);
+ if(m != BIT32SZ){
+ if(m < 0)
+ return -1;
+ return 0;
+ }
+
+ len = GBIT32(buf);
+ if(len <= BIT32SZ || len > n){
+ werrstr("bad length in 9P2000 message header");
+ return -1;
+ }
+ len -= BIT32SZ;
+ m = ioreadn(io9p, fd, buf+BIT32SZ, len);
+ if(m < len)
+ return 0;
+ return BIT32SZ+m;
+}
+
+/* +1 so we can always zero-terminate a write buffer */
+void *emalloc9p(ulong sz) { return emalloc(sz+1); }
+void *erealloc9p(void *v, ulong sz) { return erealloc(v, sz+1); }
+char *estrdup9p(char *s) { return estrdup(s); }
+
+void
+post(char *name, int srvfd)
+{
+ char buf[80];
+ int fd;
+
+ snprint(buf, sizeof buf, "/srv/%s", name);
+ fd = create(buf, OWRITE|ORCLOSE|OCEXEC, 0600);
+ if(fd < 0)
+ panic(buf);
+ if(fprint(fd, "%d", srvfd) < 0)
+ panic("post");
+ putenv("wsys", buf);
+ /* leave fd open */
+}
+
+/*
+ * Build pipe with OCEXEC set on second fd.
+ * Can't put it on both because we want to post one in /srv.
+ */
+int
+cexecpipe(int *p0, int *p1)
+{
+ /* pipe the hard way to get close on exec */
+ if(bind("#|", "/mnt/temp", MREPL) == -1)
+ return -1;
+ *p0 = open("/mnt/temp/data", ORDWR);
+ *p1 = open("/mnt/temp/data1", ORDWR|OCEXEC);
+ unmount(nil, "/mnt/temp");
+ if(*p0<0 || *p1<0)
+ return -1;
+ return 0;
+}
+
+static void
+srvthread(void*)
+{
+ threadsetname("fs");
+ srv(&fsys);
+}
+
+void
+startfs(void)
+{
+ io9p = ioproc();
+
+ if(cexecpipe(&fsysfd, &fsys.infd) < 0)
+ panic("pipe");
+ fsys.outfd = fsys.infd;
+ user = getuser();
+ snprint(srvpipe, sizeof(srvpipe), "lola.%s.%lud", user, (ulong)getpid());
+ post(srvpipe, fsysfd);
+// chatty9p++;
+ threadcreate(srvthread, nil, mainstacksize);
+}
+
+int
+fsmount(int id)
+{ char buf[32];
+
+ close(fsys.infd); /* close server end so mount won't hang if exiting */
+ snprint(buf, sizeof buf, "%d", id);
+ if(mount(fsysfd, -1, "/mnt/wsys", MREPL, buf) == -1){
+ fprint(2, "mount failed: %r\n");
+ return -1;
+ }
+ if(bind("/mnt/wsys", "/dev", MBEFORE) == -1){
+ fprint(2, "bind failed: %r\n");
+ return -1;
+ }
+ return 0;
+}
--- /dev/null
+++ b/inc.h
@@ -1,0 +1,397 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <keyboard.h>
+#include <mouse.h>
+#include <cursor.h>
+#include <frame.h>
+#include <fcall.h>
+#include <9p.h>
+#include <complete.h>
+#include <plumb.h>
+
+#include <ctype.h>
+
+typedef uchar bool;
+enum {
+ FALSE = 0,
+ TRUE = 1,
+
+ BIG = 3,
+ MAXWINDOWS = 1000
+};
+
+#define ALT(c, v, t) (Alt){ c, v, t, nil, nil, 0 }
+
+#define CTRL(c) ((c)&0x1F)
+
+
+typedef struct RKeyboardctl RKeyboardctl;
+struct RKeyboardctl
+{
+ Keyboardctl;
+ int kbdfd;
+};
+RKeyboardctl *initkbd(char *file, char *kbdfile);
+
+
+extern Rune *snarf;
+extern int nsnarf;
+extern int snarfversion;
+extern int snarffd;
+enum { MAXSNARF = 100*1024 };
+void putsnarf(void);
+void getsnarf(void);
+void setsnarf(char *s, int ns);
+
+typedef struct Text Text;
+struct Text
+{
+ Frame;
+ Rectangle scrollr, lastsr;
+ Image *i;
+ Rune *r;
+ uint nr;
+ uint maxr;
+ uint org; /* start of Frame's text */
+ uint q0, q1; /* selection */
+ uint qh; /* host point, output here */
+
+ /* not entirely happy with this in here */
+ bool rawmode;
+ Rune *raw;
+ int nraw;
+
+ int posx;
+};
+
+void xinit(Text *x, Rectangle textr, Rectangle scrollr, int tabwidth, Font *ft, Image *b, Image **cols);
+void xsetrects(Text *x, Rectangle textr, Rectangle scrollr);
+void xclear(Text *x);
+void xredraw(Text *x);
+void xcoldraw(Text *x);
+void xfullredraw(Text *x);
+uint xinsert(Text *x, Rune *r, int n, uint q0);
+void xfill(Text *x);
+void xdelete(Text *x, uint q0, uint q1);
+void xsetselect(Text *x, uint q0, uint q1);
+void xselect(Text *x, Mousectl *mc);
+void xscrdraw(Text *x);
+void xscroll(Text *x, Mousectl *mc, int but);
+void xscrolln(Text *x, int n);
+void xshow(Text *x, uint q0);
+void xplacetick(Text *x, uint q);
+void xtype(Text *x, Rune r);
+int xninput(Text *x);
+void xaddraw(Text *x, Rune *r, int nr);
+void xlook(Text *x);
+void xsnarf(Text *x);
+void xcut(Text *x);
+void xpaste(Text *x);
+void xsend(Text *x);
+int xplumb(Text *w, char *src, char *dir, int maxsize);
+
+enum
+{
+ // NCOL is defined by libframe, add more after it
+ PALETEXT = NCOL,
+ HOLDTEXT,
+ PALEHOLDTEXT,
+
+ NumColors
+};
+
+extern Image *background;
+extern Image *colors[NumColors];
+extern Cursor whitearrow;
+extern Cursor query;
+extern Cursor crosscursor;
+extern Cursor boxcursor;
+extern Cursor sightcursor;
+extern Cursor *corners[9];
+void initdata(void);
+
+extern int tabwidth;
+extern bool scrolling;
+extern bool notitle;
+extern int ndeskx;
+extern int ndesky;
+
+extern Screen *wscreen;
+extern Image *fakebg;
+extern Mousectl *mctl;
+extern char *startdir;
+extern bool shiftdown, ctldown;
+extern bool gotscreen;
+extern bool servekbd;
+
+
+typedef struct RuneConvBuf RuneConvBuf;
+struct RuneConvBuf
+{
+ char *buf;
+ int maxbuf; // allocated size
+ int nb; // size
+ int n; // filled
+};
+
+typedef struct Stringpair Stringpair;
+struct Stringpair /* rune and nrune or byte and nbyte */
+{
+ void *s;
+ int ns;
+};
+
+typedef struct Mousestate Mousestate;
+struct Mousestate
+{
+ Mouse;
+ ulong counter; /* serial no. of mouse event */
+};
+
+typedef struct Mousequeue Mousequeue;
+struct Mousequeue
+{
+ Mousestate q[16];
+ int ri; /* read index into queue */
+ int wi; /* write index */
+ ulong counter; /* serial no. of last mouse event we received */
+ ulong lastcounter; /* serial no. of last mouse event sent to client */
+ int lastb; /* last button state we received */
+ bool full; /* filled the queue; no more recording until client comes back */
+};
+
+typedef struct Queue Queue;
+struct Queue
+{
+ char *q[32];
+ int ri;
+ int wi;
+ bool full;
+};
+int qadd(Queue *q, char *data);
+char *qget(Queue *q);
+int qempty(Queue *q);
+
+enum
+{
+ Resized,
+ Deleted,
+ Refresh,
+ Holdon,
+ Holdoff,
+ Rawon,
+ Rawoff,
+ Wakeup
+};
+
+extern int bordersz;
+extern int titlesz;
+extern int tabsz;
+
+typedef struct Window Window;
+typedef struct WinTab WinTab;
+
+struct Window
+{
+ Ref;
+ Window *lower;
+ Window *higher;
+ bool hidden;
+ Image *frame;
+ Screen *screen;
+ int noborder;
+ bool notitle;
+ bool maximized;
+ bool sticky;
+ Rectangle rect;
+ Rectangle titlerect;
+ Rectangle tabrect;
+ Rectangle contrect;
+ Rectangle scrollr;
+ Rectangle textr;
+ Rectangle origrect;
+
+ // tmp
+ WinTab *tab;
+ WinTab *cur;
+};
+
+struct WinTab
+{
+ Ref;
+ bool deleted;
+ Window *w;
+ WinTab *next;
+ Image *content;
+ int id;
+ char name[32];
+ int namecount;
+ char *label;
+ int notefd;
+ char *dir;
+
+ Text text;
+ int holdmode;
+ bool scrolling;
+ bool wctlready;
+ bool wctlopen;
+
+ Mousectl mc;
+ Mousequeue mq;
+ int mouseopen;
+ int resized;
+
+ Cursor *cursorp;
+ Cursor cursor;
+
+ Channel *kbd;
+ Queue kq;
+ bool consctlopen;
+ bool kbdopen;
+
+ Channel *gone; /* window gone */
+ Channel *ctl; /* Wctlmesg */
+ /* channels to xreqs */
+ Channel *conswrite;
+ Channel *consread;
+ Channel *kbdread;
+ Channel *mouseread;
+ Channel *wctlread;
+ Channel *complete;
+
+ char *threadname; /* for debugging */
+};
+
+extern Window *bottomwin, *topwin;
+extern Window *windows[MAXWINDOWS];
+extern int nwindows;
+extern WinTab *wintabs[MAXWINDOWS];
+extern int nwintabs;
+extern Window *focused, *cursorwin;
+extern Point screenoff;
+
+Window *wcreate(Rectangle r, bool hidden);
+int wcolsel(Window *w);
+void wtitlectl(Window *w);
+void wdecor(Window *w);
+void wmaximize(Window *w);
+void wrestore(Window *w);
+void wresize(Window *w, Rectangle r);
+void wrecreate(Window *w);
+Window *wpointto(Point pt);
+void wdelete(Window *w);
+void wmove(Window *w, Point pos);
+void wraise(Window *w);
+void wlower(Window *w);
+void wfocus(Window *w);
+void wunfocus(Window *w);
+int whide(Window *w);
+int wunhide(Window *w);
+void wmovemouse(Window *w, Point pt, bool force);
+
+void wrelease(WinTab *w);
+void wsendmsg(WinTab *w, int type);
+WinTab *wfind(int id);
+void wsetcursor(WinTab *w);
+void wsetlabel(WinTab *w, char *label);
+void wsetname(WinTab *w);
+void wsetpid(WinTab *w, int pid, int dolabel);
+void wsethold(WinTab *w, int hold);
+void wtype(WinTab *w, Rune r);
+int wincmd(WinTab *w, int pid, char *dir, char **argv);
+
+WinTab *tcreate(Window *w, bool scrolling);
+void tfocus(WinTab *t);
+void tdelete(WinTab *t);
+void tmigrate(WinTab *t, Window *w);
+void tmoveleft(WinTab *t);
+void tmoveright(WinTab *t);
+
+WinTab *wtcreate(Rectangle r, bool hidden, bool scrolling);
+
+void screenoffset(int offx, int offy);
+
+typedef struct Wctlcmd Wctlcmd;
+struct Wctlcmd
+{
+ int cmd;
+ Rectangle r;
+ char *args;
+ int pid;
+ int id;
+ bool hidden;
+ bool scrolling;
+ char *dir;
+ char *error;
+};
+
+Wctlcmd parsewctl(char *s, Rectangle r);
+char *writewctl(WinTab *w, char *data);
+
+
+extern Cursor *cursor;
+void setcursoroverride(Cursor *c, int ov);
+void setcursornormal(Cursor *c);
+
+Rectangle newrect(void);
+int goodrect(Rectangle r);
+Rectangle centerrect(Rectangle r, Rectangle s);
+void borderTL(Image *img, Rectangle r, Image *c);
+void borderBR(Image *img, Rectangle r, Image *c);
+void winborder(Image *img, Rectangle r, Image *c1, Image *c2);
+
+void refresh(void);
+Point dmenuhit(int but, Mousectl *mc, int nx, int ny, Point last);
+void drainmouse(Mousectl *mc, WinTab *w);
+Window *pick(void);
+void grab(Window *w, int btn);
+void btn3menu(void);
+
+void inittheme(void);
+Image *getcolor(char *name, ulong defcol);
+Image *mkicon(char *px, int w, int h);
+
+
+extern Channel *opentap; /* open fromtap or totap */
+extern Channel *closetap; /* close fromtap or totap */
+extern Channel *fromtap; /* input from kbd tap program to window */
+extern Channel *totap; /* our keyboard input to tap program */
+
+extern Channel *pickchan;
+
+
+extern Srv fsys;
+void startfs(void);
+int fsmount(int id);
+
+#define runemalloc(n) malloc((n)*sizeof(Rune))
+#define runerealloc(a, n) realloc(a, (n)*sizeof(Rune))
+#define runemove(a, b, n) memmove(a, b, (n)*sizeof(Rune))
+#define min(a, b) ((a) < (b) ? (a) : (b))
+#define max(a, b) ((a) > (b) ? (a) : (b))
+
+void panic(char *s);
+void *emalloc(ulong size);
+void *erealloc(void *p, ulong size);
+char *estrdup(char *s);
+int handlebs(Stringpair *pair);
+void cnvsize(RuneConvBuf *cnv, int nb);
+int r2bfill(RuneConvBuf *cnv, Rune *rp, int nr);
+void r2bfinish(RuneConvBuf *cnv, Stringpair *pair);
+Stringpair b2r(RuneConvBuf *cnv);
+
+
+typedef struct Timer Timer;
+struct Timer
+{
+ int dt;
+ int cancel;
+ Channel *c; /* chan(int) */
+ Timer *next;
+};
+void timerinit(void);
+Timer *timerstart(int dt);
+void timerstop(Timer *t);
+void timercancel(Timer *t);
--- /dev/null
+++ b/kbd.c
@@ -1,0 +1,107 @@
+#include "inc.h"
+
+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;
+}
--- /dev/null
+++ b/main.c
@@ -1,0 +1,1100 @@
+#include "inc.h"
+
+int tabwidth;
+bool scrolling;
+bool notitle;
+int ndeskx = 3;
+int ndesky = 3;
+
+RKeyboardctl *kbctl;
+Mousectl *mctl;
+char *startdir;
+bool shiftdown, ctldown;
+bool gotscreen;
+bool servekbd;
+
+Screen *wscreen;
+Image *fakebg;
+
+Channel *pickchan;
+
+void
+killprocs(void)
+{
+ int i;
+
+ for(i = 0; i < nwintabs; i++)
+ if(wintabs[i]->notefd >= 0)
+ write(wintabs[i]->notefd, "hangup", 6);
+}
+
+static char *oknotes[] ={
+ "delete",
+ "hangup",
+ "kill",
+ "exit",
+ nil, // for debugging
+ nil
+};
+
+int
+notehandler(void*, char *msg)
+{
+ int i;
+
+ killprocs();
+ for(i = 0; oknotes[i]; i++)
+ if(strncmp(oknotes[i], msg, strlen(oknotes[i])) == 0)
+ threadexitsall(msg);
+ fprint(2, "lola %d: abort: %s\n", getpid(), msg);
+ abort();
+}
+
+/*
+ * /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 };
+
+WinTab*
+new(Rectangle r)
+{
+ WinTab *w;
+
+ w = wtcreate(r, FALSE, scrolling);
+ assert(w);
+ if(wincmd(w, 0, nil, rcargv) == 0)
+ return nil;
+ return w;
+}
+
+WinTab*
+newtab(Window *ww)
+{
+ WinTab *w;
+
+ w = tcreate(ww, scrolling);
+ assert(w);
+ if(wincmd(w, 0, nil, rcargv) == 0)
+ return nil;
+ return w;
+}
+
+
+void
+drainmouse(Mousectl *mc, WinTab *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 && w->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, int btn)
+{
+ if(w == nil)
+ w = clickwindow(btn, mctl);
+ if(w == nil)
+ setcursoroverride(nil, FALSE);
+ else{
+ Rectangle r = dragrect(btn, w->frame->r, mctl);
+ if((Dx(r) > 0 || Dy(r) > 0) && !eqrect(r, w->frame->r)){
+ 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);
+ }
+
+//TODO(tab): temp hack
+ else{
+ Window *ww = wpointto(r.min);
+ if(w == nil && ww)
+ newtab(ww);
+ }
+}
+
+void
+bandresize(Window *w)
+{
+ Rectangle r;
+ r = bandrect(w->frame->r, mctl->buttons, mctl);
+ if(!eqrect(r, w->frame->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->frame->r) == 0 || Dy(t->frame->r) == 0 || rectXrect(r, t->frame->r) == 0)
+ continue;
+ if(r.min.y < t->frame->r.min.y)
+ if(!obscured(w, Rect(r.min.x, r.min.y, r.max.x, t->frame->r.min.y), t))
+ return 0;
+ if(r.min.x < t->frame->r.min.x)
+ if(!obscured(w, Rect(r.min.x, r.min.y, t->frame->r.min.x, r.max.y), t))
+ return 0;
+ if(r.max.y > t->frame->r.max.y)
+ if(!obscured(w, Rect(r.min.x, t->frame->r.max.y, r.max.x, r.max.y), t))
+ return 0;
+ if(r.max.x > t->frame->r.max.x)
+ if(!obscured(w, Rect(t->frame->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*(bordersz+1)+font->height)
+ return 0;
+//TODO(vdesk) this changes
+ /* 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, bordersz)))
+ 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*bordersz);
+ dy = min(400, Dy(screen->r) - 2*bordersz);
+ minx = 32 + 16*i;
+ miny = 32 + 16*i;
+ i++;
+ i %= 10;
+
+ return Rect(minx, miny, minx+dx, miny+dy);
+}
+
+
+void
+btn2menu(WinTab *w)
+{
+ enum {
+ Cut,
+ Paste,
+ Snarf,
+ Plumb,
+ Look,
+ Send,
+ Scroll
+ };
+ static char *str[] = {
+ "cut",
+ "paste",
+ "snarf",
+ "plumb",
+ "look",
+ "send",
+ "scroll",
+ nil
+ };
+ static Menu menu = { str };
+
+ int sel;
+ Text *x;
+ Cursor *c;
+
+ x = &w->text;
+ str[Scroll] = w->scrolling ? "noscroll" : "scroll";
+ sel = menuhit(2, mctl, &menu, 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)
+{
+ enum {
+ New,
+ Reshape,
+ Move,
+ Delete,
+ Hide,
+ Hidden
+ };
+ static char *str[Hidden+1 + MAXWINDOWS] = {
+ "New",
+ "Resize",
+ "Move",
+ "Delete",
+ "Hide",
+ nil
+ };
+ static Menu menu = { str };
+
+ static Window *hidden[MAXWINDOWS];
+ int nhidden;
+ Window *w, *t;
+ int i, sel;
+
+ nhidden = 0;
+ for(i = 0; i < nwindows; i++){
+ t = windows[i];
+ if(!rectXrect(screen->r, t->frame->r))
+ continue;
+ if(t->hidden || obscured(t, t->frame->r, t->higher)){
+ hidden[nhidden] = windows[i];
+ str[nhidden+Hidden] = windows[i]->cur->label;
+ nhidden++;
+ }
+ }
+ str[nhidden+Hidden] = nil;
+
+ sel = menuhit(3, mctl, &menu, wscreen);
+ switch(sel){
+ case New:
+ sweep(nil);
+ break;
+ case Reshape:
+ w = pick();
+ if(w) sweep(w);
+ break;
+ case Move:
+ grab(nil, 3);
+ break;
+ case Delete:
+ w = pick();
+ if(w) wdelete(w);
+ break;
+ case Hide:
+ w = pick();
+ if(w) whide(w);
+ break;
+ default:
+ if(sel >= Hidden){
+ w = hidden[sel-Hidden];
+ if(w->hidden)
+ wunhide(w);
+ else{
+ wraise(w);
+ wfocus(w);
+ }
+ }
+ break;
+ }
+}
+
+void
+btn13menu(void)
+{
+ enum {
+ RefreshScreen,
+ Scroll,
+ Title,
+ Exit
+ };
+ static char *str[] = {
+ "Refresh",
+ "Scroll",
+ "Title",
+ "Exit",
+ nil
+ };
+ static Menu menu = { str };
+
+ str[Scroll] = scrolling ? "!Scroll" : "Scroll";
+ str[Title] = notitle ? "Title" : "!Title";
+ switch(menuhit(3, mctl, &menu, wscreen)){
+ case RefreshScreen:
+ refresh();
+ break;
+ case Scroll:
+ scrolling = !scrolling;
+ break;
+ case Title:
+ notitle = !notitle;
+ break;
+ case Exit:
+ killprocs();
+ threadexitsall(nil);
+ }
+}
+
+void
+btn12menu(void)
+{
+ int dx, dy, i, j;
+
+ dx = Dx(screen->r);
+ dy = Dy(screen->r);
+ i = screenoff.x/dx;
+ j = screenoff.y/dy;
+ Point ssel = dmenuhit(2, mctl, ndeskx, ndesky, Pt(i,j));
+ if(ssel.x >= 0 && ssel.y >= 0 &&
+ (ssel.x*dx != screenoff.x || ssel.y*dy != screenoff.y))
+ screenoffset(ssel.x*dx, ssel.y*dy);
+}
+
+static void
+wtabctl(Window *w)
+{
+ Rectangle r;
+
+ if(mctl->buttons & 7){
+ wraise(w);
+ wfocus(w);
+ r = w->tabrect;
+ int n = w->ref;
+ if(n > 1){
+ int wd = Dx(r)/n;
+ r.max.x = r.min.x + wd;
+ for(WinTab *t = w->tab; t; t = t->next){
+ if(ptinrect(mctl->xy, r)){
+ if(mctl->buttons & 1){
+ tfocus(t);
+ /* chording */
+ while(mctl->buttons){
+ int b = mctl->buttons;
+ if(b & 6){
+ if(b & 2)
+ tmoveleft(t);
+ else
+ tmoveright(t);
+ }
+ while(mctl->buttons == b)
+ readmouse(mctl);
+ }
+ }else if(mctl->buttons & 2){
+ tdelete(t);
+ while(mctl->buttons)
+ readmouse(mctl);
+ }else if(mctl->buttons & 4){
+ Point pt = mctl->xy;
+ Window *ww = pick();
+ if(ww){
+ /* move tab into clicked window */
+ tmigrate(t, ww);
+ wraise(ww);
+ wfocus(ww);
+ }else{
+ /* HACK: pick doesn't say whether we cancelled
+ * or clicked background */
+ ww = wpointto(mctl->xy);
+ if(ww == nil){
+ r = rectaddpt(w->rect, subpt(mctl->xy, pt));
+ ww = wcreate(r, 0);
+ tmigrate(t, ww);
+ }
+ }
+ return;
+ }
+ break;
+ }
+ r = rectaddpt(r, Pt(wd,0));
+ }
+ }
+ }
+}
+
+void
+mthread(void*)
+{
+ Window *w;
+ Channel *wc;
+
+ threadsetname("mousethread");
+ enum { Amouse, Apick, NALT };
+ Alt alts[NALT+1] = {
+ [Amouse] {.c = mctl->c, .v = &mctl->Mouse, .op = CHANRCV},
+ [Apick] {.c = pickchan, .v = &wc, .op = CHANRCV},
+ [NALT] {.op = CHANEND},
+ };
+ for(;;){
+ // normally done in readmouse
+ Display *d = mctl->image->display;
+ if(d->bufp > d->buf)
+ flushimage(d, 1);
+ switch(alt(alts)){
+ case Apick:
+ sendp(wc, pick());
+ break;
+ case Amouse:
+ w = wpointto(mctl->xy);
+ cursorwin = w;
+again:
+ if(w == nil){
+ /* background */
+ setcursornormal(nil);
+ while(mctl->buttons & 1){
+ if(mctl->buttons & 2)
+ btn12menu();
+ else if(mctl->buttons & 4)
+ btn13menu();
+ readmouse(mctl);
+ }
+ if(mctl->buttons & 4)
+ btn3menu();
+ }else if(!ptinrect(mctl->xy, w->contrect)){
+ /* decoration */
+ if(!w->noborder &&
+ !ptinrect(mctl->xy, insetrect(w->frame->r, bordersz))){
+ /* border */
+ setcursornormal(corners[whichcorner(w->frame->r, mctl->xy)]);
+ if(mctl->buttons & 7){
+ wraise(w);
+ wfocus(w);
+ if(mctl->buttons & 4)
+ grab(w, 3);
+ if(mctl->buttons & 3)
+ bandresize(w);
+ }
+ }else{
+ /* title bar */
+ setcursornormal(nil);
+ if(ptinrect(mctl->xy, w->titlerect))
+ wtitlectl(w);
+ else
+ wtabctl(w);
+ }
+ }else if(w->cur == nil){
+ /* no tab in window */
+ }else if(w != focused){
+ /* inactive window */
+ wsetcursor(w->cur);
+ if(mctl->buttons & 7 ||
+ mctl->buttons & (8|16) && focused && focused->cur->mouseopen){
+ wraise(w);
+ wfocus(w);
+ if(mctl->buttons & 1)
+ drainmouse(mctl, nil);
+ else
+ goto again;
+ }
+ }else if(!w->cur->mouseopen){
+ /* active text window */
+ wsetcursor(w->cur);
+ if(mctl->buttons && topwin != w)
+ wraise(w);
+ if(mctl->buttons & (1|8|16) || ptinrect(mctl->xy, w->cur->text.scrollr))
+ drainmouse(mctl, w->cur);
+ if(mctl->buttons & 2){
+ incref(w->cur);
+ btn2menu(w->cur);
+ wrelease(w->cur);
+ }
+ if(mctl->buttons & 4)
+ btn3menu();
+ }else{
+ /* active graphics window */
+ wsetcursor(w->cur);
+ drainmouse(mctl, w->cur);
+ }
+ }
+ }
+}
+
+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;
+
+ freeimage(fakebg);
+ freescreen(wscreen);
+ wscreen = allocscreen(screen, background, 0);
+ fakebg = allocwindow(wscreen, screen->r, Refbackup, DNofill);
+ draw(fakebg, fakebg->r, background, nil, ZP);
+
+ delta = subpt(nr.min, or.min);
+ for(w = bottomwin; w; w = w->higher){
+ if(w->maximized){
+ wrestore(w);
+ wresize(w, rectaddpt(w->frame->r, delta));
+ wmaximize(w);
+ }else
+ wresize(w, rectaddpt(w->frame->r, delta));
+ }
+
+ flushimage(display, 1);
+ }
+}
+
+void
+refresh(void)
+{
+ Window *w;
+
+ draw(fakebg, fakebg->r, background, nil, ZP);
+ for(w = bottomwin; w; w = w->higher){
+ if(w->maximized){
+ wrestore(w);
+ wresize(w, w->frame->r);
+ wmaximize(w);
+ }else
+ wresize(w, w->frame->r);
+ }
+}
+
+/*
+ * 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;
+ WinTab *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;
+ ctldown = utfrune(s+1, Kctl) != nil;
+ }
+ prev = cur;
+ cur = focused ? focused->cur : nil;
+ 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 && focused && cur == focused->cur)
+ 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
+initcmd(void *arg)
+{
+ char *cmd;
+ char *wsys;
+ int fd;
+
+ cmd = arg;
+ rfork(RFENVG|RFFDG|RFNOTEG|RFNAMEG);
+ wsys = getenv("wsys");
+ fd = open(wsys, ORDWR);
+ if(fd < 0)
+ fprint(2, "lola: failed to open wsys: %r\n");
+ if(mount(fd, -1, "/mnt/wsys", MREPL, "none") < 0)
+ fprint(2, "lola: failed to mount wsys: %r\n");
+ if(bind("/mnt/wsys", "/dev/", MBEFORE) < 0)
+ fprint(2, "lola: failed to bind wsys: %r\n");
+ free(wsys);
+ close(fd);
+ procexecl(nil, "/bin/rc", "rc", "-c", cmd, nil);
+ fprint(2, "lola: exec failed: %r\n");
+ exits("exec");
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: lola [-i initcmd] [-s] [-t]\n");
+ exits("usage");
+}
+
+void
+threadmain(int argc, char *argv[])
+{
+ char *initstr, *s;
+ char buf[256];
+if(strcmp(argv[0]+1, ".out") == 0){
+rfork(RFENVG);
+newwindow("-dx 1280 -dy 800");
+scrolling = TRUE;
+notitle = FALSE;
+oknotes[nelem(oknotes)-2] = "interrupt";
+}
+
+ initstr = nil;
+ ARGBEGIN{
+ case 'i':
+ initstr = EARGF(usage());
+ break;
+ case 's':
+ scrolling = TRUE;
+ break;
+ case 't':
+ notitle = TRUE;
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(getwd(buf, sizeof(buf)) == nil)
+ startdir = estrdup(".");
+ else
+ startdir = estrdup(buf);
+ s = getenv("tabstop");
+ if(s)
+ tabwidth = strtol(s, nil, 0);
+ if(tabwidth == 0)
+ tabwidth = 4;
+ free(s);
+
+ 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);
+
+ pickchan = chancreate(sizeof(Channel*), 0);
+
+ servekbd = kbctl->kbdfd >= 0;
+ snarffd = open("/dev/snarf", OREAD|OCEXEC);
+ gotscreen = access("/dev/screen", AEXIST)==0;
+
+ initdata();
+ /* hack to get menu colors referenced,
+ * so setting them with initstr will work */
+ btn12menu();
+
+ wscreen = allocscreen(screen, background, 0);
+ fakebg = allocwindow(wscreen, screen->r, Refbackup, DNofill);
+ draw(fakebg, fakebg->r, background, nil, ZP);
+
+ timerinit();
+
+
+ threadcreate(mthread, nil, mainstacksize);
+ threadcreate(resthread, nil, mainstacksize);
+ threadcreate(keyboardtap, nil, mainstacksize);
+
+ flushimage(display, 1);
+
+ startfs();
+
+ if(initstr)
+ proccreate(initcmd, initstr, mainstacksize);
+
+ threadnotify(notehandler, 1);
+}
--- /dev/null
+++ b/menuhit.c
@@ -1,0 +1,280 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <mouse.h>
+
+enum
+{
+ Margin = 4, /* outside to text */
+ Border = 2, /* outside to selection boxes */
+ Blackborder = 2, /* width of outlining border */
+ Vspacing = 2, /* extra spacing between lines of text */
+ Maxunscroll = 25, /* maximum #entries before scrolling turns on */
+ Nscroll = 20, /* number entries in scrolling part */
+ Scrollwid = 14, /* width of scroll bar */
+ Gap = 4, /* between text and scroll bar */
+};
+
+static Image *menutxt;
+static Image *back;
+static Image *high;
+static Image *bord;
+static Image *text;
+static Image *htext;
+
+Image *getcolor(char *name, ulong col);
+
+static
+void
+menucolors(void)
+{
+ /* Main tone is greenish, with negative selection */
+ back = getcolor("menuback", 0xEAFFEAFF);
+ high = getcolor("menuhigh", 0x448844FF); /* dark green */
+ bord = getcolor("menubord", 0x88CC88FF); /* not as dark green */
+ text = getcolor("menutext", 0x000000FF);
+ htext = getcolor("menuhtext", 0xEAFFEAFF);
+ if(back==nil || high==nil || bord==nil || text==nil || htext==nil)
+ goto Error;
+ return;
+
+ Error:
+ freeimage(back);
+ freeimage(high);
+ freeimage(bord);
+ freeimage(text);
+ freeimage(htext);
+ back = display->white;
+ high = display->black;
+ bord = display->black;
+ text = display->black;
+ htext = display->white;
+}
+
+/*
+ * r is a rectangle holding the text elements.
+ * return the rectangle, including its black edge, holding element i.
+ */
+static Rectangle
+menurect(Rectangle r, int i)
+{
+ if(i < 0)
+ return Rect(0, 0, 0, 0);
+ r.min.y += (font->height+Vspacing)*i;
+ r.max.y = r.min.y+font->height+Vspacing;
+ return insetrect(r, Border-Margin);
+}
+
+/*
+ * r is a rectangle holding the text elements.
+ * return the element number containing p.
+ */
+static int
+menusel(Rectangle r, Point p)
+{
+ if(!ptinrect(p, r))
+ return -1;
+ return (p.y-r.min.y)/(font->height+Vspacing);
+}
+
+static void
+paintitem(Image *m, Menu *menu, Rectangle textr, int off, int i, int highlight, Image *save, Image *restore)
+{
+ char *item;
+ Rectangle r;
+ Point pt;
+
+ if(i < 0)
+ return;
+ r = menurect(textr, i);
+ if(restore){
+ draw(m, r, restore, nil, restore->r.min);
+ return;
+ }
+ if(save)
+ draw(save, save->r, m, nil, r.min);
+ item = menu->item? menu->item[i+off] : (*menu->gen)(i+off);
+ pt.x = (textr.min.x+textr.max.x-stringwidth(font, item))/2;
+ pt.y = textr.min.y+i*(font->height+Vspacing);
+ draw(m, r, highlight? high : back, nil, pt);
+ string(m, pt, highlight? htext : text, pt, font, item);
+}
+
+/*
+ * menur is a rectangle holding all the highlightable text elements.
+ * track mouse while inside the box, return what's selected when button
+ * is raised, -1 as soon as it leaves box.
+ * invariant: nothing is highlighted on entry or exit.
+ */
+static int
+menuscan(Image *m, Menu *menu, int but, Mousectl *mc, Rectangle textr, int off, int lasti, Image *save)
+{
+ int i;
+
+ paintitem(m, menu, textr, off, lasti, 1, save, nil);
+ for(readmouse(mc); mc->buttons & (1<<(but-1)); readmouse(mc)){
+ i = menusel(textr, mc->xy);
+ if(i != -1 && i == lasti)
+ continue;
+ paintitem(m, menu, textr, off, lasti, 0, nil, save);
+ if(i == -1)
+ return i;
+ lasti = i;
+ paintitem(m, menu, textr, off, lasti, 1, save, nil);
+ }
+ return lasti;
+}
+
+static void
+menupaint(Image *m, Menu *menu, Rectangle textr, int off, int nitemdrawn)
+{
+ int i;
+
+ draw(m, insetrect(textr, Border-Margin), back, nil, ZP);
+ for(i = 0; i<nitemdrawn; i++)
+ paintitem(m, menu, textr, off, i, 0, nil, nil);
+}
+
+static void
+menuscrollpaint(Image *m, Rectangle scrollr, int off, int nitem, int nitemdrawn)
+{
+ Rectangle r;
+
+ draw(m, scrollr, back, nil, ZP);
+ r.min.x = scrollr.min.x;
+ r.max.x = scrollr.max.x;
+ r.min.y = scrollr.min.y + (Dy(scrollr)*off)/nitem;
+ r.max.y = scrollr.min.y + (Dy(scrollr)*(off+nitemdrawn))/nitem;
+ if(r.max.y < r.min.y+2)
+ r.max.y = r.min.y+2;
+ border(m, r, 1, bord, ZP);
+ if(menutxt == 0)
+ menutxt = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DDarkgreen); /* border color; BUG? */
+ if(menutxt)
+ draw(m, insetrect(r, 1), menutxt, nil, ZP);
+}
+
+int
+menuhit(int but, Mousectl *mc, Menu *menu, Screen *scr)
+{
+ int i, nitem, nitemdrawn, maxwid, lasti, off, noff, wid, screenitem;
+ int scrolling;
+ Rectangle r, menur, sc, textr, scrollr;
+ Image *b, *save, *backup;
+ Point pt;
+ char *item;
+
+ if(back == nil)
+ menucolors();
+ sc = screen->clipr;
+ replclipr(screen, 0, screen->r);
+ maxwid = 0;
+ for(nitem = 0;
+ item = menu->item? menu->item[nitem] : (*menu->gen)(nitem);
+ nitem++){
+ i = stringwidth(font, item);
+ if(i > maxwid)
+ maxwid = i;
+ }
+ if(menu->lasthit<0 || menu->lasthit>=nitem)
+ menu->lasthit = 0;
+ screenitem = (Dy(screen->r)-10)/(font->height+Vspacing);
+ if(nitem>Maxunscroll || nitem>screenitem){
+ scrolling = 1;
+ nitemdrawn = Nscroll;
+ if(nitemdrawn > screenitem)
+ nitemdrawn = screenitem;
+ wid = maxwid + Gap + Scrollwid;
+ off = menu->lasthit - nitemdrawn/2;
+ if(off < 0)
+ off = 0;
+ if(off > nitem-nitemdrawn)
+ off = nitem-nitemdrawn;
+ lasti = menu->lasthit-off;
+ }else{
+ scrolling = 0;
+ nitemdrawn = nitem;
+ wid = maxwid;
+ off = 0;
+ lasti = menu->lasthit;
+ }
+ r = insetrect(Rect(0, 0, wid, nitemdrawn*(font->height+Vspacing)), -Margin);
+ r = rectsubpt(r, Pt(wid/2, lasti*(font->height+Vspacing)+font->height/2));
+ r = rectaddpt(r, mc->xy);
+ pt = ZP;
+ if(r.max.x>screen->r.max.x)
+ pt.x = screen->r.max.x-r.max.x;
+ if(r.max.y>screen->r.max.y)
+ pt.y = screen->r.max.y-r.max.y;
+ if(r.min.x<screen->r.min.x)
+ pt.x = screen->r.min.x-r.min.x;
+ if(r.min.y<screen->r.min.y)
+ pt.y = screen->r.min.y-r.min.y;
+ menur = rectaddpt(r, pt);
+ textr.max.x = menur.max.x-Margin;
+ textr.min.x = textr.max.x-maxwid;
+ textr.min.y = menur.min.y+Margin;
+ textr.max.y = textr.min.y + nitemdrawn*(font->height+Vspacing);
+ if(scrolling){
+ scrollr = insetrect(menur, Border);
+ scrollr.max.x = scrollr.min.x+Scrollwid;
+ }else
+ scrollr = Rect(0, 0, 0, 0);
+
+ if(scr){
+ b = allocwindow(scr, menur, Refbackup, DWhite);
+ if(b == nil)
+ b = screen;
+ backup = nil;
+ }else{
+ b = screen;
+ backup = allocimage(display, menur, screen->chan, 0, -1);
+ if(backup)
+ draw(backup, menur, screen, nil, menur.min);
+ }
+ draw(b, menur, back, nil, ZP);
+ border(b, menur, Blackborder, bord, ZP);
+ save = allocimage(display, menurect(textr, 0), screen->chan, 0, -1);
+ r = menurect(textr, lasti);
+ if(pt.x || pt.y)
+ moveto(mc, divpt(addpt(r.min, r.max), 2));
+ menupaint(b, menu, textr, off, nitemdrawn);
+ if(scrolling)
+ menuscrollpaint(b, scrollr, off, nitem, nitemdrawn);
+ while(mc->buttons & (1<<(but-1))){
+ lasti = menuscan(b, menu, but, mc, textr, off, lasti, save);
+ if(lasti >= 0)
+ break;
+ while(!ptinrect(mc->xy, textr) && (mc->buttons & (1<<(but-1)))){
+ if(scrolling && ptinrect(mc->xy, scrollr)){
+ noff = ((mc->xy.y-scrollr.min.y)*nitem)/Dy(scrollr);
+ noff -= nitemdrawn/2;
+ if(noff < 0)
+ noff = 0;
+ if(noff > nitem-nitemdrawn)
+ noff = nitem-nitemdrawn;
+ if(noff != off){
+ off = noff;
+ menupaint(b, menu, textr, off, nitemdrawn);
+ menuscrollpaint(b, scrollr, off, nitem, nitemdrawn);
+ }
+ }
+ readmouse(mc);
+ }
+ }
+ if(b != screen)
+ freeimage(b);
+ if(backup){
+ draw(screen, menur, backup, nil, menur.min);
+ freeimage(backup);
+ }
+ freeimage(save);
+ replclipr(screen, 0, sc);
+ flushimage(display, 1);
+ if(lasti >= 0){
+ menu->lasthit = lasti+off;
+ return menu->lasthit;
+ }
+ return -1;
+}
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,22 @@
+< /$objtype/mkfile
+
+TARG=lola
+OFILES=\
+ main.$O \
+ text.$O \
+ wind.$O \
+ wctl.$O \
+ fs.$O \
+ util.$O \
+ kbd.$O \
+ time.$O \
+ data.$O \
+ menuhit.$O \
+ deskmenu.$O \
+ win95.$O
+
+HFILES=inc.h
+
+BIN=$home/bin/$objtype
+
+< /sys/src/cmd/mkone
--- /dev/null
+++ b/simple.c
@@ -1,0 +1,257 @@
+#include "inc.h"
+
+int bordersz = 4;
+int titlesz = 17;//19;
+int tabsz = 18;
+
+enum {
+ TITLE,
+ LTITLE,
+ TITLEHOLD,
+ LTITLEHOLD,
+ TITLETEXT,
+ LTITLETEXT,
+ TITLEHOLDTEXT,
+ LTITLEHOLDTEXT,
+ FRAME,
+ LFRAME,
+
+ NumWinColors
+};
+
+Image *wincolors[NumWinColors];
+Image *icons[4];
+
+Image *shadecol;
+
+void
+btn(Image *img, Rectangle r, Image *col, Image *icon, int down)
+{
+ USED(down);
+
+ r = centerrect(r, icon->r);
+ draw(img, r, col, icon, ZP);
+}
+
+int
+btnctl(Image *img, Rectangle r, Image *col, Image *icon)
+{
+ int over, prevover;
+
+ prevover = 1;
+ btn(img, r, col, icon, 1);
+ while(mctl->buttons){
+ readmouse(mctl);
+ over = ptinrect(mctl->xy, r);
+ if(over != prevover)
+ btn(img, r, col, icon, over);
+ prevover = over;
+ }
+ if(prevover)
+ btn(img, r, col, icon, 0);
+ return ptinrect(mctl->xy, r);
+}
+
+void
+wdecor(Window *w)
+{
+ if(w->frame == nil)
+ return;
+ int sel = wcolsel(w);
+ int c = TITLE + sel;
+ int c1 = TITLETEXT + sel;
+ int c2 = FRAME + (sel&1);
+
+ Rectangle r, b1, b2, b3;
+ int margin;
+
+ if(!w->noborder){
+ r = w->rect;
+ border(w->frame, r, bordersz, wincolors[c], ZP);
+ border(w->frame, r, 1, wincolors[c2], ZP);
+ }
+
+ if(!w->notitle){
+ r = w->titlerect;
+ draw(w->frame, r, wincolors[c], nil, ZP);
+
+ b1 = r;
+ b1.max.x -= bordersz/2;
+ b1.min.x = b1.max.x - titlesz + bordersz;
+ b1.max.y = b1.min.y + Dx(b1);
+ b2 = rectsubpt(b1, Pt(titlesz, 0));
+ b3 = rectsubpt(b2, Pt(titlesz, 0));
+ btn(w->frame, b1, wincolors[c1], icons[3], 0);
+ btn(w->frame, b2, wincolors[c1], icons[1+w->maximized], 0);
+ btn(w->frame, b3, wincolors[c1], icons[0], 0);
+
+ margin = w->noborder ? titlesz : titlesz + bordersz;
+ margin = (margin - font->height)/2;
+ Point pt = Pt(r.min.x, w->rect.min.y + margin + 1);
+ if(w->cur)
+ string(w->frame, pt, wincolors[c1], pt, font, w->cur->label);
+ }
+ border(w->frame, insetrect(w->contrect,-1), 1, wincolors[c2], ZP);
+
+ r = rectsubpt(w->tabrect, Pt(0,1));
+ draw(w->frame, r, wincolors[c], nil, ZP);
+
+ int n = w->ref;
+ if(n > 1){
+ int wd = Dx(r)/n;
+ int xxx = r.max.x;
+ r.max.x = r.min.x + wd;
+ for(WinTab *t = w->tab; t; t = t->next){
+ if(t->next == nil)
+ r.max.x = xxx;
+ if(t != w->cur)
+ draw(w->frame, r, shadecol, nil, ZP);
+ margin = (tabsz - font->height)/2;
+ Point pt = Pt(r.min.x+bordersz/2, r.min.y + margin);
+ string(w->frame, pt, wincolors[c1], pt, font, t->label);
+ r = rectaddpt(r, Pt(wd,0));
+ }
+ }
+}
+
+void
+wtitlectl(Window *w)
+{
+ if(mctl->buttons & 7){
+ wraise(w);
+ wfocus(w);
+ if(mctl->buttons & 1) {
+ int c = TITLETEXT + wcolsel(w);
+
+ Rectangle r = w->titlerect;
+ r.max.x -= bordersz/2;
+ Rectangle br = Rect(0,0,titlesz-bordersz,titlesz-bordersz);
+ Rectangle br1 = rectaddpt(br, Pt(r.max.x-titlesz+bordersz, r.min.y));
+ Rectangle br2 = rectsubpt(br1, Pt(titlesz, 0));
+ Rectangle br3 = rectsubpt(br2, Pt(titlesz, 0));
+
+ if(ptinrect(mctl->xy, br1)){
+ if(btnctl(w->frame, br1, wincolors[c], icons[3]))
+ wdelete(w);
+ }else if(ptinrect(mctl->xy, br2)){
+ if(btnctl(w->frame, br2, wincolors[c], icons[1+w->maximized])){
+ if(w->maximized)
+ wrestore(w);
+ else
+ wmaximize(w);
+ }
+ }else if(ptinrect(mctl->xy, br3)){
+ if(btnctl(w->frame, br3, wincolors[c], icons[0]))
+ whide(w);
+ }else if(!w->maximized)
+ grab(w, 1);
+ }
+ if(mctl->buttons & 4){
+ Window *ww = pick();
+ if(ww){
+ tmigrate(w->cur, ww);
+ wraise(ww);
+ wfocus(ww);
+ }
+ }
+ }
+}
+
+static char minbtn[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static char maxbtn[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+ 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static char rstbtn[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static char closebtn[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+void
+inittheme(void)
+{
+ freeimage(colors[HOLDTEXT]);
+ freeimage(colors[PALEHOLDTEXT]);
+ colors[HOLDTEXT] = getcolor("holdtext", 0x990000FF);
+ colors[PALEHOLDTEXT] = getcolor("paleholdtext", 0xBB5D00FF);
+
+// wincolors[TITLE] = getcolor("title", 0x607DA1FF);
+// wincolors[LTITLE] = getcolor("ltitle", 0xA1A1A1FF);
+
+// wincolors[TITLE] = getcolor("title", 0x2F78EDFF);
+// wincolors[LTITLE] = getcolor("ltitle", 0x7C9DE3FF);
+
+ wincolors[TITLE] = getcolor("title", 0x5297F9FF);
+ wincolors[LTITLE] = getcolor("ltitle", 0x2C60B2FF);
+ wincolors[TITLEHOLD] = getcolor("titlehold", 0xED2F2FFF);
+ wincolors[LTITLEHOLD] = getcolor("ltitlehold", 0xE36A6AFF);
+
+ wincolors[FRAME] = getcolor("frame", 0x000000FF);
+ wincolors[LFRAME] = getcolor("lframe", 0x000000FF);
+
+ wincolors[TITLETEXT] = getcolor("titletext", 0xFFFFFFFF);
+ wincolors[LTITLETEXT] = getcolor("ltitletext", 0xFFFFFFFF);
+ wincolors[TITLEHOLDTEXT] = getcolor("titleholdtext", 0xFFFFFFFF);
+ wincolors[LTITLEHOLDTEXT] = getcolor("ltitleholdtext", 0xFFFFFFFF);
+
+ icons[0] = mkicon(minbtn, 16, 13);
+ icons[1] = mkicon(maxbtn, 16, 13);
+ icons[2] = mkicon(rstbtn, 16, 13);
+ icons[3] = mkicon(closebtn, 16, 13);
+
+ shadecol = getcolor(nil, 0x00000020);
+}
--- /dev/null
+++ b/text.c
@@ -1,0 +1,987 @@
+#include "inc.h"
+
+enum
+{
+ HiWater = 640000, /* max size of history */
+ LoWater = 400000, /* min size of history after max'ed */
+ MinWater = 20000, /* room to leave available when reallocating */
+};
+
+void
+xinit(Text *x, Rectangle textr, Rectangle scrollr, int tabwidth, Font *ft, Image *b, Image **cols)
+{
+ frclear(x, FALSE);
+ frinit(x, textr, ft, b, cols);
+ x->maxtab = x->maxtab/8*tabwidth;
+ x->i = b;
+ x->scrollr = scrollr;
+ x->lastsr = ZR;
+ defaulttext = cols[TEXT];
+ defaultbg = cols[BACK];
+ xfill(x);
+ xsetselect(x, x->q0, x->q1);
+ xscrdraw(x);
+}
+
+void
+xsetrects(Text *x, Rectangle textr, Rectangle scrollr)
+{
+ frsetrects(x, textr, x->b);
+ x->scrollr = scrollr;
+}
+
+void
+xclear(Text *x)
+{
+ free(x->r);
+ x->r = nil;
+ x->nr = 0;
+ free(x->raw);
+ x->r = nil;
+ x->nraw = 0;
+ frclear(x, TRUE);
+};
+
+void
+xredraw(Text *x)
+{
+ frredraw(x);
+ xscrdraw(x);
+}
+
+static uint term_cols[] = {
+ /* text */
+ [30] = 0x000000FF, // black
+ [31] = 0xFF0000FF, // red
+ [32] = 0x00FF00FF, // green
+ [33] = 0xFFFF00FF, // yellow
+ [34] = 0x0000FFFF, // blue
+ [35] = 0xFF00FFFF, // magenta
+ [36] = 0x00FFFFFF, // cyan
+ [37] = 0xFFFFFFFF, // white
+
+ /* background */
+ [40] = 0x000000FF,
+ [41] = 0xFF0000FF,
+ [42] = 0x00FF00FF,
+ [43] = 0xFFFF00FF,
+ [44] = 0x0000FFFF,
+ [45] = 0xFF00FFFF,
+ [46] = 0x00FFFFFF,
+ [47] = 0xFFFFFFFF,
+};
+
+int rtoi(Rune *r, ulong size){
+ char num[16];
+ int i;
+ for(i = 0; i < size && i < sizeof num; i++){
+ if(r[i] < 128)
+ num[i] = (char) r[i];
+ }
+ num[i] = 0;
+ return atoi(num);
+}
+
+
+uint parsetag(Text *x, uint i, uint *t, uint *b){
+ uint start, code, begin = i;
+ for(; i < x->nchars && x->r[i] != 'm'; i++){
+ if(isdigit(x->r[i])){
+ start = i;
+ while(i < x->nchars && isdigit(x->r[i])) i++;
+ code = rtoi(&x->r[start], i - start);
+ if(code >= 30 && code <= 37) *t = term_cols[code];
+ else if(code >= 40 && code <= 47) *b = term_cols[code];
+ }
+ }
+ return i - begin;
+}
+
+Rune esc[] = { 27 };
+
+void
+xcoldraw(Text *x)
+{
+ uint taglen, start, end, t, b;
+ start = x->org;
+ end = x->org;
+ t = 0; b = 0;
+ Image *textc, *backc;
+ print("org: %d\n", x->org);
+ for(uint i = 0; i < x->nchars; i++){
+ print("i: %d r: %C\n", i, x->r[i]);
+ if(x->r[i] == 27){
+ start = i;
+ if(i + 1 >= x->nchars) break;
+ i++;
+ while(i < x->nchars && x->r[i++] != 27);
+ frdrawsel0(x, frptofchar(x, start), start, i, x->cols[TEXT], x->cols[BACK]);
+ }
+ }
+ print("org: %d nchars: %d\n--------\n", x->org, x->nchars);
+ return;
+}
+
+void
+xfullredraw(Text *x)
+{
+ xfill(x);
+ x->ticked = 0;
+ if(x->p0 > 0)
+ frdrawsel(x, frptofchar(x, 0), 0, x->p0, 0);
+ if(x->p1 < x->nchars)
+ frdrawsel(x, frptofchar(x, x->p1), x->p1, x->nchars, 0);
+ frdrawsel(x, frptofchar(x, x->p0), x->p0, x->p1, 1);
+ x->lastsr = ZR;
+ xscrdraw(x);
+}
+
+uint
+xinsert(Text *w, Rune *r, int n, uint q0)
+{
+ uint m;
+
+ if(n == 0)
+ return q0;
+ if(w->nr+n>HiWater && q0>=w->org && q0>=w->qh){
+ m = min(HiWater-LoWater, min(w->org, w->qh));
+ w->org -= m;
+ w->qh -= m;
+ if(w->q0 > m)
+ w->q0 -= m;
+ else
+ w->q0 = 0;
+ if(w->q1 > m)
+ w->q1 -= m;
+ else
+ w->q1 = 0;
+ w->nr -= m;
+ runemove(w->r, w->r+m, w->nr);
+ q0 -= m;
+ }
+ if(w->nr+n > w->maxr){
+ /*
+ * Minimize realloc breakage:
+ * Allocate at least MinWater
+ * Double allocation size each time
+ * But don't go much above HiWater
+ */
+ m = max(min(2*(w->nr+n), HiWater), w->nr+n)+MinWater;
+ if(m > HiWater)
+ m = max(HiWater+MinWater, w->nr+n);
+ if(m > w->maxr){
+ w->r = runerealloc(w->r, m);
+ w->maxr = m;
+ }
+ }
+ runemove(w->r+q0+n, w->r+q0, w->nr-q0);
+ runemove(w->r+q0, r, n);
+ w->nr += n;
+ /* if output touches, advance selection, not qh; works best for keyboard and output */
+ if(q0 <= w->q1)
+ w->q1 += n;
+ if(q0 <= w->q0)
+ w->q0 += n;
+ if(q0 < w->qh)
+ w->qh += n;
+ if(q0 < w->org)
+ w->org += n;
+ else if(q0 <= w->org+w->nchars)
+ frinsert(w, r, r+n, q0-w->org);
+ xscrdraw(w);
+ return q0;
+}
+
+void
+xfill(Text *w)
+{
+ Rune *rp;
+ int i, n, m, nl;
+
+ while(w->lastlinefull == FALSE){
+ n = w->nr-(w->org+w->nchars);
+ if(n == 0)
+ break;
+ if(n > 2000) /* educated guess at reasonable amount */
+ n = 2000;
+ rp = w->r+(w->org+w->nchars);
+
+ /*
+ * it's expensive to frinsert more than we need, so
+ * count newlines.
+ */
+ nl = w->maxlines-w->nlines;
+ m = 0;
+ for(i=0; i<n; ){
+ if(rp[i++] == '\n'){
+ m++;
+ if(m >= nl)
+ break;
+ }
+ }
+ frinsert(w, rp, rp+i, w->nchars);
+ }
+}
+
+void
+xdelete(Text *w, uint q0, uint q1)
+{
+ uint n, p0, p1;
+
+ n = q1-q0;
+ if(n == 0)
+ return;
+ runemove(w->r+q0, w->r+q1, w->nr-q1);
+ w->nr -= n;
+ if(q0 < w->q0)
+ w->q0 -= min(n, w->q0-q0);
+ if(q0 < w->q1)
+ w->q1 -= min(n, w->q1-q0);
+ if(q1 < w->qh)
+ w->qh -= n;
+ else if(q0 < w->qh)
+ w->qh = q0;
+ if(q1 <= w->org)
+ w->org -= n;
+ else if(q0 < w->org+w->nchars){
+ p1 = q1 - w->org;
+ if(p1 > w->nchars)
+ p1 = w->nchars;
+ if(q0 < w->org){
+ w->org = q0;
+ p0 = 0;
+ }else
+ p0 = q0 - w->org;
+ frdelete(w, p0, p1);
+ xfill(w);
+ }
+}
+
+void
+xsetselect(Text *w, uint q0, uint q1)
+{
+ int p0, p1;
+
+ w->posx = -1;
+ /* w->p0 and w->p1 are always right; w->q0 and w->q1 may be off */
+ w->q0 = q0;
+ w->q1 = q1;
+ /* compute desired p0,p1 from q0,q1 */
+ p0 = q0-w->org;
+ p1 = q1-w->org;
+ if(p0 < 0)
+ p0 = 0;
+ if(p1 < 0)
+ p1 = 0;
+ if(p0 > w->nchars)
+ p0 = w->nchars;
+ if(p1 > w->nchars)
+ p1 = w->nchars;
+ if(p0==w->p0 && p1==w->p1)
+ return;
+ /* screen disagrees with desired selection */
+ if(w->p1<=p0 || p1<=w->p0 || p0==p1 || w->p1==w->p0){
+ /* no overlap or too easy to bother trying */
+ frdrawsel(w, frptofchar(w, w->p0), w->p0, w->p1, 0);
+ frdrawsel(w, frptofchar(w, p0), p0, p1, 1);
+ goto Return;
+ }
+ /* overlap; avoid unnecessary painting */
+ if(p0 < w->p0){
+ /* extend selection backwards */
+ frdrawsel(w, frptofchar(w, p0), p0, w->p0, 1);
+ }else if(p0 > w->p0){
+ /* trim first part of selection */
+ frdrawsel(w, frptofchar(w, w->p0), w->p0, p0, 0);
+ }
+ if(p1 > w->p1){
+ /* extend selection forwards */
+ frdrawsel(w, frptofchar(w, w->p1), w->p1, p1, 1);
+ }else if(p1 < w->p1){
+ /* trim last part of selection */
+ frdrawsel(w, frptofchar(w, p1), p1, w->p1, 0);
+ }
+
+ Return:
+ w->p0 = p0;
+ w->p1 = p1;
+}
+
+static void
+xsetorigin(Text *w, uint org, int exact)
+{
+ int i, a, fixup;
+ Rune *r;
+ uint n;
+
+ if(org>0 && !exact){
+ /* org is an estimate of the char posn; find a newline */
+ /* don't try harder than 256 chars */
+ for(i=0; i<256 && org<w->nr; i++){
+ if(w->r[org] == '\n'){
+ org++;
+ break;
+ }
+ org++;
+ }
+ }
+ a = org-w->org;
+ fixup = 0;
+ if(a>=0 && a<w->nchars){
+ frdelete(w, 0, a);
+ fixup = 1; /* frdelete can leave end of last line in wrong selection mode; it doesn't know what follows */
+ }else if(a<0 && -a<w->nchars){
+ n = w->org - org;
+ r = w->r+org;
+ frinsert(w, r, r+n, 0);
+ }else
+ frdelete(w, 0, w->nchars);
+ w->org = org;
+ xfill(w);
+ xscrdraw(w);
+ xsetselect(w, w->q0, w->q1);
+ if(fixup && w->p1 > w->p0)
+ frdrawsel(w, frptofchar(w, w->p1-1), w->p1-1, w->p1, 1);
+}
+
+
+/*
+ * Scrolling
+ */
+
+static Rectangle
+scrpos(Rectangle r, uint p0, uint p1, uint tot)
+{
+ Rectangle q;
+ int h;
+
+ q = r;
+ h = q.max.y-q.min.y;
+ if(tot == 0)
+ return q;
+ if(tot > 1024*1024){
+ tot>>=10;
+ p0>>=10;
+ p1>>=10;
+ }
+ if(p0 > 0)
+ q.min.y += h*p0/tot;
+ if(p1 < tot)
+ q.max.y -= h*(tot-p1)/tot;
+ if(q.max.y < q.min.y+2){
+ if(q.min.y+2 <= r.max.y)
+ q.max.y = q.min.y+2;
+ else
+ q.min.y = q.max.y-2;
+ }
+ return q;
+}
+
+void
+xscrdraw(Text *w)
+{
+ Rectangle r1, r2;
+
+ if(w->i == nil)
+ return;
+
+ r1 = w->scrollr;
+ r2 = scrpos(r1, w->org, w->org+w->nchars, w->nr);
+ if(!eqrect(r2, w->lastsr)){
+ w->lastsr = r2;
+ draw(w->i, r1, w->cols[BORD], nil, ZP);
+ draw(w->i, insetrect(r2,1), w->cols[BACK], nil, ZP);
+ }
+
+ xcoldraw(w);
+}
+
+static uint
+xbacknl(Text *w, uint p, uint n)
+{
+ int i, j;
+
+ /* look for start of this line if n==0 */
+ if(n==0 && p>0 && w->r[p-1]!='\n')
+ n = 1;
+ i = n;
+ while(i-->0 && p>0){
+ --p; /* it's at a newline now; back over it */
+ if(p == 0)
+ break;
+ /* at 128 chars, call it a line anyway */
+ for(j=128; --j>0 && p>0; p--)
+ if(w->r[p-1]=='\n')
+ break;
+ }
+ return p;
+}
+
+static void
+xscrsleep(Mousectl *mc, uint dt)
+{
+ Timer *timer;
+ int y, b;
+ static Alt alts[3];
+
+ if(display->bufp > display->buf)
+ flushimage(display, 1);
+ timer = timerstart(dt);
+ y = mc->xy.y;
+ b = mc->buttons;
+ alts[0] = ALT(timer->c, nil, CHANRCV);
+ alts[1] = ALT(mc->c, &mc->Mouse, CHANRCV);
+ alts[2].op = CHANEND;
+ for(;;)
+ switch(alt(alts)){
+ case 0:
+ timerstop(timer);
+ return;
+ case 1:
+ if(abs(mc->xy.y-y)>2 || mc->buttons!=b){
+ timercancel(timer);
+ return;
+ }
+ break;
+ }
+}
+
+void
+xscroll(Text *w, Mousectl *mc, int but)
+{
+ uint p0, oldp0;
+ Rectangle s;
+ int y, my, h, first;
+
+ s = insetrect(w->scrollr, 1);
+ h = s.max.y-s.min.y;
+ oldp0 = ~0;
+ first = TRUE;
+ do{
+ my = mc->xy.y;
+ if(my < s.min.y)
+ my = s.min.y;
+ if(my >= s.max.y)
+ my = s.max.y;
+ if(but == 2){
+ y = my;
+ if(y > s.max.y-2)
+ y = s.max.y-2;
+ if(w->nr > 1024*1024)
+ p0 = ((w->nr>>10)*(y-s.min.y)/h)<<10;
+ else
+ p0 = w->nr*(y-s.min.y)/h;
+ if(oldp0 != p0)
+ xsetorigin(w, p0, FALSE);
+ oldp0 = p0;
+ readmouse(mc);
+ continue;
+ }
+ if(but == 1 || but == 4){
+ y = max(1, (my-s.min.y)/w->font->height);
+ p0 = xbacknl(w, w->org, y);
+ }else{
+ y = max(my, s.min.y+w->font->height);
+ p0 = w->org+frcharofpt(w, Pt(s.max.x, y));
+ }
+ if(oldp0 != p0)
+ xsetorigin(w, p0, TRUE);
+ oldp0 = p0;
+ /* debounce */
+ if(first){
+ if(display->bufp > display->buf)
+ flushimage(display, 1);
+ if(but > 3)
+ return;
+ sleep(200);
+ nbrecv(mc->c, &mc->Mouse);
+ first = FALSE;
+ }
+ xscrsleep(mc, 100);
+ }while(mc->buttons & (1<<(but-1)));
+ while(mc->buttons)
+ readmouse(mc);
+}
+
+void
+xscrolln(Text *x, int n)
+{
+ uint q0;
+
+ if(n < 0)
+ q0 = xbacknl(x, x->org, -n);
+ else
+ q0 = x->org+frcharofpt(x, Pt(x->Frame.r.min.x, x->Frame.r.min.y+n*x->font->height));
+ xsetorigin(x, q0, TRUE);
+}
+
+static Text *selecttext;
+static Mousectl *selectmc;
+static uint selectq;
+
+static void
+xframescroll(Text *x, int dl)
+{
+ uint endq;
+
+ if(dl == 0){
+ xscrsleep(selectmc, 100);
+ return;
+ }
+ if(dl < 0){
+ endq = x->org+x->p0;
+ }else{
+ if(x->org+x->nchars == x->nr)
+ return;
+ endq = x->org+x->p1;
+ }
+ xscrolln(x, dl);
+ xsetselect(x, min(selectq, endq), max(selectq, endq));
+}
+
+static void
+framescroll(Frame *f, int dl)
+{
+ if(f != &selecttext->Frame)
+ panic("frameselect not right frame");
+ xframescroll(selecttext, dl);
+}
+
+/*
+ * Selection and deletion helpers
+ */
+
+int
+iswordrune(Rune r)
+{
+ return r == '_' || isalpharune(r) || isdigitrune(r);
+}
+
+static int
+xbswidth(Text *w, Rune c)
+{
+ uint q, stop;
+ Rune r;
+ int wd, inword;
+
+ /* there is known to be at least one character to erase */
+ if(c == Kbs) /* ^H: erase character */
+ return 1;
+ q = w->q0;
+ stop = 0;
+ if(q > w->qh)
+ stop = w->qh;
+ inword = FALSE;
+ while(q > stop){
+ r = w->r[q-1];
+ if(r == '\n'){ /* eat at most one more character */
+ if(q == w->q0) /* eat the newline */
+ --q;
+ break;
+ }
+ /* ^W: erase word.
+ * delete a bunch of non-word characters
+ * followed by word characters */
+ if(c == CTRL('W')){
+ wd = iswordrune(r);
+ if(wd && !inword)
+ inword = TRUE;
+ else if(!wd && inword)
+ break;
+ }
+ --q;
+ }
+ return w->q0-q;
+}
+
+static Rune left1[] = { L'{', L'[', L'(', L'<', L'«', 0 };
+static Rune right1[] = { L'}', L']', L')', L'>', L'»', 0 };
+static Rune left2[] = { L'\n', 0 };
+static Rune left3[] = { L'\'', L'"', L'`', 0 };
+
+static Rune *left[] = {
+ left1,
+ left2,
+ left3,
+ nil
+};
+static Rune *right[] = {
+ right1,
+ left2,
+ left3,
+ nil
+};
+
+static int
+xclickmatch(Text *x, int cl, int cr, int dir, uint *q)
+{
+ Rune c;
+ int nest;
+
+ nest = 1;
+ for(;;){
+ if(dir > 0){
+ if(*q == x->nr)
+ break;
+ c = x->r[*q];
+ (*q)++;
+ }else{
+ if(*q == 0)
+ break;
+ (*q)--;
+ c = x->r[*q];
+ }
+ if(c == cr){
+ if(--nest==0)
+ return 1;
+ }else if(c == cl)
+ nest++;
+ }
+ return cl=='\n' && nest==1;
+}
+
+static int
+inmode(Rune r, int mode)
+{
+ return (mode == 1) ? iswordrune(r) : r && !isspacerune(r);
+}
+
+static void
+xstretchsel(Text *x, uint pt, uint *q0, uint *q1, int mode)
+{
+ int c, i;
+ Rune *r, *l, *p;
+ uint q;
+
+ *q0 = pt;
+ *q1 = pt;
+ for(i=0; left[i]!=nil; i++){
+ q = *q0;
+ l = left[i];
+ r = right[i];
+ /* try matching character to left, looking right */
+ if(q == 0)
+ c = '\n';
+ else
+ c = x->r[q-1];
+ p = runestrchr(l, c);
+ if(p != nil){
+ if(xclickmatch(x, c, r[p-l], 1, &q))
+ *q1 = q-(c!='\n');
+ return;
+ }
+ /* try matching character to right, looking left */
+ if(q == x->nr)
+ c = '\n';
+ else
+ c = x->r[q];
+ p = runestrchr(r, c);
+ if(p != nil){
+ if(xclickmatch(x, c, l[p-r], -1, &q)){
+ *q1 = *q0+(*q0<x->nr && c=='\n');
+ *q0 = q;
+ if(c!='\n' || q!=0 || x->r[0]=='\n')
+ (*q0)++;
+ }
+ return;
+ }
+ }
+ /* try filling out word to right */
+ while(*q1<x->nr && inmode(x->r[*q1], mode))
+ (*q1)++;
+ /* try filling out word to left */
+ while(*q0>0 && inmode(x->r[*q0-1], mode))
+ (*q0)--;
+}
+
+static Mouse lastclick;
+static Text *clickfrm;
+static uint clickcount;
+
+/* should be called with button 1 down */
+void
+xselect(Text *x, Mousectl *mc)
+{
+ uint q0, q1;
+ int dx, dy, dt, b;
+
+ /* reset click state if mouse is too different from last time */
+ dx = abs(mc->xy.x - lastclick.xy.x);
+ dy = abs(mc->xy.y - lastclick.xy.y);
+ dt = mc->msec - lastclick.msec;
+ if(x != clickfrm || dx > 3 || dy > 3 || dt >= 500)
+ clickcount = 0;
+
+ /* first button down can be a dragging selection or a click.
+ * subsequent buttons downs can only be clicks.
+ * both cases can be ended by chording. */
+ selectq = x->org+frcharofpt(x, mc->xy);
+ if(clickcount == 0){
+ /* what a kludge - can this be improved? */
+ selecttext = x;
+ selectmc = mc;
+ x->scroll = framescroll;
+ frselect(x, mc);
+ /* this is correct if the whole selection is visible */
+ q0 = x->org + x->p0;
+ q1 = x->org + x->p1;
+ /* otherwise replace one end with selectq */
+ if(selectq < x->org)
+ q0 = selectq;
+ if(selectq > x->org+x->nchars)
+ q1 = selectq;
+ xsetselect(x, q0, q1);
+
+ /* figure out whether it was a click */
+ if(q0 == q1 && mc->buttons == 0){
+ clickcount = 1;
+ clickfrm = x;
+ }
+ }else{
+ clickcount++;
+ xstretchsel(x, selectq, &q0, &q1, min(clickcount-1, 2));
+ xsetselect(x, q0, q1);
+ if(clickcount >= 3)
+ clickcount = 0;
+ b = mc->buttons;
+ while(mc->buttons == b)
+ readmouse(mc);
+ }
+ lastclick = mc->Mouse; /* a bit unsure if this is correct */
+
+ /* chording */
+ while(mc->buttons){
+ clickcount = 0;
+ b = mc->buttons;
+ if(b & 6){
+ if(b & 2){
+ xsnarf(x);
+ xcut(x);
+ }else{
+ xpaste(x);
+ }
+ }
+ while(mc->buttons == b)
+ readmouse(mc);
+ }
+}
+
+void
+xshow(Text *w, uint q0)
+{
+ int qe;
+ int nl;
+ uint q;
+
+ qe = w->org+w->nchars;
+ if(w->org<=q0 && (q0<qe || (q0==qe && qe==w->nr)))
+ xscrdraw(w);
+ else{
+ nl = 4*w->maxlines/5;
+ q = xbacknl(w, q0, nl);
+ /* avoid going backwards if trying to go forwards - long lines! */
+ if(!(q0>w->org && q<w->org))
+ xsetorigin(w, q, TRUE);
+ while(q0 > w->org+w->nchars)
+ xsetorigin(w, w->org+1, FALSE);
+ }
+}
+
+void
+xplacetick(Text *x, uint q)
+{
+ xsetselect(x, q, q);
+ xshow(x, q);
+}
+
+void
+xtype(Text *x, Rune r)
+{
+ uint q0, q1;
+ int nb;
+
+ xsnarf(x);
+ xcut(x);
+ switch(r){
+ case CTRL('H'): /* erase character */
+ case CTRL('W'): /* erase word */
+ case CTRL('U'): /* erase line */
+ if(x->q0==0 || x->q0==x->qh)
+ return;
+ nb = xbswidth(x, r);
+ q1 = x->q0;
+ q0 = q1-nb;
+ if(q0 < x->org){
+ q0 = x->org;
+ nb = q1-q0;
+ }
+ if(nb > 0){
+ xdelete(x, q0, q0+nb);
+ xsetselect(x, q0, q0);
+ }
+ break;
+ default:
+ xinsert(x, &r, 1, x->q0);
+ xshow(x, x->q0);
+ break;
+ }
+}
+
+int
+xninput(Text *x)
+{
+ uint q;
+ Rune r;
+
+ for(q = x->qh; q < x->nr; q++){
+ r = x->r[q];
+ if(r == '\n')
+ return q - x->qh + 1;
+ if(r == CTRL('D'))
+ return q - x->qh;
+ }
+ return -1;
+}
+
+void
+xaddraw(Text *x, Rune *r, int nr)
+{
+ x->raw = runerealloc(x->raw, x->nraw+nr);
+ runemove(x->raw+x->nraw, r, nr);
+ x->nraw += nr;
+}
+
+/* TODO: maybe pass what we're looking for in a string */
+void
+xlook(Text *x)
+{
+ int i, n, e;
+
+ i = x->q1;
+ n = i - x->q0;
+ e = x->nr - n;
+ if(n <= 0 || e < n)
+ return;
+
+ if(i > e)
+ i = 0;
+
+ while(runestrncmp(x->r+x->q0, x->r+i, n) != 0){
+ if(i < e)
+ i++;
+ else
+ i = 0;
+ }
+
+ xsetselect(x, i, i+n);
+ xshow(x, i);
+}
+
+Rune *snarf;
+int nsnarf;
+int snarfversion;
+int snarffd;
+
+void
+xsnarf(Text *x)
+{
+ if(x->q1 == x->q0)
+ return;
+ nsnarf = x->q1-x->q0;
+ snarf = runerealloc(snarf, nsnarf);
+ snarfversion++;
+ runemove(snarf, x->r+x->q0, nsnarf);
+ putsnarf();
+}
+
+void
+xcut(Text *x)
+{
+ if(x->q1 == x->q0)
+ return;
+ xdelete(x, x->q0, x->q1);
+ xsetselect(x, x->q0, x->q0);
+ xscrdraw(x);
+}
+
+void
+xpaste(Text *x)
+{
+ uint q0;
+
+ getsnarf();
+ if(nsnarf == 0)
+ return;
+ xcut(x);
+ q0 = x->q0;
+ if(x->rawmode && q0==x->nr){
+ xaddraw(x, snarf, nsnarf);
+ xsetselect(x, q0, q0);
+ }else{
+ q0 = xinsert(x, snarf, nsnarf, x->q0);
+ xsetselect(x, q0, q0+nsnarf);
+ }
+ xscrdraw(x);
+}
+
+void
+xsend(Text *x)
+{
+ getsnarf();
+ xsnarf(x);
+ if(nsnarf == 0)
+ return;
+ if(x->rawmode){
+ xaddraw(x, snarf, nsnarf);
+ if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!=CTRL('D'))
+ xaddraw(x, L"\n", 1);
+ }else{
+ xinsert(x, snarf, nsnarf, x->nr);
+ if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!=CTRL('D'))
+ xinsert(x, L"\n", 1, x->nr);
+ }
+ xplacetick(x, x->nr);
+}
+
+int
+xplumb(Text *w, char *src, char *dir, int maxsize)
+{
+ Plumbmsg *m;
+ static int fd = -2;
+ char buf[32];
+ uint p0, p1;
+
+ if(fd == -2)
+ fd = plumbopen("send", OWRITE|OCEXEC);
+ if(fd < 0)
+ return 0;
+ m = emalloc(sizeof(Plumbmsg));
+ m->src = estrdup(src);
+ m->dst = nil;
+ m->wdir = estrdup(dir);
+ m->type = estrdup("text");
+ p0 = w->q0;
+ p1 = w->q1;
+ if(w->q1 > w->q0)
+ m->attr = nil;
+ else{
+ while(p0>0 && w->r[p0-1]!=' ' && w->r[p0-1]!='\t' && w->r[p0-1]!='\n')
+ p0--;
+ while(p1<w->nr && w->r[p1]!=' ' && w->r[p1]!='\t' && w->r[p1]!='\n')
+ p1++;
+ snprint(buf, sizeof(buf), "click=%d", w->q0-p0);
+ m->attr = plumbunpackattr(buf);
+ }
+ if(p1-p0 > maxsize){
+ plumbfree(m);
+ return 0; /* too large for 9P */
+ }
+ m->data = smprint("%.*S", p1-p0, w->r+p0);
+ m->ndata = strlen(m->data);
+ if(plumbsend(fd, m) < 0){
+ plumbfree(m);
+ return 1;
+ }
+ plumbfree(m);
+ return 0;
+}
--- /dev/null
+++ b/time.c
@@ -1,0 +1,114 @@
+#include "inc.h"
+
+/* taken from rio */
+
+static Channel* ctimer; /* chan(Timer*)[100] */
+static Timer *timer;
+
+static uint
+msec(void)
+{
+ return nsec()/1000000;
+}
+
+void
+timerstop(Timer *t)
+{
+ t->next = timer;
+ timer = t;
+}
+
+void
+timercancel(Timer *t)
+{
+ t->cancel = TRUE;
+}
+
+static void
+timerproc(void*)
+{
+ int i, nt, na, dt, del;
+ Timer **t, *x;
+ uint old, new;
+
+ rfork(RFFDG);
+ threadsetname("TIMERPROC");
+ t = nil;
+ na = 0;
+ nt = 0;
+ old = msec();
+ for(;;){
+ sleep(1); /* will sleep minimum incr */
+ new = msec();
+ dt = new-old;
+ old = new;
+ if(dt < 0) /* timer wrapped; go around, losing a tick */
+ continue;
+ for(i=0; i<nt; i++){
+ x = t[i];
+ x->dt -= dt;
+ del = 0;
+ if(x->cancel){
+ timerstop(x);
+ del = 1;
+ }else if(x->dt <= 0){
+ /*
+ * avoid possible deadlock if client is
+ * now sending on ctimer
+ */
+ if(nbsendul(x->c, 0) > 0)
+ del = 1;
+ }
+ if(del){
+ memmove(&t[i], &t[i+1], (nt-i-1)*sizeof t[0]);
+ --nt;
+ --i;
+ }
+ }
+ if(nt == 0){
+ x = recvp(ctimer);
+ gotit:
+ if(nt == na){
+ na += 10;
+ t = realloc(t, na*sizeof(Timer*));
+ if(t == nil)
+ abort();
+ }
+ t[nt++] = x;
+ old = msec();
+ }
+ if(nbrecv(ctimer, &x) > 0)
+ goto gotit;
+ }
+}
+
+void
+timerinit(void)
+{
+ ctimer = chancreate(sizeof(Timer*), 100);
+ proccreate(timerproc, nil, mainstacksize);
+}
+
+/*
+ * timeralloc() and timerfree() don't lock, so can only be
+ * called from the main proc.
+ */
+
+Timer*
+timerstart(int dt)
+{
+ Timer *t;
+
+ t = timer;
+ if(t)
+ timer = timer->next;
+ else{
+ t = emalloc(sizeof(Timer));
+ t->c = chancreate(sizeof(int), 0);
+ }
+ t->next = nil;
+ t->dt = dt;
+ t->cancel = FALSE;
+ sendp(ctimer, t);
+ return t;
+}
--- /dev/null
+++ b/util.c
@@ -1,0 +1,194 @@
+#include "inc.h"
+
+/* Center rect s in rect r */
+Rectangle
+centerrect(Rectangle r, Rectangle s)
+{
+ int dx = (Dx(r) - Dx(s))/2;
+ int dy = (Dy(r) - Dy(s))/2;
+ return rectaddpt(Rect(0, 0, Dx(s), Dy(s)), Pt(r.min.x+dx, r.min.y+dy));
+}
+
+void
+borderTL(Image *img, Rectangle r, Image *c)
+{
+ // left
+ draw(img, Rect(r.min.x, r.min.y, r.min.x+1, r.max.y),
+ c, nil, ZP);
+ // top
+ draw(img, Rect(r.min.x, r.min.y, r.max.x, r.min.y+1),
+ c, nil, ZP);
+}
+
+void
+borderBR(Image *img, Rectangle r, Image *c)
+{
+ // bottom
+ draw(img, Rect(r.min.x, r.max.y-1, r.max.x, r.max.y),
+ c, nil, ZP);
+ // right
+ draw(img, Rect(r.max.x-1, r.min.y, r.max.x, r.max.y),
+ c, nil, ZP);
+}
+
+void
+winborder(Image *img, Rectangle r, Image *c1, Image *c2)
+{
+ borderTL(img, r, c1);
+ borderBR(img, r, c2);
+}
+
+
+void
+panic(char *s)
+{
+ fprint(2, "error: %s: %r\n", s);
+ threadexitsall("error");
+}
+
+void*
+emalloc(ulong size)
+{
+ void *p;
+
+ p = malloc(size);
+ if(p == nil)
+ panic("malloc failed");
+ memset(p, 0, size);
+ return p;
+}
+
+void*
+erealloc(void *p, ulong size)
+{
+ p = realloc(p, size);
+ if(p == nil)
+ panic("realloc failed");
+ return p;
+}
+
+char*
+estrdup(char *s)
+{
+ char *p;
+
+ p = malloc(strlen(s)+1);
+ if(p == nil)
+ panic("strdup failed");
+ strcpy(p, s);
+ return p;
+}
+
+/* Handle backspaces in a rune string.
+ * Set number of final runes,
+ * return number of runes to be deleted initially */
+int
+handlebs(Stringpair *pair)
+{
+ int initial;
+ Rune *start, *rp, *wp;
+ int i;
+
+ initial = 0;
+ start = rp = wp = pair->s;
+ for(i = 0; i < pair->ns; i++){
+ if(*rp == '\b'){
+ if(wp == start)
+ initial++;
+ else
+ wp--;
+ }else
+ *wp++ = *rp;
+ rp++;
+ }
+ pair->ns = wp - start;
+ return initial;
+}
+
+
+void
+cnvsize(RuneConvBuf *cnv, int nb)
+{
+ cnv->nb = nb;
+ if(cnv->maxbuf < nb+UTFmax){
+ cnv->maxbuf = nb+UTFmax;
+ cnv->buf = erealloc(cnv->buf, cnv->maxbuf);
+ }
+}
+
+int
+r2bfill(RuneConvBuf *cnv, Rune *rp, int nr)
+{
+ int i;
+ for(i = 0; cnv->n < cnv->nb && i < nr; i++)
+ cnv->n += runetochar(&cnv->buf[cnv->n], &rp[i]);
+ return i;
+}
+void
+r2bfinish(RuneConvBuf *cnv, Stringpair *pair)
+{
+ int nb;
+
+ nb = pair->ns;
+ pair->ns = min(nb, cnv->n);
+ memmove(pair->s, cnv->buf, pair->ns);
+ cnv->n = max(0, cnv->n-nb);
+ memmove(cnv->buf, cnv->buf+nb, cnv->n);
+}
+
+// TODO: not sure about the signature of this...
+// maybe pass in allocated pair?
+// don't include null runes
+Stringpair
+b2r(RuneConvBuf *cnv)
+{
+ Stringpair pair;
+ Rune *rp;
+ int i;
+
+ rp = runemalloc(cnv->n);
+ pair.s = rp;
+ pair.ns = 0;
+ i = 0;
+ // TODO: optimize this
+ // we know there are full runes until the end
+ while(fullrune(cnv->buf+i, cnv->n-i)){
+ i += chartorune(rp, cnv->buf+i);
+ if(*rp){
+ rp++;
+ pair.ns++;
+ }
+ }
+ memmove(cnv->buf, cnv->buf+i, cnv->n-i);
+ cnv->n -= i;
+
+ return pair;
+}
+
+int
+qadd(Queue *q, char *data)
+{
+ if(q->full)
+ return 0;
+ q->q[q->wi++] = data;
+ q->wi %= nelem(q->q);
+ q->full = q->wi == q->ri;
+ return 1;
+}
+
+char*
+qget(Queue *q)
+{
+ char *data;
+
+ data = q->q[q->ri++];
+ q->ri %= nelem(q->q);
+ q->full = FALSE;
+ return data;
+}
+
+int
+qempty(Queue *q)
+{
+ return q->ri == q->wi && !q->full;
+}
--- /dev/null
+++ b/wctl.c
@@ -1,0 +1,452 @@
+#include "inc.h"
+
+/*
+ * TODO: i feel like this could use some cleanup
+ */
+
+char Ebadwr[] = "bad rectangle in wctl request";
+char Ewalloc[] = "window allocation failed in wctl request";
+
+/* >= Top are disallowed if mouse button is pressed.
+ * > New need a window. */
+enum
+{
+ Screenoffset,
+ Screenrefresh,
+ New,
+ Newtab,
+ Resize,
+ Move,
+ Scroll,
+ Noscroll,
+ Border,
+ Noborder,
+ Title,
+ Notitle,
+ Sticky,
+ Nosticky,
+ Set,
+ Top,
+ Bottom,
+ Current,
+ Hide,
+ Unhide,
+ Delete,
+};
+
+static char *cmds[] = {
+ [Screenoffset] = "screenoffset",
+ [Screenrefresh] = "refresh",
+ [New] = "new",
+ [Newtab] = "newtab",
+ [Resize] = "resize",
+ [Move] = "move",
+ [Scroll] = "scroll",
+ [Noscroll] = "noscroll",
+ [Border] = "border",
+ [Noborder] = "noborder",
+ [Title] = "title",
+ [Notitle] = "notitle",
+ [Sticky] = "sticky",
+ [Nosticky] = "nosticky",
+ [Set] = "set",
+ [Top] = "top",
+ [Bottom] = "bottom",
+ [Current] = "current",
+ [Hide] = "hide",
+ [Unhide] = "unhide",
+ [Delete] = "delete",
+ nil
+};
+
+enum
+{
+ Cd,
+ Deltax,
+ Deltay,
+ Hidden,
+ Id,
+ Maxx,
+ Maxy,
+ Minx,
+ Miny,
+ PID,
+ R,
+ Scrolling,
+ Noscrolling,
+};
+
+static char *params[] = {
+ [Cd] = "-cd",
+ [Deltax] = "-dx",
+ [Deltay] = "-dy",
+ [Hidden] = "-hide",
+ [Id] = "-id",
+ [Maxx] = "-maxx",
+ [Maxy] = "-maxy",
+ [Minx] = "-minx",
+ [Miny] = "-miny",
+ [PID] = "-pid",
+ [R] = "-r",
+ [Scrolling] = "-scroll",
+ [Noscrolling] = "-noscroll",
+ nil
+};
+
+static int
+word(char **sp, char *tab[])
+{
+ char *s, *t;
+ int i;
+
+ s = *sp;
+ while(isspacerune(*s))
+ s++;
+ t = s;
+ while(*s!='\0' && !isspacerune(*s))
+ s++;
+ for(i=0; tab[i]!=nil; i++)
+ if(s-t > 0 && strncmp(tab[i], t, s-t) == 0){
+ *sp = s;
+ return i;
+ }
+ return -1;
+}
+
+int
+set(int sign, int neg, int abs, int pos)
+{
+ if(sign < 0)
+ return neg;
+ if(sign > 0)
+ return pos;
+ return abs;
+}
+
+void
+shift(int *minp, int *maxp, int min, int max)
+{
+ if(*maxp > max){
+ *minp += max-*maxp;
+ *maxp = max;
+ }
+ if(*minp < min){
+ *maxp += min-*minp;
+ if(*maxp > max)
+ *maxp = max;
+ *minp = min;
+ }
+}
+
+Rectangle
+rectonscreen(Rectangle r)
+{
+//TODO(vdesk) this changes
+return r;
+ shift(&r.min.x, &r.max.x, screen->r.min.x, screen->r.max.x);
+ shift(&r.min.y, &r.max.y, screen->r.min.y, screen->r.max.y);
+ return r;
+}
+
+/* permit square brackets, in the manner of %R */
+int
+riostrtol(char *s, char **t)
+{
+ int n;
+
+ while(*s!='\0' && (*s==' ' || *s=='\t' || *s=='['))
+ s++;
+ if(*s == '[')
+ s++;
+ n = strtol(s, t, 10);
+ if(*t != s)
+ while((*t)[0] == ']')
+ (*t)++;
+ return n;
+}
+
+Wctlcmd
+parsewctl(char *s, Rectangle r)
+{
+ Wctlcmd cmd;
+
+ int n, nt, param, xy, sign;
+ char *f[2], *t;
+
+ cmd.id = 0;
+ cmd.pid = 0;
+ cmd.hidden = FALSE;
+ cmd.scrolling = scrolling;
+ cmd.dir = nil;
+ cmd.error = nil;
+ cmd.cmd = word(&s, cmds);
+ if(cmd.cmd < 0)
+ goto Lose;
+ switch(cmd.cmd){
+ case Screenoffset:
+ r = ZR;
+ break;
+ case New:
+ r = newrect();
+ break;
+ }
+
+ while((param = word(&s, params)) >= 0){
+ switch(param){ /* special cases */
+ case Hidden:
+ cmd.hidden = TRUE;
+ continue;
+ case Scrolling:
+ cmd.scrolling = TRUE;
+ continue;
+ case Noscrolling:
+ cmd.scrolling = FALSE;
+ continue;
+ case R:
+ r.min.x = riostrtol(s, &t);
+ if(t == s)
+ goto Lose;
+ s = t;
+ r.min.y = riostrtol(s, &t);
+ if(t == s)
+ goto Lose;
+ s = t;
+ r.max.x = riostrtol(s, &t);
+ if(t == s)
+ goto Lose;
+ s = t;
+ r.max.y = riostrtol(s, &t);
+ if(t == s)
+ goto Lose;
+ s = t;
+ continue;
+ }
+ while(isspacerune(*s))
+ s++;
+ if(param == Cd){
+ cmd.dir = s;
+ if((nt = gettokens(cmd.dir, f, nelem(f), " \t\r\n\v\f")) < 1)
+ goto Lose;
+ n = strlen(cmd.dir);
+ if(cmd.dir[0] == '\'' && cmd.dir[n-1] == '\'')
+ (cmd.dir++)[n-1] = '\0'; /* drop quotes */
+ s += n+(nt-1);
+ continue;
+ }
+ sign = 0;
+ if(*s == '-'){
+ sign = -1;
+ s++;
+ }else if(*s == '+'){
+ sign = +1;
+ s++;
+ }
+ if(!isdigitrune(*s))
+ goto Lose;
+ xy = riostrtol(s, &s);
+ switch(param){
+ case -1:
+ cmd.error = "unrecognized wctl parameter";
+ return cmd;
+ case Minx:
+ r.min.x = set(sign, r.min.x-xy, xy, r.min.x+xy);
+ break;
+ case Miny:
+ r.min.y = set(sign, r.min.y-xy, xy, r.min.y+xy);
+ break;
+ case Maxx:
+ r.max.x = set(sign, r.max.x-xy, xy, r.max.x+xy);
+ break;
+ case Maxy:
+ r.max.y = set(sign, r.max.y-xy, xy, r.max.y+xy);
+ break;
+ case Deltax:
+ r.max.x = set(sign, r.max.x-xy, r.min.x+xy, r.max.x+xy);
+ break;
+ case Deltay:
+ r.max.y = set(sign, r.max.y-xy, r.min.y+xy, r.max.y+xy);
+ break;
+ case Id:
+ cmd.id = xy;
+ break;
+ case PID:
+ cmd.pid = xy;
+ break;
+ }
+ }
+ if(cmd.cmd == Screenoffset)
+ cmd.r = r;
+ else
+ cmd.r = rectonscreen(rectaddpt(r, screen->r.min));
+ while(isspacerune(*s))
+ s++;
+ if(cmd.cmd != New && cmd.cmd != Newtab && *s != '\0'){
+ cmd.error = "extraneous text in wctl message";
+ return cmd;
+ }
+ cmd.args = s;
+ return cmd;
+Lose:
+ cmd.error = "unrecognized wctl command";
+ return cmd;
+}
+
+char*
+wctlcmd(WinTab *w, Rectangle r, int cmd)
+{
+ Window *ww = w->w;
+ switch(cmd){
+ case Move:
+ r = rectaddpt(Rect(0,0,Dx(ww->frame->r),Dy(ww->frame->r)), r.min);
+ if(!goodrect(r))
+ return Ebadwr;
+ if(!eqpt(r.min, ww->frame->r.min))
+ wmove(ww, r.min);
+ break;
+ case Resize:
+ if(!goodrect(r))
+ return Ebadwr;
+ if(!eqrect(r, ww->frame->r))
+ wresize(ww, r);
+ break;
+// TODO: these three work somewhat differently in rio
+ case Top:
+ wraise(ww);
+ break;
+ case Bottom:
+ wlower(ww);
+ break;
+ case Current:
+ if(ww->hidden)
+ return "window is hidden";
+ wfocus(ww);
+ wraise(ww);
+ break;
+ case Hide:
+ switch(whide(ww)){
+ case -1: return "window already hidden";
+ case 0: return "hide failed";
+ }
+ break;
+ case Unhide:
+ switch(wunhide(ww)){
+ case -1: return "window not hidden";
+ case 0: return "hide failed";
+ }
+ break;
+ case Delete:
+ wdelete(ww);
+ break;
+ case Scroll:
+ w->scrolling = TRUE;
+ xshow(&w->text, w->text.nr);
+ wsendmsg(w, Wakeup);
+ break;
+ case Noscroll:
+ w->scrolling = FALSE;
+ wsendmsg(w, Wakeup);
+ break;
+ case Border:
+ ww->noborder &= ~1;
+ wrecreate(ww);
+ break;
+ case Noborder:
+ ww->noborder |= 1;
+ wrecreate(ww);
+ break;
+ case Title:
+ ww->notitle = FALSE;
+ wrecreate(ww);
+ break;
+ case Notitle:
+ ww->notitle = TRUE;
+ wrecreate(ww);
+ break;
+ case Sticky:
+ ww->sticky = TRUE;
+ break;
+ case Nosticky:
+ ww->sticky = FALSE;
+ break;
+ default:
+ return "invalid wctl message";
+ }
+ return nil;
+}
+
+char*
+wctlnew(WinTab *w, Wctlcmd cmd)
+{
+ char *argv[4], **args;
+
+ args = nil;
+ if(cmd.pid == 0){
+ argv[0] = "rc";
+ argv[1] = "-c";
+ while(isspacerune(*cmd.args))
+ cmd.args++;
+ if(*cmd.args == '\0'){
+ argv[1] = "-i";
+ argv[2] = nil;
+ }else{
+ argv[2] = cmd.args;
+ argv[3] = nil;
+ }
+ args = argv;
+ }
+ if(wincmd(w, cmd.pid, cmd.dir, args) == 0)
+ return "window creation failed";
+ return nil;
+}
+
+char*
+writewctl(WinTab *w, char *data)
+{
+ Rectangle r;
+ Wctlcmd cmd;
+
+ if(w == nil)
+ r = ZR;
+ else
+ r = rectsubpt(w->w->frame->r, screen->r.min);
+ cmd = parsewctl(data, r);
+ if(cmd.error)
+ return cmd.error;
+
+ if(cmd.id != 0){
+ w = wfind(cmd.id);
+ if(w == nil)
+ return "no such window id";
+ }
+
+ if(w == nil && cmd.cmd > New)
+ return "command needs to be run within a window";
+
+ switch(cmd.cmd){
+ case Screenoffset:
+ screenoffset(cmd.r.min.x, cmd.r.min.y);
+ return nil;
+ case Screenrefresh:
+ refresh();
+ return nil;
+ case New:
+ w = wtcreate(cmd.r, cmd.hidden, cmd.scrolling);
+ if(w == nil)
+ return "window creation failed";
+ return wctlnew(w, cmd);
+ case Newtab:
+ w = tcreate(w->w, cmd.scrolling);
+ if(w == nil)
+ return "window creation failed";
+ return wctlnew(w, cmd);
+ case Set:
+ if(cmd.pid > 0)
+ wsetpid(w, cmd.pid, 0);
+ return nil;
+ default:
+ incref(w);
+ cmd.error = wctlcmd(w, cmd.r, cmd.cmd);
+ wrelease(w);
+ return cmd.error;
+ }
+}
--- /dev/null
+++ b/win3.c
@@ -1,0 +1,338 @@
+#include "inc.h"
+
+int bordersz = 4;
+int titlesz = 19;
+int tabsz = 23;
+
+enum {
+ ColDefault,
+ ColHilight,
+ ColShadow,
+ ColTitle,
+ ColTitleInact,
+ ColTitleText,
+ ColTitleTextInact,
+
+ ColFrame,
+ ColBorder,
+ ColBorderInact,
+
+ NumWinColors
+};
+
+Image *wincolors[NumWinColors];
+Image *icons[5];
+
+void
+winbtn(Image *img, Rectangle r, Image *icon, int down)
+{
+ draw(img, r, wincolors[ColDefault], nil, ZP);
+ if(down){
+ borderTL(img, r, wincolors[ColShadow]);
+ r = insetrect(r, 1);
+ }else{
+ winborder(img, r, wincolors[ColHilight], wincolors[ColShadow]);
+ r = insetrect(r, 1);
+ borderBR(img, r, wincolors[ColShadow]);
+ }
+
+ r = centerrect(r, icon->r);
+ if(down)
+ r = rectaddpt(r, Pt(1,1));
+ draw(img, r, icon, icon, ZP);
+}
+
+void
+winbtnflat(Image *img, Rectangle r, Image *icon, Image *icondown, int down)
+{
+ if(down){
+ draw(img, r, wincolors[ColShadow], nil, ZP);
+ }else{
+ draw(img, r, wincolors[ColDefault], nil, ZP);
+ }
+
+ r = centerrect(r, icon->r);
+ if(down)
+ icon = icondown;
+ draw(img, r, icon, icon, ZP);
+}
+
+int
+winbtnctl(Image *img, Rectangle r, Image *icon)
+{
+ int over, prevover;
+
+ prevover = 1;
+ winbtn(img, r, icon, 1);
+ while(mctl->buttons){
+ readmouse(mctl);
+ over = ptinrect(mctl->xy, r);
+ if(over != prevover)
+ winbtn(img, r, icon, over);
+ prevover = over;
+ }
+ if(prevover)
+ winbtn(img, r, icon, 0);
+ return ptinrect(mctl->xy, r);
+}
+
+int
+winbtnctlflat(Image *img, Rectangle r, Image *icon, Image *icondown)
+{
+ int over, prevover;
+
+ prevover = 1;
+ winbtnflat(img, r, icon, icondown, 1);
+ while(mctl->buttons){
+ readmouse(mctl);
+ over = ptinrect(mctl->xy, r);
+ if(over != prevover)
+ winbtnflat(img, r, icon, icondown, over);
+ prevover = over;
+ }
+ if(prevover)
+ winbtnflat(img, r, icon, icondown, 0);
+ return ptinrect(mctl->xy, r);
+}
+
+
+
+void
+wdecor(Window *w)
+{
+ if(w->frame == nil)
+ return;
+
+ int inact = w != focused;
+ Rectangle r;
+
+ if(!w->noborder){
+ r = w->rect;
+ border(w->frame, r, bordersz, wincolors[ColBorder + inact], ZP);
+ border(w->frame, r, 1, wincolors[ColFrame], ZP);
+ border(w->frame, insetrect(r,3), 1, wincolors[ColFrame], ZP);
+
+ Rectangle br = rectaddpt(Rect(0,0,1,bordersz), r.min);
+ int dx = Dx(r);
+ int dy = Dy(r);
+ int off = bordersz+titlesz-1;
+ draw(w->frame, rectaddpt(br, Pt(off,0)), wincolors[ColFrame], nil, ZP);
+ draw(w->frame, rectaddpt(br, Pt(off,dy-bordersz)), wincolors[ColFrame], nil, ZP);
+ draw(w->frame, rectaddpt(br, Pt(dx-1-off,0)), wincolors[ColFrame], nil, ZP);
+ draw(w->frame, rectaddpt(br, Pt(dx-1-off,dy-bordersz)), wincolors[ColFrame], nil, ZP);
+
+ br = rectaddpt(Rect(0,0,bordersz,1), r.min);
+ draw(w->frame, rectaddpt(br, Pt(0,off)), wincolors[ColFrame], nil, ZP);
+ draw(w->frame, rectaddpt(br, Pt(dx-bordersz,off)), wincolors[ColFrame], nil, ZP);
+ draw(w->frame, rectaddpt(br, Pt(0,dy-1-off)), wincolors[ColFrame], nil, ZP);
+ draw(w->frame, rectaddpt(br, Pt(dx-bordersz,dy-1-off)), wincolors[ColFrame], nil, ZP);
+
+ r = insetrect(r, bordersz);
+ }
+
+ if(!w->notitle){
+ r = w->titlerect;
+ r.max.y -= 1;
+ draw(w->frame, r, wincolors[ColTitle + inact], nil, ZP);
+ draw(w->frame, Rect(r.min.x,r.max.y,r.max.x,r.max.y+1), wincolors[ColFrame], nil, ZP);
+
+ // menu
+ Rectangle br = Rect(r.min.x,r.min.y,r.min.x+titlesz-1,r.min.y+titlesz-1);
+ winbtnflat(w->frame, br, icons[3], icons[4], 0);
+ border(w->frame, insetrect(br,-1), 1, display->black, ZP);
+
+ // max/restore
+ br.max.x = r.max.x;
+ br.min.x = br.max.x-titlesz+1;
+ winbtn(w->frame, br, icons[1+w->maximized], 0);
+ border(w->frame, insetrect(br,-1), 1, display->black, ZP);
+
+ // min
+ br = rectaddpt(br, Pt(-titlesz,0));
+ winbtn(w->frame, br, icons[0], 0);
+ border(w->frame, insetrect(br,-1), 1, display->black, ZP);
+
+ if(w->cur){
+ int sp = (Dx(r)-stringwidth(font, w->cur->label))/2;
+ Point pt = Pt(r.min.x+sp, r.min.y);
+ string(w->frame, pt, wincolors[ColTitleText + inact], pt, font, w->cur->label);
+ }
+ }
+
+ r = w->tabrect;
+ draw(w->frame, r, wincolors[ColDefault], nil, ZP);
+ draw(w->frame, Rect(r.min.x,r.max.y-1,r.max.x,r.max.y), display->black, nil, ZP);
+
+ int n = w->ref;
+ if(n > 1){
+ int wd = Dx(r)/n;
+ r.max.y -= 1;
+ int xxx = r.max.x;
+ r.max.x = r.min.x + wd;
+ for(WinTab *t = w->tab; t; t = t->next){
+ if(t->next == nil)
+ r.max.x = xxx;
+ if(t == w->cur)
+ border(w->frame, insetrect(r,1), 1, display->black, ZP);
+ int margin = (tabsz - font->height)/2;
+ Point pt = Pt(r.min.x+bordersz/2, r.min.y + margin);
+ string(w->frame, pt, display->black, pt, font, t->label);
+ r = rectaddpt(r, Pt(wd,0));
+ }
+ }
+}
+
+void
+wtitlectl(Window *w)
+{
+ if(mctl->buttons & 7){
+ wraise(w);
+ wfocus(w);
+ if(mctl->buttons & 1) {
+ Rectangle r = w->rect;
+ if(!w->noborder)
+ r = insetrect(r, bordersz);
+ Rectangle br = Rect(0,0,titlesz-1,titlesz-1);
+ Rectangle br1 = rectaddpt(br, r.min);
+ Rectangle br2 = rectaddpt(br1, Pt(Dx(r)-titlesz+1, 0));
+ Rectangle br3 = rectaddpt(br2, Pt(-titlesz, 0));
+
+ if(ptinrect(mctl->xy, br1)){
+ if(winbtnctlflat(w->frame, br1, icons[3], icons[4]))
+ wdelete(w);
+ }else if(ptinrect(mctl->xy, br2)){
+ if(winbtnctl(w->frame, br2, icons[1+w->maximized])){
+ if(w->maximized)
+ wrestore(w);
+ else
+ wmaximize(w);
+ }
+ }else if(ptinrect(mctl->xy, br3)){
+ if(winbtnctl(w->frame, br3, icons[0]))
+ whide(w);
+ }else if(!w->maximized)
+ grab(w, 1);
+ }
+ if(mctl->buttons & 4)
+ btn3menu();
+ }
+}
+
+
+static char minbtn[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static char maxbtn[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static char rstbtn[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static char menubtn[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
+ 0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 6, 0,
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 0,
+ 0, 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+static char menubtninv[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0,
+ 0, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 7, 0,
+ 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 7, 0,
+ 0, 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+
+void
+inittheme(void)
+{
+ background = getcolor("background", 0xC0C7C8FF);
+
+ wincolors[ColDefault] = getcolor("button_face", 0xC0C7C8FF);
+ wincolors[ColHilight] = getcolor("button_hilight", 0xFFFFFFFF);
+ wincolors[ColShadow] = getcolor("button_shadow", 0x87888FFF);
+ wincolors[ColTitle] = getcolor("titlebar_active", 0x5787a8FF);
+ wincolors[ColTitleInact] = getcolor("titlebar_inactive", 0xFFFFFFFF);
+ wincolors[ColTitleText] = getcolor("titlebar_text_active", 0xFFFFFFFF);
+ wincolors[ColTitleTextInact] = getcolor("titlebar_text_inactive", 0x000000FF);
+ wincolors[ColFrame] = getcolor("window_frame", 0x000000FF);
+ wincolors[ColBorder] = getcolor("border_active", 0xC0C7C8FF);
+ wincolors[ColBorderInact] = getcolor("border_inactive", 0xFFFFFFFF);
+
+ icons[0] = mkicon(minbtn, 16, 16);
+ icons[1] = mkicon(maxbtn, 16, 16);
+ icons[2] = mkicon(rstbtn, 16, 16);
+ icons[3] = mkicon(menubtn, 16, 16);
+ icons[4] = mkicon(menubtninv, 16, 16);
+}
--- /dev/null
+++ b/win95.c
@@ -1,0 +1,279 @@
+#include "inc.h"
+
+int bordersz = 4;
+int titlesz = 19;
+int tabsz = 20;
+
+enum {
+ ColDefault,
+ ColLight1,
+ ColLight2,
+ ColDark1,
+ ColDark2,
+ ColTitle,
+ ColTitleInact,
+ ColTitleText,
+ ColTitleTextInact,
+
+ NumWinColors
+};
+
+Image *wincolors[NumWinColors];
+Image *icons[5];
+
+void
+winbtn(Image *img, Rectangle r, Image *icon, int down)
+{
+ if(down){
+ winborder(img, r, wincolors[ColDark2], wincolors[ColLight2]);
+ r = insetrect(r, 1);
+ winborder(img, r, wincolors[ColDark1], wincolors[ColLight1]);
+ }else{
+ winborder(img, r, wincolors[ColLight2], wincolors[ColDark2]);
+ r = insetrect(r, 1);
+ winborder(img, r, wincolors[ColLight1], wincolors[ColDark1]);
+ }
+ r = insetrect(r, 1);
+ draw(img, r, wincolors[ColDefault], nil, ZP);
+
+ r = insetrect(r,-2);
+ if(down)
+ r = rectaddpt(r, Pt(1,1));
+ draw(img, r, icon, icon, ZP);
+}
+
+void
+winframe(Image *img, Rectangle r)
+{
+ winborder(img, r, wincolors[ColLight1], wincolors[ColDark2]);
+ r = insetrect(r, 1);
+ winborder(img, r, wincolors[ColLight2], wincolors[ColDark1]);
+}
+
+int
+winbtnctl(Image *img, Rectangle r, Image *icon)
+{
+ int over, prevover;
+
+ prevover = 1;
+ winbtn(img, r, icon, 1);
+ while(mctl->buttons){
+ readmouse(mctl);
+ over = ptinrect(mctl->xy, r);
+ if(over != prevover)
+ winbtn(img, r, icon, over);
+ prevover = over;
+ }
+ if(prevover)
+ winbtn(img, r, icon, 0);
+ return ptinrect(mctl->xy, r);
+}
+
+void
+wdecor(Window *w)
+{
+ if(w->frame == nil)
+ return;
+
+ int inact = w != focused;
+ Rectangle r;
+
+ if(!w->noborder){
+ r = w->rect;
+ border(w->frame, r, bordersz, wincolors[ColDefault], ZP);
+ winframe(w->frame, r);
+ }
+
+ if(!w->notitle){
+ r = w->titlerect;
+ r.max.y -= 1;
+ draw(w->frame, r, wincolors[ColTitle + inact], nil, ZP);
+ draw(w->frame, Rect(r.min.x,r.max.y,r.max.x,r.max.y+1), wincolors[ColDefault], nil, ZP);
+
+ // draw buttons
+ Rectangle br = insetrect(r, 2);
+ br.min.x = br.max.x - Dy(br) - 2;
+ winbtn(w->frame, br, icons[3], 0);
+ br = rectaddpt(br, Pt(-Dx(br)-2, 0));
+ winbtn(w->frame, br, icons[1+w->maximized], 0);
+ br = rectaddpt(br, Pt(-Dx(br), 0));
+ winbtn(w->frame, br, icons[0], 0);
+
+ br = rectaddpt(icons[4]->r, insetrect(r,1).min);
+ draw(w->frame, br, icons[4], icons[4], ZP);
+
+ Point pt = Pt(r.min.x + 2 + titlesz-1, r.min.y);
+ if(w->cur)
+ string(w->frame, pt, wincolors[ColTitleText + inact], pt, font, w->cur->label);
+ }
+
+
+ r = w->tabrect;
+ draw(w->frame, r, wincolors[ColDefault], nil, ZP);
+ draw(w->frame, Rect(r.min.x,r.max.y-1,r.max.x,r.max.y), display->black, nil, ZP);
+
+ int n = w->ref;
+ if(n > 1){
+ int wd = Dx(r)/n;
+ r.max.y -= 1;
+ int xxx = r.max.x;
+ r.max.x = r.min.x + wd;
+ for(WinTab *t = w->tab; t; t = t->next){
+ if(t->next == nil)
+ r.max.x = xxx;
+ if(t == w->cur)
+ border(w->frame, insetrect(r,1), 1, display->black, ZP);
+ int margin = (tabsz - font->height)/2;
+ Point pt = Pt(r.min.x+bordersz/2, r.min.y + margin);
+ string(w->frame, pt, display->black, pt, font, t->label);
+ r = rectaddpt(r, Pt(wd,0));
+ }
+ }
+}
+
+void
+wtitlectl(Window *w)
+{
+ if(mctl->buttons & 7){
+ wraise(w);
+ wfocus(w);
+ if(mctl->buttons & 1) {
+ Rectangle r = w->titlerect;
+ r.max.y -= 1;
+ Rectangle br1 = insetrect(r, 2);
+ br1.min.x = br1.max.x - Dy(br1) - 2;
+ Rectangle br2 = rectaddpt(br1, Pt(-Dx(br1)-2, 0));
+ Rectangle br3 = rectaddpt(br1, Pt(-2*Dx(br1)-2, 0));
+
+ if(ptinrect(mctl->xy, br1)){
+ if(winbtnctl(w->frame, br1, icons[3]))
+ wdelete(w);
+ }else if(ptinrect(mctl->xy, br2)){
+ if(winbtnctl(w->frame, br2, icons[1+w->maximized])){
+ if(w->maximized)
+ wrestore(w);
+ else
+ wmaximize(w);
+ }
+ }else if(ptinrect(mctl->xy, br3)){
+ if(winbtnctl(w->frame, br3, icons[0]))
+ whide(w);
+ }else if(!w->maximized)
+ grab(w, 1);
+ }
+ if(mctl->buttons & 4)
+ btn3menu();
+ }
+}
+
+
+
+static char minbtn[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static char maxbtn[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+ 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static char rstbtn[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static char closebtn[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static char appbtn[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1,
+ 0, 0, 2, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 3, 1,
+ 0, 0, 2, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 3, 1,
+ 0, 0, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 1,
+ 0, 0, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1,
+ 0, 0, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1,
+ 0, 0, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1,
+ 0, 0, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1,
+ 0, 0, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1,
+ 0, 0, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1,
+ 0, 0, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1,
+ 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+void
+inittheme(void)
+{
+ background = getcolor("background", 0x008080FF);
+
+ wincolors[ColDefault] = getcolor("3d_face", 0xC0C0C0FF);
+// wincolors[ColLight1] = getcolor("3d_hilight1", 0xDFDFDFFF);
+ wincolors[ColLight1] = getcolor("3d_hilight1", 0xC0C0C0FF);
+ wincolors[ColLight2] = getcolor("3d_hilight2", 0xFFFFFFFF);
+ wincolors[ColDark1] = getcolor("3d_shadow1", 0x808080FF);
+ wincolors[ColDark2] = getcolor("3d_shadow2", 0x000000FF);
+ wincolors[ColTitle] = getcolor("titlebar_active", 0x000080FF);
+ wincolors[ColTitleInact] = getcolor("titlebar_inactive", 0x808080FF);
+ wincolors[ColTitleText] = getcolor("titlebar_text_active", 0xFFFFFFFF);
+ wincolors[ColTitleTextInact] = getcolor("titlebar_text_inactive", 0xC0C0C0FF);
+
+ icons[0] = mkicon(minbtn, 16, 14);
+ icons[1] = mkicon(maxbtn, 16, 14);
+ icons[2] = mkicon(rstbtn, 16, 14);
+ icons[3] = mkicon(closebtn, 16, 14);
+ icons[4] = mkicon(appbtn, 16, 16);
+}
--- /dev/null
+++ b/wind.c
@@ -1,0 +1,1390 @@
+#include "inc.h"
+
+Window *bottomwin, *topwin;
+Window *windows[MAXWINDOWS];
+int nwindows;
+WinTab *wintabs[MAXWINDOWS];
+int nwintabs;
+Window *focused, *cursorwin;
+
+Point screenoff;
+
+static void wrepaint(Window *w);
+static void winthread(void *arg);
+
+static void
+wlistpushback(Window *w)
+{
+ w->higher = bottomwin;
+ if(bottomwin) bottomwin->lower = w;
+ w->lower = nil;
+ if(topwin == nil) topwin = w;
+ bottomwin = w;
+}
+
+static void
+wlistpushfront(Window *w)
+{
+ w->lower = topwin;
+ if(topwin) topwin->higher = w;
+ w->higher = nil;
+ if(bottomwin == nil) bottomwin = w;
+ topwin = w;
+}
+
+static void
+wlistremove(Window *w)
+{
+ if(w->lower)
+ w->lower->higher = w->higher;
+ else
+ bottomwin = w->higher;
+ if(w->higher)
+ w->higher->lower = w->lower;
+ else
+ topwin = w->lower;
+ w->higher = nil;
+ w->lower = nil;
+}
+
+void
+wmaximize(Window *w)
+{
+ w->maximized = 1;
+ w->noborder |= 2;
+ w->origrect = w->frame->r;
+ wresize(w, screen->r);
+}
+
+void
+wrestore(Window *w)
+{
+ w->maximized = 0;
+ w->noborder &= ~2;
+ wresize(w, w->origrect);
+}
+
+static void
+wcalcrects(Window *w, Rectangle r)
+{
+ w->rect = r;
+ w->contrect = r;
+ if(!w->noborder)
+ w->contrect = insetrect(w->contrect, bordersz);
+
+ w->titlerect = ZR;
+ if(!w->notitle){
+ w->titlerect = w->contrect;
+ w->titlerect.max.y = w->titlerect.min.y + titlesz;
+ w->contrect.min.y += titlesz;
+ }
+
+ w->tabrect = ZR;
+ if(w->ref > 1){
+ w->tabrect = w->contrect;
+ w->tabrect.max.y = w->tabrect.min.y + tabsz;
+ w->contrect.min.y += tabsz;
+ }
+
+ r = insetrect(w->contrect, 1);
+ w->scrollr = r;
+ w->scrollr.max.x = w->scrollr.min.x + 12;
+ w->textr = r;
+ w->textr.min.x = w->scrollr.max.x + 4;
+}
+
+int
+wcolsel(Window *w)
+{
+ return (w != focused) + (w->cur && w->cur->holdmode)*2;
+}
+
+static void
+wsetcolors(WinTab *w)
+{
+// TODO: this should use wcolsel
+ int c = w->holdmode ?
+ w->w == focused ? HOLDTEXT : PALEHOLDTEXT :
+ w->w == focused ? TEXT : PALETEXT;
+ w->text.cols[TEXT] = colors[c];
+}
+
+static void
+tinit(WinTab *t)
+{
+ Window *w;
+
+ if(t->deleted)
+ return;
+ w = t->w;
+ t->mc.image = w->frame;
+ t->content = allocwindow(w->screen, w->contrect, Refbackup, DNofill);
+ assert(t->content);
+ draw(t->content, t->content->r, colors[BACK], nil, ZP);
+ xinit(&t->text, w->textr, w->scrollr, tabwidth, font, t->content, colors);
+}
+
+static void
+tdeinit(WinTab *t)
+{
+ /* have to move image out of the way
+ * because client program can hold a ref
+ * and mess up our drawing. */
+ if(t->content)
+ originwindow(t->content, t->content->r.min, screen->r.max);
+ freeimage(t->content);
+ t->content = nil;
+ t->mc.image = nil;
+}
+
+/* get rid of window visually */
+static void
+wremove(Window *w)
+{
+ if(w->frame)
+ originwindow(w->frame, w->frame->r.min, screen->r.max);
+}
+
+static void
+wfreeimages(Window *w)
+{
+ freescreen(w->screen);
+ w->screen = nil;
+
+ freeimage(w->frame);
+ w->frame = nil;
+}
+
+/* create images, destroy first if they exist.
+ * either only tab images e.g. when the tab bar appears/disappears
+ * or full window when window is resized/moved. */
+static void
+wreinit(Window *w, bool all)
+{
+ Rectangle r, hr;
+
+ for(WinTab *t = w->tab; t; t = t->next)
+ tdeinit(t);
+
+ if(all){
+ r = w->rect;
+ /* reference can be held by client program
+ * indefinitely which would keep this on screen. */
+ wremove(w);
+ wfreeimages(w);
+ if(w->hidden){
+ hr = rectaddpt(r, subpt(screen->r.max, r.min));
+ w->frame = allocwindow(wscreen, hr, Refbackup, DNofill);
+ originwindow(w->frame, r.min, hr.min);
+ }else
+ w->frame = allocwindow(wscreen, r, Refbackup, DNofill);
+ w->screen = allocscreen(w->frame, colors[BACK], 0);
+ assert(w->screen);
+ }
+
+ for(WinTab *t = w->tab; t; t = t->next)
+ tinit(t);
+
+ tfocus(w->cur);
+}
+
+// TODO: find better name
+void
+wrecreate(Window *w)
+{
+ wcalcrects(w, w->rect);
+ wreinit(w, 0);
+ for(WinTab *t = w->tab; t; t = t->next)
+ wsendmsg(t, Resized);
+}
+
+
+static int id = 1;
+
+Window*
+wcreate(Rectangle r, bool hidden)
+{
+ Window *w;
+
+ w = emalloc(sizeof(Window));
+ w->hidden = hidden;
+ w->notitle = notitle; // TODO: argument?
+ wcalcrects(w, r);
+ wreinit(w, 1);
+ wlistpushfront(w);
+ // TODO: could be more graceful here
+ assert(nwindows < MAXWINDOWS);
+ windows[nwindows++] = w;
+
+ wfocus(w);
+
+ return w;
+}
+
+void
+tfocus(WinTab *t)
+{
+ if(t == nil || t->deleted)
+ return;
+ t->w->cur = t;
+ topwindow(t->content);
+ wrepaint(t->w);
+}
+
+WinTab*
+tcreate(Window *w, bool scrolling)
+{
+ WinTab *t, **tp;
+
+ /* recreate window when tab bar appears
+ * before we attach the new tab. */
+ incref(w);
+ if(w->ref == 2)
+ wrecreate(w);
+
+ t = emalloc(sizeof(WinTab));
+ incref(t);
+ t->w = w;
+ for(tp = &w->tab; *tp; tp = &(*tp)->next);
+ *tp = t;
+ tinit(t);
+ t->id = id++;
+ t->notefd = -1;
+ wsetlabel(t, "<unnamed>");
+ t->dir = estrdup(startdir);
+ t->scrolling = scrolling;
+
+ t->mc.c = chancreate(sizeof(Mouse), 16);
+ t->gone = chancreate(sizeof(int), 0);
+ t->kbd = chancreate(sizeof(char*), 16);
+ t->ctl = chancreate(sizeof(int), 0);
+ t->conswrite = chancreate(sizeof(Channel**), 0);
+ t->consread = chancreate(sizeof(Channel**), 0);
+ t->kbdread = chancreate(sizeof(Channel**), 0);
+ t->mouseread = chancreate(sizeof(Channel**), 0);
+ t->wctlread = chancreate(sizeof(Channel**), 0);
+ t->complete = chancreate(sizeof(Completion*), 0);
+ threadcreate(winthread, t, mainstacksize);
+
+ wsetname(t);
+ // TODO: could be more graceful here
+ assert(nwintabs < MAXWINDOWS);
+ wintabs[nwintabs++] = t;
+
+ tfocus(t);
+
+ return t;
+}
+
+WinTab*
+wtcreate(Rectangle r, bool hidden, bool scrolling)
+{
+ return tcreate(wcreate(r, hidden), scrolling);
+}
+
+/* called from winthread when it exits */
+static void
+wfree(WinTab *w)
+{
+ if(w->notefd >= 0)
+ close(w->notefd);
+ xclear(&w->text);
+ chanclose(w->mc.c);
+ chanclose(w->gone);
+ chanclose(w->kbd);
+ chanclose(w->ctl);
+ chanclose(w->conswrite);
+ chanclose(w->consread);
+ chanclose(w->kbdread);
+ chanclose(w->mouseread);
+ chanclose(w->wctlread);
+ chanclose(w->complete);
+ free(w->label);
+ free(w);
+}
+
+static void
+wdestroy(Window *w)
+{
+ int i;
+
+ assert(w != focused); /* this must be done elsewhere */
+ assert(w->tab == nil);
+ wlistremove(w);
+ for(i = 0; i < nwindows; i++)
+ if(windows[i] == w){
+ nwindows--;
+ memmove(&windows[i], &windows[i+1], (nwindows-i)*sizeof(Window*));
+ break;
+ }
+ wfreeimages(w);
+ free(w);
+ flushimage(display, 1);
+}
+
+int
+inwinthread(WinTab *w)
+{
+ return w->threadname == threadgetname();
+}
+
+/* decrement reference, close window once all tabs gone. */
+static int
+wrelease_(Window *w)
+{
+ if(decref(w) == 0){
+ assert(w->tab == nil);
+ assert(w->cur == nil);
+ wremove(w);
+ wunfocus(w);
+ assert(w != focused);
+ wdestroy(w);
+ return 0;
+ }else{
+ assert(w->ref > 0);
+ return w->ref;
+ }
+}
+
+/* logically and visually close the tab.
+ * struct, thread and link into window will stick
+ * around until all references are gone.
+ * safe to call multiple times. */
+static void
+tclose(WinTab *w)
+{
+ int i;
+
+ if(w->deleted)
+ return;
+ w->deleted = TRUE;
+ for(i = 0; i < nwintabs; i++)
+ if(wintabs[i] == w){
+ nwintabs--;
+ memmove(&wintabs[i], &wintabs[i+1], (nwintabs-i)*sizeof(WinTab*));
+ break;
+ }
+ tdeinit(w);
+}
+
+/* detach tab from window */
+void
+tdetach(WinTab *t)
+{
+ WinTab **tp;
+ Window *w = t->w;
+
+ if(w == nil)
+ return;
+
+ /* remove tab from window */
+ for(tp = &w->tab; *tp; tp = &(*tp)->next){
+ if(*tp == t){
+ (*tp) = t->next;
+ t->next = nil;
+ t->w = nil;
+ break;
+ }
+ }
+ assert(t->w == nil);
+ tdeinit(t);
+
+ /* find new focused tab */
+ if(w->cur == t){
+ w->cur = *tp;
+ if(w->cur == nil)
+ for(w->cur = w->tab;
+ w->cur && w->cur->next;
+ w->cur = w->cur->next);
+ }
+ if(wrelease_(w) > 0){
+ /* complete redraw if tab bar disappears */
+ if(w->ref == 1)
+ wrecreate(w);
+ else
+ tfocus(w->cur);
+ }
+}
+
+void
+tmigrate(WinTab *t, Window *w)
+{
+ WinTab **tp;
+
+ if(t->w == w)
+ return;
+ tdetach(t);
+
+ /* recreate window when tab bar appears
+ * before we attach the new tab. */
+ incref(w);
+ if(w->ref == 2)
+ wrecreate(w);
+
+ t->w = w;
+ for(tp = &w->tab; *tp; tp = &(*tp)->next);
+ *tp = t;
+ tinit(t);
+
+ tfocus(t);
+ wsendmsg(t, Resized);
+}
+
+/* this SUCKS, want doubly linked lists */
+static WinTab**
+getprevptr(WinTab *t)
+{
+ WinTab **tp;
+ for(tp = &t->w->tab; *tp; tp = &(*tp)->next)
+ if(*tp == t)
+ return tp;
+ return nil;
+}
+static WinTab*
+getprev(WinTab *t)
+{
+ WinTab *tt;
+ for(tt = t->w->tab; tt; tt = tt->next)
+ if(tt->next == t)
+ return tt;
+ return nil;
+}
+
+static void
+tswapadjacent(WinTab *l, WinTab *r)
+{
+ WinTab **tp;
+
+ tp = getprevptr(l);
+ assert(tp);
+ l->next = r->next;
+ r->next = l;
+ *tp = r;
+ wdecor(l->w);
+}
+
+void
+tmoveleft(WinTab *r)
+{
+ WinTab *l;
+ l = getprev(r);
+ if(l == nil) return;
+ tswapadjacent(l, r);
+}
+
+void
+tmoveright(WinTab *l)
+{
+ WinTab *r;
+ r = l->next;
+ if(r == nil) return;
+ tswapadjacent(l, r);
+}
+
+/* decrement reference, close tab once all references gone. */
+void
+wrelease(WinTab *t)
+{
+ if(decref(t) == 0){
+ /* increment ref count temporarily
+ * so win thread doesn't exit too early */
+ incref(t);
+ tdetach(t);
+ tclose(t);
+ decref(t);
+ if(!inwinthread(t))
+ wsendmsg(t, Wakeup);
+ }else
+ assert(t->ref > 0);
+}
+
+void
+tdelete(WinTab *t)
+{
+ assert(!t->deleted);
+ tdetach(t);
+ tclose(t);
+
+ wsendmsg(t, Deleted);
+}
+
+
+void
+wsendmsg(WinTab *w, int type)
+{
+ assert(!inwinthread(w));
+ sendul(w->ctl, type);
+}
+
+WinTab*
+wfind(int id)
+{
+ int i;
+
+ for(i = 0; i < nwintabs; i++)
+ if(wintabs[i]->id == id)
+ return wintabs[i];
+ return nil;
+}
+
+Window*
+wpointto(Point pt)
+{
+ Window *w;
+
+ for(w = topwin; w; w = w->lower)
+ if(!w->hidden && ptinrect(pt, w->frame->r))
+ return w;
+ return nil;
+}
+
+void
+wsetcursor(WinTab *w)
+{
+ if(w->w == cursorwin)
+ setcursornormal(w->holdmode ? &whitearrow : w->cursorp);
+}
+
+void
+wsetlabel(WinTab *w, char *label)
+{
+ free(w->label);
+ w->label = estrdup(label);
+ wdecor(w->w);
+}
+
+void
+wsetname(WinTab *w)
+{
+ int i, n;
+ char err[ERRMAX];
+
+ n = snprint(w->name, sizeof(w->name)-2, "%s.%d.%d", "noborder", w->id, w->namecount++);
+ for(i='A'; i<='Z'; i++){
+ if(nameimage(w->content, w->name, 1) > 0)
+ return;
+ errstr(err, sizeof err);
+ if(strcmp(err, "image name in use") != 0)
+ break;
+ w->name[n] = i;
+ w->name[n+1] = 0;
+ }
+ w->name[0] = 0;
+ fprint(2, "lola: setname failed: %s\n", err);
+}
+
+void
+wsetpid(WinTab *w, int pid, int dolabel)
+{
+ char buf[32];
+ int ofd;
+
+ ofd = w->notefd;
+ if(pid <= 0)
+ w->notefd = -1;
+ else{
+ if(dolabel){
+ snprint(buf, sizeof(buf), "rc %lud", (ulong)pid);
+ wsetlabel(w, buf);
+ }
+ snprint(buf, sizeof(buf), "/proc/%lud/notepg", (ulong)pid);
+ w->notefd = open(buf, OWRITE|OCEXEC);
+ }
+ if(ofd >= 0)
+ close(ofd);
+}
+
+void
+wdelete(Window *w)
+{
+ wremove(w);
+ wunfocus(w);
+ while(w->tab)
+ tdelete(w->tab);
+}
+
+static void
+wrepaint(Window *w)
+{
+ wsetcolors(w->cur);
+ wdecor(w);
+ if(!w->cur->mouseopen)
+ xredraw(&w->cur->text);
+}
+
+/* restore window order after reshaping has disturbed it */
+void
+worder(void)
+{
+ Window *w;
+ for(w = bottomwin; w; w = w->higher)
+ if(!w->hidden)
+ topwindow(w->frame);
+}
+
+void
+wresize(Window *w, Rectangle r)
+{
+ wcalcrects(w, r);
+ wreinit(w, 1);
+ if(w != topwin && !w->hidden)
+ worder();
+ for(WinTab *t = w->tab; t; t = t->next)
+ wsendmsg(t, Resized);
+}
+
+void
+wmove(Window *w, Point pos)
+{
+ wresize(w, rectaddpt(w->frame->r, subpt(pos, w->frame->r.min)));
+}
+
+void
+wraise(Window *w)
+{
+ wlistremove(w);
+ wlistpushfront(w);
+ topwindow(w->frame);
+ flushimage(display, 1);
+}
+
+void
+wlower(Window *w)
+{
+ wlistremove(w);
+ wlistpushback(w);
+ bottomwindow(w->frame);
+ bottomwindow(fakebg);
+ flushimage(display, 1);
+}
+
+static void
+wfocuschanged(Window *w)
+{
+// TODO(tab):
+ if(w == nil || w->cur == nil)
+ return;
+ w->cur->wctlready = TRUE;
+ wrepaint(w);
+ if(!inwinthread(w->cur))
+ wsendmsg(w->cur, Wakeup);
+}
+
+void
+wfocus(Window *w)
+{
+ Window *prev;
+
+ if(w == focused)
+ return;
+ prev = focused;
+ focused = w;
+ if(prev && prev->cur){
+// TODO(tab): check this
+ WinTab *t = prev->cur;
+ /* release keys (if possible) */
+ char *s = estrdup("K");
+ if(nbsendp(t->kbd, s) != 1)
+ free(s);
+ /* release mouse buttons */
+ if(t->mc.buttons){
+ t->mc.buttons = 0;
+ t->mq.counter++;
+ }
+ }
+ wfocuschanged(prev);
+ wfocuschanged(focused);
+}
+
+void
+wunfocus(Window *w)
+{
+ if(w == focused)
+ wfocus(nil);
+}
+
+// TODO(tab): wctl ready everyone?
+int
+whide(Window *w)
+{
+ if(w->hidden)
+ return -1;
+ incref(w->tab);
+ wremove(w);
+ wunfocus(w);
+ w->hidden = TRUE;
+ w->tab->wctlready = TRUE;
+ wsendmsg(w->tab, Wakeup);
+ wrelease(w->tab);
+ return 1;
+}
+
+// TODO(tab): wctl ready everyone?
+int
+wunhide(Window *w)
+{
+ if(!w->hidden)
+ return -1;
+ incref(w->tab);
+ w->hidden = FALSE;
+ w->tab->wctlready = TRUE;
+ originwindow(w->frame, w->frame->r.min, w->frame->r.min);
+ wraise(w);
+ wfocus(w);
+ wrelease(w->tab);
+ return 1;
+}
+
+void
+wsethold(WinTab *w, int hold)
+{
+ int switched;
+
+ if(hold)
+ switched = w->holdmode++ == 0;
+ else
+ switched = --w->holdmode == 0;
+ if(switched){
+ wsetcursor(w);
+ wrepaint(w->w);
+ }
+}
+
+/* Normally the mouse will only be moved inside the window.
+ * The force argument can move the mouse anywhere. */
+void
+wmovemouse(Window *w, Point pt, bool force)
+{
+ // TODO? rio also checks menuing and such
+ if(force ||
+ w == focused && wpointto(mctl->xy) == w && ptinrect(pt, w->rect))
+ moveto(mctl, pt);
+}
+
+/*
+ * Need to do this in a separate proc because if process we're interrupting
+ * is dying and trying to print tombstone, kernel is blocked holding p->debug lock.
+ */
+static void
+interruptproc(void *v)
+{
+ int *notefd;
+
+ notefd = v;
+ write(*notefd, "interrupt", 9);
+ close(*notefd);
+ free(notefd);
+}
+
+/*
+ * Filename completion
+ */
+
+typedef struct Completejob Completejob;
+struct Completejob
+{
+ char *dir;
+ char *str;
+ WinTab *win;
+};
+
+static void
+completeproc(void *arg)
+{
+ Completejob *job;
+ Completion *c;
+
+ job = arg;
+ threadsetname("namecomplete %s", job->dir);
+
+ c = complete(job->dir, job->str);
+ if(c != nil && sendp(job->win->complete, c) <= 0)
+ freecompletion(c);
+
+ wrelease(job->win);
+
+ free(job->dir);
+ free(job->str);
+ free(job);
+}
+
+static int
+windfilewidth(WinTab *w, uint q0, int oneelement)
+{
+ uint q;
+ Rune r;
+
+ q = q0;
+ while(q > 0){
+ r = w->text.r[q-1];
+ if(r<=' ' || r=='=' || r=='^' || r=='(' || r=='{')
+ break;
+ if(oneelement && r=='/')
+ break;
+ --q;
+ }
+ return q0-q;
+}
+
+static void
+namecomplete(WinTab *w)
+{
+ Text *x;
+ int nstr, npath;
+ Rune *path, *str;
+ char *dir, *root;
+ Completejob *job;
+
+ x = &w->text;
+ /* control-f: filename completion; works back to white space or / */
+ if(x->q0<x->nr && x->r[x->q0]>' ') /* must be at end of word */
+ return;
+ nstr = windfilewidth(w, x->q0, TRUE);
+ str = x->r+(x->q0-nstr);
+ npath = windfilewidth(w, x->q0-nstr, FALSE);
+ path = x->r+(x->q0-nstr-npath);
+
+ /* is path rooted? if not, we need to make it relative to window path */
+ if(npath>0 && path[0]=='/')
+ dir = smprint("%.*S", npath, path);
+ else {
+ if(strcmp(w->dir, "") == 0)
+ root = ".";
+ else
+ root = w->dir;
+ dir = smprint("%s/%.*S", root, npath, path);
+ }
+ if(dir == nil)
+ return;
+
+ /* run in background, winctl will collect the result on w->complete chan */
+ job = emalloc(sizeof *job);
+ job->str = smprint("%.*S", nstr, str);
+ job->dir = cleanname(dir);
+ job->win = w;
+ incref(w);
+ proccreate(completeproc, job, mainstacksize);
+}
+
+static void
+showcandidates(WinTab *w, Completion *c)
+{
+ Text *x;
+ int i;
+ Fmt f;
+ Rune *rp;
+ uint nr, qline;
+ char *s;
+
+ x = &w->text;
+ runefmtstrinit(&f);
+ if (c->nmatch == 0)
+ s = "[no matches in ";
+ else
+ s = "[";
+ if(c->nfile > 32)
+ fmtprint(&f, "%s%d files]\n", s, c->nfile);
+ else{
+ fmtprint(&f, "%s", s);
+ for(i=0; i<c->nfile; i++){
+ if(i > 0)
+ fmtprint(&f, " ");
+ fmtprint(&f, "%s", c->filename[i]);
+ }
+ fmtprint(&f, "]\n");
+ }
+ rp = runefmtstrflush(&f);
+ nr = runestrlen(rp);
+
+ /* place text at beginning of line before cursor and host point */
+ qline = min(x->qh, x->q0);
+ while(qline>0 && x->r[qline-1] != '\n')
+ qline--;
+
+ if(qline == x->qh){
+ /* advance host point to avoid readback */
+ x->qh = xinsert(x, rp, nr, qline)+nr;
+ }else{
+ xinsert(x, rp, nr, qline);
+ }
+ free(rp);
+}
+
+void
+wkeyctl(WinTab *w, Rune r)
+{
+ Text *x;
+ int nlines, n;
+ int *notefd;
+
+ x = &w->text;
+ nlines = x->maxlines; /* need signed */
+ if(!w->mouseopen){
+ switch(r){
+
+ /* Scrolling */
+ case Kscrollonedown:
+ n = mousescrollsize(x->maxlines);
+ xscrolln(x, max(n, 1));
+ return;
+ case Kdown:
+ xscrolln(x, shiftdown ? 1 : nlines/3);
+ return;
+ case Kpgdown:
+ xscrolln(x, nlines*2/3);
+ return;
+ case Kscrolloneup:
+ n = mousescrollsize(x->maxlines);
+ xscrolln(x, -max(n, 1));
+ return;
+ case Kup:
+ xscrolln(x, -(shiftdown ? 1 : nlines/3));
+ return;
+ case Kpgup:
+ xscrolln(x, -nlines*2/3);
+ return;
+
+ case Khome:
+ xshow(x, 0);
+ return;
+ case Kend:
+ xshow(x, x->nr);
+ return;
+
+ /* Cursor movement */
+ case Kleft:
+ if(x->q0 > 0)
+ xplacetick(x, x->q0-1);
+ return;
+ case Kright:
+ if(x->q1 < x->nr)
+ xplacetick(x, x->q1+1);
+ return;
+ case CTRL('A'):
+ while(x->q0 > 0 && x->r[x->q0-1] != '\n' &&
+ x->q0 != x->qh)
+ x->q0--;
+ xplacetick(x, x->q0);
+ return;
+ case CTRL('E'):
+ while(x->q0 < x->nr && x->r[x->q0] != '\n')
+ x->q0++;
+ xplacetick(x, x->q0);
+ return;
+ case CTRL('B'):
+ xplacetick(x, x->qh);
+ return;
+
+ /* Hold mode */
+ case Kesc:
+ wsethold(w, !w->holdmode);
+ return;
+ case Kdel:
+ if(w->holdmode)
+ wsethold(w, FALSE);
+ break;
+ }
+ }
+
+ if(x->rawmode && (x->q0 == x->nr || w->mouseopen))
+ xaddraw(x, &r, 1);
+ else if(r == Kdel){
+ x->qh = x->nr;
+ xshow(x, x->qh);
+ if(w->notefd < 0)
+ return;
+ notefd = emalloc(sizeof(int));
+ *notefd = dup(w->notefd, -1);
+ proccreate(interruptproc, notefd, 4096);
+ }else if(r == CTRL('F') || r == Kins)
+ namecomplete(w);
+ else
+ xtype(x, r);
+}
+
+void
+wmousectl(WinTab *w)
+{
+ int but;
+
+ for(but = 1; but < 6; but++)
+ if(w->mc.buttons == 1<<(but-1))
+ goto found;
+ return;
+found:
+
+ incref(w);
+ if(shiftdown && but > 3)
+ wkeyctl(w, but == 4 ? Kscrolloneup : Kscrollonedown);
+ else if(ptinrect(w->mc.xy, w->text.scrollr) || but > 3)
+ xscroll(&w->text, &w->mc, but);
+ else if(but == 1)
+ xselect(&w->text, &w->mc);
+ wrelease(w);
+}
+
+void
+winctl(WinTab *w, int type)
+{
+ Text *x;
+ int i;
+
+ if(type == Deleted)
+ if(w->notefd >= 0)
+ write(w->notefd, "hangup", 6);
+ if(w->deleted)
+ return;
+
+ x = &w->text;
+ switch(type){
+ case Resized:
+ wsetname(w);
+ w->resized = TRUE;
+ w->wctlready = TRUE;
+ w->mc.buttons = 0; /* avoid re-triggering clicks on resize */
+ w->mq.counter++; /* cause mouse to be re-read */
+ break;
+
+ case Refresh:
+ /* take over window again */
+ draw(w->content, w->content->r, x->cols[BACK], nil, ZP);
+ xfullredraw(&w->text);
+ break;
+
+ case Holdon:
+ wsethold(w, TRUE);
+ break;
+ case Holdoff:
+ wsethold(w, FALSE);
+ break;
+
+ case Rawon:
+ break;
+ case Rawoff:
+// TODO: better to remove one by one? not sure if wkeyctl is safe
+ for(i = 0; i < x->nraw; i++)
+ wkeyctl(w, x->raw[i]);
+ x->nraw = 0;
+ break;
+ }
+}
+
+static void
+winthread(void *arg)
+{
+ WinTab *w;
+ Text *x;
+ Rune r, *rp;
+ char *s;
+ int cm;
+ enum { AKbd, AMouse, ACtl, AConsWrite, AConsRead,
+ AKbdRead, AMouseRead, AWctlRead, AComplete, Agone, NALT };
+ Alt alts[NALT+1];
+ Channel *fsc;
+ Stringpair pair;
+ int i, nb, nr, initial;
+ uint q0;
+ RuneConvBuf cnv;
+ Mousestate m;
+ Completion *comp;
+
+ w = arg;
+ threadsetname("winthread-%d", w->id);
+ w->threadname = threadgetname();
+ x = &w->text;
+ nr = 0;
+ memset(&cnv, 0, sizeof(cnv));
+ fsc = chancreate(sizeof(Stringpair), 0);
+
+ alts[AKbd] = ALT(w->kbd, &s, CHANRCV);
+ alts[AMouse] = ALT(w->mc.c, &w->mc.Mouse, CHANRCV);
+ alts[ACtl] = ALT(w->ctl, &cm, CHANRCV);
+ alts[AConsWrite] = ALT(w->conswrite, &fsc, CHANSND);
+ alts[AConsRead] = ALT(w->consread, &fsc, CHANSND);
+ alts[AKbdRead] = ALT(w->kbdread, &fsc, CHANSND);
+ alts[AMouseRead] = ALT(w->mouseread, &fsc, CHANSND);
+ alts[AWctlRead] = ALT(w->wctlread, &fsc, CHANSND);
+ alts[AComplete] = ALT(w->complete, &comp, CHANRCV);
+ alts[Agone] = ALT(w->gone, nil, CHANNOP);
+ alts[NALT].op = CHANEND;
+
+ for(;;){
+ if(w->deleted){
+ alts[Agone].op = CHANSND;
+ alts[AConsWrite].op = CHANNOP;
+ alts[AConsRead].op = CHANNOP;
+ alts[AKbdRead].op = CHANNOP;
+ alts[AMouseRead].op = CHANNOP;
+ alts[AWctlRead].op = CHANNOP;
+ }else{
+ nr = xninput(x);
+ if(!w->holdmode && (nr >= 0 || cnv.n > 0 || x->rawmode && x->nraw > 0))
+ alts[AConsRead].op = CHANSND;
+ else
+ alts[AConsRead].op = CHANNOP;
+ if(w->scrolling || w->mouseopen || x->qh <= x->org+x->nchars)
+ alts[AConsWrite].op = CHANSND;
+ else
+ alts[AConsWrite].op = CHANNOP;
+ if(w->kbdopen && !qempty(&w->kq))
+ alts[AKbdRead].op = CHANSND;
+ else
+ alts[AKbdRead].op = CHANNOP;
+ if(w->mouseopen && w->mq.counter != w->mq.lastcounter)
+ alts[AMouseRead].op = CHANSND;
+ else
+ alts[AMouseRead].op = CHANNOP;
+ alts[AWctlRead].op = w->wctlready ? CHANSND : CHANNOP;
+ }
+
+ switch(alt(alts)){
+ case AKbd:
+ if(!qadd(&w->kq, s))
+ free(s);
+ if(!w->kbdopen)
+ while(!qempty(&w->kq)){
+ s = qget(&w->kq);
+ if(*s == 'c'){
+ chartorune(&r, s+1);
+ if(r)
+ wkeyctl(w, r);
+ }
+ free(s);
+ }
+ break;
+
+ case AKbdRead:
+ recv(fsc, &pair);
+ nb = 0;
+ while(!qempty(&w->kq)){
+ s = w->kq.q[w->kq.ri];
+ i = strlen(s)+1;
+ if(nb+i > pair.ns)
+ break;
+ qget(&w->kq);
+ memmove((char*)pair.s + nb, s, i);
+ free(s);
+ nb += i;
+ }
+ pair.ns = nb;
+ send(fsc, &pair);
+ break;
+
+ case AMouse:
+ if(w->mouseopen){
+ Mousestate *mp;
+ w->mq.counter++;
+ /* queue click events in ring buffer.
+ * pure movement only in else branch of the case below */
+ if(!w->mq.full && w->mq.lastb != w->mc.buttons){
+ mp = &w->mq.q[w->mq.wi++];
+ w->mq.wi %= nelem(w->mq.q);
+ w->mq.full = w->mq.wi == w->mq.ri;
+ mp->Mouse = w->mc;
+ mp->counter = w->mq.counter;
+ w->mq.lastb = w->mc.buttons;
+ }
+ }else
+ wmousectl(w);
+ break;
+
+ case AMouseRead:
+ recv(fsc, &pair);
+ w->mq.full = FALSE;
+ /* first return queued clicks, then current state */
+ if(w->mq.wi != w->mq.ri){
+ m = w->mq.q[w->mq.ri++];
+ w->mq.ri %= nelem(w->mq.q);
+ }else
+ m = (Mousestate){w->mc.Mouse, w->mq.counter};
+ w->mq.lastcounter = m.counter;
+
+ pair.ns = snprint(pair.s, pair.ns, "%c%11d %11d %11d %11ld ",
+ "mr"[w->resized], m.xy.x, m.xy.y, m.buttons, m.msec);
+ w->resized = FALSE;
+ send(fsc, &pair);
+ break;
+
+ case AConsWrite:
+ recv(fsc, &pair);
+ initial = handlebs(&pair);
+ if(initial){
+ initial = min(initial, x->qh);
+ xdelete(x, x->qh-initial, x->qh);
+ }
+ x->qh = xinsert(x, pair.s, pair.ns, x->qh) + pair.ns;
+ free(pair.s);
+ if(w->scrolling || w->mouseopen)
+ xshow(x, x->qh);
+ xscrdraw(x);
+ break;
+
+ case AConsRead:
+ recv(fsc, &pair);
+ cnvsize(&cnv, pair.ns);
+ nr = r2bfill(&cnv, x->r+x->qh, nr);
+ x->qh += nr;
+ /* if flushed by ^D, skip the ^D */
+ if(!(nr > 0 && x->r[x->qh-1] == '\n') &&
+ x->qh < x->nr && x->r[x->qh] == CTRL('D'))
+ x->qh++;
+ if(x->rawmode){
+ nr = r2bfill(&cnv, x->raw, x->nraw);
+ x->nraw -= nr;
+ runemove(x->raw, x->raw+nr, x->nraw);
+ }
+ r2bfinish(&cnv, &pair);
+ send(fsc, &pair);
+ break;
+
+ case AWctlRead:
+ w->wctlready = FALSE;
+ recv(fsc, &pair);
+Window *ww = w->w;
+ pair.ns = snprint(pair.s, pair.ns, "%11d %11d %11d %11d %11s %11s ",
+ ww->frame->r.min.x, ww->frame->r.min.y,
+ ww->frame->r.max.x, ww->frame->r.max.y,
+ ww->cur == w ? ww == focused ? "current" : "notcurrent"
+ : "tab",
+ ww->hidden ? "hidden" : "visible");
+ send(fsc, &pair);
+ break;
+
+ case ACtl:
+ winctl(w, cm);
+ break;
+
+ case AComplete:
+ if(w->content){
+ if(!comp->advance)
+ showcandidates(w, comp);
+ if(comp->advance){
+ rp = runesmprint("%s", comp->string);
+ if(rp){
+ nr = runestrlen(rp);
+ q0 = x->q0;
+ q0 = xinsert(x, rp, nr, q0);
+ xshow(x, q0+nr);
+ free(rp);
+ }
+ }
+ }
+ freecompletion(comp);
+ break;
+ }
+ flushimage(display, 1);
+
+ /* window is gone, clean up and exit thread */
+ if(w->ref == 0){
+ wfree(w);
+ chanfree(fsc);
+ free(cnv.buf);
+ return;
+ }
+ }
+}
+
+static void
+shellproc(void *args)
+{
+ WinTab *w;
+ Channel *pidc;
+ void **arg;
+ char *cmd, *dir;
+ char **argv;
+
+ arg = args;
+ w = arg[0];
+ pidc = arg[1];
+ cmd = arg[2];
+ argv = arg[3];
+ dir = arg[4];
+ rfork(RFNAMEG|RFFDG|RFENVG);
+ if(fsmount(w->id) < 0){
+ fprint(2, "mount failed: %r\n");
+ sendul(pidc, 0);
+ threadexits("mount failed");
+ }
+ close(0);
+ if(open("/dev/cons", OREAD) < 0){
+ fprint(2, "can't open /dev/cons: %r\n");
+ sendul(pidc, 0);
+ threadexits("/dev/cons");
+ }
+ close(1);
+ if(open("/dev/cons", OWRITE) < 0){
+ fprint(2, "can't open /dev/cons: %r\n");
+ sendul(pidc, 0);
+ threadexits("open"); /* BUG? was terminate() */
+ }
+ /* remove extra ref hanging from creation.
+ * not in main proc here, so be careful with wrelease! */
+ assert(w->ref > 1);
+ wrelease(w);
+
+ notify(nil);
+ dup(1, 2);
+ if(dir)
+ chdir(dir);
+ procexec(pidc, cmd, argv);
+ _exits("exec failed");
+}
+
+int
+wincmd(WinTab *w, int pid, char *dir, char **argv)
+{
+ Channel *cpid;
+ void *args[5];
+
+ if(argv){
+ cpid = chancreate(sizeof(int), 0);
+ assert(cpid);
+ args[0] = w;
+ args[1] = cpid;
+ args[2] = "/bin/rc";
+ args[3] = argv;
+ args[4] = dir;
+ proccreate(shellproc, args, mainstacksize);
+ pid = recvul(cpid);
+ chanfree(cpid);
+ if(pid == 0){
+ wdelete(w->w);
+ return 0;
+ }
+ }
+
+ wsetpid(w, pid, 1);
+ if(dir){
+ free(w->dir);
+ w->dir = estrdup(dir);
+ }
+
+ return pid;
+}
+
+void
+screenoffset(int offx, int offy)
+{
+ Window *w;
+ Point off, delta;
+
+ off = Pt(offx, offy);
+ delta = subpt(off, screenoff);
+ screenoff = off;
+ for(w = bottomwin; w; w = w->higher){
+ if(w->sticky){
+ /* Don't move but cause resize event because
+ * program may want some kind of notification */
+ wmove(w, w->frame->r.min);
+ continue;
+ }
+ if(w->maximized){
+ wrestore(w);
+ wmove(w, subpt(w->frame->r.min, delta));
+ wmaximize(w);
+ }else
+ wmove(w, subpt(w->frame->r.min, delta));
+ }
+
+ flushimage(display, 1);
+}