Terminal.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797
  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 <SharedGraphics/Painter.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. Terminal::Terminal(int ptm_fd)
  19. : m_ptm_fd(ptm_fd)
  20. , m_font(Font::default_font())
  21. , m_notifier(ptm_fd, GNotifier::Read)
  22. {
  23. m_notifier.on_ready_to_read = [this] (GNotifier& notifier) {
  24. byte buffer[BUFSIZ];
  25. ssize_t nread = read(notifier.fd(), buffer, sizeof(buffer));
  26. if (nread < 0) {
  27. dbgprintf("Terminal read error: %s\n", strerror(errno));
  28. perror("read(ptm)");
  29. GApplication::the().quit(1);
  30. return;
  31. }
  32. if (nread == 0) {
  33. dbgprintf("Terminal: EOF on master pty, closing.\n");
  34. GApplication::the().quit(0);
  35. return;
  36. }
  37. for (ssize_t i = 0; i < nread; ++i)
  38. on_char(buffer[i]);
  39. flush_dirty_lines();
  40. };
  41. set_fill_with_background_color(false);
  42. m_line_height = font().glyph_height() + m_line_spacing;
  43. set_size(80, 25);
  44. }
  45. Terminal::Line::Line(word columns)
  46. : length(columns)
  47. {
  48. characters = new byte[length];
  49. attributes = new Attribute[length];
  50. memset(characters, ' ', length);
  51. }
  52. Terminal::Line::~Line()
  53. {
  54. delete [] characters;
  55. delete [] attributes;
  56. }
  57. void Terminal::Line::clear(Attribute attribute)
  58. {
  59. if (dirty) {
  60. memset(characters, ' ', length);
  61. for (word i = 0 ; i < length; ++i)
  62. attributes[i] = attribute;
  63. return;
  64. }
  65. for (unsigned i = 0 ; i < length; ++i) {
  66. if (characters[i] != ' ')
  67. dirty = true;
  68. characters[i] = ' ';
  69. }
  70. for (unsigned i = 0 ; i < length; ++i) {
  71. if (attributes[i] != attribute)
  72. dirty = true;
  73. attributes[i] = attribute;
  74. }
  75. }
  76. Terminal::~Terminal()
  77. {
  78. for (size_t i = 0; i < m_rows; ++i)
  79. delete m_lines[i];
  80. delete [] m_lines;
  81. free(m_horizontal_tabs);
  82. }
  83. void Terminal::clear()
  84. {
  85. for (size_t i = 0; i < rows(); ++i)
  86. line(i).clear(m_current_attribute);
  87. set_cursor(0, 0);
  88. }
  89. inline bool is_valid_parameter_character(byte ch)
  90. {
  91. return ch >= 0x30 && ch <= 0x3f;
  92. }
  93. inline bool is_valid_intermediate_character(byte ch)
  94. {
  95. return ch >= 0x20 && ch <= 0x2f;
  96. }
  97. inline bool is_valid_final_character(byte ch)
  98. {
  99. return ch >= 0x40 && ch <= 0x7e;
  100. }
  101. unsigned parse_uint(const String& str, bool& ok)
  102. {
  103. unsigned value = 0;
  104. for (size_t i = 0; i < str.length(); ++i) {
  105. if (str[i] < '0' || str[i] > '9') {
  106. ok = false;
  107. return 0;
  108. }
  109. value = value * 10;
  110. value += str[i] - '0';
  111. }
  112. ok = true;
  113. return value;
  114. }
  115. static inline Color lookup_color(unsigned color)
  116. {
  117. return Color::from_rgb(xterm_colors[color]);
  118. }
  119. void Terminal::escape$m(const Vector<unsigned>& params)
  120. {
  121. if (params.size() == 3 && params[1] == 5) {
  122. if (params[0] == 38) {
  123. m_current_attribute.foreground_color = params[2];
  124. return;
  125. } else if (params[0] == 48) {
  126. m_current_attribute.background_color = params[2];
  127. return;
  128. }
  129. }
  130. for (auto param : params) {
  131. switch (param) {
  132. case 0:
  133. // Reset
  134. m_current_attribute.reset();
  135. break;
  136. case 1:
  137. // Bold
  138. //m_current_attribute.bold = true;
  139. break;
  140. case 30:
  141. case 31:
  142. case 32:
  143. case 33:
  144. case 34:
  145. case 35:
  146. case 36:
  147. case 37:
  148. // Foreground color
  149. m_current_attribute.foreground_color = param - 30;
  150. break;
  151. case 40:
  152. case 41:
  153. case 42:
  154. case 43:
  155. case 44:
  156. case 45:
  157. case 46:
  158. case 47:
  159. // Background color
  160. m_current_attribute.background_color = param - 40;
  161. break;
  162. }
  163. }
  164. }
  165. void Terminal::escape$s(const Vector<unsigned>&)
  166. {
  167. m_saved_cursor_row = m_cursor_row;
  168. m_saved_cursor_column = m_cursor_column;
  169. }
  170. void Terminal::escape$u(const Vector<unsigned>&)
  171. {
  172. set_cursor(m_saved_cursor_row, m_saved_cursor_column);
  173. }
  174. void Terminal::escape$H(const Vector<unsigned>& params)
  175. {
  176. unsigned row = 1;
  177. unsigned col = 1;
  178. if (params.size() >= 1)
  179. row = params[0];
  180. if (params.size() >= 2)
  181. col = params[1];
  182. set_cursor(row - 1, col - 1);
  183. }
  184. void Terminal::escape$A(const Vector<unsigned>& params)
  185. {
  186. int num = 1;
  187. if (params.size() >= 1)
  188. num = params[0];
  189. if (num == 0)
  190. num = 1;
  191. int new_row = (int)m_cursor_row - num;
  192. if (new_row < 0)
  193. new_row = 0;
  194. set_cursor(new_row, m_cursor_column);
  195. }
  196. void Terminal::escape$B(const Vector<unsigned>& params)
  197. {
  198. int num = 1;
  199. if (params.size() >= 1)
  200. num = params[0];
  201. if (num == 0)
  202. num = 1;
  203. int new_row = (int)m_cursor_row + num;
  204. if (new_row >= m_rows)
  205. new_row = m_rows - 1;
  206. set_cursor(new_row, m_cursor_column);
  207. }
  208. void Terminal::escape$C(const Vector<unsigned>& params)
  209. {
  210. int num = 1;
  211. if (params.size() >= 1)
  212. num = params[0];
  213. if (num == 0)
  214. num = 1;
  215. int new_column = (int)m_cursor_column + num;
  216. if (new_column >= m_columns)
  217. new_column = m_columns - 1;
  218. set_cursor(m_cursor_row, new_column);
  219. }
  220. void Terminal::escape$D(const Vector<unsigned>& params)
  221. {
  222. int num = 1;
  223. if (params.size() >= 1)
  224. num = params[0];
  225. if (num == 0)
  226. num = 1;
  227. int new_column = (int)m_cursor_column - num;
  228. if (new_column < 0)
  229. new_column = 0;
  230. set_cursor(m_cursor_row, new_column);
  231. }
  232. void Terminal::escape$K(const Vector<unsigned>& params)
  233. {
  234. int mode = 0;
  235. if (params.size() >= 1)
  236. mode = params[0];
  237. switch (mode) {
  238. case 0:
  239. // Clear from cursor to end of line.
  240. for (int i = m_cursor_column; i < m_columns; ++i) {
  241. put_character_at(m_cursor_row, i, ' ');
  242. }
  243. break;
  244. case 1:
  245. // FIXME: Clear from cursor to beginning of screen.
  246. unimplemented_escape();
  247. break;
  248. case 2:
  249. unimplemented_escape();
  250. break;
  251. default:
  252. unimplemented_escape();
  253. break;
  254. }
  255. }
  256. void Terminal::escape$J(const Vector<unsigned>& params)
  257. {
  258. int mode = 0;
  259. if (params.size() >= 1)
  260. mode = params[0];
  261. switch (mode) {
  262. case 0:
  263. // Clear from cursor to end of screen.
  264. for (int i = m_cursor_column; i < m_columns; ++i) {
  265. put_character_at(m_cursor_row, i, ' ');
  266. }
  267. for (int row = m_cursor_row + 1; row < m_rows; ++row) {
  268. for (int column = 0; column < m_columns; ++column) {
  269. put_character_at(row, column, ' ');
  270. }
  271. }
  272. break;
  273. case 1:
  274. // FIXME: Clear from cursor to beginning of screen.
  275. unimplemented_escape();
  276. break;
  277. case 2:
  278. clear();
  279. break;
  280. case 3:
  281. // FIXME: <esc>[3J should also clear the scrollback buffer.
  282. clear();
  283. break;
  284. default:
  285. unimplemented_escape();
  286. break;
  287. }
  288. }
  289. void Terminal::escape$M(const Vector<unsigned>& params)
  290. {
  291. int count = 1;
  292. if (params.size() >= 1)
  293. count = params[0];
  294. if (count == 1 && m_cursor_row == 0) {
  295. scroll_up();
  296. return;
  297. }
  298. int max_count = m_rows - m_cursor_row;
  299. count = min(count, max_count);
  300. dbgprintf("Delete %d line(s) starting from %d\n", count, m_cursor_row);
  301. // FIXME: Implement.
  302. ASSERT_NOT_REACHED();
  303. }
  304. void Terminal::execute_xterm_command()
  305. {
  306. m_final = '@';
  307. bool ok;
  308. unsigned value = parse_uint(String((const char*)m_xterm_param1.data(), m_xterm_param1.size()), ok);
  309. if (ok) {
  310. switch (value) {
  311. case 0:
  312. case 1:
  313. case 2:
  314. set_window_title(String((const char*)m_xterm_param2.data(), m_xterm_param2.size()));
  315. break;
  316. default:
  317. unimplemented_xterm_escape();
  318. break;
  319. }
  320. }
  321. m_xterm_param1.clear_with_capacity();
  322. m_xterm_param2.clear_with_capacity();
  323. }
  324. void Terminal::execute_escape_sequence(byte final)
  325. {
  326. m_final = final;
  327. auto paramparts = String((const char*)m_parameters.data(), m_parameters.size()).split(';');
  328. Vector<unsigned> params;
  329. for (auto& parampart : paramparts) {
  330. bool ok;
  331. unsigned value = parse_uint(parampart, ok);
  332. if (!ok) {
  333. m_parameters.clear_with_capacity();
  334. m_intermediates.clear_with_capacity();
  335. // FIXME: Should we do something else?
  336. return;
  337. }
  338. params.append(value);
  339. }
  340. switch (final) {
  341. case 'A': escape$A(params); break;
  342. case 'B': escape$B(params); break;
  343. case 'C': escape$C(params); break;
  344. case 'D': escape$D(params); break;
  345. case 'H': escape$H(params); break;
  346. case 'J': escape$J(params); break;
  347. case 'K': escape$K(params); break;
  348. case 'M': escape$M(params); break;
  349. case 'm': escape$m(params); break;
  350. case 's': escape$s(params); break;
  351. case 'u': escape$u(params); break;
  352. default:
  353. dbgprintf("Terminal::execute_escape_sequence: Unhandled final '%c'\n", final);
  354. break;
  355. }
  356. m_parameters.clear_with_capacity();
  357. m_intermediates.clear_with_capacity();
  358. }
  359. void Terminal::newline()
  360. {
  361. word new_row = m_cursor_row;
  362. if (m_cursor_row == (rows() - 1)) {
  363. scroll_up();
  364. } else {
  365. ++new_row;
  366. }
  367. set_cursor(new_row, 0);
  368. }
  369. void Terminal::scroll_up()
  370. {
  371. // NOTE: We have to invalidate the cursor first.
  372. invalidate_cursor();
  373. delete m_lines[0];
  374. for (word row = 1; row < rows(); ++row)
  375. m_lines[row - 1] = m_lines[row];
  376. m_lines[m_rows - 1] = new Line(m_columns);
  377. ++m_rows_to_scroll_backing_store;
  378. m_need_full_flush = true;
  379. }
  380. void Terminal::set_cursor(unsigned a_row, unsigned a_column)
  381. {
  382. unsigned row = min(a_row, m_rows - 1u);
  383. unsigned column = min(a_column, m_columns - 1u);
  384. if (row == m_cursor_row && column == m_cursor_column)
  385. return;
  386. ASSERT(row < rows());
  387. ASSERT(column < columns());
  388. invalidate_cursor();
  389. m_cursor_row = row;
  390. m_cursor_column = column;
  391. if (column != columns() - 1)
  392. m_stomp = false;
  393. invalidate_cursor();
  394. }
  395. void Terminal::put_character_at(unsigned row, unsigned column, byte ch)
  396. {
  397. ASSERT(row < rows());
  398. ASSERT(column < columns());
  399. auto& line = this->line(row);
  400. if ((line.characters[column] == ch) && (line.attributes[column] == m_current_attribute))
  401. return;
  402. line.characters[column] = ch;
  403. line.attributes[column] = m_current_attribute;
  404. line.dirty = true;
  405. }
  406. void Terminal::on_char(byte ch)
  407. {
  408. #ifdef TERMINAL_DEBUG
  409. dbgprintf("Terminal::on_char: %b (%c), fg=%u, bg=%u\n", ch, ch, m_current_attribute.foreground_color, m_current_attribute.background_color);
  410. #endif
  411. switch (m_escape_state) {
  412. case ExpectBracket:
  413. if (ch == '[')
  414. m_escape_state = ExpectParameter;
  415. else if (ch == ']')
  416. m_escape_state = ExpectXtermParameter1;
  417. else
  418. m_escape_state = Normal;
  419. return;
  420. case ExpectXtermParameter1:
  421. if (ch != ';') {
  422. m_xterm_param1.append(ch);
  423. return;
  424. }
  425. m_escape_state = ExpectXtermParameter2;
  426. return;
  427. case ExpectXtermParameter2:
  428. if (ch != '\007') {
  429. m_xterm_param2.append(ch);
  430. return;
  431. }
  432. m_escape_state = ExpectXtermFinal;
  433. [[fallthrough]];
  434. case ExpectXtermFinal:
  435. m_escape_state = Normal;
  436. if (ch == '\007')
  437. execute_xterm_command();
  438. return;
  439. case ExpectParameter:
  440. if (is_valid_parameter_character(ch)) {
  441. m_parameters.append(ch);
  442. return;
  443. }
  444. m_escape_state = ExpectIntermediate;
  445. [[fallthrough]];
  446. case ExpectIntermediate:
  447. if (is_valid_intermediate_character(ch)) {
  448. m_intermediates.append(ch);
  449. return;
  450. }
  451. m_escape_state = ExpectFinal;
  452. [[fallthrough]];
  453. case ExpectFinal:
  454. if (is_valid_final_character(ch)) {
  455. m_escape_state = Normal;
  456. execute_escape_sequence(ch);
  457. return;
  458. }
  459. m_escape_state = Normal;
  460. return;
  461. case Normal:
  462. break;
  463. }
  464. switch (ch) {
  465. case '\0':
  466. return;
  467. case '\033':
  468. m_escape_state = ExpectBracket;
  469. return;
  470. case 8: // Backspace
  471. if (m_cursor_column) {
  472. set_cursor(m_cursor_row, m_cursor_column - 1);
  473. put_character_at(m_cursor_row, m_cursor_column, ' ');
  474. return;
  475. }
  476. return;
  477. case '\a':
  478. // FIXME: Bell!
  479. return;
  480. case '\t': {
  481. for (unsigned i = m_cursor_column; i < columns(); ++i) {
  482. if (m_horizontal_tabs[i]) {
  483. set_cursor(m_cursor_row, i);
  484. return;
  485. }
  486. }
  487. return;
  488. }
  489. case '\r':
  490. set_cursor(m_cursor_row, 0);
  491. return;
  492. case '\n':
  493. newline();
  494. return;
  495. }
  496. auto new_column = m_cursor_column + 1;
  497. if (new_column < columns()) {
  498. put_character_at(m_cursor_row, m_cursor_column, ch);
  499. set_cursor(m_cursor_row, new_column);
  500. } else {
  501. if (m_stomp) {
  502. m_stomp = false;
  503. newline();
  504. put_character_at(m_cursor_row, m_cursor_column, ch);
  505. set_cursor(m_cursor_row, 1);
  506. } else {
  507. // Curious: We wait once on the right-hand side
  508. m_stomp = true;
  509. put_character_at(m_cursor_row, m_cursor_column, ch);
  510. }
  511. }
  512. }
  513. void Terminal::inject_string(const String& str)
  514. {
  515. for (size_t i = 0; i < str.length(); ++i)
  516. on_char(str[i]);
  517. }
  518. void Terminal::unimplemented_escape()
  519. {
  520. StringBuilder builder;
  521. builder.appendf("((Unimplemented escape: %c", m_final);
  522. if (!m_parameters.is_empty()) {
  523. builder.append(" parameters:");
  524. for (size_t i = 0; i < m_parameters.size(); ++i)
  525. builder.append((char)m_parameters[i]);
  526. }
  527. if (!m_intermediates.is_empty()) {
  528. builder.append(" intermediates:");
  529. for (size_t i = 0; i < m_intermediates.size(); ++i)
  530. builder.append((char)m_intermediates[i]);
  531. }
  532. builder.append("))");
  533. inject_string(builder.to_string());
  534. }
  535. void Terminal::unimplemented_xterm_escape()
  536. {
  537. auto message = String::format("((Unimplemented xterm escape: %c))\n", m_final);
  538. inject_string(message);
  539. }
  540. void Terminal::set_size(word columns, word rows)
  541. {
  542. if (columns == m_columns && rows == m_rows)
  543. return;
  544. if (m_lines) {
  545. for (size_t i = 0; i < m_rows; ++i)
  546. delete m_lines[i];
  547. delete m_lines;
  548. }
  549. m_columns = columns;
  550. m_rows = rows;
  551. m_cursor_row = 0;
  552. m_cursor_column = 0;
  553. m_saved_cursor_row = 0;
  554. m_saved_cursor_column = 0;
  555. if (m_horizontal_tabs)
  556. free(m_horizontal_tabs);
  557. m_horizontal_tabs = static_cast<byte*>(malloc(columns));
  558. for (unsigned i = 0; i < columns; ++i)
  559. m_horizontal_tabs[i] = (i % 8) == 0;
  560. // Rightmost column is always last tab on line.
  561. m_horizontal_tabs[columns - 1] = 1;
  562. m_lines = new Line*[rows];
  563. for (size_t i = 0; i < rows; ++i)
  564. m_lines[i] = new Line(columns);
  565. m_pixel_width = m_columns * font().glyph_width() + m_inset * 2;
  566. m_pixel_height = (m_rows * (font().glyph_height() + m_line_spacing)) + (m_inset * 2) - m_line_spacing;
  567. set_size_policy(SizePolicy::Fixed, SizePolicy::Fixed);
  568. set_preferred_size({ m_pixel_width, m_pixel_height });
  569. m_rows_to_scroll_backing_store = 0;
  570. m_needs_background_fill = true;
  571. force_repaint();
  572. winsize ws;
  573. ws.ws_row = rows;
  574. ws.ws_col = columns;
  575. int rc = ioctl(m_ptm_fd, TIOCSWINSZ, &ws);
  576. ASSERT(rc == 0);
  577. }
  578. Rect Terminal::glyph_rect(word row, word column)
  579. {
  580. int y = row * m_line_height;
  581. int x = column * font().glyph_width();
  582. return { x + m_inset, y + m_inset, font().glyph_width(), font().glyph_height() };
  583. }
  584. Rect Terminal::row_rect(word row)
  585. {
  586. int y = row * m_line_height;
  587. Rect rect = { m_inset, y + m_inset, font().glyph_width() * m_columns, font().glyph_height() };
  588. rect.inflate(0, m_line_spacing);
  589. return rect;
  590. }
  591. bool Terminal::Line::has_only_one_background_color() const
  592. {
  593. if (!length)
  594. return true;
  595. // FIXME: Cache this result?
  596. auto color = attributes[0].background_color;
  597. for (size_t i = 1; i < length; ++i) {
  598. if (attributes[i].background_color != color)
  599. return false;
  600. }
  601. return true;
  602. }
  603. void Terminal::event(GEvent& event)
  604. {
  605. if (event.type() == GEvent::WindowBecameActive || event.type() == GEvent::WindowBecameInactive) {
  606. m_in_active_window = event.type() == GEvent::WindowBecameActive;
  607. invalidate_cursor();
  608. update();
  609. }
  610. return GWidget::event(event);
  611. }
  612. void Terminal::keydown_event(GKeyEvent& event)
  613. {
  614. char ch = !event.text().is_empty() ? event.text()[0] : 0;
  615. if (event.ctrl()) {
  616. if (ch >= 'a' && ch <= 'z') {
  617. ch = ch - 'a' + 1;
  618. } else if (ch == '\\') {
  619. ch = 0x1c;
  620. }
  621. }
  622. switch (event.key()) {
  623. case KeyCode::Key_Up:
  624. write(m_ptm_fd, "\033[A", 3);
  625. break;
  626. case KeyCode::Key_Down:
  627. write(m_ptm_fd, "\033[B", 3);
  628. break;
  629. case KeyCode::Key_Right:
  630. write(m_ptm_fd, "\033[C", 3);
  631. break;
  632. case KeyCode::Key_Left:
  633. write(m_ptm_fd, "\033[D", 3);
  634. break;
  635. default:
  636. write(m_ptm_fd, &ch, 1);
  637. break;
  638. }
  639. }
  640. void Terminal::paint_event(GPaintEvent&)
  641. {
  642. Painter painter(*this);
  643. if (m_needs_background_fill) {
  644. m_needs_background_fill = false;
  645. painter.fill_rect(rect(), Color(Color::Black).with_alpha(255 * m_opacity));
  646. }
  647. if (m_rows_to_scroll_backing_store && m_rows_to_scroll_backing_store < m_rows) {
  648. int first_scanline = m_inset;
  649. int second_scanline = m_inset + (m_rows_to_scroll_backing_store * m_line_height);
  650. int num_rows_to_memcpy = m_rows - m_rows_to_scroll_backing_store;
  651. int scanlines_to_copy = (num_rows_to_memcpy * m_line_height) - m_line_spacing;
  652. fast_dword_copy(
  653. painter.target()->scanline(first_scanline),
  654. painter.target()->scanline(second_scanline),
  655. scanlines_to_copy * m_pixel_width
  656. );
  657. line(max(0, m_cursor_row - m_rows_to_scroll_backing_store)).dirty = true;
  658. }
  659. m_rows_to_scroll_backing_store = 0;
  660. invalidate_cursor();
  661. for (word row = 0; row < m_rows; ++row) {
  662. auto& line = this->line(row);
  663. if (!line.dirty)
  664. continue;
  665. line.dirty = false;
  666. bool has_only_one_background_color = line.has_only_one_background_color();
  667. if (has_only_one_background_color) {
  668. painter.fill_rect(row_rect(row), lookup_color(line.attributes[0].background_color).with_alpha(255 * m_opacity));
  669. }
  670. for (word column = 0; column < m_columns; ++column) {
  671. bool should_reverse_fill_for_cursor = m_in_active_window && row == m_cursor_row && column == m_cursor_column;
  672. auto& attribute = line.attributes[column];
  673. char ch = line.characters[column];
  674. auto character_rect = glyph_rect(row, column);
  675. if (!has_only_one_background_color || should_reverse_fill_for_cursor) {
  676. auto cell_rect = character_rect.inflated(0, m_line_spacing);
  677. painter.fill_rect(cell_rect, lookup_color(should_reverse_fill_for_cursor ? attribute.foreground_color : attribute.background_color).with_alpha(255 * m_opacity));
  678. }
  679. if (ch == ' ')
  680. continue;
  681. painter.draw_glyph(character_rect.location(), ch, lookup_color(should_reverse_fill_for_cursor ? attribute.background_color : attribute.foreground_color));
  682. }
  683. }
  684. if (!m_in_active_window) {
  685. auto cell_rect = glyph_rect(m_cursor_row, m_cursor_column).inflated(0, m_line_spacing);
  686. painter.draw_rect(cell_rect, lookup_color(line(m_cursor_row).attributes[m_cursor_column].foreground_color));
  687. }
  688. if (m_belling)
  689. painter.draw_rect(rect(), Color::Red);
  690. }
  691. void Terminal::set_window_title(String&& title)
  692. {
  693. auto* w = window();
  694. if (!w)
  695. return;
  696. w->set_title(move(title));
  697. }
  698. void Terminal::invalidate_cursor()
  699. {
  700. line(m_cursor_row).dirty = true;
  701. }
  702. void Terminal::flush_dirty_lines()
  703. {
  704. if (m_need_full_flush) {
  705. update();
  706. m_need_full_flush = false;
  707. return;
  708. }
  709. Rect rect;
  710. for (int i = 0; i < m_rows; ++i) {
  711. if (line(i).dirty)
  712. rect = rect.united(row_rect(i));
  713. }
  714. update(rect);
  715. }
  716. void Terminal::force_repaint()
  717. {
  718. for (int i = 0; i < m_rows; ++i)
  719. line(i).dirty = true;
  720. m_need_full_flush = true;
  721. update();
  722. }
  723. void Terminal::resize_event(GResizeEvent& event)
  724. {
  725. int new_columns = event.size().width() / m_font->glyph_width();
  726. int new_rows = event.size().height() / m_line_height;
  727. set_size(new_columns, new_rows);
  728. }