ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
Terminal Display

🖥️ Terminal rendering, TTY management, and frame output More...

Files

file  display.c
 💻 Client terminal display: TTY detection, frame rendering, and interactive/stdout output routing
 
file  display.h
 ascii-chat Client Display Management Interface
 

Functions

void display_set_context (session_display_ctx_t *display_ctx)
 Set the display context created by session_client_like framework.
 
int display_init ()
 Initialize what is necessary to display ascii frames.
 
bool display_has_tty ()
 Check if display has TTY capability.
 
void display_full_reset ()
 Perform full display reset.
 
void display_reset_for_new_connection ()
 Reset display state for new connection.
 
void display_disable_logging_for_first_frame ()
 Disable terminal logging for first frame.
 
void display_render_frame (const char *frame_data)
 Render ASCII frame to display.
 
void display_cleanup ()
 Cleanup display subsystem.
 

Detailed Description

🖥️ Terminal rendering, TTY management, and frame output

Terminal Display

Overview

The terminal display subsystem manages terminal initialization, ASCII frame rendering, TTY capability detection, and terminal cleanup.

Implementation: src/client/display.c, src/client/display.h

Initialization

int display_init(void)
{
// Check if stdout is a TTY
if (!isatty(STDOUT_FILENO)) {
log_warn("stdout is not a TTY - frames may not render correctly");
g_has_tty = false;
return 0;
}
g_has_tty = true;
// Initialize TTY for raw mode
if (tty_init(&g_tty_info) != 0) {
log_error("Failed to initialize TTY");
return -1;
}
return 0;
}
int display_init()
Initialize what is necessary to display ascii frames.
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
tty_info_t g_tty_info

Frame Rendering

void display_render_frame(const char *frame_data, bool is_snapshot_frame)
{
// Disable logging for first frame to prevent corruption
if (g_is_first_frame) {
g_is_first_frame = false;
}
if (g_has_tty) {
// Clear screen and home cursor
printf("\033[2J\033[H");
fflush(stdout);
}
// Output frame data
printf("%s", frame_data);
fflush(stdout);
// Snapshot mode: leave cursor at end
if (!is_snapshot_frame && g_has_tty) {
// Home cursor for next frame
printf("\033[H");
fflush(stdout);
}
}
void display_render_frame(const char *frame_data)
Render ASCII frame to display.
void display_disable_logging_for_first_frame()
Disable terminal logging for first frame.

First Frame Optimization

Problem: Log messages corrupt first frame display

Solution:

  • Suppress terminal logging before first frame render
  • Re-enable logging after first frame displayed
  • Prevents log messages from appearing in frame area
{
// Temporarily disable terminal logging
logging_set_terminal_enabled(false);
// Will be re-enabled after first frame
}

Display Reset

Full Reset

{
if (g_has_tty) {
printf("\033[2J\033[H"); // Clear screen and home cursor
fflush(stdout);
}
}
void display_full_reset()
Perform full display reset.

Connection Reset

{
// Reset first frame flag for reconnection
g_is_first_frame = true;
}
void display_reset_for_new_connection()
Reset display state for new connection.

Cleanup

void display_cleanup(void)
{
if (g_has_tty) {
// Restore terminal settings
tty_restore(&g_tty_info);
// Clear screen and show cursor
printf("\033[2J\033[H\033[?25h");
fflush(stdout);
}
}
void display_cleanup()
Cleanup display subsystem.
See also
src/client/display.c
src/client/display.h
lib/platform/terminal.h
Client Overview

Function Documentation

◆ display_cleanup()

void display_cleanup ( )

#include <display.c>

Cleanup display subsystem.

Destroys session display context and releases all resources including keyboard input handling.

Restores terminal to original state and releases display resources. Must be called before process exit to ensure terminal is left in a usable state.

The cleanup process:

  1. Re-enables terminal echo (if it was disabled)
  2. Re-enables canonical mode (line buffering)
  3. Moves cursor to clean location (below last frame)
  4. Restores original terminal color (if color was used)
  5. Restores original terminal settings
  6. Releases terminal state structures

