ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
notice.c
Go to the documentation of this file.
6#include <libplatform/filesystem.h>
8#include <stdatomic.h>
9#include <ascii-chat/common.h>
11#include <ascii-chat/ui/popup.h>
13#include <ascii-chat/ui/text.h>
16#include <string.h>
17
18static _Thread_local ui_prompt_kind_t g_prompt_kind = UI_PROMPT_CONFIRMATION;
19static _Thread_local ui_notice_severity_t g_prompt_severity = UI_NOTICE_INFO;
20
21static const char *notice_color(ui_notice_severity_t severity) {
22 switch (severity) {
23 case UI_NOTICE_FATAL:
31 default:
33 }
34}
35
36static const char *notice_label(ui_notice_severity_t severity) {
37 switch (severity) {
38 case UI_NOTICE_FATAL:
39 return "FATAL";
41 return "ERROR";
43 return "WARNING";
45 return "SUCCESS";
46 default:
47 return "INFO";
48 }
49}
50
51static void border(frame_buffer_t *buffer, int width, ui_popup_edge_t edge, bool unicode, ui_notice_severity_t severity,
52 bool color) {
53 if (!buffer)
54 return;
55 log_color_t shade = severity == UI_NOTICE_FATAL ? LOG_COLOR_FATAL
56 : severity == UI_NOTICE_DANGER ? LOG_COLOR_ERROR
57 : severity == UI_NOTICE_WARNING ? LOG_COLOR_WARN
58 : severity == UI_NOTICE_SUCCESS ? LOG_COLOR_INFO
60 ui_popup_rule(buffer, width, edge, true, unicode, color ? shade : LOG_COLOR_RESET);
61 frame_buffer_printf(buffer, "\n%s%s", color ? "\033[1m" : "", color ? notice_color(severity) : "");
62}
63
64int ui_notice_render(frame_buffer_t *buffer, const char *text, ui_notice_severity_t severity, int cols, bool unicode,
65 bool color) {
66 if (!text)
67 text = "";
68 const char *body = text;
69 text = notice_label(severity);
70 bool rendering_label = true;
71 const char *text_end = text + strlen(text);
72 // Reserve the final column to avoid terminal autowrap at the right border.
73 int width = cols > 81 ? 80 : cols - 1;
74 bool boxed = width >= 8;
75 int content = boxed ? width - 4 : (cols > 2 ? cols - 1 : 2);
76 const char *side = unicode ? "║" : "|";
77 if (buffer && color)
78 frame_buffer_printf(buffer, "\033[1m%s", notice_color(severity));
79 if (boxed)
80 border(buffer, width, UI_POPUP_TOP, unicode, severity, color);
81 int rows = boxed ? 2 : 0;
82 const char *art_end = NULL;
83 const char *word_end = NULL;
84 bool title_word = false;
85 bool first_letter = false;
86 do {
87 // The Keymask formatter identifies complete plain-text blocks; input ANSI
88 // escapes still pass through the normal control-character sanitization.
89 const char *block_end = key_identity_art_end(text);
90 if (block_end)
91 art_end = block_end;
92 bool neutral = art_end && text < art_end;
93 int used = 0;
94 if (buffer && boxed)
95 frame_buffer_printf(buffer, "%s ", side);
96 if (buffer && color && neutral)
97 frame_buffer_append(buffer, "\033[0;39m", 7);
98 while (*text && *text != '\n') {
99 int bytes = utf8_next_char_bytes(text, (size_t)(text_end - text));
100 if (bytes <= 0)
101 bytes = 1;
102 bool control = (unsigned char)*text < 32 || (unsigned char)*text == 127 ||
103 (bytes == 2 && (unsigned char)text[0] == 0xc2 && (unsigned char)text[1] >= 0x80 &&
104 (unsigned char)text[1] <= 0x9f);
105 int cells = control ? 1 : utf8_display_width_n(text, (size_t)bytes);
106 if (cells < 0)
107 cells = 1;
108 if (used + cells > content && used)
109 break;
110 if (buffer) {
111 if (control)
112 frame_buffer_append(buffer, "?", 1);
113 else if (!rendering_label && !neutral) {
114 if (!word_end || text >= word_end) {
115 word_end = text;
116 title_word = (*text >= 'A' && *text <= 'Z') || (*text >= 'a' && *text <= 'z');
117 first_letter = true;
118 while (*word_end && *word_end != ' ' && *word_end != '\n' && *word_end != '\t') {
119 unsigned char c = (unsigned char)*word_end;
120 bool letter = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
121 bool punctuation = strchr(",.;:!?", c) && (!word_end[1] || word_end[1] == ' ' || word_end[1] == '\n');
122 if (!letter && !punctuation)
123 title_word = false;
124 ++word_end;
125 }
126 }
127 if (title_word && bytes == 1) {
128 char c = *text;
129 if (first_letter && c >= 'a' && c <= 'z')
130 c -= 'a' - 'A';
131 else if (!first_letter && c >= 'A' && c <= 'Z')
132 c += 'a' - 'A';
133 first_letter = false;
134 frame_buffer_append(buffer, &c, 1);
135 } else {
136 frame_buffer_append(buffer, text, (size_t)bytes);
137 }
138 } else
139 frame_buffer_append(buffer, text, (size_t)bytes);
140 }
141 used += cells;
142 text += bytes;
143 }
144 if (buffer) {
145 if (color && neutral)
146 frame_buffer_printf(buffer, "\033[1m%s", notice_color(severity));
147 if (boxed)
148 frame_buffer_printf(buffer, "%*s %s", content - used, "", side);
149 frame_buffer_append(buffer, "\n", 1);
150 }
151 ++rows;
152 if (*text == '\n')
153 ++text;
154 if (!*text) {
155 if (!rendering_label)
156 break;
157 if (boxed) {
158 border(buffer, width, UI_POPUP_SEPARATOR, unicode, severity, color);
159 ++rows;
160 }
161 rendering_label = false;
162 text = body;
163 text_end = text + strlen(text);
164 }
165 } while (true);
166 if (boxed)
167 border(buffer, width, UI_POPUP_BOTTOM, unicode, severity, color);
168 if (buffer && color)
169 frame_buffer_append(buffer, "\033[0m", 4);
170 return rows;
171}
172
193
194const char *ui_notice_title(const void *data) {
195 const notice_snapshot_t *notice = data;
196 return notice->severity == UI_NOTICE_WARNING ? "warning"
197 : notice->severity == UI_NOTICE_DANGER || notice->severity == UI_NOTICE_FATAL ? "error"
198 : "notice";
199}
200
201#define FIRST_USE_LINE_COUNT 64
202#define FIRST_USE_LINE_BYTES 512
203#define FIRST_USE_CONTENT_COLUMNS 66
204
205static size_t first_use_wrap(const char *text, int width, char lines[FIRST_USE_LINE_COUNT][FIRST_USE_LINE_BYTES]) {
206 size_t count = 0;
207 for (const char *line = text; *line && count < FIRST_USE_LINE_COUNT;) {
208 const char *end = strchr(line, '\n');
209 if (!end)
210 end = line + strlen(line);
211 const char *tab = memchr(line, '\t', (size_t)(end - line));
212 bool shortcut = tab && tab - line == 1;
213 char key[2] = {0}, styled[128] = {0};
214 if (shortcut) {
215 key[0] = *line;
216 ui_help_keys(styled, sizeof(styled), key);
217 line = tab + 1;
218 }
219 if (line == end) {
220 lines[count++][0] = '\0';
221 } else {
222 bool first = true;
223 while (line < end && count < FIRST_USE_LINE_COUNT) {
224 while (line < end && *line == ' ')
225 ++line;
226 if (line == end)
227 break;
228 int available = width - (shortcut ? 3 : 0);
229 size_t length = (size_t)(end - line);
230 if (length > (size_t)available) {
231 length = (size_t)available;
232 size_t split = length;
233 while (split > 0 && line[split] != ' ')
234 --split;
235 if (split)
236 length = split;
237 }
238 if (shortcut)
239 snprintf(lines[count], FIRST_USE_LINE_BYTES, "%s %.*s", first ? styled : " ", (int)length, line);
240 else
241 snprintf(lines[count], FIRST_USE_LINE_BYTES, "%.*s", (int)length, line);
242 ++count;
243 first = false;
244 line += length;
245 }
246 }
247 line = *end ? end + 1 : end;
248 }
249 return count;
250}
251
252static void render_first_use(terminal_size_t size, const notice_snapshot_t *notice) {
253 int width = size.cols - 6;
256 if (width < 4 || size.rows < 8)
257 return;
259 size_t count = first_use_wrap(notice->text, width, lines);
260 if (!count)
261 return;
262 int logo_rows = size.rows >= 15 && size.cols >= 52 ? 4 : size.rows >= 10 ? 1 : 0;
263 int page_rows = size.rows - 7 - logo_rows;
264 int pages = (int)((count + (size_t)page_rows - 1) / (size_t)page_rows);
265 notice_snapshot_t *state = (notice_snapshot_t *)notice;
266 if (state->rendered_cols != size.cols || state->rendered_rows != size.rows) {
267 atomic_store(&state->presented_page, -1);
268 state->rendered_cols = size.cols;
269 state->rendered_rows = size.rows;
270 }
271 atomic_store(&state->pages, pages);
272 int page = atomic_load(&state->page);
273 if (page >= pages) {
274 page = pages - 1;
275 atomic_store(&state->page, page);
276 }
277 const char *visible[FIRST_USE_LINE_COUNT];
278 size_t start = (size_t)page * (size_t)page_rows;
279 size_t visible_count = count - start;
280 if (visible_count > (size_t)page_rows)
281 visible_count = (size_t)page_rows;
282 for (size_t i = 0; i < visible_count; ++i)
283 visible[i] = lines[start + i];
284 char enter[128], escape[128], footer[512];
285 ui_help_keys(enter, sizeof(enter), "Enter");
286 ui_help_keys(escape, sizeof(escape), "Esc");
287 snprintf(footer, sizeof(footer), "%d/%d %s %s %s%s", page + 1, pages, enter, page + 1 == pages ? "done" : "next",
288 escape, width >= 25 ? " close" : "");
289 ui_popup_t popup = {.title = notice->text + notice->title_offset,
290 .lines = visible,
291 .line_count = visible_count,
292 .footer = footer,
293 .top_margin = logo_rows,
294 .minimum_width = width + 4};
295 frame_buffer_t *output = frame_buffer_create(size.rows, size.cols);
296 if (!output)
297 return;
298 frame_buffer_append(output, "\033[H\033[2J", 7);
299 if (ui_popup_render(output, size, &popup)) {
300 if (logo_rows) {
301 int popup_height = (int)visible_count + 5;
302 int popup_top = logo_rows + (size.rows - logo_rows - popup_height) / 2 + 1;
303 frame_buffer_printf(output, "\033[%d;1H", popup_top - logo_rows);
304 int tick = (int)((time_get_ns() / (NS_PER_SEC_INT / 60)) % 1800);
305 invitation_render_logo(output, size.cols - 1, tick, notice->color, logo_rows == 1);
306 }
307 state->rendered_page = page;
309 }
310 frame_buffer_destroy(output);
311}
312
313static void render_notice(terminal_size_t size, const void *data) {
314 const notice_snapshot_t *notice = data;
315 if (notice->marker_offset) {
316 render_first_use(size, notice);
317 return;
318 }
319 // Only the presentation thread owns these counters. Every queued notice gets
320 // its own full reading interval when it first becomes visible.
321 static uint64_t active_id;
322 static uint64_t started;
323 static uint64_t last_tick;
324 static terminal_size_t previous_size;
325 uint64_t now = time_get_ns();
326 if (active_id != notice->started || previous_size.cols != size.cols || previous_size.rows != size.rows) {
327 active_id = notice->started;
328 started = now;
329 } else if (last_tick && now - last_tick > NS_PER_SEC_INT) {
330 // Time spent behind a prompt must not consume the reading interval.
331 started += now - last_tick;
332 }
333 last_tick = now;
334 previous_size = size;
335 frame_buffer_t *buffer = frame_buffer_create(0, 0);
336 if (!buffer)
337 return;
338 int rows = ui_notice_render(buffer, notice->text, notice->severity, size.cols, notice->unicode, notice->color);
339 int page_rows = size.rows > 2 ? size.rows - 2 : 1;
340 int pages = (rows + page_rows - 1) / page_rows;
341 uint64_t page = (time_get_ns() - started) / (10 * NS_PER_SEC_INT);
342 if (page >= (uint64_t)pages) {
343 frame_buffer_destroy(buffer);
345 return;
346 }
347 const char *start = frame_buffer_get_content(buffer);
348 const char *end = start + frame_buffer_get_length(buffer);
349 for (uint64_t i = 0; i < page * (uint64_t)page_rows && start < end; ++i) {
350 const char *newline = memchr(start, '\n', (size_t)(end - start));
351 start = newline ? newline + 1 : end;
352 }
353 const char *stop = start;
354 for (int i = 0; i < page_rows && stop < end; ++i) {
355 const char *newline = memchr(stop, '\n', (size_t)(end - stop));
356 stop = newline ? newline + 1 : end;
357 }
358 ui_controller_write(STDERR_FILENO, "\033[H\033[2J", 7);
359 if (notice->color) {
360 ui_controller_write(STDERR_FILENO, "\033[1m", 4);
361 ui_controller_write(STDERR_FILENO, notice_color(notice->severity), strlen(notice_color(notice->severity)));
362 }
363 ui_controller_write(STDERR_FILENO, start, (size_t)(stop - start));
364 if (notice->color)
365 ui_controller_write(STDERR_FILENO, "\033[0m", 4);
366 if (pages > 1)
367 ui_controller_printf(STDERR_FILENO, "Page %llu/%d (advances automatically)", (unsigned long long)page + 1, pages);
368 frame_buffer_destroy(buffer);
369}
370
372 if (!text)
373 return SET_ERRNO(ERROR_INVALID_PARAM, "Missing notice text");
374 bool tty = platform_isatty(STDERR_FILENO);
375 bool color = !GET_OPTION(strip_ansi) && terminal_should_color_output(STDERR_FILENO, options_terminal_settings());
376 bool unicode = tty && terminal_should_use_utf8(GET_OPTION(force_utf8));
377 if (severity != UI_NOTICE_FATAL && tty && ui_controller_is_presenting() && !ui_controller_is_owner() &&
378 !GET_OPTION(snapshot_mode)) {
379 size_t text_bytes = strlen(text) + 1;
380 size_t bytes = sizeof(notice_snapshot_t) + text_bytes;
381 notice_snapshot_t *snapshot = SAFE_MALLOC(bytes, notice_snapshot_t *);
382 if (!snapshot)
383 return SET_ERRNO(ERROR_MEMORY, "Cannot allocate notice");
384 snapshot->severity = severity;
385 snapshot->unicode = unicode;
386 snapshot->color = color;
387 snapshot->started = time_get_ns();
388 snapshot->marker_offset = snapshot->title_offset = 0;
389 atomic_init(&snapshot->page, 0);
390 atomic_init(&snapshot->pages, 0);
391 atomic_init(&snapshot->presented_page, -1);
392 snapshot->rendered_page = -1;
393 snapshot->rendered_cols = snapshot->rendered_rows = 0;
394 memcpy(snapshot->text, text, text_bytes);
396 UI_SCREEN_NOTICE, STDERR_FILENO, (terminal_size_t){.cols = 1, .rows = 1}, render_notice, snapshot, bytes);
397 SAFE_FREE(snapshot);
398 return result;
399 }
400 frame_buffer_t *buffer = frame_buffer_create(0, 0);
401 if (!buffer)
402 return SET_ERRNO(ERROR_MEMORY, "Cannot allocate notice buffer");
403 if (severity == UI_NOTICE_FATAL && tty)
404 frame_buffer_append(buffer, "\033[0m\033[?25h\r\n", 12);
405 int cols = tty ? ui_controller_size().cols : 80;
406 ui_notice_render(buffer, text, severity, cols, unicode, color);
408 if (severity == UI_NOTICE_FATAL && !ui_controller_is_owner())
410 else
411 result = ui_controller_write(STDERR_FILENO, frame_buffer_get_content(buffer), frame_buffer_get_length(buffer));
412 frame_buffer_destroy(buffer);
413 return result;
414}
415
416asciichat_error_t ui_notice_present_first_use(const char *title, const char *text, const char *marker_path) {
417 if (!title || !text || !marker_path || !platform_isatty(STDERR_FILENO) || GET_OPTION(snapshot_mode))
418 return SET_ERRNO(ERROR_INVALID_PARAM, "First-use notice requires an interactive terminal and marker path");
419 size_t text_bytes = strlen(text) + 1, title_bytes = strlen(title) + 1, marker_bytes = strlen(marker_path) + 1;
420 if (title_bytes > SIZE_MAX - marker_bytes - sizeof(notice_snapshot_t) ||
421 text_bytes > SIZE_MAX - title_bytes - marker_bytes - sizeof(notice_snapshot_t))
422 return SET_ERRNO(ERROR_BUFFER_OVERFLOW, "First-use notice is too large");
423 size_t bytes = sizeof(notice_snapshot_t) + text_bytes + title_bytes + marker_bytes;
424 notice_snapshot_t *snapshot = SAFE_MALLOC(bytes, notice_snapshot_t *);
425 if (!snapshot)
426 return SET_ERRNO(ERROR_MEMORY, "Cannot allocate first-use notice");
427 snapshot->severity = UI_NOTICE_INFO;
428 snapshot->unicode = terminal_should_use_utf8(GET_OPTION(force_utf8));
429 snapshot->color = !GET_OPTION(strip_ansi) && terminal_should_color_output(STDERR_FILENO, options_terminal_settings());
430 snapshot->started = time_get_ns();
431 snapshot->title_offset = text_bytes;
432 snapshot->marker_offset = text_bytes + title_bytes;
433 atomic_init(&snapshot->page, 0);
434 atomic_init(&snapshot->pages, 0);
435 atomic_init(&snapshot->presented_page, -1);
436 snapshot->rendered_page = -1;
437 snapshot->rendered_cols = snapshot->rendered_rows = 0;
438 memcpy(snapshot->text, text, text_bytes);
439 memcpy(snapshot->text + text_bytes, title, title_bytes);
440 memcpy(snapshot->text + snapshot->marker_offset, marker_path, marker_bytes);
442 UI_SCREEN_NOTICE, STDERR_FILENO, (terminal_size_t){.cols = 20, .rows = 8}, render_notice, snapshot, bytes);
443 SAFE_FREE(snapshot);
444 return result;
445}
446
447void ui_notice_frame_presented(const void *data) {
448 notice_snapshot_t *notice = (notice_snapshot_t *)data;
449 if (notice && notice->marker_offset)
450 atomic_store(&notice->presented_page, notice->rendered_page);
451}
452
454 notice_snapshot_t *notice = (notice_snapshot_t *)data;
455 if (!notice || !notice->marker_offset)
457 if (key == KEY_ESCAPE || key == 'q' || key == 3)
459 if (key == KEY_QUESTION)
460 return UI_NOTICE_KEY_HELP;
461 int page = atomic_load(&notice->page);
462 int pages = atomic_load(&notice->pages);
463 if (key == KEY_LEFT && page > 0) {
464 atomic_store(&notice->presented_page, -1);
465 atomic_store(&notice->page, page - 1);
466 } else if ((key == '\r' || key == '\n' || key == KEY_RIGHT) && pages > 0 &&
467 atomic_load(&notice->presented_page) == page) {
468 if (page + 1 < pages) {
469 atomic_store(&notice->presented_page, -1);
470 atomic_store(&notice->page, page + 1);
471 } else if (key == '\r' || key == '\n') {
473 }
474 }
476}
477
478void ui_notice_first_use_complete(const void *data) {
479 const notice_snapshot_t *notice = data;
480 if (!notice || !notice->marker_offset)
481 return;
483 FILE *marker = platform_fopen("first-use instructions", notice->text + notice->marker_offset, "w");
484 if (!marker) {
485 log_warn("Could not save first-use instructions state; the notice may appear again: %s",
486 notice->text + notice->marker_offset);
487 } else if (fclose(marker) != 0) {
488 log_warn("Could not finish saving first-use instructions state; the notice may appear again: %s",
489 notice->text + notice->marker_offset);
490 }
492}
493
494bool ui_notice_confirm(ui_notice_severity_t severity, const char *text, unsigned timeout_seconds) {
495 return ui_notice_confirm_kind(UI_PROMPT_CONFIRMATION, severity, text, timeout_seconds);
496}
497
498bool ui_notice_confirm_kind(ui_prompt_kind_t kind, ui_notice_severity_t severity, const char *text,
499 unsigned timeout_seconds) {
500 ui_prompt_kind_t previous_kind = g_prompt_kind;
501 g_prompt_kind = kind;
502 ui_notice_severity_t previous = g_prompt_severity;
503 g_prompt_severity = severity;
504 if (terminal_can_prompt_user(!GET_OPTION(snapshot_mode)))
505 log_msg(severity == UI_NOTICE_FATAL ? LOG_FATAL
506 : severity == UI_NOTICE_DANGER ? LOG_ERROR
507 : severity == UI_NOTICE_WARNING ? LOG_WARN
508 : LOG_INFO,
509 __FILE__, __LINE__, __func__, "%s", text);
510 else
511 ui_notice_log(severity, __FILE__, __LINE__, __func__, "CONFIRMATION REQUIRED", "%s", text);
512 bool accepted = platform_prompt_yes_no_timeout(text, false, timeout_seconds);
513 g_prompt_kind = previous_kind;
514 g_prompt_severity = previous;
515 return accepted;
516}
517
518ui_prompt_kind_t ui_notice_prompt_kind(void) {
519 return g_prompt_kind;
520}
521
523 return g_prompt_severity;
524}
525
526void ui_notice_flush_snapshot(int fd, const void *data) {
527 const notice_snapshot_t *notice = data;
528 frame_buffer_t *buffer = frame_buffer_create(0, 0);
529 if (!buffer)
530 return;
531 if (platform_isatty(fd))
532 frame_buffer_append(buffer, "\033[0m\n", 5);
533 terminal_size_t size = {.cols = 80};
534 if (platform_isatty(fd))
535 (void)terminal_get_size_fd(fd, &size);
536 ui_notice_render(buffer, notice->text, notice->severity, size.cols, notice->unicode, notice->color);
538 frame_buffer_destroy(buffer);
539}
540
void errno_scope_end(asciichat_errno_scope_t scope, asciichat_errno_outcome_t outcome)
asciichat_errno_scope_t errno_scope_begin(void)
#define SET_ERRNO(code,...)
@ ASCIICHAT_ERRNO_DISMISSED
⚙️ Common definitions, error codes, macros, and types shared throughout the application
char cells[(512 *256 *2)]
Definition fft.c:21
unsigned int width
Definition fft.c:23
void frame_buffer_append(frame_buffer_t *buf, const char *str, size_t len)
Append raw bytes to the buffer.
void frame_buffer_destroy(frame_buffer_t *buf)
Destroy a frame buffer.
size_t frame_buffer_get_length(const frame_buffer_t *buf)
Get the current content length of the frame buffer.
const char * frame_buffer_get_content(const frame_buffer_t *buf)
Get a pointer to the buffer content.
frame_buffer_t * frame_buffer_create(int rows, int cols)
Create a new frame buffer.
void frame_buffer_printf(frame_buffer_t *buf, const char *fmt,...)
Printf-style append to the buffer.
struct frame_buffer frame_buffer_t
Opaque frame buffer handle.
#define SAFE_FREE(ptr)
Definition common.h:377
#define SAFE_MALLOC(size, cast)
Definition common.h:265
unsigned long long uint64_t
Definition common.h:63
asciichat_error_t
Error and exit codes - unified status values (0-255).
Definition error_codes.h:44
@ ERROR_MEMORY
Definition error_codes.h:51
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_INVALID_PARAM
@ ERROR_BUFFER_OVERFLOW
#define log_warn(...)
Log a WARN message.
Definition log/log.h:621
void log_msg(log_level_t level, const char *file, int line, const char *func, const char *fmt,...)
Log a message at a specific level.
Definition log.c:1196
log_color_t
Color enum for logging - indexes into color arrays.
Definition log/log.h:147
@ LOG_COLOR_RESET
Definition log/log.h:155
@ LOG_COLOR_FATAL
Definition log/log.h:153
@ LOG_COLOR_ERROR
Definition log/log.h:152
@ LOG_COLOR_INFO
Definition log/log.h:150
@ LOG_COLOR_WARN
Definition log/log.h:151
#define NS_PER_SEC_INT
Definition time.h:169
#define GET_OPTION(field)
Safely get a specific option field (lock-free read).
Definition options.h:1336
#define options_terminal_settings()
Definition options.h:1296
size_t platform_write_all(int fd, const void *buf, size_t count)
Write all bytes to a file descriptor, handling partial writes.
@ KEY_ESCAPE
Escape key (ESC).
Definition keyboard.h:55
@ KEY_LEFT
Left arrow key.
Definition keyboard.h:59
@ KEY_RIGHT
Right arrow key.
Definition keyboard.h:60
@ KEY_QUESTION
'?' key - show help screen
Definition keyboard.h:83
int utf8_display_width_n(const char *str, size_t max_bytes)
Calculate terminal display width of a UTF-8 string up to byte limit.
Definition utf8.c:102
int utf8_next_char_bytes(const char *str, size_t max_bytes)
Get byte length of next UTF-8 character.
Definition utf8.c:177
void ui_help_keys(char *out, size_t cap, const char *keys)
Definition help_menu.c:25
void invitation_render_logo(frame_buffer_t *buf, int cols, int frame, bool colors, bool compact)
Definition invitation.c:34
const char * key_identity_art_end(const char *text)
void ui_notice_log(ui_notice_severity_t severity, const char *file, int line, const char *func, const char *title, const char *format,...)
Definition log.c:1181
std::string escape(const std::string &str)
Escape a string for JSON output.
ui_prompt_kind_t ui_notice_prompt_kind(void)
Definition notice.c:518
#define FIRST_USE_CONTENT_COLUMNS
Definition notice.c:203
void ui_notice_dismiss(void)
Definition notice.c:541
asciichat_error_t ui_notice_present_first_use(const char *title, const char *text, const char *marker_path)
Definition notice.c:416
bool ui_notice_confirm(ui_notice_severity_t severity, const char *text, unsigned timeout_seconds)
Definition notice.c:494
int ui_notice_render(frame_buffer_t *buffer, const char *text, ui_notice_severity_t severity, int cols, bool unicode, bool color)
Definition notice.c:64
#define FIRST_USE_LINE_BYTES
Definition notice.c:202
#define FIRST_USE_LINE_COUNT
Definition notice.c:201
void ui_notice_flush_snapshot(int fd, const void *data)
Definition notice.c:526
ui_notice_key_result_t ui_notice_first_use_key(const void *data, int key)
Definition notice.c:453
void ui_notice_frame_presented(const void *data)
Definition notice.c:447
ui_notice_severity_t ui_notice_prompt_severity(void)
Definition notice.c:522
bool ui_notice_confirm_kind(ui_prompt_kind_t kind, ui_notice_severity_t severity, const char *text, unsigned timeout_seconds)
Definition notice.c:498
asciichat_error_t ui_notice_present(ui_notice_severity_t severity, const char *text)
Definition notice.c:371
const char * ui_notice_title(const void *data)
Definition notice.c:194
void ui_notice_first_use_complete(const void *data)
Definition notice.c:478
ui_notice_key_result_t
Definition notice.h:30
@ UI_NOTICE_KEY_HELP
Definition notice.h:33
@ UI_NOTICE_KEY_ORDINARY
Definition notice.h:31
@ UI_NOTICE_KEY_DISMISS
Definition notice.h:34
@ UI_NOTICE_KEY_COMPLETE
Definition notice.h:35
@ UI_NOTICE_KEY_HANDLED
Definition notice.h:32
ui_notice_severity_t
Definition notice.h:9
@ UI_NOTICE_WARNING
Definition notice.h:11
@ UI_NOTICE_SUCCESS
Definition notice.h:14
@ UI_NOTICE_INFO
Definition notice.h:10
@ UI_NOTICE_DANGER
Definition notice.h:12
@ UI_NOTICE_FATAL
Definition notice.h:13
⚙️ Unified options parsing system for ascii-chat with builder pattern and lock-free access
asciichat_error_t ui_popup_rule(frame_buffer_t *out, int width, ui_popup_edge_t edge, bool double_border, bool unicode, log_color_t color)
Definition popup.c:12
bool ui_popup_render(frame_buffer_t *output, terminal_size_t size, const ui_popup_t *box)
Definition popup.c:56
ui_popup_edge_t
Definition popup.h:9
@ UI_POPUP_BOTTOM
Definition popup.h:9
@ UI_POPUP_SEPARATOR
Definition popup.h:9
@ UI_POPUP_TOP
Definition popup.h:9
🔤 String Manipulation and Shell Escaping Utilities
When code needs to work with asciichat errno snapshot, it keeps total, copied here instead of scatter...
size_t marker_offset
Definition notice.c:178
atomic_int page
Definition notice.c:180
uint64_t started
Definition notice.c:177
ui_notice_severity_t severity
Definition notice.c:174
atomic_int presented_page
Definition notice.c:182
size_t title_offset
Definition notice.c:179
atomic_int pages
Definition notice.c:181
When code needs to work with ui popup, it keeps title, lines, line_count, footer here instead of scat...
Definition popup.h:23
const char * ui_text_color_prefix(log_color_t color)
Definition text.c:18
⏱️ High-precision timing utilities using sokol_time.h and uthash
void ui_controller_remove(ui_screen_t screen)
asciichat_error_t ui_controller_submit(ui_screen_t screen, int fd, terminal_size_t minimum, ui_render_fn render, const void *snapshot, size_t bytes)
bool ui_controller_is_presenting(void)
bool ui_controller_is_owner(void)
void ui_controller_finish(int fd, const char *data, size_t len)
terminal_size_t ui_controller_size(void)
asciichat_error_t ui_controller_write(int fd, const char *data, size_t len)
asciichat_error_t ui_controller_printf(int fd, const char *format,...)
@ UI_SCREEN_NOTICE
#define LOG_FATAL
Definition types.h:43
#define LOG_ERROR
Definition types.h:42
#define LOG_WARN
Definition types.h:41
#define LOG_INFO
Definition types.h:40
🔤 UTF-8 Encoding and Decoding Utilities