ref: 371384bf01cded8f8db4c8056557c0efdafd3c33
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>
typedef struct Key Key;
#define MIN(x,y) ((x)<(y)?(x):(y))
#define MAX(x,y) ((x)>(y)?(x):(y))
enum {
Txtoff = 16,
Coloff = 2,
Cchar = 0,
Ckey,
Cmouse,
Cresize,
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,
Dbhigh,
Dbmed,
Dblow,
Dbinv,
Numcolors,
/* this might become a bad idea in the future */
Mark_flag_selected = 1<<7,
};
struct Key {
int down;
Rune rune;
};
static Rune *linebuf;
static vlong tick;
static int gridw = 8, gridh = 8;
static int rulerstyle = Sfancy, dotstyle = Sfancy;
static int bpm = 120, pause, forward;
static int curx, cury;
static int selw = 1, selh = 1;
static int charw, charh;
static Field field;
static Mbuf_reusable mbuf, mscr;
static Oevent_list events;
static char filename[256];
static Channel *cchan;
static Field copyfield, selfield;
static int altdown;
static int mode = Minsert;
static long framedev; /* frame deviation >= 1ms */
static Rune *linebuf;
static char *style[Numstyles] = {
[Sfancy] = "fancy",
[Splain] = "plain",
[Snone] = "no",
};
static Rune dot[Numstyles] = {
[Sfancy] = L'·',
[Splain] = '.',
[Snone] = ' ',
};
static Rune ruler[Numstyles][9] = {
[Sfancy] = {
L'┌', L'┬', L'┐',
L'├', L'┼', L'┤',
L'└', L'┴', L'┘',
},
[Splain] = {
'+', '+', '+',
'+', '+', '+',
'+', '+', '+',
},
[Snone] = {
' ', ' ', ' ',
' ', ' ', ' ',
' ', ' ', ' ',
},
};
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] = 0x444444ff,
[Dfinv] = 0x000000ff,
[Dbhigh] = 0xddddddff,
[Dbmed] = 0x72dec2ff,
[Dblow] = 0x222222ff,
[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 '?':
return true;
}
return false;
}
static void
process(Oevent_list *events)
{
int i, off;
Oevent *e;
u8int u[4];
for (e = events->buffer, i = 0; i < events->count; i++, e++) {
if (e->any.oevent_type == Oevent_type_midi_note) {
Oevent_midi_note const *n = &e->midi_note;
u[0] = 1;
u[1] = 0x90 | n->channel;
u[2] = (n->octave + 1)*12 + n->note;
u[3] = n->velocity;
write(1, 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;
}
}
for (i = 0; i < nelem(noteoff); i++) {
if (noteoff[i].at > 0 && noteoff[i].at < tick) {
write(1, noteoff[i].u, 4);
noteoff[i].at = 0;
}
}
}
/*
* nsec() is wallclock and can be adjusted by timesync
* so need to use cycles() instead
*
* "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 < 1) {
if ((f = open("/dev/time", OREAD)) < 0)
sysfatal("failed to open /dev/time");
if ((n = read(f, tmp, sizeof(tmp)-1)) < 2)
sysfatal("failed to read /dev/time");
tmp[n] = 0;
e = tmp;
for (i = 0; i < 3; i++)
strtoll(e, &e, 10);
fasthz = strtoll(e, nil, 10);
if (fasthz < 1)
sysfatal("failed to read fasthz");
close(f);
cycles(&xstart);
}
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;
int w, h, fd;
char tmp[64];
threadsetname("orca/sim");
snprint(tmp, sizeof(tmp), "/proc/%d/ctl", getpid());
if((fd = open(tmp, OWRITE)) >= 0){
fprint(fd, "pri 13\n");
close(fd);
}
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 = 0;
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++;
}
}
static void
redraw(int complete)
{
Rectangle r;
Point p, top, bot;
int x, y, rx, ry, i;
int oldbg, oldfg, bg, fg, attr, selected, off;
char s[32];
Rune c;
p = addpt(screen->r.min, Pt(Txtoff, Txtoff));
top = p;
bot.x = top.x;
bot.y = screen->r.max.y - Txtoff - charh*2;
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);
r = screen->r;
r.min.x += MIN(field.width*charw, Dx(r)-Txtoff) + Txtoff;
draw(screen, r, color[Dback], nil, ZP);
}
bg = -1;
fg = -1;
for (y = 0; y < field.height && p.y < bot.y-charh; y++) {
p.x = top.x;
for (x = i = 0; x < field.width && x < screen->r.max.x-Txtoff; x++) {
oldbg = bg;
oldfg = fg;
off = field.width*y + x;
c = field.buffer[off];
attr = mbuf.buffer[off];
selected = x >= curx && y >= cury && x < curx+selw && y < cury+selh;
if (selected)
attr |= Mark_flag_selected;
else
attr &= ~Mark_flag_selected;
if (!complete && c == copyfield.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 += charw;
continue;
}
copyfield.buffer[off] = c;
mscr.buffer[off] = attr;
bg = selected ? Dbinv : Dback;
fg = selected ? Dfinv : Dfhigh;
if (c == '.')
c = dot[dotstyle];
if (c == dot[dotstyle] && attr == 0) {
if ((x % gridw) == 0 && (y % gridh) == 0) {
rx = !!x + (x + 1) / field.width;
ry = !!y + (y + 1) / field.height;
c = rulerstyle == Snone ? dot[dotstyle] : ruler[rulerstyle][ry*3+rx];
}
fg = Dflow;
} else if (!selected) {
if (c == '#') {
fg = Dfmed;
} else {
if (c >= 'A' && c <= 'Z') {
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 (i > 0 && (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 += charh;
}
r = screen->r;
r.min.y = MIN(top.y + field.height*charh, bot.y-charh);
draw(screen, r, color[Dback], nil, ZP);
i = 0;
sprint(s, "%udx%ud", field.width, field.height);
i += runesprint(linebuf, "%-13s", s);
sprint(s, "%d/%d", gridw, gridh);
i += runesprint(linebuf+i, "%-9s", s);
sprint(s, "%lldf", MAX(0, tick));
i += runesprint(linebuf+i, "%-8s", s);
i += runesprint(linebuf+i, "%-8d", bpm);
sprint(s, "%ldms", labs(framedev));
i += runesprint(linebuf+i, "%-8s", s);
runestringn(screen, bot, color[Dfhigh], ZP, font, linebuf, i);
bot.y += charh;
i = 0;
sprint(s, "%ud,%ud", curx, cury);
i += runesprint(linebuf, "%-13s", s);
sprint(s, "%d:%d", selw, selh);
i += runesprint(linebuf+i, "%-9s", s);
i += runesprint(linebuf+i, "%-8s", 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)
{
Field_load_error e;
if ((e = field_load_file(path, &field)) != Field_load_error_ok) {
werrstr(field_load_error_string(e));
return -1;
}
curx = MIN(curx, field.width-1);
cury = MIN(cury, field.height-1);
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 void
selset(Rune key)
{
int y;
for (y = cury; y < cury+selh && y < field.height; y++)
memset(&field.buffer[curx + field.width*y], key, MIN(field.width-curx, selw));
}
static void
selcopy(void)
{
Biobuf *b;
int y;
if ((b = Bopen("/dev/snarf", OWRITE)) != nil) {
for (y = cury; y < cury+selh && y < field.height; y++) {
Bwrite(b, &field.buffer[curx + field.width*y], MIN(selw, field.width-curx));
Bputc(b, '\n');
}
Bterm(b);
}
}
static void
selpaste(void)
{
Biobuf *b;
char *s;
int cols, rows, n;
if ((b = Bopen("/dev/snarf", OREAD)) != nil) {
for (cols = rows = 0; (s = Brdstr(b, '\n', 1)) != nil; rows++) {
if ((n = Blinelen(b)) > cols)
cols = n;
if (cury+rows < field.height)
memmove(&field.buffer[curx + field.width*(cury+rows)], s, MIN(n, field.width-curx));
free(s);
}
selw = MAX(1, cols);
selh = MAX(1, rows);
Bterm(b);
}
}
static void
fieldset(Rune key)
{
if (mode == Minsert) {
selset(key);
} else {
field.buffer[curx + field.width*cury] = key;
if (curx < field.width-1)
curx++;
}
}
static void
selmove(int x, int y)
{
int i, n;
if (curx+x < 0 || cury+y < 0)
return;
field_resize_raw(&selfield, selh, selw);
gbuffer_copy_subrect(
field.buffer, selfield.buffer,
field.height, field.width, selh, selw,
cury, curx, 0, 0, selh, selw
);
n = MIN(selw, field.width-curx);
for (i = cury; i < cury+selh && i < field.height; i++) {
memset(&field.buffer[curx + field.width*i], '.', n);
memset(&mbuf.buffer[curx + field.width*i], 0, n);
}
gbuffer_copy_subrect(
selfield.buffer, field.buffer,
selh, selw, field.height, field.width,
0, 0, cury+y, curx+x, selh, selw
);
}
static void
selmap(int (*f)(int))
{
int x, y;
for (y = cury; y < cury+selh && y < field.height; y++) {
for (x = curx; x < curx+selw && x < field.width; x++) {
field.buffer[x + field.width*y] = f(field.buffer[x + field.width*y]);
}
}
}
static int
snaplow(int n, int gridn)
{
n--;
n -= (n % gridn) > 0 ? (n % gridn) : gridn;
return MAX(1, n+1);
}
static int
snaphigh(int n, int gridn)
{
n += gridn;
n -= n % gridn - 1;
return n;
}
static void
screensize(int *w, int *h)
{
*w = snaplow((Dx(screen->r) - 2*Txtoff) / charw, gridw);
*h = snaplow(((Dy(screen->r) - 2*Txtoff) - 3*charh) / charh, gridh);
}
static void
kbdproc(void *k)
{
char buf[128], buf2[128], *s;
Channel *kchan;
int kfd, n;
Key key;
Rune r;
threadsetname("kbdproc");
if ((kfd = open("/dev/kbd", OREAD)) < 0)
sysfatal("can't open kbd: %r");
kchan = k;
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);
/* no break */
default:
continue;
case 'k':
s = buf+1;
while (*s){
s += chartorune(&r, s);
if (utfrune(buf2+1, r) == nil) {
key.down = 1;
key.rune = r;
nbsend(kchan, &key);
}
}
break;
case 'K':
s = buf2+1;
while (*s) {
s += chartorune(&r, s);
if(utfrune(buf+1, r) == nil) {
key.down = 0;
key.rune = r;
nbsend(kchan, &key);
}
}
break;
}
strcpy(buf2, buf);
}
}
void
threadmain(int argc, char **argv)
{
Mousectl *mctl;
Key key;
Keyboardctl kctl;
Mouse m;
char tmp[256];
Channel *kchan;
int oldw, oldh, w, h, n, shiftdown, complete, movex, movey;
Alt a[Numchan+1] = {
[Cchar] = { nil, &key.rune, CHANRCV },
[Ckey] = { nil, &key, CHANRCV },
[Cmouse] = { nil, &m, CHANRCV },
[Cresize] = { nil, nil, CHANRCV },
[Credraw] = { nil, nil, CHANRCV },
{ nil, nil, CHANEND },
};
USED(argc, argv);
kchan = chancreate(sizeof(Key), 20);
cchan = chancreate(sizeof(Rune), 20);
proccreate(kbdproc, kchan, mainstacksize);
srand(time(0));
threadsetname("orca/draw");
if(initdraw(nil, nil, "orca") < 0)
sysfatal("initdraw: %r");
if ((mctl = initmouse(nil, screen)) == nil)
sysfatal("initmouse: %r");
kctl.c = cchan;
kctl.file = "/dev/null";
kctl.consfd = kctl.pid = kctl.ctlfd = -1;
a[Cchar].c = cchan;
a[Ckey].c = kchan;
a[Cmouse].c = mctl->c;
a[Cresize].c = mctl->resizec;
a[Credraw].c = chancreate(sizeof(ulong), 0);
for (n = 0; n < Numcolors; n++)
color[n] = allocimage(display, Rect(0, 0, 1, 1), RGBA32, 1, setalpha(theme[n] & ~0xff, theme[n] & 0xff));
charw = stringwidth(font, "X");
charh = font->height;
screensize(&w, &h);
field_init_fill(&field, h, w, '.');
field_init_fill(©field, h, w, '.');
field_init(&selfield);
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);
oevent_list_init(&events);
proccreate(orcathread, a[Credraw].c, mainstacksize);
shiftdown = 0;
altdown = 0;
complete = 1;
movex = 1;
movey = 1;
for (;;) {
redraw(complete);
complete = 0;
oldw = w = field.width;
oldh = h = field.height;
noredraw:
switch (alt(a)) {
case Cmouse:
if (m.buttons == 4) {
menu3i[Menu3dotstyle] = tmp;
menu3i[Menu3rulerstyle] = 1 + menu3i[Menu3dotstyle] + sprintf(tmp, "%s dots", style[(dotstyle+1) % Numstyles]);
sprintf(menu3i[Menu3rulerstyle], "%s rulers", style[(rulerstyle+1) % Numstyles]);
n = menuhit(3, mctl, &menu3, nil);
if (n == Menu3load || n == Menu3save) {
strncpy(tmp, filename, sizeof(tmp));
if (enter("file path:", tmp, sizeof(tmp), mctl, &kctl, nil) > 0) {
if (n == Menu3load && fieldload(tmp) == 0) {
w = field.width;
h = field.height;
strncpy(filename, tmp, sizeof(filename));
} else if (n == Menu3save && fieldsave(tmp) == 0) {
strncpy(filename, tmp, sizeof(filename));
}
}
} else if (n == Menu3dotstyle) {
dotstyle = ++dotstyle % Numstyles;
} else if (n == Menu3rulerstyle) {
rulerstyle = ++rulerstyle % Numstyles;
} else if (n == Menu3exit) {
goto end;
}
complete = 1;
} else {
goto noredraw;
}
break;
case Cresize:
getwindow(display, Refnone);
complete = 1;
break;
case Ckey:
switch (key.rune) {
case Kshift:
shiftdown = key.down;
break;
case Kalt:
altdown = key.down;
break;
case Kctl:
movex = key.down ? gridw : 1;
movey = key.down ? gridh : 1;
break;
case Kup:
case Kdown:
case Kleft:
case Kright:
break;
default:
// fprint(2, "unknown key down/up 0x%x\n", key.rune);
break;
}
break;
case Credraw:
break;
case Cchar:
switch (key.rune) {
case 0x0b: /* C-k */
case Kup:
if (shiftdown || mode == Mselect)
selh = MAX(1, selh-movey);
else {
if (altdown || mode == Mslide)
selmove(0, -movey);
cury = MAX(0, cury-movey);
}
break;
case '\n': /* C-j */
case Kdown:
if (shiftdown || mode == Mselect)
selh += movey;
else {
if (altdown || mode == Mslide)
selmove(0, movey);
cury = MIN(h-1, cury+movey);
}
break;
case Kbs: /* C-h */
case Kleft:
if (shiftdown || mode == Mselect)
selw = MAX(1, selw-movex);
else {
if (altdown || mode == Mslide)
selmove(-movex, 0);
curx = MAX(0, curx-movex);
}
break;
case 0x0c: /* C-l */
if (shiftdown) { /* FIXME this conflicts with vim keys btw */
selmap(tolower);
break;
}
case Kright:
if (shiftdown || mode == Mselect)
selw += movex;
else {
if (altdown || mode == Mslide)
selmove(movex, 0);
curx = MIN(w-1, curx+movex);
}
break;
case Ksoh: /* C-a */
if (shiftdown || mode == Mselect)
selw = curx;
curx = 0;
break;
case Kenq: /* C-e */
if (shiftdown || mode == Mselect)
selw = field.width - curx;
else
curx = field.width-1;
break;
case Khome:
if (shiftdown || mode == Mselect)
selh = cury;
cury = 0;
break;
case Kend:
if (shiftdown || mode == Mselect)
selh = field.height - cury;
else
cury = field.height-1;
break;
case 0x12: /* C-r */
tick = -1;
forward = 1;
break;
case 0x13: /* C-s */
tmp[0] = 0;
if (filename[0])
fieldsave(filename);
else if (enter("file path:", tmp, sizeof(tmp), mctl, &kctl, nil) > 0 && fieldsave(tmp) == 0)
strncpy(filename, tmp, sizeof(filename));
break;
case 0x18: /* C-x */
selcopy();
selset('.');
break;
case Ketx: /* C-c */
selcopy();
break;
case 0x16: /* C-v */
selpaste();
break;
case '[':
gridw = MAX(4, gridw-1);
complete = 1;
break;
case ']':
gridw = MIN(16, gridw+1);
complete = 1;
break;
case '{':
gridh = MAX(4, gridh-1);
complete = 1;
break;
case '}':
gridh = MIN(16, gridh+1);
complete = 1;
break;
case '(':
w = snaplow(w, gridw);
break;
case ')':
w = snaphigh(w, gridw);
break;
case '_':
h = snaplow(h, gridh);
break;
case '+':
h = snaphigh(h, gridh);
break;
case '>':
bpm++;
break;
case '<':
bpm = MAX(1, bpm-1);
break;
case 0x09: /* C-i */
case Kins:
mode = mode != Mappend ? Mappend : Minsert;
break;
case Kesc:
if (mode == Mslide || mode != Minsert)
mode = Minsert;
else
selw = selh = 1;
break;
case Kack: /* C-f */
forward = 1;
break;
case '`':
case '~':
case L'´':
mode = mode != Mslide ? Mslide : Minsert;
break;
case '\'':
mode = mode != Mselect ? Mselect : Minsert;
break;
case Knack: /* C-u */
if (shiftdown)
selmap(toupper);
break;
case ' ':
if (mode != Mappend) {
pause = !pause;
break;
}
default:
if (key.rune == Kdel || key.rune == ' ')
key.rune = '.';
if (orca_is_valid_glyph(key.rune)) {
fieldset(key.rune);
} else {
// fprint(2, "unhandled key %04x\n", key.rune);
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], 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_resize_raw(©field, h, w);
field_copy(&field, ©field);
field_resize_raw(&field, h, w);
memset(field.buffer, '.', w*h);
gbuffer_copy_subrect(
copyfield.buffer, field.buffer,
copyfield.height, copyfield.width, h, w,
0, 0, 0, 0, MIN(h, copyfield.height), MIN(w, copyfield.width)
);
field_resize_raw(©field, h, w);
complete = 1;
}
}
end:
mbuf_reusable_deinit(&mscr);
mbuf_reusable_deinit(&mbuf);
oevent_list_deinit(&events);
field_deinit(&field);
field_deinit(©field);
field_deinit(&selfield);
free(linebuf);
threadexitsall(nil);
}