ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
display.c File Reference

🖥️ Unified terminal display implementation More...

Go to the source code of this file.

Data Structures

struct  session_display_ctx
 Internal session display context structure. More...
 

Typedefs

typedef struct session_display_ctx session_display_ctx_t
 Internal session display context structure.
 

Functions

session_display_ctx_tsession_display_create (const session_display_config_t *config)
 
void session_display_destroy (session_display_ctx_t *ctx)
 
bool session_display_has_tty (session_display_ctx_t *ctx)
 
const terminal_capabilities_t * session_display_get_caps (session_display_ctx_t *ctx)
 
const char * session_display_get_palette_chars (session_display_ctx_t *ctx)
 
size_t session_display_get_palette_len (session_display_ctx_t *ctx)
 
const char * session_display_get_luminance_palette (session_display_ctx_t *ctx)
 
int session_display_get_tty_fd (session_display_ctx_t *ctx)
 
char * session_display_convert_to_ascii (session_display_ctx_t *ctx, const image_t *image)
 
void session_display_render_frame (session_display_ctx_t *ctx, const char *frame_data)
 
void session_display_write_raw (session_display_ctx_t *ctx, const char *data, size_t len)
 
void session_display_reset (session_display_ctx_t *ctx)
 
void session_display_clear (session_display_ctx_t *ctx)
 
void session_display_cursor_home (session_display_ctx_t *ctx)
 
void session_display_set_cursor_visible (session_display_ctx_t *ctx, bool visible)
 
bool session_display_has_audio_playback (session_display_ctx_t *ctx)
 
asciichat_error_t session_display_write_audio (session_display_ctx_t *ctx, const float *buffer, size_t num_samples)
 
void session_display_toggle_help (session_display_ctx_t *ctx)
 Toggle help screen on/off (implemented in display.c for struct access)
 
bool session_display_is_help_active (session_display_ctx_t *ctx)
 Check if help screen is currently active (implemented in display.c for struct access)
 

Detailed Description

🖥️ Unified terminal display implementation

Implements the session display abstraction layer for unified terminal rendering across client, mirror, and discovery modes.

Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
January 2026

Definition in file lib/session/display.c.

Typedef Documentation

◆ session_display_ctx_t

Internal session display context structure.

Contains all state for terminal display including TTY info, capabilities, palette, and rendering state.

Function Documentation

◆ session_display_clear()

void session_display_clear ( session_display_ctx_t ctx)

Definition at line 671 of file lib/session/display.c.

671 {
672 if (!ctx || !ctx->initialized) {
673 SET_ERRNO(ERROR_INVALID_PARAM, "Session display context is NULL or uninitialized");
674 return;
675 }
676
677 // Skip terminal clear in snapshot mode to preserve rendered output
678 if (ctx->snapshot_mode) {
679 return;
680 }
681
682 // Only perform terminal operations when we have a valid TTY (not when piping)
683 if (ctx->has_tty && ctx->tty_info.fd >= 0) {
684 (void)terminal_clear_screen();
685 (void)terminal_cursor_home(ctx->tty_info.fd);
686 }
687}
bool has_tty
True if we have a valid TTY for interactive output.
tty_info_t tty_info
TTY information (file descriptor, path, ownership)
bool initialized
Context is fully initialized.
bool snapshot_mode
Snapshot mode enabled.

References session_display_ctx::has_tty, session_display_ctx::initialized, session_display_ctx::snapshot_mode, and session_display_ctx::tty_info.

◆ session_display_convert_to_ascii()

char * session_display_convert_to_ascii ( session_display_ctx_t ctx,
const image_t *  image 
)

Definition at line 337 of file lib/session/display.c.

