Terminal.cpp 28 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022
  1. #include "Terminal.h"
  2. #include "XtermColors.h"
  3. #include <string.h>
  4. #include <errno.h>
  5. #include <stdlib.h>
  6. #include <unistd.h>
  7. #include <stdio.h>
  8. #include <AK/AKString.h>
  9. #include <AK/StringBuilder.h>
  10. #include <SharedGraphics/Font.h>
  11. #include <LibGUI/GPainter.h>
  12. #include <AK/StdLibExtras.h>
  13. #include <LibGUI/GApplication.h>
  14. #include <LibGUI/GWindow.h>
  15. #include <Kernel/KeyCode.h>
  16. #include <sys/ioctl.h>
  17. //#define TERMINAL_DEBUG
  18. byte Terminal::Attribute::default_foreground_color = 7;
  19. byte Terminal::Attribute::default_background_color = 0;
  20. Terminal::Terminal(int ptm_fd, RetainPtr<CConfigFile> config)
  21. : m_ptm_fd(ptm_fd)
  22. , m_notifier(ptm_fd, CNotifier::Read)
  23. , m_config(config)
  24. {
  25. set_frame_shape(FrameShape::Container);
  26. set_frame_shadow(FrameShadow::Sunken);
  27. set_frame_thickness(2);
  28. dbgprintf("Terminal: Load config file from %s\n", m_config->file_name().characters());
  29. m_cursor_blink_timer.set_interval(m_config->read_num_entry("Text",
  30. "CursorBlinkInterval",
  31. 500));
  32. m_cursor_blink_timer.on_timeout = [this] {
  33. m_cursor_blink_state = !m_cursor_blink_state;
  34. update_cursor();
  35. };
  36. auto font_entry = m_config->read_entry("Text", "Font", "default");
  37. if (font_entry == "default")
  38. set_font(Font::default_fixed_width_font());
  39. else
  40. set_font(Font::load_from_file(font_entry));
  41. m_notifier.on_ready_to_read = [this]{
  42. byte buffer[BUFSIZ];
  43. ssize_t nread = read(m_ptm_fd, buffer, sizeof(buffer));
  44. if (nread < 0) {
  45. dbgprintf("Terminal read error: %s\n", strerror(errno));
  46. perror("read(ptm)");
  47. GApplication::the().quit(1);
  48. return;
  49. }
  50. if (nread == 0) {
  51. dbgprintf("Terminal: EOF on master pty, closing.\n");
  52. GApplication::the().quit(0);
  53. return;
  54. }
  55. for (ssize_t i = 0; i < nread; ++i)
  56. on_char(buffer[i]);
  57. flush_dirty_lines();
  58. };
  59. m_line_height = font().glyph_height() + m_line_spacing;
  60. set_size(m_config->read_num_entry("Window", "Width", 80),
  61. m_config->read_num_entry("Window", "Height", 25));
  62. }
  63. Terminal::Line::Line(word columns)
  64. : length(columns)
  65. {
  66. characters = new byte[length];
  67. attributes = new Attribute[length];
  68. memset(characters, ' ', length);
  69. }
  70. Terminal::Line::~Line()
  71. {
  72. delete [] characters;
  73. delete [] attributes;
  74. }
  75. void Terminal::Line::clear(Attribute attribute)
  76. {
  77. if (dirty) {
  78. memset(characters, ' ', length);
  79. for (word i = 0 ; i < length; ++i)
  80. attributes[i] = attribute;
  81. return;
  82. }
  83. for (unsigned i = 0 ; i < length; ++i) {
  84. if (characters[i] != ' ')
  85. dirty = true;
  86. characters[i] = ' ';
  87. }
  88. for (unsigned i = 0 ; i < length; ++i) {
  89. if (attributes[i] != attribute)
  90. dirty = true;
  91. attributes[i] = attribute;
  92. }
  93. }
  94. Terminal::~Terminal()
  95. {
  96. for (int i = 0; i < m_rows; ++i)
  97. delete m_lines[i];
  98. delete [] m_lines;
  99. free(m_horizontal_tabs);
  100. }
  101. void Terminal::clear()
  102. {
  103. for (size_t i = 0; i < rows(); ++i)
  104. line(i).clear(m_current_attribute);
  105. set_cursor(0, 0);
  106. }
  107. inline bool is_valid_parameter_character(byte ch)
  108. {
  109. return ch >= 0x30 && ch <= 0x3f;
  110. }
  111. inline bool is_valid_intermediate_character(byte ch)
  112. {
  113. return ch >= 0x20 && ch <= 0x2f;
  114. }
  115. inline bool is_valid_final_character(byte ch)
  116. {
  117. return ch >= 0x40 && ch <= 0x7e;
  118. }
  119. static inline Color lookup_color(unsigned color)
  120. {
  121. return Color::from_rgb(xterm_colors[color]);
  122. }
  123. void Terminal::escape$m(const ParamVector& params)
  124. {
  125. if (params.is_empty()) {
  126. m_current_attribute.reset();
  127. return;
  128. }
  129. if (params.size() == 3 && params[1] == 5) {
  130. if (params[0] == 38) {
  131. m_current_attribute.foreground_color = params[2];
  132. return;
  133. } else if (params[0] == 48) {
  134. m_current_attribute.background_color = params[2];
  135. return;
  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 = 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 = 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. dbgprintf("FIXME: escape$m: p: %u\n", param);
  210. }
  211. }
  212. }
  213. void Terminal::escape$s(const ParamVector&)
  214. {
  215. m_saved_cursor_row = m_cursor_row;
  216. m_saved_cursor_column = m_cursor_column;
  217. }
  218. void Terminal::escape$u(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. dbgprintf("FIXME: escape$t: Ps: %u\n", params[0]);
  227. }
  228. void Terminal::escape$r(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. dbgprintf("Error: escape: scrolling region invalid: %u-%u\n", top, bottom);
  238. return;
  239. }
  240. m_scroll_region_top = top;
  241. m_scroll_region_bottom = bottom;
  242. set_cursor(0, 0);
  243. }
  244. void Terminal::escape$H(const ParamVector& params)
  245. {
  246. unsigned row = 1;
  247. unsigned col = 1;
  248. if (params.size() >= 1)
  249. row = params[0];
  250. if (params.size() >= 2)
  251. col = params[1];
  252. set_cursor(row - 1, col - 1);
  253. }
  254. void Terminal::escape$A(const ParamVector& params)
  255. {
  256. int num = 1;
  257. if (params.size() >= 1)
  258. num = params[0];
  259. if (num == 0)
  260. num = 1;
  261. int new_row = (int)m_cursor_row - num;
  262. if (new_row < 0)
  263. new_row = 0;
  264. set_cursor(new_row, m_cursor_column);
  265. }
  266. void Terminal::escape$B(const ParamVector& params)
  267. {
  268. int num = 1;
  269. if (params.size() >= 1)
  270. num = params[0];
  271. if (num == 0)
  272. num = 1;
  273. int new_row = (int)m_cursor_row + num;
  274. if (new_row >= m_rows)
  275. new_row = m_rows - 1;
  276. set_cursor(new_row, m_cursor_column);
  277. }
  278. void Terminal::escape$C(const ParamVector& params)
  279. {
  280. int num = 1;
  281. if (params.size() >= 1)
  282. num = params[0];
  283. if (num == 0)
  284. num = 1;
  285. int new_column = (int)m_cursor_column + num;
  286. if (new_column >= m_columns)
  287. new_column = m_columns - 1;
  288. set_cursor(m_cursor_row, new_column);
  289. }
  290. void Terminal::escape$D(const ParamVector& params)
  291. {
  292. int num = 1;
  293. if (params.size() >= 1)
  294. num = params[0];
  295. if (num == 0)
  296. num = 1;
  297. int new_column = (int)m_cursor_column - num;
  298. if (new_column < 0)
  299. new_column = 0;
  300. set_cursor(m_cursor_row, new_column);
  301. }
  302. void Terminal::escape$G(const ParamVector& params)
  303. {
  304. int new_column = 1;
  305. if (params.size() >= 1)
  306. new_column = params[0] - 1;
  307. if (new_column < 0)
  308. new_column = 0;
  309. set_cursor(m_cursor_row, new_column);
  310. }
  311. void Terminal::escape$d(const ParamVector& params)
  312. {
  313. int new_row = 1;
  314. if (params.size() >= 1)
  315. new_row = params[0] - 1;
  316. if (new_row < 0)
  317. new_row = 0;
  318. set_cursor(new_row, m_cursor_column);
  319. }
  320. void Terminal::escape$X(const ParamVector& params)
  321. {
  322. // Erase characters (without moving cursor)
  323. int num = 1;
  324. if (params.size() >= 1)
  325. num = params[0];
  326. if (num == 0)
  327. num = 1;
  328. // Clear from cursor to end of line.
  329. for (int i = m_cursor_column; i < num; ++i) {
  330. put_character_at(m_cursor_row, i, ' ');
  331. }
  332. }
  333. void Terminal::escape$K(const ParamVector& params)
  334. {
  335. int mode = 0;
  336. if (params.size() >= 1)
  337. mode = params[0];
  338. switch (mode) {
  339. case 0:
  340. // Clear from cursor to end of line.
  341. for (int i = m_cursor_column; i < m_columns; ++i) {
  342. put_character_at(m_cursor_row, i, ' ');
  343. }
  344. break;
  345. case 1:
  346. // Clear from cursor to beginning of line.
  347. for (int i = 0; i < m_cursor_column; ++i) {
  348. put_character_at(m_cursor_row, i, ' ');
  349. }
  350. break;
  351. case 2:
  352. unimplemented_escape();
  353. break;
  354. default:
  355. unimplemented_escape();
  356. break;
  357. }
  358. }
  359. void Terminal::escape$J(const ParamVector& params)
  360. {
  361. int mode = 0;
  362. if (params.size() >= 1)
  363. mode = params[0];
  364. switch (mode) {
  365. case 0:
  366. // Clear from cursor to end of screen.
  367. for (int i = m_cursor_column; i < m_columns; ++i) {
  368. put_character_at(m_cursor_row, i, ' ');
  369. }
  370. for (int row = m_cursor_row + 1; row < m_rows; ++row) {
  371. for (int column = 0; column < m_columns; ++column) {
  372. put_character_at(row, column, ' ');
  373. }
  374. }
  375. break;
  376. case 1:
  377. // FIXME: Clear from cursor to beginning of screen.
  378. unimplemented_escape();
  379. break;
  380. case 2:
  381. clear();
  382. break;
  383. case 3:
  384. // FIXME: <esc>[3J should also clear the scrollback buffer.
  385. clear();
  386. break;
  387. default:
  388. unimplemented_escape();
  389. break;
  390. }
  391. }
  392. void Terminal::escape$S(const ParamVector& params)
  393. {
  394. int count = 1;
  395. if (params.size() >= 1)
  396. count = params[0];
  397. dbgprintf("Terminal: Scrolling up %d lines\n", count);
  398. for (word i = 0; i < count; i++)
  399. scroll_up();
  400. }
  401. void Terminal::escape$T(const ParamVector& params)
  402. {
  403. int count = 1;
  404. if (params.size() >= 1)
  405. count = params[0];
  406. dbgprintf("Terminal: Scrolling down %d lines\n", count);
  407. for (word i = 0; i < count; i++)
  408. scroll_down();
  409. }
  410. void Terminal::escape$L(const ParamVector& params)
  411. {
  412. int count = 1;
  413. if (params.size() >= 1)
  414. count = params[0];
  415. dbgprintf("Terminal: Adding %d lines below cursor (at line %d)\n", count, m_cursor_row);
  416. invalidate_cursor();
  417. for (word row = m_rows; row > m_cursor_row; --row)
  418. m_lines[row] = m_lines[row - 1];
  419. m_lines[m_cursor_row] = new Line(m_columns);
  420. ++m_rows_to_scroll_backing_store;
  421. m_need_full_flush = true;
  422. }
  423. void Terminal::escape$M(const ParamVector& params)
  424. {
  425. int count = 1;
  426. if (params.size() >= 1)
  427. count = params[0];
  428. if (count == 1 && m_cursor_row == 0) {
  429. scroll_up();
  430. return;
  431. }
  432. int max_count = m_rows - m_cursor_row;
  433. count = min(count, max_count);
  434. dbgprintf("Delete %d line(s) starting from %d\n", count, m_cursor_row);
  435. for (word i = 0; i < count; ++i)
  436. delete m_lines[m_cursor_row + i];
  437. for (word row = m_cursor_row + count + 1; row < rows(); ++row)
  438. m_lines[row - 1] = m_lines[row];
  439. m_lines[m_rows - 1]->clear(m_current_attribute);
  440. }
  441. void Terminal::execute_xterm_command()
  442. {
  443. m_final = '@';
  444. bool ok;
  445. unsigned value = String::copy(m_xterm_param1).to_uint(ok);
  446. if (ok) {
  447. switch (value) {
  448. case 0:
  449. case 1:
  450. case 2:
  451. set_window_title(String::copy(m_xterm_param2));
  452. break;
  453. default:
  454. unimplemented_xterm_escape();
  455. break;
  456. }
  457. }
  458. m_xterm_param1.clear_with_capacity();
  459. m_xterm_param2.clear_with_capacity();
  460. }
  461. void Terminal::execute_escape_sequence(byte final)
  462. {
  463. m_final = final;
  464. auto paramparts = String::copy(m_parameters).split(';');
  465. ParamVector params;
  466. for (auto& parampart : paramparts) {
  467. bool ok;
  468. unsigned value = parampart.to_uint(ok);
  469. if (!ok) {
  470. m_parameters.clear_with_capacity();
  471. m_intermediates.clear_with_capacity();
  472. // FIXME: Should we do something else?
  473. return;
  474. }
  475. params.append(value);
  476. }
  477. switch (final) {
  478. case 'A': escape$A(params); break;
  479. case 'B': escape$B(params); break;
  480. case 'C': escape$C(params); break;
  481. case 'D': escape$D(params); break;
  482. case 'H': escape$H(params); break;
  483. case 'J': escape$J(params); break;
  484. case 'K': escape$K(params); break;
  485. case 'M': escape$M(params); break;
  486. case 'S': escape$S(params); break;
  487. case 'T': escape$T(params); break;
  488. case 'L': escape$L(params); break;
  489. case 'G': escape$G(params); break;
  490. case 'X': escape$X(params); break;
  491. case 'd': escape$d(params); break;
  492. case 'm': escape$m(params); break;
  493. case 's': escape$s(params); break;
  494. case 'u': escape$u(params); break;
  495. case 't': escape$t(params); break;
  496. case 'r': escape$r(params); break;
  497. default:
  498. dbgprintf("Terminal::execute_escape_sequence: Unhandled final '%c'\n", final);
  499. break;
  500. }
  501. m_parameters.clear_with_capacity();
  502. m_intermediates.clear_with_capacity();
  503. }
  504. void Terminal::newline()
  505. {
  506. word new_row = m_cursor_row;
  507. if (m_cursor_row == (rows() - 1)) {
  508. scroll_up();
  509. } else {
  510. ++new_row;
  511. }
  512. set_cursor(new_row, 0);
  513. }
  514. void Terminal::scroll_up()
  515. {
  516. // NOTE: We have to invalidate the cursor first.
  517. invalidate_cursor();
  518. delete m_lines[m_scroll_region_top];
  519. for (word row = m_scroll_region_top + 1; row < m_scroll_region_bottom; ++row)
  520. m_lines[row - 1] = m_lines[row];
  521. m_lines[m_scroll_region_bottom - 1] = new Line(m_columns);
  522. ++m_rows_to_scroll_backing_store;
  523. m_need_full_flush = true;
  524. }
  525. void Terminal::scroll_down()
  526. {
  527. // NOTE: We have to invalidate the cursor first.
  528. invalidate_cursor();
  529. for (word row = m_scroll_region_bottom; row > m_scroll_region_top; --row)
  530. m_lines[row] = m_lines[row - 1];
  531. m_lines[m_scroll_region_top] = new Line(m_columns);
  532. --m_rows_to_scroll_backing_store;
  533. m_need_full_flush = true;
  534. }
  535. void Terminal::set_cursor(unsigned a_row, unsigned a_column)
  536. {
  537. unsigned row = min(a_row, m_rows - 1u);
  538. unsigned column = min(a_column, m_columns - 1u);
  539. if (row == m_cursor_row && column == m_cursor_column)
  540. return;
  541. ASSERT(row < rows());
  542. ASSERT(column < columns());
  543. invalidate_cursor();
  544. m_cursor_row = row;
  545. m_cursor_column = column;
  546. if (column != columns() - 1)
  547. m_stomp = false;
  548. invalidate_cursor();
  549. }
  550. void Terminal::put_character_at(unsigned row, unsigned column, byte ch)
  551. {
  552. ASSERT(row < rows());
  553. ASSERT(column < columns());
  554. auto& line = this->line(row);
  555. if ((line.characters[column] == ch) && (line.attributes[column] == m_current_attribute))
  556. return;
  557. line.characters[column] = ch;
  558. line.attributes[column] = m_current_attribute;
  559. line.dirty = true;
  560. }
  561. void Terminal::on_char(byte ch)
  562. {
  563. #ifdef TERMINAL_DEBUG
  564. dbgprintf("Terminal::on_char: %b (%c), fg=%u, bg=%u\n", ch, ch, m_current_attribute.foreground_color, m_current_attribute.background_color);
  565. #endif
  566. switch (m_escape_state) {
  567. case ExpectBracket:
  568. if (ch == '[')
  569. m_escape_state = ExpectParameter;
  570. else if (ch == '(') {
  571. m_swallow_current = true;
  572. m_escape_state = ExpectParameter;
  573. } else if (ch == ']')
  574. m_escape_state = ExpectXtermParameter1;
  575. else
  576. m_escape_state = Normal;
  577. return;
  578. case ExpectXtermParameter1:
  579. if (ch != ';') {
  580. m_xterm_param1.append(ch);
  581. return;
  582. }
  583. m_escape_state = ExpectXtermParameter2;
  584. return;
  585. case ExpectXtermParameter2:
  586. if (ch != '\007') {
  587. m_xterm_param2.append(ch);
  588. return;
  589. }
  590. m_escape_state = ExpectXtermFinal;
  591. [[fallthrough]];
  592. case ExpectXtermFinal:
  593. m_escape_state = Normal;
  594. if (ch == '\007')
  595. execute_xterm_command();
  596. return;
  597. case ExpectParameter:
  598. if (is_valid_parameter_character(ch)) {
  599. m_parameters.append(ch);
  600. return;
  601. }
  602. m_escape_state = ExpectIntermediate;
  603. [[fallthrough]];
  604. case ExpectIntermediate:
  605. if (is_valid_intermediate_character(ch)) {
  606. m_intermediates.append(ch);
  607. return;
  608. }
  609. m_escape_state = ExpectFinal;
  610. [[fallthrough]];
  611. case ExpectFinal:
  612. if (is_valid_final_character(ch)) {
  613. m_escape_state = Normal;
  614. if (!m_swallow_current)
  615. execute_escape_sequence(ch);
  616. m_swallow_current = false;
  617. return;
  618. }
  619. m_escape_state = Normal;
  620. m_swallow_current = false;
  621. return;
  622. case Normal:
  623. break;
  624. }
  625. switch (ch) {
  626. case '\0':
  627. return;
  628. case '\033':
  629. m_escape_state = ExpectBracket;
  630. m_swallow_current = false;
  631. return;
  632. case 8: // Backspace
  633. if (m_cursor_column) {
  634. set_cursor(m_cursor_row, m_cursor_column - 1);
  635. put_character_at(m_cursor_row, m_cursor_column, ' ');
  636. return;
  637. }
  638. return;
  639. case '\a':
  640. if (m_should_beep)
  641. sysbeep();
  642. else {
  643. m_visual_beep_timer.restart(200);
  644. m_visual_beep_timer.set_single_shot(true);
  645. m_visual_beep_timer.on_timeout = [this] {
  646. force_repaint();
  647. };
  648. force_repaint();
  649. }
  650. return;
  651. case '\t': {
  652. for (unsigned i = m_cursor_column; i < columns(); ++i) {
  653. if (m_horizontal_tabs[i]) {
  654. set_cursor(m_cursor_row, i);
  655. return;
  656. }
  657. }
  658. return;
  659. }
  660. case '\r':
  661. set_cursor(m_cursor_row, 0);
  662. return;
  663. case '\n':
  664. newline();
  665. return;
  666. }
  667. auto new_column = m_cursor_column + 1;
  668. if (new_column < columns()) {
  669. put_character_at(m_cursor_row, m_cursor_column, ch);
  670. set_cursor(m_cursor_row, new_column);
  671. } else {
  672. if (m_stomp) {
  673. m_stomp = false;
  674. newline();
  675. put_character_at(m_cursor_row, m_cursor_column, ch);
  676. set_cursor(m_cursor_row, 1);
  677. } else {
  678. // Curious: We wait once on the right-hand side
  679. m_stomp = true;
  680. put_character_at(m_cursor_row, m_cursor_column, ch);
  681. }
  682. }
  683. }
  684. void Terminal::inject_string(const String& str)
  685. {
  686. for (int i = 0; i < str.length(); ++i)
  687. on_char(str[i]);
  688. }
  689. void Terminal::unimplemented_escape()
  690. {
  691. StringBuilder builder;
  692. builder.appendf("((Unimplemented escape: %c", m_final);
  693. if (!m_parameters.is_empty()) {
  694. builder.append(" parameters:");
  695. for (int i = 0; i < m_parameters.size(); ++i)
  696. builder.append((char)m_parameters[i]);
  697. }
  698. if (!m_intermediates.is_empty()) {
  699. builder.append(" intermediates:");
  700. for (int i = 0; i < m_intermediates.size(); ++i)
  701. builder.append((char)m_intermediates[i]);
  702. }
  703. builder.append("))");
  704. inject_string(builder.to_string());
  705. }
  706. void Terminal::unimplemented_xterm_escape()
  707. {
  708. auto message = String::format("((Unimplemented xterm escape: %c))\n", m_final);
  709. inject_string(message);
  710. }
  711. void Terminal::set_size(word columns, word rows)
  712. {
  713. if (columns == m_columns && rows == m_rows)
  714. return;
  715. if (m_lines) {
  716. for (size_t i = 0; i < m_rows; ++i)
  717. delete m_lines[i];
  718. delete m_lines;
  719. }
  720. m_columns = columns;
  721. m_rows = rows;
  722. m_scroll_region_top = 0;
  723. m_scroll_region_bottom = rows;
  724. m_cursor_row = 0;
  725. m_cursor_column = 0;
  726. m_saved_cursor_row = 0;
  727. m_saved_cursor_column = 0;
  728. if (m_horizontal_tabs)
  729. free(m_horizontal_tabs);
  730. m_horizontal_tabs = static_cast<byte*>(malloc(columns));
  731. for (unsigned i = 0; i < columns; ++i)
  732. m_horizontal_tabs[i] = (i % 8) == 0;
  733. // Rightmost column is always last tab on line.
  734. m_horizontal_tabs[columns - 1] = 1;
  735. m_lines = new Line*[rows];
  736. for (size_t i = 0; i < rows; ++i)
  737. m_lines[i] = new Line(columns);
  738. m_pixel_width = (frame_thickness() * 2) + (m_inset * 2) + (m_columns * font().glyph_width('x'));
  739. m_pixel_height = (frame_thickness() * 2) + (m_inset * 2) + (m_rows * (font().glyph_height() + m_line_spacing)) - m_line_spacing;
  740. set_size_policy(SizePolicy::Fixed, SizePolicy::Fixed);
  741. set_preferred_size({ m_pixel_width, m_pixel_height });
  742. m_rows_to_scroll_backing_store = 0;
  743. m_needs_background_fill = true;
  744. force_repaint();
  745. winsize ws;
  746. ws.ws_row = rows;
  747. ws.ws_col = columns;
  748. int rc = ioctl(m_ptm_fd, TIOCSWINSZ, &ws);
  749. ASSERT(rc == 0);
  750. }
  751. Rect Terminal::glyph_rect(word row, word column)
  752. {
  753. int y = row * m_line_height;
  754. int x = column * font().glyph_width('x');
  755. return { x + frame_thickness() + m_inset, y + frame_thickness() + m_inset, font().glyph_width('x'), font().glyph_height() };
  756. }
  757. Rect Terminal::row_rect(word row)
  758. {
  759. int y = row * m_line_height;
  760. Rect rect = { frame_thickness() + m_inset, y + frame_thickness() + m_inset, font().glyph_width('x') * m_columns, font().glyph_height() };
  761. rect.inflate(0, m_line_spacing);
  762. return rect;
  763. }
  764. bool Terminal::Line::has_only_one_background_color() const
  765. {
  766. if (!length)
  767. return true;
  768. // FIXME: Cache this result?
  769. auto color = attributes[0].background_color;
  770. for (size_t i = 1; i < length; ++i) {
  771. if (attributes[i].background_color != color)
  772. return false;
  773. }
  774. return true;
  775. }
  776. void Terminal::event(CEvent& event)
  777. {
  778. if (event.type() == GEvent::WindowBecameActive || event.type() == GEvent::WindowBecameInactive) {
  779. m_in_active_window = event.type() == GEvent::WindowBecameActive;
  780. if (!m_in_active_window) {
  781. m_cursor_blink_timer.stop();
  782. } else {
  783. m_cursor_blink_state = true;
  784. m_cursor_blink_timer.start();
  785. }
  786. invalidate_cursor();
  787. update();
  788. }
  789. return GWidget::event(event);
  790. }
  791. void Terminal::keydown_event(GKeyEvent& event)
  792. {
  793. char ch = !event.text().is_empty() ? event.text()[0] : 0;
  794. if (event.ctrl()) {
  795. if (ch >= 'a' && ch <= 'z') {
  796. ch = ch - 'a' + 1;
  797. } else if (ch == '\\') {
  798. ch = 0x1c;
  799. }
  800. }
  801. switch (event.key()) {
  802. case KeyCode::Key_Up:
  803. write(m_ptm_fd, "\033[A", 3);
  804. break;
  805. case KeyCode::Key_Down:
  806. write(m_ptm_fd, "\033[B", 3);
  807. break;
  808. case KeyCode::Key_Right:
  809. write(m_ptm_fd, "\033[C", 3);
  810. break;
  811. case KeyCode::Key_Left:
  812. write(m_ptm_fd, "\033[D", 3);
  813. break;
  814. case KeyCode::Key_Home:
  815. write(m_ptm_fd, "\033[H", 3);
  816. break;
  817. case KeyCode::Key_End:
  818. write(m_ptm_fd, "\033[F", 3);
  819. break;
  820. case KeyCode::Key_RightShift:
  821. dbgprintf("Terminal: A wild Right Shift key is pressed. Not handled.\n");
  822. break;
  823. default:
  824. write(m_ptm_fd, &ch, 1);
  825. break;
  826. }
  827. }
  828. void Terminal::paint_event(GPaintEvent& event)
  829. {
  830. GFrame::paint_event(event);
  831. GPainter painter(*this);
  832. if (m_needs_background_fill) {
  833. m_needs_background_fill = false;
  834. if (m_visual_beep_timer.is_active())
  835. painter.fill_rect(frame_inner_rect(), Color::Red);
  836. else
  837. painter.fill_rect(frame_inner_rect(), Color(Color::Black).with_alpha(255 * m_opacity));
  838. }
  839. if (m_rows_to_scroll_backing_store && m_rows_to_scroll_backing_store < m_rows) {
  840. int first_scanline = m_inset;
  841. int second_scanline = m_inset + (m_rows_to_scroll_backing_store * m_line_height);
  842. int num_rows_to_memcpy = m_rows - m_rows_to_scroll_backing_store;
  843. int scanlines_to_copy = (num_rows_to_memcpy * m_line_height) - m_line_spacing;
  844. memcpy(
  845. painter.target()->scanline(first_scanline),
  846. painter.target()->scanline(second_scanline),
  847. scanlines_to_copy * painter.target()->pitch()
  848. );
  849. line(max(0, m_cursor_row - m_rows_to_scroll_backing_store)).dirty = true;
  850. }
  851. m_rows_to_scroll_backing_store = 0;
  852. invalidate_cursor();
  853. for (word row = 0; row < m_rows; ++row) {
  854. auto& line = this->line(row);
  855. if (!line.dirty)
  856. continue;
  857. line.dirty = false;
  858. bool has_only_one_background_color = line.has_only_one_background_color();
  859. if (m_visual_beep_timer.is_active())
  860. painter.fill_rect(row_rect(row), Color::Red);
  861. else if (has_only_one_background_color)
  862. painter.fill_rect(row_rect(row), lookup_color(line.attributes[0].background_color).with_alpha(255 * m_opacity));
  863. for (word column = 0; column < m_columns; ++column) {
  864. bool should_reverse_fill_for_cursor = m_cursor_blink_state && m_in_active_window && row == m_cursor_row && column == m_cursor_column;
  865. auto& attribute = line.attributes[column];
  866. char ch = line.characters[column];
  867. auto character_rect = glyph_rect(row, column);
  868. if (!has_only_one_background_color || should_reverse_fill_for_cursor) {
  869. auto cell_rect = character_rect.inflated(0, m_line_spacing);
  870. painter.fill_rect(cell_rect, lookup_color(should_reverse_fill_for_cursor ? attribute.foreground_color : attribute.background_color).with_alpha(255 * m_opacity));
  871. }
  872. if (ch == ' ')
  873. continue;
  874. painter.draw_glyph(character_rect.location(), ch, lookup_color(should_reverse_fill_for_cursor ? attribute.background_color : attribute.foreground_color));
  875. }
  876. }
  877. if (!m_in_active_window) {
  878. auto cell_rect = glyph_rect(m_cursor_row, m_cursor_column).inflated(0, m_line_spacing);
  879. painter.draw_rect(cell_rect, lookup_color(line(m_cursor_row).attributes[m_cursor_column].foreground_color));
  880. }
  881. }
  882. void Terminal::set_window_title(const String& title)
  883. {
  884. auto* w = window();
  885. if (!w)
  886. return;
  887. w->set_title(title);
  888. }
  889. void Terminal::invalidate_cursor()
  890. {
  891. line(m_cursor_row).dirty = true;
  892. }
  893. void Terminal::flush_dirty_lines()
  894. {
  895. if (m_need_full_flush) {
  896. update();
  897. m_need_full_flush = false;
  898. return;
  899. }
  900. Rect rect;
  901. for (int i = 0; i < m_rows; ++i) {
  902. if (line(i).dirty)
  903. rect = rect.united(row_rect(i));
  904. }
  905. update(rect);
  906. }
  907. void Terminal::force_repaint()
  908. {
  909. m_needs_background_fill = true;
  910. for (int i = 0; i < m_rows; ++i)
  911. line(i).dirty = true;
  912. update();
  913. }
  914. void Terminal::resize_event(GResizeEvent& event)
  915. {
  916. int new_columns = (event.size().width() - frame_thickness() * 2 - m_inset * 2) / font().glyph_width('x');
  917. int new_rows = (event.size().height() - frame_thickness() * 2 - m_inset * 2) / m_line_height;
  918. set_size(new_columns, new_rows);
  919. }
  920. void Terminal::apply_size_increments_to_window(GWindow& window)
  921. {
  922. window.set_size_increment({ font().glyph_width('x'), m_line_height });
  923. window.set_base_size({ frame_thickness() * 2 + m_inset * 2, frame_thickness() * 2 + m_inset * 2 });
  924. }
  925. void Terminal::update_cursor()
  926. {
  927. invalidate_cursor();
  928. flush_dirty_lines();
  929. }
  930. void Terminal::set_opacity(float opacity)
  931. {
  932. if (m_opacity == opacity)
  933. return;
  934. window()->set_has_alpha_channel(opacity < 1);
  935. m_opacity = opacity;
  936. force_repaint();
  937. }