kvm : keyboard/video/mouse support for virtual consoles
Table of Contents
- 1. Goals
- 2. Class Hierarchy
- 3.
ITerm
: the abstract screen interface - 4. Data Types
- 5.
TBaseTerm
- 6.
TGridTerm
- 7.
TAnsiTerm
- 8.
TSubTerm
: a window inside a terminal - 9.
THookTerm
: wraps another term with callbacks for all routines - 10. TODO
TVideoTerm
: uses free pascal'svideo
unit - 11. char mnemonics for ansi colors.
- 12. Text driver, for redirecting
write
andwriteln
- 13. Unit Life cycle
- 14. The Terminal Stack
- 15. APPENDIX Top-level convenience routines
- 16. OUTPUT
kvm.pas
1 Goals
This module implements an enhanced terminal device with support for unicode and a 256 color palette.
2 Class Hierarchy
- building blocks
-
TPoint
- an (x,y) record pair used for tracking the cursor
-
TTextAttr
- a (fg, bg : byte) pair, representing a 256 color palette
-
TTermCell
- a (TTextAttr, WideChar) record pair
-
TTermGrid
- a GGrid2D subclass containing TTermCell records
-
- the interface shared by all units
-
TBaseTerm
- abstract superclass
-
TGridTerm
- generic in-memory terminal
-
TSubTerm
- represents the sub-window of another term
-
-
TAnsiTerm
- outputs ansi escape codes, for external terminal emulators
-
TVideoTerm
- uses free pascal's
video
unit
-
-
THookTerm
- allows callbacks on emit, resize, etc
-
3 ITerm
: the abstract screen interface
A screen tracks a cursor position, bounds, color.
type ITerm = interface ['{8309B694-C1C4-11E3-8461-00188B5936E2}'] { queries } function Width : word; function Height: word; function XMax : word; function YMax : word; function WhereX: word; function WhereY: word; function GetTextAttr : word; function asTerm : ITerm; { commands } procedure ClrScr; procedure ClrEol; procedure NewLine; procedure ScrollUp; procedure Fg( color : byte ); procedure Bg( color : byte ); procedure Emit( s : TStr ); procedure GotoXY( x, y : word ); procedure InsLine; procedure DelLine; procedure SetTextAttr( value : word ); procedure ShowCursor; procedure HideCursor; procedure Resize( NewW, NewH : word ); { properties } property TextAttr : word read GetTextAttr write SetTextAttr; procedure dump; end;
4 Data Types
4.1 TTextAttr
For our text attributes, we're going to use 256 colors. This strikes a good balance between storage space and aesthetics. There's really not much need for more colors than this when we're talking about a fixed-width text display.
type TTextAttr = record bg : byte; fg : byte; end;
4.2 TTermCell
A terminal cell combines a text attribute with a 16-bit WideChar.
type TTermCell = record ch : widechar; attr : TTextAttr; end;
4.3 TTermGrid
A terminal's display buffer is essentially a grid of such cells. I'm using my generic GGrid2d
class here to avoid duplicating code.
type TTermGrid = class (specialize GGrid2d<TTermCell>) private function GetAttr( const x, y : word ) : TTextAttr; function GetChar( const x, y : word ) : WideChar; procedure SetAttr( const x, y : word; const value : TTextAttr ); procedure SetChar( const x, y : word; const value : WideChar ); public property attrs[ x, y : word ] : TTextAttr read GetAttr write SetAttr; property chars[ x, y : word ] : WideChar read GetChar write SetChar; end;
function TTermGrid.GetAttr( const x, y : word ) : TTextAttr; begin result.fg := self[ x, y ].attr.fg; result.bg := self[ x, y ].attr.bg; end; procedure TTermGrid.SetAttr( const x, y : word; const value : TTextAttr ); begin with _data[ xyToI( x, y ) ].attr do begin bg := value.bg; fg := value.fg; end end; function TTermGrid.GetChar( const x, y : word ) : WideChar; begin result := self[ x, y ].ch; end; procedure TTermGrid.SetChar( const x, y : word; const value : WideChar ); begin _data[ xyToI( x, y ) ].ch := value; end;
4.4 TPoint
type TPoint = record x, y : cardinal; end;
4.5 Event Types
type TOnEmit = procedure( s : TStr ) of object; TOnGotoXY = procedure( x, y : word ) of object; TOnSetTextAttr = procedure( a : TTextAttr ) of object; TOnSetColor = procedure( color : byte ) of object;
5 TBaseTerm
5.1 interface
type TBaseTerm = class (TInterfacedObject, ITerm) protected _attr : TTextAttr; _curs : TPoint; _w, _h : word; public constructor Create( NewW, NewH : word ); virtual; function asTerm : ITerm; // weak reference function Width : word; virtual; function Height : word; virtual; function xMax : word; virtual; function yMax : word; virtual; function WhereX : word; virtual; function WhereY : word; virtual; procedure GotoXY( x, y : word ); virtual; procedure ClrScr; virtual; procedure ClrEol; virtual; procedure NewLine; virtual; procedure ScrollUp; virtual; procedure Fg( color : byte ); procedure Bg( color : byte ); function GetTextAttr : word; procedure SetTextAttr( value : word ); virtual; procedure EmitChar( ch : TChr ); virtual; procedure Emit( s : TStr ); procedure InsLine; virtual; procedure DelLine; virtual; procedure ShowCursor; virtual; procedure HideCursor; virtual; procedure Resize( NewW, NewH : word ); virtual; procedure dump; virtual; protected _OnEmit : TOnEmit; _OnGotoXY : TOnGotoXY; _OnSetTextAttr : TOnSetTextAttr; _OnSetFg, _OnSetBg : TOnSetColor; published property w : word read Width; property h : word read Height; property OnEmit : TOnEmit read _OnEmit write _OnEmit; property OnGotoXY : TOnGotoXY read _OnGotoXY write _OnGotoXY; property OnSetTextAttr : TOnSetTextAttr read _OnSetTextAttr write _OnSetTextAttr; property OnSetFg : TOnSetColor read _OnSetFg write _OnSetFg; property OnSetBg : TOnSetColor read _OnSetBg write _OnSetBg; property TextAttr : word read GetTextAttr write SetTextAttr; end;
5.2 implementation
5.2.1 constructor
constructor TBaseTerm.Create( NewW, NewH : word ); begin _w := NewW; _h := NewH; _curs.x := 0; _curs.y := 0; _attr.fg := $07; _attr.bg := $00; // light gray on black end;
5.2.2 memory management helpers
This is so you can treat the instance as an ITerm without accidentally freeing the thing.
function TBaseTerm.asTerm : ITerm; begin result := self; result._AddRef end;
5.2.3 display geometry
function TBaseTerm.Width : word; begin result := _w end; function TBaseTerm.Height: word; begin result := _h end; function TBaseTerm.XMax : word; begin result := max(0, _w-1) end; function TBaseTerm.YMax : word; begin result := max(0, _h-1) end; procedure TBaseTerm.Resize( NewW, NewH : word ); begin _w := NewW; _h := NewH; end;
5.2.4 cursor position
function TBaseTerm.WhereX : word; begin result := _curs.x end; function TBaseTerm.WhereY : word; begin result := _curs.y end; procedure TBaseTerm.GotoXY( x, y : word ); begin _curs.x := x; _curs.y := y; if assigned(_OnGotoXY) then _OnGotoXY( x, y ); end;
procedure TBaseTerm.ClrScr; var y : word; i : integer; begin for y := 0 to yMax do begin gotoxy(0, y); for i := 1 to self.width do Emit(' '); end; gotoxy(0, 0); end; procedure TBaseTerm.ClrEol; var oldX, i : word; begin oldX := _curs.x; if oldX < xMax then for i := oldX to xMax do Emit(' ') else ok; { ensure curs'.x = curs.x ; curs'.y = curs.y } self.gotoXY( oldX, _curs.y ); end; procedure TBaseTerm.NewLine; var yOld : word; begin yOld := wherey; if yOld = yMax then begin scrollUp; gotoXY( 0, yMax ); chk.equal( _curs.y, yMax, 'should be at bottom' ) end else begin gotoXY( 0, yOld+1 ) end; chk.equal( _curs.x, 0 ); end; procedure TBaseTerm.ScrollUp; var x, y : cardinal; begin x := _curs.x; y := _curs.y; gotoXY(0,0); delLine; gotoXY(x, y); end;
5.2.5 cursor display
It may not always be possible to change the shape of the cursor, so by default, these do nothing.
procedure TBaseTerm.ShowCursor; begin ok end; procedure TBaseTerm.HideCursor; begin ok end;
5.2.6 TODO ins/delete lines
These may have to be pushed down into gridterm, or else everything needs to have a grid.
procedure TBaseTerm.InsLine; begin ok end; procedure TBaseTerm.DelLine; begin ok end;
5.2.7 text atttributes
These control the foreground and background colors of the characters generated with (emit).
function TBaseTerm.GetTextAttr : word; begin result := _attr.bg shl 8 + _attr.fg end; procedure TBaseTerm.SetTextAttr( value : word ); var newAttr : TTextAttr; begin newAttr := WordToAttr(value); if newAttr.fg <> _attr.fg then Fg(newAttr.fg); if newAttr.bg <> _attr.bg then Bg(newAttr.bg); end; procedure TBaseTerm.Fg( color : byte ); begin _attr.fg := color; if assigned( _OnSetFg ) then _OnSetFg( color ); end; procedure TBaseTerm.Bg( color : byte ); begin _attr.bg := color; if assigned( _OnSetBg ) then _OnSetBg( color ); end;
5.2.8 text emitter
procedure TBaseTerm.EmitChar( ch : TChr ); begin end; procedure TBaseTerm.Emit( s : TStr ); var ch : widechar = #0; begin for ch in s do begin if ch = ^I then Emit(' ') else if ch = ^J then NewLine else if ord(ch) < 32 then ok else begin if _curs.x = _w then NewLine; EmitChar(ch); _curs.x += 1; if assigned(_OnEmit) then _OnEmit(ch); end end end;
5.2.9 debug routines
procedure tbaseterm.dump; begin if self = nil then trace('[NIL]') else begin trace(['TERM[', self.classname, ']']); indent; begin trace(['w:', _w, ' h:', _h]); end; dedent; end; end;
6 TGridTerm
6.1 interface
type TGridTerm = class (TBaseTerm, ITerm) private _grid : TTermGrid; public constructor Create( NewW, NewH : word ); override; destructor Destroy; override; function GetCell( const x, y : word ) : TTermCell; procedure PutCell( const x, y : word; const cell : TTermCell ); procedure ClrScr; override; procedure EmitChar( wc : widechar ); override; property cells[ x, y : word ] : TTermCell read GetCell write PutCell; default; procedure DelLine; override; procedure Resize( newW, newH : word ); override; end;
6.2 Implementation
constructor TGridTerm.Create( NewW, NewH : word ); begin inherited create( NewW, NewH ); _grid := TTermGrid.Create( NewW, NewH ); clrscr; end; destructor TGridTerm.Destroy; begin; _grid.Free; inherited destroy; end; procedure TGridTerm.Resize( newW, newH : word ); begin inherited resize( newW, newH ); _grid.Resize( newW, newH ); clrscr; end; procedure TGridTerm.ClrScr; var cell : TTermCell; begin inherited clrscr; cell.ch := ' '; cell.attr := _attr; _grid.fill(cell); gotoxy(0,0); end; procedure TGridTerm.EmitChar( wc : widechar ); var cell : TTermCell; begin if (_curs.x < _w) and (_curs.y < _h) then begin cell.attr := _attr; cell.ch := wc; _grid[_curs.x, _curs.y] := cell; end end; function TGridTerm.GetCell( const x, y : word ) : TTermCell; begin result := _grid[x,y] end; procedure TGridTerm.PutCell( const x, y : word; const cell : TTermCell ); begin _grid[x,y] := cell; end; procedure TGridTerm.DelLine; var curx, cury, x, y : integer; a : TTextAttr; c : TTermCell; begin curx := wherex; cury := wherey; a := _attr; for y := cury to ymax-1 do begin gotoxy(0, y); for x := 0 to xmax do begin c := _grid[x, y+1]; SetTextAttr(AttrToWord(c.attr)); emit(c.ch); end; end; gotoxy(0, ymax); clreol; gotoxy(curx, cury); settextattr(attrtoword(a)); end;
7 TAnsiTerm
type TAnsiTerm = class (TBaseTerm) public constructor Create( NewW, NewH : word; CurX, CurY : byte ); reintroduce; procedure DoGotoXY( x, y : word ); procedure DoEmit( s : TStr ); // the rest of these should be callbacks too: procedure ResetColor; procedure DoSetFg( color : byte ); procedure DoSetBg( color : byte ); procedure ClrScr; override; procedure ShowCursor; override; procedure HideCursor; override; procedure ScrollUp; override; end;
constructor TAnsiTerm.Create(NewW, NewH : word; CurX, CurY : byte); begin inherited Create( NewW, NewH ); // we set xy directly because the cursor is already // somewhere when the program starts. _curs.x := curx; _curs.y := cury; _OnGotoXY := @DoGotoXY; _OnEmit := @DoEmit; _OnSetFg := @DoSetFg; _OnSetBg := @DoSetBg; resetcolor; end; procedure TAnsiTerm.DoSetFg( color : byte ); begin { xterm 256-color extensions } write( stdout, #27, '[38;5;', color , 'm' ) end; procedure TAnsiTerm.DoSetBg( color : byte ); begin { xterm 256-color extensions } write( stdout, #27, '[48;5;', color , 'm' ) end; procedure TAnsiTerm.ClrScr; begin write( stdout, #27, '[H', #27, '[J' ); _curs.x := 0; _curs.y := 0; end; procedure TAnsiTerm.DoGotoXY( x, y : word ); begin write(stdout, #27, '[', y + 1, ';', x + 1, 'H' ) end; procedure TAnsiTerm.DoEmit( s : TStr ); begin write(stdout, utf8encode(s)); end; procedure TAnsiTerm.ScrollUp; var x, y : word; begin y := _curs.y; if y = ymax then writeln(stdout) else begin x := _curs.x; gotoxy(0,ymax); writeln(stdout); gotoxy(x,y); end; end; procedure TAnsiTerm.ResetColor; begin _attr.bg := 0; _attr.fg := 7; write(stdout, #27, '[0m' ) end; procedure TAnsiTerm.ShowCursor; // !! xterm / dec terminals begin write(stdout, #27, '[?25h'); end; procedure TAnsiTerm.HideCursor; // !! xterm / dec terminals begin write(stdout, #27, '[?25l'); end;
8 TSubTerm
: a window inside a terminal
8.1 interface
type TSubTerm = class (TGridTerm) protected _term : ITerm; _x, _y : word; public constructor Create(term : ITerm; x, y, NewW, NewH : word ); reintroduce; procedure DoGotoXY( x, y : word ); procedure DoEmit( s : TStr ); procedure DoSetFg( color : byte ); procedure DoSetBg( color : byte ); procedure HideCursor; override; procedure ShowCursor; override; end;
8.2 implementation
We start with a handful of member variables to track the bounds:
constructor TSubTerm.Create(term : ITerm; x, y, NewW, NewH : word ); begin inherited Create(NewW, NewH); _term := term; _x := x; _y := y; _OnEmit := @DoEmit; _OnGotoXy := @DoGotoXY; _OnSetFg := @DoSetFg; _OnSetBg := @DoSetBg; end; procedure TSubTerm.DoGotoXY( x, y : word ); begin _term.GotoXY( x + _x, y + _y ); end; procedure TSubTerm.DoEmit( s : TStr ); begin _term.Emit( s ); end; procedure TSubTerm.DoSetFg( color : byte ); begin _term.Fg(color) end; procedure TSubTerm.DoSetBg( color : byte ); begin _term.Bg(color) end; procedure TSubTerm.HideCursor; begin _term.HideCursor; end; procedure TSubTerm.ShowCursor; begin _term.ShowCursor; end;
9 THookTerm
: wraps another term with callbacks for all routines
9.1 interface
type TTermMessage = (hkClrScr, hkClrEol, hkNewLine, hkScrollUp, hkFg, hkBg, hkEmit, hkGoXY, hkInsLine, hkDelLine, hkAttr, hkShowCursor, hkHideCursor, hkResize ); TTermCallback = procedure( msg : TTermMessage; args : array of variant ) of object; type THookTerm = class (TInterfacedObject, ITerm) protected _self : ITerm; _Subject : ITerm; // the term to which we will relay events _OnChange : TTermCallback; published constructor Create; function asTerm : ITerm; procedure DoNothing( msg : TTermMessage; args : array of variant ); property OnChange : TTermCallback read _OnChange write _OnChange; function Width : word; function Height: word; function XMax : word; function YMax : word; function WhereX: word; function WhereY: word; procedure ClrScr; procedure ClrEol; procedure NewLine; procedure ScrollUp; procedure Fg( color : byte ); procedure Bg( color : byte ); procedure Emit( s : TStr ); procedure GotoXY( x, y : word ); procedure InsLine; procedure DelLine; procedure SetTextAttr( value : word ); function GetTextAttr : word; procedure ShowCursor; procedure HideCursor; procedure Resize( NewW, NewH : word ); property TextAttr : word read GetTextAttr write SetTextAttr; public { debug stuff } function GetSubject : ITerm; property subject : ITerm read GetSubject write _subject; procedure dump; end;
9.2 implementation
9.2.1 constructor and empty callback
constructor THookTerm.Create; begin inherited; _self := ITerm(self); _OnChange := @self.DoNothing; _Subject := kvm.asTerm; end; procedure THookTerm.Dump; var indent : TStr; begin if self = nil then trace('[NIL]') else begin trace('THookTerm'); trace(' _subject: '); _subject.dump; end end; // HookTerms don't descend from baseterm ( for now, anyway ) function THookTerm.asTerm : ITerm; begin result := _self; result._addref; end; function THookTerm.GetSubject : ITerm; begin result := _subject.asTerm end; procedure THookTerm.DoNothing( msg : TTermMessage; args : array of variant ); begin // empty method as default callback end;
9.2.2 passthrough queries (no callback)
function THookTerm.Width : word; begin result := _subject.width end; function THookTerm.Height: word; begin result := _subject.height end; function THookTerm.XMax : word; begin result := _subject.xmax end; function THookTerm.YMax : word; begin result := _subject.ymax end; function THookTerm.WhereX: word; begin result := _subject.wherex end; function THookTerm.WhereY: word; begin result := _subject.wherex end; function THookTerm.GetTextAttr : word; begin result := _subject.textattr end;
9.2.3 callbacks
procedure THookTerm.ClrScr; begin _subject.ClrScr; OnChange( hkClrScr, [ ]); end; procedure THookTerm.ClrEol; begin _subject.ClrScr; OnChange( hkClrEol, [ ]); end; procedure THookTerm.NewLine; begin _subject.ClrScr; OnChange( hkNewLine, [ ]); end; procedure THookTerm.ScrollUp; begin _subject.ScrollUp; OnChange( hkScrollUp, [ ]); end; procedure THookTerm.Fg( color : byte ); begin _subject.Fg(color); OnChange( hkFg, [ color ]); end; procedure THookTerm.Bg( color : byte ); begin _subject.Bg(color); OnChange( hkBg, [ color ]); end; procedure THookTerm.Emit( s : TStr ); begin _subject.Emit( s ); OnChange( hkEmit, [ s ]); end; procedure THookTerm.GotoXY( x, y : word ); begin _subject.GotoXY( x, y ); OnChange( hkGoXY, [ x, y ]); end; procedure THookTerm.InsLine; begin _subject.InsLine; OnChange( hkInsLine, [ ]); end; procedure THookTerm.DelLine; begin _subject.DelLine; OnChange( hkDelLine, [ ]); end; procedure THookTerm.SetTextAttr( value : word ); begin _subject.SetTexTAttr(value); OnChange( hkAttr, [ value ]); end; procedure THookTerm.ShowCursor; begin _subject.ShowCursor; OnChange( hkShowCursor, [ ]); end; procedure THookTerm.HideCursor; begin _subject.HideCursor; OnChange( hkHideCursor, [ ]); end; procedure THookTerm.Resize( NewW, NewH : word ); begin _subject.Resize( newW, newH ); OnChange( hkResize, [ NewW, NewH ]); end;
10 TODO TVideoTerm
: uses free pascal's video
unit
type TVideoTerm = class (TANSITerm) end;
11 char mnemonics for ansi colors.
procedure bg( ch : char ); var i : byte; begin i := pos( ch, 'krgybmcwKRGYBMCW' ); if i > 0 then bg( i - 1 ); end; procedure fg( ch : char ); var i : byte; begin i := pos( ch, 'krgybmcwKRGYBMCW' ); if i > 0 then fg( i - 1 ); end;
These allow you to use one-letter characters for the first 16 colors, instead of refering to them by number. They are arranged according to the ANSI standard.
k | 0 | black | K | 8 | dark gray | |
r | 1 | red | R | 9 | light red | |
g | 2 | green | G | 10 | light green | |
y | 3 | dark yellow/brown | Y | 11 | yellow | |
b | 4 | blue | B | 12 | light blue | |
m | 5 | magenta | M | 13 | light magenta | |
c | 6 | cyan | C | 14 | light cyan | |
w | 7 | light gray | W | 15 | white |
See also the cw unit (color + write).
12 Text driver, for redirecting write
and writeln
function KvmWrite(var f: textrec): integer; var s: ansistring; begin if f.bufpos > 0 then begin setlength(s, f.bufpos); move(f.buffer, s[1], f.bufpos); kvm.emit(TStr(s)); // convert to widestring end; f.bufpos := 0; Result := 0; end; function KvmClose(var txt: TTextRec): integer; begin Result := 0; end; function KvmOpen(var txt: TTextRec): integer; begin case txt.mode of fmOutput: begin txt.inOutFunc := @KvmWrite; txt.flushFunc := @KvmWrite; end else // todo : error; end; Result := 0; end; // http://docwiki.embarcadero.com/RADStudio/XE5/en/Standard_Routines_and_Input-Output procedure AssignKvm(var txt: Text); begin Assign(txt, ''); with TTextRec(txt) do begin mode := fmClosed; openFunc := @KvmOpen; closeFunc := @KvmClose; end; end;
13 Unit Life cycle
There are basically three steps to deal with:
initialization <<redirect-io>> <<create-term-obj>> <<create-term-stack>> finalization { the terms are freed automatically by reference count } PopTerms; work := nil;
First, we want to redirect the Output
file, so that calls to Write
and WriteLn
are sent through KvmWrite
. Since we may still need to access the standard output (especially in the case of ANSITerm
), we'll also create a new file descriptor.
Assign(stdout,''); Rewrite(stdout);
AssignKVM(output); Rewrite(output);
The second step is simply to create a new ITerm
instance and assign the work
variable.
{$IFDEF UNIX} function GetLiveAnsiTerm : TAnsiTerm; var termw, termh : byte; curx, cury : byte; begin terminal.getwh(termw, termh); curx := terminal.startX; cury := terminal.startY; result := TAnsiTerm.Create( termw, termh, curx, cury ); end; {$ENDIF}
{$IFDEF UNIX} work :={$IFDEF VIDEOKVM}TVideoTerm.Create {$ELSE}GetLiveANSITerm{$ENDIF}; {$ELSE} work := TGridTerm.Create(64, 16); {$ENDIF}
The third step is just to initialize an empty stack:
termstack := TTermStack.Create(32);
14 The Terminal Stack
14.1 interface
We maintain a stack of ITerm instances so that kvm.work
can be assigned and later restored.
{ context stack } procedure PushTerm( term : ITerm ); function SubTerm( x, y, w, h : word ) : ITerm; procedure PopTerm; procedure PopTerms;
PushTerm
pushes the current terminal onto a stack and sets kvm.work
to the given terminal.
SubTerm
instantiates a new TSubTerm
that controls a subregion of kvm.work
and then calls PushTerm
on it. This is handy for drawing nested components. See TView.Update
in utv.pas for an example.
PopTerm
discards the topmost item on the stack and restors kvm.work
.
PopTerm
calls PopTerm
until the stack is empty. This is done automatically during finalization, and is only exposed in the interface so that cx.pas can direct the stacktrace to the main terminal in the event of an uncaught exception.
14.2 implementation
type TTermStack = specialize GStack<ITerm>; var termStack : TTermStack; var work : ITerm; procedure PushTerm( term : ITerm ); begin termStack.push( work ); work := term; end; function SubTerm( x, y, w, h : word ) : ITerm; begin result := TSubTerm.Create( work, x, y , w , h ); pushTerm( result ); end; procedure PopTerm; begin work := termStack.Pop; end; procedure PopTerms; begin while termStack.count > 0 do work := termStack.Pop; end;
15 APPENDIX Top-level convenience routines
In general, you're only going to work with one screen at a time, so it's convenient to have a set of routines that deal with whatever the current screen happens to be at the moment.
15.1 interface
{ conversion helpers } function WordToAttr(w : word): TTextAttr; function AttrToWord(a : TTextAttr) : word; { convenience routines for global instance } function asTerm : ITerm; // always a weak reference function Width : word; function Height: word; function XMax : word; function YMax : word; function WhereX : word; function WhereY : word; procedure ClrScr; procedure ClrEol; procedure Newline; procedure Fg( color : byte ); procedure Bg( color : byte ); procedure Emit( s : TStr ); procedure GotoXY( x, y : word ); procedure InsLine; procedure DelLine; procedure SetTextAttr( value : word ); function GetTextAttr : word; property TextAttr : word read GetTextAttr write SetTextAttr; procedure ShowCursor; procedure HideCursor;
15.2 implementation
15.3 conversions
function WordToAttr(w : word): TTextAttr; inline; begin result.bg := hi(w); result.fg := lo(w); end; function AttrToWord(a : TTextAttr) : word; inline; begin result := (word(a.bg) shl 8) + word(a.fg); end;
15.4 convenience routines
The others just delegate to the work
term.
function asTerm : ITerm; begin result := work.asTerm end; function Width : word; begin result := work.Width end; function Height : word; begin result := work.Height end; function XMax : word; begin result := work.xMax end; function YMax : word; begin result := work.yMax end; function WhereX : word; begin result := work.WhereX end; function WhereY : word; begin result := work.WhereY end; procedure Fg( color : byte ); begin work.Fg( color ) end; procedure Bg( color : byte ); begin work.Bg( color ) end; procedure Emit( s : TStr ); begin work.Emit( s ) end; procedure GotoXY( x, y : word ); begin work.GotoXY( x, y ) end; procedure ClrScr; begin work.ClrScr end; procedure ClrEol; begin work.ClrEol end; procedure NewLine; begin work.NewLine end; procedure InsLine; begin work.InsLine end; procedure DelLine; begin work.DelLine end; procedure ShowCursor; begin work.ShowCursor end; procedure HideCursor; begin work.HideCursor end; procedure SetTextAttr( value : word ); begin work.TextAttr := value end; function GetTextAttr : word; begin result := work.TextAttr end;
16 OUTPUT kvm.pas
{!! WARNING!! GENERATED FILE. edit ../org/kvm.pas.org instead!! !!} {$mode objfpc}{$i xpc.inc}{$m+} unit kvm; interface uses xpc, ugrid2d, sysutils, strutils, chk, stacks, {$ifdef VIDEOKVM}video {$else}terminal {$endif} ; var stdout : text; <<ITerm>> <<TTextAttr>> <<toplevel>> <<TTermCell>> <<TTermGrid>> <<TPoint>> <<event-types>> <<TBaseTerm>> <<TGridTerm>> <<TAnsiTerm>> <<TVideoTerm>> <<TSubTerm>> <<THookTerm>> procedure fg( ch : char ); procedure bg( ch : char ); <<extras>> <<@kvm:interface>> implementation <<@kvm:impl>> <<@hook:impl>> <<textdriver>> <<lifecycle>> end.