337 {
338 if (!ctx) {
339 SET_ERRNO(ERROR_INVALID_PARAM, "session_display_convert_to_ascii: ctx is NULL");
340 return NULL;
341 }
342
343 if (!ctx->initialized) {
344 SET_ERRNO(ERROR_INVALID_STATE, "session_display_convert_to_ascii: ctx not initialized");
345 return NULL;
346 }
347
348 if (!image) {
349 SET_ERRNO(ERROR_INVALID_PARAM, "session_display_convert_to_ascii: image is NULL");
350 return NULL;
351 }
352
353 // Get conversion parameters from command-line options
354 unsigned short int width = GET_OPTION(width);
355 unsigned short int height = GET_OPTION(height);
356 bool stretch = GET_OPTION(stretch);
357 bool preserve_aspect_ratio = !stretch;
358
359 // Determine if we should apply flip_x (with macOS webcam caveat)
360 bool flip_x_enabled = GET_OPTION(flip_x);
361 bool flip_y_enabled = GET_OPTION(flip_y);
362
363 // On macOS, webcam respects platform default but ignores the flip_x flag
364 // (like FaceTime - always flipped by default regardless of user preference)
365#ifdef __APPLE__
366 // Check if this frame is from a webcam source
367 // For now, we apply the same default but ignore user toggle
368 // TODO: Add source tracking to know if frame is from webcam
369 flip_x_enabled = false; // Ignore flip_x for now on macOS (will be enhanced with source tracking)
370#endif
371
372 color_filter_t color_filter = GET_OPTION(color_filter);
373
374 // Make a mutable copy of terminal capabilities for ascii_convert_with_capabilities
375 terminal_capabilities_t caps_copy = ctx->caps;
376
377 // MEASURE EVERY OPERATION - Debug systematic timing
378 uint64_t t_flip_start = time_get_ns();
379
380 // Apply horizontal and/or vertical flips if requested
381 image_t *flipped_image = NULL;
382 const image_t *display_image = image;
383
384 if ((flip_x_enabled || flip_y_enabled) && image->w > 1 && image->h > 1 && image->pixels) {
385 START_TIMER("image_flip");
386 uint64_t t_flip_alloc_start = time_get_ns();
387 flipped_image = image_new((size_t)image->w, (size_t)image->h);
388 uint64_t t_flip_alloc_end = time_get_ns();
389
390 if (flipped_image) {
391 uint64_t t_flip_memcpy_start = time_get_ns();
392 // OPTIMIZATION: Copy entire image first (sequential memory access - cache-friendly)
393 memcpy(flipped_image->pixels, image->pixels, (size_t)image->w * (size_t)image->h * sizeof(rgb_pixel_t));
394 uint64_t t_flip_memcpy_end = time_get_ns();
395
396 uint64_t t_flip_reverse_start = time_get_ns();
397
398 // Apply horizontal flip (X-axis)
399 if (flip_x_enabled) {
400#if SIMD_SUPPORT_NEON
401 // Use NEON-accelerated flip on ARM processors
402 image_flip_horizontal_neon(flipped_image);
403#else
404 // Scalar fallback for non-NEON systems
405 for (int y = 0; y < image->h; y++) {
406 rgb_pixel_t *row = &flipped_image->pixels[y * image->w];
407 for (int x = 0; x < image->w / 2; x++) {
408 rgb_pixel_t temp = row[x];
409 row[x] = row[image->w - 1 - x];
410 row[image->w - 1 - x] = temp;
411 }
412 }
413#endif
414 }
415
416 // Apply vertical flip (Y-axis)
417 if (flip_y_enabled) {
418 for (int y = 0; y < image->h / 2; y++) {
419 rgb_pixel_t *top_row = &flipped_image->pixels[y * image->w];
420 rgb_pixel_t *bottom_row = &flipped_image->pixels[(image->h - 1 - y) * image->w];
421 for (int x = 0; x < image->w; x++) {
422 rgb_pixel_t temp = top_row[x];
423 top_row[x] = bottom_row[x];
424 bottom_row[x] = temp;
425 }
426 }
427 }
428
429 uint64_t t_flip_reverse_end = time_get_ns();
430 display_image = flipped_image;
431
432 log_dev("TIMING_FLIP: alloc=%llu us, memcpy=%llu us, flip=%llu us (x=%d, y=%d)",
433 (t_flip_alloc_end - t_flip_alloc_start) / 1000, (t_flip_memcpy_end - t_flip_memcpy_start) / 1000,
434 (t_flip_reverse_end - t_flip_reverse_start) / 1000, flip_x_enabled, flip_y_enabled);
435 }
436 STOP_TIMER_AND_LOG_EVERY(dev, 3 * NS_PER_SEC_INT, 3 * NS_PER_MS_INT, "image_flip",
437 "IMAGE_FLIP: Flip complete (%.2f ms)");
438 }
439 uint64_t t_flip_end = time_get_ns();
440
441 uint64_t t_filter_start = time_get_ns();
442 // No pixel-based filtering for rainbow (ANSI replacement happens later)
443 uint64_t t_filter_end = time_get_ns();
444
445 uint64_t t_convert_start = time_get_ns();
446 // Call the standard ASCII conversion using the display image (unfiltered)
447 // This ensures character selection is based on original image brightness
448 START_TIMER("ascii_convert_with_capabilities");
449 char *result = ascii_convert_with_capabilities(display_image, width, height, &caps_copy, preserve_aspect_ratio,
450 stretch, ctx->palette_chars);
451 STOP_TIMER_AND_LOG_EVERY(dev, 3 * NS_PER_SEC_INT, 5 * NS_PER_MS_INT, "ascii_convert_with_capabilities",
452 "ASCII_CONVERT: Conversion complete (%.2f ms)");
453 uint64_t t_convert_end = time_get_ns();
454
455 // Apply rainbow color filter to ANSI output by replacing RGB values
456 // This preserves character selection while applying the filter colors
457 if (result && color_filter == COLOR_FILTER_RAINBOW) {
458 uint64_t t_color_replace_start = time_get_ns();
459 float time_seconds = (float)t_filter_start / (float)NS_PER_SEC_INT;
460
461 char *rainbow_result = rainbow_replace_ansi_colors(result, time_seconds);
462 if (rainbow_result) {
463 SAFE_FREE(result);
464 result = rainbow_result;
465 }
466
467 uint64_t t_color_replace_end = time_get_ns();
468 log_dev("COLOR_REPLACE: %.2f ms", (double)(t_color_replace_end - t_color_replace_start) / (double)NS_PER_MS_INT);
469 }
470
471 // Apply digital rain effect if enabled
472 if (result && ctx->digital_rain) {
473 uint64_t t_rain_start = time_get_ns();
474 uint64_t current_time_ns = t_rain_start;
475 float delta_time = (float)(current_time_ns - ctx->last_frame_time_ns) / (float)NS_PER_SEC_INT;
476 ctx->last_frame_time_ns = current_time_ns;
477
478 char *rain_result = digital_rain_apply(ctx->digital_rain, result, delta_time);
479 if (rain_result) {
480 SAFE_FREE(result);
481 result = rain_result;
482 }
483
484 uint64_t t_rain_end = time_get_ns();
485 log_dev("DIGITAL_RAIN: Effect applied (%.2f ms)", (double)(t_rain_end - t_rain_start) / (double)NS_PER_MS_INT);
486 }
487
488 uint64_t t_cleanup_start = time_get_ns();
489 // Clean up flipped image if created
490 START_TIMER("ascii_convert_cleanup");
491 if (flipped_image) {
492 image_destroy(flipped_image);
493 }
494 STOP_TIMER_AND_LOG_EVERY(dev, 3 * NS_PER_SEC_INT, 2 * NS_PER_MS_INT, "ascii_convert_cleanup",
495 "ASCII_CONVERT_CLEANUP: Cleanup complete (%.2f ms)");
496 uint64_t t_cleanup_end = time_get_ns();
497
498 // Log total breakdown with actual measured times
499 log_dev("CONVERT_TIMING: flip=%llu us, filter=%llu us, convert=%llu us, cleanup=%llu us, TOTAL=%llu us",
500 (t_flip_end - t_flip_start) / 1000, (t_filter_end - t_filter_start) / 1000,
501 (t_convert_end - t_convert_start) / 1000, (t_cleanup_end - t_cleanup_start) / 1000,
502 (t_cleanup_end - t_flip_start) / 1000);
503
504 return result;
505}
char * ascii_convert_with_capabilities(image_t *original, const ssize_t width, const ssize_t height, const terminal_capabilities_t *caps, const bool use_aspect_ratio, const bool stretch, const char *palette_chars)
Definition ascii.c:190
char * rainbow_replace_ansi_colors(const char *ansi_string, float time_seconds)
char * digital_rain_apply(digital_rain_t *rain, const char *frame, float delta_time)
char palette_chars[256]
Palette character string for rendering.
digital_rain_t * digital_rain
Digital rain effect context (NULL if disabled)
uint64_t last_frame_time_ns
Last frame timestamp for digital rain delta time calculation.
terminal_capabilities_t caps
Detected terminal capabilities.
uint64_t time_get_ns(void)
Definition util/time.c:48
void image_destroy(image_t *p)
Definition video/image.c:85
image_t * image_new(size_t width, size_t height)
Definition video/image.c:36

