ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches

🖥️ Unified terminal display abstraction for session-based rendering More...

Go to the source code of this file.

Data Structures

struct  session_display_config_t
 Terminal-rendering and snapshot settings used to create a display context. More...

Typedefs

typedef bool(* session_display_should_exit_fn) (void *user_data)
 Callback type to check if initialization should be cancelled.

Functions

session_display_ctx_t * session_display_create (const session_display_config_t *config)
 Create a new session display context.
session_display_ctx_t * session_display_get_global_context (void)
 Get the global display context (used for signal handlers and special cleanup).
void session_display_set_global_context_public (session_display_ctx_t *ctx)
 Set the global display context (public setter for discovery cleanup).
asciichat_error_t session_display_finish_render_file (session_display_ctx_t *ctx)
 Destroy session display context and free resources.
void session_display_begin_drain (session_display_ctx_t *ctx, uint64_t total, bool total_known)
void session_display_destroy (session_display_ctx_t *ctx)
void session_display_set_stdin_reader (session_display_ctx_t *ctx, void *reader)
 Set stdin frame reader for ASCII-to-video rendering.
bool session_display_has_tty (session_display_ctx_t *ctx)
 Check if display has a TTY (terminal) available.
const terminal_capabilities_t * session_display_get_caps (session_display_ctx_t *ctx)
 Get detected terminal capabilities.
const char * session_display_get_palette_chars (session_display_ctx_t *ctx)
 Get the palette characters string.
size_t session_display_get_palette_len (session_display_ctx_t *ctx)
 Get the palette character count.
const char * session_display_get_luminance_palette (session_display_ctx_t *ctx)
 Get the luminance mapping palette.
int session_display_get_tty_fd (session_display_ctx_t *ctx)
 Get the TTY file descriptor.
void * session_display_get_stdin_reader (session_display_ctx_t *ctx)
 Get the stdin frame reader for ASCII-to-video rendering.
bool session_display_has_render_file (session_display_ctx_t *ctx)
 Check if display has render-file configured.
uint32_t session_display_get_render_fps (session_display_ctx_t *ctx)
 Get the render FPS configured for file output.
asciichat_error_t session_display_init_render_file (session_display_ctx_t *ctx, uint32_t fps)
 Initialize render-file output after the capture frame rate is known.
void session_display_set_render_live_timing (session_display_ctx_t *ctx)
void session_display_set_render_fps (session_display_ctx_t *ctx, uint32_t fps)
 Set the render FPS for file output encoding.
bool session_display_has_first_frame (session_display_ctx_t *ctx)
 Check if the display has rendered its first frame.
void session_display_reset_first_frame (session_display_ctx_t *ctx)
 Reset the first frame flag to allow splash screen to show again on next render.
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.
char * session_display_convert_to_ascii_reuse (session_display_ctx_t *ctx, const image_t *image)
void session_display_release_ascii (session_display_ctx_t *ctx, char *ascii)
void session_display_reset_stabilizer (session_display_ctx_t *ctx, bool recording)
void session_display_write_ascii (session_display_ctx_t *ctx, const char *frame_data)
 Write ASCII frame to terminal only (no encoding).
asciichat_error_t session_display_encode_frame (session_display_ctx_t *ctx, const image_t *image, uint64_t captured_ns)
 Encode frame to render-file (FFmpeg only, no terminal output).
void session_display_render_frame (session_display_ctx_t *ctx, const char *frame_data)
 Render an ASCII frame to the terminal.
void session_display_write_raw (session_display_ctx_t *ctx, const char *data, size_t len)
 Render raw bytes to the terminal without frame processing.
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 session_display_reset (session_display_ctx_t *ctx)
 Reset terminal to default state.
void session_display_clear (session_display_ctx_t *ctx)
 Clear the terminal screen.
void session_display_cursor_home (session_display_ctx_t *ctx)
 Move cursor to home position (top-left).
bool session_display_has_audio_playback (session_display_ctx_t *ctx)
 Check if display has audio playback configured.
asciichat_error_t session_display_write_audio (session_display_ctx_t *ctx, const float *buffer, size_t num_samples)
 Write audio samples to playback buffer.