Critical: Must be called even if display_init() failed - partial init may need cleanup.

See also
display_init "Initialize display subsystem"

Definition at line 363 of file src/client/display.c.

363 {
364 // Cleanup keyboard input if it was initialized
365 if (g_keyboard_enabled) {
367 g_keyboard_enabled = false;
368 }
369
370 // Cleanup optional local media capture context if it was created
371 if (g_display_capture_ctx) {
372 session_capture_destroy(g_display_capture_ctx);
373 g_display_capture_ctx = NULL;
374 }
375
376 // Display context is now managed by session_client_like framework
377 // Do not destroy it here - the framework will handle cleanup
378 g_display_ctx = NULL;
379}
void keyboard_destroy(void)
Cleanup keyboard input system and restore terminal.
void session_capture_destroy(session_capture_ctx_t *ctx)
Destroy session capture context and free resources.

References keyboard_destroy(), and session_capture_destroy().

◆ display_disable_logging_for_first_frame()

void display_disable_logging_for_first_frame ( )

#include <display.c>

Disable terminal logging for first frame.

Disables terminal logging before clearing the display for the first frame to prevent log output from interfering with ASCII display.

Temporarily disables terminal logging output to prevent debug/info messages from corrupting the first rendered ASCII frame.

Call sequence:

  1. display_disable_logging_for_first_frame() - before rendering frame
  2. display_render_frame() - renders frame with logging disabled
  3. Logging re-enabled automatically after first frame

Why: The first frame render clears the terminal and draws the ASCII grid. If logging is active, log messages would overwrite or corrupt the grid.

See also
display_render_frame "Render ASCII frame"

Definition at line 290 of file src/client/display.c.

290 {
291 // Atomically check if this is the first frame and mark as not-first
292 // Avoids TOCTOU race where multiple threads could both think they're first
293 bool was_first_frame = atomic_exchange_bool(&g_is_first_frame_of_connection, false);
294
295 if (was_first_frame) {
296 // Disable terminal logging before clearing display and rendering first frame
298
299 // Signal the intro splash screen to stop - first frame is ready to render
301 }
302}
bool atomic_exchange_bool(atomic_t *a, bool new_value)
Atomically exchange a boolean and return the old value.
Definition atomic.c:303
void log_set_terminal_output(bool enabled)
Control stderr output to terminal.
Definition log/log.c:700
int splash_intro_done(void)
Signal that intro splash should end (first frame ready to render)
Definition splash.c:693

References atomic_exchange_bool(), log_set_terminal_output(), and splash_intro_done().

Referenced by display_render_frame().

◆ display_full_reset()

void display_full_reset ( )

#include <display.c>

Perform full display reset.

Executes complete terminal reset sequence for clean display state. Safe to call multiple times and handles mode-specific behavior.

Clears the terminal and resets display state. Called by server clear commands or on SIGWINCH terminal resize events.

The reset:

  1. Clears the terminal screen
  2. Resets cursor to home position
  3. Resets text attributes (color, bold, etc.)
  4. Resets first-frame tracking (re-suppresses logging on next frame)
See also
display_reset_for_new_connection "Reset for new connection"

Definition at line 264 of file src/client/display.c.

264 {
265 if (g_display_ctx && !should_exit()) {
266 session_display_reset(g_display_ctx);
267 }
268}
void session_display_reset(session_display_ctx_t *ctx)
Reset terminal to default state.
bool should_exit(void)
Definition misc.c:131

References session_display_reset(), and should_exit().

Referenced by server_connection_lost().

◆ display_has_tty()

bool display_has_tty ( )

#include <display.c>

Check if display has TTY capability.

Returns
true if TTY is available for interactive output, false otherwise

Checks if stdout is connected to a terminal (TTY) or redirected to a file/pipe. The client requires TTY for interactive rendering unless in snapshot or test mode.