References ascii_convert_with_capabilities(), session_display_ctx::caps, session_display_ctx::digital_rain, digital_rain_apply(), image_destroy(), image_new(), session_display_ctx::initialized, session_display_ctx::last_frame_time_ns, session_display_ctx::palette_chars, rainbow_replace_ansi_colors(), and time_get_ns().

Referenced by session_render_loop().

◆ session_display_create()

session_display_ctx_t * session_display_create ( const session_display_config_t *  config)

Definition at line 121 of file lib/session/display.c.

121 {
122 // Auto-create config from command-line options if NULL
123 session_display_config_t auto_config = {0};
124 if (!config) {
125 auto_config.snapshot_mode = GET_OPTION(snapshot_mode);
126 auto_config.palette_type = GET_OPTION(palette_type);
127 auto_config.custom_palette = GET_OPTION(palette_custom_set) ? GET_OPTION(palette_custom) : NULL;
128 auto_config.color_mode = TERM_COLOR_AUTO;
129 config = &auto_config;
130 }
131
132 // Check if we should exit before starting initialization
133 if (config->should_exit_callback && config->should_exit_callback(config->callback_data)) {
134 return NULL;
135 }
136
137 // Allocate context
138 session_display_ctx_t *ctx = SAFE_CALLOC(1, sizeof(session_display_ctx_t), session_display_ctx_t *);
139
140 // Store configuration
141 ctx->snapshot_mode = config->snapshot_mode;
142 ctx->palette_type = config->palette_type;
143 ctx->audio_playback_enabled = config->enable_audio_playback;
144 ctx->audio_ctx = config->audio_ctx;
145 atomic_init(&ctx->first_frame, true);
146 atomic_init(&ctx->help_screen_active, false);
147
148 // Get TTY info for direct terminal access
149 ctx->tty_info = get_current_tty();
150
151 // Determine if we have a valid TTY
152 // Check if stdout is a TTY, not just any fd (stdin/stderr could be TTY while stdout is piped).
153 // If stdout is piped/redirected, never perform terminal operations regardless of other fds.
154 if (ctx->tty_info.fd >= 0) {
155 ctx->has_tty = (platform_isatty(ctx->tty_info.fd) != 0) && terminal_is_stdout_tty();
156 }
157
158 // In piped mode, force all logs to stderr to prevent frame data corruption
161 // Set stdout to line buffering to ensure output is flushed at newlines
162 // This helps with snapshot mode where each frame ends with a newline
163 //(void)setvbuf(stdout, NULL, _IOLBF, 0);
164 }
165
166 // Detect terminal capabilities
168
169 // Set wants_padding based on snapshot mode and TTY status
170 // Disable padding when:
171 // - In snapshot mode (one frame and exit)
172 // - When stdout is not a TTY (piped/redirected output)
173 // Enable padding for interactive terminal sessions
174 bool is_snapshot_mode = config->snapshot_mode;
175 bool is_interactive = terminal_is_interactive();
176 ctx->caps.wants_padding = is_interactive && !is_snapshot_mode;
177
178 log_debug("Padding mode: wants_padding=%d (snapshot=%d, interactive=%d, stdin_tty=%d, stdout_tty=%d)",
179 ctx->caps.wants_padding, is_snapshot_mode, is_interactive, terminal_is_stdin_tty(),
181
182 // Apply color mode override if specified
183 if (config->color_mode != TERM_COLOR_AUTO) {
184 ctx->caps.color_level = config->color_mode;
185 }
186
187 // Apply command-line overrides
188 ctx->caps = apply_color_mode_override(ctx->caps);
189
190 // Initialize palette
191 int palette_result = initialize_client_palette(config->palette_type, config->custom_palette, ctx->palette_chars,
192 &ctx->palette_len, ctx->luminance_palette);
193 if (palette_result != ASCIICHAT_OK) {
194 log_warn("Failed to initialize palette, using default");
195 // Fall back to standard palette
196 (void)initialize_client_palette(PALETTE_STANDARD, NULL, ctx->palette_chars, &ctx->palette_len,
197 ctx->luminance_palette);
198 }
199
200 // Pre-warm UTF-8 palette cache during initialization (not during rendering)
201 // This is expensive on first use (creates lookup tables), so warm it up now to avoid frame lag
202 // This function is imported from lib/video/simd/common.h
203 extern utf8_palette_cache_t *get_utf8_palette_cache(const char *ascii_chars);
204 (void)get_utf8_palette_cache((const char *)ctx->palette_chars);
205 log_debug("UTF-8 palette cache pre-warmed during display initialization");
206
207 // Initialize ANSI fast lookup tables based on terminal capabilities
208 if (ctx->caps.color_level == TERM_COLOR_TRUECOLOR) {
210 } else if (ctx->caps.color_level == TERM_COLOR_256) {
212 } else if (ctx->caps.color_level == TERM_COLOR_16) {
214 }
215 // TERM_COLOR_MONO requires no init
216
217 // Initialize ASCII output system if we have a TTY
218 if (ctx->has_tty && ctx->tty_info.fd >= 0) {
219 ascii_write_init(ctx->tty_info.fd, false);
220 }
221
222 // Initialize digital rain effect if enabled
223 if (GET_OPTION(matrix_rain)) {
224 // Get terminal dimensions for grid size
225 unsigned short int width_us = (unsigned short int)GET_OPTION(width);
226 unsigned short int height_us = (unsigned short int)GET_OPTION(height);
227
228 // If dimensions are not set, detect from terminal
229 if (width_us == 0 || height_us == 0) {
230 (void)get_terminal_size(&width_us, &height_us);
231 }
232
233 int width = (int)width_us;
234 int height = (int)height_us;
235
236 ctx->digital_rain = digital_rain_init(width, height);
237 if (ctx->digital_rain) {
238 // Set color from color filter if active
239 color_filter_t filter = GET_OPTION(color_filter);
241 log_info("Digital rain effect enabled: %dx%d grid", width, height);
242 } else {
243 log_warn("Failed to initialize digital rain effect");
244 }
246 }
247
248 ctx->initialized = true;
249 return ctx;
250}
void ansi_fast_init_256color(void)
Definition ansi_fast.c:205
void ansi_fast_init(void)
Definition ansi_fast.c:198
void ansi_fast_init_16color(void)
Definition ansi_fast.c:257
asciichat_error_t ascii_write_init(int fd, bool reset_terminal)
Definition ascii.c:41
void digital_rain_set_color_from_filter(digital_rain_t *rain, color_filter_t filter)
digital_rain_t * digital_rain_init(int num_columns, int num_rows)
void log_set_force_stderr(bool enabled)
int initialize_client_palette(palette_type_t palette_type, const char *custom_chars, char client_palette_chars[256], size_t *client_palette_len, char client_luminance_palette[256])
Definition palette.c:299
bool terminal_is_interactive(void)
bool terminal_is_stdin_tty(void)
bool terminal_is_stdout_tty(void)
bool terminal_should_force_stderr(void)
terminal_capabilities_t detect_terminal_capabilities(void)
asciichat_error_t get_terminal_size(unsigned short int *width, unsigned short int *height)
Internal session display context structure.
atomic_bool first_frame
First frame flag for logging control.
atomic_bool help_screen_active
Help screen active flag (toggled with '?') - atomic for thread-safe access.
char luminance_palette[256]
Luminance-to-character mapping table (256 entries)
void * audio_ctx
Audio context for playback (borrowed, not owned)
bool audio_playback_enabled
Audio playback is enabled.
palette_type_t palette_type
Configured palette type.
size_t palette_len
Number of characters in palette.
int platform_isatty(int fd)
Definition util.c:61
utf8_palette_cache_t * get_utf8_palette_cache(const char *ascii_chars)