void session_display_write_ascii_frame (session_display_ctx_t *ctx, const char *ascii, bool fresh)

Detailed Description

🖥️ Unified terminal display abstraction for session-based rendering

Definition in file src/common/session/display.h.

Function Documentation

◆ session_display_convert_to_ascii_reuse()

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

Single display owner; result must be released before its next conversion.

Definition at line 932 of file src/common/session/display.c.

932 {
933 ASCII_OTEL_SCOPE(SESSION_DISPLAY_CONVERT_TO_ASCII_REUSE);
934 return convert_to_ascii(ctx, image, true);
935}
#define ASCII_OTEL_SCOPE(id)
Definition otel.h:72

References ASCII_OTEL_SCOPE.

Referenced by session_pipeline_run_main(), and session_render_loop().

◆ session_display_init_render_file()

asciichat_error_t session_display_init_render_file ( session_display_ctx_t * ctx,
uint32_t fps )

Initialize render-file output after the capture frame rate is known.

Parameters
ctxDisplay context
fpsFrame rate used for output timing
Returns
ASCIICHAT_OK on success

Initialize render-file output after the capture frame rate is known.

Executes comprehensive terminal reset sequence for clean display state. Skips terminal control operations in snapshot mode.

Parameters
snapshot_modeWhether snapshot mode is enabled

Definition at line 170 of file src/common/session/display.c.

170 {
171 if (!ctx) {
172 return SET_ERRNO(ERROR_INVALID_PARAM, "Display context is NULL");
173 }
174
175 const char *render_file_opt = GET_OPTION(render_file);
176 if ((!render_file_opt || !render_file_opt[0]) && GET_OPTION(render_kitty))
177 render_file_opt = "--kitty";
178 if (!render_file_opt || render_file_opt[0] == '\0') {
179 return ASCIICHAT_OK;
180 }
181 if (ctx->render_file) {
182 return ASCIICHAT_OK;
183 }
184
185 uint32_t encoder_fps = fps;
186 if (encoder_fps == 0) {
187 encoder_fps = (uint32_t)GET_OPTION(fps);
188 }
189 if (encoder_fps == 0) {
190 encoder_fps = 60;
191 }
192
193 int width = (int)GET_OPTION(width);
194 int height = (int)GET_OPTION(height);
195 ctx->render_fps = encoder_fps;
196
197 log_info("render-file: Initializing encoder after media timing selection: path='%s', grid=%dx%d, fps=%u",
198 render_file_opt, width, height, encoder_fps);
200 render_file_create(render_file_opt, width, height, (int)encoder_fps, GET_OPTION(render_theme), &ctx->render_file);
201 if (err != ASCIICHAT_OK) {
203 if (HAS_ERRNO(&error))
204 log_error("render-file: %s", error.context_message);
205 log_error("render-file: Failed to create encoder %s", asciichat_error_format(NULL, err));
206 return err;
207 }
208
209 log_info("render-file: Encoder initialized successfully at %u FPS", encoder_fps);
210 return ASCIICHAT_OK;
211}
const char * asciichat_error_format(const char *message, asciichat_error_t code)
#define SET_ERRNO(code,...)
#define HAS_ERRNO(context)
unsigned int height
Definition fft.c:23
unsigned int width
Definition fft.c:23
unsigned int uint32_t
Definition common.h:62
asciichat_error_t
Error and exit codes - unified status values (0-255).
Definition error_codes.h:44
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_INVALID_PARAM
#define log_error(...)
Log an ERROR message.
Definition log/log.h:634
#define log_info(...)
Log an INFO message.
Definition log/log.h:608
#define GET_OPTION(field)
Safely get a specific option field (lock-free read).
Definition options.h:1336
asciichat_error_t render_file_create(const char *output_path, int cols, int rows, int fps, int theme, render_file_ctx_t **out)
Definition renderer.c:53
render_file_ctx_t * render_file
Render-to-file context (NULL if disabled).
uint32_t render_fps
Video FPS for render-file encoding.

