summaryrefslogtreecommitdiff
path: root/appl/wm/minitel/screen.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/wm/minitel/screen.b')
-rw-r--r--appl/wm/minitel/screen.b1610
1 files changed, 1610 insertions, 0 deletions
diff --git a/appl/wm/minitel/screen.b b/appl/wm/minitel/screen.b
new file mode 100644
index 00000000..4313d48d
--- /dev/null
+++ b/appl/wm/minitel/screen.b
@@ -0,0 +1,1610 @@
+#
+# Occasional references are made to sections and tables in the
+# France Telecom Minitel specification
+#
+# Copyright © 1998 Vita Nuova Limited. All rights reserved.
+#
+
+include "mdisplay.m";
+
+disp: MDisplay;
+
+Rect, Point : import Draw;
+
+# display character sets
+videotex, semigraphic, french, american :import MDisplay;
+
+# display foreground colour attributes
+fgBlack, fgBlue, fgRed, fgMagenta,
+fgGreen, fgCyan, fgYellow, fgWhite :import MDisplay;
+
+# display background colour attributes
+bgBlack, bgBlue, bgRed, bgMagenta,
+bgGreen, bgCyan, bgYellow, bgWhite :import MDisplay;
+
+fgMask, bgMask : import MDisplay;
+
+# display formatting attributes
+attrB, attrW, attrH, attrP, attrF, attrC, attrL, attrD :import MDisplay;
+
+# Initial attributes - white on black
+ATTR0: con fgWhite|bgBlack&~(attrB|attrW|attrH|attrP|attrF|attrC|attrL|attrD);
+
+# special features
+Cursor, Scroll, Insert
+ : con (1 << iota);
+
+# Screen states
+Sstart, Sss2, Sesc, Srepeat, Saccent, Scsi0, Scsi1, Sus0, Sus1, Sskip,
+Siso2022, Siso6429, Stransparent, Sdrcs, Sconceal, Swaitfor
+ : con iota;
+
+# Filter states
+FSstart, FSesc, FSsep, FS6429, FS2022: con iota;
+
+Screen: adt {
+ m: ref Module; # common attributes
+ ctxt: ref Draw->Context;
+ in: chan of ref Event; # from the terminal
+
+ image: ref Draw->Image; # Mdisplay image
+ dispr40, dispr80: Rect; # 40 and 80 column display region
+ oldtmode: int; # old terminal mode
+ rows: int; # number of screen rows (25 for minitel)
+ cols: int; # number of screen cols (40 or 80)
+ cset: int; # current display charset
+
+ pos: Point; # current writing position (x:1, y:0)
+ attr: int; # display attribute set
+ spec: int; # special features
+ savepos: Point; # `pos' before moving to row zero
+ saveattr: int; # `attr' before moving to row zero
+ savech: int; # last character `Put'
+ delimit: int; # attr changed, make next space a delimiter
+ cursor: int; # update cursor soon
+
+ state: int; # recogniser state
+ a0: int; # recogniser arg 0
+ a1: int; # recogniser arg 1
+
+ fstate: int; # filter state
+ fsaved: array of byte; # filter `chars so far'
+ badp: int; # filter because of bad parameter
+
+ ignoredata: int; # ignore data from
+
+ init: fn(s: self ref Screen, ctxt: ref Draw->Context, r40, r80: Rect);
+ reset: fn(s: self ref Screen);
+ run: fn(s: self ref Screen);
+ quit: fn(s: self ref Screen);
+ setmode: fn(s: self ref Screen, tmode: int);
+ runstate: fn(s: self ref Screen, data: array of byte);
+ put: fn(s: self ref Screen, str: string);
+ msg: fn(s: self ref Screen, str: string);
+};
+
+Screen.init(s: self ref Screen, ctxt: ref Draw->Context, r40, r80: Rect)
+{
+ disp = load MDisplay MDisplay->PATH;
+ if(disp == nil)
+ fatal("can't load the display module: "+MDisplay->PATH);
+
+ s.m = ref Module(0, 0);
+ s.ctxt = ctxt;
+ s.dispr40 = r40;
+ s.dispr80 = r80;
+ s.oldtmode = -1;
+ s.in = chan of ref Event;
+ disp->Init(s.ctxt);
+ s.reset();
+ s.pos = Point(1, 1);
+ s.savech = 0;
+ s.cursor = 1;
+ s.ignoredata = 0;
+ s.fstate = FSstart;
+}
+
+Screen.reset(s: self ref Screen)
+{
+ s.setmode(T.mode);
+ indicators(s);
+ s.state = Sstart;
+}
+
+Screen.run(s: self ref Screen)
+{
+Runloop:
+ for(;;) alt {
+ ev := <- s.in =>
+ pick e := ev {
+ Equit =>
+ break Runloop;
+ Eproto =>
+ case e.cmd {
+ Creset =>
+ s.reset();
+ Cproto =>
+ case e.a0 {
+ START =>
+ case e.a1 {
+ SCROLLING =>
+ s.spec |= Scroll;
+ }
+ STOP =>
+ case e.a1 {
+ SCROLLING =>
+ s.spec &= ~Scroll;
+ }
+ MIXED =>
+ case e.a1 {
+ MIXED1 => # videotex -> mixed
+ if(T.mode != Mixed)
+ s.setmode(Mixed);
+ T.mode = Mixed;
+ MIXED2 => # mixed -> videotex
+ if(T.mode != Videotex)
+ s.setmode(Videotex);
+ T.mode = Videotex;
+ }
+ }
+ Ccursor => # update the cursor soon
+ s.cursor = 1;
+ Cindicators =>
+ indicators(s);
+ Cscreenoff =>
+ s.ignoredata = 1;
+ s.state = Sstart;
+ Cscreenon =>
+ s.ignoredata = 0;
+ * => break;
+ }
+ Edata =>
+ if(s.ignoredata)
+ continue;
+ oldpos := s.pos;
+ oldspec := s.spec;
+ da := filter(s, e.data);
+ while(len da > 0) {
+ s.runstate(da[0]);
+ da = da[1:];
+ }
+
+ if(s.pos.x != oldpos.x || s.pos.y != oldpos.y || (s.spec&Cursor)^(oldspec&Cursor))
+ s.cursor = 1;
+ if(s.cursor) {
+ if(s.spec & Cursor)
+ disp->Cursor(s.pos);
+ else
+ disp->Cursor(Point(-1,-1));
+ s.cursor = 0;
+ refresh();
+ } else if(e.from == Mkeyb)
+ refresh();
+ }
+ }
+ send(nil);
+}
+
+# row0 indicators (1.2.2)
+indicators(s: ref Screen)
+{
+ col: int;
+ ch: string;
+
+ attr := fgWhite|bgBlack;
+ case T.state {
+ Local =>
+ ch = "F";
+ Connecting =>
+ ch = "C";
+ attr |= attrF;
+ Online =>
+ ch = "C";
+ }
+ if(s.cols == 40) {
+ col = 39;
+ attr |= attrP;
+ } else
+ col = 77;
+ disp->Put(ch, Point(col, 0), videotex, attr, 0);
+}
+
+Screen.setmode(s: self ref Screen, tmode: int)
+{
+ dispr: Rect;
+ delims: int;
+ ulheight: int;
+ s.rows = 25;
+ s.spec = 0;
+ s.attr = s.saveattr = ATTR0;
+ s.delimit = 0;
+ s.pos = s.savepos = Point(-1, -1);
+ s.cursor = 1;
+ case tmode {
+ Videotex =>
+ s.cset = videotex;
+ s.cols = 40;
+ dispr = s.dispr40;
+ delims = 1;
+ ulheight = 2;
+ s.pos = Point(1,1);
+ s.spec &= ~Cursor;
+ Mixed =>
+# s.cset = french;
+ s.cset = videotex;
+ s.cols = 80;
+ dispr = s.dispr80;
+ delims = 0;
+ ulheight = 1;
+ s.spec |= Scroll;
+ s.pos = Point(1, 1);
+ Ascii =>
+ s.cset = french;
+ s.cols = 80;
+ dispr = s.dispr80;
+ delims = 0;
+ ulheight = 1;
+ };
+ if(tmode != s.oldtmode) {
+ (nil, s.image) = disp->Mode(((0,0),(0,0)), 0, 0, 0, 0, nil);
+ T.layout(s.cols);
+ fontpath := sprint("/fonts/minitel/f%dx%d", s.cols, s.rows);
+ (nil, s.image) = disp->Mode(dispr, s.cols, s.rows, ulheight, delims, fontpath);
+ T.setkbmode(tmode);
+ }
+ disp->Reveal(0); # concealing enabled (1.2.2)
+ disp->Cursor(Point(-1,-1));
+ s.oldtmode = tmode;
+}
+
+Screen.quit(nil: self ref Screen)
+{
+ disp->Quit();
+}
+
+Screen.runstate(s: self ref Screen, data: array of byte)
+{
+ while(len data > 0)
+ case T.mode {
+ Videotex =>
+ data = vstate(s, data);
+ Mixed =>
+ data = mstate(s, data);
+ Ascii =>
+ data = astate(s, data);
+ };
+}
+
+# process a byte from set C0
+vc0(s: ref Screen, ch: int)
+{
+ case ch {
+# SOH => # not in spec, wait for 16r04
+# s.a0 = 16r04;
+# s.state = Swaitfor;
+ SS2 =>
+ s.state = Sss2;
+ SYN =>
+ s.state = Sss2; # not in the spec, but acts like SS2
+ ESC =>
+ s.state = Sesc;
+ SO =>
+ s.cset = semigraphic;
+ s.attr &= ~(attrH|attrW|attrP); # 1.2.4.2
+ s.attr &= ~attrL; # 1.2.4.3
+ SI =>
+ s.cset = videotex;
+ s.attr &= ~attrL; # 1.2.4.3
+ s.attr &= ~(attrH|attrW|attrP); # some servers seem to assume this too
+ SEP or SS3 => # 1.2.7
+ s.state = Sskip;
+ BS =>
+ if(s.pos.x == 1) {
+ if(s.pos.y == 0)
+ break;
+ if(s.pos.y == 1)
+ s.pos.y = s.rows - 1;
+ else
+ s.pos.y -= 1;
+ s.pos.x = s.cols;
+ } else
+ s.pos.x -= 1;
+ HT =>
+ if(s.pos.x == s.cols) {
+ if(s.pos.y == 0)
+ break;
+ if(s.pos.y == s.rows - 1)
+ s.pos.y = 1;
+ else
+ s.pos.y += 1;
+ s.pos.x = 1;
+ } else
+ s.pos.x += 1;
+ LF =>
+ if(s.pos.y == s.rows - 1)
+ if(s.spec&Scroll)
+ scroll(1, 1);
+ else
+ s.pos.y = 1;
+ else if(s.pos.y == 0) { # restore attributes on leaving row zero
+ s.pos = s.savepos;
+ s.attr = s.saveattr;
+ } else
+ s.pos.y += 1;
+ VT =>
+ if(s.pos.y == 1)
+ if(s.spec&Scroll)
+ scroll(1, -1);
+ else
+ s.pos.y = s.rows - 1;
+ else if(s.pos.y == 0)
+ break;
+ else
+ s.pos.y -= 1;
+ CR =>
+ s.pos.x = 1;
+ CAN =>
+ cols := s.cols - s.pos.x + 1;
+ disp->Put(dup(' ', cols), Point(s.pos.x,s.pos.y), s.cset, s.attr, 0);
+ US =>
+ # expect US row, col
+ s.state = Sus0;
+ FF =>
+ s.cset = videotex;
+ s.attr = ATTR0;
+ s.pos = Point(1,1);
+ s.spec &= ~Cursor;
+ s.cursor = 1;
+ clear(s);
+ RS =>
+ s.cset = videotex;
+ s.attr = ATTR0;
+ s.pos = Point(1,1);
+ s.spec &= ~Cursor;
+ s.cursor = 1;
+ CON =>
+ s.spec |= Cursor;
+ s.cursor = 1;
+ COFF =>
+ s.spec &= ~Cursor;
+ s.cursor = 1;
+ REP =>
+ # repeat
+ s.state = Srepeat;
+ NUL =>
+ # padding character - ignore, but may appear anywhere
+ ;
+ BEL =>
+ # ah ...
+ ;
+ }
+}
+
+# process a byte from the set c1 - introduced by the ESC character
+vc1(s: ref Screen, ch: int)
+{
+ if(ISC0(ch)) {
+ s.state = Sstart;
+ vc0(s, ch);
+ return;
+ }
+ if(ch >= 16r20 && ch <= 16r2f) {
+ if(ch == 16r25)
+ s.state = Stransparent;
+ else if(ch == 16r23)
+ s.state = Sconceal;
+ else
+ s.state = Siso2022;
+ s.a0 = s.a1 = 0;
+ return;
+ }
+
+ fg := bg := -1;
+ case ch {
+ 16r35 or
+ 16r36 or
+ 16r37 =>
+ s.state = Sskip; # skip next char unless C0
+ return;
+
+ 16r5b => # CSI sequence
+ s.a0 = s.a1 = 0;
+ if(s.pos.y > 0) # 1.2.5.2
+ s.state = Scsi0;
+ return;
+
+ # foreground colour
+ 16r40 => fg = fgBlack;
+ 16r41 => fg = fgRed;
+ 16r42 => fg = fgGreen;
+ 16r43 => fg = fgYellow;
+ 16r44 => fg = fgBlue;
+ 16r45 => fg = fgMagenta;
+ 16r46 => fg = fgCyan;
+ 16r47 => fg = fgWhite;
+
+ # background colour
+ 16r50 => bg = bgBlack;
+ 16r51 => bg = bgRed;
+ 16r52 => bg = bgGreen;
+ 16r53 => bg = bgYellow;
+ 16r54 => bg = bgBlue;
+ 16r55 => bg = bgMagenta;
+ 16r56 => bg = bgCyan;
+ 16r57 => bg = bgWhite;
+
+ # flashing
+ 16r48 => s.attr |= attrF;
+ 16r49 => s.attr &= ~attrF;
+
+ # conceal (serial attribute)
+ 16r58 => s.attr |= attrC;
+ s.delimit = 1;
+ 16r5f => s.attr &= ~attrC;
+ s.delimit = 1;
+
+ # start lining (+separated graphics) (serial attribute)
+ 16r5a => s.attr |= attrL;
+ s.delimit = 1;
+ 16r59 => s.attr &= ~attrL;
+ s.delimit = 1;
+
+ # reverse polarity
+ 16r5d => s.attr |= attrP;
+ 16r5c => s.attr &= ~attrP;
+
+ # normal size
+ 16r4c =>
+ s.attr &= ~(attrW|attrH);
+
+ # double height
+ 16r4d =>
+ if(s.pos.y < 2)
+ break;
+ s.attr &= ~(attrW|attrH);
+ s.attr |= attrH;
+
+ # double width
+ 16r4e =>
+ if(s.pos.y < 1)
+ break;
+ s.attr &= ~(attrW|attrH);
+ s.attr |= attrW;
+
+ # double size
+ 16r4f =>
+ if(s.pos.y < 2)
+ break;
+ s.attr |= (attrW|attrH);
+ }
+ if(fg >= 0) {
+ s.attr &= ~fgMask;
+ s.attr |= fg;
+ }
+ if(bg >= 0) {
+ s.attr &= ~bgMask;
+ s.attr |= bg;
+ s.delimit = 1;
+ }
+ s.state = Sstart;
+}
+
+
+# process a SS2 character
+vss2(s: ref Screen, ch: int)
+{
+ if(ISC0(ch)) {
+ s.state = Sstart;
+ vc0(s, ch);
+ return;
+ }
+ case ch {
+ 16r41 or # grave # 5.1.2
+ 16r42 or # acute
+ 16r43 or # circumflex
+ 16r48 or # umlaut
+ 16r4b => # cedilla
+ s.a0 = ch;
+ s.state = Saccent;
+ return;
+ 16r23 => ch = '£'; # Figure 2.8
+ 16r24 => ch = '$';
+ 16r26 => ch = '#';
+ 16r27 => ch = '§';
+ 16r2c => ch = 16rc3; # '←';
+ 16r2d => ch = 16rc0; # '↑';
+ 16r2e => ch = 16rc4; # '→';
+ 16r2f => ch = 16rc5; # '↓';
+ 16r30 => ch = '°';
+ 16r31 => ch = '±';
+ 16r38 => ch = '÷';
+ 16r3c => ch = '¼';
+ 16r3d => ch = '½';
+ 16r3e => ch = '¾';
+ 16r7a => ch = 'œ';
+ 16r6a => ch = 'Œ';
+ 16r7b => ch = 'ß';
+ }
+ s.put(tostr(ch));
+ s.savech = ch;
+ s.state = Sstart;
+}
+
+# process CSI functions
+vcsi(s: ref Screen, ch: int)
+{
+ case s.state {
+ Scsi0 =>
+ case ch {
+ # move cursor up n rows, stop at top of screen
+ 'A' =>
+ s.pos.y -= s.a0;
+ if(s.pos.y < 1)
+ s.pos.y = 1;
+
+ # move cursor down n rows, stop at bottom of screen
+ 'B' =>
+ s.pos.y += s.a0;
+ if(s.pos.y >= s.rows)
+ s.pos.y = s.rows - 1;
+
+ # move cursor n columns right, stop at edge of screen
+ 'C' =>
+ s.pos.x += s.a0;
+ if(s.pos.x > s.cols)
+ s.pos.x = s.cols;
+
+ # move cursor n columns left, stop at edge of screen
+ 'D' =>
+ s.pos.x -= s.a0;
+ if(s.pos.x < 1)
+ s.pos.x = 1;
+
+ # direct cursor addressing
+ ';' =>
+ s.state = Scsi1;
+ return;
+
+ 'J' =>
+ case s.a0 {
+ # clears from the cursor to the end of the screen inclusive
+ 0 =>
+ rowclear(s.pos.y, s.pos.x, s.cols);
+ for(r:=s.pos.y+1; r<s.rows; r++)
+ rowclear(r, 1, s.cols);
+ # clears from the beginning of the screen to the cursor inclusive
+ 1 =>
+ for(r:=1; r<s.pos.y; r++)
+ rowclear(r, 1, s.cols);
+ rowclear(s.pos.y, 1, s.pos.x);
+ # clears the entire screen
+ 2 =>
+ clear(s);
+ }
+
+ 'K' =>
+ case s.a0 {
+ # clears from the cursor to the end of the row
+ 0 => rowclear(s.pos.y, s.pos.x, s.cols);
+
+ # clears from the start of the row to the cursor
+ 1 => rowclear(s.pos.y, 1, s.pos.x);
+
+ # clears the entire row in which the cursor is positioned
+ 2 => rowclear(s.pos.y, 1, s.cols);
+ }
+
+ # deletes n characters from cursor position
+ 'P' =>
+ rowclear(s.pos.y, s.pos.x, s.pos.x+s.a0-1);
+
+ # inserts n characters from cursor position
+ '@' =>
+ disp->Put(dup(' ', s.a0), Point(s.pos.x,s.pos.y), s.cset, s.attr, 1);
+
+ # starts cursor insert mode
+ 'h' =>
+ if(s.a0 == 4)
+ s.spec |= Insert;
+
+ 'l' => # ends cursor insert mode
+ if(s.a0 == 4)
+ s.spec &= ~Insert;
+
+ # deletes n rows from cursor row
+ 'M' =>
+ scroll(s.pos.y, s.a0);
+
+ # inserts n rows from cursor row
+ 'L' =>
+ scroll(s.pos.y, -1*s.a0);
+ }
+ s.state = Sstart;
+ Scsi1 =>
+ case ch {
+ # direct cursor addressing
+ 'H' =>
+ if(s.a0 > 0 && s.a0 < s.rows && s.a1 > 0 && s.a1 <= s.cols)
+ s.pos = Point(s.a1, s.a0);
+ }
+ s.state = Sstart;
+ }
+}
+
+# Screen state - Videotex mode
+vstate(s: ref Screen, data: array of byte): array of byte
+{
+ i: int;
+ for(i = 0; i < len data; i++) {
+ ch := int data[i];
+
+ if(debug['s']) {
+ cs:="";
+ if(s.cset==videotex) cs = "v"; else cs="s";
+ fprint(stderr, "vstate %d, %ux (%c) %.4ux %.4ux %s (%d,%d)\n", s.state, ch, ch, s.attr, s.spec, cs, s.pos.y, s.pos.x);
+ }
+ case s.state {
+ Sstart =>
+ if(ISG0(ch) || ch == SP) {
+ n := 0;
+ str := "";
+ while(i < len data) {
+ ch = int data[i];
+ if(ISG0(ch) || ch == SP)
+ str[n++] = int data[i++];
+ else {
+ i--;
+ break;
+ }
+ }
+ if(n > 0) {
+ if(debug['s'])
+ fprint(stderr, "vstate puts(%s)\n", str);
+ s.put(str);
+ s.savech = str[n-1];
+ }
+ } else if(ISC0(ch))
+ vc0(s, ch);
+ else if(ch == DEL) {
+ if(s.cset == semigraphic)
+ ch = 16r5f;
+ s.put(tostr(ch));
+ s.savech = ch;
+ }
+ Sss2 =>
+ if(ch == NUL) # 1.2.6.1
+ continue;
+ if(s.cset == semigraphic) # 1.2.3.4
+ continue;
+ vss2(s, ch);
+ Sesc =>
+ if(ch == NUL)
+ continue;
+ vc1(s, ch);
+ Srepeat =>
+ # byte from `columns' 4 to 7 gives repeat count on 6 bits
+ # of the last `Put' character
+ if(ch == NUL)
+ continue;
+ if(ISC0(ch)) {
+ s.state = Sstart;
+ vc0(s, ch);
+ break;
+ }
+ if(ch >= 16r40 && ch <= 16r7f)
+ s.put(dup(s.savech, (ch-16r40)));
+ s.state = Sstart;
+ Saccent =>
+ case s.a0 {
+ 16r41 => # grave
+ case ch {
+ 'a' => ch = 'à';
+ 'e' => ch = 'è';
+ 'u' => ch = 'ù';
+ }
+ 16r42 => # acute
+ case ch {
+ 'e' => ch = 'é';
+ }
+ 16r43 => # circumflex
+ case ch {
+ 'a' => ch = 'â';
+ 'e' => ch = 'ê';
+ 'i' => ch = 'î';
+ 'o' => ch = 'ô';
+ 'u' => ch = 'û';
+ }
+ 16r48 => # umlaut
+ case ch {
+ 'a' => ch = 'ä';
+ 'e' => ch = 'ë';
+ 'i' => ch = 'ï';
+ 'o' => ch = 'ö';
+ 'u' => ch = 'ü';
+ }
+ 16r4b => # cedilla
+ case ch {
+ 'c' => ch = 'ç';
+ }
+ }
+ s.put(tostr(ch));
+ s.savech = ch;
+ s.state = Sstart;
+ Scsi0 =>
+ if(ch >= 16r30 && ch <= 16r39) {
+ s.a0 *= 10;
+ s.a0 += (ch - 16r30);
+ } else if((ch >= 16r20 && ch <= 16r29) || (ch >= 16r3a && ch <= 16r3f)) { # 1.2.7
+ s.a0 = 0;
+ s.state = Siso6429;
+ } else
+ vcsi(s, ch);
+ Scsi1 =>
+ if(ch >= 16r30 && ch <= 16r39) {
+ s.a1 *= 10;
+ s.a1 += (ch - 16r30);
+ } else
+ vcsi(s, ch);
+ Sus0 =>
+ if(ch == 16r23) { # start DRCS definition
+ s.state = Sdrcs;
+ s.a0 = 0;
+ break;
+ }
+ if(ch >= 16r40 && ch < 16r80)
+ s.a0 = (ch - 16r40);
+ else if(ch >= 16r30 && ch <= 16r32)
+ s.a0 = (ch - 16r30);
+ else
+ s.a0 = -1;
+ s.state = Sus1;
+ Sus1 =>
+ if(ch >= 16r40 && ch < 16r80)
+ s.a1 = (ch - 16r40);
+ else if(ch >= 16r30 && ch <= 16r39) {
+ s.a1 = (ch - 16r30);
+ s.a0 = s.a0*10 + s.a1; # shouldn't be used any more
+ s.a1 = 1;
+ } else
+ s.a1 = -1;
+ # US row, col : this is how you get to row zero
+ if(s.a0 >= 0 && s.a0 < s.rows && s.a1 > 0 && s.a1 <= s.cols) {
+ if(s.a0 == 0 && s.pos.y > 0) {
+ s.savepos = s.pos;
+ s.saveattr = s.attr;
+ }
+ s.pos = Point(s.a1, s.a0);
+ s.delimit = 0; # 1.2.5.3, don't reset serial attributes
+ s.attr = ATTR0;
+ s.cset = videotex;
+ }
+ s.state = Sstart;
+ Sskip =>
+ # swallow the next character unless from C0
+ s.state = Sstart;
+ if(ISC0(ch))
+ vc0(s, ch);
+ Swaitfor =>
+ # ignore characters until the character in a0 inclusive
+ if(ch == s.a0)
+ s.state = Sstart;
+ Siso2022 =>
+ # 1.2.7
+ # swallow (upto) 3 characters from column 2,
+ # then 1 character from columns 3 to 7
+ if(ch == NUL)
+ continue;
+ if(ISC0(ch)) {
+ s.state = Sstart;
+ vc0(s, ch);
+ break;
+ }
+ s.a0++;
+ if(s.a0 <= 3) {
+ if(ch >= 16r20 && ch <= 16r2f)
+ break;
+ }
+ if (s.a0 <= 4 && ch >= 16r30 && ch <= 16r7f) {
+ s.state = Sstart;
+ break;
+ }
+ s.state = Sstart;
+ s.put(tostr(DEL));
+ Siso6429 =>
+ # 1.2.7
+ # swallow characters from column 3,
+ # or column 2, then 1 from column 4 to 7
+ if(ISC0(ch)) {
+ s.state = Sstart;
+ vc0(s, ch);
+ break;
+ }
+ if(ch >= 16r20 && ch <= 16r3f)
+ break;
+ if(ch >= 16r40 && ch <= 16r7f) {
+ s.state = Sstart;
+ break;
+ }
+ s.state = Sstart;
+ s.put(tostr(DEL));
+ Stransparent =>
+ # 1.2.7
+ # ignore all codes until ESC, 25, 40 or ESC, 2F, 3F
+ # progress in s.a0 and s.a1
+ match := array [] of {
+ array [] of { ESC, 16r25, 16r40 },
+ array [] of { ESC, 16r2f, 16r3f },
+ };
+ if(ch == ESC) {
+ s.a0 = s.a1 = 1;
+ break;
+ }
+ if(ch == match[0][s.a0])
+ s.a0++;
+ else
+ s.a0 = 0;
+ if(ch == match[1][s.a1])
+ s.a1++;
+ else
+ s.a1 = 0;
+ if(s.a0 == 3 || s.a1 == 3)
+ s.state = Sstart;
+ Sdrcs =>
+ if(s.a0 > 0) { # fixed number of bytes to skip in a0
+ s.a0--;
+ if(s.a0 == 0) {
+ s.state = Sstart;
+ break;
+ }
+ } else if(ch == US) # US XX YY - end of DRCS
+ s.state = Sus0;
+ else if(ch == 16r20) # US 23 20 20 20 4[23] 49
+ s.a0 = 4;
+ Sconceal =>
+ # 1.2.4.4
+ # ESC 23 20 58 - Conceal fields
+ # ESC 23 20 5F - Reveal fields
+ # ESC 23 21 XX - Filter
+ # progress in s.a0
+ case s.a0 {
+ 0 =>
+ if(ch == 16r20 || ch == 16r21)
+ s.a0 = ch;
+ 16r20 =>
+ case ch {
+ 16r58 =>
+ disp->Reveal(0);
+ disp->Refresh();
+ 16r5f =>
+ disp->Reveal(1);
+ disp->Refresh();
+ }
+ s.state = Sstart;
+ 16r21 =>
+ s.state = Sstart;
+ }
+ }
+ }
+ if (i < len data)
+ return data[i:];
+ else
+ return nil;
+}
+
+# Screen state - Mixed mode
+mstate(s: ref Screen, data: array of byte): array of byte
+{
+ i: int;
+Stateloop:
+ for(i = 0; i < len data; i++) {
+ ch := int data[i];
+
+ if(debug['s']) {
+ cs:="";
+ if(s.cset==videotex) cs = "v"; else cs="s";
+ fprint(stderr, "mstate %d, %ux (%c) %.4ux %.4ux %s (%d,%d)\n", s.state, ch, ch, s.attr, s.fstate, cs, s.pos.y, s.pos.x);
+ }
+ case s.state {
+ Sstart =>
+ if(ISG0(ch) || ch == SP) {
+ n := 0;
+ str := "";
+ while(i < len data) {
+ ch = int data[i];
+ if(ISG0(ch) || ch == SP)
+ str[n++] = int data[i++];
+ else {
+ i--;
+ break;
+ }
+ }
+ if(n > 0) {
+ if(debug['s'])
+ fprint(stderr, "mstate puts(%s)\n", str);
+ s.put(str);
+ s.savech = str[n-1];
+ }
+ } else if(ISC0(ch))
+ mc0(s, ch);
+ else if(ch == DEL) {
+ if(s.cset == semigraphic)
+ ch = 16r5f;
+ s.put(tostr(ch));
+ s.savech = ch;
+ }
+ Sesc =>
+ if(ch == NUL)
+ continue;
+ mc1(s, ch);
+ Scsi0 =>
+ if(ch >= 16r30 && ch <= 16r39) {
+ s.a0 *= 10;
+ s.a0 += (ch - 16r30);
+ } else if(ch == '?') {
+ s.a0 = '?';
+ } else
+ mcsi(s, ch);
+ if(T.mode != Mixed) # CSI ? { changes to Videotex mode
+ break Stateloop;
+ Scsi1 =>
+ if(ch >= 16r30 && ch <= 16r39) {
+ s.a1 *= 10;
+ s.a1 += (ch - 16r30);
+ } else
+ mcsi(s, ch);
+ Sus0 =>
+ if(ch >= 16r40 && ch < 16r80)
+ s.a0 = (ch - 16r40);
+ else if(ch >= 16r30 && ch <= 16r32)
+ s.a0 = (ch - 16r30);
+ else
+ s.a0 = -1;
+ s.state = Sus1;
+ Sus1 =>
+ if(ch >= 16r40 && ch < 16r80)
+ s.a1 = (ch - 16r40);
+ else if(ch >= 16r30 && ch <= 16r39) {
+ s.a1 = (ch - 16r30);
+ s.a0 = s.a0*10 + s.a1; # shouldn't be used any more
+ s.a1 = 1;
+ } else
+ s.a1 = -1;
+ # US row, col : this is how you get to row zero
+ if(s.a0 >= 0 && s.a0 < s.rows && s.a1 > 0 && s.a1 <= s.cols) {
+ if(s.a0 == 0 && s.pos.y > 0) {
+ s.savepos = s.pos;
+ s.saveattr = s.attr;
+ }
+ s.pos = Point(s.a1, s.a0);
+ s.delimit = 0; # 1.2.5.3, don't reset serial attributes
+ s.attr = ATTR0;
+ s.cset = videotex;
+ }
+ s.state = Sstart;
+ Siso6429 =>
+ # 1.2.7
+ # swallow characters from column 3,
+ # or column 2, then 1 from column 4 to 7
+ if(ISC0(ch)) {
+ s.state = Sstart;
+ mc0(s, ch);
+ break;
+ }
+ if(ch >= 16r20 && ch <= 16r3f)
+ break;
+ if(ch >= 16r40 && ch <= 16r7f) {
+ s.state = Sstart;
+ break;
+ }
+ s.state = Sstart;
+ s.put(tostr(DEL));
+ }
+ }
+ if (i < len data)
+ return data[i:];
+ else
+ return nil;
+ return nil;
+}
+
+# process a byte from set C0 - Mixed mode
+mc0(s: ref Screen, ch: int)
+{
+ case ch {
+ ESC =>
+ s.state = Sesc;
+ SO =>
+# s.cset = french;
+ ;
+ SI =>
+# s.cset = american;
+ ;
+ BS =>
+ if(s.pos.x > 1)
+ s.pos.x -= 1;
+ HT =>
+ s.pos.x += 8;
+ if(s.pos.x > s.cols)
+ s.pos.x = s.cols;
+ LF or VT or FF =>
+ if(s.pos.y == s.rows - 1)
+ if(s.spec&Scroll)
+ scroll(1, 1);
+ else
+ s.pos.y = 1;
+ else if(s.pos.y == 0) { # restore attributes on leaving row zero
+ if(ch == LF) { # 4.5
+ s.pos = s.savepos;
+ s.attr = s.saveattr;
+ }
+ } else
+ s.pos.y += 1;
+ CR =>
+ s.pos.x = 1;
+ CAN or SUB => # displays the error symbol - filled in rectangle
+ disp->Put(dup(16r5f, 1), Point(s.pos.x,s.pos.y), s.cset, s.attr, 0);
+ NUL =>
+ # padding character - ignore, but may appear anywhere
+ ;
+ BEL =>
+ # ah ...
+ ;
+ XON => # screen copying
+ ;
+ XOFF => # screen copying
+ ;
+ US =>
+ # expect US row, col
+ s.state = Sus0;
+ }
+}
+
+# process a byte from the set c1 - introduced by the ESC character - Mixed mode
+mc1(s: ref Screen, ch: int)
+{
+ if(ISC0(ch)) {
+ s.state = Sstart;
+ mc0(s, ch);
+ return;
+ }
+ case ch {
+ 16r5b => # CSI sequence
+ s.a0 = s.a1 = 0;
+ if(s.pos.y > 0) # 1.2.5.2
+ s.state = Scsi0;
+ return;
+
+ 16r44 or # IND like LF
+ 16r45 => # NEL like CR LF
+ if(ch == 16r45)
+ s.pos.x = 1;
+ if(s.pos.y == s.rows - 1)
+ if(s.spec&Scroll)
+ scroll(1, 1);
+ else
+ s.pos.y = 1;
+ else if(s.pos.y == 0) { # restore attributes on leaving row zero
+ s.pos = s.savepos;
+ s.attr = s.saveattr;
+ } else
+ s.pos.y += 1;
+ 16r4d => # RI
+ if(s.pos.y == 1)
+ if(s.spec&Scroll)
+ scroll(1, -1);
+ else
+ s.pos.y = s.rows - 1;
+ else if(s.pos.y == 0)
+ break;
+ else
+ s.pos.y -= 1;
+ }
+ s.state = Sstart;
+}
+
+
+# process CSI functions - Mixed mode
+mcsi(s: ref Screen, ch: int)
+{
+ case s.state {
+ Scsi0 =>
+ case ch {
+ # move cursor up n rows, stop at top of screen
+ 'A' =>
+ if(s.a0 == 0)
+ s.a0 = 1;
+ s.pos.y -= s.a0;
+ if(s.pos.y < 1)
+ s.pos.y = 1;
+
+ # move cursor down n rows, stop at bottom of screen
+ 'B' =>
+ if(s.a0 == 0)
+ s.a0 = 1;
+ s.pos.y += s.a0;
+ if(s.pos.y >= s.rows)
+ s.pos.y = s.rows - 1;
+
+ # move cursor n columns right, stop at edge of screen
+ 'C' =>
+ if(s.a0 == 0)
+ s.a0 = 1;
+ s.pos.x += s.a0;
+ if(s.pos.x > s.cols)
+ s.pos.x = s.cols;
+
+ # move cursor n columns left, stop at edge of screen
+ 'D' =>
+ if(s.a0 == 0)
+ s.a0 = 1;
+ s.pos.x -= s.a0;
+ if(s.pos.x < 1)
+ s.pos.x = 1;
+
+ # second parameter
+ ';' =>
+ s.state = Scsi1;
+ return;
+
+ 'J' =>
+ case s.a0 {
+ # clears from the cursor to the end of the screen inclusive
+ 0 =>
+ rowclear(s.pos.y, s.pos.x, s.cols);
+ for(r:=s.pos.y+1; r<s.rows; r++)
+ rowclear(r, 1, s.cols);
+ # clears from the beginning of the screen to the cursor inclusive
+ 1 =>
+ for(r:=1; r<s.pos.y; r++)
+ rowclear(r, 1, s.cols);
+ rowclear(s.pos.y, 1, s.pos.x);
+ # clears the entire screen
+ 2 =>
+ clear(s);
+ }
+
+ 'K' =>
+ case s.a0 {
+ # clears from the cursor to the end of the row
+ 0 => rowclear(s.pos.y, s.pos.x, s.cols);
+
+ # clears from the start of the row to the cursor
+ 1 => rowclear(s.pos.y, 1, s.pos.x);
+
+ # clears the entire row in which the cursor is positioned
+ 2 => rowclear(s.pos.y, 1, s.cols);
+ }
+
+ # inserts n characters from cursor position
+ '@' =>
+ disp->Put(dup(' ', s.a0), Point(s.pos.x,s.pos.y), s.cset, s.attr, 1);
+
+ # starts cursor insert mode
+ 'h' =>
+ if(s.a0 == 4)
+ s.spec |= Insert;
+
+ 'l' => # ends cursor insert mode
+ if(s.a0 == 4)
+ s.spec &= ~Insert;
+
+ # inserts n rows from cursor row
+ 'L' =>
+ scroll(s.pos.y, -1*s.a0);
+ s.pos.x = 1;
+
+ # deletes n rows from cursor row
+ 'M' =>
+ scroll(s.pos.y, s.a0);
+ s.pos.x = 1;
+
+ # deletes n characters from cursor position
+ 'P' =>
+ rowclear(s.pos.y, s.pos.x, s.pos.x+s.a0-1);
+
+ # select Videotex mode
+ '{' =>
+ if(s.a0 == '?') {
+ T.mode = Videotex;
+ s.setmode(T.mode);
+ }
+
+ # display attributes
+ 'm' =>
+ case s.a0 {
+ 0 => s.attr &= ~(attrL|attrF|attrP|attrB);
+ 1 => s.attr |= attrB;
+ 4 => s.attr |= attrL;
+ 5 => s.attr |= attrF;
+ 7 => s.attr |= attrP;
+ 22 => s.attr &= ~attrB;
+ 24 => s.attr &= ~attrL;
+ 25 => s.attr &= ~attrF;
+ 27 => s.attr &= ~attrP;
+ }
+ # direct cursor addressing
+ 'H' =>
+ if(s.a0 == 0)
+ s.a0 = 1;
+ if(s.a1 == 0)
+ s.a1 = 1;
+ if(s.a0 > 0 && s.a0 < s.rows && s.a1 > 0 && s.a1 <= s.cols)
+ s.pos = Point(s.a1, s.a0);
+ }
+ s.state = Sstart;
+ Scsi1 =>
+ case ch {
+ # direct cursor addressing
+ 'H' =>
+ if(s.a0 == 0)
+ s.a0 = 1;
+ if(s.a1 == 0)
+ s.a1 = 1;
+ if(s.a0 > 0 && s.a0 < s.rows && s.a1 > 0 && s.a1 <= s.cols)
+ s.pos = Point(s.a1, s.a0);
+ }
+ s.state = Sstart;
+ }
+}
+
+
+# Screen state - ASCII mode
+astate(nil: ref Screen, nil: array of byte): array of byte
+{
+ return nil;
+}
+
+# Put a string in the current attributes to the current writing position
+Screen.put(s: self ref Screen, str: string)
+{
+ while((l := len str) > 0) {
+ n := s.cols - s.pos.x + 1; # characters that will fit on this row
+ if(s.attr & attrW) {
+ if(n > 1) # fit normal width character in last column
+ n /= 2;
+ }
+ if(n > l)
+ n = l;
+ if(s.delimit) { # set delimiter bit on 1st space (if any)
+ for(i:=0; i<n; i++)
+ if(str[i] == ' ')
+ break;
+ if(i > 0) {
+ disp->Put(str[0:i], s.pos, s.cset, s.attr, s.spec&Insert);
+ incpos(s, i);
+ }
+ if(i < n) {
+ if(debug['s']) {
+ cs:="";
+ if(s.cset==videotex) cs = "v"; else cs="s";
+ fprint(stderr, "D %ux %s\n", s.attr|attrD, cs);
+ }
+ disp->Put(tostr(str[i]), s.pos, s.cset, s.attr|attrD, s.spec&Insert);
+ incpos(s, 1);
+ s.delimit = 0;
+ # clear serial attributes once used
+ # hang onto background attribute - needed for semigraphics
+ case s.cset {
+ videotex =>
+ s.attr &= ~(attrL|attrC);
+ semigraphic =>
+ s.attr &= ~(attrC);
+ }
+ }
+ if(i < n-1) {
+ disp->Put(str[i+1:n], s.pos, s.cset, s.attr, s.spec&Insert);
+ incpos(s, n-(i+1));
+ }
+ } else {
+ disp->Put(str[0:n], s.pos, s.cset, s.attr, s.spec&Insert);
+ incpos(s, n);
+ }
+ if(n < len str)
+ str = str[n:];
+ else
+ str = nil;
+ }
+# if(T.state == Local || T.spec&Echo)
+# refresh();
+}
+
+# increment the current writing position by `n' cells.
+# caller must ensure that `n' characters can fit
+incpos(s: ref Screen, n: int)
+{
+ if(s.attr & attrW)
+ s.pos.x += 2*n;
+ else
+ s.pos.x += n;
+ if(s.pos.x > s.cols)
+ if(s.pos.y == 0) # no wraparound from row zero
+ s.pos.x = s.cols;
+ else {
+ s.pos.x = 1;
+ if(s.pos.y == s.rows - 1 && s.spec&Scroll) {
+ if(s.attr & attrH) {
+ scroll(1, 2);
+ } else {
+ scroll(1, 1);
+ rowclear(s.pos.y, 1, s.cols);
+ }
+ } else {
+ if(s.attr & attrH)
+ s.pos.y += 2;
+ else
+ s.pos.y += 1;
+ if(s.pos.y >= s.rows)
+ s.pos.y -= (s.rows-1);
+ }
+ }
+}
+
+# clear row `r' from `first' to `last' column inclusive
+rowclear(r, first, last: int)
+{
+ # 16r5f is the semi-graphic black rectangle
+ disp->Put(dup(16r5f, last-first+1), Point(first,r), semigraphic, fgBlack, 0);
+# disp->Put(dup(' ', last-first+1), Point(first,r), S.cset, fgBlack, 0);
+}
+
+clear(s: ref Screen)
+{
+ for(r:=1; r<s.rows; r++)
+ rowclear(r, 1, s.cols);
+}
+
+# called to suggest a display update
+refresh()
+{
+ disp->Refresh();
+}
+
+# scroll the screen
+scroll(topline, nlines: int)
+{
+ disp->Scroll(topline, nlines);
+ disp->Refresh();
+}
+
+# filter the specified ISO6429 and ISO2022 codes from the screen input
+# TODO: filter some ISO2022 sequences
+filter(s: ref Screen, data: array of byte): array of array of byte
+{
+ case T.mode {
+ Videotex =>
+ return vfilter(s, data);
+ Mixed =>
+ return mfilter(s, data);
+ Ascii =>
+ return afilter(s, data);
+ }
+ return nil;
+}
+
+# filter the specified ISO6429 and ISO2022 codes from the screen input
+vfilter(s: ref Screen, data: array of byte): array of array of byte
+{
+ ba := array [0] of array of byte;
+ changed := 0;
+
+ d0 := 0;
+ for(i:=0; i<len data; i++) {
+ ch := int data[i];
+ case s.fstate {
+ FSstart =>
+ if(ch == ESC) {
+ s.fstate = FSesc;
+ changed = 1;
+ if(i > d0)
+ ba = dappend(ba, data[d0:i]);
+ d0 = i+1;
+ }
+ FSesc =>
+ d0 = i+1;
+ changed = 1;
+ if(ch == '[') {
+ s.fstate = FS6429;
+ s.fsaved = array [0] of byte;
+ s.badp = 0;
+# } else if(ch == 16r20) {
+# s.fstate = FS2022;
+# s.fsaved = array [0] of byte;
+ s.badp = 0;
+ } else if(ch == ESC) {
+ ba = dappend(ba, array [] of { byte ESC });
+ s.fstate = FSesc;
+ } else {
+ # false alarm - don't filter
+ ba = dappend(ba, array [] of { byte ESC, byte ch });
+ s.fstate = FSstart;
+ }
+ FS6429 => # filter out invalid CSI sequences
+ d0 = i+1;
+ changed = 1;
+ if(ch >= 16r20 && ch <= 16r3f) {
+ if((ch < 16r30 || ch > 16r39) && ch != ';')
+ s.badp = 1;
+ a := array [len s.fsaved + 1] of byte;
+ a[0:] = s.fsaved[0:];
+ a[len a - 1] = byte ch;
+ s.fsaved = a;
+ } else {
+ valid := 1;
+ case ch {
+ 'A' => ;
+ 'B' => ;
+ 'C' => ;
+ 'D' => ;
+ 'H' => ;
+ 'J' => ;
+ 'K' => ;
+ 'P' => ;
+ '@' => ;
+ 'h' => ;
+ 'l' => ;
+ 'M' => ;
+ 'L' => ;
+ * =>
+ valid = 0;
+ }
+ if(s.badp)
+ valid = 0;
+ if(debug['f'])
+ fprint(stderr, "vfilter %d: %s%c\n", valid, string s.fsaved, ch);
+ if(valid) { # false alarm - don't filter
+ ba = dappend(ba, array [] of { byte ESC, byte '[' });
+ ba = dappend(ba, s.fsaved);
+ ba = dappend(ba, array [] of { byte ch } );
+ }
+ s.fstate = FSstart;
+ }
+ FS2022 => ;
+ }
+ }
+ if(changed) {
+ if(i > d0)
+ ba = dappend(ba, data[d0:i]);
+ return ba;
+ }
+ return array [] of { data };
+}
+
+# filter the specified ISO6429 and ISO2022 codes from the screen input - Videotex
+mfilter(s: ref Screen, data: array of byte): array of array of byte
+{
+ ba := array [0] of array of byte;
+ changed := 0;
+
+ d0 := 0;
+ for(i:=0; i<len data; i++) {
+ ch := int data[i];
+ case s.fstate {
+ FSstart =>
+ case ch {
+ ESC =>
+ s.fstate = FSesc;
+ changed = 1;
+ if(i > d0)
+ ba = dappend(ba, data[d0:i]);
+ d0 = i+1;
+ SEP =>
+ s.fstate = FSsep;
+ changed = 1;
+ if(i > d0)
+ ba = dappend(ba, data[d0:i]);
+ d0 = i+1;
+ }
+ FSesc =>
+ d0 = i+1;
+ changed = 1;
+ if(ch == '[') {
+ s.fstate = FS6429;
+ s.fsaved = array [0] of byte;
+ s.badp = 0;
+ } else if(ch == ESC) {
+ ba = dappend(ba, array [] of { byte ESC });
+ s.fstate = FSesc;
+ } else {
+ # false alarm - don't filter
+ ba = dappend(ba, array [] of { byte ESC, byte ch });
+ s.fstate = FSstart;
+ }
+ FSsep =>
+ d0 = i+1;
+ changed = 1;
+ if(ch == ESC) {
+ ba = dappend(ba, array [] of { byte SEP });
+ s.fstate = FSesc;
+ } else if(ch == SEP) {
+ ba = dappend(ba, array [] of { byte SEP });
+ s.fstate = FSsep;
+ } else {
+ if(ch >= 16r00 && ch <= 16r1f)
+ ba = dappend(ba, array [] of { byte SEP , byte ch });
+ # consume the character
+ s.fstate = FSstart;
+ }
+ FS6429 => # filter out invalid CSI sequences
+ d0 = i+1;
+ changed = 1;
+ if(ch >= 16r20 && ch <= 16r3f) {
+ if((ch < 16r30 || ch > 16r39) && ch != ';' && ch != '?')
+ s.badp = 1;
+ a := array [len s.fsaved + 1] of byte;
+ a[0:] = s.fsaved[0:];
+ a[len a - 1] = byte ch;
+ s.fsaved = a;
+ } else {
+ valid := 1;
+ case ch {
+ 'm' => ;
+ 'A' => ;
+ 'B' => ;
+ 'C' => ;
+ 'D' => ;
+ 'H' => ;
+ 'J' => ;
+ 'K' => ;
+ '@' => ;
+ 'h' => ;
+ 'l' => ;
+ 'L' => ;
+ 'M' => ;
+ 'P' => ;
+ '{' => # allow CSI ? {
+ n := len s.fsaved;
+ if(n == 0 || s.fsaved[n-1] != byte '?')
+ s.badp = 1;
+ * =>
+ valid = 0;
+ }
+ if(s.badp) # only decimal params
+ valid = 0;
+ if(debug['f'])
+ fprint(stderr, "mfilter %d: %s%c\n", valid, string s.fsaved, ch);
+ if(valid) { # false alarm - don't filter
+ ba = dappend(ba, array [] of { byte ESC, byte '[' });
+ ba = dappend(ba, s.fsaved);
+ ba = dappend(ba, array [] of { byte ch } );
+ }
+ s.fstate = FSstart;
+ }
+ FS2022 => ;
+ }
+ }
+ if(changed) {
+ if(i > d0)
+ ba = dappend(ba, data[d0:i]);
+ return ba;
+ }
+ return array [] of { data };
+}
+
+# filter the specified ISO6429 and ISO2022 codes from the screen input - Videotex
+afilter(nil: ref Screen, data: array of byte): array of array of byte
+{
+ return array [] of { data };
+}
+
+# append to an array of array of byte
+dappend(ba: array of array of byte, b: array of byte): array of array of byte
+{
+ l := len ba;
+ na := array [l+1] of array of byte;
+ na[0:] = ba[0:];
+ na[l] = b;
+ return na;
+}
+
+# Put a diagnostic string to row 0
+Screen.msg(s: self ref Screen, str: string)
+{
+ blank := string array [s.cols -4] of {* => byte ' '};
+ n := len str;
+ if(n > s.cols - 4)
+ n = s.cols - 4;
+ disp->Put(blank, Point(1, 0), videotex, 0, 0);
+ if(str != nil)
+ disp->Put(str[0:n], Point(1, 0), videotex, fgWhite|attrB, 0);
+ disp->Refresh();
+} \ No newline at end of file