References ansi_fast_init(), ansi_fast_init_16color(), ansi_fast_init_256color(), ascii_write_init(), session_display_ctx::audio_ctx, session_display_ctx::audio_playback_enabled, session_display_ctx::caps, detect_terminal_capabilities(), session_display_ctx::digital_rain, digital_rain_init(), digital_rain_set_color_from_filter(), session_display_ctx::first_frame, get_terminal_size(), get_utf8_palette_cache(), session_display_ctx::has_tty, session_display_ctx::help_screen_active, initialize_client_palette(), session_display_ctx::initialized, session_display_ctx::last_frame_time_ns, log_set_force_stderr(), session_display_ctx::luminance_palette, session_display_ctx::palette_chars, session_display_ctx::palette_len, session_display_ctx::palette_type, platform_isatty(), session_display_ctx::snapshot_mode, terminal_is_interactive(), terminal_is_stdin_tty(), terminal_is_stdout_tty(), terminal_should_force_stderr(), time_get_ns(), and session_display_ctx::tty_info.

Referenced by display_init(), and session_client_like_run().

◆ session_display_cursor_home()

void session_display_cursor_home ( session_display_ctx_t ctx)

Definition at line 689 of file lib/session/display.c.

689 {
690 if (!ctx || !ctx->initialized) {
691 SET_ERRNO(ERROR_INVALID_PARAM, "Session display context is NULL or uninitialized");
692 return;
693 }
694
695 int fd = ctx->has_tty ? ctx->tty_info.fd : STDOUT_FILENO;
696 if (fd >= 0) {
697 (void)terminal_cursor_home(fd);
698 }
699}

