Terminal.cpp 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176
  1. /*
  2. * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/Debug.h>
  7. #include <AK/StringBuilder.h>
  8. #include <AK/StringView.h>
  9. #include <LibVT/Terminal.h>
  10. namespace VT {
  11. Terminal::Terminal(TerminalClient& client)
  12. : m_client(client)
  13. {
  14. }
  15. Terminal::~Terminal()
  16. {
  17. }
  18. void Terminal::clear()
  19. {
  20. for (size_t i = 0; i < rows(); ++i)
  21. m_lines[i].clear(m_current_attribute);
  22. set_cursor(0, 0);
  23. }
  24. void Terminal::clear_including_history()
  25. {
  26. m_history.clear();
  27. m_history_start = 0;
  28. clear();
  29. m_client.terminal_history_changed();
  30. }
  31. inline bool is_valid_parameter_character(u8 ch)
  32. {
  33. return ch >= 0x30 && ch <= 0x3f;
  34. }
  35. inline bool is_valid_intermediate_character(u8 ch)
  36. {
  37. return ch >= 0x20 && ch <= 0x2f;
  38. }
  39. inline bool is_valid_final_character(u8 ch)
  40. {
  41. return ch >= 0x40 && ch <= 0x7e;
  42. }
  43. void Terminal::alter_mode(bool should_set, bool question_param, const ParamVector& params)
  44. {
  45. int mode = 2;
  46. if (params.size() > 0) {
  47. mode = params[0];
  48. }
  49. if (!question_param) {
  50. switch (mode) {
  51. // FIXME: implement *something* for this
  52. default:
  53. unimplemented_escape();
  54. break;
  55. }
  56. } else {
  57. switch (mode) {
  58. case 3: {
  59. // 80/132-column mode (DECCOLM)
  60. unsigned new_columns = should_set ? 80 : 132;
  61. dbgln("Setting {}-column mode", new_columns);
  62. set_size(new_columns, rows());
  63. clear();
  64. break;
  65. }
  66. case 25:
  67. // Hide cursor command, but doesn't need to be run (for now, because
  68. // we don't do inverse control codes anyways)
  69. if (should_set)
  70. dbgln("Terminal: Hide Cursor escapecode received. Not needed: ignored.");
  71. else
  72. dbgln("Terminal: Show Cursor escapecode received. Not needed: ignored.");
  73. break;
  74. default:
  75. dbgln("Set Mode: Unimplemented mode {}", mode);
  76. break;
  77. }
  78. }
  79. }
  80. void Terminal::RM(bool question_param, const ParamVector& params)
  81. {
  82. alter_mode(true, question_param, params);
  83. }
  84. void Terminal::SM(bool question_param, const ParamVector& params)
  85. {
  86. alter_mode(false, question_param, params);
  87. }
  88. void Terminal::SGR(const ParamVector& params)
  89. {
  90. if (params.is_empty()) {
  91. m_current_attribute.reset();
  92. return;
  93. }
  94. if (params.size() >= 3) {
  95. bool should_set = true;
  96. auto kind = params[1];
  97. u32 color = 0;
  98. switch (kind) {
  99. case 5: // 8-bit
  100. color = xterm_colors[params[2]];
  101. break;
  102. case 2: // 24-bit
  103. for (size_t i = 0; i < 3; ++i) {
  104. u8 component = 0;
  105. if (params.size() - 2 > i) {
  106. component = params[i + 2];
  107. }
  108. color <<= 8;
  109. color |= component;
  110. }
  111. break;
  112. default:
  113. should_set = false;
  114. break;
  115. }
  116. if (should_set) {
  117. if (params[0] == 38) {
  118. m_current_attribute.foreground_color = color;
  119. return;
  120. } else if (params[0] == 48) {
  121. m_current_attribute.background_color = color;
  122. return;
  123. }
  124. }
  125. }
  126. for (auto param : params) {
  127. switch (param) {
  128. case 0:
  129. // Reset
  130. m_current_attribute.reset();
  131. break;
  132. case 1:
  133. m_current_attribute.flags |= Attribute::Bold;
  134. break;
  135. case 3:
  136. m_current_attribute.flags |= Attribute::Italic;
  137. break;
  138. case 4:
  139. m_current_attribute.flags |= Attribute::Underline;
  140. break;
  141. case 5:
  142. m_current_attribute.flags |= Attribute::Blink;
  143. break;
  144. case 7:
  145. m_current_attribute.flags |= Attribute::Negative;
  146. break;
  147. case 22:
  148. m_current_attribute.flags &= ~Attribute::Bold;
  149. break;
  150. case 23:
  151. m_current_attribute.flags &= ~Attribute::Italic;
  152. break;
  153. case 24:
  154. m_current_attribute.flags &= ~Attribute::Underline;
  155. break;
  156. case 25:
  157. m_current_attribute.flags &= ~Attribute::Blink;
  158. break;
  159. case 27:
  160. m_current_attribute.flags &= ~Attribute::Negative;
  161. break;
  162. case 30:
  163. case 31:
  164. case 32:
  165. case 33:
  166. case 34:
  167. case 35:
  168. case 36:
  169. case 37:
  170. // Foreground color
  171. if (m_current_attribute.flags & Attribute::Bold)
  172. param += 8;
  173. m_current_attribute.foreground_color = xterm_colors[param - 30];
  174. break;
  175. case 39:
  176. // reset foreground
  177. m_current_attribute.foreground_color = Attribute::default_foreground_color;
  178. break;
  179. case 40:
  180. case 41:
  181. case 42:
  182. case 43:
  183. case 44:
  184. case 45:
  185. case 46:
  186. case 47:
  187. // Background color
  188. if (m_current_attribute.flags & Attribute::Bold)
  189. param += 8;
  190. m_current_attribute.background_color = xterm_colors[param - 40];
  191. break;
  192. case 49:
  193. // reset background
  194. m_current_attribute.background_color = Attribute::default_background_color;
  195. break;
  196. default:
  197. dbgln("FIXME: SGR: p: {}", param);
  198. }
  199. }
  200. }
  201. void Terminal::SCOSC(const ParamVector&)
  202. {
  203. m_saved_cursor_row = m_cursor_row;
  204. m_saved_cursor_column = m_cursor_column;
  205. }
  206. void Terminal::SCORC(const ParamVector&)
  207. {
  208. set_cursor(m_saved_cursor_row, m_saved_cursor_column);
  209. }
  210. void Terminal::XTERM_WM(const ParamVector& params)
  211. {
  212. if (params.size() < 1)
  213. return;
  214. dbgln("FIXME: XTERM_WM: Ps: {} (param count: {})", params[0], params.size());
  215. }
  216. void Terminal::DECSTBM(const ParamVector& params)
  217. {
  218. unsigned top = 1;
  219. unsigned bottom = m_rows;
  220. if (params.size() >= 1)
  221. top = params[0];
  222. if (params.size() >= 2)
  223. bottom = params[1];
  224. if ((bottom - top) < 2 || bottom > m_rows) {
  225. dbgln("Error: DECSTBM: scrolling region invalid: {}-{}", top, bottom);
  226. return;
  227. }
  228. m_scroll_region_top = top - 1;
  229. m_scroll_region_bottom = bottom - 1;
  230. set_cursor(0, 0);
  231. }
  232. void Terminal::CUP(const ParamVector& params)
  233. {
  234. // CUP – Cursor Position
  235. unsigned row = 1;
  236. unsigned col = 1;
  237. if (params.size() >= 1)
  238. row = params[0];
  239. if (params.size() >= 2)
  240. col = params[1];
  241. set_cursor(row - 1, col - 1);
  242. }
  243. void Terminal::HVP(const ParamVector& params)
  244. {
  245. unsigned row = 1;
  246. unsigned col = 1;
  247. if (params.size() >= 1)
  248. row = params[0];
  249. if (params.size() >= 2)
  250. col = params[1];
  251. set_cursor(row - 1, col - 1);
  252. }
  253. void Terminal::CUU(const ParamVector& params)
  254. {
  255. int num = 1;
  256. if (params.size() >= 1)
  257. num = params[0];
  258. if (num == 0)
  259. num = 1;
  260. int new_row = (int)m_cursor_row - num;
  261. if (new_row < 0)
  262. new_row = 0;
  263. set_cursor(new_row, m_cursor_column);
  264. }
  265. void Terminal::CUD(const ParamVector& params)
  266. {
  267. int num = 1;
  268. if (params.size() >= 1)
  269. num = params[0];
  270. if (num == 0)
  271. num = 1;
  272. int new_row = (int)m_cursor_row + num;
  273. if (new_row >= m_rows)
  274. new_row = m_rows - 1;
  275. set_cursor(new_row, m_cursor_column);
  276. }
  277. void Terminal::CUF(const ParamVector& params)
  278. {
  279. int num = 1;
  280. if (params.size() >= 1)
  281. num = params[0];
  282. if (num == 0)
  283. num = 1;
  284. int new_column = (int)m_cursor_column + num;
  285. if (new_column >= m_columns)
  286. new_column = m_columns - 1;
  287. set_cursor(m_cursor_row, new_column);
  288. }
  289. void Terminal::CUB(const ParamVector& params)
  290. {
  291. int num = 1;
  292. if (params.size() >= 1)
  293. num = params[0];
  294. if (num == 0)
  295. num = 1;
  296. int new_column = (int)m_cursor_column - num;
  297. if (new_column < 0)
  298. new_column = 0;
  299. set_cursor(m_cursor_row, new_column);
  300. }
  301. void Terminal::CHA(const ParamVector& params)
  302. {
  303. int new_column = 1;
  304. if (params.size() >= 1)
  305. new_column = params[0] - 1;
  306. if (new_column < 0)
  307. new_column = 0;
  308. set_cursor(m_cursor_row, new_column);
  309. }
  310. void Terminal::REP(const ParamVector& params)
  311. {
  312. if (params.size() < 1)
  313. return;
  314. for (unsigned i = 0; i < params[0]; ++i)
  315. put_character_at(m_cursor_row, m_cursor_column++, m_last_code_point);
  316. }
  317. void Terminal::VPA(const ParamVector& params)
  318. {
  319. int new_row = 1;
  320. if (params.size() >= 1)
  321. new_row = params[0] - 1;
  322. if (new_row < 0)
  323. new_row = 0;
  324. set_cursor(new_row, m_cursor_column);
  325. }
  326. void Terminal::ECH(const ParamVector& params)
  327. {
  328. // Erase characters (without moving cursor)
  329. int num = 1;
  330. if (params.size() >= 1)
  331. num = params[0];
  332. if (num == 0)
  333. num = 1;
  334. // Clear from cursor to end of line.
  335. for (int i = m_cursor_column; i < num; ++i) {
  336. put_character_at(m_cursor_row, i, ' ');
  337. }
  338. }
  339. void Terminal::EL(const ParamVector& params)
  340. {
  341. int mode = 0;
  342. if (params.size() >= 1)
  343. mode = params[0];
  344. switch (mode) {
  345. case 0:
  346. // Clear from cursor to end of line.
  347. for (int i = m_cursor_column; i < m_columns; ++i) {
  348. put_character_at(m_cursor_row, i, ' ');
  349. }
  350. break;
  351. case 1:
  352. // Clear from cursor to beginning of line.
  353. for (int i = 0; i <= m_cursor_column; ++i) {
  354. put_character_at(m_cursor_row, i, ' ');
  355. }
  356. break;
  357. case 2:
  358. // Clear the complete line
  359. for (int i = 0; i < m_columns; ++i) {
  360. put_character_at(m_cursor_row, i, ' ');
  361. }
  362. break;
  363. default:
  364. unimplemented_escape();
  365. break;
  366. }
  367. }
  368. void Terminal::ED(const ParamVector& params)
  369. {
  370. int mode = 0;
  371. if (params.size() >= 1)
  372. mode = params[0];
  373. switch (mode) {
  374. case 0:
  375. // Clear from cursor to end of screen.
  376. for (int i = m_cursor_column; i < m_columns; ++i)
  377. put_character_at(m_cursor_row, i, ' ');
  378. for (int row = m_cursor_row + 1; row < m_rows; ++row) {
  379. for (int column = 0; column < m_columns; ++column) {
  380. put_character_at(row, column, ' ');
  381. }
  382. }
  383. break;
  384. case 1:
  385. // Clear from cursor to beginning of screen.
  386. for (int i = m_cursor_column; i >= 0; --i)
  387. put_character_at(m_cursor_row, i, ' ');
  388. for (int row = m_cursor_row - 1; row >= 0; --row) {
  389. for (int column = 0; column < m_columns; ++column) {
  390. put_character_at(row, column, ' ');
  391. }
  392. }
  393. break;
  394. case 2:
  395. clear();
  396. break;
  397. case 3:
  398. // FIXME: <esc>[3J should also clear the scrollback buffer.
  399. clear();
  400. break;
  401. default:
  402. unimplemented_escape();
  403. break;
  404. }
  405. }
  406. void Terminal::SU(const ParamVector& params)
  407. {
  408. int count = 1;
  409. if (params.size() >= 1)
  410. count = params[0];
  411. for (u16 i = 0; i < count; i++)
  412. scroll_up();
  413. }
  414. void Terminal::SD(const ParamVector& params)
  415. {
  416. int count = 1;
  417. if (params.size() >= 1)
  418. count = params[0];
  419. for (u16 i = 0; i < count; i++)
  420. scroll_down();
  421. }
  422. void Terminal::IL(const ParamVector& params)
  423. {
  424. int count = 1;
  425. if (params.size() >= 1)
  426. count = params[0];
  427. invalidate_cursor();
  428. for (; count > 0; --count) {
  429. m_lines.insert(m_cursor_row + m_scroll_region_top, make<Line>(m_columns));
  430. if (m_scroll_region_bottom + 1 < m_lines.size())
  431. m_lines.remove(m_scroll_region_bottom + 1);
  432. else
  433. m_lines.remove(m_lines.size() - 1);
  434. }
  435. m_need_full_flush = true;
  436. }
  437. void Terminal::DA(const ParamVector&)
  438. {
  439. emit_string("\033[?1;0c");
  440. }
  441. void Terminal::DL(const ParamVector& params)
  442. {
  443. int count = 1;
  444. if (params.size() >= 1)
  445. count = params[0];
  446. if (count == 1 && m_cursor_row == 0) {
  447. scroll_up();
  448. return;
  449. }
  450. int max_count = m_rows - (m_scroll_region_top + m_cursor_row);
  451. count = min(count, max_count);
  452. for (int c = count; c > 0; --c) {
  453. m_lines.remove(m_cursor_row + m_scroll_region_top);
  454. if (m_scroll_region_bottom < m_lines.size())
  455. m_lines.insert(m_scroll_region_bottom, make<Line>(m_columns));
  456. else
  457. m_lines.append(make<Line>(m_columns));
  458. }
  459. }
  460. void Terminal::DCH(const ParamVector& params)
  461. {
  462. int num = 1;
  463. if (params.size() >= 1)
  464. num = params[0];
  465. if (num == 0)
  466. num = 1;
  467. auto& line = m_lines[m_cursor_row];
  468. // Move n characters of line to the left
  469. for (size_t i = m_cursor_column; i < line.length() - num; i++)
  470. line.set_code_point(i, line.code_point(i + num));
  471. // Fill remainder of line with blanks
  472. for (size_t i = line.length() - num; i < line.length(); i++)
  473. line.set_code_point(i, ' ');
  474. line.set_dirty(true);
  475. }
  476. void Terminal::execute_xterm_command()
  477. {
  478. ParamVector numeric_params;
  479. auto param_string = String::copy(m_xterm_parameters);
  480. auto params = param_string.split(';', true);
  481. m_xterm_parameters.clear_with_capacity();
  482. for (auto& parampart : params)
  483. numeric_params.append(parampart.to_uint().value_or(0));
  484. while (params.size() < 3) {
  485. params.append(String::empty());
  486. numeric_params.append(0);
  487. }
  488. m_final = '@';
  489. if (numeric_params.is_empty()) {
  490. dbgln("Empty Xterm params?");
  491. return;
  492. }
  493. switch (numeric_params[0]) {
  494. case 0:
  495. case 1:
  496. case 2:
  497. m_client.set_window_title(params[1]);
  498. break;
  499. case 8:
  500. if (params[2].is_empty()) {
  501. m_current_attribute.href = String();
  502. m_current_attribute.href_id = String();
  503. } else {
  504. m_current_attribute.href = params[2];
  505. // FIXME: Respect the provided ID
  506. m_current_attribute.href_id = String::number(m_next_href_id++);
  507. }
  508. break;
  509. case 9:
  510. m_client.set_window_progress(numeric_params[1], numeric_params[2]);
  511. break;
  512. default:
  513. unimplemented_xterm_escape();
  514. break;
  515. }
  516. }
  517. void Terminal::execute_escape_sequence(u8 final)
  518. {
  519. bool question_param = false;
  520. m_final = final;
  521. ParamVector params;
  522. if (m_parameters.size() > 0 && m_parameters[0] == '?') {
  523. question_param = true;
  524. m_parameters.remove(0);
  525. }
  526. auto paramparts = String::copy(m_parameters).split(';');
  527. for (auto& parampart : paramparts) {
  528. auto value = parampart.to_uint();
  529. if (!value.has_value()) {
  530. // FIXME: Should we do something else?
  531. m_parameters.clear_with_capacity();
  532. m_intermediates.clear_with_capacity();
  533. return;
  534. }
  535. params.append(value.value());
  536. }
  537. switch (final) {
  538. case 'A':
  539. CUU(params);
  540. break;
  541. case 'B':
  542. CUD(params);
  543. break;
  544. case 'C':
  545. CUF(params);
  546. break;
  547. case 'D':
  548. CUB(params);
  549. break;
  550. case 'H':
  551. CUP(params);
  552. break;
  553. case 'J':
  554. ED(params);
  555. break;
  556. case 'K':
  557. EL(params);
  558. break;
  559. case 'M':
  560. DL(params);
  561. break;
  562. case 'P':
  563. DCH(params);
  564. break;
  565. case 'S':
  566. SU(params);
  567. break;
  568. case 'T':
  569. SD(params);
  570. break;
  571. case 'L':
  572. IL(params);
  573. break;
  574. case 'G':
  575. CHA(params);
  576. break;
  577. case 'X':
  578. ECH(params);
  579. break;
  580. case 'b':
  581. REP(params);
  582. break;
  583. case 'd':
  584. VPA(params);
  585. break;
  586. case 'm':
  587. SGR(params);
  588. break;
  589. case 's':
  590. SCOSC(params);
  591. break;
  592. case 'u':
  593. SCORC(params);
  594. break;
  595. case 't':
  596. XTERM_WM(params);
  597. break;
  598. case 'r':
  599. DECSTBM(params);
  600. break;
  601. case 'l':
  602. RM(question_param, params);
  603. break;
  604. case 'h':
  605. SM(question_param, params);
  606. break;
  607. case 'c':
  608. DA(params);
  609. break;
  610. case 'f':
  611. HVP(params);
  612. break;
  613. case 'n':
  614. DSR(params);
  615. break;
  616. case '@':
  617. ICH(params);
  618. break;
  619. default:
  620. dbgln("Terminal::execute_escape_sequence: Unhandled final '{:c}'", final);
  621. break;
  622. }
  623. m_parameters.clear_with_capacity();
  624. m_intermediates.clear_with_capacity();
  625. }
  626. void Terminal::newline()
  627. {
  628. u16 new_row = m_cursor_row;
  629. if (m_cursor_row == m_scroll_region_bottom) {
  630. scroll_up();
  631. } else {
  632. ++new_row;
  633. }
  634. set_cursor(new_row, 0);
  635. }
  636. void Terminal::scroll_up()
  637. {
  638. // NOTE: We have to invalidate the cursor first.
  639. invalidate_cursor();
  640. if (m_scroll_region_top == 0) {
  641. auto line = move(m_lines.ptr_at(m_scroll_region_top));
  642. add_line_to_history(move(line));
  643. m_client.terminal_history_changed();
  644. }
  645. m_lines.remove(m_scroll_region_top);
  646. m_lines.insert(m_scroll_region_bottom, make<Line>(m_columns));
  647. m_need_full_flush = true;
  648. }
  649. void Terminal::scroll_down()
  650. {
  651. // NOTE: We have to invalidate the cursor first.
  652. invalidate_cursor();
  653. m_lines.remove(m_scroll_region_bottom);
  654. m_lines.insert(m_scroll_region_top, make<Line>(m_columns));
  655. m_need_full_flush = true;
  656. }
  657. void Terminal::set_cursor(unsigned a_row, unsigned a_column)
  658. {
  659. unsigned row = min(a_row, m_rows - 1u);
  660. unsigned column = min(a_column, m_columns - 1u);
  661. if (row == m_cursor_row && column == m_cursor_column)
  662. return;
  663. VERIFY(row < rows());
  664. VERIFY(column < columns());
  665. invalidate_cursor();
  666. m_cursor_row = row;
  667. m_cursor_column = column;
  668. m_stomp = false;
  669. invalidate_cursor();
  670. }
  671. void Terminal::put_character_at(unsigned row, unsigned column, u32 code_point)
  672. {
  673. VERIFY(row < rows());
  674. VERIFY(column < columns());
  675. auto& line = m_lines[row];
  676. line.set_code_point(column, code_point);
  677. line.attribute_at(column) = m_current_attribute;
  678. line.attribute_at(column).flags |= Attribute::Touched;
  679. line.set_dirty(true);
  680. m_last_code_point = code_point;
  681. }
  682. void Terminal::NEL()
  683. {
  684. newline();
  685. }
  686. void Terminal::IND()
  687. {
  688. CUD({});
  689. }
  690. void Terminal::RI()
  691. {
  692. CUU({});
  693. }
  694. void Terminal::DSR(const ParamVector& params)
  695. {
  696. if (params.size() == 1 && params[0] == 5) {
  697. // Device status
  698. emit_string("\033[0n"); // Terminal status OK!
  699. } else if (params.size() == 1 && params[0] == 6) {
  700. // Cursor position query
  701. emit_string(String::formatted("\e[{};{}R", m_cursor_row + 1, m_cursor_column + 1));
  702. } else {
  703. dbgln("Unknown DSR");
  704. }
  705. }
  706. void Terminal::ICH(const ParamVector& params)
  707. {
  708. int num = 0;
  709. if (params.size() >= 1) {
  710. num = params[0];
  711. }
  712. if (num == 0)
  713. num = 1;
  714. auto& line = m_lines[m_cursor_row];
  715. // Move characters after cursor to the right
  716. for (int i = line.length() - num; i >= m_cursor_column; --i)
  717. line.set_code_point(i + num, line.code_point(i));
  718. // Fill n characters after cursor with blanks
  719. for (int i = 0; i < num; i++)
  720. line.set_code_point(m_cursor_column + i, ' ');
  721. line.set_dirty(true);
  722. }
  723. void Terminal::on_input(u8 ch)
  724. {
  725. dbgln_if(TERMINAL_DEBUG, "Terminal::on_input: {:#02x} ({:c}), fg={}, bg={}\n", ch, ch, m_current_attribute.foreground_color, m_current_attribute.background_color);
  726. auto fail_utf8_parse = [this] {
  727. m_parser_state = Normal;
  728. on_code_point(U'�');
  729. };
  730. auto advance_utf8_parse = [this, ch] {
  731. m_parser_code_point <<= 6;
  732. m_parser_code_point |= ch & 0x3f;
  733. if (m_parser_state == UTF8Needs1Byte) {
  734. on_code_point(m_parser_code_point);
  735. m_parser_state = Normal;
  736. } else {
  737. m_parser_state = (ParserState)(m_parser_state + 1);
  738. }
  739. };
  740. switch (m_parser_state) {
  741. case GotEscape:
  742. if (ch == '[') {
  743. m_parser_state = ExpectParameter;
  744. } else if (ch == '(') {
  745. m_swallow_current = true;
  746. m_parser_state = ExpectParameter;
  747. } else if (ch == ']') {
  748. m_parser_state = ExpectXtermParameter;
  749. m_xterm_parameters.clear_with_capacity();
  750. } else if (ch == '#') {
  751. m_parser_state = ExpectHashtagDigit;
  752. } else if (ch == 'D') {
  753. IND();
  754. m_parser_state = Normal;
  755. return;
  756. } else if (ch == 'M') {
  757. RI();
  758. m_parser_state = Normal;
  759. return;
  760. } else if (ch == 'E') {
  761. NEL();
  762. m_parser_state = Normal;
  763. return;
  764. } else {
  765. dbgln("Unexpected character in GotEscape '{}'", (char)ch);
  766. m_parser_state = Normal;
  767. }
  768. return;
  769. case ExpectHashtagDigit:
  770. if (ch >= '0' && ch <= '9') {
  771. execute_hashtag(ch);
  772. m_parser_state = Normal;
  773. }
  774. return;
  775. case ExpectXtermParameter:
  776. if (ch == 27) {
  777. m_parser_state = ExpectStringTerminator;
  778. return;
  779. }
  780. if (ch == 7) {
  781. execute_xterm_command();
  782. m_parser_state = Normal;
  783. return;
  784. }
  785. m_xterm_parameters.append(ch);
  786. return;
  787. case ExpectStringTerminator:
  788. if (ch == '\\')
  789. execute_xterm_command();
  790. else
  791. dbgln("Unexpected string terminator: {:#02x}", ch);
  792. m_parser_state = Normal;
  793. return;
  794. case ExpectParameter:
  795. if (is_valid_parameter_character(ch)) {
  796. m_parameters.append(ch);
  797. return;
  798. }
  799. m_parser_state = ExpectIntermediate;
  800. [[fallthrough]];
  801. case ExpectIntermediate:
  802. if (is_valid_intermediate_character(ch)) {
  803. m_intermediates.append(ch);
  804. return;
  805. }
  806. m_parser_state = ExpectFinal;
  807. [[fallthrough]];
  808. case ExpectFinal:
  809. if (is_valid_final_character(ch)) {
  810. m_parser_state = Normal;
  811. if (!m_swallow_current)
  812. execute_escape_sequence(ch);
  813. m_swallow_current = false;
  814. return;
  815. }
  816. m_parser_state = Normal;
  817. m_swallow_current = false;
  818. return;
  819. case UTF8Needs1Byte:
  820. case UTF8Needs2Bytes:
  821. case UTF8Needs3Bytes:
  822. if ((ch & 0xc0) != 0x80) {
  823. fail_utf8_parse();
  824. } else {
  825. advance_utf8_parse();
  826. }
  827. return;
  828. case Normal:
  829. if (!(ch & 0x80))
  830. break;
  831. if ((ch & 0xe0) == 0xc0) {
  832. m_parser_state = UTF8Needs1Byte;
  833. m_parser_code_point = ch & 0x1f;
  834. return;
  835. }
  836. if ((ch & 0xf0) == 0xe0) {
  837. m_parser_state = UTF8Needs2Bytes;
  838. m_parser_code_point = ch & 0x0f;
  839. return;
  840. }
  841. if ((ch & 0xf8) == 0xf0) {
  842. m_parser_state = UTF8Needs3Bytes;
  843. m_parser_code_point = ch & 0x07;
  844. return;
  845. }
  846. fail_utf8_parse();
  847. return;
  848. }
  849. switch (ch) {
  850. case '\0':
  851. return;
  852. case '\033':
  853. m_parser_state = GotEscape;
  854. m_swallow_current = false;
  855. return;
  856. case 8: // Backspace
  857. if (m_cursor_column) {
  858. set_cursor(m_cursor_row, m_cursor_column - 1);
  859. return;
  860. }
  861. return;
  862. case '\a':
  863. m_client.beep();
  864. return;
  865. case '\t': {
  866. for (unsigned i = m_cursor_column + 1; i < columns(); ++i) {
  867. if (m_horizontal_tabs[i]) {
  868. set_cursor(m_cursor_row, i);
  869. return;
  870. }
  871. }
  872. return;
  873. }
  874. case '\r':
  875. set_cursor(m_cursor_row, 0);
  876. return;
  877. case '\n':
  878. newline();
  879. return;
  880. }
  881. on_code_point(ch);
  882. }
  883. void Terminal::on_code_point(u32 code_point)
  884. {
  885. auto new_column = m_cursor_column + 1;
  886. if (new_column < columns()) {
  887. put_character_at(m_cursor_row, m_cursor_column, code_point);
  888. set_cursor(m_cursor_row, new_column);
  889. return;
  890. }
  891. if (m_stomp) {
  892. m_stomp = false;
  893. newline();
  894. put_character_at(m_cursor_row, m_cursor_column, code_point);
  895. set_cursor(m_cursor_row, 1);
  896. } else {
  897. // Curious: We wait once on the right-hand side
  898. m_stomp = true;
  899. put_character_at(m_cursor_row, m_cursor_column, code_point);
  900. }
  901. }
  902. void Terminal::inject_string(const StringView& str)
  903. {
  904. for (size_t i = 0; i < str.length(); ++i)
  905. on_input(str[i]);
  906. }
  907. void Terminal::emit_string(const StringView& string)
  908. {
  909. m_client.emit((const u8*)string.characters_without_null_termination(), string.length());
  910. }
  911. void Terminal::handle_key_press(KeyCode key, u32 code_point, u8 flags)
  912. {
  913. bool ctrl = flags & Mod_Ctrl;
  914. bool alt = flags & Mod_Alt;
  915. bool shift = flags & Mod_Shift;
  916. unsigned modifier_mask = int(shift) + (int(alt) << 1) + (int(ctrl) << 2);
  917. auto emit_final_with_modifier = [this, modifier_mask](char final) {
  918. if (modifier_mask)
  919. emit_string(String::formatted("\e[1;{}{:c}", modifier_mask + 1, final));
  920. else
  921. emit_string(String::formatted("\e[{:c}", final));
  922. };
  923. auto emit_tilde_with_modifier = [this, modifier_mask](unsigned num) {
  924. if (modifier_mask)
  925. emit_string(String::formatted("\e[{};{}~", num, modifier_mask + 1));
  926. else
  927. emit_string(String::formatted("\e[{}~", num));
  928. };
  929. switch (key) {
  930. case KeyCode::Key_Up:
  931. emit_final_with_modifier('A');
  932. return;
  933. case KeyCode::Key_Down:
  934. emit_final_with_modifier('B');
  935. return;
  936. case KeyCode::Key_Right:
  937. emit_final_with_modifier('C');
  938. return;
  939. case KeyCode::Key_Left:
  940. emit_final_with_modifier('D');
  941. return;
  942. case KeyCode::Key_Insert:
  943. emit_tilde_with_modifier(2);
  944. return;
  945. case KeyCode::Key_Delete:
  946. emit_tilde_with_modifier(3);
  947. return;
  948. case KeyCode::Key_Home:
  949. emit_final_with_modifier('H');
  950. return;
  951. case KeyCode::Key_End:
  952. emit_final_with_modifier('F');
  953. return;
  954. case KeyCode::Key_PageUp:
  955. emit_tilde_with_modifier(5);
  956. return;
  957. case KeyCode::Key_PageDown:
  958. emit_tilde_with_modifier(6);
  959. return;
  960. default:
  961. break;
  962. }
  963. if (!code_point) {
  964. // Probably a modifier being pressed.
  965. return;
  966. }
  967. if (shift && key == KeyCode::Key_Tab) {
  968. emit_string("\033[Z");
  969. return;
  970. }
  971. // Key event was not one of the above special cases,
  972. // attempt to treat it as a character...
  973. if (ctrl) {
  974. if (code_point >= 'a' && code_point <= 'z') {
  975. code_point = code_point - 'a' + 1;
  976. } else if (code_point == '\\') {
  977. code_point = 0x1c;
  978. }
  979. }
  980. // Alt modifier sends escape prefix.
  981. if (alt)
  982. emit_string("\033");
  983. StringBuilder sb;
  984. sb.append_code_point(code_point);
  985. emit_string(sb.to_string());
  986. }
  987. void Terminal::unimplemented_escape()
  988. {
  989. StringBuilder builder;
  990. builder.appendff("Unimplemented escape: {:c}", m_final);
  991. if (!m_parameters.is_empty()) {
  992. builder.append(", parameters:");
  993. for (size_t i = 0; i < m_parameters.size(); ++i)
  994. builder.append((char)m_parameters[i]);
  995. }
  996. if (!m_intermediates.is_empty()) {
  997. builder.append(", intermediates:");
  998. for (size_t i = 0; i < m_intermediates.size(); ++i)
  999. builder.append((char)m_intermediates[i]);
  1000. }
  1001. dbgln("{}", builder.string_view());
  1002. }
  1003. void Terminal::unimplemented_xterm_escape()
  1004. {
  1005. dbgln("Unimplemented xterm escape: {:c}", m_final);
  1006. }
  1007. void Terminal::set_size(u16 columns, u16 rows)
  1008. {
  1009. if (!columns)
  1010. columns = 1;
  1011. if (!rows)
  1012. rows = 1;
  1013. if (columns == m_columns && rows == m_rows)
  1014. return;
  1015. if (rows > m_rows) {
  1016. while (m_lines.size() < rows)
  1017. m_lines.append(make<Line>(columns));
  1018. } else {
  1019. m_lines.shrink(rows);
  1020. }
  1021. for (int i = 0; i < rows; ++i)
  1022. m_lines[i].set_length(columns);
  1023. m_columns = columns;
  1024. m_rows = rows;
  1025. m_scroll_region_top = 0;
  1026. m_scroll_region_bottom = rows - 1;
  1027. m_cursor_row = min((int)m_cursor_row, m_rows - 1);
  1028. m_cursor_column = min((int)m_cursor_column, m_columns - 1);
  1029. m_saved_cursor_row = min((int)m_saved_cursor_row, m_rows - 1);
  1030. m_saved_cursor_column = min((int)m_saved_cursor_column, m_columns - 1);
  1031. m_horizontal_tabs.resize(columns);
  1032. for (unsigned i = 0; i < columns; ++i)
  1033. m_horizontal_tabs[i] = (i % 8) == 0;
  1034. // Rightmost column is always last tab on line.
  1035. m_horizontal_tabs[columns - 1] = 1;
  1036. m_client.terminal_did_resize(m_columns, m_rows);
  1037. }
  1038. void Terminal::invalidate_cursor()
  1039. {
  1040. m_lines[m_cursor_row].set_dirty(true);
  1041. }
  1042. void Terminal::execute_hashtag(u8 hashtag)
  1043. {
  1044. switch (hashtag) {
  1045. case '8':
  1046. // Confidence Test - Fill screen with E's
  1047. for (size_t row = 0; row < m_rows; ++row) {
  1048. for (size_t column = 0; column < m_columns; ++column) {
  1049. put_character_at(row, column, 'E');
  1050. }
  1051. }
  1052. break;
  1053. default:
  1054. dbgln("Unknown hashtag: '{}'", (char)hashtag);
  1055. }
  1056. }
  1057. Attribute Terminal::attribute_at(const Position& position) const
  1058. {
  1059. if (!position.is_valid())
  1060. return {};
  1061. if (position.row() >= static_cast<int>(line_count()))
  1062. return {};
  1063. auto& line = this->line(position.row());
  1064. if (static_cast<size_t>(position.column()) >= line.length())
  1065. return {};
  1066. return line.attribute_at(position.column());
  1067. }
  1068. }