References asciichat_error_format(), ASCIICHAT_OK, asciichat_error_context_t::context_message, ERROR_INVALID_PARAM, GET_OPTION, HAS_ERRNO, height, log_error, log_info, session_display_ctx::render_file, render_file_create(), session_display_ctx::render_fps, SET_ERRNO, and width.

Referenced by session_display_create().

◆ session_display_release_ascii()

void session_display_release_ascii ( session_display_ctx_t * ctx,
char * ascii )

Definition at line 942 of file src/common/session/display.c.

942 {
943 if (!ctx || ascii != ctx->ascii_workspace.buf)
944 SAFE_FREE(ascii);
945}
#define SAFE_FREE(ptr)
Definition common.h:377
char * buf
Buffer pointer (allocated, owned by caller, must be freed).

References session_display_ctx::ascii_workspace, outbuf_t::buf, and SAFE_FREE.

Referenced by session_pipeline_run_main(), and session_render_loop().

◆ session_display_reset_stabilizer()

void session_display_reset_stabilizer ( session_display_ctx_t * ctx,
bool recording )

Clear only the history owned by the calling display or recording worker.

Definition at line 937 of file src/common/session/display.c.

937 {
938 if (ctx)
939 ascii_stabilizer_reset(recording ? &ctx->recording_stabilizer : &ctx->stabilizer);
940}
void ascii_stabilizer_reset(ascii_stabilizer_t *state)
ascii_stabilizer_t recording_stabilizer

References ascii_stabilizer_reset(), session_display_ctx::recording_stabilizer, and session_display_ctx::stabilizer.

Referenced by session_pipeline_run_main().

◆ session_display_set_render_live_timing()

void session_display_set_render_live_timing ( session_display_ctx_t * ctx)

Definition at line 1280 of file src/common/session/display.c.

1280 {
1281 if (ctx && ctx->render_file)
1283}
void render_file_set_live_timing(render_file_ctx_t *ctx)
Definition renderer.c:266

References session_display_ctx::render_file, and render_file_set_live_timing().

Referenced by session_pipeline_create().

◆ session_display_write_ascii_frame()

void session_display_write_ascii_frame ( session_display_ctx_t * ctx,
const char * ascii,
bool fresh )

Publish an acquired frame, or redraw retained content without advancing displayed FPS.

Definition at line 947 of file src/common/session/display.c.

