diff options
Diffstat (limited to 'appl/wm/minitel/screen.b')
| -rw-r--r-- | appl/wm/minitel/screen.b | 1610 |
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 |
