V10/cmd/omovie/blitterm.c
#include <jerq.h>
#include <font.h>
#include "anim.h"
#define PTR (Texture *)0
Texture danger = {
0x0180, 0x0240, 0x0420, 0x0990, 0x1188, 0x2184, 0x4182, 0x8181,
0x8181, 0x4182, 0x2004, 0x1188, 0x0990, 0x0420, 0x0240, 0x0180, };
Texture bullseye = {
0x07E0, 0x1FF8, 0x399C, 0x63C6, 0x6FF6, 0xCDB3, 0xD99B, 0xFFFF,
0xFFFF, 0xD99B, 0xCDB3, 0x6FF6, 0x63C6, 0x399C, 0x1FF8, 0x07E0, };
Texture coffee = {
0x0000, 0x0380, 0x0400, 0x03E0, 0x0010, 0x07E0, 0x1FFC, 0x1002,
0x101A, 0x101A, 0x1002, 0x103C, 0x1810, 0x0FE0, 0x0000, 0x3FFC, };
Texture deadmouse = {
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x000C, 0x0082, 0x0441,
0xFFE1, 0x5FF1, 0x3FFE, 0x17F0, 0x03E0, 0x0000, 0x0000, 0x0000, };
Texture skull ={
0x0000, 0x0000, 0x0000, 0xC003, 0xE7E7, 0x3FFC, 0x0FF0, 0x0DB0,
0x07E0, 0x0660, 0x37EC, 0xE427, 0xC3C3, 0x0000, 0x0000, 0x0000, };
#define Do Drect.origin
#define Dc Drect.corner
#define kbdrect Rect(Do.x+4, Do.y+4, Dc.x+4, Do.y+16+4)
char kbdline[100]; /* keyboard input collects here */
char *pkbd = kbdline;
typedef unsigned char uchar;
#define PUT { char buf[100]; sprintf(buf,
#define END ); putstring(buf); }
#define readpoint(p) { p.x = readint(); p.y = readint(); }
#define readpair(p1,p2) { readpoint(p1); readpoint(p2); }
/* holds data for all input objects */
unsigned memsize; /* bytes */
uchar *inbuf; /* input collected here */
uchar *input; /* leave a null at front */
uchar *inp; /* next free slot in input */
int nobj = 0; /* number of objects in input */
int overflow = 0; /* 1 => too much input */
long slot[2000]; /* slots */
int slotnum;
extern uchar *savechar(), *draw_obj(), *click_obj(), *step_obj();
extern uchar *prev_obj(), *next_obj();
extern Point readpt(), fetchpt(), scalept();
int xmax, ymax;
#define MAXVIEW 10
char *viewname[MAXVIEW];
typedef struct {
Rectangle vr;
int xmax;
int ymax;
} View;
View viewpt[MAXVIEW];
int curview = 0;
int nview = 0;
#define INSET 4 /* picture inset from frame */
#define AW 8 /* arrowhead width and height */
#define AH 10
#define MARGINPCT 6
int margin = MARGINPCT; /* percent margin around edges */
#define MAXCLICK 20
char *clickname[MAXCLICK]; /* click names */
int clickval[MAXCLICK]; /* 1 => click on this */
int clicking = 0; /* number of active clicks */
int nclick = 0;
char buf[200];
char *pbuf;
#define Again 0 /* Menu items -- must be 0.. */
#define Faster (Again+1)
#define Slower (Faster+1)
#define Step (Slower+1)
#define Forward (Step+1)
#define Fatter (Forward+1)
#define Thinner (Fatter+1)
#define Xor (Thinner+1)
#define File (Xor+1)
#define Quit (File+1) /* ... down this far */
#define Backward (Quit+1) /* subsequent names are arbitrary */
#define Proceed (Backward+1)
#define Hit (Proceed+1)
int delay = 1; /* how long to delay between things */
int singstep = 0; /* single step if 1 */
#define Fwd 1
#define Back 0
int dir = Fwd; /* 1 = fwd, 0 = backward */
int fatness = 0; /* n => draw with 2n+1 lines */
int xormode = F_XOR; /* otherwise OR/CLR */
char *m3[] = { "again", "faster", "slower", "1 step", "backward",
"fatter", "thinner", "or mode", "new file", "Quit?", 0 };
char *stepmenu[] = { "1 step", "run" };
char *dirmenu[] = { "forward", "backward" };
char *modemenu[] = { "or mode", "xor mode" };
char *m3gen(n)
{
static char buf[50];
extern int delay;
if (n < 0 || n > Quit)
return 0;
else if (n == Faster) {
sprintf(buf, "faster %d", delay);
return buf;
} else if (n == Slower) {
sprintf(buf, "slower %d", delay);
return buf;
} else if (n == Step) {
return stepmenu[singstep];
} else if (n == Forward) {
return dirmenu[dir];
} else if (n == Fatter) {
sprintf(buf, "fatter %d", fatness+1);
return buf;
} else if (n == Thinner) {
sprintf(buf, "thinner %d", fatness+1);
return buf;
} else if (n == Xor) {
return xormode == F_XOR ? modemenu[0] : modemenu[1];
} else
return m3[n];
}
char *m2gen(n)
{
static char buf[50];
if (n < 0 || n >= nview+nclick)
return 0;
else if (n < nview) {
sprintf(buf, "view %s", viewname[n]);
return buf;
} else {
sprintf(buf, "click %s%s",
clickname[n-nview], clickval[n-nview] ? "*" : "");
return buf;
}
}
#ifdef V9
Menu mbut3 = { (char **) 0, m3gen };
Menu mbut2 = { (char **) 0, m2gen };
#else
Menu mbut3 = { (char **) 0, 0,0,m3gen };
Menu mbut2 = { (char **) 0, 0,0,m2gen };
#endif
int last_hit;
int last_but;
init_params()
{
int i;
for (i = 0; i < MAXCLICK; i++)
if (clickname[i]) {
free(clickname[i]);
clickname[i] = 0;
clickval[i] = 0;
}
for (i = 0; i < MAXVIEW; i++)
if (viewname[i]) {
free(viewname[i]);
viewname[i] = 0;
}
nclick = nview = curview = nobj = clicking = overflow = slotnum = 0;
}
main()
{
uchar *ip;
int i, n, c;
request(KBD|MOUSE|SEND|RCV);
top:
clear();
init_params();
putstring("terminal alive");
memsize = readint();
if (inbuf == (uchar*)NULL && (inbuf = (uchar *) alloc(memsize)) == (uchar*)NULL) {
PUT "can't allocate %d bytes", memsize END
sleep(1000);
exit(1);
}
input = inbuf+1; /* leave a null at front */
inp = inbuf+1; /* next free slot in input */
cursswitch(&deadmouse);
do_rcv();
PUT "%d objects, %d/%d bytes", nobj, inp-input, memsize END
cursswitch(PTR);
dir = Fwd;
ip = inp; /* pointing at the end */
again:
for ( ; ip; wait(CPU)) {
while (checkmouse() == 0)
wait(CPU);
n = domouse();
if (n == Quit) {
send1char(P_QUIT);
flushproto();
exit();
}
if (n == Hit) /* wait for another mouse hit */
continue;
if (n == Forward) { /* change from fwd to back -- fiddle ip */
ip = next_obj(ip);
continue;
}
if (n == Backward) {
ip = prev_obj(ip);
continue;
}
if (n == File) {
putstring("filename? ");
if (do_kbd() == 0)
continue;
send1char(P_FILE);
sendstring(kbdline);
if ((c = readchar()) == P_FILE)
goto top;
PUT "can't open file %s", kbdline END;
continue;
}
if (n == Again) {
if (P->state & RESHAPED) {
view_setup(nview);
P->state &= ~RESHAPED;
}
clear();
dir = Fwd;
ip = input;
}
if (singstep) {
if (clicking > 0)
ip = click_obj(ip, xormode, dir);
else
ip = step_obj(ip, xormode, dir);
} else { /* free running */
while (ip) {
if (clicking > 0)
ip = click_obj(ip, xormode, dir);
else
ip = step_obj(ip, xormode, dir);
if (checkmouse())
break; /* back round main loop */
sleep(delay-1);
wait(CPU);
}
}
}
if (ip == 0)
ip = dir == Fwd ? inp : input;
goto again;
}
domouse()
{
int n;
if (last_but == 1)
return Proceed;
if (last_but == 3) {
switch (last_hit) {
case Again:
return Again;
case Faster:
if (delay > 1)
delay /= 2;
return Hit;
case Slower:
delay *= 2;
return Hit;
case Step:
singstep = 1 - singstep;
n = Step;
return Hit;
case Forward:
dir = 1 - dir;
if (xormode == F_OR)
xormode = F_CLR;
else if (xormode == F_CLR)
xormode = F_OR;
return dir == Fwd ? Forward : Backward;
case Fatter:
fatness++;
return Hit;
case Thinner:
if (fatness > 0)
fatness--;
return Hit;
case Xor:
if (xormode == F_OR || xormode == F_CLR)
xormode = F_XOR;
else if (dir == Fwd)
xormode = F_OR;
else
xormode = F_CLR;
return Hit;
case File:
return File;
case Quit:
return Quit;
default:
return Hit;
}
} else if (last_but == 2) {
Rectangle r, shrink();
if (last_hit == -1)
return Hit;
else if (last_hit < nview) {
#ifdef V9
r = getrect23();
#else
r = getrect();
#endif
if (r.origin.x == 0 && r.corner.x == 0) /* bailed out */
return Hit;
if (eqpt(r.origin, r.corner))
r = Drect;
drawrect(inset(viewpt[last_hit].vr, -(INSET+fatness)), F_CLR);
drawrect(r, F_OR);
viewpt[last_hit].vr = r = inset(r, INSET+fatness);
viewpt[last_hit].xmax = r.corner.x - r.origin.x;
viewpt[last_hit].ymax = r.corner.y - r.origin.y;
return Hit;
} else { /* a click */
if (clickval[last_hit-nview]) { /* was on, so turn off */
clickval[last_hit-nview] = 0;
clicking--;
} else {
clickval[last_hit-nview] = 1;
clicking++;
}
return Hit;
}
}
}
Point pt(x, y) { Point p; p.x = x; p.y = y; return p; }
Rectangle shrink(r, pct) /* shrink rectangle by 2*pct */
Rectangle r;
{
int dx = muldiv(r.corner.x-r.origin.x, pct, 100);
int dy = muldiv(r.corner.y-r.origin.y, pct, 100);
r.origin = add(r.origin, Pt(dx,dy));
r.corner = sub(r.corner, Pt(dx,dy));
return r;
}
view_setup(n)
int n;
{
int i, dx, dy;
dx = (Dc.x - Do.x) / 2;
dy = (Dc.y - Do.y) / 2;
viewpt[0].vr = Drect;
switch (n) {
case 2:
viewpt[1].vr = Drect;
viewpt[0].vr.corner.y -= dy;
viewpt[1].vr.origin.y += dy;
break;
case 3: case 4:
viewpt[0].vr.corner = add(Drect.origin, Pt(dx,dy));
viewpt[1].vr = raddp(viewpt[0].vr, Pt(0,dy));
viewpt[2].vr = raddp(viewpt[0].vr, Pt(dx,0));
viewpt[3].vr = raddp(viewpt[0].vr, Pt(dx,dy));
break;
}
for (i = 0; i < n; i++) {
viewpt[i].vr = shrink(viewpt[i].vr, MARGINPCT);
viewpt[i].xmax = viewpt[i].vr.corner.x - viewpt[i].vr.origin.x;
viewpt[i].ymax = viewpt[i].vr.corner.y - viewpt[i].vr.origin.y;
}
}
drawrect(r, mode)
Rectangle r;
{
segment(&display, r.origin, Pt(r.origin.x,r.corner.y), mode);
segment(&display, r.origin, Pt(r.corner.x,r.origin.y), mode);
segment(&display, r.corner, Pt(r.origin.x,r.corner.y), mode);
segment(&display, r.corner, Pt(r.corner.x,r.origin.y), mode);
}
do_rcv()
{
int c, n, b, m, i;
uchar *ip;
top:
switch (c = readchar()) {
case P_INIT: /* initialize */
break;
case P_ENDFILE:
*inp = 0;
return;
case P_CLEAR:
clear();
break;
case P_DEFINE:
/* view, click, ... */
c = readchar();
assert(c == 'c' || c == 'v', "illegal define");
i = readint();
readstring(buf);
if (c == 'c') {
clickname[i] = alloc(strlen(buf)+1);
strcpy(clickname[i], buf);
nclick++;
} else { /* c == 'v' */
viewname[i] = alloc(strlen(buf)+1);
strcpy(viewname[i], buf);
nview++;
}
break;
case P_OBJECT: /* read an object */
ip = inp;
if (nobj++ == 0)
view_setup(nview);
if (nobj % 100 == 0)
PUT "%d objects, %d/%d bytes", nobj, inp-input, memsize END
read_obj();
draw_obj(ip, F_XOR, Fwd);
break;
case P_PRINT: /* print a string */
readstring(buf);
putstring(buf);
break;
default:
if (overflow)
break;
putstring("do_rcv error; unrecognized command: ");
for (pbuf = buf; c != '\n'; c = readchar())
*pbuf++ = c;
*pbuf = 0;
putstring(buf);
break;
}
goto top;
}
clear()
{
rectf(&display, Drect, F_CLR); /* clear screen */
}
read_obj() /* read an object, stick it in input[] */
{
int c, n, m;
uchar *p;
if (inp > input+memsize-50) { /* eat up at least one */
PUT "overflow at %d bytes, limit %d", inp - input, memsize END
overflow++;
return;
}
c = readchar();
switch (c) {
case ' ':
case '\n':
break;
case 'b':
case 'l':
slotnum = readint();
slot[slotnum] = inp-input;
p = savechar(c);
savechar(curview = readint());
savechar(readint()); /* options */
savept(readpt());
savept(readpt());
savect(inp-p);
break;
case 'o':
slotnum = readint();
slot[slotnum] = inp-input;
p = savechar(c);
savechar(curview = readint());
savechar(readint()); /* options */
savept(readpt());
saveint(readint()); /* radius */
savect(inp-p);
break;
case 't':
slotnum = readint();
slot[slotnum] = inp-input;
p = savechar(c);
savechar(curview = readint());
savechar(readint()); /* options */
savept(readpt());
n = readstring(inp+1); /* +1 leaves a hole */
*inp = n; /* insert count before string */
inp += n + 2; /* +2 = count before and \0 on end */
savect(inp-p);
break;
case 'e':
p = savechar(c);
savelong(slot[readint()]);
savect(inp-p);
break;
case 'c':
p = savechar(c);
savechar(readint());
savect(inp-p);
break;
}
}
Point readpt() /* read a Point */
{
Point p;
p.x = readint();
p.y = readint();
return p;
}
uchar *savechar(c)
{
*inp++ = c;
return inp-1;
}
savect(n)
{
if (n > 255)
putstring("text string too long");
*inp++ = n;
}
saveint(n)
{
*inp++ = n >> 8;
*inp++ = n & 0377;
}
savelong(n)
long n;
{
*inp++ = n >> 24;
*inp++ = n >> 16;
*inp++ = n >> 8;
*inp++ = n;
}
savept(p)
Point p;
{
*inp++ = p.x >> 8;
*inp++ = p.x & 0377;
*inp++ = p.y >> 8;
*inp++ = p.y & 0377;
}
getpoint(ip)
uchar *ip;
{
return *ip << 8 | *(ip+1);
}
long getlong(ip)
uchar *ip;
{
return *ip << 24 | *(ip+1) << 16 | *(ip+2) << 8 | *(ip+3);
}
Point scalept(v, p)
Point p;
{
p.x = p.x * viewpt[v].xmax / 10000;
p.y = viewpt[v].ymax - p.y * viewpt[v].ymax / 10000;
return p;
}
scalex(v, n)
{
return n * viewpt[v].xmax / 10000;
}
Point fetchpt(v, ip)
int v;
uchar *ip;
{
Point pt;
pt.x = *ip << 8 | *(ip+1);
pt.y = *(ip+2) << 8 | *(ip+3);
pt = scalept(v, pt);
return add(pt, viewpt[v].vr.origin);
}
/*
Encoding: type, view#, opts, coords, chars, etc., # = length of group
bvoxxyyxxyy#
lvoxxyyxxyy#
ovoxxyyrr#
tvoxxyynccc0#
ennnn#
cn#
*/
uchar *prev_obj(ip)
uchar *ip;
{
if (ip <= input)
return 0;
return ip - ip[-1] - 1;
}
uchar *next_obj(ip)
uchar *ip;
{
if (ip < input || ip >= inp)
return 0;
switch (*ip) {
case 0:
return 0;
case 'b':
case 'l':
return ip + 12;
case 'o':
return ip + 10;
case 't':
return ip + ip[7] + 10;
case 'c':
return ip + 3;
case 'e':
return ip + 6;
default:
return 0;
}
}
uchar *step_obj(ip, mode, dir) /* draw objs until one that changes something */
uchar *ip;
{
int c;
while (ip) {
c = *ip;
ip = draw_obj(ip, mode, dir);
if (c == 'b' || c == 'l' || c == 't' || c == 'e' || c == 'o')
return ip;
}
return ip;
}
uchar *click_obj(ip, mode, dir) /* draw objs until matching click */
uchar *ip;
{
int c, n = 0;
uchar *oip;
for (;;) {
oip = ip;
ip = draw_obj(ip, mode, dir);
if (ip == 0 || (oip && *oip == 'c' && clickval[oip[1]]))
return ip;
if (++n % 100 == 0) /* return occasionally to see mouse */
return ip;
}
}
uchar *draw_obj(ip, mode, dir) /* draw obj from coords at ip */
uchar *ip;
int mode, dir;
{
int c, r, thick, n, shift, head;
Point p0, p1, p2;
if (ip < input || ip >= inp)
return 0;
switch (c = *ip++) {
case 'b':
p0 = fetchpt(*ip, ip+2);
p1 = fetchpt(*ip, ip+6);
if (ip[1] == Bfill) {
if (p0.y < p1.y)
rectf(&display, Rpt(p0, p1), mode);
else
rectf(&display, Rect(p0.x,p1.y,p1.x,p0.y), mode);
} else {
segment(&display, p0, Pt(p0.x,p1.y), mode);
segment(&display, Pt(p0.x,p1.y), p1, mode);
segment(&display, p1, Pt(p1.x,p0.y), mode);
segment(&display, Pt(p1.x,p0.y), p0, mode);
}
if (dir == Fwd)
ip += 1+9+1;
else
ip -= (*(ip-2) + 2);
break;
case 'l':
p0 = fetchpt(*ip, ip+2);
p1 = fetchpt(*ip, ip+6);
thick = ip[1]/10; /* ought to be a macro! */
if (thick == Ldotted/10 || thick == Ldashed/10)
thick = 1;
thick = 2 * thick - 1; /* 1,3,5 */
fatline(p0, p1, mode, thick);
head = ip[1]%10; /* ditto */
if (head == Larrow1 || head == Larrow3)
arrow(p0, p1, AW, AH, mode);
if (head == Larrow2 || head == Larrow3)
arrow(p1, p0, AW, AH, mode);
if (dir == Fwd)
ip += 1+9+1;
else
ip -= (*(ip-2) + 2);
break;
case 'o':
p0 = fetchpt(*ip, ip+2);
r = scalex(*ip, getpoint(ip+6));
if (ip[1] == Cnofill)
circle(&display, p0, r, mode);
else
disc(&display, p0, r, mode);
if (dir == Fwd)
ip += 1+7+1;
else
ip -= (*(ip-2) + 2);
break;
case 't':
p0 = fetchpt(*ip, ip+2);
n = ip[6];
shift = (ip[1]/10) * 10; /* ought to be a macro! */
if (shift == Tljust)
shift = 0;
else if (shift == Tcenter)
shift = (9 * n) / 2; /* 9 = char width */
else
shift = 9 * n;
string(&defont, ip+7, &display, sub(p0, Pt(shift,8)), mode);
if (dir == Fwd)
ip += 1+5 + *(ip+6)+2 + 1;
else
ip -= (*(ip-2) + 2);
break;
case 'e':
erase(ip-1);
if (dir == Fwd)
ip += 5;
else
ip -= (*(ip-2) + 2);
break;
case 'c':
if (dir == Fwd)
ip += 2;
else
ip -= (*(ip-2) + 2);
break;
default:
ip = 0;
break;
}
return ip;
}
erase(ip)
uchar *ip;
{
long target = getlong(ip+1); /* target label index */
int mode = F_XOR;
if (xormode == F_OR || xormode == F_CLR)
mode = dir == Fwd ? F_CLR : F_OR;
draw_obj(input+target, mode, Fwd);
}
#define abs(x) ((x) >= 0 ? (x) : -(x))
fatline(p0, p1, mode, thick)
Point p0, p1;
{
int i, fat, beg, nl;
fat = thick * (2 * fatness + 1);
beg = fat / 2;
if (abs(p1.x-p0.x) >= abs(p1.y-p0.y)) { /* horizontal */
for (nl = 0, i = -beg; nl < fat; nl++, i++)
segment(&display, add(p0, Pt(0,i)), add(p1, Pt(0,i)), mode);
} else {
for (nl = 0, i = -beg; nl < fat; nl++, i++)
segment(&display, add(p0, Pt(i,0)), add(p1, Pt(i,0)), mode);
}
}
arrow(p1, p2, w, h, c)
Point p1, p2;
int w, h, c;
/*
** draw arrow of height,width (h,w) at p2 of segment p1,p2.
*/
{
Point d;
int norm, qx, qy, lx, ly;
d = sub(p2, p1);
norm = sqrt((long)d.x*d.x + (long)d.y*d.y);
if (norm == 0) /* shouldn't happen, but ... */
return;
qx = p2.x - h*d.x/norm;
qy = p2.y - h*d.y/norm;
lx = w/2 * -d.y/norm;
ly = w/2 * d.x/norm;
/* segment(&display, p1, p2, c); */
segment(&display, Pt(qx+lx, qy+ly), p2, c);
segment(&display, Pt(qx-lx, qy-ly), p2, c);
}
but(n) /* parameterize button1(), etc. */
int n;
{
switch (n) {
case 1: return button1();
case 2: return button2();
case 3: return button3();
case 12: return button12();
case 13: return button1()|button3(); /* not built in */
case 23: return button23();
case 123: return button123();
default: return 0;
}
}
checkmouse() /* return button touched if any */
{
int c, b;
char *p = NULL;
Point dp;
#ifdef V9
Texture *tp;
#else
Texture16 *tp;
#endif
if (!(own()&MOUSE) || !button123())
return 0; /* no hit at all */
tp = cursswitch(PTR);
last_but = 0;
last_hit = -1;
c = 0;
if (button3()) {
last_hit = menuhit(&mbut3, 3);
last_but = 3;
} else if (button2()) {
last_hit = menuhit(&mbut2, 2);
last_but = 2;
} else { /* button1() */
last_but = 1;
}
while (button123())
wait(MOUSE);
if (last_but == 3) {
p = m3[last_hit];
c = p[strlen(p) - 1];
}
if (c == '?' && !confirm(last_but))
last_hit = -1;
cursswitch(tp);
return last_but;
}
confirm(but) /* ask for confirmation if menu item ends with '?' */
{
int c;
static int but_cvt[8] = { 0, 3, 2, 0, 1, 0, 0, 0 };
#ifdef V9
Texture *tp;
#else
Texture16 *tp;
#endif
tp = cursswitch(&skull);
while (!button123())
wait(MOUSE);
c = mouse.buttons & 7;
while (button123())
wait(MOUSE);
cursswitch(tp);
return but == but_cvt[c];
}
putstring(a1, a2, a3, a4, a5)
char *a1, *a2, *a3, *a4, *a5;
{
static Point p = {0, 0};
static int jmax = 0, l;
char buf[100], *s;
p = Drect.origin;
sprintf(buf, a1, a2, a3, a4, a5);
rectf(&display, Rect(p.x,p.y,p.x+jmax,p.y+16), F_CLR);
string(&defont, buf, &display, p, F_OR);
if ((l = jstrwidth(buf)) > jmax)
jmax = l;
}
assert(c, s) /* poor man's assertion */
char *s;
{
if (!c) {
putstring("assertion %s failed\n", s);
sleep(60);
}
}
readbyte()
{
int c;
if ((c = rcvchar()) == -1) {
wait(RCV);
c = rcvchar();
}
return c;
}
sendbuf(buf, p)
char *buf, *p;
{
sendnchars((int) (p-buf), buf);
}
do_kbd()
{
int c;
pkbd = kbdline;
for (wait(KBD); (c = kbdchar()) != -1; wait(KBD)) {
if (c == '\b' && pkbd > kbdline)
--pkbd;
else if (c == '@')
pkbd = kbdline;
else
*pkbd++ = c;
*pkbd = '\0';
rectf(&display, Rect(Do.x,Do.y,Dc.x,Do.y+16), F_CLR);
string(&defont, kbdline, &display, Do, F_STORE);
if (c == '\n' || c == '\r') {
pkbd[-1] = '\0';
return '\n';
}
}
}