فهرست منبع

+ added package ptckvm - a console driver for the keyboard, video and mouse units, implemented on top of the PTCPas library

Nikolay Nikolov 10 ماه پیش
والد
کامیت
6f17e91229
5فایلهای تغییر یافته به همراه2144 افزوده شده و 0 حذف شده
  1. 1 0
      packages/fpmake_add.inc
  2. 6 0
      packages/fpmake_proc.inc
  3. 2 0
      packages/ptckvm/Makefile
  4. 38 0
      packages/ptckvm/fpmake.pp
  5. 2097 0
      packages/ptckvm/src/ptckvm.pas

+ 1 - 0
packages/fpmake_add.inc

@@ -160,3 +160,4 @@
   add_wasm_utils(ADirectory+IncludeTrailingPathDelimiter('wasm-utils'));
   add_wasm_oi(ADirectory+IncludeTrailingPathDelimiter('wasm-oi'));
   add_fcl_jsonschema(ADirectory+IncludeTrailingPathDelimiter('fcl-jsonschema'));
+  add_ptckvm(ADirectory+IncludeTrailingPathDelimiter('ptckvm'));

+ 6 - 0
packages/fpmake_proc.inc

@@ -906,3 +906,9 @@ begin
 {$include fcl-jsonschema/fpmake.pp}
 end;
 
+procedure add_ptckvm(const ADirectory: string);
+begin
+  with Installer do
+{$include ptckvm/fpmake.pp}
+end;
+

+ 2 - 0
packages/ptckvm/Makefile

@@ -0,0 +1,2 @@
+PACKAGE_NAME=ptckvm
+include ../build/Makefile.pkg

+ 38 - 0
packages/ptckvm/fpmake.pp

@@ -0,0 +1,38 @@
+{$ifndef ALLPACKAGES}
+{$mode objfpc}{$H+}
+program fpmake;
+
+uses {$ifdef unix}cthreads,{$endif} fpmkunit;
+
+Var
+  P : TPackage;
+  T : TTarget;
+begin
+  With Installer do
+    begin
+{$endif ALLPACKAGES}
+
+    P:=AddPackage('ptckvm');
+{$ifdef ALLPACKAGES}
+    P.Directory:=ADirectory;
+{$endif ALLPACKAGES}
+    P.Version:='3.3.1';
+
+    P.Author := 'Nikolay Nikolov';
+    P.License := 'LGPL with modification, ';
+    P.HomepageURL := 'https://sourceforge.net/projects/fpterm/';
+    P.Description := 'Driver for the video, keyboard and mouse units, built on top of the PTCPas graphics library.';
+    p.OSes:=[linux,win32,win64,go32v2,macosx,openbsd,freebsd];
+
+    P.SourcePath.Add('src');
+
+    P.Dependencies.Add('ptc');
+    p.Dependencies.Add('rtl-console');
+
+    T:=P.Targets.AddUnit('ptckvm.pas');
+
+{$ifndef ALLPACKAGES}
+    Run;
+    end;
+end.
+{$endif ALLPACKAGES}

+ 2097 - 0
packages/ptckvm/src/ptckvm.pas

