ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
session_status.c
Go to the documentation of this file.
1
2/* Storage capacities in elements; text buffers include the terminating NUL. */
3#define TUI_SESSION_STATUS_STATUS_LINE_BUFFER_SIZE 512
4#define TUI_SESSION_STATUS_STATUS_LINE_TRUNCATED_BUFFER_SIZE 512
5#define TUI_SESSION_STATUS_STATUS_LINE_COLORED_BUFFER_SIZE 600
6#define TUI_SESSION_STATUS_UPTIME_BUFFER_SIZE 12
7#define TUI_SESSION_STATUS_ADDR_LINE_BUFFER_SIZE 512
8#define TUI_SESSION_STATUS_ADDR_LINE_TRUNCATED_BUFFER_SIZE 512
9#define TUI_SESSION_STATUS_IPV4_ONLY_BUFFER_SIZE 64
10#define TUI_SESSION_STATUS_IPV6_ONLY_BUFFER_SIZE 64
11#define TUI_SESSION_STATUS_TEXT_BUFFER_SIZE 2048
12#define TUI_SESSION_STATUS_FOOTER_BUFFER_SIZE 128
13#define TUI_SESSION_STATUS_KEYS_BUFFER_SIZE 96
14
20#include <ascii-chat/ui/popup.h>
23#include <ascii-chat/log/grep.h>
27#include <ascii-chat/util/ip.h>
30#include <libplatform/mutex.h>
31#include <stdio.h>
32#include <string.h>
33
34void ui_status_render_header(frame_buffer_t *buf, terminal_size_t term_size, void *user_data) {
35 const ui_status_t *status = (const ui_status_t *)user_data;
36 if (!status) {
37 return;
38 }
39
40 // Calculate uptime
41 time_t now = time(NULL);
42 time_t uptime_secs = now - status->start_time;
43 int uptime_hours = uptime_secs / SEC_PER_HOUR;
44 int uptime_mins = (uptime_secs % SEC_PER_HOUR) / SEC_PER_MIN;
45 int uptime_secs_rem = uptime_secs % 60;
46
47 // Line 1: Top border
48 bool unicode = terminal_should_use_utf8(GET_OPTION(force_utf8));
49 bool colors = !GET_OPTION(strip_ansi) &&
50 terminal_should_color_output(frame_buffer_get_screen_output_fd(), options_terminal_settings());
51 frame_buffer_render_divider(buf, term_size.cols - 1, unicode, colors);
52
53 // The bundled browser font does not contain the emoji used in native status titles.
54#ifdef __EMSCRIPTEN__
55 bool status_icons = false;
56#else
57 bool status_icons = unicode;
58#endif
59
60 // Line 2: Title + Stats (centered)
65 format_uptime_hms(uptime_hours, uptime_mins, uptime_secs_rem, uptime_str, sizeof(uptime_str));
66 bool generic_log_header = status->detail && strstr(status->detail, "keyboard controls") != NULL;
67 if (generic_log_header)
68 snprintf(status_line, sizeof(status_line), status_icons ? "ascii-chat | ⏱️ %s" : "ascii-chat | uptime %s",
69 uptime_str);
70 else if (status->detail)
71 snprintf(status_line, sizeof(status_line), status_icons ? "ascii-chat %s | ⏱️ %s" : "ascii-chat %s | uptime %s",
72 status->mode_name, uptime_str);
73 else
74 snprintf(status_line, sizeof(status_line),
75 status_icons ? "ascii-chat %s | 👥 %zu | ⏱️ %s" : "ascii-chat %s | clients %zu | uptime %s",
76 status->mode_name, status->connected_count, uptime_str);
77
78 // Truncate if needed (ANSI-aware)
79 truncate_with_ellipsis(status_line, status_line_truncated, sizeof(status_line_truncated), term_size.cols - 2);
80 snprintf(status_line_colored, sizeof(status_line_colored), "\033[1m%s\033[0m",
81 colored_string(LOG_COLOR_DEBUG, status_line_truncated));
82
83 int padding = display_center_horizontal(status_line_truncated, term_size.cols);
84 for (int i = 0; i < padding; i++) {
85 frame_buffer_printf(buf, " ");
86 }
87 frame_buffer_printf(buf, "%s\n", status_line_colored);
88
89 // Line 3: Session + Addresses (centered)
90 char addr_line[TUI_SESSION_STATUS_ADDR_LINE_BUFFER_SIZE] = {0};
92 int pos = 0;
93 if (status->session_string[0] != '\0') {
94 pos += snprintf(addr_line + pos, sizeof(addr_line) - pos, unicode ? "🔗 %s" : "Session %s", status->session_string);
95 }
96 if (status->ipv4_bound && pos < (int)sizeof(addr_line) - 30) {
97 if (pos > 0) {
98 pos += snprintf(addr_line + pos, sizeof(addr_line) - pos, " | ");
99 }
100 // Extract IP and get type
102 if (extract_ip_from_address(status->ipv4_address, ipv4_only, sizeof(ipv4_only)) == 0) {
103 const char *type = get_ip_type_string(ipv4_only);
104 pos += snprintf(addr_line + pos, sizeof(addr_line) - pos, "%s (%s)", status->ipv4_address, type);
105 } else {
106 pos += snprintf(addr_line + pos, sizeof(addr_line) - pos, "%s", status->ipv4_address);
107 }
108 }
109 if (status->ipv6_bound && pos < (int)sizeof(addr_line) - 30) {
110 if (pos > 0) {
111 pos += snprintf(addr_line + pos, sizeof(addr_line) - pos, " | ");
112 }
113 // Extract IP and get type
115 if (extract_ip_from_address(status->ipv6_address, ipv6_only, sizeof(ipv6_only)) == 0) {
116 const char *type = get_ip_type_string(ipv6_only);
117 snprintf(addr_line + pos, sizeof(addr_line) - pos, "%s (%s)", status->ipv6_address, type);
118 } else {
119 snprintf(addr_line + pos, sizeof(addr_line) - pos, "%s", status->ipv6_address);
120 }
121 }
122
123 if (status->detail)
124 SAFE_STRNCPY(addr_line, status->detail, sizeof(addr_line));
125
126 // Truncate if needed (ANSI-aware)
127 truncate_with_ellipsis(addr_line, addr_line_truncated, sizeof(addr_line_truncated), term_size.cols - 2);
128
129 int addr_padding = display_center_horizontal(addr_line_truncated, term_size.cols);
130 for (int i = 0; i < addr_padding; i++) {
131 frame_buffer_printf(buf, " ");
132 }
133 frame_buffer_printf(buf, "%s\n", addr_line_truncated);
134
135 // Line 4: Bottom border
136 frame_buffer_render_divider(buf, term_size.cols - 1, unicode, colors);
137}
138
139typedef struct {
140 size_t entry;
141 int row;
148
149static const char *startup_log_summary(const char *message, char *buffer, size_t buffer_size) {
150 if (!message || !buffer || !buffer_size)
151 return message;
152
153 char plain[SESSION_LOG_LINE_MAX];
154 strip_ansi_codes(message, plain, sizeof(plain));
155 const char *summary = strstr(plain, "(): ");
156 if (summary)
157 summary += 4;
158 else {
159 summary = strrchr(plain, ']');
160 summary = summary ? summary + 1 : plain;
161 while (*summary == ' ')
162 ++summary;
163 }
164
165 snprintf(buffer, buffer_size, "[ * ] %s", summary);
166 return buffer;
167}
168
169static int log_message_rows(const char *message, int width, bool compact_startup) {
170 char formatted[SESSION_LOG_LINE_MAX + 32];
171 if (compact_startup)
172 message = startup_log_summary(message, formatted, sizeof(formatted));
173 int rows = display_height(message, width);
174 return rows > 0 ? rows : 1;
175}
176
177static size_t utf8_byte_offset_for_char(const char *text, size_t character_offset) {
178 const char *cursor = text;
179 while (*cursor && character_offset) {
180 uint32_t codepoint;
181 int length = utf8_decode((const uint8_t *)cursor, &codepoint);
182 cursor += length > 0 ? length : 1;
183 --character_offset;
184 }
185 return (size_t)(cursor - text);
186}
187
188static void append_log_row(frame_buffer_t *body, const char *message, int row, int width, bool compact_startup) {
189 char formatted[SESSION_LOG_LINE_MAX + 32];
190 if (compact_startup) {
191 message = startup_log_summary(message, formatted, sizeof(formatted));
192 }
193 char plain[SESSION_LOG_LINE_MAX];
194 strip_ansi_codes(message, plain, sizeof(plain));
195 const char *start = plain;
196 size_t remaining = strlen(plain);
197 for (int i = 0; i < row && remaining; ++i) {
198 size_t prefix = utf8_prefix_bytes_for_width(start, remaining, width);
199 if (!prefix)
200 break;
201 start += prefix;
202 remaining -= prefix;
203 }
204 if (!remaining) {
205 frame_buffer_printf(body, "\n");
206 return;
207 }
208 size_t length = utf8_prefix_bytes_for_width(start, remaining, width);
209 if (!length) {
210 frame_buffer_printf(body, "\n");
211 return;
212 }
213 if (compact_startup) {
214 char line[SESSION_LOG_LINE_MAX];
215 memcpy(line, start, length);
216 line[length] = '\0';
217 bool startup_marker = compact_startup && row == 0 && strncmp(line, "[ * ] ", 6) == 0;
218 const char *content = startup_marker ? line + 6 : line;
219 size_t match_start = 0;
220 size_t match_length = 0;
221 const char *rendered = content;
222 if (log_search_get_match_info(content, &match_start, &match_length)) {
223 size_t start_byte = utf8_byte_offset_for_char(content, match_start);
224 size_t end_byte = utf8_byte_offset_for_char(content, match_start + match_length);
225 rendered = grep_highlight(content, start_byte, end_byte - start_byte);
226 }
227 if (startup_marker)
230 frame_buffer_printf(body, "%s\033[0m\n", rendered);
231 return;
232 }
233 size_t match_start = 0, match_length = 0;
234 const char *rendered = message;
235 if (log_search_get_match_info(plain, &match_start, &match_length)) {
236 size_t start_byte = utf8_byte_offset_for_char(plain, match_start);
237 size_t end_byte = utf8_byte_offset_for_char(plain, match_start + match_length);
238 rendered = grep_highlight_colored(message, plain, start_byte, end_byte - start_byte);
239 }
240 // Replay preceding SGR sequences so wrapped rows retain their original style.
241 size_t first = (size_t)(start - plain), last = first + length, offset = 0;
242 for (const char *cursor = rendered; *cursor && offset < last;) {
243 if (cursor[0] == '\033' && cursor[1] == '[') {
244 const char *escape = cursor;
245 cursor += 2;
246 while (*cursor && !(*cursor >= '@' && *cursor <= '~'))
247 ++cursor;
248 if (*cursor) {
249 if (*cursor++ == 'm')
250 frame_buffer_append(body, escape, (size_t)(cursor - escape));
251 }
252 } else {
253 if (offset >= first)
254 frame_buffer_append(body, cursor, 1);
255 ++cursor;
256 ++offset;
257 }
258 }
259 frame_buffer_printf(body, "\033[0m\n");
260}
261
262void terminal_screen_compose(frame_buffer_t *output, terminal_size_t size, const terminal_screen_config_t *config,
263 session_log_buffer_t *logs) {
264 if (!output || !config || !config->render_header || size.cols < 1 || size.rows < 1)
265 return;
266 if (config->show_logs)
268 frame_buffer_t *body = frame_buffer_create(size.rows, size.cols);
269 if (!body)
270 return;
271 config->render_header(body, size, config->user_data);
272
273 // Measure the actual header, including wrapped lines, as the native status UI does.
274 int header_rows = 0;
275 const char *line = frame_buffer_get_content(body);
276 const char *end = line + frame_buffer_get_length(body);
277 for (const char *cursor = line; cursor < end; ++cursor) {
278 if (*cursor == '\n') {
280 size_t length = (size_t)(cursor - line);
281 if (length >= sizeof(text))
282 length = sizeof(text) - 1;
283 memcpy(text, line, length);
284 text[length] = '\0';
285 int height = display_height(text, size.cols);
286 header_rows += height > 0 ? height : 1;
287 line = cursor + 1;
288 }
289 }
290 if (!header_rows)
291 header_rows = config->fixed_header_lines;
292 bool entering = config->show_logs && log_search_is_entering();
293 // Search owns the final row for its prompt. Otherwise logs fill it.
294 int available = size.rows - header_rows - (entering ? 1 : 0);
295 bool compact_startup = config->screen == UI_SCREEN_SPLASH;
296 session_log_entry_t *entries = NULL;
297 size_t count = 0;
298 size_t scroll_total = 0, scroll_offset = 0;
299 if (config->show_logs && available > 0) {
301 log_search_filter_buffer(logs, &entries, &count);
302 else {
303 entries = SAFE_MALLOC(SESSION_LOG_BUFFER_SIZE * sizeof(*entries), session_log_entry_t *);
304 if (entries)
306 }
307 log_row_ref_t *rows = SAFE_MALLOC((size_t)available * sizeof(*rows), log_row_ref_t *);
308 if (rows) {
309 size_t total_rows = 0;
310 uint64_t previous_tail_sequence = 0;
311 size_t appended_rows = 0;
312 for (size_t entry = 0; entry < count; ++entry) {
313 size_t height = (size_t)log_message_rows(entries[entry].message, size.cols, compact_startup);
314 if (SIZE_MAX - total_rows < height)
315 total_rows = SIZE_MAX;
316 else
317 total_rows += height;
318 }
319 uint64_t tail_sequence = count ? entries[count - 1].sequence : 0;
320 previous_tail_sequence = terminal_screen_logs_last_tail_sequence();
321 if (previous_tail_sequence && tail_sequence > previous_tail_sequence) {
322 for (size_t entry = 0; entry < count; ++entry) {
323 if (entries[entry].sequence > previous_tail_sequence)
324 appended_rows += (size_t)log_message_rows(entries[entry].message, size.cols, compact_startup);
325 }
326 }
327 size_t max_offset = total_rows ? total_rows - 1 : 0;
328 size_t skip = terminal_screen_logs_retain_position(tail_sequence, appended_rows, max_offset);
329 scroll_total = total_rows;
330 scroll_offset = total_rows > (size_t)available + skip ? total_rows - (size_t)available - skip : 0;
331 size_t row_count = 0;
332 for (size_t entry = count; entry && row_count < (size_t)available; --entry) {
333 int height = log_message_rows(entries[entry - 1].message, size.cols, compact_startup);
334 if (skip >= (size_t)height) {
335 skip -= (size_t)height;
336 continue;
337 }
338 int row = height - 1 - (int)skip;
339 skip = 0;
340 for (; row >= 0 && row_count < (size_t)available; --row)
341 rows[row_count++] = (log_row_ref_t){.entry = entry - 1, .row = row};
342 }
343 for (size_t row = row_count; row; --row)
344 append_log_row(body, entries[rows[row - 1].entry].message, rows[row - 1].row, size.cols, compact_startup);
345 while (row_count++ < (size_t)available)
346 frame_buffer_printf(body, "\n");
347 SAFE_FREE(rows);
348 }
349 }
350 SAFE_FREE(entries);
351
352 // Preserve the existing repaint-in-place behavior, also supplying CR for browser terminals.
353 bool log_help_open = config->show_logs && terminal_screen_logs_help_open();
354 bool hide_cursor = log_help_open || (!entering && (config->hide_cursor || config->show_logs));
355 frame_buffer_printf(output, "\033[H%s", hide_cursor ? "\033[?25l" : "\033[?25h");
356 const char *content = frame_buffer_get_content(body);
357 size_t length = frame_buffer_get_length(body);
358 for (size_t i = 0; i < length; ++i) {
359 // Normalize CRLF before erasing the line; erasing after CR removes its text.
360 if (content[i] == '\r' && i + 1 < length && content[i + 1] == '\n')
361 continue;
362 if (content[i] == '\n') {
363 frame_buffer_printf(output, "\033[K\r");
364 // Do not advance past the bottom row when the idle log view fills it.
365 if (config->show_logs && !entering && i + 1 == length)
366 continue;
367 }
368 frame_buffer_append(output, content + i, 1);
369 }
370 if (config->show_logs && available > 0 && scroll_total > (size_t)available)
371 ui_scrollbar_render(output, header_rows + 1, size.cols, available, scroll_total, (size_t)available, scroll_offset);
372 if (entering) {
373 mutex_t *mutex = log_search_get_mutex();
374 mutex_lock(mutex);
375 const char *pattern = log_search_get_input_buffer();
376 int cursor = log_search_get_cursor_position();
377 // Keep the edit cursor visible even when a pattern exceeds the viewport.
378 int offset = cursor >= size.cols - 2 ? cursor - (size.cols - 3) : 0;
379 frame_buffer_printf(output, "\033[%d;1H/%.*s\033[K\033[%d;%dH", size.rows, size.cols - 2,
380 pattern ? pattern + offset : "", size.rows, cursor - offset + 2);
381 mutex_unlock(mutex);
382 }
383 if (log_help_open) {
385 } else if (config->show_logs && terminal_screen_logs_help_too_small()) {
386 frame_buffer_printf(output, "\033[%d;1H%s", size.rows, "Help needs at least 12 terminal rows");
387 }
389}
390
391void terminal_screen_logs_render_help(frame_buffer_t *output, terminal_size_t size) {
392 const ui_help_entry_t entries[] = {{"Down", "newer log"}, {"Up", "older log"}, {"PageDown", "newer page"},
393 {"PageUp", "older page"}, {"End", "newest log"}, {"Home", "oldest log"},
394 {"/", "search logs"}, {"q / Esc", "close viewer"}};
396 ui_help_keys(keys, sizeof(keys), "? / Esc");
397 snprintf(footer, sizeof(footer), "%s closes this help", keys);
398 ui_help_menu_t menu = {.title = "Log Viewer Keyboard Controls",
399 .entries = entries,
400 .count = sizeof(entries) / sizeof(*entries),
401 .footer = footer};
402 (void)ui_help_menu_render(output, size, &menu);
403}
unsigned char colors[(512 *256 *2) *3]
Definition fft.c:22
unsigned int height
Definition fft.c:23
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.
void frame_buffer_render_divider(frame_buffer_t *buf, int width, bool use_utf8, bool use_colors)
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.
int frame_buffer_get_screen_output_fd(void)
Get current output file descriptor for terminal screens.
struct frame_buffer frame_buffer_t
Opaque frame buffer handle.
const char * grep_highlight(const char *log_line, size_t match_start, size_t match_len)
Highlight matched portion with background color.
Definition grep.c:999
int buffer_size
Size of circular buffer.
Definition grep.c:68
const char * grep_highlight_colored(const char *colored_text, const char *plain_text, size_t match_start, size_t match_len)
Highlight matches in colored text while preserving original colors.
Definition grep.c:670
unsigned int uint32_t
Definition common.h:62
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:423
#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
unsigned char uint8_t
Definition common.h:60
struct session_log_buffer session_log_buffer_t
Thread-safe fixed-size history ring for session-screen log lines.
@ LOG_COLOR_DEBUG
Definition log/log.h:149
@ LOG_COLOR_WARN
Definition log/log.h:151
@ LOG_COLOR_WHITE
Definition log/log.h:156
#define SEC_PER_HOUR
Definition time.h:180
int format_uptime_hms(int hours, int minutes, int seconds, char *buffer, size_t buffer_size)
Format uptime as HH:MM:SS string.
Definition time.c:318
#define SEC_PER_MIN
Definition time.h:179
#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
const char * colored_string(log_color_t color, const char *text)
Build a colored string for terminal output.
Definition string.c:284
int utf8_decode(const uint8_t *s, uint32_t *codepoint)
Decode a UTF-8 sequence to a Unicode codepoint.
Definition utf8.c:27
int extract_ip_from_address(const char *addr_with_port, char *ip_out, size_t ip_out_size)
Extract IP address (without port) from formatted address string.
Definition ip.c:972
const char * get_ip_type_string(const char *ip)
Get human-readable string describing IP address type.
Definition ip.c:1010
void strip_ansi_codes(const char *input, char *output, size_t output_size)
Strip ANSI escape codes from a string.
Definition string.c:374
int display_center_horizontal(const char *text, int terminal_width)
Calculate left padding needed to center text horizontally.
int display_height(const char *text, int terminal_width)
Calculate how many display lines text will consume when wrapped.
void truncate_with_ellipsis(const char *input, char *output, size_t output_size, int max_width)
Truncate a string to a maximum display width, with ellipsis.
Definition string.c:370
void ui_help_keys(char *out, size_t cap, const char *keys)
Definition help_menu.c:25
bool ui_help_menu_render(frame_buffer_t *out, terminal_size_t size, const ui_help_menu_t *m)
Definition help_menu.c:101
🖥️ Display Width and Terminal Centering Utilities
🌍 IP Address Parsing and Formatting Utilities
std::string escape(const std::string &str)
Escape a string for JSON output.
⚙️ Unified options parsing system for ascii-chat with builder pattern and lock-free access
void ui_scrollbar_render(frame_buffer_t *out, int row, int col, int height, size_t total, size_t visible, size_t offset)
Definition scrollbar.c:40
void * log_search_get_mutex(void)
Get the mutex protecting grep state.
Definition search.c:985
bool log_search_is_entering(void)
Check if currently in input mode (typing pattern).
Definition search.c:408
void log_search_process_pending(void)
Definition search.c:469
asciichat_error_t log_search_filter_buffer(session_log_buffer_t *log_buf, session_log_entry_t **out_entries, size_t *out_count)
Definition search.c:574
const char * log_search_get_input_buffer(void)
Get current input buffer content (must hold mutex).
Definition search.c:997
bool log_search_is_active(void)
Check if filtering is active.
Definition search.c:413
bool log_search_get_match_info(const char *message, size_t *out_match_start, size_t *out_match_len)
Get match info for highlighting a log message.
Definition search.c:773
int log_search_get_cursor_position(void)
Get current cursor position in input buffer (must hold mutex).
Definition search.c:993
Interactive search filtering for terminal screens.
size_t session_log_buffer_get_recent(session_log_buffer_t *buf, session_log_entry_t *out_entries, size_t max_count)
Get recent log entries from buffer.
Thread-safe circular log buffer for session screens (status, splash).
#define SESSION_LOG_BUFFER_SIZE
#define SESSION_LOG_LINE_MAX
void ui_status_render_header(frame_buffer_t *buf, terminal_size_t term_size, void *user_data)
#define TUI_SESSION_STATUS_IPV4_ONLY_BUFFER_SIZE
#define TUI_SESSION_STATUS_ADDR_LINE_TRUNCATED_BUFFER_SIZE
void terminal_screen_compose(frame_buffer_t *output, terminal_size_t size, const terminal_screen_config_t *config, session_log_buffer_t *logs)
#define TUI_SESSION_STATUS_UPTIME_BUFFER_SIZE
#define TUI_SESSION_STATUS_IPV6_ONLY_BUFFER_SIZE
#define TUI_SESSION_STATUS_TEXT_BUFFER_SIZE
#define TUI_SESSION_STATUS_STATUS_LINE_COLORED_BUFFER_SIZE
#define TUI_SESSION_STATUS_ADDR_LINE_BUFFER_SIZE
#define TUI_SESSION_STATUS_STATUS_LINE_BUFFER_SIZE
#define TUI_SESSION_STATUS_FOOTER_BUFFER_SIZE
#define TUI_SESSION_STATUS_KEYS_BUFFER_SIZE
void terminal_screen_logs_render_help(frame_buffer_t *output, terminal_size_t size)
#define TUI_SESSION_STATUS_STATUS_LINE_TRUNCATED_BUFFER_SIZE
📊 Status screen display and management for server/discovery service modes
🔤 String Manipulation and Shell Escaping Utilities
One formatted session log line and its monotonic history sequence.
uint64_t sequence
Sequence number (monotonic, for ordering).
Configuration for terminal screen rendering.
terminal_screen_header_fn render_header
Callback to draw header content.
int fixed_header_lines
How many lines the header takes (e.g., 4 for status, 8 for splash).
bool show_logs
Whether to show log feed below header.
bool hide_cursor
Hide cursor while this screen owns the terminal.
void * user_data
Passed to render_header callback.
Definition help_menu.h:3
ui help entry keeps the values that move through the subsystem: keys, description....
Definition help_menu.h:12
Status information for server/discovery service modes.
Definition status.h:38
bool ipv4_bound
Whether IPv4 socket is bound.
Definition status.h:44
char ipv4_address[256]
Formatted IPv4 bind address with port.
Definition status.h:40
const char * detail
Optional client controls in the address row.
Definition status.h:48
bool ipv6_bound
Whether IPv6 socket is bound.
Definition status.h:45
char ipv6_address[256]
Formatted IPv6 bind address with port.
Definition status.h:41
char session_string[64]
Memorable session string (e.g., "happy-sunset-ocean").
Definition status.h:39
const char * mode_name
Mode name (e.g., "Server", "Discovery Service").
Definition status.h:49
time_t start_time
Server start time (for uptime calculation).
Definition status.h:47
size_t connected_count
Number of connected clients/servers.
Definition status.h:43
bool terminal_screen_logs_help_open(void)
size_t terminal_screen_logs_retain_position(uint64_t tail_sequence, size_t appended_rows, size_t maximum_offset)
uint64_t terminal_screen_logs_last_tail_sequence(void)
bool terminal_screen_logs_help_too_small(void)
Reusable "fixed header + scrolling logs" terminal screen abstraction.
⏱️ High-precision timing utilities using sokol_time.h and uthash
@ UI_SCREEN_SPLASH
size_t utf8_prefix_bytes_for_width(const char *str, size_t max_bytes, int max_width)
Return the byte length of a prefix fitting a terminal column limit.
Definition utf8.c:93
🔤 UTF-8 Encoding and Decoding Utilities