Browse Source

+ added unit System.Terminal

Nikolay Nikolov 9 months ago
parent
commit
4e877c3c11
2 changed files with 590 additions and 0 deletions
  1. 12 0
      packages/fcl-fpterm/fpmake.pp
  2. 578 0
      packages/fcl-fpterm/src/system.terminal.pas

+ 12 - 0
packages/fcl-fpterm/fpmake.pp

@@ -66,6 +66,18 @@ begin
         AddUnit('system.terminal.base');
       end;
 
+    T:=P.Targets.AddUnit('system.terminal.pas');
+    with T.Dependencies do
+      begin
+        AddUnit('system.terminal.base');
+        AddUnit('system.terminal.view');
+        AddUnit('system.terminal.model');
+        AddUnit('system.terminal.controller');
+        AddUnit('system.terminal.inputoutputconnection');
+        AddUnit('system.terminal.pointingdeviceinput');
+        AddUnit('system.terminal.keyboardinput');
+      end;
+
     //P.NamespaceMap:='namespaces.lst';
 
 {$ifndef ALLPACKAGES}

+ 578 - 0
packages/fcl-fpterm/src/system.terminal.pas

@@ -0,0 +1,578 @@
+{ This file is part of fpterm - a terminal emulator, written in Free Pascal
+
+  Copyright (C) 2022, 2024 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 System.Terminal;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+{$IFDEF FPC_DOTTEDUNITS}
+  System.SysUtils,
+{$ELSE FPC_DOTTEDUNITS}
+  SysUtils,
+{$ENDIF FPC_DOTTEDUNITS}
+  System.Terminal.Base,
+  System.Terminal.View,
+  System.Terminal.Model,
+  System.Terminal.Controller,
+  System.Terminal.InputOutputConnection,
+  System.Terminal.PointingDeviceInput,
+  System.Terminal.KeyboardInput;
+
+type
+
+  { TTerminal }
+
+  TTerminal = class
+  private
+    FView: TTerminalView;
+    FModel: TTerminalModel;
+    FController: TTerminalController;
+    FInputOutputConnection: ITerminalInputOutputConnection;
+    FKeyboardInput: TTerminalKeyboardInput;
+    FPointingDeviceInput: TTerminalPointingDeviceInput;
+
+    function GetHeight: Integer;
+    function GetWidth: Integer;
+    procedure TransmitData(const buf; Bytes: SizeUInt);
+    procedure ControllerOnResize(NewWidth, NewHeight: Integer);
+
+    procedure HandlePointingDevice;
+    procedure HandleKeyboard;
+    procedure HandleInputOutput(out TimeToQuit: Boolean);
+  public
+    constructor Create(AView: TTerminalView; AKeyboardInput: TTerminalKeyboardInput;
+                       APointingDeviceInput: TTerminalPointingDeviceInput);
+    destructor Destroy; override;
+
+    procedure Update(out TimeToQuit: Boolean);
+    procedure Run;
+
+    property InputOutputConnection: ITerminalInputOutputConnection read FInputOutputConnection write FInputOutputConnection;
+
+    property Width: Integer read GetWidth;
+    property Height: Integer read GetHeight;
+  end;
+
+implementation
+
+{ TTerminal }
+
+procedure TTerminal.TransmitData(const buf; Bytes: SizeUInt);
+begin
+  if Assigned(FInputOutputConnection) and (Bytes > 0) then
+    FInputOutputConnection.Write(buf, Bytes);
+end;
+
+function TTerminal.GetHeight: Integer;
+begin
+  Result := FModel.Height;
+end;
+
+function TTerminal.GetWidth: Integer;
+begin
+  Result := FModel.Width;
+end;
+
+procedure TTerminal.ControllerOnResize(NewWidth, NewHeight: Integer);
+begin
+  if Assigned(FInputOutputConnection) then
+    FInputOutputConnection.Resize(NewWidth, NewHeight);
+end;
+
+procedure TTerminal.HandlePointingDevice;
+var
+  pde: TPointingDeviceEvent;
+begin
+  if FPointingDeviceInput.EventAvailable then
+  begin
+    FPointingDeviceInput.GetEvent(pde);
+    FController.HandleMouseEvent(pde);
+  end;
+end;
+
+procedure TTerminal.HandleKeyboard;
+var
+  k: TKeyEvent;
+  ks: rawbytestring;
+  UTF16_HighSurrogate: WideChar = #0;
+begin
+  if FKeyboardInput.EventAvailable then
+  begin
+    FKeyboardInput.GetEvent(k);
+    if not (tmfKeyboardActionMode in FController.ModeFlags) then
+    begin
+      ks := '';
+      if k.UnicodeChar <> WideChar(0) then
+      begin
+        if (k.UnicodeChar >= WideChar($D800)) and (k.UnicodeChar <= WideChar($DFFF)) then
+        begin
+          if (k.UnicodeChar >= WideChar($D800)) and (k.UnicodeChar <= WideChar($DBFF)) then
+            { k.UnicodeChar is a high surrogate, save it }
+            UTF16_HighSurrogate := k.UnicodeChar
+          else
+          begin
+            { k.UnicodeChar is a low surrogate }
+            { if the previous character was a valid high surrogate, combine them to obtain a non-BMP code point }
+            if (UTF16_HighSurrogate >= WideChar($D800)) and (UTF16_HighSurrogate <= WideChar($DBFF)) then
+              ks := UTF8Encode(UTF16_HighSurrogate + k.UnicodeChar);
+            UTF16_HighSurrogate := #0;
+          end;
+        end
+        else
+        begin
+          ks := UTF8Encode(k.UnicodeChar);
+          if ks = #13 then
+          begin
+            if tmfAutoNewLine in FController.ModeFlags then
+              ks := #13#10;
+          end;
+        end;
+      end
+      else
+      begin
+        UTF16_HighSurrogate := #0;
+        case k.VirtualScanCode of
+          $3B00:  { F1 }
+            ks := FController.EncodeReturnC1(C1_SS3) + 'P';
+          $3C00:  { F2 }
+            ks := FController.EncodeReturnC1(C1_SS3) + 'Q';
+          $3D00:  { F3 }
+            ks := FController.EncodeReturnC1(C1_SS3) + 'R';
+          $3E00:  { F4 }
+            ks := FController.EncodeReturnC1(C1_SS3) + 'S';
+          $3F00:  { F5 }
+            ks := FController.EncodeReturnC1(C1_CSI) + '15~';
+          $4000:  { F6 }
+            ks := FController.EncodeReturnC1(C1_CSI) + '17~';
+          $4100:  { F7 }
+            ks := FController.EncodeReturnC1(C1_CSI) + '18~';
+          $4200:  { F8 }
+            ks := FController.EncodeReturnC1(C1_CSI) + '19~';
+          $4300:  { F9 }
+            ks := FController.EncodeReturnC1(C1_CSI) + '20~';
+          $4400:  { F10 }
+            ks := FController.EncodeReturnC1(C1_CSI) + '21~';
+          $8500:  { F11 }
+            ks := FController.EncodeReturnC1(C1_CSI) + '23~';
+          $8600:  { F12 }
+            ks := FController.EncodeReturnC1(C1_CSI) + '24~';
+          $5400:  { Shift-F1 }
+            ks := FController.EncodeReturnC1(C1_CSI) + '1;2P';
+          $5500:  { Shift-F2 }
+            ks := FController.EncodeReturnC1(C1_CSI) + '1;2Q';
+          $5600:  { Shift-F3 }
+            ks := FController.EncodeReturnC1(C1_CSI) + '1;2R';
+          $5700:  { Shift-F4 }
+            ks := FController.EncodeReturnC1(C1_CSI) + '1;2S';
+          $5800:  { Shift-F5 }
+            ks := FController.EncodeReturnC1(C1_CSI) + '15;2~';
+          $5900:  { Shift-F6 }
+            ks := FController.EncodeReturnC1(C1_CSI) + '17;2~';
+          $5A00:  { Shift-F7 }
+            ks := FController.EncodeReturnC1(C1_CSI) + '18;2~';
+          $5B00:  { Shift-F8 }
+            ks := FController.EncodeReturnC1(C1_CSI) + '19;2~';
+          $5C00:  { Shift-F9 }
+            ks := FController.EncodeReturnC1(C1_CSI) + '20;2~';
+          $5D00:  { Shift-F10 }
+            ks := FController.EncodeReturnC1(C1_CSI) + '21;2~';
+          $8700:  { Shift-F11 }
+            ks := FController.EncodeReturnC1(C1_CSI) + '23;2~';
+          $8800:  { Shift-F12 }
+            ks := FController.EncodeReturnC1(C1_CSI) + '24;2~';
+          $5E00:  { Ctrl-F1 }
+            if ssShift in k.ShiftState then
+              ks := FController.EncodeReturnC1(C1_CSI) + '1;6P'
+            else
+              ks := FController.EncodeReturnC1(C1_CSI) + '1;5P';
+          $5F00:  { Ctrl-F2 }
+            if ssShift in k.ShiftState then
+              ks := FController.EncodeReturnC1(C1_CSI) + '1;6Q'
+            else
+              ks := FController.EncodeReturnC1(C1_CSI) + '1;5Q';
+          $6000:  { Ctrl-F3 }
+            if ssShift in k.ShiftState then
+              ks := FController.EncodeReturnC1(C1_CSI) + '1;6R'
+            else
+              ks := FController.EncodeReturnC1(C1_CSI) + '1;5R';
+          $6100:  { Ctrl-F4 }
+            if ssShift in k.ShiftState then
+              ks := FController.EncodeReturnC1(C1_CSI) + '1;6S'
+            else
+              ks := FController.EncodeReturnC1(C1_CSI) + '1;5S';
+          $6200:  { Ctrl-F5 }
+            if ssShift in k.ShiftState then
+              ks := FController.EncodeReturnC1(C1_CSI) + '15;6~'
+            else
+              ks := FController.EncodeReturnC1(C1_CSI) + '15;5~';
+          $6300:  { Ctrl-F6 }
+            if ssShift in k.ShiftState then
+              ks := FController.EncodeReturnC1(C1_CSI) + '17;6~'
+            else
+              ks := FController.EncodeReturnC1(C1_CSI) + '17;5~';
+          $6400:  { Ctrl-F7 }
+            if ssShift in k.ShiftState then
+              ks := FController.EncodeReturnC1(C1_CSI) + '18;6~'
+            else
+              ks := FController.EncodeReturnC1(C1_CSI) + '18;5~';
+          $6500:  { Ctrl-F8 }
+            if ssShift in k.ShiftState then
+              ks := FController.EncodeReturnC1(C1_CSI) + '19;6~'
+            else
+              ks := FController.EncodeReturnC1(C1_CSI) + '19;5~';
+          $6600:  { Ctrl-F9 }
+            if ssShift in k.ShiftState then
+              ks := FController.EncodeReturnC1(C1_CSI) + '20;6~'
+            else
+              ks := FController.EncodeReturnC1(C1_CSI) + '20;5~';
+          $6700:  { Ctrl-F10 }
+            if ssShift in k.ShiftState then
+              ks := FController.EncodeReturnC1(C1_CSI) + '21;6~'
+            else
+              ks := FController.EncodeReturnC1(C1_CSI) + '21;5~';
+          $8900:  { Ctrl-F11 }
+            if ssShift in k.ShiftState then
+              ks := FController.EncodeReturnC1(C1_CSI) + '23;6~'
+            else
+              ks := FController.EncodeReturnC1(C1_CSI) + '23;5~';
+          $8A00:  { Ctrl-F12 }
+            if ssShift in k.ShiftState then
+              ks := FController.EncodeReturnC1(C1_CSI) + '24;6~'
+            else
+              ks := FController.EncodeReturnC1(C1_CSI) + '24;5~';
+          $6800:  { Alt-F1 }
+            ks := FController.EncodeReturnC1(C1_CSI) + '1;3P';
+          $6900:  { Alt-F2 }
+            ks := FController.EncodeReturnC1(C1_CSI) + '1;3Q';
+          $6A00:  { Alt-F3 }
+            ks := FController.EncodeReturnC1(C1_CSI) + '1;3R';
+          $6B00:  { Alt-F4 }
+            ks := FController.EncodeReturnC1(C1_CSI) + '1;3S';
+          $6C00:  { Alt-F5 }
+            ks := FController.EncodeReturnC1(C1_CSI) + '15;3~';
+          $6D00:  { Alt-F6 }
+            ks := FController.EncodeReturnC1(C1_CSI) + '17;3~';
+          $6E00:  { Alt-F7 }
+            ks := FController.EncodeReturnC1(C1_CSI) + '18;3~';
+          $6F00:  { Alt-F8 }
+            ks := FController.EncodeReturnC1(C1_CSI) + '19;3~';
+          $7000:  { Alt-F9 }
+            ks := FController.EncodeReturnC1(C1_CSI) + '20;3~';
+          $7100:  { Alt-F10 }
+            ks := FController.EncodeReturnC1(C1_CSI) + '21;3~';
+          $8B00:  { Alt-F11 }
+            ks := FController.EncodeReturnC1(C1_CSI) + '23;3~';
+          $8C00:  { Alt-F12 }
+            ks := FController.EncodeReturnC1(C1_CSI) + '24;3~';
+          $4800:  { Up Arrow }
+            if ssShift in k.ShiftState then
+              ks := FController.EncodeReturnC1(C1_CSI) + '1;2A'
+            else if tmfCursorKeysSendApplicationSequences in FController.ModeFlags then
+              ks := FController.EncodeReturnC1(C1_SS3) + 'A'
+            else
+              ks := FController.EncodeReturnC1(C1_CSI) + 'A';
+          $5000:  { Down Arrow }
+            if ssShift in k.ShiftState then
+              ks := FController.EncodeReturnC1(C1_CSI) + '1;2B'
+            else if tmfCursorKeysSendApplicationSequences in FController.ModeFlags then
+              ks := FController.EncodeReturnC1(C1_SS3) + 'B'
+            else
+              ks := FController.EncodeReturnC1(C1_CSI) + 'B';
+          $4D00:  { Right Arrow }
+            if ssShift in k.ShiftState then
+              ks := FController.EncodeReturnC1(C1_CSI) + '1;2C'
+            else if tmfCursorKeysSendApplicationSequences in FController.ModeFlags then
+              ks := FController.EncodeReturnC1(C1_SS3) + 'C'
+            else
+              ks := FController.EncodeReturnC1(C1_CSI) + 'C';
+          $4B00:  { Left Arrow }
+            if ssShift in k.ShiftState then
+              ks := FController.EncodeReturnC1(C1_CSI) + '1;2D'
+            else if tmfCursorKeysSendApplicationSequences in FController.ModeFlags then
+              ks := FController.EncodeReturnC1(C1_SS3) + 'D'
+            else
+              ks := FController.EncodeReturnC1(C1_CSI) + 'D';
+          $8D00:  { Ctrl-Up Arrow }
+            if ssShift in k.ShiftState then
+              ks := FController.EncodeReturnC1(C1_CSI) + '1;6A'
+            else
+              ks := FController.EncodeReturnC1(C1_CSI) + '1;5A';
+          $9100:  { Ctrl-Down Arrow }
+            if ssShift in k.ShiftState then
+              ks := FController.EncodeReturnC1(C1_CSI) + '1;6B'
+            else
+              ks := FController.EncodeReturnC1(C1_CSI) + '1;5B';
+          $7400:  { Ctrl-Right Arrow }
+            if ssShift in k.ShiftState then
+              ks := FController.EncodeReturnC1(C1_CSI) + '1;6C'
+            else
+              ks := FController.EncodeReturnC1(C1_CSI) + '1;5C';
+          $7300:  { Ctrl-Left Arrow }
+            if ssShift in k.ShiftState then
+              ks := FController.EncodeReturnC1(C1_CSI) + '1;6D'
+            else
+              ks := FController.EncodeReturnC1(C1_CSI) + '1;5D';
+          $4700:  { Home }
+            if ssShift in k.ShiftState then
+              ks := FController.EncodeReturnC1(C1_CSI) + '1;2H'
+            else if tmfCursorKeysSendApplicationSequences in FController.ModeFlags then
+              ks := FController.EncodeReturnC1(C1_SS3) + 'H'
+            else
+              ks := FController.EncodeReturnC1(C1_CSI) + 'H';
+          $4F00:  { End }
+            if ssShift in k.ShiftState then
+              ks := FController.EncodeReturnC1(C1_CSI) + '1;2F'
+            else if tmfCursorKeysSendApplicationSequences in FController.ModeFlags then
+              ks := FController.EncodeReturnC1(C1_SS3) + 'F'
+            else
+              ks := FController.EncodeReturnC1(C1_CSI) + 'F';
+          $5200:  { Insert }
+            if ssShift in k.ShiftState then
+              ks := FController.EncodeReturnC1(C1_CSI) + '2;2~'
+            else
+              ks := FController.EncodeReturnC1(C1_CSI) + '2~';
+          $5300:  { Delete }
+            if ssShift in k.ShiftState then
+              ks := FController.EncodeReturnC1(C1_CSI) + '3;2~'
+            else
+              ks := FController.EncodeReturnC1(C1_CSI) + '3~';
+          $4900:  { Page Up }
+            if ssShift in k.ShiftState then
+              ks := FController.EncodeReturnC1(C1_CSI) + '5;2~'
+            else
+              ks := FController.EncodeReturnC1(C1_CSI) + '5~';
+          $5100:  { Page Down }
+            if ssShift in k.ShiftState then
+              ks := FController.EncodeReturnC1(C1_CSI) + '6;2~'
+            else
+              ks := FController.EncodeReturnC1(C1_CSI) + '6~';
+          $7700:  { Ctrl-Home }
+            if ssShift in k.ShiftState then
+              ks := FController.EncodeReturnC1(C1_CSI) + '1;6H'
+            else
+              ks := FController.EncodeReturnC1(C1_CSI) + '1;5H';
+          $7500:  { Ctrl-End }
+            if ssShift in k.ShiftState then
+              ks := FController.EncodeReturnC1(C1_CSI) + '1;6F'
+            else
+              ks := FController.EncodeReturnC1(C1_CSI) + '1;5F';
+          $9200:  { Ctrl-Insert }
+            if ssShift in k.ShiftState then
+              ks := FController.EncodeReturnC1(C1_CSI) + '2;6~'
+            else
+              ks := FController.EncodeReturnC1(C1_CSI) + '2;5~';
+          $9300:  { Ctrl-Delete }
+            if ssShift in k.ShiftState then
+              ks := FController.EncodeReturnC1(C1_CSI) + '3;6~'
+            else
+              ks := FController.EncodeReturnC1(C1_CSI) + '3;5~';
+          $8400:  { Ctrl-Page Up }
+            if ssShift in k.ShiftState then
+              ks := FController.EncodeReturnC1(C1_CSI) + '5;6~'
+            else
+              ks := FController.EncodeReturnC1(C1_CSI) + '5;5~';
+          $7600:  { Ctrl-Page Down }
+            if ssShift in k.ShiftState then
+              ks := FController.EncodeReturnC1(C1_CSI) + '6;6~'
+            else
+              ks := FController.EncodeReturnC1(C1_CSI) + '6;5~';
+          $0F00:  { Shift-Tab }
+            ks := FController.EncodeReturnC1(C1_CSI) + 'Z';
+          $2900:  { Alt-` }
+            ks := #27'`';
+          $7800:  { Alt-1 }
+            ks := #27'1';
+          $7900:  { Alt-2 }
+            ks := #27'2';
+          $7A00:  { Alt-3 }
+            ks := #27'3';
+          $7B00:  { Alt-4 }
+            ks := #27'4';
+          $7C00:  { Alt-5 }
+            ks := #27'5';
+          $7D00:  { Alt-6 }
+            ks := #27'6';
+          $7E00:  { Alt-7 }
+            ks := #27'7';
+          $7F00:  { Alt-8 }
+            ks := #27'8';
+          $8000:  { Alt-9 }
+            ks := #27'9';
+          $8100:  { Alt-0 }
+            ks := #27'0';
+          $8200:  { Alt-- }
+            ks := #27'-';
+          $8300:  { Alt-= }
+            ks := #27'=';
+          $1000:  { Alt-Q }
+            ks := #27'q';
+          $1100:  { Alt-W }
+            ks := #27'w';
+          $1200:  { Alt-E }
+            ks := #27'e';
+          $1300:  { Alt-R }
+            ks := #27'r';
+          $1400:  { Alt-T }
+            ks := #27't';
+          $1500:  { Alt-Y }
+            ks := #27'y';
+          $1600:  { Alt-U }
+            ks := #27'u';
+          $1700:  { Alt-I }
+            ks := #27'i';
+          $1800:  { Alt-O }
+            ks := #27'o';
+          $1900:  { Alt-P }
+            ks := #27'p';
+          $1A00:  { Alt-[ }
+            ks := #27'[';
+          $1B00:  { Alt-] }
+            ks := #27']';
+          $2B00:  { Alt-\ }
+            ks := #27'\';
+          $1E00:  { Alt-A }
+            ks := #27'a';
+          $1F00:  { Alt-S }
+            ks := #27's';
+          $2000:  { Alt-D }
+            ks := #27'd';
+          $2100:  { Alt-F }
+            ks := #27'f';
+          $2200:  { Alt-G }
+            ks := #27'g';
+          $2300:  { Alt-H }
+            ks := #27'h';
+          $2400:  { Alt-J }
+            ks := #27'j';
+          $2500:  { Alt-K }
+            ks := #27'k';
+          $2600:  { Alt-L }
+            ks := #27'l';
+          $2700:  { Alt-; }
+            ks := #27';';
+          $2800:  { Alt-' }
+            ks := #27'''';
+          $2C00:  { Alt-Z }
+            ks := #27'z';
+          $2D00:  { Alt-X }
+            ks := #27'x';
+          $2E00:  { Alt-C }
+            ks := #27'c';
+          $2F00:  { Alt-V }
+            ks := #27'v';
+          $3000:  { Alt-B }
+            ks := #27'b';
+          $3100:  { Alt-N }
+            ks := #27'n';
+          $3200:  { Alt-M }
+            ks := #27'm';
+          $3300:  { Alt-, }
+            ks := #27',';
+          $3400:  { Alt-. }
+            ks := #27'.';
+          $3500:  { Alt-/ }
+            ks := #27'/';
+        end;
+      end;
+      if ks <> '' then
+      begin
+        FController.MaybeLocalEcho(ks);
+        TransmitData(ks[1], Length(ks));
+      end;
+    end;
+  end;
+end;
+
+procedure TTerminal.HandleInputOutput(out TimeToQuit: Boolean);
+var
+  buf: array [0..1023] of Char;
+  bytes_read: SizeInt;
+begin
+  if Assigned(FInputOutputConnection) then
+  begin
+    TimeToQuit := FInputOutputConnection.Closed;
+    if FInputOutputConnection.DataAvailable then
+    begin
+      bytes_read := FInputOutputConnection.Read(buf, SizeOf(buf));
+      if bytes_read > 0 then
+        FController.ReceiveData(buf, bytes_read);
+    end;
+  end
+  else
+    TimeToQuit := False;
+end;
+
+constructor TTerminal.Create(AView: TTerminalView;
+  AKeyboardInput: TTerminalKeyboardInput;
+  APointingDeviceInput: TTerminalPointingDeviceInput);
+begin
+  FView := AView;
+  FKeyboardInput := AKeyboardInput;
+  FPointingDeviceInput := APointingDeviceInput;
+  FModel := TTerminalModel.Create(FView);
+  FController := TTerminalController.Create(FModel, ttVT420);
+  FController.OnTransmitData := @TransmitData;
+  FController.OnResize := @ControllerOnResize;
+end;
+
+destructor TTerminal.Destroy;
+begin
+  FreeAndNil(FController);
+  FreeAndNil(FModel);
+  inherited Destroy;
+end;
+
+procedure TTerminal.Update(out TimeToQuit: Boolean);
+var
+  NewWidth, NewHeight: Integer;
+begin
+  FView.IdleLoop;
+  if FView.CheckPendingResize(NewWidth, NewHeight) then
+    FController.Resize(NewWidth, NewHeight);
+  HandlePointingDevice;
+  HandleKeyboard;
+  HandleInputOutput(TimeToQuit);
+end;
+
+procedure TTerminal.Run;
+var
+  Done: Boolean;
+begin
+  Done := False;
+  repeat
+    Update(Done);
+  until Done;
+end;
+
+end.
+