Returns
true if TTY available (interactive terminal), false if piped/redirected
Note
Test pattern and snapshot modes can proceed without TTY, but real webcam streaming requires a terminal for rendering.

Definition at line 252 of file src/client/display.c.

252 {
253 return session_display_has_tty(g_display_ctx);
254}
bool session_display_has_tty(session_display_ctx_t *ctx)
Check if display has a TTY (terminal) available.

References session_display_has_tty().

◆ display_init()

int display_init ( )

#include <display.c>

Initialize what is necessary to display ascii frames.

Initialize display subsystem.

Creates session display context for TTY management and frame rendering. Must be called once during client startup.

Returns
0 on success, negative on error

Sets up terminal for ASCII rendering and detects terminal capabilities.

Initialization steps:

  1. Checks if stdout is a TTY (error if not, unless snapshot mode or test pattern)
  2. Detects terminal color depth (24-bit true color, 256-color, 16-color, or ANSI)
  3. Detects Unicode/UTF-8 support
  4. Detects half-block character support (for 2x vertical resolution)
  5. Sets raw terminal mode (disables echo, canonical mode)
  6. Saves original terminal settings for restoration

Options affecting initialization:

  • --color: Force enable/disable color
  • --palette: Select ASCII rendering palette
  • --width, --height: Terminal dimensions (auto-detected if not set)
  • --half-block: Enable 2x vertical resolution using half-block chars
Returns
0 on success, negative on error:
  • ERROR_TERMINAL: TTY error or terminal control failed
  • ERROR_DISPLAY: Terminal dimensions invalid
Note
Called during client initialization before connection attempts. Must be paired with display_cleanup() for proper terminal restoration.
See also
display_cleanup "Cleanup display and restore terminal"

Definition at line 180 of file src/client/display.c.

