Terminal.cpp 30 KB

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