You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

805 lines
25 KiB

  1. #define TFT_BLACK 0x0000
  2. #define TFT_BLUE 0x0014
  3. #define TFT_RED 0xA000
  4. #define TFT_GREEN 0x0500
  5. #define TFT_CYAN 0x0514
  6. #define TFT_MAGENTA 0xA014
  7. #define TFT_YELLOW 0xA500
  8. #define TFT_WHITE 0xA514
  9. #define TFT_BOLD_BLACK 0x8410
  10. #define TFT_BOLD_BLUE 0x001F
  11. #define TFT_BOLD_RED 0xF800
  12. #define TFT_BOLD_GREEN 0x07E0
  13. #define TFT_BOLD_CYAN 0x07FF
  14. #define TFT_BOLD_MAGENTA 0xF81F
  15. #define TFT_BOLD_YELLOW 0xFFE0
  16. #define TFT_BOLD_WHITE 0xFFFF
  17. #include "tintty.h"
  18. #include "font454.h"
  19. // exported variable for input logic
  20. // @todo refactor
  21. bool tintty_cursor_key_mode_application;
  22. const uint16_t ANSI_COLORS[] = {
  23. TFT_BLACK,
  24. TFT_RED,
  25. TFT_GREEN,
  26. TFT_YELLOW,
  27. TFT_BLUE,
  28. TFT_MAGENTA,
  29. TFT_CYAN,
  30. TFT_WHITE
  31. };
  32. const uint16_t ANSI_BOLD_COLORS[] = {
  33. TFT_BOLD_BLACK,
  34. TFT_BOLD_RED,
  35. TFT_BOLD_GREEN,
  36. TFT_BOLD_YELLOW,
  37. TFT_BOLD_BLUE,
  38. TFT_BOLD_MAGENTA,
  39. TFT_BOLD_CYAN,
  40. TFT_BOLD_WHITE
  41. };
  42. // cursor animation
  43. const int16_t IDLE_CYCLE_MAX = 500;
  44. const int16_t IDLE_CYCLE_ON = (IDLE_CYCLE_MAX/2);
  45. const int16_t TAB_SIZE = 4;
  46. // cursor and character position is in global buffer coordinate space (may exceed screen height)
  47. struct tintty_state {
  48. // @todo consider storing cursor position as single int offset
  49. int16_t cursor_col, cursor_row;
  50. uint16_t bg_ansi_color, fg_ansi_color;
  51. bool bold;
  52. // cursor mode
  53. bool cursor_key_mode_application;
  54. // saved DEC cursor info (in screen coords)
  55. int16_t dec_saved_col, dec_saved_row, dec_saved_bg, dec_saved_fg;
  56. uint8_t dec_saved_g4bank;
  57. bool dec_saved_bold, dec_saved_no_wrap;
  58. // @todo deal with integer overflow
  59. int16_t top_row; // first displayed row in a logical scrollback buffer
  60. bool no_wrap;
  61. bool cursor_hidden;
  62. char out_char;
  63. int16_t out_char_col, out_char_row;
  64. uint8_t out_char_g4bank; // current set shift state, G0 to G3
  65. int16_t out_clear_before, out_clear_after;
  66. uint8_t g4bank_char_set[4];
  67. int16_t idle_cycle_count; // @todo track during blocking reads mid-command
  68. } state;
  69. struct tintty_rendered {
  70. int16_t cursor_col, cursor_row;
  71. int16_t top_row;
  72. } rendered;
  73. // @todo support negative cursor_row
  74. void _render(tintty_display *display) {
  75. // expose the cursor key mode state
  76. tintty_cursor_key_mode_application = state.cursor_key_mode_application;
  77. // if scrolling, prepare the "recycled" screen area
  78. if (state.top_row != rendered.top_row) {
  79. // clear the new piece of screen to be recycled as blank space
  80. // @todo handle scroll-up
  81. if (state.top_row > rendered.top_row) {
  82. // pre-clear the lines at the bottom
  83. // @todo always use black instead of current background colour?
  84. // @todo deal with overflow from multiplication by CHAR_HEIGHT
  85. /*int16_t old_bottom_y = rendered.top_row * FONT_HEIGHT + display->screen_row_count * FONT_HEIGHT; // bottom of text may not align with screen height
  86. int16_t new_bottom_y = state.top_row * FONT_HEIGHT + display->screen_height; // extend to bottom edge of new displayed area
  87. int16_t clear_sbuf_bottom = new_bottom_y % display->screen_height;
  88. int16_t clear_height = min((int)display->screen_height, new_bottom_y - old_bottom_y);
  89. int16_t clear_sbuf_top = clear_sbuf_bottom - clear_height;*/
  90. // if rectangle straddles the screen buffer top edge, render that slice at bottom edge
  91. /*if (clear_sbuf_top < 0) {
  92. display->fill_rect(
  93. 0,
  94. clear_sbuf_top + display->screen_height,
  95. display->screen_width,
  96. -clear_sbuf_top,
  97. ANSI_COLORS[state.bg_ansi_color]
  98. );
  99. }*/
  100. // if rectangle is not entirely above top edge, render the normal slice
  101. /*if (clear_sbuf_bottom > 0) {
  102. display->fill_rect(
  103. 0,
  104. max(0, (int)clear_sbuf_top),
  105. display->screen_width,
  106. clear_sbuf_bottom - max(0, (int)clear_sbuf_top),
  107. ANSI_COLORS[state.bg_ansi_color]
  108. );
  109. }*/
  110. }
  111. // update displayed scroll
  112. display->set_vscroll((state.top_row) % display->screen_row_count); // @todo deal with overflow from multiplication
  113. // save rendered state
  114. rendered.top_row = state.top_row;
  115. }
  116. // render character if needed
  117. if (state.out_char != 0) {
  118. const uint16_t fg_tft_color = state.bold ? ANSI_BOLD_COLORS[state.fg_ansi_color] : ANSI_COLORS[state.fg_ansi_color];
  119. const uint16_t bg_tft_color = ANSI_COLORS[state.bg_ansi_color];
  120. const uint8_t char_set = state.g4bank_char_set[state.out_char_g4bank & 0x03]; // ensure 0-3 value
  121. display->print_character(state.out_char_col, state.out_char_row, fg_tft_color, bg_tft_color, state.out_char);
  122. // line-before
  123. // @todo detect when straddling edge of buffer
  124. if (state.out_clear_before > 0) {
  125. const int16_t line_before_chars = min(state.out_char_col, state.out_clear_before);
  126. const int16_t lines_before = (state.out_clear_before - line_before_chars) / display->screen_col_count;
  127. display->fill_rect(
  128. (state.out_char_col - line_before_chars),
  129. (state.out_char_row) % display->screen_row_count, // @todo deal with overflow from multiplication
  130. line_before_chars,
  131. 1,
  132. ANSI_COLORS[state.bg_ansi_color]
  133. );
  134. for (int16_t i = 0; i < lines_before; i += 1) {
  135. display->fill_rect(
  136. 0,
  137. (state.out_char_row - 1 - i) % display->screen_row_count, // @todo deal with overflow from multiplication
  138. display->screen_col_count,
  139. 1,
  140. ANSI_COLORS[state.bg_ansi_color]
  141. );
  142. }
  143. }
  144. // line-after
  145. // @todo detect when straddling edge of buffer
  146. if (state.out_clear_after > 0) {
  147. const int16_t line_after_chars = min(display->screen_col_count - 1 - state.out_char_col, (int) state.out_clear_after);
  148. const int16_t lines_after = (state.out_clear_after - line_after_chars) / display->screen_col_count;
  149. display->fill_rect(
  150. state.out_char_col + 1,
  151. (state.out_char_row) % display->screen_row_count, // @todo deal with overflow from multiplication
  152. line_after_chars,
  153. 1,
  154. ANSI_COLORS[state.bg_ansi_color]
  155. );
  156. for (int16_t i = 0; i < lines_after; i += 1) {
  157. display->fill_rect(
  158. 0,
  159. (state.out_char_row + 1 + i) % display->screen_row_count, // @todo deal with overflow from multiplication
  160. display->screen_col_count,
  161. 1,
  162. ANSI_COLORS[state.bg_ansi_color]
  163. );
  164. }
  165. }
  166. // clear for next render
  167. state.out_char = 0;
  168. state.out_clear_before = 0;
  169. state.out_clear_after = 0;
  170. // the char draw may overpaint the cursor, in which case
  171. // mark it for repaint
  172. if (
  173. rendered.cursor_col == state.out_char_col &&
  174. rendered.cursor_row == state.out_char_row
  175. ) {
  176. display->print_cursor(rendered.cursor_col, rendered.cursor_row, ANSI_COLORS[state.bg_ansi_color]);
  177. rendered.cursor_col = -1;
  178. }
  179. }
  180. // reflect new cursor bar render state
  181. const bool cursor_bar_shown = (
  182. !state.cursor_hidden &&
  183. state.idle_cycle_count < IDLE_CYCLE_ON
  184. );
  185. // clear existing rendered cursor bar if needed
  186. // @todo detect if it is already cleared during scroll
  187. if (rendered.cursor_col >= 0) {
  188. if (
  189. !cursor_bar_shown ||
  190. rendered.cursor_col != state.cursor_col ||
  191. rendered.cursor_row != state.cursor_row
  192. ) {
  193. display->print_cursor(rendered.cursor_col, rendered.cursor_row, ANSI_COLORS[state.bg_ansi_color]);
  194. // record the fact that cursor bar is not on screen
  195. rendered.cursor_col = -1;
  196. }
  197. }
  198. // render new cursor bar if not already shown
  199. // (sometimes right after clearing existing bar)
  200. if (rendered.cursor_col < 0) {
  201. if (cursor_bar_shown) {
  202. display->print_cursor(rendered.cursor_col, rendered.cursor_row, ANSI_COLORS[state.bg_ansi_color]);
  203. display->print_cursor(state.cursor_col, state.cursor_row, state.bold ? ANSI_BOLD_COLORS[state.fg_ansi_color] : ANSI_COLORS[state.fg_ansi_color]);
  204. // save new rendered state
  205. rendered.cursor_col = state.cursor_col;
  206. rendered.cursor_row = state.cursor_row;
  207. }
  208. }
  209. }
  210. void _ensure_cursor_vscroll(tintty_display *display) {
  211. // move displayed window down to cover cursor
  212. // @todo support scrolling up as well
  213. if (state.cursor_row - state.top_row >= display->screen_row_count) {
  214. state.top_row = state.cursor_row - display->screen_row_count + 1;
  215. }
  216. }
  217. void _send_sequence(
  218. void (*send_char)(char ch),
  219. char* str
  220. ) {
  221. // send zero-terminated sequence character by character
  222. while (*str) {
  223. send_char(*str);
  224. str += 1;
  225. }
  226. }
  227. char _read_decimal(
  228. char (*peek_char)(),
  229. char (*read_char)()
  230. ) {
  231. uint16_t accumulator = 0;
  232. while (isdigit(peek_char())) {
  233. const char digit_character = read_char();
  234. const uint16_t digit = digit_character - '0';
  235. accumulator = accumulator * 10 + digit;
  236. }
  237. return accumulator;
  238. }
  239. void _apply_graphic_rendition(
  240. uint16_t* arg_list,
  241. uint16_t arg_count
  242. ) {
  243. if (arg_count == 0) {
  244. // special case for resetting to default style
  245. state.bg_ansi_color = 0;
  246. state.fg_ansi_color = 7;
  247. state.bold = false;
  248. return;
  249. }
  250. // process commands
  251. // @todo support bold/etc for better colour support
  252. // @todo 39/49?
  253. for (uint16_t arg_index = 0; arg_index < arg_count; arg_index += 1) {
  254. const uint16_t arg_value = arg_list[arg_index];
  255. if (arg_value == 0) {
  256. // reset to default style
  257. state.bg_ansi_color = 0;
  258. state.fg_ansi_color = 7;
  259. state.bold = false;
  260. } else if (arg_value == 1) {
  261. // bold
  262. state.bold = true;
  263. } else if (arg_value >= 30 && arg_value <= 37) {
  264. // foreground ANSI colour
  265. state.fg_ansi_color = arg_value - 30;
  266. } else if (arg_value >= 40 && arg_value <= 47) {
  267. // background ANSI colour
  268. state.bg_ansi_color = arg_value - 40;
  269. }
  270. }
  271. }
  272. void _apply_mode_setting(
  273. bool mode_on,
  274. uint16_t* arg_list,
  275. uint16_t arg_count
  276. ) {
  277. // process modes
  278. for (uint16_t arg_index = 0; arg_index < arg_count; arg_index += 1) {
  279. const uint16_t mode_id = arg_list[arg_index];
  280. switch (mode_id) {
  281. case 4:
  282. // insert/replace mode
  283. // @todo this should be off for most practical purposes anyway?
  284. // ... otherwise visually shifting line text is expensive
  285. break;
  286. case 20:
  287. // auto-LF
  288. // ignoring per http://vt100.net/docs/vt220-rm/chapter4.html section 4.6.6
  289. break;
  290. case 34:
  291. // cursor visibility
  292. state.cursor_hidden = !mode_on;
  293. break;
  294. }
  295. }
  296. }
  297. void _exec_escape_question_command(
  298. char (*peek_char)(),
  299. char (*read_char)(),
  300. void (*send_char)(char ch)
  301. ) {
  302. // @todo support multiple mode commands
  303. // per http://vt100.net/docs/vt220-rm/chapter4.html section 4.6.1,
  304. // ANSI and DEC modes cannot mix; that is, '[?25;20;?7l' is not a valid Esc-command
  305. // (noting this because https://www.gnu.org/software/screen/manual/html_node/Control-Sequences.html
  306. // makes it look like the question mark is a prefix)
  307. const uint16_t mode = _read_decimal(peek_char, read_char);
  308. const bool mode_on = (read_char() != 'l');
  309. switch (mode) {
  310. case 1:
  311. // cursor key mode (normal/application)
  312. state.cursor_key_mode_application = mode_on;
  313. break;
  314. case 7:
  315. // auto wrap mode
  316. state.no_wrap = !mode_on;
  317. break;
  318. case 25:
  319. // cursor visibility
  320. state.cursor_hidden = !mode_on;
  321. break;
  322. }
  323. }
  324. // @todo cursor position report
  325. void _exec_escape_bracket_command_with_args(
  326. char (*peek_char)(),
  327. char (*read_char)(),
  328. void (*send_char)(char ch),
  329. tintty_display *display,
  330. uint16_t* arg_list,
  331. uint16_t arg_count
  332. ) {
  333. // convenient arg getter
  334. #define ARG(index, default_value) (arg_count > index ? arg_list[index] : default_value)
  335. // process next character after Escape-code, bracket and any numeric arguments
  336. const char command_character = read_char();
  337. switch (command_character) {
  338. case '?':
  339. // question-mark commands
  340. _exec_escape_question_command(peek_char, read_char, send_char);
  341. break;
  342. case 'A':
  343. // cursor up (no scroll)
  344. state.cursor_row = max((int)state.top_row, state.cursor_row - ARG(0, 1));
  345. break;
  346. case 'B':
  347. // cursor down (no scroll)
  348. state.cursor_row = min(state.top_row + display->screen_row_count - 1, state.cursor_row + ARG(0, 1));
  349. break;
  350. case 'C':
  351. // cursor right (no scroll)
  352. state.cursor_col = min(display->screen_col_count - 1, state.cursor_col + ARG(0, 1));
  353. break;
  354. case 'D':
  355. // cursor left (no scroll)
  356. state.cursor_col = max(0, state.cursor_col - ARG(0, 1));
  357. break;
  358. case 'H':
  359. case 'f':
  360. // Direct Cursor Addressing (row;col)
  361. state.cursor_col = max(0, min(display->screen_col_count - 1, ARG(1, 1) - 1));
  362. state.cursor_row = state.top_row + max(0, min(display->screen_row_count - 1, ARG(0, 1) - 1));
  363. break;
  364. case 'J':
  365. // clear screen
  366. state.out_char = ' ';
  367. state.out_char_col = state.cursor_col;
  368. state.out_char_row = state.cursor_row;
  369. {
  370. const int16_t rel_row = state.cursor_row - state.top_row;
  371. state.out_clear_before = ARG(0, 0) != 0
  372. ? rel_row * display->screen_col_count + state.cursor_col
  373. : 0;
  374. state.out_clear_after = ARG(0, 0) != 1
  375. ? (display->screen_row_count - 1 - rel_row) * display->screen_col_count + (display->screen_col_count - 1 - state.cursor_col)
  376. : 0;
  377. }
  378. break;
  379. case 'K':
  380. // clear line
  381. state.out_char = ' ';
  382. state.out_char_col = state.cursor_col;
  383. state.out_char_row = state.cursor_row;
  384. state.out_clear_before = ARG(0, 0) != 0
  385. ? state.cursor_col
  386. : 0;
  387. state.out_clear_after = ARG(0, 0) != 1
  388. ? display->screen_col_count - 1 - state.cursor_col
  389. : 0;
  390. break;
  391. case 'm':
  392. // graphic rendition mode
  393. _apply_graphic_rendition(arg_list, arg_count);
  394. break;
  395. case 'h':
  396. // set mode
  397. _apply_mode_setting(true, arg_list, arg_count);
  398. break;
  399. case 'l':
  400. // unset mode
  401. _apply_mode_setting(false, arg_list, arg_count);
  402. break;
  403. }
  404. }
  405. void _exec_escape_bracket_command(
  406. char (*peek_char)(),
  407. char (*read_char)(),
  408. void (*send_char)(char ch),
  409. tintty_display *display
  410. ) {
  411. const uint16_t MAX_COMMAND_ARG_COUNT = 10;
  412. uint16_t arg_list[MAX_COMMAND_ARG_COUNT];
  413. uint16_t arg_count = 0;
  414. // start parsing arguments if any
  415. // (this means that '' is treated as no arguments, but '0;' is treated as two arguments, each being zero)
  416. // @todo ignore trailing semi-colon instead of treating it as marking an extra zero arg?
  417. if (isdigit(peek_char())) {
  418. // keep consuming arguments while we have space
  419. while (arg_count < MAX_COMMAND_ARG_COUNT) {
  420. // consume decimal number
  421. arg_list[arg_count] = _read_decimal(peek_char, read_char);
  422. arg_count += 1;
  423. // stop processing if next char is not separator
  424. if (peek_char() != ';') {
  425. break;
  426. }
  427. // consume separator before starting next argument
  428. read_char();
  429. }
  430. }
  431. _exec_escape_bracket_command_with_args(
  432. peek_char,
  433. read_char,
  434. send_char,
  435. display,
  436. arg_list,
  437. arg_count
  438. );
  439. }
  440. // set the characters displayed for given G0-G3 bank
  441. void _exec_character_set(
  442. uint8_t g4bank_index,
  443. char (*read_char)()
  444. ) {
  445. switch (read_char()) {
  446. case 'A':
  447. case 'B':
  448. // normal character set (UK/US)
  449. state.g4bank_char_set[g4bank_index] = 0;
  450. break;
  451. case '0':
  452. // line-drawing
  453. state.g4bank_char_set[g4bank_index] = 1;
  454. break;
  455. default:
  456. // alternate sets are unsupported
  457. state.g4bank_char_set[g4bank_index] = 0;
  458. break;
  459. }
  460. }
  461. // @todo terminal reset
  462. // @todo parse modes with arguments even if they are no-op
  463. void _exec_escape_code(
  464. char (*peek_char)(),
  465. char (*read_char)(),
  466. void (*send_char)(char ch),
  467. tintty_display *display
  468. ) {
  469. // read next character after Escape-code
  470. // @todo time out?
  471. char esc_character = read_char();
  472. // @todo support for (, ), #, c, cursor save/restore
  473. switch (esc_character) {
  474. case '[':
  475. _exec_escape_bracket_command(peek_char, read_char, send_char, display);
  476. break;
  477. case 'D':
  478. // index (move down and possibly scroll)
  479. state.cursor_row += 1;
  480. _ensure_cursor_vscroll(display);
  481. break;
  482. case 'M':
  483. // reverse index (move up and possibly scroll)
  484. state.cursor_row -= 1;
  485. _ensure_cursor_vscroll(display);
  486. break;
  487. case 'E':
  488. // next line
  489. state.cursor_row += 1;
  490. state.cursor_col = 0;
  491. _ensure_cursor_vscroll(display);
  492. break;
  493. case 'Z':
  494. // Identify Terminal (DEC Private)
  495. _send_sequence(send_char, "\e[?1;0c"); // DA response: no options
  496. break;
  497. case '7':
  498. // save cursor
  499. // @todo verify that the screen-relative coordinate approach is valid
  500. state.dec_saved_col = state.cursor_col;
  501. state.dec_saved_row = state.cursor_row - state.top_row; // relative to top
  502. state.dec_saved_bg = state.bg_ansi_color;
  503. state.dec_saved_fg = state.fg_ansi_color;
  504. state.dec_saved_g4bank = state.out_char_g4bank;
  505. state.dec_saved_bold = state.bold;
  506. state.dec_saved_no_wrap = state.no_wrap;
  507. break;
  508. case '8':
  509. // restore cursor
  510. state.cursor_col = state.dec_saved_col;
  511. state.cursor_row = state.dec_saved_row + state.top_row; // relative to top
  512. state.bg_ansi_color = state.dec_saved_bg;
  513. state.fg_ansi_color = state.dec_saved_fg;
  514. state.out_char_g4bank = state.dec_saved_g4bank;
  515. state.bold = state.dec_saved_bold;
  516. state.no_wrap = state.dec_saved_no_wrap;
  517. break;
  518. case '=':
  519. case '>':
  520. // keypad mode setting - ignoring
  521. break;
  522. case '(':
  523. // set G0
  524. _exec_character_set(0, read_char);
  525. break;
  526. case ')':
  527. // set G1
  528. _exec_character_set(1, read_char);
  529. break;
  530. case '*':
  531. // set G2
  532. _exec_character_set(2, read_char);
  533. break;
  534. case '+':
  535. // set G3
  536. _exec_character_set(3, read_char);
  537. break;
  538. default:
  539. // unrecognized character, silently ignore
  540. break;
  541. }
  542. }
  543. void _main(
  544. char (*peek_char)(),
  545. char (*read_char)(),
  546. void (*send_char)(char str),
  547. tintty_display *display,
  548. tintty_keyboard *keyboard
  549. ) {
  550. // start in default idle state
  551. char initial_character = read_char();
  552. if (initial_character >= 0x20 && initial_character <= 0x7e) {
  553. // output displayable character
  554. state.out_char = initial_character;
  555. state.out_char_col = state.cursor_col;
  556. state.out_char_row = state.cursor_row;
  557. // update caret
  558. state.cursor_col += 1;
  559. if (state.cursor_col >= display->screen_col_count) {
  560. if (state.no_wrap) {
  561. state.cursor_col = display->screen_col_count - 1;
  562. } else {
  563. state.cursor_col = 0;
  564. state.cursor_row += 1;
  565. _ensure_cursor_vscroll(display);
  566. }
  567. }
  568. // reset idle state
  569. state.idle_cycle_count = 0;
  570. } else {
  571. // @todo bell, answer-back (0x05), delete
  572. switch (initial_character) {
  573. case '\a':
  574. // bell
  575. keyboard->bell();
  576. break;
  577. case '\n':
  578. // line-feed
  579. state.cursor_row += 1;
  580. _ensure_cursor_vscroll(display);
  581. break;
  582. case '\r':
  583. // carriage-return
  584. state.cursor_col = 0;
  585. break;
  586. case '\b':
  587. // backspace
  588. state.cursor_col -= 1;
  589. if (state.cursor_col < 0) {
  590. if (state.no_wrap) {
  591. state.cursor_col = 0;
  592. } else {
  593. state.cursor_col = display->screen_col_count - 1;
  594. state.cursor_row -= 1;
  595. _ensure_cursor_vscroll(display);
  596. }
  597. }
  598. break;
  599. case '\t':
  600. // tab
  601. {
  602. // @todo blank out the existing characters? not sure if that is expected
  603. const int16_t tab_num = state.cursor_col / TAB_SIZE;
  604. state.cursor_col = min(display->screen_col_count - 1, (tab_num + 1) * TAB_SIZE);
  605. }
  606. break;
  607. case '\e':
  608. // Escape-command
  609. _exec_escape_code(peek_char, read_char, send_char, display);
  610. break;
  611. case '\x0f':
  612. // Shift-In (use G0)
  613. // see also the fun reason why these are called this way:
  614. // https://en.wikipedia.org/wiki/Shift_Out_and_Shift_In_characters
  615. state.out_char_g4bank = 0;
  616. break;
  617. case '\x0e':
  618. // Shift-Out (use G1)
  619. state.out_char_g4bank = 1;
  620. break;
  621. default:
  622. // nothing, just animate cursor
  623. delay(1);
  624. state.idle_cycle_count = (state.idle_cycle_count + 1) % IDLE_CYCLE_MAX;
  625. }
  626. }
  627. _render(display);
  628. }
  629. void tintty_run(
  630. char (*peek_char)(),
  631. char (*read_char)(),
  632. void (*send_char)(char str),
  633. tintty_display *display,
  634. tintty_keyboard *keyboard
  635. ) {
  636. // set up initial state
  637. state.cursor_col = 0;
  638. state.cursor_row = 0;
  639. state.top_row = 0;
  640. state.no_wrap = 0;
  641. state.cursor_hidden = 0;
  642. state.bg_ansi_color = 0;
  643. state.fg_ansi_color = 7;
  644. state.bold = false;
  645. state.cursor_key_mode_application = false;
  646. state.dec_saved_col = 0;
  647. state.dec_saved_row = 0;
  648. state.dec_saved_bg = state.bg_ansi_color;
  649. state.dec_saved_fg = state.fg_ansi_color;
  650. state.dec_saved_g4bank = 0;
  651. state.dec_saved_bold = state.bold;
  652. state.dec_saved_no_wrap = false;
  653. state.out_char = 0;
  654. state.out_char_g4bank = 0;
  655. state.g4bank_char_set[0] = 0;
  656. state.g4bank_char_set[1] = 0;
  657. state.g4bank_char_set[2] = 0;
  658. state.g4bank_char_set[3] = 0;
  659. rendered.cursor_col = -1;
  660. rendered.cursor_row = -1;
  661. // clear screen
  662. display->fill_rect(0, 0, display->screen_width, display->screen_height, TFT_BLACK);
  663. // reset TFT scroll to default
  664. display->set_vscroll(0);
  665. // initial render
  666. _render(display);
  667. // send CR to indicate that the screen is ready
  668. // (this works with the agetty --wait-cr option to help wait until Arduino boots)
  669. send_char('\r');
  670. // main read cycle
  671. while (1) {
  672. _main(peek_char, read_char, send_char, display, keyboard);
  673. }
  674. }
  675. void tintty_idle(
  676. tintty_display *display
  677. ) {
  678. delay(1);
  679. // animate cursor
  680. state.idle_cycle_count = (state.idle_cycle_count + 1) % IDLE_CYCLE_MAX;
  681. // re-render
  682. _render(display);
  683. }