13#include <ascii-chat/session/display.h>
14#include <ascii-chat/common.h>
15#include <ascii-chat/log/logging.h>
16#include <ascii-chat/options/options.h>
17#include <ascii-chat/util/time.h>
18#include <ascii-chat/platform/terminal.h>
19#include <ascii-chat/platform/abstraction.h>
20#include <ascii-chat/video/ansi_fast.h>
21#include <ascii-chat/video/palette.h>
22#include <ascii-chat/video/ascii.h>
23#include <ascii-chat/video/color_filter.h>
24#include <ascii-chat/video/digital_rain.h>
25#include <ascii-chat/video/image.h>
26#include <ascii-chat/audio/audio.h>
27#include <ascii-chat/asciichat_errno.h>
28#include <ascii-chat/video/simd/neon.h>
53 terminal_capabilities_t
caps;
104static void full_terminal_reset_internal(
bool snapshot_mode) {
107 (void)terminal_reset(STDOUT_FILENO);
108 (void)terminal_clear_screen();
109 (void)terminal_cursor_home(STDOUT_FILENO);
110 (void)terminal_clear_scrollback(STDOUT_FILENO);
111 if (!snapshot_mode) {
112 (void)terminal_hide_cursor(STDOUT_FILENO,
true);
123 session_display_config_t auto_config = {0};
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;
133 if (config->should_exit_callback && config->should_exit_callback(config->callback_data)) {
174 bool is_snapshot_mode = config->snapshot_mode;
176 ctx->
caps.wants_padding = is_interactive && !is_snapshot_mode;
178 log_debug(
"Padding mode: wants_padding=%d (snapshot=%d, interactive=%d, stdin_tty=%d, stdout_tty=%d)",
183 if (config->color_mode != TERM_COLOR_AUTO) {
184 ctx->
caps.color_level = config->color_mode;
188 ctx->
caps = apply_color_mode_override(ctx->
caps);
193 if (palette_result != ASCIICHAT_OK) {
194 log_warn(
"Failed to initialize palette, using default");
205 log_debug(
"UTF-8 palette cache pre-warmed during display initialization");
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) {
223 if (GET_OPTION(matrix_rain)) {
225 unsigned short int width_us = (
unsigned short int)GET_OPTION(width);
226 unsigned short int height_us = (
unsigned short int)GET_OPTION(height);
229 if (width_us == 0 || height_us == 0) {
233 int width = (int)width_us;
234 int height = (int)height_us;
239 color_filter_t filter = GET_OPTION(color_filter);
241 log_info(
"Digital rain effect enabled: %dx%d grid", width, height);
243 log_warn(
"Failed to initialize digital rain effect");
254 SET_ERRNO(ERROR_INVALID_PARAM,
"Session display context is NULL");
287 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters: ctx=%p", ctx);
295 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters: ctx=%p", ctx);
303 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters: ctx=%p", ctx);
311 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters: ctx=%p", ctx);
319 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters: ctx=%p", ctx);
327 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters: ctx=%p", ctx);
339 SET_ERRNO(ERROR_INVALID_PARAM,
"session_display_convert_to_ascii: ctx is NULL");
344 SET_ERRNO(ERROR_INVALID_STATE,
"session_display_convert_to_ascii: ctx not initialized");
349 SET_ERRNO(ERROR_INVALID_PARAM,
"session_display_convert_to_ascii: image is NULL");
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;
360 bool flip_x_enabled = GET_OPTION(flip_x);
361 bool flip_y_enabled = GET_OPTION(flip_y);
369 flip_x_enabled =
false;
372 color_filter_t color_filter = GET_OPTION(color_filter);
375 terminal_capabilities_t caps_copy = ctx->
caps;
381 image_t *flipped_image = NULL;
382 const image_t *display_image = image;
384 if ((flip_x_enabled || flip_y_enabled) && image->w > 1 && image->h > 1 && image->pixels) {
385 START_TIMER(
"image_flip");
387 flipped_image =
image_new((
size_t)image->w, (
size_t)image->h);
393 memcpy(flipped_image->pixels, image->pixels, (
size_t)image->w * (
size_t)image->h *
sizeof(rgb_pixel_t));
399 if (flip_x_enabled) {
402 image_flip_horizontal_neon(flipped_image);
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;
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;
430 display_image = flipped_image;
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);
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)");
448 START_TIMER(
"ascii_convert_with_capabilities");
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)");
457 if (result && color_filter == COLOR_FILTER_RAINBOW) {
459 float time_seconds = (float)t_filter_start / (
float)NS_PER_SEC_INT;
462 if (rainbow_result) {
464 result = rainbow_result;
468 log_dev(
"COLOR_REPLACE: %.2f ms", (
double)(t_color_replace_end - t_color_replace_start) / (
double)NS_PER_MS_INT);
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;
481 result = rain_result;
485 log_dev(
"DIGITAL_RAIN: Effect applied (%.2f ms)", (
double)(t_rain_end - t_rain_start) / (
double)NS_PER_MS_INT);
490 START_TIMER(
"ascii_convert_cleanup");
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)");
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);
513 SET_ERRNO(ERROR_INVALID_PARAM,
"Display context is NULL or uninitialized");
518 SET_ERRNO(ERROR_INVALID_PARAM,
"Frame data is NULL");
529 size_t frame_len = strnlen(frame_data, 1024 * 1024);
530 if (frame_len == 0) {
531 SET_ERRNO(ERROR_INVALID_PARAM,
"Frame data is empty");
537 size_t max_line_chars = 0;
538 size_t current_line_chars = 0;
539 bool in_ansi_code =
false;
541 for (
size_t i = 0; i < frame_len; i++) {
542 char c = frame_data[i];
547 }
else if (in_ansi_code && c ==
'm') {
549 in_ansi_code =
false;
550 }
else if (!in_ansi_code && c ==
'\n') {
552 if (current_line_chars > max_line_chars) {
553 max_line_chars = current_line_chars;
555 current_line_chars = 0;
556 }
else if (!in_ansi_code) {
558 current_line_chars++;
562 if (current_line_chars > 0) {
563 if (current_line_chars > max_line_chars) {
564 max_line_chars = current_line_chars;
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,
591 bool use_tty_control = ctx->
has_tty;
593 START_TIMER(
"frame_write");
594 if (use_tty_control) {
597 (void)terminal_cursor_home(STDOUT_FILENO);
605 char *write_buf = SAFE_MALLOC(frame_len + 1,
char *);
607 memcpy(write_buf, frame_data, frame_len);
608 write_buf[frame_len] =
'\n';
615 SAFE_FREE(write_buf);
620 const char newline =
'\n';
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)");
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);
654 SET_ERRNO(ERROR_INVALID_PARAM,
"Session display context is NULL or uninitialized");
665 (void)terminal_reset(ctx->
tty_info.fd);
666 (void)terminal_hide_cursor(ctx->
tty_info.fd,
false);
673 SET_ERRNO(ERROR_INVALID_PARAM,
"Session display context is NULL or uninitialized");
684 (void)terminal_clear_screen();
685 (void)terminal_cursor_home(ctx->
tty_info.fd);
691 SET_ERRNO(ERROR_INVALID_PARAM,
"Session display context is NULL or uninitialized");
697 (void)terminal_cursor_home(fd);
703 SET_ERRNO(ERROR_INVALID_PARAM,
"Session display context is NULL or uninitialized");
709 (void)terminal_hide_cursor(ctx->
tty_info.fd, !visible);
715 SET_ERRNO(ERROR_INVALID_PARAM,
"Session display context is NULL or uninitialized");
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,
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;
740 uint32_t write_idx = atomic_load(&rb->write_index);
741 uint32_t read_idx = atomic_load(&rb->read_index);
744 uint32_t available = (read_idx - write_idx - 1) & (AUDIO_RING_BUFFER_SIZE - 1);
746 if ((uint32_t)num_samples > available) {
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));
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));
764 atomic_store(&rb->write_index, (write_idx + num_samples) % AUDIO_RING_BUFFER_SIZE);
781 SET_ERRNO(ERROR_INVALID_PARAM,
"Session display context is NULL");
794 SET_ERRNO(ERROR_INVALID_PARAM,
"Session display context is NULL");
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.
void ansi_fast_init_256color(void)
void ansi_fast_init(void)
void ansi_fast_init_16color(void)
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)
asciichat_error_t ascii_write_init(int fd, bool reset_terminal)
void ascii_write_destroy(int fd, bool reset_terminal)
char * rainbow_replace_ansi_colors(const char *ansi_string, float time_seconds)
void digital_rain_set_color_from_filter(digital_rain_t *rain, color_filter_t filter)
void digital_rain_destroy(digital_rain_t *rain)
char * digital_rain_apply(digital_rain_t *rain, const char *frame, float delta_time)
digital_rain_t * digital_rain_init(int num_columns, int num_rows)
void session_display_destroy(session_display_ctx_t *ctx)
session_display_ctx_t * session_display_create(const session_display_config_t *config)
void session_display_clear(session_display_ctx_t *ctx)
size_t session_display_get_palette_len(session_display_ctx_t *ctx)
struct session_display_ctx session_display_ctx_t
Internal session display context structure.
void session_display_set_cursor_visible(session_display_ctx_t *ctx, bool visible)
void session_display_write_raw(session_display_ctx_t *ctx, const char *data, size_t len)
asciichat_error_t session_display_write_audio(session_display_ctx_t *ctx, const float *buffer, size_t num_samples)
bool session_display_has_tty(session_display_ctx_t *ctx)
void session_display_cursor_home(session_display_ctx_t *ctx)
const char * session_display_get_luminance_palette(session_display_ctx_t *ctx)
void session_display_render_frame(session_display_ctx_t *ctx, const char *frame_data)
int session_display_get_tty_fd(session_display_ctx_t *ctx)
const char * session_display_get_palette_chars(session_display_ctx_t *ctx)
const terminal_capabilities_t * session_display_get_caps(session_display_ctx_t *ctx)
bool session_display_has_audio_playback(session_display_ctx_t *ctx)
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)
char * session_display_convert_to_ascii(session_display_ctx_t *ctx, const image_t *image)
void session_display_reset(session_display_ctx_t *ctx)
void session_display_toggle_help(session_display_ctx_t *ctx)
Toggle help screen on/off (implemented in display.c for struct access)
void log_set_force_stderr(bool enabled)
bool log_lock_terminal(void)
void log_unlock_terminal(bool previous_state)
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])
Internal session display context structure.
atomic_bool first_frame
First frame flag for logging control.
char palette_chars[256]
Palette character string for rendering.
atomic_bool help_screen_active
Help screen active flag (toggled with '?') - atomic for thread-safe access.
bool has_tty
True if we have a valid TTY for interactive output.
tty_info_t tty_info
TTY information (file descriptor, path, ownership)
char luminance_palette[256]
Luminance-to-character mapping table (256 entries)
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.
bool initialized
Context is fully initialized.
void * audio_ctx
Audio context for playback (borrowed, not owned)
terminal_capabilities_t caps
Detected terminal capabilities.
bool audio_playback_enabled
Audio playback is enabled.
palette_type_t palette_type
Configured palette type.
bool snapshot_mode
Snapshot mode enabled.
size_t palette_len
Number of characters in palette.
uint64_t time_get_ns(void)
int platform_isatty(int fd)
void image_destroy(image_t *p)
image_t * image_new(size_t width, size_t height)
utf8_palette_cache_t * get_utf8_palette_cache(const char *ascii_chars)
int platform_close(int fd)