References session_display_ctx::has_tty, session_display_ctx::initialized, and session_display_ctx::tty_info.

◆ session_display_destroy()

void session_display_destroy ( session_display_ctx_t ctx)

Definition at line 252 of file lib/session/display.c.

252 {
253 if (!ctx) {
254 SET_ERRNO(ERROR_INVALID_PARAM, "Session display context is NULL");
255 return;
256 }
257
258 // Cleanup ASCII rendering if we had a TTY
259 // Don't reset terminal in snapshot mode to preserve the rendered output
260 if (ctx->has_tty && ctx->tty_info.fd >= 0) {
262 }
263
264 // Close the controlling terminal if we opened it
265 if (ctx->tty_info.owns_fd && ctx->tty_info.fd >= 0) {
266 (void)platform_close(ctx->tty_info.fd);
267 ctx->tty_info.fd = -1;
268 ctx->tty_info.owns_fd = false;
269 }
270
271 // Cleanup digital rain effect
272 if (ctx->digital_rain) {
274 ctx->digital_rain = NULL;
275 }
276
277 ctx->initialized = false;
278 SAFE_FREE(ctx);
279}
void ascii_write_destroy(int fd, bool reset_terminal)
Definition ascii.c:355
void digital_rain_destroy(digital_rain_t *rain)
int platform_close(int fd)

References ascii_write_destroy(), session_display_ctx::digital_rain, digital_rain_destroy(), session_display_ctx::has_tty, session_display_ctx::initialized, platform_close(), session_display_ctx::snapshot_mode, and session_display_ctx::tty_info.

Referenced by session_client_like_run().

◆ session_display_get_caps()

const terminal_capabilities_t * session_display_get_caps ( session_display_ctx_t ctx)

Definition at line 293 of file lib/session/display.c.

293 {
294 if (!ctx || !ctx->initialized) {
295 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: ctx=%p", ctx);
296 return NULL;
297 }
298 return &ctx->caps;
299}

References session_display_ctx::caps, and session_display_ctx::initialized.

◆ session_display_get_luminance_palette()

const char * session_display_get_luminance_palette ( session_display_ctx_t ctx)

Definition at line 317 of file lib/session/display.c.