947 {
948 ASCII_OTEL_SCOPE(SESSION_DISPLAY_WRITE_ASCII_FRAME);
949 if (!ctx || !ctx->initialized) {
950 SET_ERRNO(ERROR_INVALID_PARAM, "Display context is NULL or uninitialized");
951 return;
952 }
953
954 // Shutdown is asynchronous; queued frames must not retry a failed output.
955 if (atomic_load_bool(&ctx->output_failed)) {
956 return;
957 }
958
959 if (!ascii) {
960 SET_ERRNO(ERROR_INVALID_PARAM, "ASCII data is NULL");
961 return;
962 }
963
964 // Re-evaluate color_level on every frame to pick up live color_mode changes
965 terminal_color_mode_t color_mode = (terminal_color_mode_t)GET_OPTION(color_mode);
966 if (color_mode != TERM_COLOR_AUTO) {
967 ctx->caps.color_level = color_mode;
968 }
969
970 // Suppress frame rendering when help screen is active
971
972 bool visualization_enabled = GET_OPTION(waveform) || GET_OPTION(fft);
973 char *visualization_frame = visualization_enabled ? session_display_create_visualization_frame(ctx) : NULL;
974 if (visualization_frame)
975 ascii = visualization_frame;
976
977 // Leave room for cursor controls when passing the frame to the presenter.
978 const size_t max_frame_len = UI_CONTROLLER_MAX_FRAME_BYTES - 7;
979 size_t frame_len = strnlen(ascii, max_frame_len);
980 if (frame_len == max_frame_len) {
981 SET_ERRNO(ERROR_BUFFER_FULL, "ASCII frame exceeds the presenter limit");
982 SAFE_FREE(visualization_frame);
983 return;
984 }
985 if (frame_len == 0) {
986 SET_ERRNO(ERROR_INVALID_PARAM, "ASCII data is empty");
987 SAFE_FREE(visualization_frame);
988 return;
989 }
990
991 /* Inspect the payload before any terminal presentation. This is bounded
992 * diagnostics: it records shape/control-byte anomalies and a short tail,
993 * never the full webcam frame. */
994 {
995 size_t lines = 0, current = 0, min_line = SIZE_MAX, max_line = 0, last_line = 0;
996 unsigned control_bytes = 0, escape_bytes = 0;
997 for (size_t i = 0; i < frame_len; ++i) {
998 unsigned char byte = (unsigned char)ascii[i];
999 if (byte == '\n') {
1000 min_line = MIN(min_line, current);
1001 max_line = MAX(max_line, current);
1002 last_line = current;
1003 ++lines;
1004 current = 0;
1005 } else {
1006 current++;
1007 if (byte < 32 && byte != '\r' && byte != '\t')
1008 ++control_bytes;
1009 if (byte == 0x1b)
1010 ++escape_bytes;
1011 }
1012 }
1013 if (current) {
1014 min_line = MIN(min_line, current);
1015 max_line = MAX(max_line, current);
1016 last_line = current;
1017 }
1018 char tail[64] = {0};
1019 size_t tail_start = frame_len > 24 ? frame_len - 24 : 0;
1020 size_t tail_used = 0;
1021 for (size_t i = tail_start; i < frame_len && tail_used + 4 < sizeof(tail); ++i)
1022 tail_used += (size_t)safe_snprintf(tail + tail_used, sizeof(tail) - tail_used, "%02X ", (unsigned char)ascii[i]);
1023
1024 // Also capture the actual glyphs in the final row. Byte/line counts alone
1025 // cannot distinguish a bad terminal write from a bad rendered frame.
1026 size_t row_start = 0;
1027 for (size_t i = 0; i < frame_len; ++i)
1028 if (ascii[i] == '\n')
1029 row_start = i + 1;
1030 char last_row[192] = {0};
1031 size_t row_used = 0;
1032 for (size_t i = row_start; i < frame_len && row_used + 5 < sizeof(last_row); ++i) {
1033 unsigned char byte = (unsigned char)ascii[i];
1034 if (byte == 0x1b) {
1035 ++i;
1036 while (i < frame_len && !((ascii[i] >= '@' && ascii[i] <= 'Z') || (ascii[i] >= 'a' && ascii[i] <= 'z')))
1037 ++i;
1038 continue;
1039 }
1040 if (byte >= 32 && byte < 127)
1041 last_row[row_used++] = (char)byte;
1042 else if (byte == '\r' || byte == '\t')
1043 last_row[row_used++] = ' ';
1044 else
1045 row_used += (size_t)safe_snprintf(last_row + row_used, sizeof(last_row) - row_used, "\\x%02X", byte);
1046 }
1048 "FRAME_DATA: bytes=%zu lines=%zu line_bytes=%zu..%zu last=%zu controls=%u escapes=%u last_row=\"%s\" tail=%s",
1049 frame_len, lines, min_line == SIZE_MAX ? 0 : min_line, max_line, last_line, control_bytes,
1050 escape_bytes, last_row, tail);
1051 }
1052
1053 // Apply digital rain effect if enabled
1054 char *display_frame = (char *)ascii;
1055 char *rain_result = NULL;
1056 if (ctx->digital_rain && !visualization_enabled) {
1057 uint64_t t_rain_start = time_get_ns();
1058 uint64_t current_time_ns = t_rain_start;
1059 float delta_time = (float)(current_time_ns - ctx->last_frame_time_ns) / (float)NS_PER_SEC_INT;
1060 ctx->last_frame_time_ns = current_time_ns;
1061
1062 // Update digital rain color from current filter
1063 color_filter_t current_filter = GET_OPTION(color_filter);
1065
1066 rain_result = digital_rain_apply(ctx->digital_rain, (char *)ascii, delta_time);
1067 if (rain_result) {
1068 display_frame = rain_result;
1069 frame_len = strnlen(rain_result, max_frame_len);
1070 if (frame_len == max_frame_len) {
1071 SET_ERRNO(ERROR_BUFFER_FULL, "Digital rain frame exceeds the presenter limit");
1072 SAFE_FREE(rain_result);
1073 SAFE_FREE(visualization_frame);
1074 return;
1075 }
1076
1077 uint64_t t_rain_end = time_get_ns();
1078 char rain_str[TIME_DURATION_BUFFER_SIZE];
1079 time_pretty(t_rain_end - t_rain_start, -1, rain_str, sizeof(rain_str));
1080 log_debug("DIGITAL_RAIN (write_ascii): Effect applied (%s)", rain_str);
1081 }
1082 }
1083
1084 // Binary video owns stdout in every terminal mode.
1085 const char *output = GET_OPTION(render_file);
1086 bool binary_stdout = ctx->render_file && (strcmp(output, "-") == 0 || strcmp(output, "pipe:") == 0 ||
1087 strcmp(output, "--kitty") == 0 || GET_OPTION(render_kitty));
1088 char *zalgo_frame = NULL;
1089 int zalgo_level = GET_OPTION(zalgo);
1090 if (zalgo_level > 0 && !binary_stdout) {
1091 asciichat_error_t err =
1092 zalgo_apply(display_frame, frame_len, zalgo_level, UI_CONTROLLER_MAX_FRAME_BYTES - 7, &zalgo_frame);
1093 if (err == ASCIICHAT_OK) {
1094 display_frame = zalgo_frame;
1095 frame_len = strlen(zalgo_frame);
1096 } else {
1097 log_warn_every(5 * US_PER_SEC_INT, "Cannot apply Zalgo: %s", asciichat_error_format(NULL, err));
1098 }
1099 }
1100
1101 char *plain_frame = NULL;
1102 if (GET_OPTION(strip_ansi)) {
1103 // Expand REP before stripping escapes so repeated cells are preserved.
1104 char *expanded = ansi_expand_rle(display_frame, frame_len);
1105 if (expanded) {
1106 size_t capacity = strlen(expanded) + 1;
1107 plain_frame = SAFE_MALLOC(capacity, char *);
1108 strip_ansi_codes(expanded, plain_frame, capacity);
1109 SAFE_FREE(expanded);
1110 display_frame = plain_frame;
1111 frame_len = strlen(plain_frame);
1112 }
1113 }
1114
1115 // Handle first frame - perform initial terminal reset and splash cleanup
1116 bool was_first_frame = atomic_exchange_bool(&ctx->first_frame, false);
1117 if (was_first_frame) {
1120 // Stop splash screen when first frame is ready
1123 }
1124
1125 if (binary_stdout) {
1126 if (GET_OPTION(snapshot_mode) && !g_snapshot_first_frame_rendered) {
1128 g_snapshot_first_frame_rendered_ns = time_get_ns();
1129 }
1130 SAFE_FREE(plain_frame);
1131 SAFE_FREE(zalgo_frame);
1132 SAFE_FREE(rain_result);
1133 SAFE_FREE(visualization_frame);
1134 return;
1135 }
1136
1137 // Output routing logic
1138 // A snapshot still needs terminal cursor framing when stdout is a real TTY.
1139 // `terminal_is_interactive()` also depends on stdin, which is commonly false
1140 // for scripted snapshot invocations even though the PTY can display output.
1141 bool use_tty_control = ctx->has_tty ||
1142 (ctx->snapshot_mode && ctx->tty_info.fd >= 0 && platform_isatty(ctx->tty_info.fd));
1143
1144 uint64_t frame_write_started_ns = time_get_ns();
1145
1146 // Increment shared frame counter for both client and host rendering
1147 // This metric is used by debug scripts to measure FPS across all modes
1148 static atomic_t g_display_frames_rendered = {0};
1149 int frame_number = atomic_fetch_add_u64(&g_display_frames_rendered, 1) + 1;
1150 log_dev("[FRAMES_RENDERED_TOTAL] %d", frame_number);
1151
1152 if (use_tty_control) {
1153 // TTY mode: Buffer cursor control + frame data together for atomic frame display
1154 const char *cursor_home_sequence = "\033[H";
1155 const char *erase_below_sequence = "\033[J";
1156 size_t cursor_seq_len = 3;
1157 size_t erase_below_len = 3;
1158 // The frame is shorter than the terminal in some configurations. Erase
1159 // from the final frame cursor to the bottom of the screen in the same
1160 // submission so stale rows cannot survive a resize or a shorter frame.
1161 size_t total_size = cursor_seq_len + frame_len + erase_below_len;
1162
1163 if (ctx->tty_frame_capacity < total_size) {
1164 size_t capacity = ctx->tty_frame_capacity ? ctx->tty_frame_capacity : 4096;
1165 while (capacity < total_size && capacity <= SIZE_MAX / 2)
1166 capacity *= 2;
1167 if (capacity < total_size)
1168 capacity = total_size;
1169 ctx->tty_frame_buffer = SAFE_REALLOC(ctx->tty_frame_buffer, capacity, char *);
1170 ctx->tty_frame_capacity = capacity;
1171 }
1172 char *frame_buffer = ctx->tty_frame_buffer;
1173 if (frame_buffer) {
1174 memcpy(frame_buffer, cursor_home_sequence, cursor_seq_len);
1175 memcpy(frame_buffer + cursor_seq_len, display_frame, frame_len);
1176 memcpy(frame_buffer + cursor_seq_len + frame_len, erase_below_sequence, erase_below_len);
1177
1178 log_debug_every(US_PER_SEC_INT, "FRAME_WRITE_TTY: Writing %zu bytes (cursor=%zu + frame=%zu) to stdout",
1179 total_size, cursor_seq_len, frame_len);
1180 int output_fd = ctx->tty_info.fd >= 0 ? ctx->tty_info.fd : STDOUT_FILENO;
1181 asciichat_error_t presented = ASCIICHAT_OK;
1182 if (ctx->snapshot_mode) {
1183 // Snapshot processes may exit immediately after the first frame. Write
1184 // directly so teardown cannot discard a queued UI-controller update.
1185 if (platform_write_all(output_fd, frame_buffer, total_size) != total_size)
1186 presented = SET_ERRNO(ERROR_FILE_OPERATION, "Cannot write snapshot frame to terminal");
1187 } else {
1189 UI_SCREEN_MEDIA, output_fd, (terminal_size_t){.cols = UI_MEDIA_MIN_COLS, .rows = UI_MEDIA_MIN_ROWS},
1190 frame_buffer, total_size);
1191 }
1192 if (was_first_frame && presented == ASCIICHAT_OK)
1194 ssize_t written = presented == ASCIICHAT_OK ? (ssize_t)total_size : -1;
1195 log_debug_every(US_PER_SEC_INT, "FRAME_WRITE_TTY: Wrote %zd bytes (requested %zu)", written, total_size);
1196
1197 // Start snapshot timer on first ASCII frame rendered
1198 if (GET_OPTION(snapshot_mode) && !g_snapshot_first_frame_rendered) {
1200 g_snapshot_first_frame_rendered_ns = time_get_ns();
1201 log_debug("SNAPSHOT: FIRST ASCII FRAME RENDERED (write_ascii) - Timer started");
1202 }
1203
1204 // The UI controller copies the submission before this workspace is reused.
1205 }
1206
1207 // Live presentation owns terminal writes on its worker. Draining here
1208 // blocks packet reception, including audio, behind a slow terminal reader.
1209 if (ctx->snapshot_mode) {
1210 (void)fflush(stdout);
1211 int flush_fd = (ctx->tty_info.fd >= 0) ? ctx->tty_info.fd : STDOUT_FILENO;
1212 (void)terminal_flush(flush_fd);
1213 }
1214
1215 } else if (terminal_is_interactive()) {
1216 // Piped to interactive terminal: output ASCII frames with newline
1217 char *write_buf = SAFE_MALLOC(frame_len + 1, char *);
1218 if (write_buf) {
1219 memcpy(write_buf, display_frame, frame_len);
1220 write_buf[frame_len] = '\n';
1221 if (ui_controller_write(STDOUT_FILENO, write_buf, frame_len + 1) != ASCIICHAT_OK) {
1222 atomic_store_bool(&ctx->output_failed, true);
1224 SAFE_FREE(write_buf);
1225 goto cleanup_frame;
1226 }
1227 SAFE_FREE(write_buf);
1228 }
1229
1230 if (ctx->snapshot_mode) {
1231 (void)fflush(stdout);
1232 int flush_fd = (ctx->tty_info.fd >= 0) ? ctx->tty_info.fd : STDOUT_FILENO;
1233 (void)terminal_flush(flush_fd);
1234 }
1235 } else {
1236 // Named render files leave stdout available for ASCII output
1237 char *write_buf = SAFE_MALLOC(frame_len + 1, char *);
1238 if (write_buf) {
1239 memcpy(write_buf, display_frame, frame_len);
1240 write_buf[frame_len] = '\n';
1241 if (ui_controller_write(STDOUT_FILENO, write_buf, frame_len + 1) != ASCIICHAT_OK) {
1242 atomic_store_bool(&ctx->output_failed, true);
1244 SAFE_FREE(write_buf);
1245 goto cleanup_frame;
1246 }
1247
1248 // Start snapshot timer on first ASCII frame rendered
1249 if (GET_OPTION(snapshot_mode) && !g_snapshot_first_frame_rendered) {
1251 g_snapshot_first_frame_rendered_ns = time_get_ns();
1252 log_debug("SNAPSHOT: FIRST ASCII FRAME RENDERED (write_ascii piped) - Timer started");
1253 }
1254
1255 SAFE_FREE(write_buf);
1256 }
1257
1258 // Writes already reach the OS synchronously; streaming frames do not need
1259 // a durability barrier or a terminal drain on the receiving thread.
1260 (void)fflush(stdout);
1261 if (ctx->snapshot_mode && ctx->has_tty) {
1262 int flush_fd = (ctx->tty_info.fd >= 0) ? ctx->tty_info.fd : STDOUT_FILENO;
1263 (void)terminal_flush(flush_fd);
1264 }
1265 }
1266
1267cleanup_frame:
1268 log_dev_every(3 * US_PER_SEC_INT, "FRAME_WRITE: Published in %.2f ms",
1269 (double)(time_get_ns() - frame_write_started_ns) / NS_PER_MS_INT);
1270
1271 // Clean up digital rain result if allocated
1272 SAFE_FREE(plain_frame);
1273 SAFE_FREE(zalgo_frame);
1274 if (rain_result) {
1275 SAFE_FREE(rain_result);
1276 }
1277 SAFE_FREE(visualization_frame);
1278}
#define APP_CALLBACK_VOID(callback_name)
int color_mode
Definition fft.c:25
#define SAFE_REALLOC(ptr, size, cast)
Definition common.h:285
#define MIN(a, b)
Return the minimum of two values.
Definition common.h:170
#define SAFE_MALLOC(size, cast)
Definition common.h:265
unsigned long long uint64_t
Definition common.h:63
#define MAX(a, b)
Return the maximum of two values.
Definition common.h:175
@ ERROR_BUFFER_FULL
@ ERROR_FILE_OPERATION
#define log_dev(...)
Log a DEV message (most verbose, development only).
Definition log/log.h:581
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:595
#define NS_PER_SEC_INT
Definition time.h:169
int time_pretty(uint64_t nanoseconds, int decimals, char *buffer, size_t buffer_size)
Format nanoseconds as a compact duration with configurable sub-minute precision.
Definition time.c:374
#define TIME_DURATION_BUFFER_SIZE
Definition time.h:40
#define NS_PER_MS_INT
Definition time.h:168
#define US_PER_SEC_INT
Definition time.h:173
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Platform-safe snprintf wrapper.
size_t platform_write_all(int fd, const void *buf, size_t count)
Write all bytes to a file descriptor, handling partial writes.
int splash_intro_done(void)
Signal that intro splash should end (first frame ready to render).
Definition splash.c:426
void splash_wait_for_animation(void)
Wait for splash animation thread to fully exit.
Definition splash.c:451
void strip_ansi_codes(const char *input, char *output, size_t output_size)
Strip ANSI escape codes from a string.
Definition string.c:374
void digital_rain_set_color_from_filter(digital_rain_t *rain, color_filter_t filter)
Set digital rain color from color filter.
char * digital_rain_apply(digital_rain_t *rain, const char *frame, float delta_time)
Apply digital rain effect to ASCII frame.
char * ansi_expand_rle(const char *input, size_t input_len)
Expand RLE escape sequences in a string.
Definition rle.c:14
#define log_debug_every(interval_us, fmt,...)
Rate-limited DEBUG logging.
Definition log/log.h:749
#define log_warn_every(interval_us, fmt,...)
Rate-limited WARN logging.
Definition log/log.h:755
#define log_dev_every(interval_us, fmt,...)
Rate-limited DEV logging.
Definition log/log.h:746
void signal_exit(void)
Definition main.c:253
bool g_snapshot_first_frame_rendered
uint64_t g_snapshot_first_frame_rendered_ns
Growable byte buffer for assembling one complete terminal frame.
bool has_tty
True if we have a valid TTY for interactive output.
tty_info_t tty_info
TTY information (file descriptor, path, ownership).
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.
atomic_t first_frame
First frame flag for logging control.
atomic_t output_failed
Stop submitting frames after the output consumer disappears.
terminal_capabilities_t caps
Detected terminal capabilities.
bool snapshot_mode
Snapshot mode enabled.
#define UI_MEDIA_MIN_COLS
Definition too_small.h:5
#define UI_MEDIA_MIN_ROWS
Definition too_small.h:6
void ui_controller_remove(ui_screen_t screen)
void ui_controller_title_activity(ui_title_activity_t activity)
void ui_controller_title_ready(void)
asciichat_error_t ui_controller_write(int fd, const char *data, size_t len)
asciichat_error_t ui_controller_present_retained(ui_screen_t screen, int fd, terminal_size_t minimum, const char *data, size_t len)
asciichat_error_t ui_controller_present(ui_screen_t screen, int fd, terminal_size_t minimum, const char *data, size_t len)
@ UI_TITLE_ACTIVITY_IDLE
#define UI_CONTROLLER_MAX_FRAME_BYTES
@ UI_SCREEN_MEDIA
@ UI_SCREEN_SPLASH
asciichat_error_t zalgo_apply(const char *input, size_t length, int level, size_t max_output_bytes, char **output)
Definition zalgo.c:69

