ref: b1fc24bed0a7a676d7f5927cd860724425189dbe
dir: /plan9.c/
#include "plan9.h" #include "field.h" #include "gbuffer.h" #include "sim.h" #include <bio.h> #include <draw.h> #include <mouse.h> #include <keyboard.h> #include <thread.h> #define MIN(x,y) ((x)<=(y)?(x):(y)) #define MAX(x,y) ((x)>=(y)?(x):(y)) #define is_movement(c) (c == 'W' || c == 'N' || c == 'E' || c == 'S') #define is_send(c) (c == ':' || c == '%' || c == '!' || c == '?' || c == ';' || c == ';' || c == '$') enum { Txtoff = 16, Coloff = 2, Cresize = 0, Ckey, Cmouse, Credraw, Numchan, Sfancy = 0, Splain, Snone, Numstyles, Minsert = 0, Mappend, Mslide, Mselect, Nummodes, Menu3load = 0, Menu3save, Menu3dotstyle, Menu3rulerstyle, Menu3exit, Nummenu3, Dback = 0, Dfhigh, Dfmed, Dflow, Dfinv, Dbmed, Dbinv, Numcolors, /* this might become a bad idea in the future */ Mark_flag_group_highlight = 1<<6, Mark_flag_selected = 1<<7, Midicn = 0<<4, /* cable number. FIXME: write midifs */ }; typedef struct Snap Snap; struct Snap { int bpm, apm; vlong tick; Point cur, scroll; Rectangle sel; Field field; }; static int bpm = 120, apm = 120; static Point rulers = {8, 8}; static int rulerstyle = Sfancy, dotstyle = Sfancy; static char *ip, *udpport; static int udp = -1; static char *midipath; static int midi = -1; static u8int noteoff[16][128]; /* 16 channels, 128 notes each */ static Rune *linebuf; static Rune cursor = '@'; static vlong tick; static Point glyphsz; static Point cur, scroll, txtoff, move = { .x = 1, .y = 1 }; static Rectangle sel; static Field field; static Mbuf_reusable mbuf, mscr; static char filename[256]; static Field fscr, fsel; static bool altdown, shiftdown, ctldown, pause, forward; static int mode = Minsert; static long framedev; /* frame deviation in ms */ static char *shellcmd; static Snap *snaps; static int numsnaps, nextsnap; static int maxsnaps = 100; static struct { struct { char *menu; Rune r; }dot; struct { char *menu; Rune r[9]; }ruler; }styles[Numstyles] = { [Sfancy] = { .dot = {"fancy dots", L'·'}, .ruler = {"fancy rulers", {L'╭', L'┬', L'╮', L'├', L'┼', L'┤', L'╰', L'┴', L'╯'}}, }, [Splain] = { .dot = {"plain dots", '.'}, .ruler = {"plain rulers", {'+', '+', '+', '+', '+', '+', '+', '+', '+'}}, }, [Snone] = { .dot = {"no dots", ' '}, .ruler = {"no rulers", {0}}, }, }; static u32int theme[Numcolors] = { [Dback] = 0x000000ff, [Dfhigh] = 0xffffffff, [Dfmed] = 0x777777ff, [Dflow] = 0x272727ff, [Dfinv] = 0x000000ff, [Dbmed] = 0x72dec2ff, [Dbinv] = 0xffb545ff, }; static Image *color[Numcolors]; static char *modes[Nummodes] = { [Minsert] = "insert", [Mappend] = "append", [Mslide] = "slide ", [Mselect] = "select", }; static char *menu3i[Nummenu3+1] = { [Menu3load] = "load", [Menu3save] = "save", [Menu3exit] = "exit", }; static Menu menu3 = { .item = menu3i, }; static void snapshot(void) { Snap *s; int i; if (maxsnaps < 1) return; if (nextsnap >= maxsnaps) { nextsnap--; field_deinit(&snaps->field); memmove(snaps, snaps+1, sizeof(Snap)*nextsnap); } s = &snaps[nextsnap++]; s->bpm = bpm; s->apm = apm; s->tick = tick; s->cur = cur; s->scroll = scroll; s->sel = sel; for (i = nextsnap; i < numsnaps; i++) field_deinit(&snaps[i].field); field_init(&s->field); field_copy(&field, &s->field); numsnaps = nextsnap; } static void gotosnapshot(int i) { Snap *s; s = &snaps[i]; bpm = s->bpm; apm = s->apm; tick = s->tick; cur = s->cur; scroll = s->scroll; sel = s->sel; field_copy(&s->field, &field); } static void undo(void) { if (nextsnap < 1) return; if (nextsnap == numsnaps) { snapshot(); nextsnap--; } gotosnapshot(--nextsnap); } static void redo(void) { if (nextsnap >= numsnaps-1) return; gotosnapshot(++nextsnap); if (nextsnap == numsnaps) nextsnap--; } Usz orca_round_up_power2(Usz x) { x -= 1; x |= x >> 1; x |= x >> 2; x |= x >> 4; x |= x >> 8; x |= x >> 16; return x + 1; } bool orca_is_valid_glyph(Glyph c) { if (c >= '0' && c <= '9') return true; if (c >= 'A' && c <= 'Z') return true; if (c >= 'a' && c <= 'z') return true; switch (c) { case '!': case '#': case '%': case '*': case '.': case ':': case ';': case '=': case '?': case '$': return true; } return false; } static Glyph fieldget(int x, int y) { if (x < field.width && y < field.height && x >= 0 && y >= 0) return field.buffer[x + field.width*y]; return 0; } static void fieldset(int x, int y, Glyph c) { if (x < field.width && y < field.height && x >= 0 && y >= 0) field.buffer[x + field.width*y] = c; } static void fieldsetn(int x, int y, Glyph c, int n) { if (y >= 0 && y < field.height) { for (; n > 0 && x < field.width; n--, x++) field.buffer[x + field.width*y] = c; } } static void selpasteb(Biobuf *b) { char *s; int cols, rows, i, n, off; Glyph c; for (cols = rows = 0; (s = Brdstr(b, '\n', 1)) != nil;) { if ((n = Blinelen(b)) > cols) cols = MIN(n, field.width-sel.min.x); if (sel.min.y+rows < field.height) { off = sel.min.x + field.width*(sel.min.y+rows); for (i = 0; i < MIN(n, field.width-sel.min.x); i++) { c = orca_is_valid_glyph(s[i]) ? s[i] : '.'; if (c != '.' || !shiftdown) field.buffer[off+i] = c; } rows++; } free(s); } sel.max.x = sel.min.x + MAX(0, cols-1); sel.max.y = sel.min.y + MAX(0, rows-1); if (sel.max.x < cur.x) cur.x = sel.max.x; if (sel.max.y < cur.y) cur.y = sel.max.y; } static void runshell(void *x) { int *p; p = x; dup(p[0], 0); close(p[0]); close(p[1]); dup(p[3], 1); close(p[3]); close(p[2]); dup(open("/dev/null", OWRITE), 2); procexecl(nil, "/bin/rc", "rc", "-c", shellcmd, nil); threadexits("exec: %r"); } static void shellpipe(char *s) { Biobuf *in, *out; int x, y, p[4]; Glyph g; shellcmd = s+1; pipe(p); pipe(p+2); procrfork(runshell, p, 4096, RFFDG); close(p[0]); close(p[3]); out = Bfdopen(p[1], OWRITE); in = Bfdopen(p[2], OREAD); for (y = sel.min.y; *s != '<' && y <= sel.max.y; y++) { for (x = sel.min.x; x <= sel.max.x; x++) { if ((g = fieldget(x, y)) == '.') g = ' '; Bputc(out, g); } Bputc(out, '\n'); } Bterm(out); if (*s != '>') selpasteb(in); Bterm(in); } static void midiopen(char *path) { if (midi >= 0) { close(midi); midi = -1; } if (path != nil) { free(midipath); midipath = strdup(path); } if (midi < 0 && midipath[0] && (midi = open(midipath, OWRITE)) < 0) { fprint(2, "midi failed: %r\n"); /* FIXME display error */ } } static void netdial(char *newip, char *newudpport) { if (udp >= 0) { close(udp); udp = -1; } if (newip != nil) { free(ip); ip = strdup(newip); } if (newudpport != nil) { free(udpport); udpport = strdup(newudpport); } if (udp < 0 && ip[0] && udpport[0]) { if (udpport[0] == '/') udp = open(udpport, OWRITE); else udp = dial(netmkaddr(ip, "udp", udpport), nil, nil, nil); if (udp < 0) /* FIXME display error */ fprint(2, "udp failed: %r\n"); } } static void selset(Rune key) { int y; bool commented; if (key == '#') { commented = true; for (y = sel.min.y; y <= sel.max.y && commented; y++) commented = fieldget(sel.min.x, y) == key && fieldget(sel.max.x, y) == key; if (commented) key = '.'; } else { commented = false; } for (y = sel.min.y; y <= sel.max.y; y++) { if (key == '#' || commented) { fieldset(sel.min.x, y, key); fieldset(sel.max.x, y, key); } else { fieldsetn(sel.min.x, y, key, Dx(sel)+1); } } } static void selcopy(void) { Biobuf *b; int y; if ((b = Bopen("/dev/snarf", OWRITE)) != nil) { for (y = sel.min.y; y <= sel.max.y; y++) { Bwrite(b, &field.buffer[sel.min.x + field.width*y], Dx(sel)+1); Bputc(b, '\n'); } Bterm(b); } } static void selpaste(void) { Biobuf *b; if ((b = Bopen("/dev/snarf", OREAD)) != nil) selpasteb(b); } static void nosnapshot(void) { } static void command(char *s, void (*snapshot)(void)) { char tmp[256]; char *a, *b; int x, y; snprint(tmp, sizeof(tmp), "%s", s); s = tmp; if (s[0] == ',') { snapshot(); cur = ZP; sel = Rect(0, 0, field.width, field.height); s++; } if (s[0] == '|' || s[0] == '>' || s[0] == '<') { if (s[0] != '>') snapshot(); shellpipe(s); return; } if ((a = strpbrk(s, " \t")) != nil) *a++ = 0; if (s[0] == 'p' && s[1] == 'l') /* play */ pause = false; else if (s[0] == 's' && s[1] == 't') /* stop */ pause = true; else if (s[0] == 'r' && s[1] == 'u') /* run */ forward = true; else if (s[0] == 'c' && s[1] == 'o') /* copy */ selcopy(); else if (s[0] == 'p' && s[1] == 'a') { /* paste */ snapshot(); selpaste(); } else if (s[0] == 'e' && s[1] == 'r') { /* erase */ snapshot(); selset('.'); } else if (s[0] == 'p' && s[1] == 'r') { /* print */ for (y = sel.min.y; y <= sel.max.y; y++) { for (x = sel.min.x; x <= sel.max.x; x++) putchar(field.buffer[x + y*field.width]); putchar('\n'); } fflush(stdout); } else if (a != nil) { x = atoi(a); if (s[0] == 'b' && s[1] == 'p') /* bpm */ apm = bpm = MAX(1, x); else if (s[0] == 'a' && s[1] == 'p') /* apm */ apm = MAX(1, x); else if (s[0] == 'f' && s[1] == 'r') /* frame */ tick = MAX(0, x); else if (s[0] == 's' && s[1] == 'k') /* skip */ tick = MAX(0, tick+x); else if (s[0] == 'r' && s[1] == 'e') /* rewind */ tick = MAX(0, tick-x); else if (s[0] == 'i' && s[1] == 'p') /* ip */ netdial(a, nil); else if (s[0] == 'u' && s[1] == 'd') /* udp */ netdial(nil, a); else if (s[0] == 'm' && s[1] == 'i') /* midi */ midiopen(a); else if (s[0] == 'w' && s[1] == 'r') { /* write:abcd;13;14 */ b = a; x = cur.x; y = cur.y; if ((a = strpbrk(b, " \t")) != nil) { *a++ = 0; x = atoi(a); if ((a = strpbrk(a, " \t")) != nil) y = atoi(++a); } for (; *b != 0; x++, b++) fieldset(x, y, *b); } /* FIXME color, find, select, inject, write, time */ } else if (strcmp(s, "undo") == 0) { undo(); } else if (strcmp(s, "redo") == 0) { redo(); } } static void process(Oevent_list *events) { int c, n, t; Oevent *e; u8int u[4]; char tmp[64]; for (e = events->buffer; e != events->buffer+events->count; e++) { t = e->any.oevent_type; if (midi >= 0) { /* * USB MIDI allegedly receives bulk transfers of a certain max size * so a smarter thing to do would be to buffer up all notes on/off * and write them in bulk after that. * But a better way to do it would be through midifs, so it's * implemented once. Still need to list available midi devices anyway. */ if (t == Oevent_type_midi_note) { Oevent_midi_note *m = &e->midi_note; if (m->mono) { /* idk if that's the right thing to do, just stop what's been playing on that channel */ u[0] = Midicn | 0x8; u[1] = 0x80 | m->channel; u[3] = 0; for (n = 0; n < nelem(noteoff[m->channel]); n++) { if (noteoff[m->channel][n] > 0) { noteoff[m->channel][n] = 0; u[2] = n; write(midi, u, 4); } } } u[0] = Midicn | 0x9; u[1] = 0x90 | m->channel; u[2] = (m->octave + 1)*12 + m->note; u[3] = m->velocity; write(midi, u, 4); noteoff[m->channel][u[2]] = m->duration + 1; continue; } else if (t == Oevent_type_midi_cc) { Oevent_midi_cc *c = &e->midi_cc; u[0] = Midicn | 0xb; u[1] = 0xb0 | c->channel; u[2] = c->control; u[3] = c->value; write(midi, u, 4); continue; } else if (t == Oevent_type_midi_pb) { Oevent_midi_pb *p = &e->midi_pb; u[0] = Midicn | 0xe; u[1] = 0xe0 | p->channel; u[2] = p->lsb; u[3] = p->msb; write(midi, u, 4); continue; } } if (t == Oevent_type_udp_string && udp >= 0) { Oevent_udp_string *u = &e->udp_string; write(udp, u->chars, u->count); /* FIXME show errors */ continue; } else if (t == Oevent_type_cmd_string) { Oevent_cmd_string *c = &e->cmd_string; memmove(tmp, c->chars, c->count); tmp[c->count] = 0; command(tmp, nosnapshot); } } if (midi >= 0) { u[3] = 0; for (c = 0; c < nelem(noteoff); c++) { for (n = 0; n < nelem(noteoff[c]); n++) { if (noteoff[c][n] > 0 && --noteoff[c][n] == 0) { u[0] = Midicn | 0x8; u[1] = 0x80 | c; u[2] = n; write(midi, u, 4); } } } } } /* * nsec() is wallclock and can be adjusted by timesync * so need to use cycles() instead, but fall back to * nsec() in case we can't * * "fasthz" is how many ticks there are in a second * can be read from /dev/time * * perhaps using RDTSCP is even better */ static uvlong nanosec(void) { static uvlong fasthz, xstart; uvlong x, div; int f, n, i; char tmp[128], *e; if (fasthz == ~0ULL) return nsec() - xstart; if (fasthz == 0) { fasthz = ~0ULL; if ((f = open("/dev/time", OREAD)) >= 0 && (n = read(f, tmp, sizeof(tmp)-1)) > 2) { tmp[n] = 0; e = tmp; for (i = 0; i < 3; i++) strtoll(e, &e, 10); if ((fasthz = strtoll(e, nil, 10)) < 1) fasthz = ~0ULL; else cycles(&xstart); } close(f); if (fasthz == ~0ULL) { fprint(2, "couldn't get fasthz, falling back to nsec()\n"); fprint(2, "you might want to disable aux/timesync\n"); xstart = nsec(); return 0; } } cycles(&x); x -= xstart; for (div = 1000000000ULL; x < 0x1999999999999999ULL && div > 1 ; div /= 10ULL, x *= 10ULL); return x / (fasthz / div); } static void orcathread(void *drawchan) { vlong start, end, n; vlong processold, processnew; Oevent_list events; int w, h; threadsetname("orca/sim"); oevent_list_init(&events); processnew = nanosec(); for (;;) { start = nanosec(); w = field.width; h = field.height; mbuffer_clear(mbuf.buffer, h, w); oevent_list_clear(&events); orca_run(field.buffer, mbuf.buffer, h, w, tick, &events, 0); processold = processnew; processnew = nanosec(); process(&events); nbsendul(drawchan, 0); forward = false; do { end = 15000000000LL/bpm; /* 1e9*60/4 */ n = nanosec() - start; if (n >= end && !pause) break; /* unpause is not precise at all */ if (pause || end - n > 750000000LL) sleep(70); else if (end - n > 25000000LL) sleep(20); else if (end - n > 10000000LL) sleep(1); } while (!forward); framedev = (processnew - processold - 15000000000LL/bpm)/1000000LL; tick++; if (apm < bpm) bpm--; else if (apm > bpm) bpm++; } } static void redraw(int complete) { static Point oldscroll; Rectangle r; Point p, top, bot; int x, y, rx, ry, i; Point max; int oldbg, oldfg, bg, fg, attr, off; bool selected, grouphl; char s[32]; Rune c, csel; /* bottom text is always in the same place */ bot.x = screen->r.min.x + Txtoff; bot.y = screen->r.max.y - glyphsz.y*2 - Txtoff; txtoff.x = Txtoff; txtoff.y = Txtoff; max.x = (Dx(screen->r) - 2*Txtoff) / glyphsz.x; if (max.x > field.width) { txtoff.x += (max.x - field.width) * glyphsz.x / 2; max.x = field.width; } max.y = (bot.y - screen->r.min.y - Txtoff - glyphsz.y) / glyphsz.y; if (max.y > field.height) { txtoff.y += (max.y - field.height) * glyphsz.y / 2; max.y = field.height; } top = addpt(screen->r.min, txtoff); if (cur.x >= max.x+scroll.x-1) scroll.x = cur.x-max.x+1; else if (cur.x < scroll.x) scroll.x = cur.x; if (cur.y >= max.y+scroll.y-1) scroll.y = cur.y-max.y+1; else if (cur.y < scroll.y) scroll.y = cur.y; if (!eqpt(oldscroll, scroll)) complete = 1; oldscroll = scroll; if (complete) { r = screen->r; r.max.y = r.min.y + txtoff.y; draw(screen, r, color[Dback], nil, ZP); r = screen->r; r.max.x = r.min.x + txtoff.x; draw(screen, r, color[Dback], nil, ZP); } off = field.width*cur.y + cur.x; csel = field.buffer[off]; bg = -1; fg = -1; r = screen->r; p.y = top.y; for (y = scroll.y; y < MIN(field.height, scroll.y+max.y); y++) { p.x = top.x; for (x = scroll.x, i = 0; x < MIN(field.width, scroll.x+max.x); x++) { oldbg = bg; oldfg = fg; off = field.width*y + x; c = field.buffer[off]; attr = mbuf.buffer[off]; selected = x >= sel.min.x && y >= sel.min.y && x <= sel.max.x && y <= sel.max.y; if (selected) attr |= Mark_flag_selected; else attr &= ~Mark_flag_selected; /* highlight the same char */ grouphl = c != '.' && csel == c && (x != cur.x || y != cur.y) && (attr & (Mark_flag_input|Mark_flag_lock|Mark_flag_output)) == 0; if (grouphl) attr |= Mark_flag_group_highlight; else attr &= ~Mark_flag_group_highlight; if (c == '.' && eqpt(Pt(x, y), cur)) c = cursor; if (!grouphl && !complete && c == fscr.buffer[off] && attr == mscr.buffer[off]) { if (i > 0) { p = runestringnbg(screen, p, color[oldfg], ZP, font, linebuf, i, color[oldbg], ZP); i = 0; } p.x += glyphsz.x; continue; } fscr.buffer[off] = c; mscr.buffer[off] = attr; bg = selected ? Dbinv : Dback; fg = selected ? Dfinv : (grouphl ? Dbinv: Dfmed); if (c == '.') c = styles[dotstyle].dot.r; if (c == styles[dotstyle].dot.r && (attr & ~Mark_flag_selected) == 0) { if ((x % rulers.x) == 0 && (y % rulers.y) == 0) { rx = !!x + (x + 1) / field.width; ry = !!y + (y + 1) / field.height; c = rulerstyle == Snone ? styles[dotstyle].dot.r : styles[rulerstyle].ruler.r[ry*3+rx]; } fg = selected ? Dfmed : Dflow; } else if (!selected && !grouphl) { if (c == '#') { fg = Dfmed; } else { if ((c >= 'A' && c <= 'Z' && !is_movement(c)) || is_send(c)) { bg = Dbmed; fg = Dfinv; } if (attr & Mark_flag_input) { bg = Dback; fg = Dfhigh; } else if (attr & Mark_flag_lock) { bg = Dback; fg = Dfmed; } } if (attr & Mark_flag_output) { bg = Dfhigh; fg = Dfinv; } if (attr & Mark_flag_haste_input) { bg = Dback; fg = Dbmed; } } if (bg != oldbg || fg != oldfg) { p = runestringnbg(screen, p, color[oldfg], ZP, font, linebuf, i, color[oldbg], ZP); i = 0; } linebuf[i++] = c; } runestringnbg(screen, p, color[fg], ZP, font, linebuf, i, color[bg], ZP); p.y += glyphsz.y; } r = screen->r; r.min.x += txtoff.x + max.x*glyphsz.x; draw(screen, r, color[Dback], nil, ZP); r = screen->r; r.min.y += txtoff.y + max.y*glyphsz.y; draw(screen, r, color[Dback], nil, ZP); p = top; p.y += glyphsz.y*(max.y-1)/2; if (scroll.x > 0) { p.x = top.x - txtoff.x; string(screen, p, color[Dfmed], ZP, font, "←"); } if (max.x+scroll.x < field.width) { p.x = top.x + max.x*glyphsz.x + txtoff.x - glyphsz.x; string(screen, p, color[Dfmed], ZP, font, "→"); } p = top; p.x += glyphsz.x*(max.x-1)/2; if (scroll.y > 0) { p.y = screen->r.min.y; string(screen, p, color[Dfmed], ZP, font, "↑"); } if (max.y+scroll.y < field.height) { p.y = top.y + max.y*glyphsz.y; string(screen, p, color[Dfmed], ZP, font, "↓"); } i = 0; sprint(s, "%udx%ud", field.width, field.height); i += runesprint(linebuf, "%-10s", s); sprint(s, "%d/%d", rulers.x, rulers.y); i += runesprint(linebuf+i, "%-9s", s); sprint(s, "%lldf%c", MAX(0, tick), pause ? '~' : 0); i += runesprint(linebuf+i, "%-9s", s); off = sprint(s, "%d", bpm); if (apm != bpm) off += sprint(s+off, "%+d", apm-bpm); sprint(s+off, "%c", (tick % 4) == 0 ? '*' : 0); i += runesprint(linebuf+i, "%-9s", s); sprint(s, "%ldms", labs(framedev)); i += runesprint(linebuf+i, "%-8s", s); runestringn(screen, bot, color[Dfhigh], ZP, font, linebuf, i); bot.y += glyphsz.y; i = 0; sprint(s, "%ud,%ud", cur.x, cur.y); i += runesprint(linebuf, "%-10s", s); sprint(s, "%d:%d", sel.min.x < cur.x ? -Dx(sel) : Dx(sel), sel.max.x < cur.x ? -Dy(sel) : Dy(sel)); i += runesprint(linebuf+i, "%-9s", s); i += runesprint(linebuf+i, "%-9s", modes[altdown ? Mslide : mode]); i += runesprint(linebuf+i, "%s", filename[0] ? filename : "unnamed"); runestringn(screen, bot, color[Dfhigh], ZP, font, linebuf, i); flushimage(display, 1); } static int fieldload(char *path) { Dir *d; Field_load_error e; if ((d = dirstat(path)) == nil || d->length < 1 || (d->type & DMDIR) != 0) { free(d); werrstr("invalid orca file"); return -1; } if ((e = field_load_file(path, &field)) != Field_load_error_ok) { werrstr(field_load_error_string(e)); return -1; } cur = ZP; sel = ZR; return 0; } static int fieldsave(char *path) { FILE *f; if ((f = fopen(path, "w")) == nil) return -1; field_fput(&field, f); fclose(f); return 0; } static Point ptclamp(Point p) { p.x = MAX(0, MIN((int)field.width-1, p.x)); p.y = MAX(0, MIN((int)field.height-1, p.y)); return p; } static void curmove(int x, int y) { Point xy; xy = Pt(x, y); cur = ptclamp(addpt(cur, xy)); sel.min = ptclamp(addpt(sel.min, xy)); sel.max = ptclamp(addpt(sel.max, xy)); } static void selmove(int x, int y) { int i; if (sel.min.x+x < 0 || sel.min.x >= field.width || sel.min.y+y < 0 || sel.min.y+y >= field.height) return; field_resize_raw(&fsel, Dy(sel)+1, Dx(sel)+1); gbuffer_copy_subrect( field.buffer, fsel.buffer, field.height, field.width, Dy(sel)+1, Dx(sel)+1, sel.min.y, sel.min.x, 0, 0, Dy(sel)+1, Dx(sel)+1 ); for (i = sel.min.y; i <= sel.max.y; i++) { fieldsetn(sel.min.x, i, '.', Dx(sel)+1); memset(&mbuf.buffer[sel.min.x + field.width*i], 0, Dx(sel)+1); } gbuffer_copy_subrect( fsel.buffer, field.buffer, Dy(sel)+1, Dx(sel)+1, field.height, field.width, 0, 0, sel.min.y+y, sel.min.x+x, Dy(sel)+1, Dx(sel)+1 ); } static void selmap(int (*f)(int)) { int x, y; for (y = sel.min.y; y <= sel.max.y; y++) { for (x = sel.min.x; x <= sel.max.x; x++) { fieldset(x, y, f(fieldget(x, y))); } } } static int snaplow(int n, int rulern) { n--; n -= (n % rulern) > 0 ? (n % rulern) : rulern; return MAX(1, n+1); } static int snaphigh(int n, int rulern) { n += rulern; n -= n % rulern - 1; return n; } static void screensize(int *w, int *h) { *w = snaplow((Dx(screen->r) - 2*Txtoff) / glyphsz.x, rulers.x); *h = snaplow(((Dy(screen->r) - 2*Txtoff) - 3*glyphsz.y) / glyphsz.y, rulers.y); } static void stdinproc(void *) { char buf[256]; int n, i; threadsetname("stdinproc"); for (;;) { if ((n = read(0, buf, sizeof(buf)-1)) <= 0) break; for (i = 0; i < n; i++) { if (buf[i] == '\r' || buf[i] == '\n' || buf[i] == 0) { buf[i] = 0; while (buf[i+1] == '\n') i++; command(buf, snapshot); n -= i; memmove(buf, buf+i+1, n); i = 0; } } } threadexits(nil); } static void kbdproc(void *cchan) { char buf[128], buf2[128], *s; int kfd, n, kbin; Rune r; threadsetname("kbdproc"); if ((kfd = open("/dev/kbd", OREAD)) < 0) sysfatal("/dev/kbd: %r"); kbin = open("/dev/kbin", OWRITE); buf2[0] = 0; buf2[1] = 0; buf[0] = 0; for (;;) { if (buf[0] != 0) { n = strlen(buf)+1; memmove(buf, buf+n, sizeof(buf)-n); } if (buf[0] == 0) { n = read(kfd, buf, sizeof(buf)-1); if (n <= 0) break; buf[n-1] = 0; buf[n] = 0; } switch (buf[0]) { case 'c': if (chartorune(&r, buf+1) > 0 && r != Runeerror) nbsend(cchan, &r); default: continue; case 'k': s = buf+1; while (*s) { s += chartorune(&r, s); if (utfrune(buf2+1, r) == nil) { if (r == Kalt) { /* magic trick: write Alt scancode to disable the "compose" mode */ if (kbin >= 0) write(kbin, "\x46", 1); altdown = true; } else if (r == Kshift) { shiftdown = true; } else if (r == Kctl) { ctldown = true; move = rulers; } } } break; case 'K': s = buf2+1; while (*s) { s += chartorune(&r, s); if (utfrune(buf+1, r) == nil) { if (r == Kalt) { altdown = false; } else if (r == Kshift) { shiftdown = false; } else if (r == Kctl) { ctldown = false; move = Pt(1, 1); } } } break; } strcpy(buf2, buf); } threadexits(nil); } static void selext(int xdt, int ydt) { if (sel.max.x > cur.x || (sel.min.x == cur.x && xdt > 0)) sel.max.x += xdt; else sel.min.x += xdt; if (sel.max.y > cur.y || (sel.min.y == cur.y && ydt > 0)) sel.max.y += ydt; else sel.min.y += ydt; sel.min.x = MAX(0, MIN((int)field.width-1, sel.min.x)); sel.max.x = MAX(0, MIN((int)field.width-1, sel.max.x)); sel.min.y = MAX(0, MIN((int)field.height-1, sel.min.y)); sel.max.y = MAX(0, MIN((int)field.height-1, sel.max.y)); } static Point ptmouse(Point p) { p = subpt(subpt(p, screen->r.min), txtoff); p.x /= glyphsz.x; p.y /= glyphsz.y; p.x += scroll.x; p.y += scroll.y; return ptclamp(p); } static void usage(void) { print("usage: %s [-p] [-b bpm] [-c cursor] [-l undo_limit] [-s WxH] [-r random_seed] [-i ip_address] [-u udp_port] [-m midi_path] [file]\n", argv0); threadexitsall("usage"); } void threadmain(int argc, char **argv) { Mousectl *mctl; Keyboardctl kctl; Rune r; Mouse m; Point p; char tmp[256]; char cmd[256]; int oldw, oldh, w, h, n, oldbuttons; long seed; bool complete; Alt a[Numchan+1] = { [Ckey] = { nil, &r, CHANRCV }, [Cmouse] = { nil, &m, CHANRCV }, [Cresize] = { nil, nil, CHANRCV }, [Credraw] = { nil, nil, CHANRCV }, { nil, nil, CHANEND }, }; srand(time(0)); w = h = 0; ip = strdup("127.0.0.1"); udpport = strdup("41961"); midipath = strdup(""); ARGBEGIN{ case 'p': pause = true; break; case 'b': bpm = atoi(EARGF(usage())); if (bpm < 1) { fprint(2, "invalid bpm %d\n", bpm); threadexitsall("args"); } break; case 's': if (sscanf(EARGF(usage()), "%dx%d", &w, &h) != 2) usage(); if (w <= 0 || h <= 0 || w > ORCA_X_MAX || h > ORCA_Y_MAX) { fprint(2, "invalid dimensions %dx%d\n", w, h); threadexitsall("args"); } break; case 'r': if ((seed = atol(EARGF(usage()))) < 0) { fprint(2, "invalid seed %ld\n", seed); threadexitsall("args"); } srand(seed); break; case 'c': if (chartorune(&cursor, EARGF(usage())) < 1 || cursor == Runeerror) { fprint(2, "invalid cursor \"%s\"\n", EARGF(usage())); threadexitsall("args"); } break; case 'i': free(ip); ip = EARGF(usage()); break; case 'u': free(udpport); udpport = EARGF(usage()); break; case 'm': free(midipath); midipath = EARGF(usage()); break; case 'l': if ((maxsnaps = atoi(EARGF(usage()))) < 0) { fprint(2, "invalid undo limit %s\n", EARGF(usage())); threadexitsall("args"); } break; default: usage(); }ARGEND if (argc > 1) usage(); if (argc == 1) { field_init(&field); snprint(filename, sizeof(filename), "%s", argv[0]); if (fieldload(filename) != 0) { fprint(2, "%s: %r\n", filename); threadexitsall("file"); } w = field.width; h = field.height; } threadsetname("orca/draw"); if(initdraw(nil, nil, "orca") < 0) sysfatal("initdraw: %r"); if ((mctl = initmouse(nil, screen)) == nil) sysfatal("initmouse: %r"); display->locking = 1; unlockdisplay(display); a[Ckey].c = chancreate(sizeof(Rune), 20); a[Cmouse].c = mctl->c; a[Cresize].c = mctl->resizec; a[Credraw].c = chancreate(sizeof(ulong), 0); proccreate(kbdproc, a[Ckey].c, mainstacksize); kctl.c = a[Ckey].c; proccreate(stdinproc, nil, mainstacksize); for (n = 0; n < Numcolors; n++) color[n] = allocimage(display, Rect(0, 0, 1, 1), RGB24, 1, theme[n]); glyphsz.x = stringwidth(font, "@"); glyphsz.y = font->height; if (filename[0] == 0) { if (w == 0 || h == 0) screensize(&w, &h); field_init_fill(&field, h, w, '.'); } field_init_fill(&fscr, h, w, '.'); field_init(&fsel); linebuf = malloc(sizeof(Rune)*MAX(w+1, 64)); memset(noteoff, 0, sizeof(noteoff)); mbuf_reusable_init(&mbuf); mbuf_reusable_ensure_size(&mbuf, h, w); memset(mbuf.buffer, 0, w*h); mbuf_reusable_init(&mscr); mbuf_reusable_ensure_size(&mscr, h, w); memset(mscr.buffer, 0, w*h); proccreate(orcathread, a[Credraw].c, mainstacksize); shiftdown = false; altdown = false; complete = true; move.x = 1; move.y = 1; oldbuttons = 0; cmd[0] = 0; netdial(nil, nil); midiopen(nil); snaps = calloc(maxsnaps, sizeof(Snap)); for (;;) { redraw(complete); complete = false; oldw = w = field.width; oldh = h = field.height; noredraw: switch (alt(a)) { case -1: goto end; case Cmouse: if (m.buttons == 1) { if (altdown) { p = ptmouse(m.xy); if (!eqpt(p, cur)) { if (oldbuttons == 0) snapshot(); selmove(p.x-cur.x, p.y-cur.y); curmove(p.x-cur.x, p.y-cur.y); } } else if (oldbuttons == 0 && !shiftdown) { cur = ptmouse(m.xy); sel.min = cur; sel.max = cur; } else if (oldbuttons == 1 || shiftdown) { sel.max = ptmouse(m.xy); sel.min = sel.max; if (sel.max.x < cur.x) sel.max.x = cur.x; else sel.min.x = cur.x; if (sel.max.y < cur.y) sel.max.y = cur.y; else sel.min.y = cur.y; } oldbuttons = m.buttons; break; } oldbuttons = m.buttons; if (m.buttons == 3) { /* cut */ selcopy(); selset('.'); } else if (m.buttons == 5) { /* paste */ selpaste(); } else if (m.buttons == 4) { /* menu */ menu3i[Menu3dotstyle] = styles[(dotstyle+1) % Numstyles].dot.menu; menu3i[Menu3rulerstyle] = styles[(rulerstyle+1) % Numstyles].ruler.menu; n = menuhit(3, mctl, &menu3, nil); snprint(tmp, sizeof(tmp), "%s", filename); if (n == Menu3load) { if (enter("load from:", tmp, sizeof(tmp), mctl, &kctl, nil) > 0 && fieldload(tmp) == 0) { w = field.width; h = field.height; snprint(filename, sizeof(filename), "%s", tmp); } } else if (n == Menu3save) { if ((tmp[0] != 0 || enter("save to:", tmp, sizeof(tmp), mctl, &kctl, nil) > 0) && fieldsave(tmp) == 0) snprint(filename, sizeof(filename), "%s", tmp); } else if (n == Menu3dotstyle) { dotstyle = ++dotstyle % Numstyles; } else if (n == Menu3rulerstyle) { rulerstyle = ++rulerstyle % Numstyles; } else if (n == Menu3exit) { goto end; } complete = true; } else { goto noredraw; } break; case Cresize: getwindow(display, Refnone); complete = true; scroll = ZP; break; case Credraw: break; case Ckey: switch (r) { case '\n': /* C-j */ /* FIXME bang it */ break; case Kup: if (shiftdown || mode == Mselect) selext(0, -move.y); else { if (altdown || mode == Mslide) { snapshot(); selmove(0, -move.y); } curmove(0, -move.y); } break; case Kdown: if (shiftdown || mode == Mselect) selext(0, move.y); else { if (altdown || mode == Mslide) { snapshot(); selmove(0, move.y); } curmove(0, move.y); } break; case Kleft: if (shiftdown || mode == Mselect) { selext(-move.x, 0); } else { if (altdown || mode == Mslide) { snapshot(); selmove(-move.x, 0); } curmove(-move.x, 0); } break; case Kright: if (shiftdown || mode == Mselect) selext(move.x, 0); else { if (altdown || mode == Mslide) { snapshot(); selmove(move.x, 0); } curmove(move.x, 0); } break; case Ksoh: /* C-a */ if (shiftdown || mode == Mselect) selext(-ORCA_X_MAX, 0); else curmove(-ORCA_X_MAX, 0); break; case Kenq: /* C-e */ if (shiftdown || mode == Mselect) selext(ORCA_X_MAX, 0); else curmove(ORCA_X_MAX, 0); break; case Khome: if (shiftdown || mode == Mselect) selext(0, -ORCA_Y_MAX); else curmove(0, -ORCA_Y_MAX); break; case Kend: if (shiftdown || mode == Mselect) selext(0, ORCA_Y_MAX); else curmove(0, ORCA_Y_MAX); break; case 0x12: /* C-r */ tick = -1; forward = true; break; case 0x13: /* C-s */ tmp[0] = 0; if (filename[0]) fieldsave(filename); else if (enter("file path:", tmp, sizeof(tmp), nil, &kctl, nil) > 0 && fieldsave(tmp) == 0) snprint(filename, sizeof(filename), "%s", tmp); break; case 0x18: /* C-x */ snapshot(); selcopy(); selset('.'); break; case Ketx: /* C-c */ selcopy(); break; case 0x16: /* C-v */ snapshot(); selpaste(); break; case '[': rulers.x = MAX(4, rulers.x-1); complete = true; break; case ']': rulers.x = MIN(16, rulers.x+1); complete = true; break; case '{': rulers.y = MAX(4, rulers.y-1); complete = true; break; case '}': rulers.y = MIN(16, rulers.y+1); complete = true; break; case '(': snapshot(); w = snaplow(w, rulers.x); break; case ')': snapshot(); w = snaphigh(w, rulers.x); break; case '_': snapshot(); h = snaplow(h, rulers.y); break; case '+': snapshot(); h = snaphigh(h, rulers.y); break; case '>': apm = ++bpm; break; case '<': apm = bpm = MAX(1, bpm-1); break; case 0x09: /* C-i */ case Kins: mode = mode != Mappend ? Mappend : Minsert; break; case 0x0b: /* C-k */ if (enter("command:", cmd, sizeof(cmd), nil, &kctl, nil) > 0) command(cmd, snapshot); break; case Kesc: if (mode == Mslide || mode != Minsert) mode = Minsert; else { sel.min = cur; sel.max = cur; } break; case Kack: /* C-f */ forward = true; break; case 0x1a: /* C-z */ undo(); w = field.width; h = field.height; break; case 0x19: redo(); w = field.width; h = field.height; break; case '`': case '~': case L'´': mode = mode != Mslide ? Mslide : Minsert; break; case '\'': mode = mode != Mselect ? Mselect : Minsert; break; case Knack: /* C-u */ snapshot(); selmap(toupper); break; case 0x0c: /* C-l */ snapshot(); selmap(tolower); break; case Kbs: /* C-h */ snapshot(); if (mode != Mappend) { selset('.'); } else { curmove(-1, 0); fieldset(cur.x, cur.y, '.'); } break; case ' ': if (mode != Mappend) { pause = !pause; break; } default: if (r == Kdel || r == ' ') r = '.'; if (orca_is_valid_glyph(r)) { snapshot(); if (mode != Mappend) { selset(r); } else { fieldset(cur.x, cur.y, r); curmove(1, 0); } } else { // fprint(2, "unhandled key %04x\n", r); goto noredraw; } break; } } if (field.width != oldw) w = field.width; if (field.height != oldh) h = field.height; if (w != oldw || h != oldh) { mbuf_reusable_ensure_size(&mscr, h, w); memset(mscr.buffer, 0, w*h); for (n = 0; n < oldh; n++) memmove(&mscr.buffer[n*w], &mbuf.buffer[n*oldw], MIN(w, oldw)); mbuf_reusable_ensure_size(&mbuf, h, w); memmove(mbuf.buffer, mscr.buffer, w*h); linebuf = realloc(linebuf, sizeof(Rune)*MAX(w+1, 64)); field_copy(&field, &fscr); field_resize_raw(&field, h, w); memset(field.buffer, '.', w*h); gbuffer_copy_subrect( fscr.buffer, field.buffer, fscr.height, fscr.width, h, w, 0, 0, 0, 0, MIN(h, fscr.height), MIN(w, fscr.width) ); field_resize_raw(&fscr, h, w); curmove(0, 0); complete = true; } } end: chanclose(a[Ckey].c); chanclose(a[Credraw].c); mbuf_reusable_deinit(&mscr); field_deinit(&fscr); field_deinit(&fsel); free(linebuf); close(udp); threadexitsall(nil); }