vtemuctl.pas 59 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192
  1. {
  2. Double Commander
  3. -------------------------------------------------------------------------
  4. Virtual terminal emulator control
  5. Alexander Koblov, 2021-2022
  6. Based on ComPort Library
  7. https://sourceforge.net/projects/comport
  8. Author:
  9. Dejan Crnila, 1998 - 2002
  10. Maintainers:
  11. Lars B. Dybdahl, 2003
  12. Brian Gochnauer, 2010
  13. License:
  14. Public Domain
  15. }
  16. unit VTEmuCtl;
  17. {$mode delphi}
  18. {$pointermath on}
  19. interface
  20. uses
  21. LCLType, Classes, Controls, StdCtrls, ExtCtrls, Forms, Messages, Graphics,
  22. VTEmuEsc, LCLIntf, Types, LazUtf8, LMessages;
  23. type
  24. TOnRxBuf = procedure(Sender: TObject; const Buffer; Count: Integer) of object;
  25. { TCustomPtyDevice }
  26. TCustomPtyDevice = class(TComponent)
  27. protected
  28. FOnRxBuf: TOnRxBuf;
  29. FConnected: Boolean;
  30. protected
  31. procedure SetConnected(AValue: Boolean); virtual; abstract;
  32. public
  33. function WriteStr(const Str: string): Integer; virtual; abstract;
  34. function SetCurrentDir(const Path: String): Boolean; virtual; abstract;
  35. function SetScreenSize(aCols, aRows: Integer): Boolean; virtual; abstract;
  36. property OnRxBuf: TOnRxBuf read FOnRxBuf write FOnRxBuf;
  37. property Connected: Boolean read FConnected write SetConnected default False;
  38. end;
  39. TCustomComTerminal = class; // forward declaration
  40. // terminal character
  41. PComTermChar = ^TComTermChar;
  42. TComTermChar = record
  43. Ch: TUTF8Char;
  44. FrontColor: TColor;
  45. BackColor: TColor;
  46. Underline: Boolean;
  47. Bold: Boolean;
  48. end;
  49. // buffer which holds terminal screen data
  50. TComTermBuffer = class
  51. private
  52. FBuffer: PByte;
  53. FTabs: Pointer;
  54. FTopLeft: TPoint;
  55. FCaretPos: TPoint;
  56. FScrollRange: TRect;
  57. FOwner: TCustomComTerminal;
  58. strict private
  59. FRows: Integer;
  60. FColumns: Integer;
  61. public
  62. constructor Create(AOwner: TCustomComTerminal);
  63. destructor Destroy; override;
  64. procedure Init(ARows, AColumns: Integer);
  65. procedure SetChar(Column, Row: Integer; TermChar: TComTermChar);
  66. function GetChar(Column, Row: Integer): TComTermChar;
  67. procedure SetTab(Column: Integer; Put: Boolean);
  68. function GetTab(Column: Integer): Boolean;
  69. function NextTab(Column: Integer): Integer;
  70. procedure ClearAllTabs;
  71. procedure ScrollDown;
  72. procedure ScrollUp;
  73. procedure EraseScreenLeft(Column, Row: Integer);
  74. procedure EraseScreenRight(Column, Row: Integer);
  75. procedure EraseLineLeft(Column, Row: Integer);
  76. procedure EraseLineRight(Column, Row: Integer);
  77. procedure EraseChar(Column, Row, Count: Integer);
  78. procedure DeleteChar(Column, Row, Count: Integer);
  79. procedure DeleteLine(Row, Count: Integer);
  80. procedure InsertLine(Row, Count: Integer);
  81. function GetLineLength(Line: Integer): Integer;
  82. function GetLastLine: Integer;
  83. property Rows: Integer read FRows;
  84. property Columns: Integer read FColumns;
  85. end;
  86. // terminal types
  87. TTermEmulation = (teVT100orANSI, teVT52, teNone);
  88. TTermCaret = (tcBlock, tcUnderline, tcNone);
  89. TAdvanceCaret = (acChar, acReturn, acLineFeed, acReverseLineFeed,
  90. acTab, acBackspace, acPage);
  91. TArrowKeys = (akTerminal, akWindows);
  92. TTermAttributes = record
  93. FrontColor: TColor;
  94. BackColor: TColor;
  95. Invert: Boolean;
  96. Bold: Boolean;
  97. Underline: Boolean;
  98. end;
  99. TTermMode = record
  100. Keys: TArrowKeys;
  101. CharSet: Boolean;
  102. MouseMode: Boolean;
  103. MouseTrack: Boolean;
  104. end;
  105. TEscapeEvent = procedure(Sender: TObject; var EscapeCodes: TEscapeCodes) of object;
  106. TUnhandledEvent = procedure(Sender: TObject; Code: TEscapeCode; Data: string) of object;
  107. TUnhandledModeEvent = procedure(Sender: TObject; const Data: string; OnOff: Boolean) of object;
  108. TStrRecvEvent = procedure(Sender: TObject; var Str: string) of object;
  109. TChScreenEvent = procedure(Sender: TObject; Ch: TUTF8Char) of object;
  110. // communication terminal control
  111. { TCustomComTerminal }
  112. TCustomComTerminal = class(TCustomControl)
  113. private
  114. FPtyDevice: TCustomPtyDevice;
  115. FScrollBars: TScrollStyle;
  116. FArrowKeys: TArrowKeys;
  117. FWantTab: Boolean;
  118. FColumns: Integer;
  119. FRows: Integer;
  120. FVisibleRows: Integer;
  121. FLocalEcho: Boolean;
  122. FSendLF: Boolean;
  123. FAppendLF: Boolean;
  124. FForce7Bit: Boolean;
  125. FWrapLines: Boolean;
  126. FSmoothScroll: Boolean;
  127. FAutoFollow : Boolean;
  128. FFontHeight: Integer;
  129. FFontWidth: Integer;
  130. FPartChar: TUTF8Char;
  131. FEmulation: TTermEmulation;
  132. FCaret: TTermCaret;
  133. FCaretPos: TPoint;
  134. FSaveCaret: TPoint;
  135. FCaretCreated: Boolean;
  136. FTopLeft: TPoint;
  137. FCaretHeight: Integer;
  138. FSaveAttr: TTermAttributes;
  139. FBuffer: TComTermBuffer;
  140. FMainBuffer: TComTermBuffer;
  141. FAlternateBuffer: TComTermBuffer;
  142. FParams: TStrings;
  143. FEscapeCodes: TEscapeCodes;
  144. FTermAttr: TTermAttributes;
  145. FTermMode: TTermMode;
  146. FOnChar: TChScreenEvent;
  147. FOnGetEscapeCodes: TEscapeEvent;
  148. FOnUnhandledCode: TUnhandledEvent;
  149. FOnUnhandledMode: TUnhandledModeEvent;
  150. FOnStrRecieved: TStrRecvEvent;
  151. procedure AdvanceCaret(Kind: TAdvanceCaret);
  152. function CalculateMetrics: Boolean;
  153. procedure CreateEscapeCodes;
  154. procedure CreateTerminalCaret;
  155. procedure DrawChar(AColumn, ARow: Integer; Ch: TComTermChar);
  156. function GetCharAttr: TComTermChar;
  157. function GetConnected: Boolean;
  158. procedure HideCaret;
  159. procedure InitCaret;
  160. procedure InvalidatePortion(ARect: TRect);
  161. procedure ModifyScrollBar(ScrollBar, ScrollCode, Pos: Integer);
  162. procedure SetColumns(const Value: Integer);
  163. procedure SetPtyDevice(const Value: TCustomPtyDevice);
  164. procedure SetConnected(const Value: Boolean);
  165. procedure SetEmulation(const Value: TTermEmulation);
  166. procedure SetRows(const Value: Integer);
  167. procedure SetScrollBars(const Value: TScrollStyle);
  168. procedure SetCaret(const Value: TTermCaret);
  169. procedure SetAttributes(AParams: TStrings);
  170. procedure SetMode(AParams: TStrings; OnOff: Boolean);
  171. procedure ShowCaret;
  172. procedure StringReceived(Str: string);
  173. procedure PaintTerminal(Rect: TRect);
  174. procedure PaintDesign;
  175. procedure PutChar(Ch: TUTF8Char);
  176. function PutEscapeCode(ACode: TEscapeCode; AParams: TStrings): Boolean;
  177. procedure RestoreAttr;
  178. procedure RestoreCaretPos;
  179. procedure RxBuf(Sender: TObject; const Buffer; Count: Integer);
  180. procedure SaveAttr;
  181. procedure SaveCaretPos;
  182. procedure SendChar(Ch: TUTF8Char);
  183. procedure SendCode(Code: TEscapeCode; AParams: TStrings);
  184. procedure SendCodeNoEcho(Code: TEscapeCode; AParams: TStrings);
  185. procedure MouseEvent(Code: TEscapeCode; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  186. procedure PerformTest(ACh: Char);
  187. procedure UpdateScrollPos;
  188. procedure UpdateScrollRange;
  189. procedure WrapLine(AWidth: Integer);
  190. protected
  191. procedure CMFontChanged(var Message: TMessage); message CM_FONTCHANGED;
  192. procedure CMColorChanged(var Message: TMessage); message CM_COLORCHANGED;
  193. procedure WMGetDlgCode(var Message: TWMGetDlgCode); message WM_GETDLGCODE;
  194. procedure WMHScroll(var Message: TWMHScroll); message WM_HSCROLL;
  195. procedure WMKillFocus(var Message: TWMSetFocus); message WM_KILLFOCUS;
  196. procedure WMLButtonDown(var Message: TLMLButtonDown); message WM_LBUTTONDOWN;
  197. procedure WMSetFocus(var Message: TWMSetFocus); message WM_SETFOCUS;
  198. procedure WMSize(var Msg: TWMSize); message WM_SIZE;
  199. procedure WMVScroll(var Message: TWMVScroll); message WM_VSCROLL;
  200. function CanAutoSize(var NewWidth, NewHeight: Integer): Boolean; override;
  201. procedure CreateParams(var Params: TCreateParams); override;
  202. procedure KeyDown(var Key: Word; Shift: TShiftState); override;
  203. procedure KeyPress(var Key: Char); override;
  204. procedure UTF8KeyPress(var UTF8Key: TUTF8Char); override;
  205. procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
  206. procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
  207. procedure CreateWnd; override;
  208. procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  209. procedure Paint; override;
  210. procedure DoChar(Ch: TUTF8Char); dynamic;
  211. procedure DoGetEscapeCodes(var EscapeCodes: TEscapeCodes); dynamic;
  212. procedure DoStrRecieved(var Str: string); dynamic;
  213. procedure DoUnhandledCode(Code: TEscapeCode; Data: string); dynamic;
  214. procedure DoUnhandledMode(const Data: string; OnOff: Boolean); dynamic;
  215. function DoMouseWheel(Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint): Boolean; override;
  216. public
  217. constructor Create(AOwner: TComponent); override;
  218. destructor Destroy; override;
  219. procedure ClearScreen;
  220. procedure MoveCaret(AColumn, ARow: Integer);
  221. procedure Write(const Buffer:string; Size: Integer);
  222. procedure WriteStr(const Str: string);
  223. procedure WriteEscCode(ACode: TEscapeCode; AParams: TStrings);
  224. procedure LoadFromStream(Stream: TStream);
  225. procedure SaveToStream(Stream: TStream);
  226. procedure SelectFont;
  227. property AppendLF: Boolean read FAppendLF write FAppendLF default False;
  228. property AutoFollow : Boolean read FAutoFollow write FAutoFollow default True;
  229. property ArrowKeys: TArrowKeys read FArrowKeys write FArrowKeys default akTerminal;
  230. property Caret: TTermCaret read FCaret write SetCaret default tcBlock;
  231. property Connected: Boolean read GetConnected write SetConnected stored False;
  232. property PtyDevice: TCustomPtyDevice read FPtyDevice write SetPtyDevice;
  233. property Columns: Integer read FColumns write SetColumns default 80;
  234. property Emulation: TTermEmulation read FEmulation write SetEmulation;
  235. property EscapeCodes: TEscapeCodes read FEscapeCodes;
  236. property Force7Bit: Boolean read FForce7Bit write FForce7Bit default False;
  237. property LocalEcho: Boolean read FLocalEcho write FLocalEcho default False;
  238. property SendLF: Boolean read FSendLF write FSendLF default False;
  239. property ScrollBars: TScrollStyle read FScrollBars write SetScrollBars;
  240. property SmoothScroll: Boolean read FSmoothScroll write FSmoothScroll default False;
  241. property Rows: Integer read FRows write SetRows default 24;
  242. property WantTab: Boolean read FWantTab write FWantTab default False;
  243. property WrapLines: Boolean read FWrapLines write FWrapLines default False;
  244. property OnChar: TChScreenEvent read FOnChar write FOnChar;
  245. property OnGetEscapeCodes: TEscapeEvent
  246. read FOnGetEscapeCodes write FOnGetEscapeCodes;
  247. property OnStrRecieved: TStrRecvEvent
  248. read FOnStrRecieved write FOnStrRecieved;
  249. property OnUnhandledMode: TUnhandledModeEvent
  250. read FOnUnhandledMode write FOnUnhandledMode;
  251. property OnUnhandledCode: TUnhandledEvent
  252. read FOnUnhandledCode write FOnUnhandledCode;
  253. end;
  254. // publish properties
  255. TVirtualTerminal = class(TCustomComTerminal)
  256. published
  257. property Align;
  258. property AppendLF;
  259. property ArrowKeys;
  260. property BorderStyle;
  261. property Color;
  262. property Columns;
  263. property PtyDevice;
  264. property Connected;
  265. property DragCursor;
  266. property DragMode;
  267. property Emulation;
  268. property Enabled;
  269. property Font;
  270. property Force7Bit;
  271. property Hint;
  272. property LocalEcho;
  273. property ParentColor;
  274. property ParentShowHint;
  275. property PopupMenu;
  276. property Rows;
  277. property ScrollBars;
  278. property SendLF;
  279. property ShowHint;
  280. property SmoothScroll;
  281. property TabOrder;
  282. property TabStop default True;
  283. property Caret;
  284. property Visible;
  285. property WantTab;
  286. property WrapLines;
  287. property Anchors;
  288. property AutoSize;
  289. property Constraints;
  290. property DragKind;
  291. property OnChar;
  292. property OnClick;
  293. property OnDblClick;
  294. property OnDragDrop;
  295. property OnDragOver;
  296. property OnEndDrag;
  297. property OnEnter;
  298. property OnExit;
  299. property OnGetEscapeCodes;
  300. property OnKeyDown;
  301. property OnKeyPress;
  302. property OnKeyUp;
  303. property OnMouseDown;
  304. property OnMouseMove;
  305. property OnMouseUp;
  306. property OnStartDrag;
  307. property OnStrRecieved;
  308. property OnUnhandledCode;
  309. property OnConstrainedResize;
  310. property OnDockDrop;
  311. property OnEndDock;
  312. property OnMouseWheel;
  313. property OnMouseWheelDown;
  314. property OnMouseWheelUp;
  315. property OnResize;
  316. property OnStartDock;
  317. property OnUnDock;
  318. property OnContextPopup;
  319. end;
  320. implementation
  321. uses
  322. SysUtils, Dialogs, Math, VTColorTable, VTWideCharWidth;
  323. const
  324. TMPF_FIXED_PITCH = $01;
  325. (*****************************************
  326. * TComTermBuffer class *
  327. *****************************************)
  328. // create class
  329. constructor TComTermBuffer.Create(AOwner: TCustomComTerminal);
  330. begin
  331. inherited Create;
  332. FOwner := AOwner;
  333. FTopLeft := Classes.Point(1, 1);
  334. FCaretPos := Classes.Point(1, 1);
  335. end;
  336. // destroy class
  337. destructor TComTermBuffer.Destroy;
  338. begin
  339. if FBuffer <> nil then
  340. begin
  341. FreeMem(FBuffer);
  342. FreeMem(FTabs);
  343. end;
  344. inherited Destroy;
  345. end;
  346. // put char in buffer
  347. procedure TComTermBuffer.SetChar(Column, Row: Integer; TermChar: TComTermChar);
  348. var
  349. Address: Integer;
  350. begin
  351. if (Row > FRows) or (Column > FColumns) then
  352. Exit;
  353. Address := (Row - 1) * FColumns + (Column - 1);
  354. PComTermChar(FBuffer + (Address * SizeOf(TComTermChar)))^:= TermChar;
  355. end;
  356. // get char from buffer
  357. function TComTermBuffer.GetChar(Column, Row: Integer): TComTermChar;
  358. var
  359. Address: Integer;
  360. begin
  361. if (Row > FRows) or (Column > FColumns) then
  362. Exit(Default(TComTermChar));
  363. Address := (Row - 1) * FColumns + (Column - 1);
  364. Result:= PComTermChar(FBuffer + (Address * SizeOf(TComTermChar)))^;
  365. end;
  366. // scroll down up line
  367. procedure TComTermBuffer.ScrollDown;
  368. begin
  369. DeleteLine(FScrollRange.Top, 1);
  370. end;
  371. // scroll up one line
  372. procedure TComTermBuffer.ScrollUp;
  373. begin
  374. InsertLine(FScrollRange.Top, 1)
  375. end;
  376. procedure TComTermBuffer.EraseLineLeft(Column, Row: Integer);
  377. var
  378. Index: Integer;
  379. B: PComTermChar;
  380. begin
  381. if (Row > FRows) or (Column > FColumns) then Exit;
  382. // in memory
  383. B:= PComTermChar(FBuffer) + ((Row - 1) * FColumns);
  384. for Index:= 0 to Column - 1 do
  385. begin
  386. B[Index].Ch:= #32;
  387. B[Index].BackColor:= FOwner.FTermAttr.BackColor;
  388. B[Index].FrontColor:= FOwner.FTermAttr.FrontColor;
  389. end;
  390. // on screen
  391. if FOwner.DoubleBuffered then
  392. FOwner.Invalidate
  393. else
  394. FOwner.InvalidatePortion(Classes.Rect(1, Row, Column, Row));
  395. end;
  396. // erase line
  397. procedure TComTermBuffer.EraseLineRight(Column, Row: Integer);
  398. var
  399. Index: Integer;
  400. Count: Integer;
  401. B: PComTermChar;
  402. begin
  403. if (Row > FRows) or (Column > FColumns) then Exit;
  404. // in memory
  405. Count:= (FColumns - Column + 1);
  406. B:= PComTermChar(FBuffer) + ((Row - 1) * FColumns + (Column - 1));
  407. for Index:= 0 to Count - 1 do
  408. begin
  409. B[Index].Ch:= #32;
  410. B[Index].BackColor:= FOwner.FTermAttr.BackColor;
  411. B[Index].FrontColor:= FOwner.FTermAttr.FrontColor;
  412. end;
  413. // on screen
  414. if FOwner.DoubleBuffered then
  415. FOwner.Invalidate
  416. else
  417. FOwner.InvalidatePortion(Classes.Rect(Column, Row, FColumns, Row));
  418. end;
  419. procedure TComTermBuffer.EraseChar(Column, Row, Count: Integer);
  420. var
  421. Index: Integer;
  422. B: PComTermChar;
  423. begin
  424. if (Row > FRows) or (Column > FColumns) then Exit;
  425. if (Column + Count > FColumns) then Count:= FColumns - Column;
  426. // in memory
  427. B:= PComTermChar(FBuffer) + ((Row - 1) * FColumns + (Column - 1));
  428. for Index:= 0 to Count - 1 do
  429. begin
  430. B[Index].Ch:= #32;
  431. B[Index].BackColor:= FOwner.FTermAttr.BackColor;
  432. B[Index].FrontColor:= FOwner.FTermAttr.FrontColor;
  433. end;
  434. // on screen
  435. if FOwner.DoubleBuffered then
  436. FOwner.Invalidate
  437. else
  438. FOwner.InvalidatePortion(Classes.Rect(Column, Row, FColumns, Row));
  439. end;
  440. procedure TComTermBuffer.DeleteChar(Column, Row, Count: Integer);
  441. var
  442. Index: Integer;
  443. DstAddr: PComTermChar;
  444. SrcAddr: PComTermChar;
  445. begin
  446. if (Row > FRows) or (Column > FColumns) then Exit;
  447. if (Column + Count > FColumns) then Count:= FColumns - Column;
  448. // in memory
  449. DstAddr:= PComTermChar(FBuffer) + ((Row - 1) * FColumns + (Column - 1));
  450. SrcAddr:= PComTermChar(FBuffer) + ((Row - 1) * FColumns + (Column - 1) + Count);
  451. // Move characters
  452. Count:= (FColumns - (Column + Count));
  453. Move(SrcAddr^, DstAddr^, Count * SizeOf(TComTermChar));
  454. // Erase moved
  455. for Index:= 0 to Count - 1 do
  456. begin
  457. SrcAddr[Index].Ch:= #32;
  458. SrcAddr[Index].BackColor:= FOwner.FTermAttr.BackColor;
  459. SrcAddr[Index].FrontColor:= FOwner.FTermAttr.FrontColor;
  460. end;
  461. // on screen
  462. if FOwner.DoubleBuffered then
  463. FOwner.Invalidate
  464. else
  465. FOwner.InvalidatePortion(Classes.Rect(Column, Row, FColumns, Row));
  466. end;
  467. procedure TComTermBuffer.DeleteLine(Row, Count: Integer);
  468. var
  469. Index: Integer;
  470. B: PComTermChar;
  471. DstAddr: Pointer;
  472. SrcAddr: Pointer;
  473. BytesToMove: Integer;
  474. Top, Bottom: Integer;
  475. begin
  476. Top:= FScrollRange.Top;
  477. Bottom:= FScrollRange.Bottom;
  478. if (Row < Top) or (Row > Bottom) then Exit;
  479. if (Row - 1) + Count > Bottom then Count:= Bottom - Row + 1;
  480. if Row < Bottom then
  481. begin
  482. DstAddr := (FBuffer + (Row - 1) * FColumns * SizeOf(TComTermChar));
  483. SrcAddr := (FBuffer + (Row + Count - 1) * FColumns * SizeOf(TComTermChar));
  484. BytesToMove := (Bottom - Row - Count + 1) * FColumns * SizeOf(TComTermChar);
  485. // scroll in buffer
  486. Move(SrcAddr^, DstAddr^, BytesToMove);
  487. end;
  488. B:= PComTermChar(FBuffer) + ((Bottom - Count) * FColumns);
  489. for Index:= 0 to Count * FColumns - 1 do
  490. begin
  491. B[Index].Ch:= #32;
  492. B[Index].BackColor:= FOwner.FTermAttr.BackColor;
  493. B[Index].FrontColor:= FOwner.FTermAttr.FrontColor;
  494. end;
  495. // on screen
  496. if FOwner.DoubleBuffered then
  497. FOwner.Invalidate
  498. else
  499. FOwner.InvalidatePortion(Classes.Rect(1, Row, FColumns, Bottom));
  500. end;
  501. procedure TComTermBuffer.InsertLine(Row, Count: Integer);
  502. var
  503. Index: Integer;
  504. B: PComTermChar;
  505. DstAddr: Pointer;
  506. SrcAddr: Pointer;
  507. BytesToMove: Integer;
  508. Top, Bottom: Integer;
  509. begin
  510. Top:= FScrollRange.Top;
  511. Bottom:= FScrollRange.Bottom;
  512. if (Row < Top) or (Row > Bottom) then Exit;
  513. if (Row - 1) + Count > Bottom then Count:= Bottom - Row + 1;
  514. if Row < Bottom then
  515. begin
  516. SrcAddr := (FBuffer + (Row - 1) * FColumns * SizeOf(TComTermChar));
  517. DstAddr := (FBuffer + (Row + Count - 1) * FColumns * SizeOf(TComTermChar));
  518. BytesToMove := (Bottom - Row - Count + 1) * FColumns * SizeOf(TComTermChar);
  519. // scroll in buffer
  520. Move(SrcAddr^, DstAddr^, BytesToMove);
  521. end;
  522. B:= PComTermChar(FBuffer) + ((Row - 1) * FColumns);
  523. for Index:= 0 to Count * FColumns - 1 do
  524. begin
  525. B[Index].Ch:= #32;
  526. B[Index].BackColor:= FOwner.FTermAttr.BackColor;
  527. B[Index].FrontColor:= FOwner.FTermAttr.FrontColor;
  528. end;
  529. // on screen
  530. if FOwner.DoubleBuffered then
  531. FOwner.Invalidate
  532. else
  533. FOwner.InvalidatePortion(Classes.Rect(1, Row, FColumns, Bottom));
  534. end;
  535. // erase screen
  536. procedure TComTermBuffer.EraseScreenLeft(Column, Row: Integer);
  537. var
  538. Index: Integer;
  539. Count: Integer;
  540. B: PComTermChar;
  541. begin
  542. if (Row > FRows) or (Column > FColumns) then Exit;
  543. // in memory
  544. B:= PComTermChar(FBuffer);
  545. Count:= (Row * FColumns + Column);
  546. for Index:= 0 to Count - 1 do
  547. begin
  548. B[Index].Ch:= #32;
  549. B[Index].BackColor:= FOwner.FTermAttr.BackColor;
  550. B[Index].FrontColor:= FOwner.FTermAttr.FrontColor;
  551. end;
  552. // on screen
  553. if FOwner.DoubleBuffered then
  554. FOwner.Invalidate
  555. else
  556. FOwner.InvalidatePortion(Classes.Rect(1, 1, FColumns, Row))
  557. end;
  558. // erase screen
  559. procedure TComTermBuffer.EraseScreenRight(Column, Row: Integer);
  560. var
  561. Index: Integer;
  562. Count: Integer;
  563. B: PComTermChar;
  564. begin
  565. if (Row > FRows) or (Column > FColumns) then Exit;
  566. // in memory
  567. B:= PComTermChar(FBuffer) + ((Row - 1) * FColumns + (Column - 1));
  568. Count:= ((FRows - Row) * FColumns + (FColumns - Column) + 1);
  569. for Index:= 0 to Count - 1 do
  570. begin
  571. B[Index].Ch:= #32;
  572. B[Index].BackColor:= FOwner.FTermAttr.BackColor;
  573. B[Index].FrontColor:= FOwner.FTermAttr.FrontColor;
  574. end;
  575. // on screen
  576. if FOwner.DoubleBuffered then
  577. FOwner.Invalidate
  578. else
  579. FOwner.InvalidatePortion(Classes.Rect(1, Row, FColumns, FRows))
  580. end;
  581. // init buffer
  582. procedure TComTermBuffer.Init(ARows, AColumns: Integer);
  583. var
  584. I: Integer;
  585. begin
  586. if ARows > 0 then
  587. FRows:= ARows;
  588. if AColumns > 0 then
  589. FColumns:= AColumns;
  590. if FBuffer <> nil then
  591. begin
  592. FreeMem(FBuffer);
  593. FreeMem(FTabs);
  594. end;
  595. GetMem(FBuffer, FColumns * FRows * SizeOf(TComTermChar));
  596. FillChar(FBuffer^, FColumns * FRows * SizeOf(TComTermChar), 0);
  597. GetMem(FTabs, FColumns * SizeOf(Boolean));
  598. FillChar(FTabs^, FColumns * SizeOf(Boolean), 0);
  599. I := 1;
  600. while (I <= FColumns) do
  601. begin
  602. SetTab(I, True);
  603. Inc(I, 8);
  604. end;
  605. FScrollRange.Top:= 1;
  606. FScrollRange.Bottom:= FRows;
  607. end;
  608. // get tab at Column
  609. function TComTermBuffer.GetTab(Column: Integer): Boolean;
  610. begin
  611. Result := Boolean((FTabs + (Column - 1) * SizeOf(Boolean))^);
  612. end;
  613. // set tab at column
  614. procedure TComTermBuffer.SetTab(Column: Integer; Put: Boolean);
  615. begin
  616. Boolean((FTabs + (Column - 1) * SizeOf(Boolean))^) := Put;
  617. end;
  618. // find nexts tab position
  619. function TComTermBuffer.NextTab(Column: Integer): Integer;
  620. var
  621. I: Integer;
  622. begin
  623. I := Column;
  624. while (I <= FColumns) do
  625. if GetTab(I) then
  626. Break
  627. else
  628. Inc(I);
  629. if I > FColumns then
  630. Result := 0
  631. else
  632. Result := I;
  633. end;
  634. // clear all tabs
  635. procedure TComTermBuffer.ClearAllTabs;
  636. begin
  637. FillChar(FTabs^, FColumns * SizeOf(Boolean), 0);
  638. end;
  639. function TComTermBuffer.GetLineLength(Line: Integer): Integer;
  640. var
  641. I: Integer;
  642. begin
  643. Result := 0;
  644. for I := 1 to FColumns do
  645. if GetChar(I, Line).Ch <> #0 then
  646. Result := I;
  647. end;
  648. function TComTermBuffer.GetLastLine: Integer;
  649. var
  650. J: Integer;
  651. begin
  652. Result := 0;
  653. for J := 1 to FRows do
  654. if GetLineLength(J) > 0 then
  655. Result := J;
  656. end;
  657. (*****************************************
  658. * TComCustomTerminal control *
  659. *****************************************)
  660. // create control
  661. constructor TCustomComTerminal.Create(AOwner: TComponent);
  662. begin
  663. FScrollBars := ssVertical;
  664. inherited Create(AOwner);
  665. Parent:= TWinControl(AOwner);
  666. BorderStyle := bsSingle;
  667. Color := clBlack;
  668. DoubleBuffered := True;
  669. TabStop := True;
  670. Font.Name := 'Consolas';
  671. Font.Color:= clWhite;
  672. FEmulation := teVT100orANSI;
  673. FColumns := 80;
  674. FRows := 100;
  675. FVisibleRows:= 25;
  676. FWrapLines := True;
  677. FAutoFollow := True;
  678. FCaretPos := Classes.Point(1, 1);
  679. FTopLeft := Classes.Point(1, 1);
  680. FMainBuffer := TComTermBuffer.Create(Self);
  681. FAlternateBuffer := TComTermBuffer.Create(Self);
  682. FTermAttr.FrontColor := Font.Color;
  683. FTermAttr.BackColor := Color;
  684. FBuffer:= FMainBuffer;
  685. FParams:= TStringList.Create;
  686. CreateEscapeCodes;
  687. if not (csDesigning in ComponentState) then
  688. begin
  689. FMainBuffer.Init(FRows, FColumns);
  690. FAlternateBuffer.Init(FVisibleRows, FColumns);
  691. end;
  692. SetBounds(Left, Top, 400, 250);
  693. end;
  694. // destroy control
  695. destructor TCustomComTerminal.Destroy;
  696. begin
  697. PtyDevice := nil;
  698. FMainBuffer.Free;
  699. FAlternateBuffer.Free;
  700. FEscapeCodes.Free;
  701. FParams.Free;
  702. inherited Destroy;
  703. end;
  704. // clear terminal screen
  705. procedure TCustomComTerminal.ClearScreen;
  706. begin
  707. FBuffer.Init(0, 0);
  708. MoveCaret(1, 1);
  709. Invalidate;
  710. end;
  711. // move caret
  712. procedure TCustomComTerminal.MoveCaret(AColumn, ARow: Integer);
  713. begin
  714. if AColumn > FBuffer.Columns then
  715. begin
  716. if FWrapLines then
  717. FCaretPos.X := FBuffer.Columns + 1
  718. else
  719. FCaretPos.X := FBuffer.Columns
  720. end
  721. else
  722. if AColumn < 1 then
  723. FCaretPos.X := 1
  724. else
  725. FCaretPos.X := AColumn;
  726. if ARow > FBuffer.Rows then
  727. FCaretPos.Y := FBuffer.Rows
  728. else
  729. if ARow < 1 then
  730. FCaretPos.Y := 1
  731. else
  732. FCaretPos.Y := ARow;
  733. if FCaretCreated then
  734. SetCaretPos((FCaretPos.X - FTopLeft.X) * FFontWidth,
  735. (FCaretPos.Y - FTopLeft.Y) * FFontHeight + FFontHeight - FCaretHeight);
  736. end;
  737. // write data to screen
  738. procedure TCustomComTerminal.Write(const Buffer:string; Size: Integer);
  739. var
  740. I: Integer;
  741. L: Integer;
  742. Ch: TUTF8Char;
  743. Res: TEscapeResult;
  744. begin
  745. HideCaret;
  746. try
  747. // show it on screen
  748. I:= 1;
  749. while I <= Size do
  750. begin
  751. L:= UTF8CodepointSizeFast(@Buffer[I]);
  752. Ch:= Copy(Buffer, I, L);
  753. // got partial character
  754. if (I + L - 1 > Size) then
  755. begin
  756. FPartChar:= Ch;
  757. Break;
  758. end;
  759. if (FEscapeCodes <> nil) then
  760. begin
  761. Res := FEscapeCodes.ProcessChar(Ch);
  762. if Res = erChar then
  763. PutChar(FEscapeCodes.Character);
  764. if Res = erCode then
  765. begin
  766. if not PutEscapeCode(FEscapeCodes.Code, FEscapeCodes.Params) then
  767. DoUnhandledCode(FEscapeCodes.Code, FEscapeCodes.Data);
  768. FEscapeCodes.Params.Clear;
  769. end;
  770. end
  771. else begin
  772. PutChar(Ch);
  773. end;
  774. I+= L;
  775. end;
  776. finally
  777. ShowCaret;
  778. end;
  779. end;
  780. // write string on screen, but not to port
  781. procedure TCustomComTerminal.WriteStr(const Str: string);
  782. begin
  783. Write(Str, Length(Str));
  784. end;
  785. // write escape code on screen
  786. procedure TCustomComTerminal.WriteEscCode(ACode: TEscapeCode;
  787. AParams: TStrings);
  788. begin
  789. if FEscapeCodes <> nil then
  790. PutEscapeCode(ACode, AParams);
  791. end;
  792. // load screen buffer from file
  793. procedure TCustomComTerminal.LoadFromStream(Stream: TStream);
  794. var
  795. ABuffer: TBytes;
  796. begin
  797. HideCaret;
  798. ABuffer:= Default(TBytes);
  799. SetLength(ABuffer, Stream.Size);
  800. Stream.ReadBuffer(ABuffer[0], Length(ABuffer));
  801. RxBuf(Self, ABuffer[0], Length(ABuffer));
  802. ShowCaret;
  803. end;
  804. // save screen buffer to file
  805. procedure TCustomComTerminal.SaveToStream(Stream: TStream);
  806. var
  807. I, J: Integer;
  808. Ch: TUTF8Char;
  809. EndLine: string;
  810. LastChar, LastLine: Integer;
  811. begin
  812. EndLine := #13#10;
  813. LastLine := FBuffer.GetLastLine;
  814. for J := 1 to LastLine do
  815. begin
  816. LastChar := FBuffer.GetLineLength(J);
  817. if LastChar > 0 then
  818. begin
  819. for I := 1 to LastChar do
  820. begin
  821. Ch := FBuffer.GetChar(I, J).Ch;
  822. // replace null characters with blanks
  823. if Ch = #0 then
  824. Ch := #32;
  825. Stream.Write(Ch, Length(Ch));
  826. end;
  827. end;
  828. // new line
  829. if J <> LastLine then
  830. Stream.Write(EndLine[1], Length(EndLine));
  831. end;
  832. end;
  833. // select terminal font
  834. procedure TCustomComTerminal.SelectFont;
  835. begin
  836. with TFontDialog.Create(Application) do
  837. begin
  838. Options := Options + [fdFixedPitchOnly];
  839. Font := Self.Font;
  840. if Execute then
  841. Self.Font := Font;
  842. Free;
  843. end;
  844. end;
  845. // process font change
  846. procedure TCustomComTerminal.CMFontChanged(var Message: TMessage);
  847. begin
  848. inherited;
  849. FTermAttr.FrontColor := Font.Color;
  850. if not CalculateMetrics then
  851. ;//Font.Name := ComTerminalFont.Name;
  852. if fsUnderline in Font.Style then
  853. Font.Style := Font.Style - [fsUnderline];
  854. AdjustSize;
  855. UpdateScrollRange;
  856. end;
  857. procedure TCustomComTerminal.CMColorChanged(var Message: TMessage);
  858. begin
  859. inherited;
  860. FTermAttr.BackColor := Color;
  861. end;
  862. procedure TCustomComTerminal.WMGetDlgCode(var Message: TWMGetDlgCode);
  863. begin
  864. // request arrow keys and WM_CHAR message to be handled by the control
  865. Message.Result := DLGC_WANTARROWS or DLGC_WANTCHARS;
  866. // tab key
  867. if FWantTab then
  868. Message.Result := Message.Result or DLGC_WANTTAB;
  869. end;
  870. // lost focus
  871. procedure TCustomComTerminal.WMKillFocus(var Message: TWMSetFocus);
  872. begin
  873. // destroy caret because it could be requested by some other control
  874. DestroyCaret(Handle);
  875. FCaretCreated := False;
  876. inherited;
  877. end;
  878. // gained focus
  879. procedure TCustomComTerminal.WMSetFocus(var Message: TWMSetFocus);
  880. begin
  881. inherited;
  882. // control activated, create caret
  883. InitCaret;
  884. end;
  885. // left button pressed
  886. procedure TCustomComTerminal.WMLButtonDown(var Message: TLMLButtonDown);
  887. begin
  888. // set focus when left button down
  889. if CanFocus and TabStop then
  890. SetFocus;
  891. inherited;
  892. end;
  893. // size changed
  894. procedure TCustomComTerminal.WMSize(var Msg: TWMSize);
  895. var
  896. ARows, AColumns: Integer;
  897. begin
  898. inherited WMSize(Msg);
  899. if (ClientWidth = 0) or (ClientHeight = 0) then
  900. Exit;
  901. ARows:= Max(2, ClientHeight div FFontHeight);
  902. AColumns:= Max(2, ClientWidth div FFontWidth);
  903. if (ARows <> FVisibleRows) or (AColumns <> FColumns) then
  904. begin
  905. FColumns := AColumns;
  906. FVisibleRows := ARows;
  907. FRows := Max(FRows, FVisibleRows);
  908. AdjustSize;
  909. if not ((csLoading in ComponentState) or (csDesigning in ComponentState)) then
  910. begin
  911. FMainBuffer.Init(FRows, FColumns);
  912. FAlternateBuffer.Init(FVisibleRows, FColumns);
  913. if Assigned(FPtyDevice) then
  914. FPtyDevice.SetScreenSize(FColumns, FVisibleRows);
  915. Invalidate;
  916. end;
  917. UpdateScrollRange;
  918. if (FCaretPos.Y = FBuffer.Rows) or
  919. ((FCaretPos.Y - FTopLeft.Y) >= FVisibleRows) then
  920. begin
  921. ARows:= FCaretPos.Y - FVisibleRows;
  922. ModifyScrollBar(SB_Vert, SB_THUMBPOSITION, ARows);
  923. end;
  924. end;
  925. end;
  926. // vertical scroll
  927. procedure TCustomComTerminal.WMHScroll(var Message: TWMHScroll);
  928. begin
  929. ModifyScrollBar(SB_HORZ, Message.ScrollCode, Message.Pos);
  930. end;
  931. // horizontal scroll
  932. procedure TCustomComTerminal.WMVScroll(var Message: TWMVScroll);
  933. begin
  934. ModifyScrollBar(SB_VERT, Message.ScrollCode, Message.Pos);
  935. end;
  936. // set size to fit whole terminal screen
  937. function TCustomComTerminal.CanAutoSize(var NewWidth,
  938. NewHeight: Integer): Boolean;
  939. var
  940. Border: Integer;
  941. begin
  942. Result := True;
  943. if Align in [alNone, alLeft, alRight] then
  944. begin
  945. NewWidth := FFontWidth * FColumns;
  946. if BorderStyle = bsSingle then
  947. begin
  948. Border := SM_CXBORDER;
  949. NewWidth := NewWidth + 2 * GetSystemMetrics(BORDER);
  950. end;
  951. end;
  952. if Align in [alNone, alTop, alBottom] then
  953. begin
  954. NewHeight := FFontHeight * FRows;
  955. if BorderStyle = bsSingle then
  956. begin
  957. Border := SM_CYBORDER;
  958. NewHeight := NewHeight + 2 * GetSystemMetrics(Border);
  959. end;
  960. end;
  961. end;
  962. // set control parameters
  963. procedure TCustomComTerminal.CreateParams(var Params: TCreateParams);
  964. const
  965. BorderStyles: array[TBorderStyle] of DWORD = (0, WS_BORDER);
  966. begin
  967. inherited CreateParams(Params);
  968. with Params do
  969. begin
  970. Style := Style or BorderStyles[BorderStyle];
  971. if NewStyleControls and (BorderStyle = bsSingle) then
  972. begin
  973. Style := Style and not WS_BORDER;
  974. ExStyle := ExStyle or WS_EX_CLIENTEDGE;
  975. end;
  976. if FScrollBars in [ssVertical, ssBoth] then
  977. Style := Style or WS_VSCROLL;
  978. if FScrollBars in [ssHorizontal, ssBoth] then
  979. Style := Style or WS_HSCROLL;
  980. end;
  981. ControlStyle := ControlStyle + [csOpaque];
  982. end;
  983. // key down
  984. procedure TCustomComTerminal.KeyDown(var Key: Word; Shift: TShiftState);
  985. var
  986. Code: TEscapeCode;
  987. begin
  988. inherited KeyDown(Key, Shift);
  989. if (Key in [VK_TAB, VK_ESCAPE]) then
  990. begin
  991. SendChar(Chr(Key));
  992. Key:= 0;
  993. Exit;
  994. end;
  995. if (Key = VK_BACK) then
  996. begin
  997. SendChar(#$7f);
  998. Key:= 0;
  999. Exit;
  1000. end;
  1001. if Key in [VK_F1..VK_F12] then
  1002. begin
  1003. Code := ecFuncKey;
  1004. FParams.Text:= IntToStr(Key - VK_F1);
  1005. SendCode(Code, FParams);
  1006. Exit;
  1007. end;
  1008. case Key of
  1009. VK_INSERT: Code := ecInsertKey;
  1010. VK_DELETE: Code := ecDeleteKey;
  1011. VK_PRIOR: Code := ecPageUpKey;
  1012. VK_NEXT: Code := ecPageDownKey;
  1013. else
  1014. Code := ecUnknown;
  1015. end;
  1016. if (Code <> ecUnknown) then
  1017. begin
  1018. SendCode(Code, nil);
  1019. Exit;
  1020. end;
  1021. case Key of
  1022. VK_UP: Code := ecCursorUp;
  1023. VK_DOWN: Code := ecCursorDown;
  1024. VK_LEFT: Code := ecCursorLeft;
  1025. VK_RIGHT: Code := ecCursorRight;
  1026. VK_HOME: Code := ecCursorHome;
  1027. VK_END: Code := ecCursorEnd;
  1028. else
  1029. Code := ecUnknown;
  1030. end;
  1031. if FTermMode.Keys = akTerminal then
  1032. begin
  1033. if Code <> ecUnknown then
  1034. if FArrowKeys = akTerminal then
  1035. SendCode(Code, nil)
  1036. else
  1037. PutEscapeCode(Code, nil);
  1038. end
  1039. else
  1040. case Code of
  1041. ecCursorUp: SendCode(ecAppCursorUp, nil);
  1042. ecCursorDown: SendCode(ecAppCursorDown, nil);
  1043. ecCursorLeft: SendCode(ecAppCursorLeft, nil);
  1044. ecCursorRight: SendCode(ecAppCursorRight, nil);
  1045. ecCursorHome: SendCode(ecAppCursorHome, nil);
  1046. ecCursorEnd: SendCode(ecAppCursorEnd, nil);
  1047. end;
  1048. {$IFDEF LCLGTK2}
  1049. if Key in [VK_UP, VK_DOWN] then
  1050. begin
  1051. Key:= 0;
  1052. end;
  1053. {$ENDIF}
  1054. end;
  1055. // key pressed
  1056. procedure TCustomComTerminal.KeyPress(var Key: Char);
  1057. begin
  1058. inherited KeyPress(Key);
  1059. // SendChar(Key);
  1060. end;
  1061. procedure TCustomComTerminal.UTF8KeyPress(var UTF8Key: TUTF8Char);
  1062. begin
  1063. inherited UTF8KeyPress(UTF8Key);
  1064. SendChar(UTF8Key);
  1065. end;
  1066. procedure TCustomComTerminal.MouseDown(Button: TMouseButton;
  1067. Shift: TShiftState; X, Y: Integer);
  1068. begin
  1069. inherited MouseDown(Button, Shift, X, Y);
  1070. MouseEvent(ecMouseDown, Button, Shift, X, Y);
  1071. end;
  1072. procedure TCustomComTerminal.MouseUp(Button: TMouseButton; Shift: TShiftState;
  1073. X, Y: Integer);
  1074. begin
  1075. inherited MouseUp(Button, Shift, X, Y);
  1076. MouseEvent(ecMouseUp, Button, Shift, X, Y);
  1077. end;
  1078. procedure TCustomComTerminal.CreateWnd;
  1079. begin
  1080. inherited CreateWnd;
  1081. if FScrollBars in [ssVertical, ssBoth] then
  1082. ShowScrollBar(Handle, SB_VERT, True);
  1083. if FScrollBars in [ssHorizontal, ssBoth] then
  1084. ShowScrollBar(Handle, SB_HORZ, True);
  1085. end;
  1086. procedure TCustomComTerminal.Notification(AComponent: TComponent;
  1087. Operation: TOperation);
  1088. begin
  1089. inherited Notification(AComponent, Operation);
  1090. if (AComponent = FPtyDevice) and (Operation = opRemove) then
  1091. PtyDevice := nil;
  1092. end;
  1093. // paint characters
  1094. procedure TCustomComTerminal.PaintTerminal(Rect: TRect);
  1095. var
  1096. I, J, X, Y: Integer;
  1097. Ch: TComTermChar;
  1098. begin
  1099. HideCaret;
  1100. if (Rect.Bottom + FTopLeft.Y - 1) > FBuffer.Rows then
  1101. Dec(Rect.Bottom);
  1102. if (Rect.Right + FTopLeft.X - 1) > FBuffer.Columns then
  1103. Dec(Rect.Right);
  1104. for J := Rect.Top to Rect.Bottom do
  1105. begin
  1106. Y := J + FTopLeft.Y - 1;
  1107. for I := Rect.Left to Rect.Right do
  1108. begin
  1109. X := I + FTopLeft.X - 1;
  1110. Ch := FBuffer.GetChar(X, Y);
  1111. if Ch.Ch <> Chr(0) then
  1112. DrawChar(I, J, Ch);
  1113. end;
  1114. end;
  1115. ShowCaret;
  1116. end;
  1117. procedure TCustomComTerminal.PaintDesign;
  1118. begin
  1119. Canvas.TextOut(0, 0, 'Virtual Terminal Emulator');
  1120. end;
  1121. procedure TCustomComTerminal.Paint;
  1122. var
  1123. ARect: TRect;
  1124. begin
  1125. Canvas.Font := Font;
  1126. Canvas.Brush.Color := Color;
  1127. if csDesigning in ComponentState then
  1128. PaintDesign
  1129. else
  1130. begin
  1131. MoveCaret(FCaretPos.X, FCaretPos.Y);
  1132. // don't paint whole screen, but only the invalidated portion
  1133. ARect.Left := Canvas.ClipRect.Left div FFontWidth + 1;
  1134. ARect.Right := Min(Canvas.ClipRect.Right div FFontWidth + 1, FBuffer.Columns);
  1135. ARect.Top := Canvas.ClipRect.Top div FFontHeight + 1;
  1136. ARect.Bottom := Min(Canvas.ClipRect.Bottom div FFontHeight + 1, FBuffer.Rows);
  1137. PaintTerminal(ARect);
  1138. end;
  1139. end;
  1140. // creates caret
  1141. procedure TCustomComTerminal.CreateTerminalCaret;
  1142. begin
  1143. FCaretHeight := 0;
  1144. if FCaret = tcBlock then
  1145. FCaretHeight := FFontHeight
  1146. else
  1147. if FCaret = tcUnderline then
  1148. FCaretHeight := FFontHeight div 8;
  1149. if FCaretHeight > 0 then
  1150. begin
  1151. CreateCaret(Handle, 0, FFontWidth, FCaretHeight);
  1152. FCaretCreated := True;
  1153. end;
  1154. end;
  1155. // string received from com port
  1156. procedure TCustomComTerminal.StringReceived(Str: string);
  1157. begin
  1158. DoStrRecieved(Str);
  1159. WriteStr(Str);
  1160. end;
  1161. // draw one character on screen, but do not put it in buffer
  1162. procedure TCustomComTerminal.DrawChar(AColumn, ARow: Integer;
  1163. Ch: TComTermChar);
  1164. var
  1165. OldBackColor, OldFrontColor: Integer;
  1166. begin
  1167. OldBackColor := Canvas.Brush.Color;
  1168. OldFrontColor := Canvas.Font.Color;
  1169. Canvas.Brush.Color := Ch.BackColor;
  1170. Canvas.Font.Color := Ch.FrontColor;
  1171. if Ch.Bold then
  1172. Canvas.Font.Style := Canvas.Font.Style + [fsBold]
  1173. else begin
  1174. Canvas.Font.Style := Canvas.Font.Style - [fsBold];
  1175. end;
  1176. if Ch.Underline then
  1177. Canvas.Font.Style := Canvas.Font.Style + [fsUnderline]
  1178. else begin
  1179. Canvas.Font.Style := Canvas.Font.Style - [fsUnderline];
  1180. end;
  1181. Canvas.TextOut((AColumn - 1) * FFontWidth, (ARow - 1) * FFontHeight, Ch.Ch);
  1182. Canvas.Brush.Color := OldBackColor;
  1183. Canvas.Font.Color := OldFrontColor;
  1184. end;
  1185. procedure TCustomComTerminal.WrapLine(AWidth: Integer);
  1186. begin
  1187. if FCaretPos.X + AWidth > FBuffer.Columns + 1 then
  1188. begin
  1189. if FCaretPos.Y = FBuffer.Rows then
  1190. begin
  1191. FBuffer.ScrollDown;
  1192. MoveCaret(1, FCaretPos.Y);
  1193. end
  1194. else begin
  1195. MoveCaret(1, FCaretPos.Y + 1)
  1196. end;
  1197. end;
  1198. end;
  1199. // move caret after new char is put on screen
  1200. procedure TCustomComTerminal.AdvanceCaret(Kind: TAdvanceCaret);
  1201. var
  1202. I: Integer;
  1203. begin
  1204. case Kind of
  1205. acChar:
  1206. begin
  1207. if (FCaretPos.X < FColumns) or FWrapLines then
  1208. MoveCaret(FCaretPos.X + 1, FCaretPos.Y);
  1209. end;
  1210. acReturn: MoveCaret(1, FCaretPos.Y);
  1211. acLineFeed:
  1212. begin
  1213. if FCaretPos.Y = FBuffer.FScrollRange.Bottom then
  1214. FBuffer.ScrollDown
  1215. else
  1216. MoveCaret(FCaretPos.X, FCaretPos.Y + 1);
  1217. end;
  1218. acReverseLineFeed:
  1219. begin
  1220. if FCaretPos.Y = FBuffer.FScrollRange.Top then
  1221. FBuffer.ScrollUp
  1222. else
  1223. MoveCaret(FCaretPos.X, FCaretPos.Y - 1);
  1224. end;
  1225. acBackSpace: MoveCaret(FCaretPos.X - 1, FCaretPos.Y);
  1226. acTab:
  1227. begin
  1228. I := FBuffer.NextTab(FCaretPos.X + 1);
  1229. if I > 0 then
  1230. MoveCaret(I, FCaretPos.Y);
  1231. end;
  1232. acPage:
  1233. ClearScreen;
  1234. end;
  1235. if FAutoFollow then
  1236. begin
  1237. if (FCaretPos.Y - FTopLeft.Y) > FVisibleRows then
  1238. begin
  1239. I:= FCaretPos.Y - FVisibleRows + 1;
  1240. ModifyScrollBar(SB_Vert, SB_THUMBPOSITION, I);
  1241. end;
  1242. end;
  1243. end;
  1244. // set character attributes
  1245. procedure TCustomComTerminal.SetAttributes(AParams: TStrings);
  1246. var
  1247. I, Value: Integer;
  1248. procedure AllOff;
  1249. begin
  1250. FTermAttr.FrontColor := Font.Color;
  1251. FTermAttr.BackColor := Color;
  1252. FTermAttr.Invert := False;
  1253. FTermAttr.Bold := False;
  1254. FTermAttr.Underline := False;
  1255. end;
  1256. function GetExtendedColor(var Index: Integer): TColor;
  1257. var
  1258. RGB: Integer;
  1259. R, G, B: Byte;
  1260. AParam: Integer;
  1261. begin
  1262. AParam:= FEscapeCodes.GetParam(Index + 1, AParams);
  1263. // Color from RGB value
  1264. if AParam = 2 then
  1265. begin
  1266. R:= FEscapeCodes.GetParam(Index + 2, AParams);
  1267. G:= FEscapeCodes.GetParam(Index + 3, AParams);
  1268. B:= FEscapeCodes.GetParam(Index + 4, AParams);
  1269. Result:= RGBToColor(R, G, B);
  1270. Inc(Index, 4);
  1271. end
  1272. // Color from 256 color palette
  1273. else if (AParam = 5) then
  1274. begin
  1275. RGB:= FEscapeCodes.GetParam(Index + 2, AParams);
  1276. if (RGB >= 0) and (RGB < 256) then
  1277. begin
  1278. Result:= Color256Table[RGB];
  1279. end;
  1280. Inc(Index, 2);
  1281. end;
  1282. end;
  1283. begin
  1284. I:= 1;
  1285. if AParams.Count = 0 then
  1286. AllOff;
  1287. while I <= AParams.Count do
  1288. begin
  1289. Value := FEscapeCodes.GetParam(I, AParams);
  1290. case Value of
  1291. 0: AllOff;
  1292. 1: FTermAttr.Bold := True;
  1293. 4: FTermAttr.Underline := True;
  1294. 7: FTermAttr.Invert := True;
  1295. 22: FTermAttr.Bold := False;
  1296. 24: FTermAttr.Underline := False;
  1297. 27: FTermAttr.Invert := False;
  1298. // Extended foreground color
  1299. 38: FTermAttr.FrontColor := GetExtendedColor(I);
  1300. // Default foreground color
  1301. 39: FTermAttr.FrontColor := Font.Color;
  1302. // Extended background color
  1303. 48: FTermAttr.BackColor := GetExtendedColor(I);
  1304. // Default background color
  1305. 49: FTermAttr.BackColor := Color;
  1306. // NEW foreground colors
  1307. else if (Value in [30..37]) then
  1308. FTermAttr.FrontColor := Color256Table[Value - 30]
  1309. // NEW background colors
  1310. else if (Value in [40..47]) then
  1311. FTermAttr.BackColor := Color256Table[Value - 40]
  1312. // BRIGHT foreground colors
  1313. else if (Value in [90..97]) then
  1314. FTermAttr.FrontColor := Color256Table[Value - 90 + 8]
  1315. // BRIGHT background colors
  1316. else if (Value in [100..107]) then
  1317. FTermAttr.BackColor := Color256Table[Value - 100 + 8]
  1318. else begin
  1319. DoUnhandledCode(ecAttributes, IntToStr(Value));
  1320. end;
  1321. end;
  1322. Inc(I);
  1323. end;
  1324. end;
  1325. procedure TCustomComTerminal.SetMode(AParams: TStrings; OnOff: Boolean);
  1326. var
  1327. Str: string;
  1328. begin
  1329. if AParams.Count = 0 then
  1330. Exit;
  1331. Str := AParams[0];
  1332. if Str = '?1' then
  1333. begin
  1334. if OnOff then
  1335. FTermMode.Keys := akWindows
  1336. else
  1337. FTermMode.Keys := akTerminal;
  1338. end
  1339. else if Str = '?7' then
  1340. FWrapLines := OnOff
  1341. else if Str = '?3' then
  1342. begin
  1343. if OnOff then
  1344. Columns := 132
  1345. else
  1346. Columns := 80;
  1347. end
  1348. else if Str = '?1002' then
  1349. FTermMode.MouseTrack:= OnOff
  1350. else if Str = '?1006' then
  1351. FTermMode.MouseMode:= OnOff
  1352. else if Str = '?1049' then
  1353. begin
  1354. FBuffer.FTopLeft:= FTopLeft;
  1355. FBuffer.FCaretPos:= FCaretPos;
  1356. if OnOff then
  1357. FBuffer := FAlternateBuffer
  1358. else begin
  1359. FBuffer := FMainBuffer;
  1360. end;
  1361. FTopLeft:= FBuffer.FTopLeft;
  1362. FCaretPos:= FBuffer.FCaretPos;
  1363. UpdateScrollRange;
  1364. Invalidate;
  1365. end
  1366. else begin
  1367. DoUnhandledMode(Str, OnOff);
  1368. end;
  1369. end;
  1370. // invalidate portion of screen
  1371. procedure TCustomComTerminal.InvalidatePortion(ARect: TRect);
  1372. var
  1373. Rect: TRect;
  1374. begin
  1375. Rect.Left := Max((ARect.Left - FTopLeft.X) * FFontWidth, 0);
  1376. Rect.Right := Max((ARect.Right - FTopLeft.X + 1) * FFontWidth, 0);
  1377. Rect.Top := Max((ARect.Top - FTopLeft.Y) * FFontHeight, 0);
  1378. Rect.Bottom := Max((ARect.Bottom - FTopLeft.Y + 1) * FFontHeight, 0);
  1379. InvalidateRect(Handle, @Rect, True);
  1380. end;
  1381. // modify scroll bar
  1382. procedure TCustomComTerminal.ModifyScrollBar(ScrollBar, ScrollCode,
  1383. Pos: Integer);
  1384. var
  1385. CellSize, OldPos, APos, Dx, Dy: Integer;
  1386. begin
  1387. if (ScrollCode = SB_ENDSCROLL) or
  1388. ((ScrollCode = SB_THUMBTRACK) and not FSmoothScroll)
  1389. then
  1390. Exit;
  1391. if ScrollBar = SB_HORZ then
  1392. CellSize := FFontWidth
  1393. else
  1394. CellSize := FFontHeight;
  1395. APos := GetScrollPos(Handle, ScrollBar);
  1396. OldPos := APos;
  1397. case ScrollCode of
  1398. SB_LINEUP: Dec(APos);
  1399. SB_LINEDOWN: Inc(APos);
  1400. SB_PAGEUP: Dec(APos, ClientHeight div CellSize);
  1401. SB_PAGEDOWN: Inc(APos, ClientHeight div CellSize);
  1402. SB_THUMBPOSITION,
  1403. SB_THUMBTRACK: APos := Pos;
  1404. end;
  1405. SetScrollPos(Handle, ScrollBar, APos, True);
  1406. APos := GetScrollPos(Handle, ScrollBar);
  1407. if ScrollBar = SB_HORZ then
  1408. begin
  1409. FTopLeft.X := APos + 1;
  1410. Dx := (OldPos - APos) * FFontWidth;
  1411. Dy := 0;
  1412. end else
  1413. begin
  1414. FTopLeft.Y := APos + 1;
  1415. Dx := 0;
  1416. Dy := (OldPos - APos) * FFontHeight;
  1417. end;
  1418. if DoubleBuffered then
  1419. Invalidate
  1420. else
  1421. ScrollWindowEx(Handle, Dx, Dy, nil, nil, 0, nil, SW_ERASE or SW_INVALIDATE);
  1422. end;
  1423. // calculate scroll position
  1424. procedure TCustomComTerminal.UpdateScrollPos;
  1425. begin
  1426. if FScrollBars in [ssBoth, ssHorizontal] then
  1427. begin
  1428. SetScrollPos(Handle, SB_HORZ, FTopLeft.X - 1, True);
  1429. end;
  1430. if FScrollBars in [ssBoth, ssVertical] then
  1431. begin
  1432. SetScrollPos(Handle, SB_VERT, FTopLeft.Y - 1, True);
  1433. end;
  1434. end;
  1435. // calculate scroll range
  1436. procedure TCustomComTerminal.UpdateScrollRange;
  1437. var
  1438. OldScrollBars: TScrollStyle;
  1439. AHeight, AWidth: Integer;
  1440. // is scroll bar visible?
  1441. function ScrollBarVisible(Code: Word): Boolean;
  1442. var
  1443. Min, Max: Integer;
  1444. begin
  1445. Result := False;
  1446. if (ScrollBars = ssBoth) or
  1447. ((Code = SB_HORZ) and (ScrollBars = ssHorizontal)) or
  1448. ((Code = SB_VERT) and (ScrollBars = ssVertical)) then
  1449. begin
  1450. GetScrollRange(Handle, Code, Min, Max);
  1451. Result := Min <> Max;
  1452. end;
  1453. end;
  1454. procedure SetRange(Code, Max: Integer);
  1455. var
  1456. Info: TScrollInfo;
  1457. begin
  1458. Info:= Default(TScrollInfo);
  1459. Info.fMask := SIF_RANGE or SIF_PAGE;
  1460. Info.nMax := Max;
  1461. Info.nPage := 1;
  1462. SetScrollInfo(Handle, Code, Info, False);
  1463. end;
  1464. // set horizontal range
  1465. procedure SetHorzRange;
  1466. var
  1467. Max: Integer;
  1468. AColumns: Integer;
  1469. begin
  1470. if OldScrollBars in [ssBoth, ssHorizontal] then
  1471. begin
  1472. AColumns := AWidth div FFontWidth;
  1473. if AColumns >= FBuffer.Columns then
  1474. SetRange(SB_HORZ, 1) // screen is wide enough, hide scroll bar
  1475. else
  1476. begin
  1477. Max := FBuffer.Columns - (AColumns - 1);
  1478. SetRange(SB_HORZ, Max);
  1479. end;
  1480. end;
  1481. end;
  1482. // set vertical range
  1483. procedure SetVertRange;
  1484. var
  1485. Max, ARows: Integer;
  1486. begin
  1487. if OldScrollBars in [ssBoth, ssVertical] then
  1488. begin
  1489. ARows := AHeight div FFontHeight;
  1490. if ARows >= FBuffer.Rows then
  1491. SetRange(SB_VERT, 1) // screen is high enough, hide scroll bar
  1492. else
  1493. begin
  1494. Max := FBuffer.Rows - (ARows - 1);
  1495. SetRange(SB_VERT, Max);
  1496. end;
  1497. end;
  1498. end;
  1499. begin
  1500. if (FScrollBars = ssNone) or (FBuffer = nil) then
  1501. Exit;
  1502. AHeight := ClientHeight;
  1503. AWidth := ClientWidth;
  1504. if ScrollBarVisible(SB_HORZ) then
  1505. Inc(AHeight, GetSystemMetrics(SM_CYHSCROLL));
  1506. if ScrollBarVisible(SB_VERT) then
  1507. Inc(AWidth, GetSystemMetrics(SM_CXVSCROLL));
  1508. // Temporarily mark us as not having scroll bars to avoid recursion
  1509. OldScrollBars := FScrollBars;
  1510. FScrollBars := ssNone;
  1511. try
  1512. SetHorzRange;
  1513. AHeight := ClientHeight;
  1514. SetVertRange;
  1515. if AWidth <> ClientWidth then
  1516. begin
  1517. AWidth := ClientWidth;
  1518. SetHorzRange;
  1519. end;
  1520. finally
  1521. FScrollBars := OldScrollBars;
  1522. end;
  1523. // range changed, update scroll bar position
  1524. UpdateScrollPos;
  1525. end;
  1526. // hide caret
  1527. procedure TCustomComTerminal.HideCaret;
  1528. begin
  1529. if FCaretCreated then
  1530. LCLIntf.HideCaret(Handle);
  1531. end;
  1532. // show caret
  1533. procedure TCustomComTerminal.ShowCaret;
  1534. begin
  1535. if FCaretCreated then
  1536. LCLIntf.ShowCaret(Handle);
  1537. end;
  1538. // send character to com port
  1539. procedure TCustomComTerminal.SendChar(Ch: TUTF8Char);
  1540. begin
  1541. if (FPtyDevice <> nil) and (FPtyDevice.Connected) then
  1542. begin
  1543. FPtyDevice.WriteStr(Ch);
  1544. if FLocalEcho then
  1545. begin
  1546. // local echo is on, show character on screen
  1547. HideCaret;
  1548. PutChar(Ch);
  1549. ShowCaret;
  1550. end;
  1551. // send line feeds after carriage return
  1552. if (Ch = Chr(13)) and FSendLF then
  1553. SendChar(Chr(10));
  1554. end;
  1555. end;
  1556. // send escape code
  1557. procedure TCustomComTerminal.SendCode(Code: TEscapeCode; AParams: TStrings);
  1558. begin
  1559. if (FPtyDevice <> nil) and (FPtyDevice.Connected) and (FEscapeCodes <> nil) then
  1560. begin
  1561. FPtyDevice.WriteStr(FEscapeCodes.EscCodeToStr(Code, AParams));
  1562. if FLocalEcho then
  1563. begin
  1564. // local echo is on, show character on screen
  1565. HideCaret;
  1566. PutEscapeCode(Code, AParams);
  1567. ShowCaret;
  1568. end;
  1569. end;
  1570. end;
  1571. // send escape code to port
  1572. procedure TCustomComTerminal.SendCodeNoEcho(Code: TEscapeCode; AParams: TStrings);
  1573. begin
  1574. if (FPtyDevice <> nil) and (FPtyDevice.Connected) and (FEscapeCodes <> nil) then
  1575. FPtyDevice.WriteStr(FEscapeCodes.EscCodeToStr(Code, AParams));
  1576. end;
  1577. procedure TCustomComTerminal.MouseEvent(Code: TEscapeCode;
  1578. Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  1579. var
  1580. AButton: Integer;
  1581. begin
  1582. if (FTermMode.MouseMode and FTermMode.MouseTrack) then
  1583. begin
  1584. case Button of
  1585. mbLeft: AButton:= 0;
  1586. mbRight: AButton:= 2;
  1587. mbMiddle: AButton:= 1;
  1588. else AButton:= Ord(Button);
  1589. end;
  1590. FParams.Text:= IntToStr(AButton);
  1591. FParams.Add(IntToStr(X div FFontWidth + 1));
  1592. FParams.Add(IntToStr(Y div FFontHeight + 1));
  1593. SendCodeNoEcho(Code, FParams);
  1594. end;
  1595. end;
  1596. // process escape code on screen
  1597. function TCustomComTerminal.PutEscapeCode(ACode: TEscapeCode; AParams: TStrings): Boolean;
  1598. begin
  1599. Result := True;
  1600. with FEscapeCodes do
  1601. case ACode of
  1602. ecCursorUp: MoveCaret(FCaretPos.X, FCaretPos.Y - GetParam(1, AParams));
  1603. ecCursorDown: MoveCaret(FCaretPos.X, FCaretPos.Y + GetParam(1, AParams));
  1604. ecCursorRight: MoveCaret(FCaretPos.X + GetParam(1, AParams), FCaretPos.Y);
  1605. ecCursorLeft: MoveCaret(FCaretPos.X - GetParam(1, AParams), FCaretPos.Y);
  1606. ecCursorNextLine: MoveCaret(1, FCaretPos.Y + GetParam(1, AParams));
  1607. ecCursorPrevLine: MoveCaret(1, FCaretPos.Y - GetParam(1, AParams));
  1608. ecCursorMove: MoveCaret(GetParam(2, AParams), GetParam(1, AParams));
  1609. ecCursorMoveX: MoveCaret(GetParam(1, AParams), FCaretPos.Y);
  1610. ecCursorMoveY: MoveCaret(FCaretPos.X, GetParam(1, AParams));
  1611. ecReverseLineFeed: AdvanceCaret(acReverseLineFeed);
  1612. ecEraseLineLeft: FBuffer.EraseLineLeft(FCaretPos.X, FCaretPos.Y);
  1613. ecEraseLineRight: FBuffer.EraseLineRight(FCaretPos.X, FCaretPos.Y);
  1614. ecEraseLine:
  1615. begin
  1616. FBuffer.EraseLineRight(1, FCaretPos.Y);
  1617. MoveCaret(1, FCaretPos.Y)
  1618. end;
  1619. ecEraseScreenLeft: FBuffer.EraseScreenLeft(FCaretPos.X, FCaretPos.Y);
  1620. ecEraseScreenRight: FBuffer.EraseScreenRight(FCaretPos.X, FCaretPos.Y);
  1621. ecEraseScreen:
  1622. begin
  1623. FBuffer.EraseScreenRight(1, 1);
  1624. MoveCaret(1, 1)
  1625. end;
  1626. ecEraseChar: FBuffer.EraseChar(FCaretPos.X, FCaretPos.Y, GetParam(1, AParams));
  1627. ecDeleteChar: FBuffer.DeleteChar(FCaretPos.X, FCaretPos.Y, GetParam(1, AParams));
  1628. ecIdentify:
  1629. begin
  1630. AParams.Clear;
  1631. AParams.Add('2');
  1632. SendCodeNoEcho(ecIdentResponse, AParams);
  1633. end;
  1634. ecSetTab: FBuffer.SetTab(FCaretPos.X, True);
  1635. ecClearTab: FBuffer.SetTab(FCaretPos.X, False);
  1636. ecClearAllTabs: FBuffer.ClearAllTabs;
  1637. ecAttributes: SetAttributes(AParams);
  1638. ecSetMode: SetMode(AParams, True);
  1639. ecResetMode: SetMode(AParams, False);
  1640. ecReset:
  1641. begin
  1642. AParams.Clear;
  1643. AParams.Add('0');
  1644. SetAttributes(AParams);
  1645. end;
  1646. ecSaveCaret: SaveCaretPos;
  1647. ecRestoreCaret: RestoreCaretPos;
  1648. ecSaveCaretAndAttr: begin SaveCaretPos; SaveAttr; end;
  1649. ecRestoreCaretAndAttr: begin RestoreCaretPos; RestoreAttr; end;
  1650. ecQueryCursorPos:
  1651. begin
  1652. AParams.Clear;
  1653. AParams.Add(IntToStr(FCaretPos.Y));
  1654. AParams.Add(IntToStr(FCaretPos.X));
  1655. SendCodeNoEcho(ecReportCursorPos, AParams);
  1656. end;
  1657. ecQueryDevice: SendCodeNoEcho(ecReportDeviceOK, nil);
  1658. ecTest: PerformTest('E');
  1659. ecScrollRegion:
  1660. begin
  1661. FBuffer.FScrollRange.Top:= GetParam(1, AParams);
  1662. FBuffer.FScrollRange.Bottom:= GetParam(2, AParams);
  1663. end;
  1664. ecScrollDown,
  1665. ecInsertLine: FBuffer.InsertLine(FCaretPos.Y, GetParam(1, AParams));
  1666. ecScrollUp,
  1667. ecDeleteLine: FBuffer.DeleteLine(FCaretPos.Y, GetParam(1, AParams));
  1668. ecSoftReset:
  1669. begin
  1670. FTermMode.CharSet:= False;
  1671. FBuffer.FScrollRange.Top:= 1;
  1672. FBuffer.FScrollRange.Bottom:= FBuffer.Rows;
  1673. end;
  1674. ecCharSet:
  1675. begin
  1676. // Designate Character Set
  1677. if (AParams.Count > 0) and (Length(AParams[0]) > 0) then
  1678. FTermMode.CharSet:= (AParams[0] = '0');
  1679. end
  1680. else
  1681. Result := False;
  1682. end;
  1683. end;
  1684. // calculate font height and width
  1685. function TCustomComTerminal.CalculateMetrics: Boolean;
  1686. var
  1687. Metrics: TTextMetric;
  1688. begin
  1689. GetTextMetrics(Canvas.Handle, Metrics);
  1690. FFontHeight := Metrics.tmHeight;
  1691. FFontWidth := Metrics.tmAveCharWidth;
  1692. // allow only fixed pitch fonts
  1693. Result := (Metrics.tmPitchAndFamily and TMPF_FIXED_PITCH) = 0;
  1694. end;
  1695. // visual character is appears on screen
  1696. procedure TCustomComTerminal.DoChar(Ch: TUTF8Char);
  1697. begin
  1698. if Assigned(FOnChar) then
  1699. FOnChar(Self, Ch);
  1700. end;
  1701. // get custom escape codes processor
  1702. procedure TCustomComTerminal.DoGetEscapeCodes(
  1703. var EscapeCodes: TEscapeCodes);
  1704. begin
  1705. if Assigned(FOnGetEscapeCodes) then
  1706. FOnGetEscapeCodes(Self, EscapeCodes);
  1707. end;
  1708. // string recieved
  1709. procedure TCustomComTerminal.DoStrRecieved(var Str: string);
  1710. begin
  1711. if Assigned(FOnStrRecieved) then
  1712. FOnStrRecieved(Self, Str);
  1713. end;
  1714. // let application handle unhandled escape code
  1715. procedure TCustomComTerminal.DoUnhandledCode(Code: TEscapeCode;
  1716. Data: string);
  1717. begin
  1718. if Assigned(FOnUnhandledCode) then
  1719. FOnUnhandledCode(Self, Code, Data);
  1720. end;
  1721. procedure TCustomComTerminal.DoUnhandledMode(const Data: string; OnOff: Boolean);
  1722. begin
  1723. if Assigned(FOnUnhandledMode) then
  1724. FOnUnhandledMode(Self, Data, OnOff);
  1725. end;
  1726. function TCustomComTerminal.DoMouseWheel(Shift: TShiftState;
  1727. WheelDelta: Integer; MousePos: TPoint): Boolean;
  1728. var
  1729. APos: Integer;
  1730. begin
  1731. Result:= True;
  1732. APos:= GetScrollPos(Handle, SB_VERT);
  1733. if WheelDelta < 0 then
  1734. APos:= APos + Mouse.WheelScrollLines
  1735. else begin
  1736. APos:= APos - Mouse.WheelScrollLines;
  1737. end;
  1738. ModifyScrollBar(SB_VERT, SB_THUMBPOSITION, APos);
  1739. end;
  1740. // create escape codes processor
  1741. procedure TCustomComTerminal.CreateEscapeCodes;
  1742. begin
  1743. if csDesigning in ComponentState then
  1744. Exit;
  1745. case FEmulation of
  1746. teVT52: FEscapeCodes := TEscapeCodesVT52.Create;
  1747. teVT100orANSI: FEscapeCodes := TEscapeCodesVT100.Create;
  1748. else
  1749. begin
  1750. FEscapeCodes := nil;
  1751. DoGetEscapeCodes(FEscapeCodes);
  1752. end;
  1753. end;
  1754. end;
  1755. // perform screen test
  1756. procedure TCustomComTerminal.PerformTest(ACh: Char);
  1757. var
  1758. I, J: Integer;
  1759. TermCh: TComTermChar;
  1760. begin
  1761. with TermCh do
  1762. begin
  1763. Ch := ACh;
  1764. FrontColor := Font.Color;
  1765. BackColor := Color;
  1766. Underline := False;
  1767. end;
  1768. for I := 1 to FBuffer.Columns do
  1769. for J := 1 to FBuffer.Rows do
  1770. FBuffer.SetChar(I, J, TermCh);
  1771. Invalidate;
  1772. end;
  1773. // get current character properties
  1774. function TCustomComTerminal.GetCharAttr: TComTermChar;
  1775. begin
  1776. if FTermAttr.Invert then
  1777. // Result.FrontColor := Color
  1778. Result.FrontColor := FTermAttr.BackColor
  1779. else
  1780. // Result.BackColor := Font.Color;
  1781. Result.FrontColor := FTermAttr.FrontColor;
  1782. if FTermAttr.Invert then
  1783. // Result.BackColor := Font.Color
  1784. Result.BackColor := FTermAttr.FrontColor
  1785. else
  1786. // Result.FrontColor := Color
  1787. Result.BackColor := FTermAttr.BackColor;
  1788. // NEW end changes
  1789. Result.Bold := FTermAttr.Bold;
  1790. Result.Underline := FTermAttr.Underline;
  1791. Result.Ch := #0;
  1792. end;
  1793. // put one character on screen
  1794. procedure TCustomComTerminal.PutChar(Ch: TUTF8Char);
  1795. var
  1796. AWidth: Integer;
  1797. TermCh: TComTermChar;
  1798. begin
  1799. case Ch[1] of
  1800. #8: AdvanceCaret(acBackspace);
  1801. #9: AdvanceCaret(acTab);
  1802. #10: AdvanceCaret(acLineFeed);
  1803. #12: AdvanceCaret(acPage);
  1804. #13: AdvanceCaret(acReturn);
  1805. #32..#255:
  1806. begin
  1807. TermCh := GetCharAttr;
  1808. if not FTermMode.CharSet then
  1809. TermCh.Ch := Ch
  1810. else begin
  1811. case Ch[1] of
  1812. 'j': TermCh.Ch := '┘';
  1813. 'k': TermCh.Ch := '┐';
  1814. 'l': TermCh.Ch := '┌';
  1815. 'm': TermCh.Ch := '└';
  1816. 'n': TermCh.Ch := '┼';
  1817. 'q': TermCh.Ch := '─';
  1818. 't': TermCh.Ch := '├';
  1819. 'u': TermCh.Ch := '┤';
  1820. 'v': TermCh.Ch := '┴';
  1821. 'w': TermCh.Ch := '┬';
  1822. 'x': TermCh.Ch := '│';
  1823. else TermCh.Ch := Ch;
  1824. end;
  1825. end;
  1826. AWidth:= UTF8Width(Ch);
  1827. if AWidth <= 0 then Exit;
  1828. if FWrapLines then WrapLine(AWidth);
  1829. FBuffer.SetChar(FCaretPos.X, FCaretPos.Y, TermCh);
  1830. DrawChar(FCaretPos.X - FTopLeft.X + 1, FCaretPos.Y - FTopLeft.Y + 1, TermCh);
  1831. AdvanceCaret(acChar);
  1832. Dec(AWidth);
  1833. while (AWidth > 0) do
  1834. begin
  1835. TermCh.Ch := #0;
  1836. FBuffer.SetChar(FCaretPos.X, FCaretPos.Y, TermCh);
  1837. AdvanceCaret(acChar);
  1838. Dec(AWidth);
  1839. end;
  1840. end;
  1841. end;
  1842. DoChar(Ch);
  1843. end;
  1844. // init caret
  1845. procedure TCustomComTerminal.InitCaret;
  1846. begin
  1847. CreateTerminalCaret;
  1848. MoveCaret(FCaretPos.X, FCaretPos.Y);
  1849. ShowCaret;
  1850. end;
  1851. // restore caret position
  1852. procedure TCustomComTerminal.RestoreCaretPos;
  1853. begin
  1854. MoveCaret(FSaveCaret.X, FSaveCaret.Y);
  1855. end;
  1856. // save caret position
  1857. procedure TCustomComTerminal.SaveCaretPos;
  1858. begin
  1859. FSaveCaret := FCaretPos;
  1860. end;
  1861. // restore attributes
  1862. procedure TCustomComTerminal.RestoreAttr;
  1863. begin
  1864. FTermAttr := FSaveAttr;
  1865. end;
  1866. // save attributes
  1867. procedure TCustomComTerminal.SaveAttr;
  1868. begin
  1869. FSaveAttr := FTermAttr;
  1870. end;
  1871. procedure TCustomComTerminal.RxBuf(Sender: TObject; const Buffer; Count: Integer);
  1872. var
  1873. L: Integer;
  1874. Str: String;
  1875. // append line feeds to carriage return
  1876. procedure AppendLineFeeds;
  1877. var
  1878. I: Integer;
  1879. begin
  1880. I := 1;
  1881. while I <= Length(Str) do
  1882. begin
  1883. if Str[I] = Chr(13) then
  1884. Str := Copy(Str, 1, I) + Chr(10) + Copy(Str, I + 1, Length(Str) - I);
  1885. Inc(I);
  1886. end;
  1887. end;
  1888. // convert to 7 bit data
  1889. procedure Force7BitData;
  1890. var
  1891. I: Integer;
  1892. begin
  1893. for I := 1 to Length(Str) do
  1894. Str[I] := Char(Byte(Str[I]) and $0FFFFFFF);
  1895. end;
  1896. begin
  1897. if (Length(FPartChar) = 0) then
  1898. begin
  1899. SetLength(Str, Count);
  1900. Move(Buffer, Str[1], Count);
  1901. end
  1902. else begin
  1903. L:= Length(FPartChar);
  1904. SetLength(Str, Count + L);
  1905. Move(FPartChar[1], Str[1], L);
  1906. Move(Buffer, Str[L + 1], Count);
  1907. FPartChar:= EmptyStr;
  1908. end;
  1909. if FForce7Bit then
  1910. begin
  1911. Force7BitData;
  1912. end;
  1913. if FAppendLF then
  1914. begin
  1915. AppendLineFeeds;
  1916. end;
  1917. StringReceived(Str);
  1918. end;
  1919. function TCustomComTerminal.GetConnected: Boolean;
  1920. begin
  1921. Result := False;
  1922. if FPtyDevice <> nil then
  1923. Result := FPtyDevice.Connected;
  1924. end;
  1925. procedure TCustomComTerminal.SetConnected(const Value: Boolean);
  1926. begin
  1927. if FPtyDevice <> nil then
  1928. FPtyDevice.Connected := Value;
  1929. end;
  1930. procedure TCustomComTerminal.SetScrollBars(const Value: TScrollStyle);
  1931. begin
  1932. if FScrollBars <> Value then
  1933. begin
  1934. FScrollBars := Value;
  1935. RecreateWnd(Self);
  1936. end;
  1937. end;
  1938. procedure TCustomComTerminal.SetColumns(const Value: Integer);
  1939. begin
  1940. if Value <> FColumns then
  1941. begin
  1942. FColumns := Min(Max(2, Value), 256);
  1943. AdjustSize;
  1944. if not ((csLoading in ComponentState) or (csDesigning in ComponentState)) then
  1945. begin
  1946. FMainBuffer.Init(0, FColumns);
  1947. FAlternateBuffer.Init(0, FColumns);
  1948. if Assigned(FPtyDevice) then
  1949. FPtyDevice.SetScreenSize(FColumns, FVisibleRows);
  1950. Invalidate;
  1951. end;
  1952. UpdateScrollRange;
  1953. end;
  1954. end;
  1955. procedure TCustomComTerminal.SetRows(const Value: Integer);
  1956. var
  1957. ARows: Integer;
  1958. begin
  1959. ARows := Max(Value, FVisibleRows);
  1960. if ARows <> FRows then
  1961. begin
  1962. FRows := ARows;
  1963. if not ((csLoading in ComponentState) or (csDesigning in ComponentState)) then
  1964. begin
  1965. FMainBuffer.Init(FRows, 0);
  1966. end;
  1967. UpdateScrollRange;
  1968. end;
  1969. end;
  1970. procedure TCustomComTerminal.SetEmulation(const Value: TTermEmulation);
  1971. begin
  1972. if FEmulation <> Value then
  1973. begin
  1974. FEmulation := Value;
  1975. if not (csLoading in ComponentState) then
  1976. begin
  1977. FEscapeCodes.Free;
  1978. CreateEscapeCodes;
  1979. end;
  1980. end;
  1981. end;
  1982. procedure TCustomComTerminal.SetCaret(const Value: TTermCaret);
  1983. begin
  1984. if Value <> FCaret then
  1985. begin
  1986. FCaret := Value;
  1987. if Focused then
  1988. begin
  1989. DestroyCaret(Handle);
  1990. InitCaret;
  1991. end;
  1992. end;
  1993. end;
  1994. procedure TCustomComTerminal.SetPtyDevice(const Value: TCustomPtyDevice);
  1995. begin
  1996. if Value <> FPtyDevice then
  1997. begin
  1998. if FPtyDevice <> nil then
  1999. begin
  2000. FPtyDevice.OnRxBuf:= nil;
  2001. end;
  2002. FPtyDevice := Value;
  2003. if FPtyDevice <> nil then
  2004. begin
  2005. FPtyDevice.OnRxBuf:= RxBuf;
  2006. FPtyDevice.FreeNotification(Self);
  2007. FPtyDevice.SetScreenSize(FColumns, FVisibleRows);
  2008. end;
  2009. end;
  2010. end;
  2011. end.