#include #include #include #include #include #include #include #include #include #include #include "dat.h" #include "fns.h" /* * WASHINGTON (AP) - The Food and Drug Administration warned * consumers Wednesday not to use ``Rio'' hair relaxer products * because they may cause severe hair loss or turn hair green.... * The FDA urged consumers who have experienced problems with Rio * to notify their local FDA office, local health department or the * company at 1‑800‑543‑3002. */ void resize(void); void move(void); void delete(void); void hide(void); void unhide(int); void newtile(int); Image *sweep(void); Image *bandsize(Window*); Image* drag(Window*, Rectangle*); void refresh(Rectangle); void resized(void); Channel *exitchan; /* chan(int) */ Channel *winclosechan; /* chan(Window*); */ Rectangle viewr; int threadrforkflag = 0; /* should be RFENVG but that hides rio from plumber */ void mousethread(void*); void keyboardthread(void*); void winclosethread(void*); void deletethread(void*); void initcmd(void*); char *fontname, *backname; int mainpid; enum { New, Reshape, Move, Delete, Hide, Exit, }; enum { Cut, Paste, Snarf, Plumb, Send, Scroll, }; char *menu2str[] = { [Cut] "cut", [Paste] "paste", [Snarf] "snarf", [Plumb] "plumb", [Send] "send", [Scroll] "scroll", nil }; Menu menu2 = { menu2str }; int Hidden = Exit+1; char *menu3str[8 + nelem(hidden)] = { [New] "New", [Reshape] "Resize", [Move] "Move", [Delete] "Delete", [Hide] "Hide", [Exit] "Exit", nil }; Menu menu3 = { menu3str }; char *rcargv[] = { "rc", "-i", nil }; char *kbdargv[] = { "rc", "-c", nil, nil }; int errorshouldabort = 0; void derror(Display*, char *errorstr) { error(errorstr); } void usage(void) { fprint(2, "usage: rio [-b background] [-f font] [-i initcmd] [-k kbdcmd] [-s]\n"); exits("usage"); } void threadmain(int argc, char *argv[]) { char *initstr, *kbdin, *s; static void *arg[1]; char buf[256]; Image *i; Rectangle r; if(strstr(argv[0], ".out") == nil){ menu3str[Exit] = nil; Hidden--; } initstr = nil; kbdin = nil; maxtab = 0; ARGBEGIN{ case 'b': backname = ARGF(); if(backname == nil) usage(); break; case 'f': fontname = ARGF(); if(fontname == nil) usage(); break; case 'i': initstr = ARGF(); if(initstr == nil) usage(); break; case 'k': if(kbdin != nil) usage(); kbdin = ARGF(); if(kbdin == nil) usage(); break; case 's': scrolling = TRUE; break; }ARGEND mainpid = getpid(); if(getwd(buf, sizeof buf) == nil) startdir = estrdup("."); else startdir = estrdup(buf); if(fontname == nil) fontname = getenv("font"); if(fontname == nil) fontname = "/lib/font/bit/lucm/unicode.9.font"; s = getenv("tabstop"); if(s != nil) maxtab = strtol(s, nil, 0); if(maxtab == 0) maxtab = 4; free(s); /* check font before barging ahead */ if(access(fontname, 0) < 0){ fprint(2, "rio: can't access %s: %r\n", fontname); exits("font open"); } putenv("font", fontname); snarffd = open("/dev/snarf", OREAD|OCEXEC); if(geninitdraw(nil, derror, nil, "rio", nil, Refnone) < 0){ fprint(2, "rio: can't open display: %r\n"); exits("display open"); } iconinit(backname); view = screen; viewr = view->r; mousectl = initmouse(nil, screen); if(mousectl == nil) error("can't find mouse"); mouse = mousectl; keyboardctl = initkeyboard(nil); if(keyboardctl == nil) error("can't find keyboard"); wscreen = allocscreen(screen, background, 0); if(wscreen == nil) error("can't allocate screen"); draw(view, viewr, background, nil, viewr.min); flushimage(display, 1); exitchan = chancreate(sizeof(int), 0); winclosechan = chancreate(sizeof(Window*), 0); deletechan = chancreate(sizeof(char*), 0); timerinit(); threadcreate(keyboardthread, nil, STACK); threadcreate(mousethread, nil, STACK); threadcreate(winclosethread, nil, STACK); threadcreate(deletethread, nil, STACK); filsys = filsysinit(xfidinit()); if(filsys == nil) fprint(2, "rio: can't create file system server: %r\n"); else{ errorshouldabort = 1; /* suicide if there's trouble after this */ if(initstr) proccreate(initcmd, initstr, STACK); if(kbdin){ kbdargv[2] = kbdin; r = screen->r; r.max.x = r.min.x+300; r.max.y = r.min.y+80; i = allocwindow(wscreen, r, Refbackup, DWhite); wkeyboard = new(i, FALSE, scrolling, 0, nil, "/bin/rc", kbdargv); if(wkeyboard == nil) error("can't create keyboard window"); } threadnotify(shutdown, 1); recv(exitchan, nil); } killprocs(); threadexitsall(nil); } /* * /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); if(fd < 0) return; /* snarf buffer could be huge, so fprint will truncate; do it in blocks */ for(i=0; i= 256) n = 256; if(fprint(fd, "%.*S", n, snarf+i) < 0) break; } close(fd); } void getsnarf(void) { int i, n, nb, nulls; char *sn, buf[1024]; if(snarffd < 0) return; sn = nil; i = 0; seek(snarffd, 0, 0); while((n = read(snarffd, buf, sizeof buf)) > 0){ sn = erealloc(sn, i+n+1); memmove(sn+i, buf, n); i += n; sn[i] = 0; } if(i > 0){ snarf = runerealloc(snarf, i+1); cvttorunes(sn, i, snarf, &nb, &nsnarf, &nulls); free(sn); } } void initcmd(void *arg) { char *cmd; cmd = arg; rfork(RFENVG|RFFDG|RFNOTEG|RFNAMEG); procexecl(nil, "/bin/rc", "rc", "-c", cmd, nil); fprint(2, "rio: exec failed: %r\n"); exits("exec"); } char *oknotes[] = { "delete", "hangup", "kill", "exit", nil }; int shutdown(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, "rio %d: abort: %s\n", getpid(), msg); abort(); exits(msg); return 0; } void killprocs(void) { int i; for(i=0; ipid, "hangup"); } void nextwin(void) { Window *w; int i, j, k, t; k = 0; t = 0; for(i=0; itopped > t){ k = i; t = window[i]->topped; } } i = k; k = nwindow; while(k-- > 0){ i = (i + 1) % nwindow; if(w = window[i]){ for(j=0; ji); wtopme(w); break; skip: continue; } } } void keyboardthread(void*) { Rune buf[2][20], *rp; int n, i; static int switcher; threadsetname("keyboardthread"); n = 0; for(;;){ rp = buf[n]; n = 1-n; recv(keyboardctl->c, rp); for(i=1; ic, rp+i) <= 0) break; rp[i] = L'\0'; if(input && strcmp(input->label, "rio")==0){ if(((nwindow - nhidden) > 1) && *rp == 0xe){ nextwin(); continue; } } else { if(*rp == 0xd || *rp == 0xe){ nextwin(); continue; } } if(input != nil) sendp(input->ck, rp); } } /* * Used by /dev/kbdin */ void keyboardsend(char *s, int cnt) { Rune *r; int i, nb, nr; r = runemalloc(cnt); /* BUGlet: partial runes will be converted to error runes */ cvttorunes(s, cnt, r, &nb, &nr, nil); for(i=0; ic, &r[i]); free(r); } int portion(int x, int lo, int hi) { x -= lo; hi -= lo; if(x < 20) return 0; if(x > hi-20) return 2; return 1; } int whichcorner(Window *w, Point p) { int i, j; i = portion(p.x, w->screenr.min.x, w->screenr.max.x); j = portion(p.y, w->screenr.min.y, w->screenr.max.y); return 3*j+i; } void cornercursor(Window *w, Point p, int force) { if(w!=nil && winborder(w, p)) riosetcursor(corners[whichcorner(w, p)], force); else wsetcursor(w, force); } /* thread to allow fsysproc to synchronize window closing with main proc */ void winclosethread(void*) { Window *w; threadsetname("winclosethread"); for(;;){ w = recvp(winclosechan); wclose(w); } } /* thread to make Deleted windows that the client still holds disappear offscreen after an interval */ void deletethread(void*) { char *s; Image *i; threadsetname("deletethread"); for(;;){ s = recvp(deletechan); i = namedimage(display, s); if(i != nil){ /* move it off-screen to hide it, since client is slow in letting it go */ originwindow(i, i->r.min, view->r.max); } freeimage(i); free(s); } } void deletetimeoutproc(void *v) { char *s; s = v; sleep(750); /* remove window from screen after 3/4 of a second */ sendp(deletechan, s); } /* * Button 6 - keyboard toggle - has been pressed. * Send event to keyboard, wait for button up, send that. * Note: there is no coordinate translation done here; this * is just about getting button 6 to the keyboard simulator. */ void keyboardhide(void) { send(wkeyboard->mc.c, mouse); do readmouse(mousectl); while(mouse->buttons & (1<<5)); send(wkeyboard->mc.c, mouse); } void mousethread(void*) { int sending, inside, scrolling, moving, band; Window *oin, *w, *winput; Image *i; Rectangle r; Point xy; Mouse tmp; enum { MReshape, MMouse, NALT }; static Alt alts[NALT+1]; threadsetname("mousethread"); sending = FALSE; scrolling = FALSE; moving = FALSE; alts[MReshape].c = mousectl->resizec; alts[MReshape].v = nil; alts[MReshape].op = CHANRCV; alts[MMouse].c = mousectl->c; alts[MMouse].v = &mousectl->Mouse; alts[MMouse].op = CHANRCV; alts[NALT].op = CHANEND; for(;;) switch(alt(alts)){ case MReshape: resized(); break; case MMouse: if(wkeyboard!=nil && (mouse->buttons & (1<<5))){ keyboardhide(); break; } Again: winput = input; /* override everything for the keyboard window */ if(wkeyboard!=nil && ptinrect(mouse->xy, wkeyboard->screenr)){ /* make sure it's on top; this call is free if it is */ wtopme(wkeyboard); winput = wkeyboard; } if(winput!=nil && winput->i!=nil){ /* convert to logical coordinates */ xy.x = mouse->xy.x + (winput->i->r.min.x-winput->screenr.min.x); xy.y = mouse->xy.y + (winput->i->r.min.y-winput->screenr.min.y); /* the up and down scroll buttons are not subject to the usual rules */ if((mouse->buttons&(8|16)) && !winput->mouseopen) goto Sending; inside = ptinrect(mouse->xy, insetrect(winput->screenr, Selborder)); if(winput->mouseopen) scrolling = FALSE; else if(scrolling) scrolling = mouse->buttons; else scrolling = mouse->buttons && ptinrect(xy, winput->scrollr); /* topped will be zero if window has been bottomed */ if(sending == FALSE && !scrolling && winborder(winput, mouse->xy) && winput->topped>0){ moving = TRUE; }else if(inside && (scrolling || winput->mouseopen || (mouse->buttons&1))) sending = TRUE; }else sending = FALSE; w = wpointto(mouse->xy); if((w != nil) && (w != input) && (!moving) && (!mouse->buttons)){ wcurrent(w); wborder(w, Selborder); flushimage(display, 1); continue; } if(sending){ Sending: if(mouse->buttons == 0){ cornercursor(winput, mouse->xy, 0); sending = FALSE; }else wsetcursor(winput, 0); tmp = mousectl->Mouse; tmp.xy = xy; send(winput->mc.c, &tmp); continue; } /* change cursor if over anyone's border */ if(w != nil) cornercursor(w, mouse->xy, 0); else riosetcursor(nil, 0); if(moving && (mouse->buttons&7)){ oin = winput; band = mouse->buttons & 3; sweeping = 1; if(band) i = bandsize(winput); else i = drag(winput, &r); sweeping = 0; if(i != nil){ if(winput == oin){ if(band) wsendctlmesg(winput, Reshaped, i->r, i); else wsendctlmesg(winput, Moved, r, i); cornercursor(winput, mouse->xy, 1); }else freeimage(i); } } if(w != nil) cornercursor(w, mouse->xy, 0); /* we're not sending the event, but if button is down maybe we should */ if(mouse->buttons){ /* w->topped will be zero if window has been bottomed */ if(w==nil || (w==winput && w->topped>0)){ if(mouse->buttons & 1){ ; }else if(mouse->buttons & 2){ if(winput && !winput->mouseopen) button2menu(winput); }else if(mouse->buttons & 4) button3menu(); }else{ /* if button 1 event in the window, top the window and wait for button up. */ /* otherwise, top the window and pass the event on */ if(wtop(mouse->xy) && (mouse->buttons!=1 || winborder(w, mouse->xy))) goto Again; goto Drain; } } moving = FALSE; break; Drain: do readmouse(mousectl); while(mousectl->buttons); moving = FALSE; goto Again; /* recalculate mouse position, cursor */ } } void resized(void) { Image *im; int i, j, ishidden; Rectangle r; Point o, n; Window *w; if(getwindow(display, Refnone) < 0) error("failed to re-attach window"); freescrtemps(); view = screen; freescreen(wscreen); wscreen = allocscreen(screen, background, 0); if(wscreen == nil) error("can't re-allocate screen"); draw(view, view->r, background, nil, view->r.min); o = subpt(viewr.max, viewr.min); n = subpt(view->clipr.max, view->clipr.min); for(i=0; ideleted) continue; r = rectsubpt(w->i->r, viewr.min); /* r.min.x = (r.min.x*n.x)/o.x; r.min.y = (r.min.y*n.y)/o.y; r.max.x = (r.max.x*n.x)/o.x; r.max.y = (r.max.y*n.y)/o.y; if(Dx(r)<100){ r.max.x = r.min.x + 100; } if(Dy(r)<3*font->height){ r.max.y = r.min.y + 3*font->height; } */ r = rectaddpt(r, screen->clipr.min); ishidden = 0; for(j=0; jchan, 0, DWhite); r = ZR; }else im = allocwindow(wscreen, r, Refbackup, DWhite); if(im) wsendctlmesg(w, Reshaped, r, im); } viewr = screen->r; flushimage(display, 1); } void button3menu(void) { int i; menu3str[Hidden] = "[Hidden]"; for(i=0; ilabel; menu3str[i+Hidden+1] = nil; sweeping = 1; switch(i = piemenuhit(3, mousectl, &menu3, wscreen)){ case -1: break; case New: new(sweep(), FALSE, scrolling, 0, nil, "/bin/rc", nil); break; case Reshape: resize(); break; case Move: move(); break; case Delete: delete(); break; case Hide: hide(); break; case Exit: if(Hidden > Exit){ send(exitchan, nil); break; } /* else fall through */ default: unhide(i); break; } sweeping = 0; } void button2menu(Window *w) { if(w->deleted) return; incref(w); if(w->scrolling) menu2str[Scroll] = "noscroll"; else menu2str[Scroll] = "scroll"; switch(piemenuhit(2, mousectl, &menu2, wscreen)){ case Cut: wsnarf(w); wcut(w); wscrdraw(w); break; case Snarf: wsnarf(w); break; case Paste: getsnarf(); wpaste(w); wscrdraw(w); break; case Plumb: wplumb(w); break; case Send: getsnarf(); wsnarf(w); if(nsnarf == 0) break; if(w->rawing){ waddraw(w, snarf, nsnarf); if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004') waddraw(w, L"\n", 1); }else{ winsert(w, snarf, nsnarf, w->nr); if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004') winsert(w, L"\n", 1, w->nr); } wsetselect(w, w->nr, w->nr); wshow(w, w->nr); break; case Scroll: if(w->scrolling ^= 1) wshow(w, w->nr); break; } wclose(w); wsendctlmesg(w, Wakeup, ZR, nil); flushimage(display, 1); } Point onscreen(Point p) { p.x = max(screen->clipr.min.x, p.x); p.x = min(screen->clipr.max.x, p.x); p.y = max(screen->clipr.min.y, p.y); p.y = min(screen->clipr.max.y, p.y); return p; } Image* sweep(void) { Image *i, *oi; Rectangle r; Point p0, p; i = nil; menuing = TRUE; riosetcursor(&crosscursor, 1); while(mouse->buttons == 0) readmouse(mousectl); p0 = onscreen(mouse->xy); p = p0; r.min = p; r.max = p; oi = nil; while(mouse->buttons == 4){ readmouse(mousectl); if(mouse->buttons != 4 && mouse->buttons != 0) break; if(!eqpt(mouse->xy, p)){ p = onscreen(mouse->xy); r = canonrect(Rpt(p0, p)); if(Dx(r)>5 && Dy(r)>5){ i = allocwindow(wscreen, r, Refnone, 0xEEEEEEFF); /* grey */ freeimage(oi); if(i == nil) goto Rescue; oi = i; border(i, r, Selborder, red, ZP); flushimage(display, 1); } } } if(mouse->buttons != 0) goto Rescue; if(i==nil || Dx(i->r)<100 || Dy(i->r)<3*font->height) goto Rescue; oi = i; i = allocwindow(wscreen, oi->r, Refbackup, DWhite); freeimage(oi); if(i == nil) goto Rescue; border(i, r, Selborder, red, ZP); cornercursor(input, mouse->xy, 1); goto Return; Rescue: freeimage(i); i = nil; cornercursor(input, mouse->xy, 1); while(mouse->buttons) readmouse(mousectl); Return: moveto(mousectl, mouse->xy); /* force cursor update; ugly */ menuing = FALSE; return i; } void drawedge(Image **bp, Rectangle r) { Image *b = *bp; if(b != nil && Dx(b->r) == Dx(r) && Dy(b->r) == Dy(r)) originwindow(b, r.min, r.min); else{ freeimage(b); *bp = allocwindow(wscreen, r, Refbackup, DRed); } } void drawborder(Rectangle r, int show) { static Image *b[4]; int i; if(show == 0){ for(i = 0; i < 4; i++){ freeimage(b[i]); b[i] = nil; } }else{ r = canonrect(r); drawedge(&b[0], Rect(r.min.x, r.min.y, r.min.x+Borderwidth, r.max.y)); drawedge(&b[1], Rect(r.min.x+Borderwidth, r.min.y, r.max.x-Borderwidth, r.min.y+Borderwidth)); drawedge(&b[2], Rect(r.max.x-Borderwidth, r.min.y, r.max.x, r.max.y)); drawedge(&b[3], Rect(r.min.x+Borderwidth, r.max.y-Borderwidth, r.max.x-Borderwidth, r.max.y)); } } Image* drag(Window *w, Rectangle *rp) { Image *i, *ni; Point p, op, d, dm, om; Rectangle r; i = w->i; menuing = TRUE; om = mouse->xy; riosetcursor(&boxcursor, 1); dm = subpt(mouse->xy, w->screenr.min); d = subpt(i->r.max, i->r.min); op = subpt(mouse->xy, dm); drawborder(Rect(op.x, op.y, op.x+d.x, op.y+d.y), 1); flushimage(display, 1); while(mouse->buttons == 4){ p = subpt(mouse->xy, dm); if(!eqpt(p, op)){ drawborder(Rect(p.x, p.y, p.x+d.x, p.y+d.y), 1); flushimage(display, 1); op = p; } readmouse(mousectl); } r = Rect(op.x, op.y, op.x+d.x, op.y+d.y); drawborder(r, 0); cornercursor(w, mouse->xy, 1); moveto(mousectl, mouse->xy); /* force cursor update; ugly */ menuing = FALSE; flushimage(display, 1); if(mouse->buttons!=0 || (ni=allocwindow(wscreen, r, Refbackup, DWhite))==nil){ moveto(mousectl, om); while(mouse->buttons) readmouse(mousectl); *rp = Rect(0, 0, 0, 0); return nil; } draw(ni, ni->r, i, nil, i->r.min); *rp = r; return ni; } Point cornerpt(Rectangle r, Point p, int which) { switch(which){ case 0: /* top left */ p = Pt(r.min.x, r.min.y); break; case 2: /* top right */ p = Pt(r.max.x,r.min.y); break; case 6: /* bottom left */ p = Pt(r.min.x, r.max.y); break; case 8: /* bottom right */ p = Pt(r.max.x, r.max.y); break; case 1: /* top edge */ p = Pt(p.x,r.min.y); break; case 5: /* right edge */ p = Pt(r.max.x, p.y); break; case 7: /* bottom edge */ p = Pt(p.x, r.max.y); break; case 3: /* left edge */ p = Pt(r.min.x, p.y); break; } return p; } Rectangle whichrect(Rectangle r, Point p, int which) { switch(which){ case 0: /* top left */ r = Rect(p.x, p.y, r.max.x, r.max.y); break; case 2: /* top right */ r = Rect(r.min.x, p.y, p.x, r.max.y); break; case 6: /* bottom left */ r = Rect(p.x, r.min.y, r.max.x, p.y); break; case 8: /* bottom right */ r = Rect(r.min.x, r.min.y, p.x, p.y); break; case 1: /* top edge */ r = Rect(r.min.x, p.y, r.max.x, r.max.y); break; case 5: /* right edge */ r = Rect(r.min.x, r.min.y, p.x, r.max.y); break; case 7: /* bottom edge */ r = Rect(r.min.x, r.min.y, r.max.x, p.y); break; case 3: /* left edge */ r = Rect(p.x, r.min.y, r.max.x, r.max.y); break; } return canonrect(r); } Image* bandsize(Window *w) { Image *i; Rectangle r, or; Point p, startp; int which, but; p = mouse->xy; which = whichcorner(w, p); p = cornerpt(w->screenr, p, which); wmovemouse(w, p); readmouse(mousectl); r = whichrect(w->screenr, p, which); drawborder(r, 1); or = r; startp = p; but = mouse->buttons; while(mouse->buttons == but){ p = onscreen(mouse->xy); r = whichrect(w->screenr, p, which); if(!eqrect(r, or) && goodrect(r)){ drawborder(r, 1); flushimage(display, 1); or = r; } readmouse(mousectl); } p = mouse->xy; drawborder(or, 0); flushimage(display, 1); wsetcursor(w, 1); if(mouse->buttons!=0 || Dx(or)<100 || Dy(or)<3*font->height){ while(mouse->buttons) readmouse(mousectl); return nil; } if(abs(p.x-startp.x)+abs(p.y-startp.y) <= 1) return nil; i = allocwindow(wscreen, or, Refbackup, DWhite); if(i == nil) return nil; border(i, r, Selborder, red, ZP); return i; } Window* pointto(int wait) { Window *w; menuing = TRUE; riosetcursor(&sightcursor, 1); while(mouse->buttons == 0) readmouse(mousectl); if(mouse->buttons == 4) w = wpointto(mouse->xy); else w = nil; if(wait) while(mouse->buttons){ if(mouse->buttons!=4 && w !=nil){ /* cancel */ cornercursor(input, mouse->xy, 0); w = nil; } readmouse(mousectl); } cornercursor(input, mouse->xy, 0); moveto(mousectl, mouse->xy); /* force cursor update; ugly */ menuing = FALSE; return w; } void delete(void) { Window *w; w = pointto(TRUE); if(w) wsendctlmesg(w, Deleted, ZR, nil); } void resize(void) { Window *w; Image *i; w = pointto(TRUE); if(w == nil) return; i = sweep(); if(i) wsendctlmesg(w, Reshaped, i->r, i); } void move(void) { Window *w; Image *i; Rectangle r; w = pointto(FALSE); if(w == nil) return; i = drag(w, &r); if(i) wsendctlmesg(w, Moved, r, i); cornercursor(input, mouse->xy, 1); } int whide(Window *w) { Image *i; int j; for(j=0; j