Terminal.cpp 20 KB

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