317 {
318 if (!ctx || !ctx->initialized) {
319 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: ctx=%p", ctx);
320 return NULL;
321 }
322 return ctx->luminance_palette;
323}

References session_display_ctx::initialized, and session_display_ctx::luminance_palette.

◆ session_display_get_palette_chars()

const char * session_display_get_palette_chars ( session_display_ctx_t ctx)

Definition at line 301 of file lib/session/display.c.

301 {
302 if (!ctx || !ctx->initialized) {
303 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: ctx=%p", ctx);
304 return NULL;
305 }
306 return ctx->palette_chars;
307}

References session_display_ctx::initialized, and session_display_ctx::palette_chars.

◆ session_display_get_palette_len()

size_t session_display_get_palette_len ( session_display_ctx_t ctx)

Definition at line 309 of file lib/session/display.c.

309 {
310 if (!ctx || !ctx->initialized) {
311 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: ctx=%p", ctx);
312 return 0;
313 }
314 return ctx->palette_len;
315}

References session_display_ctx::initialized, and session_display_ctx::palette_len.

◆ session_display_get_tty_fd()

int session_display_get_tty_fd ( session_display_ctx_t ctx)

Definition at line 325 of file lib/session/display.c.

325 {
326 if (!ctx || !ctx->initialized) {
327 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: ctx=%p", ctx);
328 return -1;
329 }
330 return ctx->tty_info.fd;
331}

References session_display_ctx::initialized, and session_display_ctx::tty_info.

Referenced by session_display_render_help().

◆ session_display_has_audio_playback()

bool session_display_has_audio_playback ( session_display_ctx_t ctx)

Definition at line 713 of file lib/session/display.c.

713 {
714 if (!ctx || !ctx->initialized) {
715 SET_ERRNO(ERROR_INVALID_PARAM, "Session display context is NULL or uninitialized");
716 return false;
717 }
718 return ctx->audio_playback_enabled;
719}

References session_display_ctx::audio_playback_enabled, and session_display_ctx::initialized.

◆ session_display_has_tty()

bool session_display_has_tty ( session_display_ctx_t ctx)

Definition at line 285 of file lib/session/display.c.

285 {
286 if (!ctx || !ctx->initialized) {
287 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: ctx=%p", ctx);
288 return false;
289 }
290 return ctx->has_tty;
291}

References session_display_ctx::has_tty, and session_display_ctx::initialized.

Referenced by display_has_tty(), and session_display_render_help().

◆ session_display_is_help_active()

bool session_display_is_help_active ( session_display_ctx_t ctx)

Check if help screen is currently active (implemented in display.c for struct access)

Definition at line 792 of file lib/session/display.c.

792 {
793 if (!ctx) {
794 SET_ERRNO(ERROR_INVALID_PARAM, "Session display context is NULL");
795 return false;
796 }
797
798 return atomic_load(&ctx->help_screen_active);
799}

References session_display_ctx::help_screen_active.

Referenced by display_render_frame(), session_handle_keyboard_input(), and session_render_loop().

◆ session_display_render_frame()

void session_display_render_frame ( session_display_ctx_t ctx,
const char *  frame_data 
)

Definition at line 511 of file lib/session/display.c.

