ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
status.c
Go to the documentation of this file.
1
11#include <ascii-chat/log/grep.h>
18#include <ascii-chat/util/ip.h>
21#include <ascii-chat/common.h>
22#include <stdio.h>
23#include <string.h>
24#include <time.h>
25#include <unistd.h>
26#include <ascii-chat/atomic.h>
27
29 // Initialize the shared terminal screen log buffer
31 if (buf) {
33 }
34}
35
37 // Unregister from logger before destroying
39 // Cleanup the shared terminal screen log buffer
41}
42
44 // Delegate to terminal_screen log abstraction
46}
47
48// ============================================================================
49// Status Display
50// ============================================================================
51
52asciichat_error_t ui_status_gather(tcp_server_t *server, const char *session_string, const char *ipv4_address,
53 const char *ipv6_address, uint16_t port, time_t start_time, const char *mode_name,
54 bool session_is_mdns_only, ui_status_t *out_status) {
55 if (!server || !out_status || !mode_name) {
56 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters for ui_status_gather");
57 }
58
59 memset(out_status, 0, sizeof(*out_status));
60
61 // Copy session string
62 if (session_string) {
63 SAFE_STRNCPY(out_status->session_string, session_string, sizeof(out_status->session_string));
64 }
65
66 // Format IPv4 address
67 out_status->ipv4_bound = (ipv4_address && ipv4_address[0] != '\0');
68 if (out_status->ipv4_bound) {
69 snprintf(out_status->ipv4_address, sizeof(out_status->ipv4_address), "%s:%u", ipv4_address, port);
70 }
71
72 // Format IPv6 address
73 out_status->ipv6_bound = (ipv6_address && ipv6_address[0] != '\0');
74 if (out_status->ipv6_bound) {
75 snprintf(out_status->ipv6_address, sizeof(out_status->ipv6_address), "[%s]:%u", ipv6_address, port);
76 }
77
78 out_status->port = port;
79 out_status->start_time = start_time;
80 out_status->mode_name = mode_name;
81 out_status->session_is_mdns_only = session_is_mdns_only;
82 out_status->connected_count = tcp_server_get_client_count(server);
83
84 return ASCIICHAT_OK;
85}
86
104static void render_ui_status_header(frame_buffer_t *buf, terminal_size_t term_size, void *user_data) {
105 const ui_status_t *status = (const ui_status_t *)user_data;
106 if (!status) {
107 return;
108 }
109
110 // Calculate uptime
111 time_t now = time(NULL);
112 time_t uptime_secs = now - status->start_time;
113 int uptime_hours = uptime_secs / SEC_PER_HOUR;
114 int uptime_mins = (uptime_secs % SEC_PER_HOUR) / SEC_PER_MIN;
115 int uptime_secs_rem = uptime_secs % 60;
116
117 // Line 1: Top border
118 frame_buffer_printf(buf, "\033[1;36m━");
119 for (int i = 1; i < term_size.cols - 1; i++) {
120 frame_buffer_printf(buf, "━");
121 }
122 frame_buffer_printf(buf, "\033[0m\n");
123
124 // Line 2: Title + Stats (centered)
125 char status_line[512];
126 char status_line_truncated[512];
127 char status_line_colored[600];
128 char uptime_str[12];
129 format_uptime_hms(uptime_hours, uptime_mins, uptime_secs_rem, uptime_str, sizeof(uptime_str));
130 snprintf(status_line, sizeof(status_line), "ascii-chat %s | 👥 %zu | ⏱️ %s", status->mode_name,
131 status->connected_count, uptime_str);
132
133 // Truncate if needed (ANSI-aware)
134 truncate_with_ellipsis(status_line, status_line_truncated, sizeof(status_line_truncated), term_size.cols - 2);
135 snprintf(status_line_colored, sizeof(status_line_colored), "\033[1;36m%s\033[0m", status_line_truncated);
136
137 int padding = display_center_horizontal(status_line_truncated, term_size.cols);
138 for (int i = 0; i < padding; i++) {
139 frame_buffer_printf(buf, " ");
140 }
141 frame_buffer_printf(buf, "%s\n", status_line_colored);
142
143 // Line 3: Session + Addresses (centered)
144 char addr_line[512];
145 char addr_line_truncated[512];
146 int pos = 0;
147 if (status->session_string[0] != '\0') {
148 pos += snprintf(addr_line + pos, sizeof(addr_line) - pos, "🔗 %s", status->session_string);
149 }
150 if (status->ipv4_bound && pos < (int)sizeof(addr_line) - 30) {
151 if (pos > 0) {
152 pos += snprintf(addr_line + pos, sizeof(addr_line) - pos, " | ");
153 }
154 // Extract IP and get type
155 char ipv4_only[64];
156 if (extract_ip_from_address(status->ipv4_address, ipv4_only, sizeof(ipv4_only)) == 0) {
157 const char *type = get_ip_type_string(ipv4_only);
158 pos += snprintf(addr_line + pos, sizeof(addr_line) - pos, "%s (%s)", status->ipv4_address, type);
159 } else {
160 pos += snprintf(addr_line + pos, sizeof(addr_line) - pos, "%s", status->ipv4_address);
161 }
162 }
163 if (status->ipv6_bound && pos < (int)sizeof(addr_line) - 30) {
164 if (pos > 0) {
165 pos += snprintf(addr_line + pos, sizeof(addr_line) - pos, " | ");
166 }
167 // Extract IP and get type
168 char ipv6_only[64];
169 if (extract_ip_from_address(status->ipv6_address, ipv6_only, sizeof(ipv6_only)) == 0) {
170 const char *type = get_ip_type_string(ipv6_only);
171 snprintf(addr_line + pos, sizeof(addr_line) - pos, "%s (%s)", status->ipv6_address, type);
172 } else {
173 snprintf(addr_line + pos, sizeof(addr_line) - pos, "%s", status->ipv6_address);
174 }
175 }
176
177 // Truncate if needed (ANSI-aware)
178 truncate_with_ellipsis(addr_line, addr_line_truncated, sizeof(addr_line_truncated), term_size.cols - 2);
179
180 int addr_padding = display_center_horizontal(addr_line_truncated, term_size.cols);
181 for (int i = 0; i < addr_padding; i++) {
182 frame_buffer_printf(buf, " ");
183 }
184 frame_buffer_printf(buf, "%s\n", addr_line_truncated);
185
186 // Line 4: Bottom border
187 frame_buffer_printf(buf, "\033[1;36m━");
188 for (int i = 1; i < term_size.cols - 1; i++) {
189 frame_buffer_printf(buf, "━");
190 }
191 frame_buffer_printf(buf, "\033[0m\n");
192}
193
194void ui_status_display(const ui_status_t *status) {
195 if (!status) {
196 return;
197 }
198
199 // Only render the status screen if:
200 // 1. Terminal is interactive (default behavior), OR
201 // 2. User explicitly set --status-screen on command line (force it regardless of terminal)
202 // In non-interactive mode without explicit flag, logs flow to stdout/stderr normally
203 if (!terminal_is_interactive() && !GET_OPTION(status_screen_explicitly_set)) {
204 return;
205 }
206
207 // Redirect status screen to stderr when stdout is piped (not a TTY).
208 // This keeps stdout clean for frame data while status goes to stderr.
209 // Uses platform_isatty() so non-desktop platforms (iOS, WASM) can override.
210 if (!platform_isatty(STDOUT_FILENO)) {
211 terminal_screen_set_output_fd(STDERR_FILENO);
212 }
213
214 // If --grep pattern was provided, enter interactive grep mode with it pre-populated
215 // Only do this once (check if not already entering)
216 static bool grep_mode_entered = false;
217 if (!grep_mode_entered && grep_get_last_pattern() && grep_get_last_pattern()[0] != '\0') {
219 grep_mode_entered = true;
220 }
221
222 // Use terminal_screen abstraction for rendering
223 terminal_screen_config_t config = {
225 .render_header = render_ui_status_header,
226 .user_data = (void *)status,
227 .show_logs = true,
228 };
229
230 terminal_screen_render(&config);
231}
232
238 if (!status) {
239 return true;
240 }
241
242 // Only render the status screen if:
243 // 1. Terminal is interactive (default behavior), OR
244 // 2. User explicitly set --status-screen on command line (force it regardless of terminal)
245 // In non-interactive mode without explicit flag, logs flow to stdout/stderr normally
246 if (!terminal_is_interactive() && !GET_OPTION(status_screen_explicitly_set)) {
247 return true;
248 }
249
250 // If --grep pattern was provided, enter interactive grep mode with it pre-populated
251 static bool grep_mode_entered = false;
252 if (!grep_mode_entered && grep_get_last_pattern() && grep_get_last_pattern()[0] != '\0') {
254 grep_mode_entered = true;
255 }
256
257 // Initialize keyboard for interactive grep
258 bool keyboard_enabled = false;
259 if (keyboard_init() == ASCIICHAT_OK) {
260 keyboard_enabled = true;
261 }
262
263 // Use terminal_screen abstraction for rendering
264 terminal_screen_config_t config = {
266 .render_header = render_ui_status_header,
267 .user_data = (void *)status,
268 .show_logs = true,
269 };
270
271 terminal_screen_render(&config);
272
273 // Poll keyboard for Escape to exit or for interactive grep
274 bool should_exit_status = false;
275 if (keyboard_enabled) {
277 if (key == KEY_ESCAPE) {
278 // Escape key: cancel grep if active, otherwise exit status screen
279 if (log_search_is_active()) {
280 log_search_exit_mode(false); // Cancel grep without applying
281 } else {
282 should_exit_status = true; // Exit status screen
283 }
284 } else if (key != KEY_NONE && log_search_should_handle(key)) {
286 }
287 }
288
289 // Cleanup keyboard
290 if (keyboard_enabled) {
292 }
293
294 return !should_exit_status; // Return false if user wants to exit
295}
296
297void ui_status_update(tcp_server_t *server, const char *session_string, const char *ipv4_address,
298 const char *ipv6_address, uint16_t port, time_t start_time, const char *mode_name,
299 bool session_is_mdns_only, uint64_t *last_update_ns) {
300 if (!server || !last_update_ns) {
301 return;
302 }
303
304 // Update at FPS rate (60 Hz = 16.67ms by default)
305 uint32_t fps = GET_OPTION(fps);
306 if (fps == 0) {
307 fps = 60; // Default
308 }
309
310 // Calculate frame interval in microseconds
311 uint64_t frame_interval_us = US_PER_SEC_INT / fps;
312
313 // Get current time in microseconds using platform abstraction
315
316 // Check if enough time has passed
317 if ((now_us - *last_update_ns) < frame_interval_us) {
318 return; // Too soon, skip frame
319 }
320
321 ui_status_t status;
322 if (ui_status_gather(server, session_string, ipv4_address, ipv6_address, port, start_time, mode_name,
323 session_is_mdns_only, &status) == ASCIICHAT_OK) {
324 ui_status_display(&status);
325 *last_update_ns = now_us;
326 }
327}
Platform abstraction layer umbrella header providing unified cross-platform API.
⚛️ Atomic operations abstraction layer with debug tracking
⚙️ Common definitions, error codes, macros, and types shared throughout the application
void frame_buffer_printf(frame_buffer_t *buf, const char *fmt,...)
Printf-style append to the buffer.
Buffered terminal frame rendering.
const char * grep_get_last_pattern(void)
Get the last (most recent) filter pattern string.
Definition grep.c:1326
unsigned short uint16_t
Definition common.h:57
unsigned int uint32_t
Definition common.h:58
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:414
unsigned long long uint64_t
Definition common.h:59
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_INVALID_PARAM
void log_clear_session_log_buffer(void)
Unregister the session log buffer.
Definition log/log.c:1847
void log_set_session_log_buffer(session_log_buffer_t *buf)
Register a session log buffer with the logger.
Definition log/log.c:1838
#define SEC_PER_HOUR
Definition time.h:168
int format_uptime_hms(int hours, int minutes, int seconds, char *buffer, size_t buffer_size)
Format uptime as HH:MM:SS string.
Definition util/time.c:354
#define SEC_PER_MIN
Definition time.h:167
#define US_PER_SEC_INT
Definition time.h:161
#define GET_OPTION(field)
Safely get a specific option field (lock-free read)
asciichat_error_t keyboard_init(void)
Initialize keyboard input system.
keyboard_key_t keyboard_read_nonblocking(void)
Read next keyboard input without blocking.
uint64_t platform_get_monotonic_time_us(void)
Get monotonic time in microseconds.
void keyboard_destroy(void)
Cleanup keyboard input system and restore terminal.
keyboard_key_t
Unified keyboard key code enumeration.
Definition keyboard.h:54
@ KEY_ESCAPE
Escape key (ESC)
Definition keyboard.h:56
@ KEY_NONE
No key pressed or no input available.
Definition keyboard.h:55
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:1053
const char * get_ip_type_string(const char *ip)
Get human-readable string describing IP address type.
Definition ip.c:1091
int display_center_horizontal(const char *text, int terminal_width)
Calculate left padding needed to center text horizontally.
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.
⚙️ Unified options parsing system for ascii-chat with builder pattern and lock-free access
Thread-safe circular log buffer for session screens (status, splash)
🖥️ Display Width and Terminal Centering Utilities
🌍 IP Address Parsing and Formatting Utilities
🎮 Cross-platform keyboard input interface for ascii-chat
size_t tcp_server_get_client_count(tcp_server_t *server)
Get client count.
bool terminal_is_interactive(void)
Check if the session is fully interactive.
void log_search_enter_mode(void)
Enter search mode (user pressed '/')
Definition search.c:214
bool log_search_is_active(void)
Check if filtering is active.
Definition search.c:390
void log_search_exit_mode(bool accept)
Exit search mode.
Definition search.c:287
bool log_search_should_handle(int key)
Check if a key should be handled by grep module.
Definition search.c:399
asciichat_error_t log_search_handle_key(keyboard_key_t key)
Process keyboard input for grep.
Definition search.c:414
Interactive search filtering for terminal screens.
void ui_status_log_clear(void)
Clear all log messages from status screen buffer.
Definition status.c:43
void ui_status_display(const ui_status_t *status)
Display status screen.
Definition status.c:194
void ui_status_log_destroy(void)
Cleanup status screen log capture system.
Definition status.c:36
asciichat_error_t ui_status_gather(tcp_server_t *server, const char *session_string, const char *ipv4_address, const char *ipv6_address, uint16_t port, time_t start_time, const char *mode_name, bool session_is_mdns_only, ui_status_t *out_status)
Gather current status information.
Definition status.c:52
void ui_status_update(tcp_server_t *server, const char *session_string, const char *ipv4_address, const char *ipv6_address, uint16_t port, time_t start_time, const char *mode_name, bool session_is_mdns_only, uint64_t *last_update_ns)
Periodically update status display with live logs at FPS rate.
Definition status.c:297
void ui_status_log_init(void)
Initialize status screen log capture system.
Definition status.c:28
bool ui_status_display_interactive(const ui_status_t *status)
Display status screen with keyboard input support Returns true if status screen should continue,...
Definition status.c:237
📊 Status screen display and management for server/discovery service modes
Frame buffer structure.
Internal circular buffer structure.
Configuration for terminal screen rendering.
int fixed_header_lines
How many lines the header takes (e.g., 4 for status, 8 for splash)
Terminal size structure.
Definition terminal.h:163
int cols
Number of columns (width) in terminal.
Definition terminal.h:165
Status information for server/discovery service modes.
Definition status.h:30
bool ipv4_bound
Whether IPv4 socket is bound.
Definition status.h:36
char ipv4_address[256]
Formatted IPv4 bind address with port.
Definition status.h:32
bool ipv6_bound
Whether IPv6 socket is bound.
Definition status.h:37
uint16_t port
TCP listen port.
Definition status.h:34
char ipv6_address[256]
Formatted IPv6 bind address with port.
Definition status.h:33
char session_string[64]
Memorable session string (e.g., "happy-sunset-ocean")
Definition status.h:31
const char * mode_name
Mode name (e.g., "Server", "Discovery Service")
Definition status.h:40
time_t start_time
Server start time (for uptime calculation)
Definition status.h:39
bool session_is_mdns_only
Whether session is mDNS-only or ACDS.
Definition status.h:38
size_t connected_count
Number of connected clients/servers.
Definition status.h:35
Cross-platform system functions interface for ascii-chat.
🖥️ Cross-platform terminal interface for ascii-chat
void terminal_screen_log_destroy(void)
Standard log cleanup for terminal screens.
void terminal_screen_render(const terminal_screen_config_t *config)
Render a terminal screen with fixed header and scrolling logs.
void terminal_screen_log_clear(void)
Clear buffered logs for terminal screens.
session_log_buffer_t * terminal_screen_log_init(void)
Initialize session log buffer for terminal screens.
void terminal_screen_set_output_fd(int fd)
Set output file descriptor for terminal screens (splash/status)
Reusable "fixed header + scrolling logs" terminal screen abstraction.
⏱️ High-precision timing utilities using sokol_time.h and uthash
🔤 String Manipulation and Shell Escaping Utilities
int platform_isatty(int fd)
Check if a file descriptor is a terminal.
Definition util.c:63