vtemuesc.pas 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  1. {
  2. Double Commander
  3. -------------------------------------------------------------------------
  4. Virtual terminal emulator escape codes
  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. License:
  13. Public Domain
  14. }
  15. unit VTEmuEsc;
  16. {$mode delphi}
  17. interface
  18. uses
  19. Classes, LCLType;
  20. type
  21. // terminal character result
  22. TEscapeResult = (erChar, erCode, erNothing);
  23. // terminal escape codes
  24. TEscapeCode = (ecUnknown, ecNotCompleted, ecCursorUp, ecCursorDown, ecCursorLeft,
  25. ecCursorRight, ecCursorHome, ecCursorEnd, ecCursorMove, ecCursorMoveX, ecCursorMoveY,
  26. ecReverseLineFeed, ecAppCursorLeft, ecAppCursorRight, ecAppCursorUp, ecAppCursorDown,
  27. ecAppCursorHome, ecAppCursorEnd, ecCursorNextLine, ecCursorPrevLine, ecInsertKey,
  28. ecDeleteKey, ecPageUpKey, ecPageDownKey,
  29. ecMouseDown, ecMouseUp, ecEraseLineLeft, ecEraseLineRight, ecEraseScreenRight, ecEraseScreenLeft,
  30. ecEraseLine, ecEraseScreen, ecEraseChar, ecDeleteChar, ecSetTab, ecClearTab, ecClearAllTabs,
  31. ecIdentify, ecIdentResponse, ecQueryDevice, ecReportDeviceOK,
  32. ecReportDeviceFailure, ecQueryCursorPos, ecReportCursorPos,
  33. ecAttributes, ecSetMode, ecResetMode, ecReset, ecCharSet, ecSoftReset,
  34. ecSaveCaretAndAttr, ecRestoreCaretAndAttr, ecSaveCaret, ecRestoreCaret,
  35. ecTest, ecFuncKey, ecSetTextParams, ecScrollRegion, ecDeleteLine,
  36. ecInsertLine, ecKeypadApp, ecKeypadNum, ecScrollUp, ecScrollDown);
  37. // terminal escape codes processor
  38. TEscapeCodes = class
  39. private
  40. FCharacter: TUTF8Char;
  41. FCode: TEscapeCode;
  42. FData: string;
  43. FParams: TStrings;
  44. public
  45. constructor Create;
  46. destructor Destroy; override;
  47. function ProcessChar(const Ch: TUTF8Char): TEscapeResult; virtual; abstract;
  48. function EscCodeToStr(Code: TEscapeCode; AParams: TStrings): string; virtual; abstract;
  49. function GetParam(Num: Integer; AParams: TStrings): Integer;
  50. property Data: string read FData;
  51. property Code: TEscapeCode read FCode;
  52. property Character: TUTF8Char read FCharacter;
  53. property Params: TStrings read FParams;
  54. end;
  55. // VT52 escape codes
  56. TEscapeCodesVT52 = class(TEscapeCodes)
  57. private
  58. FInSequence: Boolean;
  59. function DetectCode(Str: string): TEscapeCode;
  60. public
  61. function ProcessChar(const Ch: TUTF8Char): TEscapeResult; override;
  62. function EscCodeToStr(Code: TEscapeCode; AParams: TStrings): string; override;
  63. end;
  64. // ANSI/VT100 escape codes
  65. TEscapeCodesVT100 = class(TEscapeCodes)
  66. private
  67. FInSequence: Boolean;
  68. FInExtSequence: Boolean;
  69. FInOscSequence: Boolean;
  70. function DetectCode(Str: string): TEscapeCode;
  71. function DetectExtCode(Str: string): TEscapeCode;
  72. function DetectOscCode(Str: string): TEscapeCode;
  73. public
  74. function ProcessChar(const Ch: TUTF8Char): TEscapeResult; override;
  75. function EscCodeToStr(Code: TEscapeCode; AParams: TStrings): string; override;
  76. end;
  77. implementation
  78. uses
  79. SysUtils;
  80. (*****************************************
  81. * TEscapeCodes class *
  82. *****************************************)
  83. constructor TEscapeCodes.Create;
  84. begin
  85. inherited Create;
  86. FParams := TStringList.Create;
  87. end;
  88. destructor TEscapeCodes.Destroy;
  89. begin
  90. FParams.Free;
  91. inherited Destroy;
  92. end;
  93. function TEscapeCodes.GetParam(Num: Integer; AParams: TStrings): Integer;
  94. begin
  95. if (AParams = nil) or (AParams.Count < Num) then
  96. Result := 1
  97. else
  98. try
  99. Result := StrToInt(AParams[Num - 1]);
  100. except
  101. Result := 1;
  102. end;
  103. end;
  104. (*****************************************
  105. * TEscapeCodesVT52 class *
  106. *****************************************)
  107. // process character
  108. function TEscapeCodesVT52.ProcessChar(const Ch: TUTF8Char): TEscapeResult;
  109. var
  110. TempCode: TEscapeCode;
  111. begin
  112. Result := erNothing;
  113. if not FInSequence then
  114. begin
  115. if Ch = #27 then
  116. begin
  117. FData := '';
  118. FInSequence := True;
  119. end
  120. else begin
  121. FCharacter := Ch;
  122. Result := erChar;
  123. end;
  124. end else
  125. begin
  126. FData := FData + Ch;
  127. TempCode := DetectCode(FData);
  128. if TempCode <> ecNotCompleted then
  129. begin
  130. FCode := TempCode;
  131. FInSequence := False;
  132. Result := erCode;
  133. end;
  134. end;
  135. end;
  136. // escape code to string
  137. function TEscapeCodesVT52.EscCodeToStr(Code: TEscapeCode; AParams: TStrings): string;
  138. begin
  139. case Code of
  140. ecCursorUp: Result := #27'A';
  141. ecCursorDown: Result := #27'B';
  142. ecCursorRight: Result := #27'C';
  143. ecCursorLeft: Result := #27'D';
  144. ecCursorHome: Result := #27'H';
  145. ecReverseLineFeed: Result := #27'I';
  146. ecEraseScreenRight: Result := #27'J';
  147. ecEraseLineRight: Result := #27'K';
  148. ecIdentify: Result := #27'Z';
  149. ecIdentResponse: Result := #27'/Z';
  150. ecCursorMove: Result := #27'Y' +
  151. Chr(GetParam(1, AParams) + 31) + Chr(GetParam(2, AParams) + 31);
  152. else
  153. Result := '';
  154. end;
  155. end;
  156. // get escape code from string
  157. function TEscapeCodesVT52.DetectCode(Str: string): TEscapeCode;
  158. begin
  159. Result := ecUnknown;
  160. case Str[1] of
  161. 'A': Result := ecCursorUp;
  162. 'B': Result := ecCursorDown;
  163. 'C': Result := ecCursorRight;
  164. 'D': Result := ecCursorLeft;
  165. 'H': Result := ecCursorMove;
  166. 'I': Result := ecReverseLineFeed;
  167. 'J': Result := ecEraseScreenRight;
  168. 'K': Result := ecEraseLineRight;
  169. 'Z': Result := ecIdentify;
  170. '/': begin
  171. if Length(Str) = 1 then
  172. Result := ecNotCompleted
  173. else
  174. if (Length(Str) = 2) and (Str = '/Z') then
  175. Result := ecIdentResponse;
  176. end;
  177. 'Y': begin
  178. if Length(Str) < 3 then
  179. Result := ecNotCompleted
  180. else
  181. begin
  182. Result := ecCursorMove;
  183. FParams.Add(IntToStr(Ord(Str[3]) - 31));
  184. FParams.Add(IntToStr(Ord(Str[2]) - 31));
  185. end;
  186. end;
  187. end;
  188. end;
  189. (*****************************************
  190. * TEscapeCodesVT100class *
  191. *****************************************)
  192. // process character
  193. function TEscapeCodesVT100.ProcessChar(const Ch: TUTF8Char): TEscapeResult;
  194. var
  195. TempCode: TEscapeCode;
  196. begin
  197. Result := erNothing;
  198. if not FInSequence then
  199. begin
  200. if Ch = #27 then
  201. begin
  202. FData := '';
  203. FInSequence := True;
  204. end
  205. else begin
  206. FCharacter := Ch;
  207. Result := erChar;
  208. end;
  209. end else
  210. begin
  211. FData := FData + Ch;
  212. TempCode := ecNotCompleted;
  213. if FInExtSequence then
  214. TempCode := DetectExtCode(FData)
  215. else if FInOscSequence then
  216. TempCode := DetectOscCode(FData)
  217. else
  218. // character [ after ESC defines extended escape code
  219. if FData[1] = '[' then
  220. FInExtSequence := True
  221. else if FData[1] = ']' then
  222. FInOscSequence := True
  223. else
  224. TempCode := DetectCode(FData);
  225. if TempCode <> ecNotCompleted then
  226. begin
  227. FCode := TempCode;
  228. FInSequence := False;
  229. FInExtSequence := False;
  230. FInOscSequence := False;
  231. Result := erCode;
  232. end;
  233. end;
  234. end;
  235. // escape code to string conversion
  236. function TEscapeCodesVT100.EscCodeToStr(Code: TEscapeCode;
  237. AParams: TStrings): string;
  238. var
  239. AKey: Integer;
  240. begin
  241. case Code of
  242. ecIdentify: Result := #27'[c';
  243. ecIdentResponse: Result := Format(#27'[?1;%dc', [GetParam(1, AParams)]);
  244. ecQueryCursorPos: Result := #27'[6n';
  245. ecReportCursorPos: Result := Format(#27'[%d;%dR', [GetParam(1, AParams), GetParam(2, AParams)]);
  246. ecQueryDevice: Result := #27'[5n';
  247. ecReportDeviceOK: Result := #27'[0n';
  248. ecReportDeviceFailure: Result := #27'[3n';
  249. ecCursorUp: Result := #27'[A';
  250. ecCursorDown: Result := #27'[B';
  251. ecCursorRight: Result := #27'[C';
  252. ecAppCursorLeft: Result := #27'OD';
  253. ecAppCursorUp: Result := #27'OA';
  254. ecAppCursorDown: Result := #27'OB';
  255. ecAppCursorRight: Result := #27'OC';
  256. ecAppCursorHome: Result := #27'OH';
  257. ecAppCursorEnd: Result := #27'OF';
  258. ecCursorLeft: Result := #27'[D';
  259. ecCursorHome: Result := #27'[H';
  260. ecCursorEnd: Result := #27'[F';
  261. ecCursorMove: Result := Format(#27'[%d;%df', [GetParam(1, AParams), GetParam(2, AParams)]);
  262. ecEraseScreenRight: Result := #27'[J';
  263. ecEraseLineRight: Result := #27'[K';
  264. ecEraseScreen: Result := #27'[2J';
  265. ecEraseLine: Result := #27'[2K';
  266. ecSetTab: Result := #27'H';
  267. ecClearTab: Result := #27'[g';
  268. ecClearAllTabs: Result := #27'[3g';
  269. ecAttributes: Result := #27'[m'; // popravi
  270. ecSetMode: Result := #27'[h';
  271. ecResetMode: Result := #27'[l';
  272. ecReset: Result := #27'c';
  273. ecSaveCaret: Result := #27'[s';
  274. ecRestoreCaret: Result := #27'[u';
  275. ecSaveCaretAndAttr: Result := #27'7';
  276. ecRestoreCaretAndAttr: Result := #27'8';
  277. ecTest: Result := #27'#8';
  278. ecFuncKey:
  279. begin
  280. AKey:= GetParam(1, AParams);
  281. case AKey of
  282. 0: Result := #27'OP';
  283. 1: Result := #27'OQ';
  284. 2: Result := #27'OR';
  285. 3: Result := #27'OS';
  286. 4: Result := #27'[15~';
  287. 5: Result := #27'[17~';
  288. 6: Result := #27'[18~';
  289. 7: Result := #27'[19~';
  290. 8: Result := #27'[20~';
  291. 9: Result := #27'[21~';
  292. 10: Result := #27'[23~';
  293. 11: Result := #27'[24~';
  294. end;
  295. end;
  296. ecInsertKey: Result := #27'[2~';
  297. ecDeleteKey: Result := #27'[3~';
  298. ecPageUpKey: Result := #27'[5~';
  299. ecPageDownKey: Result := #27'[6~';
  300. ecMouseDown:
  301. Result := Format(#27'[<%d;%d;%dM', [GetParam(1, AParams), GetParam(2, AParams), GetParam(3, AParams)]);
  302. ecMouseUp:
  303. Result := Format(#27'[<%d;%d;%dm', [GetParam(1, AParams), GetParam(2, AParams), GetParam(3, AParams)]);
  304. else
  305. Result := '';
  306. end;
  307. end;
  308. // get vt100 escape code from string
  309. function TEscapeCodesVT100.DetectCode(Str: string): TEscapeCode;
  310. begin
  311. if Length(Str) = 1 then
  312. case Str[1] of
  313. 'H': Result := ecSetTab;
  314. 'c': Result := ecReset;
  315. 'M': Result := ecReverseLineFeed;
  316. '7': Result := ecSaveCaretAndAttr;
  317. '8': Result := ecRestoreCaretAndAttr;
  318. '=': Result := ecKeypadApp;
  319. '>': Result := ecKeypadNum;
  320. '#': Result := ecNotCompleted;
  321. 'O': Result := ecNotCompleted;
  322. '(': Result := ecNotCompleted;
  323. else
  324. Result := ecUnknown;
  325. end
  326. else
  327. begin
  328. Result := ecUnknown;
  329. if Str = '#8' then
  330. Result := ecTest
  331. else if Str[1] = 'O' then
  332. begin
  333. case Str[2] of
  334. 'A': Result := ecAppCursorUp;
  335. 'B': Result := ecAppCursorDown;
  336. 'C': Result := ecAppCursorRight;
  337. 'D': Result := ecAppCursorLeft;
  338. 'H': Result := ecAppCursorHome;
  339. 'F': Result := ecAppCursorEnd;
  340. end;
  341. end
  342. else if Str[1] = '(' then
  343. begin
  344. FParams.Clear;
  345. FParams.Add(Str[2]);
  346. Result:= ecCharSet;
  347. end;
  348. end;
  349. end;
  350. // get extended vt100 escape code from string
  351. function TEscapeCodesVT100.DetectExtCode(Str: string): TEscapeCode;
  352. var
  353. LastCh: Char;
  354. TempParams: TStrings;
  355. procedure ParseParams(Str: string);
  356. var
  357. I: Integer;
  358. TempStr: string;
  359. begin
  360. I := 1;
  361. TempStr := '';
  362. while I <= Length(Str) do
  363. begin
  364. if (Str[I] = ';') and (TempStr <> '') then
  365. begin
  366. TempParams.Add(TempStr);
  367. TempStr := '';
  368. end
  369. else
  370. TempStr := TempStr + Str[I];
  371. Inc(I);
  372. end;
  373. if (TempStr <> '') then
  374. TempParams.Add(TempStr);
  375. end;
  376. function CodeEraseScreen: TEscapeCode;
  377. var
  378. Str: string;
  379. begin
  380. if TempParams.Count = 0 then
  381. Result := ecEraseScreenRight
  382. else
  383. begin
  384. Str := TempParams[0];
  385. case Str[1] of
  386. '0': Result := ecEraseScreenRight;
  387. '1': Result := ecEraseScreenLeft;
  388. '2': Result := ecEraseScreen;
  389. else
  390. Result := ecUnknown;
  391. end;
  392. end;
  393. TempParams.Clear;
  394. end;
  395. function CodeEraseLine: TEscapeCode;
  396. var
  397. Str: string;
  398. begin
  399. if TempParams.Count = 0 then
  400. Result := ecEraseLineRight
  401. else
  402. begin
  403. Str := TempParams[0];
  404. case Str[1] of
  405. '0': Result := ecEraseLineRight;
  406. '1': Result := ecEraseLineLeft;
  407. '2': Result := ecEraseLine;
  408. else
  409. Result := ecUnknown;
  410. end;
  411. end;
  412. TempParams.Clear;
  413. end;
  414. function CodeTab: TEscapeCode;
  415. var
  416. Str: string;
  417. begin
  418. if TempParams.Count = 0 then
  419. Result := ecClearTab
  420. else
  421. begin
  422. Str := TempParams[0];
  423. case Str[1] of
  424. '0': Result := ecClearTab;
  425. '3': Result := ecClearAllTabs;
  426. else
  427. Result := ecUnknown;
  428. end;
  429. end;
  430. TempParams.Clear;
  431. end;
  432. function CodeDevice: TEscapeCode;
  433. var
  434. Str: string;
  435. begin
  436. if TempParams.Count = 0 then
  437. Result := ecUnknown
  438. else
  439. begin
  440. Str := TempParams[0];
  441. case Str[1] of
  442. '5': Result := ecQueryDevice;
  443. '0': Result := ecReportDeviceOK;
  444. '3': Result := ecReportDeviceFailure;
  445. '6': Result := ecQueryCursorPos;
  446. else
  447. Result := ecUnknown;
  448. end;
  449. end;
  450. TempParams.Clear;
  451. end;
  452. function CodeIdentify: TEscapeCode;
  453. begin
  454. if (TempParams.Count = 0) or
  455. ((TempParams.Count = 1) and (TempParams[0] = '0'))
  456. then
  457. Result := ecIdentify
  458. else
  459. if (TempParams.Count = 2) and (TempParams[1] = '?1') then
  460. Result := ecIdentResponse
  461. else
  462. Result := ecUnknown;
  463. end;
  464. begin
  465. Result := ecNotCompleted;
  466. LastCh := Str[Length(Str)];
  467. {$IFDEF Unicode} if not CharInSet(LastCh,['A'..'Z', 'a'..'z']) then Exit;
  468. {$ELSE} if not (LastCh in ['A'..'Z', 'a'..'z']) then Exit; {$ENDIF}
  469. TempParams := TStringList.Create;
  470. try
  471. ParseParams(Copy(Str, 2, Length(Str) - 2));
  472. case LastCh of
  473. 'A': Result := ecCursorUp;
  474. 'B': Result := ecCursorDown;
  475. 'C': Result := ecCursorRight;
  476. 'D': Result := ecCursorLeft;
  477. 'E': Result := ecCursorNextLine;
  478. 'F': Result := ecCursorPrevLine;
  479. 'H': Result := ecCursorMove;
  480. 'f': Result := ecCursorMove;
  481. 'd': Result := ecCursorMoveY;
  482. 'G': Result := ecCursorMoveX;
  483. 'J': Result := CodeEraseScreen;
  484. 'K': Result := CodeEraseLine;
  485. 'X': Result := ecEraseChar;
  486. 'P': Result := ecDeleteChar;
  487. 'g': Result := CodeTab;
  488. 'm': Result := ecAttributes;
  489. 'h': Result := ecSetMode;
  490. 'l': Result := ecResetMode;
  491. 's': Result := ecSaveCaret;
  492. 'u': Result := ecRestoreCaret;
  493. 'n': Result := CodeDevice;
  494. 'c': Result := CodeIdentify;
  495. 'R': Result := ecReportCursorPos;
  496. 'r': Result := ecScrollRegion;
  497. 'S': Result := ecScrollUp;
  498. 'T': Result := ecScrollDown;
  499. 'L': Result := ecInsertLine;
  500. 'M': Result := ecDeleteLine;
  501. 'p': Result := ecSoftReset;
  502. else
  503. Result := ecUnknown;
  504. end;
  505. FParams.Assign(TempParams);
  506. finally
  507. TempParams.Free;
  508. end;
  509. end;
  510. function TEscapeCodesVT100.DetectOscCode(Str: string): TEscapeCode;
  511. var
  512. LastCh: Char;
  513. begin
  514. Result := ecNotCompleted;
  515. LastCh := Str[Length(Str)];
  516. if (LastCh = #7) then
  517. begin
  518. Result:= ecSetTextParams;
  519. end;
  520. end;
  521. end.