511 {
512 if (!ctx || !ctx->initialized) {
513 SET_ERRNO(ERROR_INVALID_PARAM, "Display context is NULL or uninitialized");
514 return;
515 }
516
517 if (!frame_data) {
518 SET_ERRNO(ERROR_INVALID_PARAM, "Frame data is NULL");
519 return;
520 }
521
522 // Suppress frame rendering when help screen is active
523 // Network reception continues in background, frames are just not displayed
524 if (atomic_load(&ctx->help_screen_active)) {
525 return;
526 }
527
528 // Calculate frame length
529 size_t frame_len = strnlen(frame_data, 1024 * 1024); // Max 1MB frame
530 if (frame_len == 0) {
531 SET_ERRNO(ERROR_INVALID_PARAM, "Frame data is empty");
532 return;
533 }
534
535 // Debug: check for lines that might shoot off to the right
536 // Find longest line in frame data (visible characters between newlines, excluding ANSI codes)
537 size_t max_line_chars = 0;
538 size_t current_line_chars = 0;
539 bool in_ansi_code = false;
540
541 for (size_t i = 0; i < frame_len; i++) {
542 char c = frame_data[i];
543
544 if (c == '\033') {
545 // Start of ANSI escape sequence
546 in_ansi_code = true;
547 } else if (in_ansi_code && c == 'm') {
548 // End of ANSI escape sequence
549 in_ansi_code = false;
550 } else if (!in_ansi_code && c == '\n') {
551 // End of line
552 if (current_line_chars > max_line_chars) {
553 max_line_chars = current_line_chars;
554 }
555 current_line_chars = 0;
556 } else if (!in_ansi_code) {
557 // Regular character (not ANSI code)
558 current_line_chars++;
559 }
560 }
561
562 if (current_line_chars > 0) {
563 if (current_line_chars > max_line_chars) {
564 max_line_chars = current_line_chars;
565 }
566 }
567
568 unsigned short int term_width = GET_OPTION(width);
569 if (max_line_chars > term_width) {
570 log_warn("FRAME_ANALYSIS: Line %zu chars exceeds terminal width %u - this may cause wrapping!", max_line_chars,
571 term_width);
572 }
573
574 // Handle first frame - perform initial terminal reset only
575 if (atomic_load(&ctx->first_frame)) {
576 atomic_store(&ctx->first_frame, false);
577
578 // NOTE: log_set_terminal_output(false) skipped here to avoid deadlocks with audio worker threads
579 // Terminal logging will continue during rendering but won't corrupt the final frame
580
581 // Perform initial terminal reset
582 if (ctx->has_tty) {
583 full_terminal_reset_internal(ctx->snapshot_mode);
584 }
585 }
586
587 // Output routing logic:
588 // - TTY mode: always render with cursor control (including snapshot mode for animation)
589 // - Snapshot mode on non-TTY: render only final frame WITHOUT cursor control
590 // - Piped mode: render every frame WITHOUT cursor control (allows continuous output to files)
591 bool use_tty_control = ctx->has_tty;
592
593 START_TIMER("frame_write");
594 if (use_tty_control) {
595 // TTY mode: just reset cursor to home and redraw frame without clearing
596 // This avoids flashing at high framerates and is more efficient than clearing
597 (void)terminal_cursor_home(STDOUT_FILENO);
598 // Send frame data (overwrites previous frame from cursor position)
599 (void)platform_write_all(STDOUT_FILENO, frame_data, frame_len);
600 (void)terminal_flush(STDOUT_FILENO);
601 } else if (terminal_is_interactive()) {
602 // Piped to an interactive terminal: output ASCII frames
603 // Combine frame and newline into single write call
604 // Allocate temporary buffer for frame + newline to minimize syscalls
605 char *write_buf = SAFE_MALLOC(frame_len + 1, char *);
606 if (write_buf) {
607 memcpy(write_buf, frame_data, frame_len);
608 write_buf[frame_len] = '\n';
609
610 // Use thread-safe console lock for proper synchronization
611 bool prev_lock_state = log_lock_terminal();
612 (void)platform_write_all(STDOUT_FILENO, write_buf, frame_len + 1);
613 log_unlock_terminal(prev_lock_state);
614
615 SAFE_FREE(write_buf);
616 } else {
617 // Fallback: two writes if allocation fails
618 (void)platform_write_all(STDOUT_FILENO, frame_data, frame_len);
619 bool prev_lock_state = log_lock_terminal();
620 const char newline = '\n';
621 (void)platform_write_all(STDOUT_FILENO, &newline, 1);
622 log_unlock_terminal(prev_lock_state);
623 }
624
625 // Flush kernel write buffer so piped data appears immediately to readers
626 (void)terminal_flush(STDOUT_FILENO);
627 }
628 STOP_TIMER_AND_LOG_EVERY(dev, 3 * NS_PER_SEC_INT, 5 * NS_PER_MS_INT, "frame_write",
629 "FRAME_WRITE: Write and flush complete (%.2f ms)");
630}
size_t platform_write_all(int fd, const void *buf, size_t count)
Write all data to file descriptor with automatic retry on transient errors.
Definition abstraction.c:39
bool log_lock_terminal(void)
void log_unlock_terminal(bool previous_state)
asciichat_error_t terminal_flush(int fd)

References session_display_ctx::first_frame, session_display_ctx::has_tty, session_display_ctx::help_screen_active, session_display_ctx::initialized, log_lock_terminal(), log_unlock_terminal(), platform_write_all(), session_display_ctx::snapshot_mode, terminal_flush(), and terminal_is_interactive().

Referenced by display_render_frame(), and session_render_loop().

◆ session_display_reset()

void session_display_reset ( session_display_ctx_t ctx)

Definition at line 652 of file lib/session/display.c.

652 {
653 if (!ctx || !ctx->initialized) {
654 SET_ERRNO(ERROR_INVALID_PARAM, "Session display context is NULL or uninitialized");
655 return;
656 }
657
658 // Skip terminal reset in snapshot mode to preserve rendered output
659 if (ctx->snapshot_mode) {
660 return;
661 }
662
663 // Only perform terminal operations if we have a valid TTY
664 if (ctx->has_tty && ctx->tty_info.fd >= 0) {
665 (void)terminal_reset(ctx->tty_info.fd);
666 (void)terminal_hide_cursor(ctx->tty_info.fd, false); // Show cursor
667 (void)terminal_flush(ctx->tty_info.fd);
668 }
669}

References session_display_ctx::has_tty, session_display_ctx::initialized, session_display_ctx::snapshot_mode, terminal_flush(), and session_display_ctx::tty_info.

Referenced by display_full_reset().

◆ session_display_set_cursor_visible()