@@ -0,0 +1,2097 @@
+{
+  An implementation of a console driver, integrated with the video, keyboard
+  and mouse units, built on top of the PTCPas graphics library.
+
+  Copyright (C) 2006, 2021, 2022 Nikolay Nikolov <[email protected]>
+
+  This library is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Library General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or (at your
+  option) any later version with the following modification:
+
+  As a special exception, the copyright holders of this library give you
+  permission to link this library with independent modules to produce an
+  executable, regardless of the license terms of these independent modules,and
+  to copy and distribute the resulting executable under terms of your choice,
+  provided that you also meet, for each linked independent module, the terms
+  and conditions of the license of that module. An independent module is a
+  module which is not derived from or based on this library. If you modify
+  this library, you may extend this exception to your version of the library,
+  but you are not obligated to do so. If you do not wish to do so, delete this
+  exception statement from your version.
+
+  This program is distributed in the hope that it will be useful, but WITHOUT
+  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
+  for more details.
+
+  You should have received a copy of the GNU Library General Public License
+  along with this library; if not, write to the Free Software Foundation,
+  Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA.
+}
+
+unit ptckvm;
+
+{$mode objfpc}
+
+interface
+
+uses
+  Keyboard, Video, Mouse;
+
+procedure RegisterPtcKvmDrivers;
+function CheckPendingResize(out NewMode: TVideoMode): Boolean;
+procedure StartBlinkingCursor;
+procedure StopBlinkingCursor;
+
+var
+  InitialWidth: Integer = 80;
+  InitialHeight: Integer = 25;
+  FontFileName: string = 'fptermfont.psfu';
+
+implementation
+
+uses
+  SysUtils, ptc, ptceventqueue;
+
+type
+  TSubsystem = (sKeyboard, sVideo, sMouse);
+  TSubsystems = set of TSubsystem;
+
+  { TBitmapFont }
+
+  TBitmapFont = class
+  strict private
+    FGlyphCount: Integer;
+    FWidth, FHeight: Integer;
+    FData: array of Boolean;
+    FCodePointToGlyph: array [UCS4Char] of Integer;
+
+    procedure AddCodePointForGlyph(GlyphId: Integer; CodePoint: LongWord);
+    procedure AddUTF8SequenceForGlyph(GlyphId: Integer; Seq: UTF8String);
+    function GetPixel(GlyphId, Y, X: Integer): Boolean;
+    procedure SetGlyphCount(AValue: Integer);
+    procedure SetPixel(GlyphId, Y, X: Integer; AValue: Boolean);
+    function DetectPSF1(var F: File): Boolean;
+    function DetectPSF2(var F: File): Boolean;
+    function DetectRAW(var F: File): Boolean;
+    procedure CreateFromRAW(var F: File);
+    procedure CreateFromPSF1(var F: File);
+    procedure CreateFromPSF2(var F: File);
+  public
+    constructor Create(AWidth, AHeight, AGlyphCount: Integer);
+    constructor CreateFromFile(const AFileName: string);
+
+    function EGC2GlyphId(const EGC: UnicodeString): Integer;
+
+    property Width: Integer read FWidth;
+    property Height: Integer read FHeight;
+    property GlyphCount: Integer read FGlyphCount write SetGlyphCount;
+    property Pixel [GlyphId, Y, X: Integer]: Boolean read GetPixel write SetPixel;
+  end;
+
+var
+  InitializedSubsystems: TSubsystems;
+
+  CurrentFont: TBitmapFont;
+  CurrentWidth, CurrentHeight: Integer; { characters }
+  CurrentWidthPixels, CurrentHeightPixels: Integer; { pixels }
+
+  CursorType: Word;
+  CursorBlinkingEnabled: Boolean;
+  CursorBlinkState: Boolean;  { true - visible; false - invisible }
+  FastBlinkTextState: Boolean;  { true - visible; false - invisible }
+  SlowBlinkTextState: Boolean;  { true - visible; false - invisible }
+  MouseX, MouseY: Integer;
+  MouseButtons: Integer;
+  OldMouseX, OldMouseY: Integer;
+  OldMouseButtons: Integer;
+  ShiftState: Word;
+
+  LastDrawnCursorType: Word;
+  LastDrawnCursorBlinkState: Boolean;
+  LastDrawnFastBlinkTextState: Boolean;
+  LastDrawnSlowBlinkTextState: Boolean;
+  LastDrawnCursorX: Word;
+  LastDrawnCursorY: Word;
+
+  LastFrameTime: Double;
+  LastPolledEventBuffer: TEnhancedKeyEvent;
+  PendingResize: IPTCResizeEvent;
+
+  { ptc stuff }
+  Format: IPTCFormat;
+  Timer: IPTCTimer;
+  CursorBlinkTimer: IPTCTimer;
+  KeyPressEvents: TEventQueue;
+  Palette: IPTCPalette;
+  Surface: IPTCSurface;
+  Console: IPTCConsole;
+
+{ TBitmapFont }
+
+procedure TBitmapFont.AddCodePointForGlyph(GlyphId: Integer; CodePoint: LongWord);
+begin
+  if CodePoint > High(UCS4Char) then
+    raise EArgumentOutOfRangeException.Create('CodePoint not in valid range');
+  if (CodePoint >= $D800) and (CodePoint <= $DFFF) then
+    raise EArgumentOutOfRangeException.Create('CodePoint must not be in the reserved U+D800 to U+DFFF range');
+  FCodePointToGlyph[CodePoint] := GlyphId;
+end;
+
+procedure TBitmapFont.AddUTF8SequenceForGlyph(GlyphId: Integer; Seq: UTF8String);
+begin
+  {todo...}
+end;
+
+function TBitmapFont.GetPixel(GlyphId, Y, X: Integer): Boolean;
+begin
+  Result := FData[(GlyphId * FHeight + Y) * FWidth + X];
+end;
+
+procedure TBitmapFont.SetGlyphCount(AValue: Integer);
+begin
+  if FGlyphCount = AValue then
+    exit;
+  FGlyphCount := AValue;
+  SetLength(FData, FGlyphCount * FWidth * FHeight);
+end;
+
+procedure TBitmapFont.SetPixel(GlyphId, Y, X: Integer; AValue: Boolean);
+begin
+  FData[(GlyphId * FHeight + Y) * FWidth + X] := AValue;
+end;
+
+function TBitmapFont.DetectPSF1(var F: File): Boolean;
+var
+  HeaderMagic: array [0..1] of Byte;
+begin
+  Result := false;
+  if FileSize(F) < 4 then
+    exit;
+  Seek(F, 0);
+  BlockRead(F, HeaderMagic, SizeOf(HeaderMagic));
+  Result := (HeaderMagic[0] = $36) and
+            (HeaderMagic[1] = $04);
+end;
+
+function TBitmapFont.DetectPSF2(var F: File): Boolean;
+var
+  HeaderMagic: array [0..3] of Byte;
+begin
+  Result := false;
+  if FileSize(F) < 32 then
+    exit;
+  Seek(F, 0);
+  BlockRead(F, HeaderMagic, SizeOf(HeaderMagic));
+  Result := (HeaderMagic[0] = $72) and
+            (HeaderMagic[1] = $b5) and
+            (HeaderMagic[2] = $4a) and
+            (HeaderMagic[3] = $86);
+end;
+
+function TBitmapFont.DetectRAW(var F: File): Boolean;
+begin
+  case FileSize(F) of
+    256*8,
+    256*14,
+    256*16:
+      Result := True;
+    else
+      Result := False;
+  end;
+end;
+
+procedure TBitmapFont.CreateFromRAW(var F: File);
+var
+  I, Y, X: Integer;
+  B: Byte;
+begin
+  case FileSize(F) of
+    256*8:
+      Create(8, 8, 256);
+    256*14:
+      Create(8, 14, 256);
+    256*16:
+      Create(8, 16, 256);
+    else
+      raise Exception.Create('Unknown font file format');
+  end;
+  for I := 0 to GlyphCount - 1 do
+    for Y := 0 to Height - 1 do
+    begin
+      BlockRead(F, B, 1);
+      for X := 0 to Width - 1 do
+      begin
+        Pixel[I, Y, X] := (B and $80) <> 0;
+        B := B shl 1;
+      end;
+    end;
+end;
+
+procedure TBitmapFont.CreateFromPSF1(var F: File);
+const
+  PSF1_MODE512    = $01;
+  PSF1_MODEHASTAB = $02;
+  PSF1_MODEHASSEQ = $04;
+  PSF1_MAXMODE    = $05;
+type
+  TPsf1Header = packed record
+    magic:    array [0..1] of Byte;
+    mode:     Byte;
+    charsize: Byte;
+  end;
+var
+  Header: TPsf1Header;
+  I, Y, X: Integer;
+  B: Byte;
+  WC: WideChar;
+  Seq: UnicodeString;
+begin
+  Seek(F, 0);
+  BlockRead(F, Header, SizeOf(Header));
+  if (Header.mode and PSF1_MODE512) <> 0 then
+    Create(8, Header.charsize, 512)
+  else
+    Create(8, Header.charsize, 256);
+  for I := 0 to GlyphCount - 1 do
+  begin
+    for Y := 0 to Height - 1 do
+    begin
+      BlockRead(F, B, 1);
+      for X := 0 to Width - 1 do
+      begin
+        Pixel[I, Y, X] := (B and $80) <> 0;
+        B := B shl 1;
+      end;
+    end;
+  end;
+  if (Header.mode and PSF1_MODEHASTAB) <> 0 then
+  begin
+    for I := 0 to GlyphCount - 1 do
+    begin
+      repeat
+        BlockRead(F, WC, 2);
+        if (WC <> WideChar($FFFF)) and (WC <> WideChar($FFFE)) then
+          AddCodePointForGlyph(I, Ord(WC));
+      until (WC = WideChar($FFFF)) or (WC = WideChar($FFFE));
+      while WC = WideChar($FFFE) do
+      begin
+        Seq := '';
+        repeat
+          BlockRead(F, WC, 2);
+          if (WC <> WideChar($FFFF)) and (WC <> WideChar($FFFE)) then
+            Seq := Seq + WC;
+        until (WC = WideChar($FFFF)) or (WC = WideChar($FFFE));
+        AddUTF8SequenceForGlyph(I, UTF8Encode(Seq));
+      end;
+    end;
+  end;
+end;
+
+procedure TBitmapFont.CreateFromPSF2(var F: File);
+const
+  PSF2_HAS_UNICODE_TABLE = 1;
+type
+  TPsf2Header = packed record
+    magic:         array [0..3] of Byte;
+    version:       UInt32;
+    headersize:    UInt32;
+    flags:         UInt32;
+    length:        UInt32;
+    charsize:      UInt32;
+    height, width: UInt32;
+  end;
+var
+  Header: TPsf2Header;
+  I, BitsLeft, Y, X, J: Integer;
+  B: Byte;
+  CodePoint: UCS4Char;
+  Seq: UTF8String;
+begin
+  Seek(F, 0);
+  BlockRead(F, Header, SizeOf(Header));
+  {$ifdef FPC_BIG_ENDIAN}
+  with Header do
+  begin
+    version := SwapEndian(version);
+    headersize := SwapEndian(headersize);
+    flags := SwapEndian(flags);
+    length := SwapEndian(length);
+    charsize := SwapEndian(charsize);
+    height := SwapEndian(height);
+    width := SwapEndian(width);
+  end;
+  {$endif FPC_BIG_ENDIAN}
+  Create(Header.width, Header.height, Header.length);
+  for I := 0 to GlyphCount - 1 do
+  begin
+    BitsLeft := 0;
+    Seek(F, Header.headersize + I * Header.charsize);
+    for Y := 0 to Height - 1 do
+    begin
+      { each row starts on a byte boundary }
+      if BitsLeft <> 0 then
+      begin
+        BitsLeft := 8;
+        BlockRead(F, B, 1);
+      end;
+      for X := 0 to Width - 1 do
+      begin
+        if BitsLeft = 0 then
+        begin
+          BitsLeft := 8;
+          BlockRead(F, B, 1);
+        end;
+        Pixel[I, Y, X] := (B and $80) <> 0;
+        B := B shl 1;
+        Dec(BitsLeft);
+      end;
+    end;
+  end;
+  if (Header.flags and PSF2_HAS_UNICODE_TABLE) <> 0 then
+  begin
+    Seek(F, Header.headersize + Header.length * Header.charsize);
+    for I := 0 to GlyphCount - 1 do
+    begin
+      repeat
+        BlockRead(F, B, 1);
+        if (B <> $FF) and (B <> $FE) then
+        begin
+          case B of
+            0..127:
+              AddCodePointForGlyph(I, B);
+            %11000000..%11011111:
+              begin
+                CodePoint := B and %11111;
+                BlockRead(F, B, 1);
+                if (B and %11000000) <> %10000000 then
+                  raise Exception.Create('Error in file');
+                CodePoint := (CodePoint shl 6) or (B and %111111);
+                AddCodePointForGlyph(I, CodePoint);
+              end;
+            %11100000..%11101111:
+              begin
+                CodePoint := B and %1111;
+                for J := 1 to 2 do
+                begin
+                  BlockRead(F, B, 1);
+                  if (B and %11000000) <> %10000000 then
+                    raise Exception.Create('Error in file');
+                  CodePoint := (CodePoint shl 6) or (B and %111111);
+                end;
+                AddCodePointForGlyph(I, CodePoint);
+              end;
+            %11110000..%11110111:
+              begin
+                CodePoint := B and %111;
+                for J := 1 to 3 do
+                begin
+                  BlockRead(F, B, 1);
+                  if (B and %11000000) <> %10000000 then
+                    raise Exception.Create('Error in file');
+                  CodePoint := (CodePoint shl 6) or (B and %111111);
+                end;
+                AddCodePointForGlyph(I, CodePoint);
+              end;
+          end;
+        end;
+      until (B = $FF) or (B = $FE);
+      while B = $FE do
+      begin
+        Seq := '';
+        repeat
+          BlockRead(F, B, 1);
+          if (B <> $FF) and (B <> $FE) then
+            Seq := Seq + Chr(B);
+        until (B = $FF) or (B = $FE);
+        AddUTF8SequenceForGlyph(I, Seq);
+      end;
+    end;
+  end;
+end;
+
+constructor TBitmapFont.Create(AWidth, AHeight, AGlyphCount: Integer);
+begin
+  FWidth := AWidth;
+  FHeight := AHeight;
+  FGlyphCount := 0;
+  GlyphCount := AGlyphCount;
+end;
+
+constructor TBitmapFont.CreateFromFile(const AFileName: string);
+var
+  F: File;
+begin
+  AssignFile(F, AFileName);
+  Reset(F, 1);
+  try
+    if DetectPSF2(F) then
+      CreateFromPSF2(F)
+    else if DetectPSF1(F) then
+      CreateFromPSF1(F)
+    else if DetectRAW(F) then
+      CreateFromRAW(F)
+    else
+      raise Exception.Create('Unknown font file format');
+  finally
+    CloseFile(F);
+  end;
+end;
+
+const
+  convert_lowascii_to_Unicode:array[#0..#31] of WideChar=(
+    #8199,#9786,#9787,#9829,#9830,#9827,#9824,#8226,
+    #9688,#9675,#9689,#9794,#9792,#9834,#9835,#9788,
+    #9658,#9668,#8597,#8252,#0182,#0167,#9644,#8616,
+    #8593,#8595,#8594,#8592,#8735,#8596,#9650,#9660
+  );
+
+function ExtendedGraphemeCluster2LegacyChar(const EGC: UnicodeString; CodePage: TSystemCodePage): Char;
+
+  function GenConvert: Char;
+    var
+      tmpS: RawByteString;
+    begin
+      tmpS:=System.UTF8Encode(EGC);
+      System.SetCodePage(tmpS,CodePage,True);
+      if Length(tmpS)=1 then
+        Result:=tmpS[1]
+      else
+        Result:='?';
+    end;
+
+var
+  Ch: Char;
+begin
+  if (Length(EGC) = 1) then
+    begin
+      for Ch:=Low(convert_lowascii_to_Unicode) to High(convert_lowascii_to_Unicode) do
+        if convert_lowascii_to_Unicode[Ch]=EGC[1] then
+          begin
+            Result:=Ch;
+            exit;
+          end;
+      case Ord(EGC[1]) of
+        32..126:
+          Result:=Chr(Ord(EGC[1]));
+        $2302:
+          Result:=#127;
+        else
+          Result:=GenConvert;
+      end
+    end
+  else
+    Result:=GenConvert;
+end;
+
+function TBitmapFont.EGC2GlyphId(const EGC: UnicodeString): Integer;
+begin
+  Result := -1;
+  if (Length(EGC) = 1) and ((Ord(EGC[1]) < $D800) or (Ord(EGC[1]) > $DFFF)) then
+    Result := FCodePointToGlyph[Ord(EGC[1])]
+  else if (Length(EGC) = 2) and ((Ord(EGC[1]) >= $D800) and (Ord(EGC[1]) <= $DBFF)) and
+                                ((Ord(EGC[2]) >= $DC00) and (Ord(EGC[2]) <= $DFFF)) then
+    Result := FCodePointToGlyph[$10000 + (((Ord(EGC[1]) - $D800) shl 10) or (Ord(EGC[2]) - $DC00))];
+  if Result = -1 then
+    Result := Ord(ExtendedGraphemeCluster2LegacyChar(EGC,437));
+end;
+
+procedure SetMode(Width, Height: Integer);
+var
+  IsResize: Boolean;
+begin
+  { resize or reopen? }
+  IsResize := (PendingResize <> nil) and (Width = (PendingResize.Width div CurrentFont.Width)) and (Height = (PendingResize.Height div CurrentFont.Height));
+
+  if not IsResize then
+    Console.Close;
+  Surface := nil;
+
+  CurrentWidth := Width;
+  CurrentHeight := Height;
+  if IsResize then
+  begin
+    CurrentWidthPixels := PendingResize.Width;
+    CurrentHeightPixels := PendingResize.Height;
+  end
+  else
+  begin
+    CurrentWidthPixels := CurrentWidth * CurrentFont.Width;
+    CurrentHeightPixels := CurrentHeight * CurrentFont.Height;
+  end;
+
+  Surface := TPTCSurfaceFactory.CreateNew(CurrentWidthPixels, CurrentHeightPixels, Format);
+  if IsResize then
+  begin
+    Console.InternalResize(CurrentWidthPixels, CurrentHeightPixels);
+    PendingResize := nil;
+  end
+  else
+    Console.Open('ptc kvm', CurrentWidthPixels, CurrentHeightPixels, Format);
+
+  Surface.Palette(Palette);
+  Console.Palette(Palette);
+
+  CursorBlinkTimer.Stop;
+  CursorBlinkTimer.SetTime(0);
+  CursorBlinkTimer.Start;
+
+  Timer.Stop;
+  Timer.SetTime(0);
+  LastFrameTime := Timer.Time;
+  Timer.Start;
+end;
+
+procedure UpdateBlinkState;
+const
+  BlinkPeriod = 16/70;
+begin
+  if CursorBlinkTimer.Time >= BlinkPeriod then
+  begin
+    FastBlinkTextState := not FastBlinkTextState;
+    if not FastBlinkTextState then
+      SlowBlinkTextState := not SlowBlinkTextState;
+    CursorBlinkTimer.SetTime(0);
+  end;
+  if CursorBlinkingEnabled then
+    CursorBlinkState := FastBlinkTextState
+  else
+    CursorBlinkState := True;
+end;
+
+procedure KVMInit;
+const
+  PaletteData: array [0..255] of Uint32 =
+  ($000000, $0000AA, $00AA00, $00AAAA, $AA0000, $AA00AA, $AA5500, $AAAAAA,
+   $555555, $5555FF, $55FF55, $55FFFF, $FF5555, $FF55FF, $FFFF55, $FFFFFF,
+
+   { 6x6x6 colour cube }
+   $000000, $00005f, $000087, $0000af, $0000d7, $0000ff,
+   $005f00, $005f5f, $005f87, $005faf, $005fd7, $005fff,
+   $008700, $00875f, $008787, $0087af, $0087d7, $0087ff,
+   $00af00, $00af5f, $00af87, $00afaf, $00afd7, $00afff,
+   $00d700, $00d75f, $00d787, $00d7af, $00d7d7, $00d7ff,
+   $00ff00, $00ff5f, $00ff87, $00ffaf, $00ffd7, $00ffff,
+
+   $5f0000, $5f005f, $5f0087, $5f00af, $5f00d7, $5f00ff,
+   $5f5f00, $5f5f5f, $5f5f87, $5f5faf, $5f5fd7, $5f5fff,
+   $5f8700, $5f875f, $5f8787, $5f87af, $5f87d7, $5f87ff,
+   $5faf00, $5faf5f, $5faf87, $5fafaf, $5fafd7, $5fafff,
+   $5fd700, $5fd75f, $5fd787, $5fd7af, $5fd7d7, $5fd7ff,
+   $5fff00, $5fff5f, $5fff87, $5fffaf, $5fffd7, $5fffff,
+
+   $870000, $87005f, $870087, $8700af, $8700d7, $8700ff,
+   $875f00, $875f5f, $875f87, $875faf, $875fd7, $875fff,
+   $878700, $87875f, $878787, $8787af, $8787d7, $8787ff,
+   $87af00, $87af5f, $87af87, $87afaf, $87afd7, $87afff,
+   $87d700, $87d75f, $87d787, $87d7af, $87d7d7, $87d7ff,
+   $87ff00, $87ff5f, $87ff87, $87ffaf, $87ffd7, $87ffff,
+
+   $af0000, $af005f, $af0087, $af00af, $af00d7, $af00ff,
+   $af5f00, $af5f5f, $af5f87, $af5faf, $af5fd7, $af5fff,
+   $af8700, $af875f, $af8787, $af87af, $af87d7, $af87ff,
+   $afaf00, $afaf5f, $afaf87, $afafaf, $afafd7, $afafff,
+   $afd700, $afd75f, $afd787, $afd7af, $afd7d7, $afd7ff,
+   $afff00, $afff5f, $afff87, $afffaf, $afffd7, $afffff,
+
+   $d70000, $d7005f, $d70087, $d700af, $d700d7, $d700ff,
+   $d75f00, $d75f5f, $d75f87, $d75faf, $d75fd7, $d75fff,
+   $d78700, $d7875f, $d78787, $d787af, $d787d7, $d787ff,
+   $d7af00, $d7af5f, $d7af87, $d7afaf, $d7afd7, $d7afff,
+   $d7d700, $d7d75f, $d7d787, $d7d7af, $d7d7d7, $d7d7ff,
+   $d7ff00, $d7ff5f, $d7ff87, $d7ffaf, $d7ffd7, $d7ffff,
+
+   $ff0000, $ff005f, $ff0087, $ff00af, $ff00d7, $ff00ff,
+   $ff5f00, $ff5f5f, $ff5f87, $ff5faf, $ff5fd7, $ff5fff,
+   $ff8700, $ff875f, $ff8787, $ff87af, $ff87d7, $ff87ff,
+   $ffaf00, $ffaf5f, $ffaf87, $ffafaf, $ffafd7, $ffafff,
+   $ffd700, $ffd75f, $ffd787, $ffd7af, $ffd7d7, $ffd7ff,
+   $ffff00, $ffff5f, $ffff87, $ffffaf, $ffffd7, $ffffff,
+
+   { grayscale }
+   $080808, $121212, $1c1c1c, $262626, $303030, $3a3a3a, $444444, $4e4e4e,
+   $585858, $626262, $6c6c6c, $767676, $808080, $8a8a8a, $949494, $9e9e9e,
+   $a8a8a8, $b2b2b2, $bcbcbc, $c6c6c6, $d0d0d0, $dadada, $e4e4e4, $eeeeee);
+begin
+  MouseX := 0;
+  MouseY := 0;
+  MouseButtons := 0;
+  OldMouseX := 0;
+  OldMouseY := 0;
+  OldMouseButtons := 0;
+  ShiftState := 0;
+  try
+    FreeAndNil(KeyPressEvents);
+    FreeAndNil(CurrentFont);
+    CurrentFont := TBitmapFont.CreateFromFile(FontFileName);
+
+    Format := TPTCFormatFactory.CreateNew(8);
+    Timer := TPTCTimerFactory.CreateNew;
+    CursorBlinkTimer := TPTCTimerFactory.CreateNew;
+    Palette := TPTCPaletteFactory.CreateNew;
+    Console := TPTCConsoleFactory.CreateNew;
+    Console.Option('windowed output');
+    Console.Option('resizable window');
+    //Console.Option('intercept window close');
+
+    Palette.Load(PaletteData);
+
+    CursorBlinkingEnabled := True;
+    CursorBlinkState := True;
+    FastBlinkTextState := True;
+    SlowBlinkTextState := True;
+
+    KeyPressEvents := TEventQueue.Create;
+
+    SetMode(InitialWidth, InitialHeight);
+  except
+    on error : TPTCError do
+      error.report;
+  end;
+end;
+
+procedure KVMDone;
+begin
+  Surface := nil;
+  Console := nil;
+  Palette := nil;
+  Timer := nil;
+  CursorBlinkTimer := nil;
+  Format := nil;
+  FreeAndNil(KeyPressEvents);
+  CurrentWidth := 0;
+  CurrentHeight := 0;
+  CurrentWidthPixels := 0;
+  CurrentHeightPixels := 0;
+end;
+
+procedure KVMUpdateScreen;
+var
+  ptr, p2, p3: Puint8;
+  X, Y, CX, CY: Integer;
+  IsCursor: Boolean;
+  CursorHeight: Integer;
+  EGC: UnicodeString;
+  EVA: TEnhancedVideoAttributes;
+  GlyphId: Integer;
+  Attr: Byte;
+  foreground, background: Byte;
+begin
+  LastDrawnCursorType := CursorType;
+  LastDrawnCursorX := CursorX;
+  LastDrawnCursorY := CursorY;
+  LastDrawnCursorBlinkState := CursorBlinkState;
+  LastDrawnFastBlinkTextState := FastBlinkTextState;
+  LastDrawnSlowBlinkTextState := SlowBlinkTextState;
+  case CursorType of
+    crUnderLine:
+      CursorHeight := CurrentFont.Height div 4;
+    crBlock:
+      CursorHeight := CurrentFont.Height;
+    crHalfBlock:
+      CursorHeight := CurrentFont.Height div 2;
+  end;
+  try
+    ptr := Surface.Lock;
+    try
+      for Y := 0 to CurrentHeight - 1 do
+        for X := 0 to CurrentWidth - 1 do
+        begin
+          IsCursor := CursorBlinkState and (X = CursorX) and (Y = CursorY) and (CursorType <> 0);
+
+          EGC := EnhancedVideoBuf[Y * ScreenWidth + X].ExtendedGraphemeCluster;
+          EVA := EnhancedVideoBuf[Y * ScreenWidth + X].EnhancedVideoAttributes;
+          if evaInverse in EVA then
+          begin
+            foreground := EnhancedVideoBuf[Y * ScreenWidth + X].BackgroundColor;
+            background := EnhancedVideoBuf[Y * ScreenWidth + X].ForegroundColor;
+          end
+          else
+          begin
+            foreground := EnhancedVideoBuf[Y * ScreenWidth + X].ForegroundColor;
+            background := EnhancedVideoBuf[Y * ScreenWidth + X].BackgroundColor;
+          end;
+          { todo: use a bold font? }
+          if (evaBold in EVA) and (foreground <= 15) then
+            foreground := foreground or 8;
+          if ((evaBlinkSlow in EVA) and not SlowBlinkTextState) or
+             ((evaBlinkFast in EVA) and not FastBlinkTextState) or
+             (evaInvisible in EVA) then
+            foreground := background;
+          GlyphId := CurrentFont.EGC2GlyphId(EGC);
+
+          p2 := ptr + X * CurrentFont.Width + Y * CurrentFont.Height * Surface.Pitch;
+          for CY := 0 to CurrentFont.Height - 1 do
+          begin
+            p3 := p2;
+
+            if ((not IsCursor) or (CY < (CurrentFont.Height - CursorHeight))) and
+               ((not (evaUnderlined in EVA)) or (CY <> CurrentFont.Height - 1)) and
+               ((not (evaDoublyUnderlined in EVA)) or ((CY <> CurrentFont.Height - 1) and (CY <> CurrentFont.Height - 3))) and
+               ((not (evaCrossedOut in EVA)) or (CY <> (CurrentFont.Height div 2))) then
+            begin
+              for CX := 0 to CurrentFont.Width - 1 do
+              begin
+                if CurrentFont.Pixel[GlyphId, CY, CX] then
+                  p3^ := foreground
+                else
+                  p3^ := background;
+                Inc(p3);
+              end;
+            end
+            else
+            begin
+              for CX := 0 to CurrentFont.Width - 1 do
+              begin
+                p3^ := foreground;
+                Inc(p3);
+              end;
+            end;
+
+            Inc(p2, Surface.Pitch);
+          end;
+        end;
+    finally
+      Surface.Unlock;
+    end;
+    Surface.Copy(Console);
+    Console.Update;
+  except
+    on Error: TPTCError do
+      Error.Report;
+  end;
+end;
+
+procedure KVMHandleKeyEvent(KeyEvent: IPTCKeyEvent);
+begin
+  if KeyEvent.Press then
+    KeyPressEvents.AddEvent(KeyEvent);
+  { update shift state }
+  case KeyEvent.Code of
+    PTCKEY_SHIFT:
+      if pmkRightKey in KeyEvent.ModifierKeys then
+      begin
+        if KeyEvent.Press then
+          ShiftState := ShiftState or 1
+        else
+          ShiftState := ShiftState and not 1;
+      end
+      else
+      begin
+        if KeyEvent.Press then
+          ShiftState := ShiftState or 2
+        else
+          ShiftState := ShiftState and not 2;
+      end;
+    PTCKEY_CONTROL:
+      begin
+        if pmkRightKey in KeyEvent.ModifierKeys then
+        begin
+          if KeyEvent.Press then
+            ShiftState := ShiftState or $400
+          else
+            ShiftState := ShiftState and not $400;
+        end
+        else
+        begin
+          if KeyEvent.Press then
+            ShiftState := ShiftState or $100
+          else
+            ShiftState := ShiftState and not $100;
+        end;
+        ShiftState := (ShiftState and not 4) or ((ShiftState and $400) shr 8) or ((ShiftState and $100) shr 6);
+      end;
+    PTCKEY_ALT:
+      begin
+        if pmkRightKey in KeyEvent.ModifierKeys then
+        begin
+          if KeyEvent.Press then
+            ShiftState := ShiftState or $800
+          else
+            ShiftState := ShiftState and not $800;
+        end
+        else
+        begin
+          if KeyEvent.Press then
+            ShiftState := ShiftState or $200
+          else
+            ShiftState := ShiftState and not $200;
+        end;
+        ShiftState := (ShiftState and not 8) or ((ShiftState and $800) shr 8) or ((ShiftState and $200) shr 6);
+      end;
+  end;
+end;
+
+procedure KVMHandleMouseEvent(MouseEvent: IPTCMouseEvent);
+var
+  B: TPTCMouseButton;
+  ME: TMouseEvent;
+begin
+  if not (sMouse in InitializedSubsystems) then
+    exit;
+
+  MouseX := MouseEvent.X div CurrentFont.Width;
+  MouseY := MouseEvent.Y div CurrentFont.Height;
+  MouseButtons := 0;
+  for B := PTCMouseButton1 to PTCMouseButton16 do
+    if B in MouseEvent.ButtonState then
+      MouseButtons := MouseButtons or (1 shl (Ord(B) - Ord(PTCMouseButton1)));
+
+  if (MouseX <> OldMouseX) or (MouseY <> OldMouseY) or (MouseButtons <> OldMouseButtons) then
+  begin
+    FillChar(ME, SizeOf(ME), 0);
+    ME.X := MouseX;
+    ME.Y := MouseY;
+    ME.Buttons := MouseButtons;
+
+    if (MouseX <> OldMouseX) or (MouseY <> OldMouseY) then
+      ME.Action := MouseActionMove;
+    if MouseButtons <> OldMouseButtons then
+      if (MouseButtons and OldMouseButtons) <> OldMouseButtons then
+        ME.Action := MouseActionUp
+      else
+        ME.Action := MouseActionDown;
+
+    PutMouseEvent(ME);
+  end
+end;
+
+procedure KVMHandleEvents;
+var
+  Event: IPTCEvent;
+begin
+  while Console.NextEvent(Event, False, PTCAnyEvent) do
+    if Event <> nil then
+    begin
+      if Event is IPTCResizeEvent then
+        PendingResize := Event as IPTCResizeEvent
+      else if Event is IPTCKeyEvent then
+        KVMHandleKeyEvent(Event as IPTCKeyEvent)
+      else if Event is IPTCMouseEvent then
+        KVMHandleMouseEvent(Event as IPTCMouseEvent);
+    end;
+end;
+
+function KVMGetKeyEvent: IPTCKeyEvent;
+var
+  Event: IPTCEvent;
+begin
+  repeat
+    KVMHandleEvents;
+    Event := KeyPressEvents.NextEvent([PTCKeyEvent]);
+  until Event <> nil;
+  Result := Event as IPTCKeyEvent;
+end;
+
+function KVMPollKeyEvent: IPTCKeyEvent;
+var
+  Event: IPTCEvent;
+begin
+  KVMHandleEvents;
+  Event := KeyPressEvents.NextEvent([PTCKeyEvent]);
+
+  if Event <> nil then
+    Result := Event as IPTCKeyEvent
+  else
+    Result := nil;
+end;
+
+function ConvertPTCKeyEventToTEnhancedKeyEvent(ev: IPTCKeyEvent): TEnhancedKeyEvent;
+var
+  ShiftState: TEnhancedShiftState;
+begin
+  if (ev = nil) or (ev.Code = -1) then
+  begin
+    Result := NilEnhancedKeyEvent;
+    exit;
+  end;
+
+  ShiftState := [];
+  if ev.Shift then
+    Include(ShiftState, essShift);
+  if ev.Control then
+    Include(ShiftState, essCtrl);
+  if ev.Alt then
+    Include(ShiftState, essAlt);
+
+  Result.AsciiChar := #0;
+  Result.UnicodeChar := WideChar(0);
+
+  if ev.alt then
+    case ev.Code of
+      PTCKEY_ESCAPE:
+        Result.VirtualScanCode := $0100;
+      PTCKEY_F1:
+        begin
+          Result.VirtualScanCode := $6800;
+          Result.VirtualKeyCode := kbdF1;
+        end;
+      PTCKEY_F2:
+        begin
+          Result.VirtualScanCode := $6900;
+          Result.VirtualKeyCode := kbdF2;
+        end;
+      PTCKEY_F3:
+        begin
+          Result.VirtualScanCode := $6A00;
+          Result.VirtualKeyCode := kbdF3;
+        end;
+      PTCKEY_F4:
+        begin
+          Result.VirtualScanCode := $6B00;
+          Result.VirtualKeyCode := kbdF4;
+        end;
+      PTCKEY_F5:
+        begin
+          Result.VirtualScanCode := $6C00;
+          Result.VirtualKeyCode := kbdF5;
+        end;
+      PTCKEY_F6:
+        begin
+          Result.VirtualScanCode := $6D00;
+          Result.VirtualKeyCode := kbdF6;
+        end;
+      PTCKEY_F7:
+        begin
+          Result.VirtualScanCode := $6E00;
+          Result.VirtualKeyCode := kbdF7;
+        end;
+      PTCKEY_F8:
+        begin
+          Result.VirtualScanCode := $6F00;
+          Result.VirtualKeyCode := kbdF8;
+        end;
+      PTCKEY_F9:
+        begin
+          Result.VirtualScanCode := $7000;
+          Result.VirtualKeyCode := kbdF9;
+        end;
+      PTCKEY_F10:
+        begin
+          Result.VirtualScanCode := $7100;
+          Result.VirtualKeyCode := kbdF10;
+        end;
+      PTCKEY_F11:
+        begin
+          Result.VirtualScanCode := $8B00;
+          Result.VirtualKeyCode := kbdF11;
+        end;
+      PTCKEY_F12:
+        begin
+          Result.VirtualScanCode := $8C00;
+          Result.VirtualKeyCode := kbdF12;
+        end;
+      PTCKEY_ENTER:
+        begin
+          if pmkNumPadKey in ev.ModifierKeys then
+            Result.VirtualScanCode := $A600
+          else
+            Result.VirtualScanCode := $1C00;
+        end;
+      PTCKEY_BACKSPACE:
+        Result.VirtualScanCode := $0E00;
+      PTCKEY_TAB:
+        Result.VirtualScanCode := $A500;
+      PTCKEY_UP:
+        Result.VirtualScanCode := $9800;
+      PTCKEY_LEFT:
+        Result.VirtualScanCode := $9B00;
+      PTCKEY_DOWN:
+        Result.VirtualScanCode := $A000;
+      PTCKEY_RIGHT:
+        Result.VirtualScanCode := $9D00;
+      PTCKEY_INSERT:
+        Result.VirtualScanCode := $A200;
+      PTCKEY_HOME:
+        Result.VirtualScanCode := $9700;
+      PTCKEY_PAGEUP:
+        Result.VirtualScanCode := $9900;
+      PTCKEY_DELETE:
+        Result.VirtualScanCode := $A300;
+      PTCKEY_END:
+        Result.VirtualScanCode := $9F00;
+      PTCKEY_PAGEDOWN:
+        Result.VirtualScanCode := $A100;
+      PTCKEY_BACKQUOTE:
+        Result.VirtualScanCode := $2900;
+      PTCKEY_ONE:
+        Result.VirtualScanCode := $7800;
+      PTCKEY_TWO:
+        Result.VirtualScanCode := $7900;
+      PTCKEY_THREE:
+        Result.VirtualScanCode := $7A00;
+      PTCKEY_FOUR:
+        Result.VirtualScanCode := $7B00;
+      PTCKEY_FIVE:
+        Result.VirtualScanCode := $7C00;
+      PTCKEY_SIX:
+        Result.VirtualScanCode := $7D00;
+      PTCKEY_SEVEN:
+        Result.VirtualScanCode := $7E00;
+      PTCKEY_EIGHT:
+        Result.VirtualScanCode := $7F00;
+      PTCKEY_NINE:
+        Result.VirtualScanCode := $8000;
+      PTCKEY_ZERO:
+        Result.VirtualScanCode := $8100;
+      PTCKEY_MINUS:
+        Result.VirtualScanCode := $8200;
+      PTCKEY_EQUALS:
+        Result.VirtualScanCode := $8300;
+      PTCKEY_Q:
+        Result.VirtualScanCode := $1000;
+      PTCKEY_W:
+        Result.VirtualScanCode := $1100;
+      PTCKEY_E:
+        Result.VirtualScanCode := $1200;
+      PTCKEY_R:
+        Result.VirtualScanCode := $1300;
+      PTCKEY_T:
+        Result.VirtualScanCode := $1400;
+      PTCKEY_Y:
+        Result.VirtualScanCode := $1500;
+      PTCKEY_U:
+        Result.VirtualScanCode := $1600;
+      PTCKEY_I:
+        Result.VirtualScanCode := $1700;
+      PTCKEY_O:
+        Result.VirtualScanCode := $1800;
+      PTCKEY_P:
+        Result.VirtualScanCode := $1900;
+      PTCKEY_OPENBRACKET:
+        Result.VirtualScanCode := $1A00;
+      PTCKEY_CLOSEBRACKET:
+        Result.VirtualScanCode := $1B00;
+      PTCKEY_BACKSLASH:
+        Result.VirtualScanCode := $2B00;
+      PTCKEY_A:
+        Result.VirtualScanCode := $1E00;
+      PTCKEY_S:
+        Result.VirtualScanCode := $1F00;
+      PTCKEY_D:
+        Result.VirtualScanCode := $2000;
+      PTCKEY_F:
+        Result.VirtualScanCode := $2100;
+      PTCKEY_G:
+        Result.VirtualScanCode := $2200;
+      PTCKEY_H:
+        Result.VirtualScanCode := $2300;
+      PTCKEY_J:
+        Result.VirtualScanCode := $2400;
+      PTCKEY_K:
+        Result.VirtualScanCode := $2500;
+      PTCKEY_L:
+        Result.VirtualScanCode := $2600;
+      PTCKEY_SEMICOLON:
+        Result.VirtualScanCode := $2700;
+      PTCKEY_QUOTE:
+        Result.VirtualScanCode := $2800;
+      PTCKEY_Z:
+        Result.VirtualScanCode := $2C00;
+      PTCKEY_X:
+        Result.VirtualScanCode := $2D00;
+      PTCKEY_C:
+        Result.VirtualScanCode := $2E00;
+      PTCKEY_V:
+        Result.VirtualScanCode := $2F00;
+      PTCKEY_B:
+        Result.VirtualScanCode := $3000;
+      PTCKEY_N:
+        Result.VirtualScanCode := $3100;
+      PTCKEY_M:
+        Result.VirtualScanCode := $3200;
+      PTCKEY_COMMA:
+        Result.VirtualScanCode := $3300;
+      PTCKEY_PERIOD:
+        Result.VirtualScanCode := $3400;
+      PTCKEY_SLASH:
+        Result.VirtualScanCode := $3500;
+      PTCKEY_SPACE:
+        begin
+          Result.AsciiChar := #32;
+          Result.UnicodeChar := WideChar(32);
+          Result.VirtualScanCode := $3920;
+        end;
+    end
+  else if ev.Control then
+    case ev.Code of
+      PTCKEY_ESCAPE:
+        begin
+          Result.AsciiChar := #27;
+          Result.UnicodeChar := WideChar(27);
+          Result.VirtualScanCode := $011B;
+        end;
+      PTCKEY_F1:
+        begin
+          Result.VirtualScanCode := $5E00;
+          Result.VirtualKeyCode := kbdF1;
+        end;
+      PTCKEY_F2:
+        begin
+          Result.VirtualScanCode := $5F00;
+          Result.VirtualKeyCode := kbdF2;
+        end;
+      PTCKEY_F3:
+        begin
+          Result.VirtualScanCode := $6000;
+          Result.VirtualKeyCode := kbdF3;
+        end;
+      PTCKEY_F4:
+        begin
+          Result.VirtualScanCode := $6100;
+          Result.VirtualKeyCode := kbdF4;
+        end;
+      PTCKEY_F5:
+        begin
+          Result.VirtualScanCode := $6200;
+          Result.VirtualKeyCode := kbdF5;
+        end;
+      PTCKEY_F6:
+        begin
+          Result.VirtualScanCode := $6300;
+          Result.VirtualKeyCode := kbdF6;
+        end;
+      PTCKEY_F7:
+        begin
+          Result.VirtualScanCode := $6400;
+          Result.VirtualKeyCode := kbdF7;
+        end;
+      PTCKEY_F8:
+        begin
+          Result.VirtualScanCode := $6500;
+          Result.VirtualKeyCode := kbdF8;
+        end;
+      PTCKEY_F9:
+        begin
+          Result.VirtualScanCode := $6600;
+          Result.VirtualKeyCode := kbdF9;
+        end;
+      PTCKEY_F10:
+        begin
+          Result.VirtualScanCode := $6700;
+          Result.VirtualKeyCode := kbdF10;
+        end;
+      PTCKEY_F11:
+        begin
+          Result.VirtualScanCode := $8900;
+          Result.VirtualKeyCode := kbdF11;
+        end;
+      PTCKEY_F12:
+        begin
+          Result.VirtualScanCode := $8A00;
+          Result.VirtualKeyCode := kbdF12;
+        end;
+      PTCKEY_ENTER:
+        begin
+          Result.AsciiChar := #10;
+          Result.UnicodeChar := WideChar(10);
+          if pmkNumPadKey in ev.ModifierKeys then
+            Result.VirtualScanCode := $E00A
+          else
+            Result.VirtualScanCode := $1C0A;
+        end;
+      PTCKEY_BACKSPACE:
+        begin
+          Result.AsciiChar := #127;
+          Result.UnicodeChar := WideChar(127);
+          Result.VirtualScanCode := $0E7F;
+        end;
+      PTCKEY_TAB:
+        Result.VirtualScanCode := $9400;
+      PTCKEY_UP:
+        Result.VirtualScanCode := $8D00;
+      PTCKEY_LEFT:
+        Result.VirtualScanCode := $7300;
+      PTCKEY_DOWN:
+        Result.VirtualScanCode := $9100;
+      PTCKEY_RIGHT:
+        Result.VirtualScanCode := $7400;
+      PTCKEY_INSERT:
+        Result.VirtualScanCode := $9200;
+      PTCKEY_HOME:
+        Result.VirtualScanCode := $7700;
+      PTCKEY_PAGEUP:
+        Result.VirtualScanCode := $8400;
+      PTCKEY_DELETE:
+        Result.VirtualScanCode := $9300;
+      PTCKEY_END:
+        Result.VirtualScanCode := $7500;
+      PTCKEY_PAGEDOWN:
+        Result.VirtualScanCode := $7600;
+      PTCKEY_TWO:
+        Result.VirtualScanCode := $0300;
+      PTCKEY_SIX:
+        begin
+          Result.AsciiChar := #30;
+          Result.UnicodeChar := WideChar(30);
+          Result.VirtualScanCode := $071E;
+        end;
+      PTCKEY_MINUS:
+        begin
+          Result.AsciiChar := #31;
+          Result.UnicodeChar := WideChar(31);
+          Result.VirtualScanCode := $0C1F;
+        end;
+      PTCKEY_Q:
+        begin
+          Result.AsciiChar := #17;
+          Result.UnicodeChar := WideChar(17);
+          Result.VirtualScanCode := $1011;
+        end;
+      PTCKEY_W:
+        begin
+          Result.AsciiChar := #23;
+          Result.UnicodeChar := WideChar(23);
+          Result.VirtualScanCode := $1117;
+        end;
+      PTCKEY_E:
+        begin
+          Result.AsciiChar := #5;
+          Result.UnicodeChar := WideChar(5);
+          Result.VirtualScanCode := $1205;
+        end;
+      PTCKEY_R:
+        begin
+          Result.AsciiChar := #18;
+          Result.UnicodeChar := WideChar(18);
+          Result.VirtualScanCode := $1312;
+        end;
+      PTCKEY_T:
+        begin
+          Result.AsciiChar := #20;
+          Result.UnicodeChar := WideChar(20);
+          Result.VirtualScanCode := $1414;
+        end;
+      PTCKEY_Y:
+        begin
+          Result.AsciiChar := #25;
+          Result.UnicodeChar := WideChar(25);
+          Result.VirtualScanCode := $1519;
+        end;
+      PTCKEY_U:
+        begin
+          Result.AsciiChar := #21;
+          Result.UnicodeChar := WideChar(21);
+          Result.VirtualScanCode := $1615;
+        end;
+      PTCKEY_I:
+        begin
+          Result.AsciiChar := #9;
+          Result.UnicodeChar := WideChar(9);
+          Result.VirtualScanCode := $1709;
+        end;
+      PTCKEY_O:
+        begin
+          Result.AsciiChar := #15;
+          Result.UnicodeChar := WideChar(15);
+          Result.VirtualScanCode := $180F;
+        end;
+      PTCKEY_P:
+        begin
+          Result.AsciiChar := #16;
+          Result.UnicodeChar := WideChar(16);
+          Result.VirtualScanCode := $1910;
+        end;
+      PTCKEY_OPENBRACKET:
+        begin
+          Result.AsciiChar := #27;
+          Result.UnicodeChar := WideChar(27);
+          Result.VirtualScanCode := $1A1B;
+        end;
+      PTCKEY_CLOSEBRACKET:
+        begin
+          Result.AsciiChar := #29;
+          Result.UnicodeChar := WideChar(29);
+          Result.VirtualScanCode := $1B1D;
+        end;
+      PTCKEY_BACKSLASH:
+        begin
+          Result.AsciiChar := #28;
+          Result.UnicodeChar := WideChar(28);
+          Result.VirtualScanCode := $2B1C;
+        end;
+      PTCKEY_A:
+        begin
+          Result.AsciiChar := #1;
+          Result.UnicodeChar := WideChar(1);
+          Result.VirtualScanCode := $1E01;
+        end;
+      PTCKEY_S:
+        begin
+          Result.AsciiChar := #19;
+          Result.UnicodeChar := WideChar(19);
+          Result.VirtualScanCode := $1F13;
+        end;
+      PTCKEY_D:
+        begin
+          Result.AsciiChar := #4;
+          Result.UnicodeChar := WideChar(4);
+          Result.VirtualScanCode := $2004;
+        end;
+      PTCKEY_F:
+        begin
+          Result.AsciiChar := #6;
+          Result.UnicodeChar := WideChar(6);
+          Result.VirtualScanCode := $2106;
+        end;
+      PTCKEY_G:
+        begin
+          Result.AsciiChar := #7;
+          Result.UnicodeChar := WideChar(7);
+          Result.VirtualScanCode := $2207;
+        end;
+      PTCKEY_H:
+        begin
+          Result.AsciiChar := #8;
+          Result.UnicodeChar := WideChar(8);
+          Result.VirtualScanCode := $2308;
+        end;
+      PTCKEY_J:
+        begin
+          Result.AsciiChar := #10;
+          Result.UnicodeChar := WideChar(10);
+          Result.VirtualScanCode := $240A;
+        end;
+      PTCKEY_K:
+        begin
+          Result.AsciiChar := #11;
+          Result.UnicodeChar := WideChar(11);
+          Result.VirtualScanCode := $250B;
+        end;
+      PTCKEY_L:
+        begin
+          Result.AsciiChar := #12;
+          Result.UnicodeChar := WideChar(12);
+          Result.VirtualScanCode := $260C;
+        end;
+      PTCKEY_Z:
+        begin
+          Result.AsciiChar := #26;
+          Result.UnicodeChar := WideChar(26);
+          Result.VirtualScanCode := $2C1A;
+        end;
+      PTCKEY_X:
+        begin
+          Result.AsciiChar := #24;
+          Result.UnicodeChar := WideChar(24);
+          Result.VirtualScanCode := $2D18;
+        end;
+      PTCKEY_C:
+        begin
+          Result.AsciiChar := #3;
+          Result.UnicodeChar := WideChar(3);
+          Result.VirtualScanCode := $2E03;
+        end;
+      PTCKEY_V:
+        begin
+          Result.AsciiChar := #22;
+          Result.UnicodeChar := WideChar(22);
+          Result.VirtualScanCode := $2F16;
+        end;
+      PTCKEY_B:
+        begin
+          Result.AsciiChar := #2;
+          Result.UnicodeChar := WideChar(2);
+          Result.VirtualScanCode := $3002;
+        end;
+      PTCKEY_N:
+        begin
+          Result.AsciiChar := #14;
+          Result.UnicodeChar := WideChar(14);
+          Result.VirtualScanCode := $310E;
+        end;
+      PTCKEY_M:
+        begin
+          Result.AsciiChar := #13;
+          Result.UnicodeChar := WideChar(13);
+          Result.VirtualScanCode := $320D;
+        end;
+    end
+  else if ev.shift then
+    case ev.Code of
+      PTCKEY_ESCAPE:
+        begin
+          Result.AsciiChar := #27;
+          Result.UnicodeChar := WideChar(27);
+          Result.VirtualScanCode := $011B;
+        end;
+      PTCKEY_F1:
+        begin
+          Result.VirtualScanCode := $5400;
+          Result.VirtualKeyCode := kbdF1;
+        end;
+      PTCKEY_F2:
+        begin
+          Result.VirtualScanCode := $5500;
+          Result.VirtualKeyCode := kbdF2;
+        end;
+      PTCKEY_F3:
+        begin
+          Result.VirtualScanCode := $5600;
+          Result.VirtualKeyCode := kbdF3;
+        end;
+      PTCKEY_F4:
+        begin
+          Result.VirtualScanCode := $5700;
+          Result.VirtualKeyCode := kbdF4;
+        end;
+      PTCKEY_F5:
+        begin
+          Result.VirtualScanCode := $5800;
+          Result.VirtualKeyCode := kbdF5;
+        end;
+      PTCKEY_F6:
+        begin
+          Result.VirtualScanCode := $5900;
+          Result.VirtualKeyCode := kbdF6;
+        end;
+      PTCKEY_F7:
+        begin
+          Result.VirtualScanCode := $5A00;
+          Result.VirtualKeyCode := kbdF7;
+        end;
+      PTCKEY_F8:
+        begin
+          Result.VirtualScanCode := $5B00;
+          Result.VirtualKeyCode := kbdF8;
+        end;
+      PTCKEY_F9:
+        begin
+          Result.VirtualScanCode := $5C00;
+          Result.VirtualKeyCode := kbdF9;
+        end;
+      PTCKEY_F10:
+        begin
+          Result.VirtualScanCode := $5D00;
+          Result.VirtualKeyCode := kbdF10;
+        end;
+      PTCKEY_F11:
+        begin
+          Result.VirtualScanCode := $8700;
+          Result.VirtualKeyCode := kbdF11;
+        end;
+      PTCKEY_F12:
+        begin
+          Result.VirtualScanCode := $8800;
+          Result.VirtualKeyCode := kbdF12;
+        end;
+      PTCKEY_ENTER:
+        begin
+          Result.AsciiChar := #13;
+          Result.UnicodeChar := WideChar(13);
+          if pmkNumPadKey in ev.ModifierKeys then
+            Result.VirtualScanCode := $E00D
+          else
+            Result.VirtualScanCode := $1C0D;
+        end;
+      PTCKEY_BACKSPACE:
+        begin
+          Result.AsciiChar := #8;
+          Result.UnicodeChar := WideChar(8);
+          Result.VirtualScanCode := $0E08;
+        end;
+      PTCKEY_TAB:
+        Result.VirtualScanCode := $0F00;
+      PTCKEY_UP:
+        begin
+          Result.VirtualScanCode := $4800;
+          Result.VirtualKeyCode := kbdUp;
+        end;
+      PTCKEY_LEFT:
+        begin
+          Result.VirtualScanCode := $4B00;
+          Result.VirtualKeyCode := kbdLeft;
+        end;
+      PTCKEY_DOWN:
+        begin
+          Result.VirtualScanCode := $5000;
+          Result.VirtualKeyCode := kbdDown;
+        end;
+      PTCKEY_RIGHT:
+        begin
+          Result.VirtualScanCode := $4D00;
+          Result.VirtualKeyCode := kbdRight;
+        end;
+      PTCKEY_INSERT:
+        begin
+          Result.VirtualScanCode := $5200;
+          Result.VirtualKeyCode := kbdInsert;
+        end;
+      PTCKEY_HOME:
+        begin
+          Result.VirtualScanCode := $4700;
+          Result.VirtualKeyCode := kbdHome;
+        end;
+      PTCKEY_PAGEUP:
+        begin
+          Result.VirtualScanCode := $4900;
+          Result.VirtualKeyCode := kbdPgUp;
+        end;
+      PTCKEY_DELETE:
+        begin
+          Result.VirtualScanCode := $5300;
+          Result.VirtualKeyCode := kbdDelete;
+        end;
+      PTCKEY_END:
+        begin
+          Result.VirtualScanCode := $4F00;
+          Result.VirtualKeyCode := kbdEnd;
+        end;
+      PTCKEY_PAGEDOWN:
+        begin
+          Result.VirtualScanCode := $5100;
+          Result.VirtualKeyCode := kbdPgDn;
+        end;
+      else
+      begin
+        if (ev.unicode >= 32) and (ev.unicode <= 126) then
+        begin
+          Result.UnicodeChar := WideChar(ev.unicode);
+          Result.AsciiChar := Chr(ev.Unicode);
+        end
+        else if ((ev.unicode >= 128) and (ev.unicode <= $D7FF)) or
+                ((ev.unicode >= $E000) and (ev.unicode <= $FFFF)) then
+        begin
+          Result.UnicodeChar := WideChar(ev.unicode);
+          Result.AsciiChar := '?';
+        end
+        else
+          Result := NilEnhancedKeyEvent;
+      end;
+    end
+  else
+    case ev.Code of
+      PTCKEY_ESCAPE:
+        begin
+          Result.AsciiChar := #27;
+          Result.UnicodeChar := WideChar(27);
+          Result.VirtualScanCode := $011B;
+        end;
+      PTCKEY_F1:
+        begin
+          Result.VirtualScanCode := $3B00;
+          Result.VirtualKeyCode := kbdF1;
+        end;
+      PTCKEY_F2:
+        begin
+          Result.VirtualScanCode := $3C00;
+          Result.VirtualKeyCode := kbdF2;
+        end;
+      PTCKEY_F3:
+        begin
+          Result.VirtualScanCode := $3D00;
+          Result.VirtualKeyCode := kbdF3;
+        end;
+      PTCKEY_F4:
+        begin
+          Result.VirtualScanCode := $3E00;
+          Result.VirtualKeyCode := kbdF4;
+        end;
+      PTCKEY_F5:
+        begin
+          Result.VirtualScanCode := $3F00;
+          Result.VirtualKeyCode := kbdF5;
+        end;
+      PTCKEY_F6:
+        begin
+          Result.VirtualScanCode := $4000;
+          Result.VirtualKeyCode := kbdF6;
+        end;
+      PTCKEY_F7:
+        begin
+          Result.VirtualScanCode := $4100;
+          Result.VirtualKeyCode := kbdF7;
+        end;
+      PTCKEY_F8:
+        begin
+          Result.VirtualScanCode := $4200;
+          Result.VirtualKeyCode := kbdF8;
+        end;
+      PTCKEY_F9:
+        begin
+          Result.VirtualScanCode := $4300;
+          Result.VirtualKeyCode := kbdF9;
+        end;
+      PTCKEY_F10:
+        begin
+          Result.VirtualScanCode := $4400;
+          Result.VirtualKeyCode := kbdF10;
+        end;
+      PTCKEY_F11:
+        begin
+          Result.VirtualScanCode := $8500;
+          Result.VirtualKeyCode := kbdF11;
+        end;
+      PTCKEY_F12:
+        begin
+          Result.VirtualScanCode := $8600;
+          Result.VirtualKeyCode := kbdF12;
+        end;
+      PTCKEY_ENTER:
+        begin
+          Result.AsciiChar := #13;
+          Result.UnicodeChar := WideChar(13);
+          if pmkNumPadKey in ev.ModifierKeys then
+            Result.VirtualScanCode := $E00D
+          else
+            Result.VirtualScanCode := $1C0D;
+        end;
+      PTCKEY_BACKSPACE:
+        begin
+          Result.AsciiChar := #8;
+          Result.UnicodeChar := WideChar(8);
+          Result.VirtualScanCode := $0E08;
+        end;
+      PTCKEY_TAB:
+        begin
+          Result.AsciiChar := #9;
+          Result.UnicodeChar := WideChar(9);
+          Result.VirtualScanCode := $0F09;
+        end;
+      PTCKEY_UP:
+        begin
+          Result.VirtualScanCode := $4800;
+          Result.VirtualKeyCode := kbdUp;
+        end;
+      PTCKEY_LEFT:
+        begin
+          Result.VirtualScanCode := $4B00;
+          Result.VirtualKeyCode := kbdLeft;
+        end;
+      PTCKEY_DOWN:
+        begin
+          Result.VirtualScanCode := $5000;
+          Result.VirtualKeyCode := kbdDown;
+        end;
+      PTCKEY_RIGHT:
+        begin
+          Result.VirtualScanCode := $4D00;
+          Result.VirtualKeyCode := kbdRight;
+        end;
+      PTCKEY_INSERT:
+        begin
+          Result.VirtualScanCode := $5200;
+          Result.VirtualKeyCode := kbdInsert;
+        end;
+      PTCKEY_HOME:
+        begin
+          Result.VirtualScanCode := $4700;
+          Result.VirtualKeyCode := kbdHome;
+        end;
+      PTCKEY_PAGEUP:
+        begin
+          Result.VirtualScanCode := $4900;
+          Result.VirtualKeyCode := kbdPgUp;
+        end;
+      PTCKEY_DELETE:
+        begin
+          Result.VirtualScanCode := $5300;
+          Result.VirtualKeyCode := kbdDelete;
+        end;
+      PTCKEY_END:
+        begin
+          Result.VirtualScanCode := $4F00;
+          Result.VirtualKeyCode := kbdEnd;
+        end;
+      PTCKEY_PAGEDOWN:
+        begin
+          Result.VirtualScanCode := $5100;
+          Result.VirtualKeyCode := kbdPgDn;
+        end;
+      else
+      begin
+        if (ev.unicode >= 32) and (ev.unicode <= 126) then
+        begin
+          Result.UnicodeChar := WideChar(ev.unicode);
+          Result.AsciiChar := Chr(ev.Unicode);
+        end
+        else if ((ev.unicode >= 128) and (ev.unicode <= $D7FF)) or
+                ((ev.unicode >= $E000) and (ev.unicode <= $FFFF)) then
+        begin
+          Result.UnicodeChar := WideChar(ev.unicode);
+          Result.AsciiChar := '?';
+        end
+        else
+          Result := NilEnhancedKeyEvent;
+      end;
+    end;
+
+  (*if ScanCode <> kbNoKey then
+  begin
+    Result.VirtualScanCode := ScanCode shl 8;
+    Result.AsciiChar := #0;
+    Result.UnicodeChar := WideChar(0);
+  end
+  else
+  begin
+    case ev.code of
+      PTCKEY_ENTER:
+        begin
+          Result.VirtualScanCode := $1C0D;
+          Result.AsciiChar := #13;
+          Result.UnicodeChar := WideChar(13);
+        end;
+      PTCKEY_ESCAPE:
+        begin
+          Result.VirtualScanCode := $011B;
+          Result.AsciiChar := #27;
+          Result.UnicodeChar := WideChar(27);
+        end;
+      PTCKEY_TAB:
+        begin
+          Result.VirtualScanCode := $0F09;
+          Result.AsciiChar := #9;
+          Result.UnicodeChar := WideChar(9);
+        end;
+      PTCKEY_BACKSPACE:
+        begin
+          if ev.alt Then
+            Result.VirtualScanCode := $0E00
+          else if ev.control Then
+            Result.VirtualScanCode := $0E7F
+          else
+          begin
+            Result.VirtualScanCode := $0E08;
+            Result.AsciiChar := #8;
+            Result.UnicodeChar := WideChar(8);
+          end;
+        end;
+      else
+      begin
+        if (ev.unicode >= 32) and (ev.unicode <= 126) then
+        begin
+          Result.UnicodeChar := WideChar(ev.unicode);
+          Result.AsciiChar := Chr(ev.Unicode);
+        end
+        else
+          Result := NilEnhancedKeyEvent;
+      end;
+    end;
+  end;
+
+  if ev.Control and (Result.AsciiChar >= '@') and (UpCase(Result.AsciiChar) <= ']') then
+  begin
+    Result.AsciiChar := Chr(Ord(UpCase(Result.AsciiChar)) - Ord('@'));
+    Result.UnicodeChar := WideChar(Result.AsciiChar);
+  end;*)
+
+  if Result <> NilEnhancedKeyEvent then
+    Result.ShiftState := ShiftState;
+end;
+
+procedure SysInitKeyboard;
+begin
+  if InitializedSubsystems = [] then
+    KVMInit;
+  Include(InitializedSubsystems, sKeyboard);
+  LastPolledEventBuffer := NilEnhancedKeyEvent;
+end;
+
+procedure SysDoneKeyboard;
+begin
+  if sKeyboard in InitializedSubsystems then
+  begin
+    Exclude(InitializedSubsystems, sKeyboard);
+    if InitializedSubsystems = [] then
+      KVMDone;
+  end;
+end;
+
+function SysGetShiftState: Byte;
+begin
+  KVMHandleEvents;
+  Result := ShiftState and $f;
+end;
+
+function SysGetEnhancedKeyEvent: TEnhancedKeyEvent;
+begin
+  if LastPolledEventBuffer <> NilEnhancedKeyEvent then
+  begin
+    Result := LastPolledEventBuffer;
+    LastPolledEventBuffer := NilEnhancedKeyEvent;
+    exit;
+  end;
+
+  repeat
+    Result := ConvertPTCKeyEventToTEnhancedKeyEvent(KVMGetKeyEvent);
+  until Result <> NilEnhancedKeyEvent;
+end;
+
+function SysPollEnhancedKeyEvent: TEnhancedKeyEvent;
+begin
+  if LastPolledEventBuffer <> NilEnhancedKeyEvent then
+  begin
+    Result := LastPolledEventBuffer;
+    exit;
+  end;
+
+  LastPolledEventBuffer := ConvertPTCKeyEventToTEnhancedKeyEvent(KVMPollKeyEvent);
+  Result := LastPolledEventBuffer;
+end;
+
+procedure SysInitVideo;
+begin
+  if InitializedSubsystems = [] then
+    KVMInit;
+  Include(InitializedSubsystems, sVideo);
+  ScreenWidth := CurrentWidth;
+  ScreenHeight := CurrentHeight;
+  ScreenColor := True;
+  CursorX := 0;
+  CursorY := 0;
+  CursorType := crUnderLine;
+end;
+
+procedure SysDoneVideo;
+begin
+  if sVideo in InitializedSubsystems then
+  begin
+    Exclude(InitializedSubsystems, sVideo);
+    if InitializedSubsystems = [] then
+      KVMDone;
+  end;
+end;
+
+procedure SysUpdateScreen(Force: Boolean);
+var
+  DoUpdate: Boolean;
+  I: SizeInt;
+begin
+  UpdateBlinkState;
+  if Force then
+    DoUpdate := True
+  else
+  begin
+    DoUpdate := (CursorType <> LastDrawnCursorType) or
+                (CursorBlinkState <> LastDrawnCursorBlinkState) or
+                (FastBlinkTextState <> LastDrawnFastBlinkTextState) or
+                (SlowBlinkTextState <> LastDrawnSlowBlinkTextState) or
+                (CursorX <> LastDrawnCursorX) or
+                (CursorY <> LastDrawnCursorY);
+    if not DoUpdate then
+      for I := Low(EnhancedVideoBuf) to High(EnhancedVideoBuf) do
+        if OldEnhancedVideoBuf[I] <> EnhancedVideoBuf[I] then
+        begin
+          DoUpdate := True;
+          break;
+        end;
+  end;
+
+  if DoUpdate then
+  begin
+    if (Timer.Time - LastFrameTime) >= (1/70) then
+    begin
+      KVMUpdateScreen;
+      LastFrameTime := Timer.Time;
+      for I := Low(EnhancedVideoBuf) to High(EnhancedVideoBuf) do
+        OldEnhancedVideoBuf[I] := EnhancedVideoBuf[I];
+    end;
+  end;
+end;
+
+function SysSetVideoMode(const mode:TVideoMode): Boolean;
+begin
+  SetMode(mode.Col, mode.Row);
+  screenwidth:=mode.col;
+  screenheight:=mode.row;
+  screencolor:=true;
+  Result := True;
+end;
+
+function SysGetVideoModeCount: Word;
+begin
+  Result := 1;
+end;
+
+function SysGetVideoModeData(Index: Word; var Data: TVideoMode): Boolean;
+begin
+  if Index < 1 then
+  begin
+    Data.Col := InitialWidth;
+    Data.Row := InitialHeight;
+    Data.Color := True;
+    Result := True;
+  end
+  else
+    Result := False;
+end;
+
+procedure SysSetCursorPos(NewCursorX, NewCursorY: Word);
+begin
+  if (NewCursorX <> CursorX) or (NewCursorY <> CursorY) then
+  begin
+    CursorX := NewCursorX;
+    CursorY := NewCursorY;
+    SysUpdateScreen(True);
+  end;
+end;
+
+function SysGetCursorType: Word;
+begin
+  Result := CursorType;
+end;
+
+procedure SysSetCursorType(NewType: Word);
+begin
+  if CursorType = NewType then
+    exit;
+  CursorType := NewType;
+  SysUpdateScreen(True);
+end;
+
+function SysGetCapabilities: Word;
+begin
+  SysGetCapabilities:=cpUnderLine + cpBlink + cpColor;
+end;
+
+procedure SysInitMouse;
+begin
+  if InitializedSubsystems = [] then
+    KVMInit;
+  Include(InitializedSubsystems, sMouse);
+end;
+
+procedure SysDoneMouse;
+begin
+  if sMouse in InitializedSubsystems then
+  begin
+    Exclude(InitializedSubsystems, sMouse);
+    if InitializedSubsystems = [] then
+      KVMDone;
+  end;
+end;
+
+function SysDetectMouse: byte;
+begin
+  Result := 3;
+end;
+
+procedure SysShowMouse;
+begin
+  {todo...}
+end;
+
+procedure SysHideMouse;
+begin
+  {todo...}
+end;
+
+function SysGetMouseX: word;
+begin
+  KVMHandleEvents;
+  Result := MouseX;
+end;
+
+function SysGetMouseY: word;
+begin
+  KVMHandleEvents;
+  Result := MouseY;
+end;
+
+function SysGetMouseButtons: word;
+begin
+  KVMHandleEvents;
+  Result := MouseButtons;
+end;
+
+procedure SysGetMouseEvent(var MouseEvent: TMouseEvent);
+var
+  NewMouseX, NewMouseY, NewMouseButtons: Integer;
+begin
+  FillChar(MouseEvent, SizeOf(MouseEvent), 0);
+end;
+
+function SysPollMouseEvent(var MouseEvent: TMouseEvent): Boolean;
+begin
+  KVMHandleEvents;
+  Result := False;
+  FillChar(MouseEvent, SizeOf(MouseEvent), 0);
+end;
+
+function CheckPendingResize(out NewMode: TVideoMode): Boolean;
+begin
+  if PendingResize <> nil then
+  begin
+    GetVideoMode(NewMode);
+    NewMode.Col := PendingResize.Width div CurrentFont.Width;
+    NewMode.Row := PendingResize.Height div CurrentFont.Height;
+    Result := True;
+  end
+  else
+  begin
+    FillChar(NewMode, SizeOf(NewMode), 0);
+    Result := False;
+  end;
+end;
+
+procedure StartBlinkingCursor;
+begin
+  CursorBlinkingEnabled := True;
+end;
+
+procedure StopBlinkingCursor;
+begin
+  CursorBlinkingEnabled := False;
+  CursorBlinkState := True;
+  UpdateScreen(False);
+end;
+
+const
+  SysKeyboardDriver : TKeyboardDriver = (
+    InitDriver : @SysInitKeyBoard;
+    DoneDriver : @SysDoneKeyBoard;
+    GetKeyevent : Nil;
+    PollKeyEvent : Nil;
+    GetShiftState : @SysGetShiftState;
+    TranslateKeyEvent : Nil;
+    TranslateKeyEventUnicode : Nil;
+    GetEnhancedKeyEvent : @SysGetEnhancedKeyEvent;
+    PollEnhancedKeyEvent : @SysPollEnhancedKeyEvent;
+  );
+  SysVideoDriver : TVideoDriver = (
+    InitDriver : nil;
+    InitEnhancedDriver: @SysInitVideo;
+    DoneDriver : @SysDoneVideo;
+    UpdateScreen : @SysUpdateScreen;
+    UpdateScreenArea : Nil;
+    ClearScreen : Nil;
+    SetVideoMode : @SysSetVideoMode;
+    GetVideoModeCount : @SysGetVideoModeCount;
+    GetVideoModeData : @SysGetVideoModeData;
+    SetCursorPos : @SysSetCursorPos;
+    GetCursorType : @SysGetCursorType;
+    SetCursorType : @SysSetCursorType;
+    GetCapabilities : @SysGetCapabilities;
+    GetActiveCodePage : Nil;
+    ActivateCodePage : Nil;
+    GetSupportedCodePageCount : Nil;
+    GetSupportedCodePage : Nil;
+  );
+  SysMouseDriver : TMouseDriver = (
+    UseDefaultQueue : true;
+    InitDriver      : @SysInitMouse;
+    DoneDriver      : @SysDoneMouse;
+    DetectMouse     : @SysDetectMouse;
+    ShowMouse       : @SysShowMouse;
+    HideMouse       : @SysHideMouse;
+    GetMouseX       : @SysGetMouseX;
+    GetMouseY       : @SysGetMouseY;
+    GetMouseButtons : @SysGetMouseButtons;
+    SetMouseXY      : Nil;
+    GetMouseEvent   : @SysGetMouseEvent;
+    PollMouseEvent  : @SysPollMouseEvent;
+    PutMouseEvent   : Nil;
+  );
+
+procedure RegisterPtcKvmDrivers;
+begin
+  SetKeyboardDriver(SysKeyboardDriver);
+  SetVideoDriver(SysVideoDriver);
+  SetMouseDriver(SysMouseDriver);
+end;
+
+begin
+  InitializedSubsystems := [];
+end.
+