ref: cb5acb9aa60af386de6910318307ed5927c5dc3f
dir: /vexed.c/
#include <u.h> #include <libc.h> #include <thread.h> #include <draw.h> #include <mouse.h> #include <keyboard.h> #include <ctype.h> #include "a.h" void redraw(void); void drawstatus(void); void drawselchange(int); enum { Emouse, Eresize, Ekeyboard, }; enum { Padding = 4, Spacing = 8, Scrollwidth = 14, }; enum { Mundo, Mredo, Msnarfhex, Msnarfascii, Mdecode, Mbinary, Minsert, Mappend, Mdelete, Mgoto, Mlook, Mnext }; char *menu2str[] = { "undo", "redo", "snarf hex", "snarf ascii", "decode", "binary", "insert", "append", "delete", "goto", "look", "next", 0 }; Menu menu2 = { menu2str }; enum { Msave, Mquit, }; char *menu3str[] = { "save", "quit", 0 }; Menu menu3 = { menu3str }; const char *filename; int modified; Buffer buf; int blines; Mousectl *mctl; Keyboardctl *kctl; int sw; int scrollsize; Rectangle scrollr; Rectangle viewr; Rectangle statusr; int scrolling; int lastbuttons; int nlines; int offset; int sel = 0; int sele = -1; uchar sbuf[255] = {0}; int nsbuf; char sstr[256] = {0}; int sindex = -1; int dimnul; int hexval(Rune k) { int v; if(isdigit(k)) v = k - '0'; else v = 10 + tolower(k) - 'a'; return v; } int search(int from) { char *s, *p; int len, oldsel; s = (char*)buf.data + from; for(;;){ len = s - (char*)buf.data; if(len >= buf.count) break; p = memchr(s, sbuf[0], buf.count - len); if(p == nil || (nsbuf > 1 && memcmp(p, sbuf, nsbuf) != 0)){ s = p + 1; continue; } oldsel = sel; sel = p - (char*)buf.data; drawselchange(oldsel); sindex = sel; return 1; } return 0; } void xundo(void) { Undo u; if(!canundo()) return; undo(&u); switch(u.action){ case Udelete: if(insert(&buf, u.index) < 0) sysfatal("insert: %r"); buf.data[u.index] = u.value; break; case Uinsert: if(delete(&buf, u.index) < 0) sysfatal("delete: %r"); break; case Uappend: if(delete(&buf, u.index + 1) < 0) sysfatal("delete: %r"); break; case Uset: buf.data[u.index] = u.value; break; } sel = u.index; modified = u.modified; blines = buf.count / 16; redraw(); } void xredo(void) { Undo u; if(!canredo()) return; redo(&u); switch(u.action){ case Udelete: if(delete(&buf, u.index) < 0) sysfatal("insert: %r"); sel = u.index; break; case Uinsert: if(insert(&buf, u.index) < 0) sysfatal("insert: %r"); sel = u.index; break; case Uappend: if(insert(&buf, u.index + 1) < 0) sysfatal("insert: %r"); sel = u.index + 1; break; case Uset: buf.data[u.index] = u.newvalue; break; } if(sel == buf.count) --sel; modified = 1; blines = buf.count / 16; redraw(); } void xsnarfhex(void) { int fd, i, n, m; fd = open("/dev/snarf", OWRITE|OCEXEC); if(fd < 0) return; if(sele == -1) fprint(fd, "%02X", buf.data[sel]); else{ n = sel < sele ? sel : sele; m = sel < sele ? sele : sel; for(i = n; i <= m; i++) fprint(fd, "%02X", buf.data[i]); } close(fd); } void xsnarfascii(void) { int fd, n, m; fd = open("/dev/snarf", OWRITE|OCEXEC); if(fd < 0) return; if(sele == -1) write(fd, &buf.data[sel], 1); else{ n = sel < sele ? sel : sele; m = sel < sele ? sele : sel; write(fd, &buf.data[n], m - n + 1); } close(fd); } void xgoto(void) { char b[16] = {0}, *endp; int n; if(enter("Address:", b, sizeof b, mctl, kctl, nil) <= 0) return; n = strtol(b, &endp, 0); if(endp == nil || endp == b) return; if(n < 0 || n >= buf.count) return; sel = n; redraw(); } void xdelete(void) { pushundo(Udelete, sel, buf.data[sel], 0, modified); if(delete(&buf, sel) < 0) sysfatal("delete: %r"); if(sel == buf.count) --sel; modified = 1; blines = buf.count/16; redraw(); } void xinsert(void) { pushundo(Uinsert, sel, 0, 0, modified); if(insert(&buf, sel) < 0) sysfatal("insert: %r"); modified = 1; blines = buf.count/16; redraw(); } void xappend(void) { pushundo(Uappend, sel, 0, 0, modified); if(append(&buf, sel) < 0) sysfatal("append: %r"); sel += 1; modified = 1; blines = buf.count/16; redraw(); } void xbinary(void) { char tmp[9] = {0}; char msg[19] = "Binary (xxxxxxxx):"; char out = 0; int n, i; for(i = 0; i < 8; i++) msg[15 - i] = (buf.data[sel] & 1 << i) ? '1' : '0'; n = enter(msg, tmp, sizeof tmp, mctl, kctl, nil); if(n <= 0) return; for(i = 0; i < 8 && i < n; i++){ if(tmp[7-i] != '0') out |= 1 << i; } buf.data[sel] = out; modified = 1; redraw(); } void xlook(void) { char tmp[255] = {0}; int n, i; n = enter("Look:", tmp, sizeof tmp, mctl, kctl, nil); if(n <= 0) return; if(n%2 != 0){ showerr("invalid byte sequence", mctl, kctl); return; } nsbuf = 0; sindex = -1; for(i = 0; i < n; i += 2){ if(!(isxdigit(tmp[i]) && isxdigit(tmp[i+1]))){ showerr("invalid character in byte sequence", mctl, kctl); return; } sbuf[nsbuf++] = 16*hexval(tmp[i]) + hexval(tmp[i+1]); } sbuf[nsbuf] = 0; snprint(sstr, sizeof sstr, "/%s", tmp); if(!search(sel)){ sindex = -1; } } void xnext(void) { if(sindex == -1) return; if(!search(sindex + 1)) search(0); } void xdecode(void) { uchar b[8] = {0}; int n, m, c; if(sele == -1){ b[7] = buf.data[sel]; }else{ n = sel < sele ? sel : sele; m = sel < sele ? sele : sel; c = m - n + 1; if(c > 8){ showerr("cannot decode more than 8 bytes", mctl, kctl); return; } memcpy(&b[8 - c], &buf.data[n], c); } showdec(b, mctl, kctl); } void save(void) { if(!modified) return; if(writefile(&buf, filename) < 0) sysfatal("writefile: %r"); modified = 0; redraw(); } int selvisible(void) { int l; l = sel/16; return offset <= l && l < offset+nlines; } int ensureselvisible(void) { if(selvisible()) return 0; offset = sel/16; return 1; } int isselected(int index) { int selected; if(sele == -1) selected = index == sel; else if(sel < sele) selected = index >= sel && index <= sele; else selected = index >= sele && index <= sel; return selected; } void drawline(int line) { int y, index, i, n, selected, hs; char b[8] = {0}, *s; Point p; Point p2; Image *c; y = viewr.min.y + line * font->height; index = (line + offset)*16; if(index >= buf.count) return; draw(screen, Rect(viewr.min.x, y, viewr.max.x, y + font->height), cols[BACK], nil, ZP); p = Pt(viewr.min.x, y); if(index/16 == sel/16){ n = snprint(b, sizeof b, "%06X", sel); p = stringnbg(screen, p, cols[BACK], ZP, font, b, n, cols[ADDR], ZP); }else{ n = snprint(b, sizeof b, "%06X", index); p = stringn(screen, p, cols[ADDR], ZP, font, b, n); } p.x += 2*Spacing; p2 = addpt(p, Pt(16*3*sw - sw + 2*Spacing, 0)); for(i = 0; i < 16; i++) { if(index + i >= buf.count) break; n = snprint(b, sizeof b, "%02X", buf.data[index + i]); s = isprint(buf.data[index + i]) ? (char*)&buf.data[index + i] : "."; selected = isselected(index + i); if(selected){ p = stringnbg(screen, p, cols[HHEX], ZP, font, b, n, cols[HIGH], ZP); p2 = stringnbg(screen, p2, cols[BACK], ZP, font, s, 1, cols[ASCII], ZP); }else{ c = dimnul && buf.data[index+i] == 0 ? cols[DHEX] : cols[HEX]; p = stringn(screen, p, c, ZP, font, b, n); p2 = stringn(screen, p2, cols[ASCII], ZP, font, s, 1); } hs = 0; if(selected && sele != -1){ if(sel < sele) hs = index + i != sele; else hs = index + i != sel; } if(hs) p = stringnbg(screen, p, cols[BACK], ZP, font, " ", 1, cols[HIGH], ZP); else p = stringn(screen, p, cols[BACK], ZP, font, " ", 1); } } void drawselchange(int oldsel) { int ol, nl; if(ensureselvisible()) redraw(); else{ ol = oldsel/16 - offset; nl = sel/16 - offset; if(ol != nl) drawline(ol); drawline(nl); drawstatus(); flushimage(display, 1); } } void drawstatus(void) { char b[64] = {0}; Point p; int x, y; draw(screen, statusr, cols[BACK], nil, ZP); y = statusr.min.y + Padding; p = string(screen, Pt(statusr.min.x + Padding, y), cols[HEX], ZP, font, filename); if(modified) string(screen, p, cols[SCROLL], ZP, font, " (modified)"); snprint(b, sizeof b, "%d/%zld (%d%%)", sel+1, buf.count, (int)((100.0 * sel) / buf.count + 0.5)); x = statusr.max.x - stringwidth(font, b) - Padding; string(screen, Pt(x, y), cols[HEX], ZP, font, b); y = statusr.max.y; line(screen, Pt(statusr.min.x, y), Pt(statusr.max.x, y), 0, 0, 0, cols[HEX], ZP); } void redraw(void) { int i, h, y, ye; Rectangle scrposr; draw(screen, screen->r, cols[BACK], nil, ZP); draw(screen, scrollr, cols[SCROLL], nil, ZP); border(screen, scrollr, 0, cols[HEX], ZP); if(blines > 0){ h = ((double)nlines / blines) * Dy(scrollr); y = ((double)offset / blines) * Dy(scrollr); if(h == 0) h = 5; ye = scrollr.min.y + y + h - 1; if(ye >= scrollr.max.y) ye = scrollr.max.y - 1; scrposr = Rect(scrollr.min.x, scrollr.min.y + y, scrollr.max.x - 1, ye); }else scrposr = Rect(scrollr.min.x, scrollr.min.y, scrollr.max.x-1, scrollr.max.y); draw(screen, scrposr, cols[BACK], nil, ZP); for(i = 0; i < nlines; i++) drawline(i); drawstatus(); flushimage(display, 1); } void clampoffset(void) { if(offset < 0) offset = 0; if(offset + blines%nlines >= blines) offset = blines - blines%nlines; } int scroll(int lines) { if(blines <= nlines) return 0; if(lines < 0 && offset == 0) return 0; if(lines > 0 && offset + nlines >= blines){ return 0; } offset += lines; clampoffset(); sel += lines * 16; if(sel < 0) sel = 0; else if(sel >= buf.count) sel = buf.count - 1; redraw(); return 1; } int indexat(Point p) { int row, col, index; row = (p.y - viewr.min.y) / font->height; col = ((p.x - viewr.min.x) / sw - 8); if(col < 0 || col > 16*3*sw) return -1; index = col/3 + row*16 + offset*16; if(index >= buf.count) return -1; return index; } void menu2hit(void) { int n; menu2str[Mnext] = sindex == -1 ? nil : sstr; n = menuhit(2, mctl, &menu2, nil); switch(n){ case Mundo: xundo(); break; case Mredo: xredo(); break; case Msnarfhex: xsnarfhex(); break; case Msnarfascii: xsnarfascii(); break; case Mdecode: xdecode(); break; case Mgoto: xgoto(); break; case Mdelete: xdelete(); break; case Minsert: xinsert(); break; case Mappend: xappend(); break; case Mbinary: xbinary(); break; case Mlook: xlook(); break; case Mnext: xnext(); break; } } void menu3hit(void) { int n; n = menuhit(3, mctl, &menu3, nil); switch(n){ case Msave: save(); break; case Mquit: threadexitsall(nil); break; } } void emouse(Mouse *m) { int n; if(lastbuttons == 0 && m->buttons != 0 && ptinrect(m->xy, scrollr)) scrolling = 1; else if(m->buttons == 0) scrolling = 0; if(scrolling){ if(m->buttons == 1){ n = (m->xy.y - scrollr.min.y) / font->height; scroll(-n); }else if(m->buttons == 2){ offset = (m->xy.y - scrollr.min.y) * blines / Dy(scrollr); clampoffset(); sel = offset*16; redraw(); }else if(m->buttons == 4){ n = (m->xy.y - scrollr.min.y) / font->height; scroll(n); } } else if(ptinrect(m->xy, viewr)){ if(m->buttons == 1){ n = indexat(m->xy); if(n >= 0){ sel = n; sele = -1; redraw(); for(readmouse(mctl); mctl->buttons == 1; readmouse(mctl)){ n = indexat(mctl->xy); if(n < 0) break; sele = n; redraw(); } } }else if(m->buttons == 2){ menu2hit(); }else if(m->buttons == 4){ menu3hit(); }else if(m->buttons == 8){ scroll(-scrollsize); }else if(m->buttons == 16){ scroll(scrollsize); } } lastbuttons = m->buttons; } void eresize(void) { Rectangle sr; int w, x; sr = screen->r; statusr = Rect(sr.min.x, sr.min.y, sr.max.x, sr.min.y + Padding + font->height + 1); scrollr = insetrect(Rect(sr.min.x, statusr.max.y+1, sr.min.x+Scrollwidth, sr.max.y), 1); sw = stringwidth(font, " "); w = Padding + 6*sw + 2*Spacing + 16*3*sw-sw + 2*Spacing + 16*sw + Padding; x = scrollr.max.x + Padding; viewr = insetrect(Rect(x, statusr.max.y + 1, x + w, sr.max.y + Padding - 2), Padding); nlines = Dy(viewr) / font->height; scrollsize = mousescrollsize(nlines); redraw(); flushimage(display, 1); } void ekeyboard(Rune k) { static long lastk = 0; static int lastv; long e; int oldsel; if(!isxdigit(k)) lastv = -1; e = time(nil); oldsel = sel; if(sele != -1){ sele = -1; redraw(); } switch(k){ case 'q': case Kdel: threadexitsall(nil); break; case Kpgup: scroll(-nlines); break; case Kpgdown: scroll(nlines); break; case Kleft: if(sel > 0){ oldsel = sel; sel--; drawselchange(oldsel); } break; case Kright: if(sel < (buf.count - 1)){ sel++; drawselchange(oldsel); } break; case Kup: if(sel >= 16){ sel -= 16; drawselchange(oldsel); } break; case Kdown: if(sel < (buf.count - 16)){ sel += 16; drawselchange(oldsel); } break; case Khome: if(sel != 0){ sel = 0; drawselchange(oldsel); } break; case Kend: if(sel != buf.count - 1){ sel = buf.count - 1; if(!selvisible()) offset = blines - blines%nlines; redraw(); } break; case 'u': case 'U': xundo(); break; case 'r': case 'R': xredo(); break; case 'g': case 'G': xgoto(); break; case 'x': case 'X': xdelete(); break; case 'i': case 'I': xinsert(); break; case 'p': case 'P': xappend(); break; case '.': xbinary(); break; case 'l': case 'L': xlook(); break; case 'n': case 'N': xnext(); break; case 's': case 'S': save(); break; case '?': xdecode(); break; default: if(isxdigit(k)){ if(e - lastk < 2 && lastv > 0) { buf.data[sel] = lastv*16 + hexval(k); patchundo(buf.data[sel]); lastv = -1; }else{ lastv = hexval(k); pushundo(Uset, sel, buf.data[sel], lastv, modified); buf.data[sel] = lastv; } modified = 1; drawselchange(oldsel); drawstatus(); }else{ lastv = -1; } } lastk = e; } void usage(void) { fprint(2, "usage: %s [-b] <filename>\n", argv0); threadexitsall("usage"); } void threadmain(int argc, char *argv[]) { Mouse m; Rune k; Alt alts[] = { { nil, &m, CHANRCV }, { nil, nil, CHANRCV }, { nil, &k, CHANRCV }, { nil, nil, CHANEND }, }; int reverse; scrolling = 0; lastbuttons = 0; reverse = 0; dimnul = 0; ARGBEGIN{ case 'b': reverse = ~0xFF; break; case 'd': dimnul = 1; break; default: usage(); }ARGEND; if(*argv == nil) usage(); filename = *argv; modified = 0; if(readfile(&buf, filename) < 0) sysfatal("readfile: %r"); blines = buf.count/16; if(initdraw(nil, nil, argv0) < 0) sysfatal("initdraw: %r"); display->locking = 0; if((mctl = initmouse(nil, screen)) == nil) sysfatal("initmouse: %r"); if((kctl = initkeyboard(nil)) == nil) sysfatal("initkeyboard: %r"); initcols(reverse); alts[Emouse].c = mctl->c; alts[Eresize].c = mctl->resizec; alts[Ekeyboard].c = kctl->c; eresize(); for(;;){ switch(alt(alts)){ case Emouse: emouse(&m); break; case Eresize: if(getwindow(display, Refnone) < 0) sysfatal("getwindow: %r"); eresize(); break; case Ekeyboard: ekeyboard(k); break; } } }