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