180 {
181 // Build display configuration from options
182 session_display_config_t config = {
183 .snapshot_mode = GET_OPTION(snapshot_mode),
184 .palette_type = GET_OPTION(palette_type),
185 .custom_palette = GET_OPTION(palette_custom_set) ? GET_OPTION(palette_custom) : NULL,
186 .color_mode = TERM_COLOR_AUTO // Will be overridden by command-line options
187 };
188
189 // Create display context using session library
190 g_display_ctx = session_display_create(&config);
191 if (!g_display_ctx) {
192 SET_ERRNO(ERROR_DISPLAY, "Failed to initialize display");
193 return -1;
194 }
195
196 // Check if client mode should load local media (--file or --url)
197 const char *media_url = GET_OPTION(media_url);
198 const char *media_file = GET_OPTION(media_file);
199
200 if ((media_url && strlen(media_url) > 0) || (media_file && strlen(media_file) > 0)) {
201 // Client mode with local media file/URL - create capture context for keyboard controls
202 session_capture_config_t capture_config = {0};
203 capture_config.target_fps = 60;
204 capture_config.resize_for_network = false;
205 capture_config.should_exit_callback = NULL;
206 capture_config.callback_data = NULL;
207
208 if (media_url && strlen(media_url) > 0) {
209 capture_config.type = MEDIA_SOURCE_FILE;
210 capture_config.path = media_url;
211 } else {
212 if (strcmp(media_file, "-") == 0) {
213 capture_config.type = MEDIA_SOURCE_STDIN;
214 capture_config.path = NULL;
215 } else {
216 capture_config.type = MEDIA_SOURCE_FILE;
217 capture_config.path = media_file;
218 }
219 }
220
221 capture_config.loop = GET_OPTION(media_loop);
222 capture_config.initial_seek_timestamp = GET_OPTION(media_seek_timestamp);
223
224 g_display_capture_ctx = session_capture_create(&capture_config);
225 if (!g_display_capture_ctx) {
226 log_warn("Failed to create capture context for local media - keyboard seek/pause disabled");
227 g_display_capture_ctx = NULL;
228 }
229 }
230
231 // Keyboard is pre-initialized from asciichat_shared_init()
232 // Only enable keyboard input if stdin is a TTY (avoid interfering with piped/redirected I/O)
233 g_keyboard_enabled = terminal_is_stdin_tty();
234
235 // Register display atomics with named debug registry
236 static bool display_atomics_registered = false;
237 if (!display_atomics_registered) {
238 NAMED_REGISTER_ATOMIC(&g_is_first_frame_of_connection, "first_frame_of_display_connection", NULL);
239 display_atomics_registered = true;
240 }
241
242 return 0;
243}
#define NAMED_REGISTER_ATOMIC(a, name, parent_ptr)
Register an atomic_t with automatic format specifier.
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
@ ERROR_DISPLAY
@ MEDIA_SOURCE_STDIN
Piped or redirected input.
Definition source.h:84
@ MEDIA_SOURCE_FILE
Media file (video/audio)
Definition source.h:83
#define GET_OPTION(field)
Safely get a specific option field (lock-free read)
session_display_ctx_t * session_display_create(const session_display_config_t *config)
Create a new session display context.
session_capture_ctx_t * session_capture_create(const session_capture_config_t *config)
Legacy function - creates either mirror or network capture based on options.
bool terminal_is_stdin_tty(void)
Check if stdin is connected to a TTY.
Configuration for session capture context.
media_source_type_t type
Media source type (WEBCAM, FILE, STDIN, TEST)
const char * path
Device index (for webcam) or file path (for file sources)
uint32_t target_fps
Target frame rate in FPS (e.g., 60 for display, 144 for network)
double initial_seek_timestamp
Seek to this timestamp after opening media (0.0 = no seek)
session_capture_should_exit_fn should_exit_callback
Optional: callback to check if initialization should be cancelled (e.g., shutdown signal)
void * callback_data
Opaque data passed to should_exit_callback.
bool resize_for_network
Resize frames to network-optimal dimensions (MAX_FRAME_WIDTH x MAX_FRAME_HEIGHT)
bool loop
Enable loop playback for file sources.
Configuration for session display context.
bool snapshot_mode
Enable snapshot mode (single frame capture)
@ TERM_COLOR_AUTO
Auto-detect color support from terminal capabilities.
Definition terminal.h:580

References session_capture_config_t::callback_data, ERROR_DISPLAY, GET_OPTION, session_capture_config_t::initial_seek_timestamp, log_warn, session_capture_config_t::loop, MEDIA_SOURCE_FILE, MEDIA_SOURCE_STDIN, NAMED_REGISTER_ATOMIC, session_capture_config_t::path, session_capture_config_t::resize_for_network, session_capture_create(), session_display_create(), SET_ERRNO, session_capture_config_t::should_exit_callback, session_display_config_t::snapshot_mode, session_capture_config_t::target_fps, TERM_COLOR_AUTO, terminal_is_stdin_tty(), and session_capture_config_t::type.

◆ display_render_frame()

void display_render_frame ( const char *  frame_data)

#include <display.c>

Render ASCII frame to display.

Render ASCII frame to display

Uses session display library for frame output routing based on display mode and snapshot requirements. Also polls for keyboard input for interactive controls (volume, color mode, flip).

Parameters
frame_dataASCII frame data to render
is_snapshot_frameWhether this is the final snapshot frame

Outputs the ASCII frame data to the terminal. This is called by the protocol thread when receiving PACKET_TYPE_ASCII_FRAME from the server.

The function:

  1. Verifies TTY is still available
  2. If first frame: disables logging, clears terminal
  3. Outputs frame data to stdout
  4. Flushes output to ensure visible immediately
  5. Re-enables logging after first frame

In snapshot mode, exits immediately after rendering.

Parameters
frame_dataASCII frame data to render (null-terminated string or binary data)
Note
Called by protocol thread - must be thread-safe with respect to terminal state (stdout writes are atomic for single call).
See also
display_init "Initialize display subsystem"