References ansi_expand_rle(), APP_CALLBACK_VOID, ASCII_OTEL_SCOPE, asciichat_error_format(), ASCIICHAT_OK, session_display_ctx::caps, color_mode, session_display_ctx::digital_rain, digital_rain_apply(), digital_rain_set_color_from_filter(), ERROR_BUFFER_FULL, ERROR_FILE_OPERATION, ERROR_INVALID_PARAM, session_display_ctx::first_frame, g_snapshot_first_frame_rendered, g_snapshot_first_frame_rendered_ns, GET_OPTION, session_display_ctx::has_tty, session_display_ctx::initialized, session_display_ctx::last_frame_time_ns, log_debug, log_debug_every, log_dev, log_dev_every, log_warn_every, MAX, MIN, NS_PER_MS_INT, NS_PER_SEC_INT, session_display_ctx::output_failed, platform_write_all(), session_display_ctx::render_file, SAFE_FREE, SAFE_MALLOC, SAFE_REALLOC, safe_snprintf(), SET_ERRNO, signal_exit(), session_display_ctx::snapshot_mode, splash_intro_done(), splash_wait_for_animation(), strip_ansi_codes(), TIME_DURATION_BUFFER_SIZE, time_pretty(), session_display_ctx::tty_frame_buffer, session_display_ctx::tty_frame_capacity, session_display_ctx::tty_info, UI_CONTROLLER_MAX_FRAME_BYTES, ui_controller_present(), ui_controller_present_retained(), ui_controller_remove(), ui_controller_title_activity(), ui_controller_title_ready(), ui_controller_write(), UI_MEDIA_MIN_COLS, UI_MEDIA_MIN_ROWS, UI_SCREEN_MEDIA, UI_SCREEN_SPLASH, UI_TITLE_ACTIVITY_IDLE, US_PER_SEC_INT, and zalgo_apply().

Referenced by session_display_write_ascii(), and session_pipeline_run_main().