ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
common/session/render.c
Go to the documentation of this file.
1
13#include "session/render.h"
14#include "session/capture.h"
15#include "session/display.h"
16#include "session/pipeline.h"
21#include <ascii-chat/common.h>
22#include <fcntl.h>
23#include <unistd.h>
24#include <ascii-chat/log/log.h>
35
36#include <stddef.h>
37#include <stdbool.h>
38#include <stdio.h>
39#include <inttypes.h>
40#ifndef _WIN32
41#include <unistd.h>
42#endif
43
44/* ============================================================================
45 * Unified Render Loop Implementation
46 * ============================================================================ */
47
48// Global flags for snapshot mode timing
50uint64_t g_snapshot_first_frame_rendered_ns = 0; // Timestamp when first frame was rendered (for terminal output)
51uint64_t g_snapshot_first_capture_ns = 0; // Timestamp when first frame was captured (for video output)
52uint64_t g_snapshot_actual_duration_ms = 0; // Actual elapsed time in ms when capture thread hits snapshot timeout
53uint64_t g_snapshot_last_capture_elapsed_ns = 0; // Elapsed time from first to last frame capture (for PTS distribution)
54
58 void *user_data) {
59 // Validate required parameters
60 log_info("[SESSION_RENDER_LOOP] START: capture=%p, display=%p, mode=%s", (void *)capture, (void *)display,
61 capture ? "SYNCHRONOUS" : "EVENT-DRIVEN");
62
63 if (!display) {
64 SET_ERRNO(ERROR_INVALID_PARAM, "session_render_loop: display context is NULL");
66 }
67
68 if (!should_exit) {
69 SET_ERRNO(ERROR_INVALID_PARAM, "session_render_loop: should_exit callback is NULL");
71 }
72
73 // Validate mode: either capture context OR custom callbacks, not both
74 if (!capture && !capture_cb) {
75 SET_ERRNO(ERROR_INVALID_PARAM, "session_render_loop: must provide either capture context or capture callback");
77 }
78
79 if (capture && capture_cb) {
80 SET_ERRNO(ERROR_INVALID_PARAM, "session_render_loop: cannot provide both capture context and capture callback");
82 }
83
84 // In event-driven mode, both capture_cb and sleep_cb must be provided together
85 if (capture_cb && !sleep_cb) {
86 SET_ERRNO(ERROR_INVALID_PARAM, "session_render_loop: capture_cb requires sleep_cb (must provide both or neither)");
88 }
89
90 if (sleep_cb && !capture_cb) {
91 SET_ERRNO(ERROR_INVALID_PARAM, "session_render_loop: sleep_cb requires capture_cb (must provide both or neither)");
93 }
94
95 // SYNCHRONOUS MODE: Use threaded pipeline for capture + render
96 if (capture != NULL) {
97 log_info("[SESSION_RENDER_LOOP_SYNCHRONOUS] Creating pipeline... (capture=%p, display=%p)", (void *)capture,
98 (void *)display);
99 session_pipeline_t *pipeline = NULL;
100 asciichat_error_t pipeline_err = session_pipeline_create(capture, display, &pipeline);
101 log_info("[SESSION_RENDER_LOOP_SYNCHRONOUS] pipeline_create returned: %d (pipeline=%p)", pipeline_err,
102 (void *)pipeline);
103 if (pipeline_err != ASCIICHAT_OK) {
104 log_error("[SESSION_RENDER_LOOP_SYNCHRONOUS] Pipeline creation failed: %d", pipeline_err);
105 return pipeline_err;
106 }
107 log_info("[SESSION_RENDER_LOOP_SYNCHRONOUS] Pipeline created successfully, running main loop...");
108
109 // Disable logging to stdout/stderr during rendering (same as event-driven mode).
110 // This prevents logs from mixing with ASCII art output. Logs still go to file.
112 asciichat_error_t run_err = session_pipeline_run_main(pipeline, should_exit, keyboard_handler, user_data);
114
115 // In snapshot mode, pass actual capture-based elapsed time to encoder for correct video duration
116 if (GET_OPTION(snapshot_mode) && display && g_snapshot_first_capture_ns > 0) {
117 uint64_t now_ns = time_get_ns();
118 uint64_t elapsed_ns = now_ns - g_snapshot_first_capture_ns;
119 double capture_elapsed_sec = (double)elapsed_ns / (double)NS_PER_SEC_INT;
120 log_info("SNAPSHOT: Pipeline finished - passing capture_elapsed=%.3f to encoder", capture_elapsed_sec);
121 session_display_set_snapshot_actual_duration(display, capture_elapsed_sec);
122 }
123
124 session_pipeline_destroy(pipeline);
125 return run_err;
126 } else {
127 log_warn("[SESSION_RENDER_LOOP] capture is NULL - will not use pipeline (capture_cb=%p)", (void *)capture_cb);
128 }
129
130 // Snapshot mode state tracking
131 bool snapshot_mode = GET_OPTION(snapshot_mode);
132 bool snapshot_done = false;
133 uint64_t frames_rendered_since_first = 0; // Track frames rendered after first frame (for snapshot delay)
134 // NOTE: g_snapshot_first_frame_rendered is set by display.c when first frame is rendered via platform_write_all()
135
136 // Help screen state tracking for clear-screen transition
137 bool help_was_active = false;
138
139 // Terminal resize tracking (for auto_width/auto_height mode)
140 unsigned short int last_terminal_width = terminal_get_effective_width();
141 unsigned short int last_terminal_height = terminal_get_effective_height();
142
143 log_info("session_render_loop: STARTING - display=%p capture=%p capture_cb=%p snapshot_mode=%s snapshot_delay=%.2f",
144 (void *)display, (void *)capture, (void *)capture_cb, snapshot_mode ? "YES" : "NO",
145 snapshot_mode ? GET_OPTION(snapshot_delay) : 0.0);
146 log_info("session_render_loop: About to enter main loop, should_exit fn check...");
147 if (should_exit(user_data)) {
148 log_warn("session_render_loop: EXIT signal already set at entry, aborting render loop");
149 return ASCIICHAT_OK;
150 }
151 log_info("session_render_loop: OK to proceed with main loop");
152
153 // Keyboard input initialization (if keyboard handler is provided)
154 // Disable keyboard only in snapshot mode
155 // Keyboard is pre-initialized from asciichat_shared_init()
156 // Allow keyboard input in all other modes if handler provided
157 bool keyboard_enabled = keyboard_handler && !snapshot_mode;
158
159 // Pause mode state tracking (for pause/unpause transitions in event-driven mode)
160 bool initial_paused_frame_rendered = false;
161 bool is_paused = false;
162 log_info("render_loop: Keyboard setup - handler=%p snapshot=%s enabled=%s", (void *)keyboard_handler,
163 snapshot_mode ? "YES" : "NO", keyboard_enabled ? "YES" : "NO");
164
165 // Log TTY detection status for debugging keyboard issues
166 log_info("TTY Status: stdin_tty=%d stdout_tty=%d interactive=%d keyboard_enabled=%d", terminal_is_stdin_tty(),
167 terminal_is_stdout_tty(), terminal_is_interactive(), keyboard_enabled);
168
169 // Frame rate timing (event-driven mode only)
170 uint64_t frame_count = 0;
171 uint64_t frame_start_ns = 0;
172
173 // Event-driven mode: disable console logging during rendering to prevent logs from corrupting frame display
174 // This must be done BEFORE the render loop to avoid repeated lock/unlock cycles that could cause deadlocks
175 log_info("[EVENT_DRIVEN_MODE] Entering render loop. snapshot_mode=%s", snapshot_mode ? "YES" : "NO");
177
178 // Main render loop
179 log_debug("session_render_loop: entering main loop");
180 int loop_iteration = 0;
181 log_info("[RENDER_LOOP_START] snapshot_mode=%s, snapshot_delay=%.2f", snapshot_mode ? "YES" : "NO",
182 snapshot_mode ? GET_OPTION(snapshot_delay) : 0.0);
183
184 // Wait for splash animation to complete before rendering first frame
185 // This prevents ASCII art from mixing with splash screen output
186 // But skip for immediate snapshots (snapshot-delay=0) which need to output first frame immediately
187 static bool splash_wait_done = false;
188 if (!splash_wait_done) {
189 splash_wait_done = true;
190 if (!(GET_OPTION(snapshot_mode) && GET_OPTION(snapshot_delay) == 0.0)) {
191 // Re-enable logging briefly for splash wait, then disable again
195 }
196 }
197
198 // In snapshot mode, we must render at least one frame before checking should_exit
199 // to ensure the snapshot delay timer can start
200 bool force_first_iteration = snapshot_mode && frame_count == 0;
201
202 // Register display context globally so signal handlers (Ctrl+C) can access it
203 // This enables Ctrl+C to close the help screen instead of quitting when help is active
205
206 while (force_first_iteration || (!should_exit(user_data) && !(snapshot_mode && snapshot_done))) {
207 force_first_iteration = false;
208 loop_iteration++;
209 log_info("[LOOP_ITER] iteration=%d, snapshot_done=%s, frame_count=%lu", loop_iteration,
210 snapshot_done ? "YES" : "NO", frame_count);
211 if (loop_iteration % 60 == 0) {
212 log_debug("session_render_loop: iteration %d, should_exit check returning false", loop_iteration);
213 }
214 // Snapshot mode: exit at start of iteration if done
215 // This prevents frame 2+ from being captured when snapshot_delay has elapsed
216 if (snapshot_mode && snapshot_done && frame_count > 0) {
217 log_info("[SNAPSHOT_EXIT] Snapshot mode: exiting at loop iteration start (snapshot_done=true, frames=%lu)",
218 frame_count);
219 break;
220 }
221
222 log_debug_every(US_PER_SEC_INT, "session_render_loop: frame %lu", frame_count);
223 // Frame timing - measure total time to maintain target FPS
224 frame_start_ns = time_get_ns();
225
226 // Frame capture and timing - mode-dependent
227 image_t *image = NULL;
228 uint64_t capture_start_ns = 0;
229 uint64_t capture_end_ns = 0;
230 uint64_t pre_convert_ns = 0;
231 uint64_t post_convert_ns = 0;
232 uint64_t conversion_elapsed_ns = 0;
233
234 // EVENT-DRIVEN MODE: Frames come from callbacks
235 // Both sleep_cb and capture_cb are guaranteed non-NULL by validation above
236 sleep_cb(user_data);
237 image = capture_cb(user_data);
238
239 if (!image) {
240 // No frame available - this is normal in async modes (network latency, etc.)
241 // Just continue to next iteration, don't exit
242 continue;
243 }
244
245 // Event-driven mode: increment frame count
246 frame_count++;
247
248 // Check for terminal resize (if auto_width or auto_height is enabled)
249 // This allows the render to adapt immediately when the user resizes the terminal
252 if (auto_width || auto_height) {
253 unsigned short int current_width = 0;
254 unsigned short int current_height = 0;
255
256 asciichat_error_t size_err = get_terminal_size(&current_width, &current_height);
257 if (size_err == ASCIICHAT_OK) {
258 bool width_changed = auto_width && (current_width != last_terminal_width);
259 bool height_changed = auto_height && (current_height != last_terminal_height);
260
261 if (width_changed || height_changed) {
262 if (width_changed) {
263 options_set_int("width", current_width);
264 log_info("Terminal width changed: %u → %u", last_terminal_width, current_width);
265 last_terminal_width = current_width;
266 }
267 if (height_changed) {
268 options_set_int("height", current_height);
269 log_info("Terminal height changed: %u → %u", last_terminal_height, current_height);
270 last_terminal_height = current_height;
271 }
272 // Clear screen when terminal is resized to avoid visual artifacts
274 }
275 }
276 }
277
278 // Convert image to ASCII using display context
279 // Handles all palette, terminal caps, width, height, stretch settings
280 pre_convert_ns = time_get_ns();
281 char *ascii_frame = session_display_convert_to_ascii(display, image);
282 post_convert_ns = time_get_ns();
283 conversion_elapsed_ns = post_convert_ns - pre_convert_ns;
284
285 if (frame_count <= 3) {
286 log_info("RENDER_LOOP[%lu]: image=%p, ascii_frame=%p (conversion took %u ms)", frame_count, (void *)image,
287 (void *)ascii_frame, (unsigned)(conversion_elapsed_ns / NS_PER_MS_INT));
288 }
289
290 // Declare variables that need scope beyond the if (ascii_frame) block
291 bool is_paused_frame = initial_paused_frame_rendered && is_paused;
292 bool output_paused_frame = snapshot_mode && is_paused_frame;
293 uint64_t pre_render_ns = 0, post_render_ns = 0;
294
295 if (ascii_frame) {
296 log_info_every(1 * NS_PER_SEC_INT, "render_loop: ascii_frame ready (len=%zu)", strlen(ascii_frame));
297
298 // Always attempt to render frames; the display context will handle filtering based on:
299 // - TTY mode: render all frames with cursor control (even in snapshot mode, for animation)
300 // - Piped mode: render all frames without cursor control (for continuous capture)
301 // - Snapshot mode on non-TTY: only the display context renders the final frame
302 // BUT: don't render if splash screen is still animating - wait for it to finish
303 bool splash_running = splash_is_running();
304 bool should_write = !splash_running;
305 if (frame_count == 1) {
306 log_dev("[RENDER_FRAME] Frame 1: splash_is_running=%s, should_write=%s", splash_running ? "YES" : "NO",
307 should_write ? "YES" : "NO");
308 }
309 if (should_write) {
310 // is_final = true when: snapshot done, or paused frame (for both snapshot and pause modes)
311 // Profile: render frame
312 pre_render_ns = time_get_ns();
313 START_TIMER("render_frame");
314
315 if (frame_count <= 5 || frame_count % 10 == 0) {
316 log_info("RENDER_LOOP: Frame %lu - calling session_display_render_frame, display=%p",
317 (unsigned long)frame_count, (void *)display);
318 }
319
320 // Check if SIGINT handler cancelled help mode (Ctrl+C while help is open).
321 // The signal handler sets an atomic flag instead of touching state directly.
322 if (display && keyboard_help_check_signal_cancel()) {
323 keyboard_help_toggle(display);
324 log_debug("Ctrl+C cancelled help screen - returning to ASCII art");
325 }
326
327 // Check if help screen is active - if so, render help instead of frame
328 // Help screen is disabled in snapshot mode and non-interactive terminals (keyboard disabled)
329 bool help_is_active = display && keyboard_help_is_active(display);
330
331 // Detect transition from help to ASCII art rendering
332 // When help closes, clear the screen before rendering ASCII art
333 if (help_was_active && !help_is_active) {
335 log_debug_every(1 * NS_PER_SEC_INT, "Cleared screen when transitioning from help to ASCII art");
336 }
337
338 if (help_is_active) {
339 keyboard_help_render(display);
340 } else {
341 session_display_render_frame(display, ascii_frame);
342 }
343
344 // Snapshot mode: increment frame counter for all displayed frames
345 // Counter starts at 1 for the first displayed frame, then 2, 3, 4... for subsequent frames
346 // Timer is started by display.c when g_snapshot_first_frame_rendered is set
347 if (snapshot_mode && g_snapshot_first_frame_rendered) {
348 frames_rendered_since_first++;
349 log_info_every(1 * NS_PER_SEC_INT, "[SNAPSHOT] Frame rendered: frames_rendered_since_first=%lu",
350 frames_rendered_since_first);
351 }
352
353 // Update help state for next iteration
354 help_was_active = help_is_active;
355
356 STOP_TIMER("render_frame");
357 }
358
359 // Keyboard input polling (if enabled) - MUST come before snapshot exit check so help screen can be toggled
360 // Allow keyboard in snapshot mode too (for help screen toggle debugging)
361 // Only enable keyboard if BOTH stdin AND stdout are TTYs to avoid buffering issues
362 // when tcsetattr() modifies the tty line discipline
363 if (keyboard_enabled && keyboard_handler) {
364 START_TIMER("keyboard_read_%lu", (unsigned long)frame_count);
366 double keyboard_elapsed_ns = STOP_TIMER("keyboard_read_%lu", (unsigned long)frame_count);
367 if (keyboard_elapsed_ns >= 0.0) {
368 char _duration_str[32];
369 time_pretty((uint64_t)keyboard_elapsed_ns, -1, _duration_str, sizeof(_duration_str));
370 log_dev_every(1 * NS_PER_SEC_INT, "RENDER[%lu] Keyboard read complete (key=%d) in %s",
371 (unsigned long)frame_count, key, _duration_str);
372 }
373 if (key != KEY_NONE) {
374 log_debug("KEYBOARD: Key pressed: code=%d char='%c'", key, (key >= 32 && key < 127) ? key : '?');
375 // Check if interactive grep should handle this key
376 if (log_search_should_handle(key)) {
377 log_debug("KEYBOARD: Grep handler taking key %d", key);
379 continue; // Force immediate re-render
380 }
381
382 // Normal keyboard handler
383 log_debug("KEYBOARD: Normal handler taking key %d", key);
384 keyboard_handler(capture, key, user_data);
385 }
386 }
387
388 // Free frame before checking exit conditions to avoid double-free
389 SAFE_FREE(ascii_frame);
390 }
391
392 // Snapshot mode: check if elapsed time has reached snapshot_delay duration (terminal render timing)
393 // Separate from capture timing - both should measure their own snapshot_delay
394 if (snapshot_mode && !snapshot_done) {
396 double snapshot_delay = GET_OPTION(snapshot_delay);
397 uint64_t now_ns = time_get_ns();
398 uint64_t elapsed_ns = now_ns - g_snapshot_first_frame_rendered_ns;
399 double elapsed_sec = (double)elapsed_ns / (double)NS_PER_SEC_INT;
400
401 if (frames_rendered_since_first == 1 || frames_rendered_since_first % 20 == 0) {
402 log_info("SNAPSHOT: RENDER_CHECK iter=%lu elapsed=%.3f target=%.2f (from first_frame_rendered)",
403 frames_rendered_since_first, elapsed_sec, snapshot_delay);
404 }
405
406 // snapshot_delay=0 means exit after first frame
407 // snapshot_delay>0 means wait that many seconds before exiting
408 // Exit is based on elapsed wall-clock time, not frame count
409 bool should_exit = (snapshot_delay == 0.0) || (elapsed_sec >= snapshot_delay);
410
411 if (should_exit) {
412 log_info("SNAPSHOT: RENDER_DONE at iteration %lu - elapsed=%.3f target=%.2f (setting snapshot_done=true)",
413 frames_rendered_since_first, elapsed_sec, snapshot_delay);
414 // We don't end frames with newlines so the next log would print on the same line as the frame's
415 // last row without an \n here. We only need this \n in stdout when interactive,
416 // so piped snapshots don't have a weird newline in stdout that they don't need.
418 printf("\n");
419 }
420 snapshot_done = true;
421 }
422 } else {
423 if (frames_rendered_since_first == 0) {
424 log_debug("SNAPSHOT: Waiting for first_frame_rendered (g_snapshot_first_frame_rendered=%d, "
425 "g_snapshot_first_frame_rendered_ns=%llu)",
427 }
428 }
429 }
430
431 // Exit conditions: snapshot mode exits after capturing the final frame or initial paused frame
432 if (snapshot_mode && (snapshot_done || output_paused_frame)) {
433 log_info("SNAPSHOT: EXIT CONDITION MET - snapshot_done=%d, output_paused_frame=%d", snapshot_done,
434 output_paused_frame);
435
436 // Calculate elapsed time from first frame captured (for video file)
437 // This is separate from render time - video should span from first capture to last capture
438 double capture_elapsed_sec = 0.0;
440 uint64_t now_ns = time_get_ns();
441 uint64_t elapsed_ns = now_ns - g_snapshot_first_capture_ns;
442 capture_elapsed_sec = (double)elapsed_ns / (double)NS_PER_SEC_INT;
443 log_info("SNAPSHOT: EXIT - Capture elapsed: %.3f seconds, render elapsed: %.3f seconds, "
444 "g_snapshot_first_capture_ns=%llu",
445 capture_elapsed_sec,
448 : 0.0,
449 (unsigned long long)g_snapshot_first_capture_ns);
450 } else {
451 log_warn("SNAPSHOT: EXIT condition met but g_snapshot_first_capture_ns is 0!");
452 }
453
454 // Pass actual capture elapsed time to encoder for correct video duration
455 log_info("SNAPSHOT: About to call session_display_set_snapshot_actual_duration with duration=%.3f, display=%p",
456 capture_elapsed_sec, (void *)display);
457 if (display) {
458 session_display_set_snapshot_actual_duration(display, capture_elapsed_sec);
459 } else {
460 log_warn("SNAPSHOT: display context is NULL!");
461 }
462
463 // Signal application to exit in snapshot mode
465 break;
466 }
467
468 // Frame rendering and timing details (moved down since snapshot exit is now above it)
469 if (ascii_frame) {
470
471 // Measure frame completion right after rendering, BEFORE keyboard polling
472 // This gives us accurate timing for just the core frame operations
473 uint64_t frame_end_render_ns = time_get_ns();
474
475 // Calculate each phase duration
476 uint64_t prestart_ms =
477 (capture_start_ns > frame_start_ns) ? (capture_start_ns - frame_start_ns) / NS_PER_MS_INT : 0;
478 uint64_t capture_ms =
479 (capture_end_ns > capture_start_ns) ? (capture_end_ns - capture_start_ns) / NS_PER_MS_INT : 0;
480 uint64_t convert_ms = conversion_elapsed_ns / NS_PER_MS_INT;
481 uint64_t render_ms =
482 (post_render_ns > pre_render_ns && post_render_ns > 0) ? (post_render_ns - pre_render_ns) / NS_PER_MS_INT : 0;
483 uint64_t total_ms =
484 (frame_end_render_ns > frame_start_ns) ? (frame_end_render_ns - frame_start_ns) / NS_PER_MS_INT : 0;
485
486 // Log phase breakdown every 5 frames
487 if (frame_count % 5 == 0) {
489 2 * NS_PER_SEC_INT,
490 "PHASE_BREAKDOWN[%lu]: prestart=%llu ms, capture=%llu ms, convert=%llu ms, render=%llu ms (total=%llu ms)",
491 frame_count, prestart_ms, capture_ms, convert_ms, render_ms, total_ms);
492 }
493 } else {
494 // Snapshot mode: even if frame conversion failed, check if we should exit
495 // This ensures snapshot_delay is honored even if display context isn't rendering
496 if (snapshot_mode && snapshot_done) {
497 break;
498 }
499 }
500
501 // In event-driven mode, frame rate limiting is handled by the sleep_cb callback
502 // Synchronous mode (with capture context) uses the pipeline and returns early above
503
504 // Note: Images returned by media sources are cached/reused and should NOT be destroyed
505 // The image pointers are managed by the source and will be cleaned up on source shutdown
506 } // while (!should_exit(user_data)) {
507
508 // Re-enable console logging after rendering completes
510 if (!snapshot_mode && terminal_is_interactive()) {
511 printf("\n");
512 }
513
514 // Keyboard input cleanup (if it was initialized)
515 if (keyboard_enabled) {
517 log_debug_every(2 * NS_PER_SEC_INT, "Keyboard input disabled");
518 }
519
520 // Unregister global display context (for signal handlers)
522
523 return ASCIICHAT_OK;
524}
Platform abstraction layer umbrella header providing unified cross-platform API.
Application-level callbacks for library code.
#define APP_CALLBACK_VOID(callback_name)
⚠️‼️ Comprehensive thread-local error context system for ascii-chat
📹 Unified media capture abstraction for session-based video sources
uint64_t g_snapshot_last_capture_elapsed_ns
bool g_snapshot_first_frame_rendered
Snapshot mode global timing variables Used to track actual duration when snapshot mode is active.
uint64_t g_snapshot_first_frame_rendered_ns
uint64_t g_snapshot_first_capture_ns
uint64_t g_snapshot_actual_duration_ms
Centralized render loop abstraction for all display modes.
⚙️ Common definitions, error codes, macros, and types shared throughout the application
🎞️ FFmpeg-based media decoder for video and audio streams
#define SAFE_FREE(ptr)
Definition common.h:376
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
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
#define log_dev(...)
Log a DEV message (most verbose, development only)
Definition log/log.h:534
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
void log_set_terminal_output(bool enabled)
Control stderr output to terminal.
Definition log/log.c:700
uint64_t time_get_ns(void)
Get current monotonic time in nanoseconds.
Definition util/time.c:108
#define NS_PER_SEC_INT
Definition time.h:157
int time_pretty(uint64_t nanoseconds, int decimals, char *buffer, size_t buffer_size)
Format nanoseconds as pretty duration with spaces and configurable precision.
Definition util/time.c:424
#define START_TIMER(name_fmt,...)
Start a timer with formatted name.
Definition time.h:333
#define NS_PER_MS_INT
Definition time.h:156
#define STOP_TIMER(name_fmt,...)
Stop a timer with formatted name and return elapsed time.
Definition time.h:403
#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 options_set_int(const char *field_name, int value)
Check if an option was explicitly set via command-line.
Definition rcu.c:761
keyboard_key_t keyboard_read_nonblocking(void)
Read next keyboard input without blocking.
asciichat_error_t terminal_clear_screen(void)
Clear the terminal screen.
void keyboard_destroy(void)
Cleanup keyboard input system and restore terminal.
keyboard_key_t
Unified keyboard key code enumeration.
Definition keyboard.h:54
@ KEY_NONE
No key pressed or no input available.
Definition keyboard.h:55
void keyboard_help_toggle(session_display_ctx_t *ctx)
Toggle keyboard help on/off (implemented in display.c for struct access)
bool(* session_should_exit_fn)(void *user_data)
Exit condition callback type.
void(* session_keyboard_handler_fn)(session_capture_ctx_t *capture, int key, void *user_data)
Keyboard input handler callback type.
bool splash_is_running(void)
Check if splash screen animation is currently running.
Definition splash.c:725
void(* session_sleep_for_frame_fn)(void *user_data)
Sleep callback for custom frame timing.
bool keyboard_help_is_active(session_display_ctx_t *ctx)
Check if keyboard help is currently active.
image_t *(* session_capture_fn)(void *user_data)
Capture callback for event-driven frame source.
void session_display_set_snapshot_actual_duration(session_display_ctx_t *ctx, double actual_duration_sec)
Set the actual wall-clock duration for snapshot mode frame timing.
void keyboard_help_render(session_display_ctx_t *ctx)
Render keyboard help centered on terminal.
asciichat_error_t session_render_loop(session_capture_ctx_t *capture, session_display_ctx_t *display, session_should_exit_fn should_exit, session_capture_fn capture_cb, session_sleep_for_frame_fn sleep_cb, session_keyboard_handler_fn keyboard_handler, void *user_data)
Unified render loop for all display modes.
void session_display_render_frame(session_display_ctx_t *ctx, const char *frame_data)
Render an ASCII frame to the terminal.
void session_display_set_global_context(session_display_ctx_t *ctx)
Set the global display context (for signal handlers like Ctrl+C)
bool keyboard_help_check_signal_cancel(void)
Check if help screen closure was requested by signal handler.
void splash_wait_for_animation(void)
Wait for splash animation thread to fully exit.
Definition splash.c:731
char * session_display_convert_to_ascii(session_display_ctx_t *ctx, const image_t *image)
Convert an image to ASCII art using display context and command-line options.
🔊 Audio Capture and Playback Interface for ascii-chat
⚙️ Unified options parsing system for ascii-chat with builder pattern and lock-free access
🎮 Cross-platform keyboard input interface for ascii-chat
🆘 Interactive keyboard help overlay for session keyboard shortcuts
📝 Logging API with multiple log levels and terminal output control
#define log_info_every(interval_us, fmt,...)
Rate-limited INFO logging.
Definition log/log.h:705
#define log_debug_every(interval_us, fmt,...)
Rate-limited DEBUG logging.
Definition log/log.h:702
#define log_dev_every(interval_us, fmt,...)
Rate-limited DEV logging.
Definition log/log.h:699
void signal_exit(void)
Definition misc.c:129
bool should_exit(void)
Definition misc.c:131
bool auto_height
bool auto_width
asciichat_error_t session_pipeline_run_main(session_pipeline_t *pipeline, session_should_exit_fn should_exit, session_keyboard_handler_fn keyboard_handler, void *user_data)
Definition pipeline.c:474
asciichat_error_t session_pipeline_create(session_capture_ctx_t *capture, session_display_ctx_t *display, session_pipeline_t **out)
Definition pipeline.c:420
asciichat_error_t session_pipeline_destroy(session_pipeline_t *pipeline)
Definition pipeline.c:557
Three-thread render pipeline: capture → display/encode.
unsigned short int terminal_get_effective_width(void)
Get effective terminal width with fallback priority.
bool terminal_is_interactive(void)
Check if the session is fully interactive.
bool terminal_is_stdin_tty(void)
Check if stdin is connected to a TTY.
bool terminal_is_stdout_tty(void)
Check if stdout is connected to a TTY.
unsigned short int terminal_get_effective_height(void)
Get effective terminal height with fallback priority.
asciichat_error_t get_terminal_size(unsigned short int *width, unsigned short int *height)
Get terminal size with multiple fallback methods.
Read ANSI ASCII frames from file descriptor, chunked by height.
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.
🎬 Unified media source abstraction for webcam, files, and stdin
Intro and status screen display for ascii-chat with animated rainbow effects.
🖥️ Unified terminal display abstraction for session-based rendering
ClangTool/LibTooling compatibility shim for stdbool.h.
Image structure.
Internal session capture context structure.
Internal session display context structure.
🖥️ Cross-platform terminal interface for ascii-chat
⏱️ High-precision timing utilities using sokol_time.h and uthash