1/* @title: Pinned status bar */
2#include <asm.h>
3#include <colors.h>
4#include <console/printf.h>
5#include <console/report.h>
6#include <console/statusbar.h>
7#include <console/term.h>
8#include <math/min_max.h>
9#include <sch/irql.h>
10#include <stdarg.h>
11#include <stdbool.h>
12#include <stddef.h>
13#include <stdint.h>
14#include <string.h>
15#include <sync/raw_spinlock.h>
16#include <time/time.h>
17#include <time/timer.h>
18
19#define BAR_MIN_COLS ((size_t) 10)
20#define BAR_MAX_COLS ((size_t) 40)
21
22#define BAR_ESC(s) serial_write((s), sizeof(s) - 1)
23
24struct bar_progress {
25 size_t done;
26 size_t total;
27 time_ms_t test_started_ms;
28 time_ms_t total_started_ms;
29 bool timed;
30 bool active;
31 char detail[REPORT_LINE_MAX];
32 char metadata_storage[REPORT_LINE_MAX];
33 char progress_storage[REPORT_LINE_MAX];
34};
35
36static struct raw_spinlock bar_lock = RAW_SPINLOCK_INIT;
37static bool bar_open;
38static struct bar_progress bar_progress;
39
40static void bar_timer_fn(struct timer *timer);
41TIMER_DEFINE(bar_timer, bar_timer_fn);
42
43static void bar_timer_arm(void) {
44 timer_modify(timer: &bar_timer, new: timer_delta_us(MS_TO_US(1000)));
45}
46
47void status_bar_open(void) {
48 if (!term_available() || term_in_panic())
49 return;
50
51 if (term_size().rows < 3)
52 return;
53
54 bool irqs_were_enabled = raw_spin_lock_high(lock: &bar_lock);
55 if (bar_open) {
56 raw_spin_unlock_irq_restore(lock: &bar_lock, irqs_were_enabled);
57 return;
58 }
59 bar_open = true;
60 raw_spin_unlock_irq_restore(lock: &bar_lock, irqs_were_enabled);
61
62 enum irql irql = printf_lock();
63
64 BAR_ESC("\r\n\r\n\r\n");
65
66 char buf[64];
67 int n = snprintf(buf, (int) sizeof(buf), "\033[1;%ur\033[%u;1H",
68 (uint32_t) (term_size().rows - 2),
69 (uint32_t) (term_size().rows - 2));
70 if (n > 0)
71 serial_write(str: buf, len: (size_t) n);
72
73 printf_unlock(i: irql);
74
75 timer_init(timer: &bar_timer, func: bar_timer_fn, NULL);
76 bar_timer_arm();
77}
78
79void status_bar_close(void) {
80 if (!term_available())
81 return;
82
83 bool irqs_were_enabled = raw_spin_lock_high(lock: &bar_lock);
84 if (!bar_open) {
85 raw_spin_unlock_irq_restore(lock: &bar_lock, irqs_were_enabled);
86 return;
87 }
88 bar_open = false;
89 bar_progress.active = false;
90 raw_spin_unlock_irq_restore(lock: &bar_lock, irqs_were_enabled);
91
92 timer_shutdown_sync(timer: &bar_timer);
93
94 enum irql irql = printf_lock();
95
96 char buf[64];
97 int n =
98 snprintf(buf, (int) sizeof(buf),
99 "\033[%u;1H\033[2K\033[%u;1H\033[2K\033[r\033[%u;1H",
100 (uint32_t) (term_size().rows - 1), (uint32_t) term_size().rows,
101 (uint32_t) /* avoid extra newline */ term_size().rows - 2);
102 if (n > 0)
103 serial_write(str: buf, len: (size_t) n);
104
105 printf_unlock(i: irql);
106}
107
108static void bar_begin(void) {
109 char buf[64];
110 int n = snprintf(buf, (int) sizeof(buf), "\0337\033[?25l\033[%u;1H\033[2K",
111 (uint32_t) (term_size().rows - 1));
112 if (n > 0)
113 serial_write(str: buf, len: (size_t) n);
114}
115
116static void bar_end(const struct report_line *metadata,
117 const struct report_line *progress) {
118 serial_write(str: report_line_str(l: metadata), len: metadata->len);
119 char buf[32];
120 int n = snprintf(buf, (int) sizeof(buf), "\033[%u;1H\033[2K",
121 (uint32_t) term_size().rows);
122 if (n > 0)
123 serial_write(str: buf, len: (size_t) n);
124 serial_write(str: report_line_str(l: progress), len: progress->len);
125 BAR_ESC(ANSI_RESET "\033[?25h\0338");
126}
127
128void status_bar_set(const char *fmt, ...) {
129 va_list ap;
130
131 if (!term_available() || term_in_panic())
132 return;
133
134 REPORT_LINE(l, term_size().cols);
135
136 va_start(ap, fmt);
137 report_line_vprintf(l: &l, fmt, ap);
138 va_end(ap);
139
140 enum irql irql = printf_lock();
141 bool irqs_were_enabled = raw_spin_lock_high(lock: &bar_lock);
142 bool open = bar_open;
143 raw_spin_unlock_irq_restore(lock: &bar_lock, irqs_were_enabled);
144 if (open) {
145 bar_begin();
146 REPORT_LINE(empty, term_size().cols);
147 bar_end(metadata: &empty, progress: &l);
148 }
149 printf_unlock(i: irql);
150}
151
152static void bar_format_duration(time_ms_t elapsed, char *buf, size_t cap) {
153 uint64_t seconds = MS_TO_SECONDS(elapsed);
154 uint64_t minutes = seconds / 60;
155 uint64_t hours = minutes / 60;
156 minutes %= 60;
157 seconds %= 60;
158
159 if (hours) {
160 snprintf(buf, (int) cap, "%lluh %llum %llus",
161 (unsigned long long) hours, (unsigned long long) minutes,
162 (unsigned long long) seconds);
163 } else if (minutes) {
164 snprintf(buf, (int) cap, "%llum %llus", (unsigned long long) minutes,
165 (unsigned long long) seconds);
166 } else {
167 snprintf(buf, (int) cap, "%llus", (unsigned long long) seconds);
168 }
169}
170
171static void bar_progress_render(struct bar_progress *progress) {
172 size_t done = MIN(progress->done, progress->total);
173 size_t total = progress->total;
174
175 size_t width = term_size().cols / 3;
176 width = CLAMP(width, BAR_MIN_COLS, BAR_MAX_COLS);
177
178 size_t filled = total ? (width * done) / total : width;
179 size_t pct = total ? (100 * done) / total : 100;
180
181 bool uni = term_unicode() && !term_plain();
182 const char *cap_l = uni ? "▐" : "[";
183 const char *cap_r = uni ? "▌" : "]";
184 const char *on = uni ? "█" : "#";
185 const char *off = uni ? "░" : "-";
186
187 struct report_line metadata;
188 struct report_line progress_line;
189 report_line_init(l: &metadata, buf: progress->metadata_storage,
190 cap: sizeof(progress->metadata_storage), max_col: term_size().cols);
191 report_line_init(l: &progress_line, buf: progress->progress_storage,
192 cap: sizeof(progress->progress_storage), max_col: term_size().cols);
193
194 report_line_puts(l: &progress_line, ANSI_BOLD ANSI_BLUE);
195 report_line_puts(l: &progress_line, s: cap_l);
196 report_line_puts(l: &progress_line, ANSI_GREEN);
197 report_line_repeat(l: &progress_line, glyph: on, n: filled);
198
199 report_line_puts(l: &progress_line, ANSI_GRAY);
200 report_line_repeat(l: &progress_line, glyph: off, n: width - filled);
201
202 report_line_puts(l: &progress_line, ANSI_BLUE);
203 report_line_puts(l: &progress_line, s: cap_r);
204 report_line_puts(l: &progress_line, ANSI_RESET);
205
206 char formatted[128];
207 snprintf(formatted, (int) sizeof(formatted),
208 " " ANSI_BOLD "%zu" ANSI_RESET "/%zu " ANSI_BOLD "%zu%%" ANSI_RESET
209 " ",
210 done, total, pct);
211 report_line_puts(l: &progress_line, s: formatted);
212
213 report_line_puts(l: &metadata, s: progress->detail);
214
215 if (progress->timed) {
216 time_ms_t now = time_get_ms();
217 time_ms_t total_elapsed = now - progress->total_started_ms;
218
219 report_line_puts(l: &metadata, s: " " ANSI_GRAY);
220 if (progress->test_started_ms) {
221 time_ms_t test_elapsed = now - progress->test_started_ms;
222 bar_format_duration(elapsed: test_elapsed, buf: formatted, cap: sizeof(formatted));
223 report_line_puts(l: &metadata, s: formatted);
224 report_line_puts(l: &metadata, s: " (total elapsed: ");
225 } else {
226 report_line_puts(l: &metadata, s: "total elapsed: ");
227 }
228 bar_format_duration(elapsed: total_elapsed, buf: formatted, cap: sizeof(formatted));
229 report_line_puts(l: &metadata, s: formatted);
230 if (progress->test_started_ms)
231 report_line_puts(l: &metadata, s: ")");
232 report_line_puts(l: &metadata, ANSI_RESET);
233 }
234
235 bar_begin();
236 bar_end(metadata: &metadata, progress: &progress_line);
237}
238
239static void status_bar_progress_v(size_t done, size_t total,
240 time_ms_t test_started_ms,
241 time_ms_t total_started_ms,
242 const char *detail_fmt, va_list ap) {
243 if (!term_available() || term_in_panic())
244 return;
245
246 enum irql irql = printf_lock();
247 bool irqs_were_enabled = raw_spin_lock_high(lock: &bar_lock);
248 if (!bar_open) {
249 raw_spin_unlock_irq_restore(lock: &bar_lock, irqs_were_enabled);
250 printf_unlock(i: irql);
251 return;
252 }
253 bar_progress.done = done;
254 bar_progress.total = total;
255 bar_progress.test_started_ms = test_started_ms;
256 bar_progress.total_started_ms = total_started_ms;
257 bar_progress.timed = total_started_ms != 0;
258 bar_progress.active = true;
259 vsnprintf(bar_progress.detail, (int) sizeof(bar_progress.detail),
260 detail_fmt, ap);
261 raw_spin_unlock_irq_restore(lock: &bar_lock, irqs_were_enabled);
262 bar_progress_render(progress: &bar_progress);
263 printf_unlock(i: irql);
264}
265
266void status_bar_progress(size_t done, size_t total, const char *detail_fmt,
267 ...) {
268 va_list ap;
269
270 va_start(ap, detail_fmt);
271 status_bar_progress_v(done, total, test_started_ms: 0, total_started_ms: 0, detail_fmt, ap);
272 va_end(ap);
273}
274
275void status_bar_progress_timed(size_t done, size_t total,
276 time_ms_t test_started_ms,
277 time_ms_t total_started_ms,
278 const char *detail_fmt, ...) {
279 va_list ap;
280
281 va_start(ap, detail_fmt);
282 status_bar_progress_v(done, total, test_started_ms, total_started_ms,
283 detail_fmt, ap);
284 va_end(ap);
285}
286
287static void bar_timer_fn(struct timer *timer) {
288 cc_ignore(timer);
289
290 if (!term_available() || term_in_panic())
291 return;
292
293 enum irql irql = printf_lock();
294 bool irqs_were_enabled = raw_spin_lock_high(lock: &bar_lock);
295 bool repaint = bar_open && bar_progress.active && bar_progress.timed;
296 bool open = bar_open;
297 raw_spin_unlock_irq_restore(lock: &bar_lock, irqs_were_enabled);
298 if (repaint) {
299 bar_progress_render(progress: &bar_progress);
300 }
301 if (open)
302 bar_timer_arm();
303 printf_unlock(i: irql);
304}
305
306void status_bar_reset(void) {
307 if (!term_available())
308 return;
309
310 bool irqs_were_enabled = raw_spin_lock_high(lock: &bar_lock);
311 bar_open = false;
312 bar_progress.active = false;
313 raw_spin_unlock_irq_restore(lock: &bar_lock, irqs_were_enabled);
314
315 char buf[64];
316 int n = snprintf(buf, (int) sizeof(buf), "\033[r\033[?25h\033[%u;1H\r\n",
317 (uint32_t) term_size().rows);
318 if (n > 0)
319 serial_write(str: buf, len: (size_t) n);
320}
321