Definition at line 316 of file src/client/display.c.

316 {
317 if (!frame_data) {
318 SET_ERRNO(ERROR_INVALID_PARAM, "Attempted to render NULL frame data");
319 return;
320 }
321
322 // Stop splash screen animation on first frame
323 // This must be called BEFORE any frame rendering to prevent splash/frame flickering
325
326 // Render help screen if active (suppresses frame rendering)
327 if (keyboard_help_is_active(g_display_ctx)) {
328 keyboard_help_render(g_display_ctx);
329
330 // Still poll keyboard for interactive controls while help is visible
331 if (g_keyboard_enabled) {
333 if (key != KEY_NONE) {
334 session_handle_keyboard_input(g_display_capture_ctx, g_display_ctx, key);
335 }
336 }
337 return; // Skip normal frame rendering
338 }
339
340 // Use session display library for frame rendering
341 session_display_render_frame(g_display_ctx, frame_data);
342
343 // Poll for keyboard input and handle interactive controls
344 // - If client mode has local media (--file/--url), pass capture context for full controls
345 // (seek, pause, play, volume, color mode, flip)
346 // - If client mode is network-only, pass NULL (volume, color mode, flip work; seek/pause ignored)
347 if (g_keyboard_enabled) {
349 if (key != KEY_NONE) {
350 session_handle_keyboard_input(g_display_capture_ctx, g_display_ctx, key);
351 }
352 }
353}
@ ERROR_INVALID_PARAM
keyboard_key_t keyboard_read_nonblocking(void)
Read next keyboard input without blocking.
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
bool keyboard_help_is_active(session_display_ctx_t *ctx)
Check if keyboard help is currently active.
void keyboard_help_render(session_display_ctx_t *ctx)
Render keyboard help centered on terminal.
void session_handle_keyboard_input(session_capture_ctx_t *capture, session_display_ctx_t *display, keyboard_key_t key)
Handle keyboard input in a session.
void session_display_render_frame(session_display_ctx_t *ctx, const char *frame_data)
Render an ASCII frame to the terminal.

References display_disable_logging_for_first_frame(), ERROR_INVALID_PARAM, KEY_NONE, keyboard_help_is_active(), keyboard_help_render(), keyboard_read_nonblocking(), session_display_render_frame(), session_handle_keyboard_input(), and SET_ERRNO.

◆ display_reset_for_new_connection()

void display_reset_for_new_connection ( )

#include <display.c>

Reset display state for new connection.

Resets the first frame tracking flag to prepare for a new connection. Call this when starting a new connection to reset first frame tracking.

Call this when starting a new connection after reconnection to reset first-frame tracking and clear any residual state from the previous connection.

The reset:

  1. Clears the terminal screen (like display_full_reset)
  2. Resets first-frame flag to suppress logging on first frame of new connection
  3. Prepares for fresh frame rendering
See also
display_reset_for_new_connection "Reset for new connection"

Definition at line 278 of file src/client/display.c.

278 {
279 atomic_store_bool(&g_is_first_frame_of_connection, true);
280}
void atomic_store_bool(atomic_t *a, bool value)
Atomically store a boolean value.
Definition atomic.c:177

References atomic_store_bool().

Referenced by protocol_start_connection().

◆ display_set_context()

void display_set_context ( session_display_ctx_t *  display_ctx)

#include <display.c>

Set the display context created by session_client_like framework.

Set the display context (framework integration)

Client mode doesn't create its own display context anymore. Instead, session_client_like_run() creates it and passes it to client_run(). This setter allows client_run() to make the framework-created context available to protocol threads that need to render frames.

Parameters
display_ctxDisplay context created by framework (may be NULL)

Called by client_run() to pass the display context created by session_client_like_run() to the display module so protocol threads can render frames.

Parameters
display_ctxDisplay context from framework (may be NULL)

Definition at line 162 of file src/client/display.c.

162 {
163 g_display_ctx = display_ctx;
164}