1/* @title: Console terminal state */
2#include <asm.h>
3#include <colors.h>
4#include <console/printf.h>
5#include <console/term.h>
6#include <math/bit.h>
7#include <stdbool.h>
8#include <stdint.h>
9#include <string.h>
10#include <time/time.h>
11
12#define TERM_PROBE_SPIN_CAP BIT(26)
13#define TERM_ESC(s) serial_write((s), sizeof(s) - 1)
14
15/* Properties of the console */
16static struct {
17 bool available;
18 bool plain;
19 bool unicode;
20 bool panic;
21 uint16_t rows;
22 uint16_t cols;
23} term = {.unicode = true};
24
25bool term_available(void) {
26 return term.available;
27}
28
29struct term_geometry term_size(void) {
30 if (!term.available || !term.rows || !term.cols)
31 return (struct term_geometry){.rows = TERM_FALLBACK_ROWS,
32 .cols = TERM_FALLBACK_COLS};
33
34 return (struct term_geometry){.rows = term.rows, .cols = term.cols};
35}
36
37void term_geometry_set(uint16_t rows, uint16_t cols) {
38 if (rows < 2 || cols < 8)
39 return;
40
41 term.rows = rows;
42 term.cols = cols;
43 term.available = true;
44}
45
46void term_set_plain(bool plain) {
47 term.plain = plain;
48}
49
50bool term_plain(void) {
51 return term.plain;
52}
53
54void term_set_unicode(bool unicode) {
55 term.unicode = unicode;
56}
57
58bool term_unicode(void) {
59 return term.unicode;
60}
61
62bool term_in_panic(void) {
63 return term.panic;
64}
65
66const char *term_style(enum term_sev sev) {
67 if (term.plain)
68 return "";
69
70 switch (sev) {
71 case TERM_SEV_DIM: return ANSI_GRAY;
72 case TERM_SEV_LABEL: return ANSI_BRIGHT_BLUE;
73 case TERM_SEV_OK: return ANSI_GREEN;
74 case TERM_SEV_WARN: return ANSI_YELLOW;
75 case TERM_SEV_CRIT: return ANSI_BOLD ANSI_RED;
76 case TERM_SEV_HEAD: return ANSI_BOLD;
77 case TERM_SEV_NORMAL:
78 default: return "";
79 }
80}
81
82const char *term_style_reset(void) {
83 return term.plain ? "" : ANSI_RESET;
84}
85
86static void term_drain_rx(void) {
87 char c;
88
89 while (serial_try_getc(out: &c))
90 ;
91}
92
93bool term_probe(void) {
94 enum { WANT_ESC, WANT_CSI, WANT_ROWS, WANT_COLS } state = WANT_ESC;
95 uint32_t rows = 0, cols = 0;
96 uint64_t spins = 0;
97 bool done = false;
98 char c;
99
100 if (term.panic)
101 return term.available;
102
103 term.available = false;
104 term_drain_rx();
105
106 /* Put the cursor past any reasonable bottom right corner, then ask where
107 * it ended up, mildly hacky */
108 TERM_ESC("\0337\033[999;999H\033[6n");
109
110 time_ms_t deadline = time_get_ms() + TERM_PROBE_TIMEOUT_MS;
111
112 while (!done && time_get_ms() < deadline) {
113 if (++spins > TERM_PROBE_SPIN_CAP)
114 break;
115
116 if (!serial_try_getc(out: &c)) {
117 cpu_pause();
118 continue;
119 }
120
121 switch (state) {
122 case WANT_ESC:
123 /* Whatever typed while waited is noise */
124 if (c == '\033')
125 state = WANT_CSI;
126 break;
127 case WANT_CSI:
128 rows = cols = 0;
129 state = (c == '[') ? WANT_ROWS : WANT_ESC;
130 break;
131 case WANT_ROWS:
132 if (c >= '0' && c <= '9')
133 rows = rows * 10 + (uint32_t) (c - '0');
134 else if (c == ';')
135 state = WANT_COLS;
136 else
137 state = WANT_ESC;
138 break;
139 case WANT_COLS:
140 if (c >= '0' && c <= '9')
141 cols = cols * 10 + (uint32_t) (c - '0');
142 else if (c == 'R')
143 done = true;
144 else
145 state = WANT_ESC;
146 break;
147 }
148 }
149
150 TERM_ESC("\0338");
151
152 /* We need at least two rows to scroll and hold the bar */
153 if (!done || rows < 2 || rows > UINT16_MAX || cols < 8 || cols > UINT16_MAX)
154 return false;
155
156 term.rows = (uint16_t) rows;
157 term.cols = (uint16_t) cols;
158 term.available = true;
159 return true;
160}
161
162void term_enter_panic(void) {
163 term.panic = true;
164
165 if (term.available)
166 TERM_ESC("\033[r\033[?25h");
167}
168