ref: babf901b4a508c3ec5d1f89655f10377bbdf9637
dir: /appl/examples/minitel/screen.b/
#
# 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();
}