void session_display_set_cursor_visible ( session_display_ctx_t ctx,
bool  visible 
)

Definition at line 701 of file lib/session/display.c.

701 {
702 if (!ctx || !ctx->initialized) {
703 SET_ERRNO(ERROR_INVALID_PARAM, "Session display context is NULL or uninitialized");
704 return;
705 }
706
707 // Only perform terminal operations when we have a valid TTY (not when piping)
708 if (ctx->has_tty && ctx->tty_info.fd >= 0) {
709 (void)terminal_hide_cursor(ctx->tty_info.fd, !visible);
710 }
711}

References session_display_ctx::has_tty, session_display_ctx::initialized, and session_display_ctx::tty_info.

◆ session_display_toggle_help()

void session_display_toggle_help ( session_display_ctx_t ctx)

Toggle help screen on/off (implemented in display.c for struct access)

Definition at line 779 of file lib/session/display.c.

779 {
780 if (!ctx) {
781 SET_ERRNO(ERROR_INVALID_PARAM, "Session display context is NULL");
782 return;
783 }
784
785 bool current = atomic_load(&ctx->help_screen_active);
786 atomic_store(&ctx->help_screen_active, !current);
787}

References session_display_ctx::help_screen_active.

Referenced by session_handle_keyboard_input().

◆ session_display_write_audio()

asciichat_error_t session_display_write_audio ( session_display_ctx_t ctx,
const float *  buffer,
size_t  num_samples 
)

Definition at line 721 of file lib/session/display.c.

721 {
722 if (!ctx || !ctx->initialized || !buffer || num_samples == 0) {
723 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: ctx=%p, buffer=%p, num_samples=%zu", ctx, buffer,
724 num_samples);
725 }
726
727 if (!ctx->audio_playback_enabled || !ctx->audio_ctx) {
728 // Audio not enabled, but not an error - just skip silently
729 return ASCIICHAT_OK;
730 }
731
732 // For mirror mode with local files: write samples directly without jitter buffering
733 // The jitter buffer is designed for network scenarios with irregular packet arrivals
734 // For local playback, we just want raw samples flowing to the speakers
735 audio_context_t *audio_ctx = (audio_context_t *)ctx->audio_ctx;
736 if (audio_ctx && audio_ctx->playback_buffer) {
737 audio_ring_buffer_t *rb = audio_ctx->playback_buffer;
738
739 // Simple direct write: append samples to ring buffer without network-oriented complexity
740 uint32_t write_idx = atomic_load(&rb->write_index);
741 uint32_t read_idx = atomic_load(&rb->read_index);
742
743 // Calculate available space in ring buffer
744 uint32_t available = (read_idx - write_idx - 1) & (AUDIO_RING_BUFFER_SIZE - 1);
745
746 if ((uint32_t)num_samples > available) {
747 // Buffer full - just skip this write to avoid distortion from overwriting old audio
748 return ASCIICHAT_OK;
749 }
750
751 // Direct memcpy to ring buffer, handling wrap-around
752 uint32_t space_before_wrap = AUDIO_RING_BUFFER_SIZE - write_idx;
753 if ((uint32_t)num_samples <= space_before_wrap) {
754 memcpy(&rb->data[write_idx], buffer, num_samples * sizeof(float));
755 } else {
756 // Split write: first part to end of buffer, second part wraps to beginning
757 uint32_t first_part = space_before_wrap;
758 uint32_t second_part = num_samples - first_part;
759 memcpy(&rb->data[write_idx], buffer, first_part * sizeof(float));
760 memcpy(&rb->data[0], &buffer[first_part], second_part * sizeof(float));
761 }
762
763 // Update write index atomically
764 atomic_store(&rb->write_index, (write_idx + num_samples) % AUDIO_RING_BUFFER_SIZE);
765
766 return ASCIICHAT_OK;
767 }
768
769 return ASCIICHAT_OK;
770}

References session_display_ctx::audio_ctx, session_display_ctx::audio_playback_enabled, and session_display_ctx::initialized.

◆ session_display_write_raw()

void session_display_write_raw ( session_display_ctx_t ctx,
const char *  data,
size_t  len 
)

Definition at line 632 of file lib/session/display.c.

632 {
633 if (!ctx || !ctx->initialized || !data || len == 0) {
634 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: ctx=%p, data=%p, len=%zu", ctx, data, len);
635 return;
636 }
637
638 int fd = -1;
639 if (ctx->has_tty && ctx->tty_info.fd >= 0) {
640 fd = ctx->tty_info.fd;
641 } else {
642 fd = STDOUT_FILENO;
643 }
644
645 // Write all data with automatic retry on transient errors
646 (void)platform_write_all(fd, data, len);
647
648 // Flush immediately after write to TTY to ensure data is sent
649 (void)terminal_flush(fd);
650}

References session_display_ctx::has_tty, session_display_ctx::initialized, platform_write_all(), terminal_flush(), and session_display_ctx::tty_info.

Referenced by session_display_render_help().