ref: fb7dd4b3a868cb8987049c95bb32e6425a73c8b9
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(); }