ref: dbc1606be9d9224c3a9ee7b3fa7dddc5ebfb2617
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,
};
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 Rune *linebuf;
static Rune cursor = '@';
static vlong tick;
static Point glyphsz;
static Point cur, scroll, 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 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 struct {
u8int u[4];
Usz at;
}noteoff[16*128]; /* 16 channels, 128 notes each */
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,
};
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;
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) {
for (i = 0; i < n; i++)
if (!orca_is_valid_glyph(s[i]))
s[i] = '.';
memmove(&field.buffer[sel.min.x + field.width*(sel.min.y+rows)], s, MIN(n, field.width-sel.min.x));
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;
}
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] && (udp = dial(netmkaddr(ip, "udp", udpport), nil, nil, nil)) < 0) {
fprint(2, "udp failed: %r\n");
/* FIXME display error */
}
}
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
command(char *s)
{
char *a;
int x, y;
if (s[0] == ',') {
cur = ZP;
sel = Rect(0, 0, field.width, field.height);
s++;
}
if (s[0] == '|' || s[0] == '>' || s[0] == '<') {
shellpipe(s);
return;
}
if ((a = strchr(s, ':')) != nil || (a = strchr(s, ' ')) != nil)
*a++ = 0;
if (strcmp(s, "play") == 0)
pause = false;
else if (strcmp(s, "stop") == 0)
pause = true;
else if (strcmp(s, "run") == 0)
forward = true;
else if (strcmp(s, "copy") == 0)
selcopy();
else if (strcmp(s, "paste") == 0)
selpaste();
else if (strcmp(s, "erase") == 0)
selset('.');
else if (strcmp(s, "print") == 0) {
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 (strcmp(s, "bpm") == 0)
apm = bpm = MAX(1, x);
else if (strcmp(s, "apm") == 0)
apm = MAX(1, x);
else if (strcmp(s, "frame") == 0)
tick = MAX(0, x);
else if (strcmp(s, "skip") == 0)
tick = MAX(0, tick+x);
else if (strcmp(s, "rewind") == 0)
tick = MAX(0, tick-x);
else if (strcmp(s, "ip") == 0)
netdial(a, nil);
else if (strcmp(s, "udp") == 0)
netdial(nil, a);
else if (strcmp(s, "midi") == 0)
midiopen(a);
/* FIXME color, find, select, inject, write, time */
}
}
static void
process(Oevent_list *events)
{
int i, off;
Oevent *e;
u8int u[4];
char tmp[64];
for (e = events->buffer, i = 0; i < events->count; i++, e++) {
if (e->any.oevent_type == Oevent_type_midi_note) {
Oevent_midi_note *n = &e->midi_note;
u[0] = 1;
u[1] = 0x90 | n->channel;
u[2] = (n->octave + 1)*12 + n->note;
u[3] = n->velocity;
if (midi >= 0)
write(midi, u, 4);
off = n->channel*128 + u[2];
noteoff[off].u[1] = 0x80 | n->channel;
noteoff[off].u[2] = u[2];
noteoff[off].u[3] = 0;
noteoff[off].at = tick + n->duration;
} else if (e->any.oevent_type == Oevent_type_cmd_string) {
Oevent_cmd_string *c = &e->cmd_string;
memmove(tmp, c->chars, c->count);
tmp[c->count] = 0;
command(tmp);
} else if (e->any.oevent_type = Oevent_type_udp_string) {
Oevent_udp_string *u = &e->udp_string;
if (udp >= 0)
write(udp, u->chars, u->count); /* FIXME show errors */
}
}
for (i = 0; midi >= 0 && i < nelem(noteoff); i++) {
if (noteoff[i].at > 0 && noteoff[i].at < tick) {
write(midi, noteoff[i].u, 4);
noteoff[i].at = 0;
}
}
}
/*
* 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);
fasthz = strtoll(e, nil, 10);
if (fasthz < 1)
fasthz = ~0ULL;
close(f);
cycles(&xstart);
}
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);
if (x < xstart) { /* wrap around */
xstart = 0;
x += 0 - xstart;
}
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;
p = addpt(screen->r.min, Pt(Txtoff, Txtoff));
top = p;
bot.x = top.x;
bot.y = screen->r.max.y - glyphsz.y*2 - Txtoff;
max.x = MIN(field.width, (Dx(screen->r) - 2*Txtoff) / glyphsz.x);
max.y = MIN(field.height, (bot.y - top.y - glyphsz.y) / glyphsz.y);
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;
draw(screen, r, color[Dback], nil, ZP);
r = screen->r;
r.max.x = r.min.x + Txtoff;
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;
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 + max.x*glyphsz.x;
draw(screen, r, color[Dback], nil, ZP);
r = screen->r;
r.min.y += Txtoff + 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;
string(screen, p, color[Dfmed], ZP, font, "←");
}
if (max.x+scroll.x < field.width) {
p.x = top.x + max.x*glyphsz.x + Txtoff - 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);
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;
Rune r;
threadsetname("kbdproc");
if ((kfd = open("/dev/kbd", OREAD)) < 0)
sysfatal("/dev/kbd: %r");
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) {
altdown = true;
} else if (r == Kshift) {
shiftdown = true;
} else if (r == Kctl) {
ctldown = true;
move = Pt(rulers.x, rulers.y);
}
}
}
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), Pt(Txtoff, 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 [-i] [-p] [-b bpm] [-s WxH] [-r random_seed] [-c cursor] [file]\n", argv0);
threadexitsall("usage");
}
void
threadmain(int argc, char **argv)
{
Mousectl *mctl;
Keyboardctl kctl;
Rune r;
Mouse m;
char tmp[256];
int oldw, oldh, w, h, n, oldbuttons;
long seed;
bool inverse, complete;
Alt a[Numchan+1] = {
[Ckey] = { nil, &r, CHANRCV },
[Cmouse] = { nil, &m, CHANRCV },
[Cresize] = { nil, nil, CHANRCV },
[Credraw] = { nil, nil, CHANRCV },
{ nil, nil, CHANEND },
};
inverse = false;
srand(time(0));
w = h = 0;
ARGBEGIN{
case 'i':
inverse = true;
break;
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;
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++) {
if (inverse)
theme[n] = ~theme[n] | 0xff;
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;
netdial("127.0.0.1", "49160");
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 (oldbuttons == 0) {
cur = ptmouse(m.xy);
sel.min = cur;
sel.max = cur;
} else if (oldbuttons == 1) {
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)
selmove(0, -move.y);
curmove(0, -move.y);
}
break;
case Kdown:
if (shiftdown || mode == Mselect)
selext(0, move.y);
else {
if (altdown || mode == Mslide)
selmove(0, move.y);
curmove(0, move.y);
}
break;
case Kleft:
if (shiftdown || mode == Mselect) {
selext(-move.x, 0);
} else {
if (altdown || mode == Mslide)
selmove(-move.x, 0);
curmove(-move.x, 0);
}
break;
case 0x0c: /* C-l */
selmap(tolower);
break;
case Kright:
if (shiftdown || mode == Mselect)
selext(move.x, 0);
else {
if (altdown || mode == Mslide)
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 */
selcopy();
selset('.');
break;
case Ketx: /* C-c */
selcopy();
break;
case 0x16: /* C-v */
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 '(':
w = snaplow(w, rulers.x);
break;
case ')':
w = snaphigh(w, rulers.x);
break;
case '_':
h = snaplow(h, rulers.y);
break;
case '+':
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 */
tmp[0] = 0;
if (enter("command:", tmp, sizeof(tmp), nil, &kctl, nil) > 0)
command(tmp);
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 '`':
case '~':
case L'´':
mode = mode != Mslide ? Mslide : Minsert;
break;
case '\'':
mode = mode != Mselect ? Mselect : Minsert;
break;
case Knack: /* C-u */
selmap(toupper);
break;
case Kbs: /* C-h */
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)) {
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 (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);
}