ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
client_like.h File Reference

Shared initialization and teardown for client-like modes (mirror, client, discovery) More...

Go to the source code of this file.

Data Structures

struct  session_client_like_config
 

Typedefs

typedef struct terminal_fd_reader_s terminal_fd_reader_t
 
typedef enum session_client_like_kind session_client_like_kind_t
 
typedef asciichat_error_t(* session_client_like_run_fn) (session_capture_ctx_t *capture, session_display_ctx_t *display, void *user_data)
 
typedef struct session_client_like_config session_client_like_config_t
 

Enumerations

enum  session_client_like_kind { SESSION_CLIENT_LIKE_KIND_MIRROR = 0 , SESSION_CLIENT_LIKE_KIND_CLIENT , SESSION_CLIENT_LIKE_KIND_DISCOVERY }
 

Functions

terminal_fd_reader_t * session_client_like_get_stdin_reader (void)
 
asciichat_error_t session_client_like_run (const session_client_like_config_t *config)
 

Variables

bool(*)(void *) session_client_like_get_render_should_exit (void)
 

Detailed Description

Shared initialization and teardown for client-like modes (mirror, client, discovery)

Provides a unified interface for running client-like modes that share common initialization patterns: media source selection, FPS probing, audio initialization, display creation, splash screen management, keepawake handling, and cleanup.

Each mode registers mode-specific callbacks to handle initialization, the main render loop, and keyboard input. The shared layer handles all the boilerplate.

Modes Supported

  • Mirror mode: Local webcam/media playback without networking
  • Client mode: Network client with per-connection initialization
  • Discovery mode: P2P mode with role negotiation

Shared Responsibilities

This layer automatically handles:

  • Terminal output management (stdout piping detection)
  • Keepawake system (platform sleep prevention)
  • Splash screen lifecycle and animation
  • Media source selection and FPS probing
  • Audio initialization and lifecycle
  • Display context creation and management
  • Render loop execution
  • Proper cleanup ordering (critical for PortAudio)

Mode-Specific Responsibilities

Mode files provide:

  • Main loop callback (run_fn) that calls session_render_loop() or custom loop
  • Optional network interrupt callback for socket shutdown
  • Optional custom exit condition for additional shutdown criteria
  • Keyboard handler for interactive controls
  • Terminal behavior preferences (newline on exit)

Memory and Lifecycle

All allocations (capture, display, audio) are owned and cleaned up by session_client_like_run(). Mode callbacks receive initialized, ready-to-use contexts and should not attempt to free them.

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

Definition in file client_like.h.

Typedef Documentation

◆ session_client_like_config_t

Configuration for session_client_like_run()

All media, display, audio, FPS, and snapshot settings are read automatically from GET_OPTION() inside the function. Callers only provide the fields below that differ per mode.

◆ session_client_like_kind_t

◆ session_client_like_run_fn

typedef asciichat_error_t(* session_client_like_run_fn) (session_capture_ctx_t *capture, session_display_ctx_t *display, void *user_data)

Mode-specific main loop callback

Called after all shared initialization is complete (capture opened, audio started, display ready, splash done). The mode runs its main loop here and returns when finished. The shared teardown runs after this returns.

Typical pattern:

// For mirror/discovery participant:
return session_render_loop(capture, display, exit_check_fn, NULL, NULL,
keyboard_handler, user_data);
// For client mode (protocol-driven):
while (!should_exit()) {
// protocol loop...
}
return ASCIICHAT_OK;
@ ASCIICHAT_OK
Definition error_codes.h:51
asciichat_error_t session_render_loop(session_capture_ctx_t *capture, session_display_ctx_t *display, session_should_exit_fn should_exit, session_capture_fn capture_cb, session_sleep_for_frame_fn sleep_cb, session_keyboard_handler_fn keyboard_handler, void *user_data)
Unified render loop for all display modes.
bool should_exit(void)
Definition misc.c:131
Parameters
captureInitialized capture context. Never NULL.
displayInitialized display context. Never NULL.
user_dataUser data passed through from config.run_user_data
Returns
ASCIICHAT_OK, or error code to abort (cleanup still runs)

Definition at line 98 of file client_like.h.

◆ terminal_fd_reader_t

Definition at line 59 of file client_like.h.

Enumeration Type Documentation

◆ session_client_like_kind

Enumerator
SESSION_CLIENT_LIKE_KIND_MIRROR 
SESSION_CLIENT_LIKE_KIND_CLIENT 
SESSION_CLIENT_LIKE_KIND_DISCOVERY 

Definition at line 63 of file client_like.h.

63 {
@ SESSION_CLIENT_LIKE_KIND_CLIENT
Definition client_like.h:65
@ SESSION_CLIENT_LIKE_KIND_DISCOVERY
Definition client_like.h:66
@ SESSION_CLIENT_LIKE_KIND_MIRROR
Definition client_like.h:64
enum session_client_like_kind session_client_like_kind_t

Function Documentation

◆ session_client_like_run()

asciichat_error_t session_client_like_run ( const session_client_like_config_t *  config)

Run a client-like mode with fully shared initialization and teardown

This function orchestrates the complete lifecycle of client-like modes:

Initialization (automatic from GET_OPTION())

Terminal:

  • Forces stderr when stdout is piped (terminal_should_force_stderr)
  • Disables terminal logging during rendering, re-enables on exit
  • Handles snapshot mode log visibility

Keepawake:

  • Validates –keepawake / –no-keepawake mutual exclusivity
  • Calls platform_enable_keepawake() unless –no-keepawake set
  • Disables keepawake on exit

Splash Screen:

  • Creates temporary display context
  • Calls splash_intro_start() to show splash animation
  • Holds splash visible during media probing and audio init
  • Calls splash_intro_done() when capture and display are ready

Media / Webcam (session_capture_ctx_t):

  • Selects source type from options (priority order):
    1. –url → MEDIA_SOURCE_FILE (FFmpeg handles http/rtsp/rtmp)
    2. –file → MEDIA_SOURCE_FILE; "-" → MEDIA_SOURCE_STDIN
    3. –test-pattern → MEDIA_SOURCE_TEST
    4. (default) → MEDIA_SOURCE_WEBCAM
  • FPS: uses GET_OPTION(fps) if > 0, otherwise probes with temporary media source
  • Applies GET_OPTION(media_seek_timestamp) as initial seek
  • Applies GET_OPTION(media_loop) for file looping
  • Calls session_capture_create() to open webcam or media file
  • Destroys capture on exit

Audio (audio_context_t):

Display (session_display_ctx_t):

  • Calls session_display_create() with snapshot and color settings
  • Passes audio context if initialized
  • Destroys display on exit

Mode-Specific Loop

Calls config->run_fn(capture, display, config->run_user_data) once all initialization is complete. This is where each mode calls session_render_loop() or runs its own protocol-thread-driven loop.

Cleanup (always runs, even on error)

Cleanup sequence (order is critical):

  1. Re-enable terminal logging
  2. audio_terminate_portaudio_final() - Free PortAudio device resources FIRST
  3. audio_stop_duplex() + audio_destroy() + SAFE_FREE
  4. session_display_destroy()
  5. session_capture_destroy()
  6. media_source_destroy() - Free capture buffers and source state
  7. session_log_buffer_destroy()
  8. platform_disable_keepawake()
  9. Print '
    ' to STDOUT_FILENO if print_newline_on_tty_exit && TTY
Parameters
configMode configuration. Must not be NULL. run_fn must not be NULL.
Returns
ASCIICHAT_OK on success, or the first error code from initialization or from run_fn. Cleanup always runs regardless of return value.

Definition at line 135 of file client_like.c.

135 {
136 log_info("session_client_like_run: ENTRY");
137 if (!config || !config->run_fn) {
138 return SET_ERRNO(ERROR_INVALID_PARAM, "config or run_fn is NULL");
139 }
140 log_info("session_client_like_run: config validated");
141
142 // Store config globally for adapter callbacks
143 g_current_config = config;
144
145 // Store the render should_exit adapter globally so mode-specific run_fn can access it
146 g_render_should_exit = display_should_exit_adapter;
147
149
150 // Keep track of what's been initialized for cleanup
151 session_capture_ctx_t *capture = NULL;
152 session_display_ctx_t *display = NULL;
153 audio_context_t *audio_ctx = NULL;
154 media_source_t *probe_source = NULL;
155 bool audio_available = false;
156
157 // ============================================================================
158 // SETUP: Terminal and Logging
159 // ============================================================================
160
161 log_debug("session_client_like_run(): Setting up terminal and logging");
162
163 // Force stderr when stdout is piped (prevents ASCII corruption in output stream)
164 bool should_force_stderr = terminal_should_force_stderr();
165 log_debug("terminal_should_force_stderr()=%d", should_force_stderr);
166
167 if (should_force_stderr) {
168 // Redirect logs to stderr to prevent corruption of stdout (for pipes)
169 // But keep terminal output enabled so we can see initialization errors
171 }
172
173 // Note: Force all logging to stderr if encoding to pipe to prevent corrupting binary video output
174 // (implemented via should_force_stderr check above)
175 const char *render_file_early = GET_OPTION(render_file);
176 log_info("[CLIENT_LIKE_RUN] render_file_early=%s (ptr=%p)", render_file_early ? render_file_early : "(null)",
177 (void *)render_file_early);
178 if (render_file_early && render_file_early[0] != '\0' &&
179 (strcmp(render_file_early, "-") == 0 || strcmp(render_file_early, "pipe:") == 0)) {
180 log_info("--render-file=- will output binary video to stdout, logging sent to stderr");
181 }
182
183 // ============================================================================
184 // SETUP: Keepawake System
185 // ============================================================================
186
187 log_debug("session_client_like_run(): Validating keepawake options");
188
189 // Validate mutual exclusivity
190 bool enable_ka = GET_OPTION(enable_keepawake);
191 bool disable_ka = GET_OPTION(disable_keepawake);
192 log_debug("enable_keepawake=%d, disable_keepawake=%d", enable_ka, disable_ka);
193
194 if (enable_ka && disable_ka) {
195 result = SET_ERRNO(ERROR_INVALID_PARAM, "--keepawake and --no-keepawake are mutually exclusive");
196 goto cleanup;
197 }
198
199 log_debug("session_client_like_run(): Enabling keepawake if needed");
200
201 // Enable keepawake unless explicitly disabled
202 if (!disable_ka) {
204 }
205
206 log_debug("session_client_like_run(): Keepawake setup complete");
207
208 result = validate_mirror_webcam_selection(config);
209 if (result != ASCIICHAT_OK) {
210 goto cleanup;
211 }
212
213 // ============================================================================
214 // SETUP: Display Context (before splash for unified TTY initialization)
215 // ============================================================================
216
217 // Create display context early to share TTY initialization between splash and rendering
218 // This prevents double TTY init which was corrupting the terminal state
219 session_display_config_t display_config = {0};
220 display_config.snapshot_mode = GET_OPTION(snapshot_mode);
221 display_config.palette_type = GET_OPTION(palette_type);
222 display_config.custom_palette = GET_OPTION(palette_custom_set) ? GET_OPTION(palette_custom) : NULL;
223 display_config.color_mode = TERM_COLOR_AUTO;
224 display_config.should_exit_callback = display_should_exit_adapter;
225 display_config.callback_data = NULL;
226 display_config.defer_render_file = true;
227
228 log_debug("[SETUP_DISPLAY_EARLY] Creating display context (before splash)");
229 display = session_display_create(&display_config);
230 if (!display) {
231 log_debug("[SETUP_DISPLAY_EARLY] session_display_create() returned NULL");
232 log_fatal("Failed to initialize display context");
233 result = ERROR_DISPLAY;
234 goto cleanup;
235 }
236 log_debug("[SETUP_DISPLAY_EARLY] Display context created, will use for splash");
237
238 // Register global display context for signal handlers (Ctrl+C help screen closure)
239 // This makes the context available to signal handlers for the entire app lifetime
241
242 // ============================================================================
243 // SETUP: Splash Screen (using unified display context)
244 // ============================================================================
245
246 log_debug("session_client_like_run(): Starting splash with display context");
247
248 // Use the display context we just created for splash animation
249 splash_intro_start(display);
250 log_debug("session_client_like_run(): splash_intro_start() returned");
251 if (splash_is_running()) {
253 }
254
255 // Detect if we're using media vs webcam (needed for splash timing)
256 const char *media_url = GET_OPTION(media_url);
257 const char *media_file = GET_OPTION(media_file);
258 bool has_media = (media_url && strlen(media_url) > 0) || (media_file && strlen(media_file) > 0);
259
260 // Show splash briefly for webcam, but skip sleep for media (which takes time anyway)
261 if (!has_media && !GET_OPTION(snapshot_mode)) {
262 APP_CALLBACK_VOID(platform_pump_events);
264 }
265 log_debug("session_client_like_run(): After splash sleep");
266
267 // Restore stderr now that splash animation and post-splash logging are done
268 // This allows logs to appear on screen again after this point
270
271 // Wait for background update check to finish and show prompt if update available
273 if (update_banner_has_update() && !GET_OPTION(snapshot_mode)) {
274 bool wants_update = update_banner_show_prompt(display);
275 if (wants_update) {
277 return ASCIICHAT_OK;
278 }
280 }
281
282 // ============================================================================
283 // SETUP: Terminal Logging in Snapshot Mode
284 // ============================================================================
285
286 log_debug("session_client_like_run(): About to disable terminal logging (snapshot=%d, discovery=%d)",
287 GET_OPTION(snapshot_mode), config->discovery != NULL);
288 // For discovery mode, skip disabling logging initially - we'll handle it after role is determined
289 // Discovery participants should show logs, only hosts should have logs disabled for rendering
290 bool should_disable_logging = !GET_OPTION(snapshot_mode) && (config->discovery == NULL);
291 if (should_disable_logging) {
292 log_debug("session_client_like_run(): Calling log_set_terminal_output(false)...");
294 log_debug("session_client_like_run(): RETURNED from log_set_terminal_output(false)");
295 } else {
296 log_debug("session_client_like_run(): Skipping log disable (snapshot=%d or discovery=%d)",
297 GET_OPTION(snapshot_mode), config->discovery != NULL);
298 }
299 log_debug("session_client_like_run(): Terminal logging setup complete, starting media source setup");
300
301 // ============================================================================
302 // SETUP: Media Source Selection and FPS Probing
303 // ============================================================================
304
305 log_debug("session_client_like_run(): Initializing capture config");
306 session_capture_config_t capture_config = {0};
308 capture_config.should_exit_callback = capture_should_exit_adapter;
309 capture_config.callback_data = NULL;
310
311 // Determine FPS explicitly set by user
312 log_debug("session_client_like_run(): Getting FPS option");
313 int user_fps = GET_OPTION(fps);
314 bool fps_explicitly_set = GET_OPTION(fps_explicitly_set);
315 log_debug("session_client_like_run(): FPS=%d (explicitly_set=%d)", user_fps, fps_explicitly_set);
316
317 // Select media source based on options (priority order)
318 log_debug("session_client_like_run(): Getting media_url option");
319 const char *media_url_val = GET_OPTION(media_url);
320 log_debug("session_client_like_run(): Getting media_file option");
321 const char *media_file_val = GET_OPTION(media_file);
322 log_debug("session_client_like_run(): media_url=%s, media_file=%s", media_url_val ? media_url_val : "(null)",
323 media_file_val ? media_file_val : "(null)");
324
325 if (media_url_val && strlen(media_url_val) > 0) {
326 // Network URL takes priority
327 log_info("Using network URL: %s (webcam disabled)", media_url_val);
328 capture_config.type = MEDIA_SOURCE_FILE;
329 capture_config.path = media_url_val;
330 capture_config.loop = false; // Network URLs cannot be looped
331
332 if (fps_explicitly_set) {
333 capture_config.target_fps = (uint32_t)user_fps;
334 log_info("Using user-specified FPS: %u", capture_config.target_fps);
335 } else {
336 // Always probe FPS for HTTP URLs, even in snapshot mode
337 // (snapshot mode needs correct FPS to capture right number of frames during snapshot_delay window)
338 log_debug("Probing FPS for HTTP URL");
339 probe_source = media_source_create(MEDIA_SOURCE_FILE, media_url_val);
340 if (probe_source) {
341 double url_fps = media_source_get_video_fps(probe_source);
342 log_info("Detected HTTP stream video FPS: %.1f", url_fps);
343 if (url_fps > 0.0) {
344 capture_config.target_fps = (uint32_t)(url_fps + 0.5);
345 } else {
346 log_warn("FPS detection failed for HTTP stream, using default 60 FPS");
347 capture_config.target_fps = 60;
348 }
349 } else {
350 log_warn("Failed to create probe source for HTTP stream, using default 60 FPS");
351 capture_config.target_fps = 60;
352 }
353 }
354 } else if (media_file_val && strlen(media_file_val) > 0) {
355 // Local file or stdin
356 log_info("DEBUG: media_file_val='%s', strcmp result=%d", media_file_val, strcmp(media_file_val, "-"));
357 if (strcmp(media_file_val, "-") == 0) {
358 log_info("Using stdin for media streaming (webcam disabled)");
359 capture_config.type = MEDIA_SOURCE_STDIN;
360 capture_config.path = NULL;
361 capture_config.loop = false;
362 if (fps_explicitly_set) {
363 capture_config.target_fps = (uint32_t)user_fps;
364 } else {
365 log_debug("Probing FPS from buffered stdin media");
366 probe_source = media_source_create(MEDIA_SOURCE_STDIN, NULL);
367 if (probe_source) {
368 double stdin_fps = media_source_get_video_fps(probe_source);
369 log_info("Detected stdin video FPS: %.1f", stdin_fps);
370 capture_config.target_fps = stdin_fps > 0.0 ? (uint32_t)(stdin_fps + 0.5) : 60;
371 } else {
372 log_warn("Failed to probe stdin FPS, using default 60 FPS");
373 capture_config.target_fps = 60;
374 }
375 }
376 } else {
377 log_info("Using media file: %s (webcam disabled)", media_file_val);
378 capture_config.type = MEDIA_SOURCE_FILE;
379 capture_config.path = media_file_val;
380 capture_config.loop = GET_OPTION(media_loop);
381
382 if (fps_explicitly_set) {
383 capture_config.target_fps = (uint32_t)user_fps;
384 log_info("Using user-specified FPS: %u", capture_config.target_fps);
385 } else {
386 // Always probe FPS for local files, even in snapshot mode
387 // (snapshot mode needs correct FPS to capture right number of frames during snapshot_delay window)
388 log_debug("Probing FPS for local file");
389 probe_source = media_source_create(MEDIA_SOURCE_FILE, media_file_val);
390 if (probe_source) {
391 double file_fps = media_source_get_video_fps(probe_source);
392 log_info("Detected file video FPS: %.1f", file_fps);
393 if (file_fps > 0.0) {
394 capture_config.target_fps = (uint32_t)(file_fps + 0.5);
395 } else {
396 log_warn("FPS detection failed, using default 60 FPS");
397 capture_config.target_fps = 60;
398 }
399 } else {
400 log_warn("Failed to create probe source for FPS detection, using default 60 FPS");
401 capture_config.target_fps = 60;
402 }
403 }
404 }
405 } else if (GET_OPTION(test_pattern)) {
406 // Test pattern
407 log_debug("session_client_like_run(): Using test pattern");
408 log_info("Using test pattern");
409 capture_config.type = MEDIA_SOURCE_TEST;
410 capture_config.path = NULL;
411 capture_config.target_fps = fps_explicitly_set ? (uint32_t)user_fps : 60;
412 capture_config.loop = false;
413 } else {
414 // Default: Try webcam if stdout is a TTY, otherwise use test pattern
415 // (webcam + piped output often fails, test pattern is reliable fallback)
416 bool stdout_is_tty = terminal_is_stdout_tty();
417 if (stdout_is_tty) {
418 // Normal terminal mode: use webcam
419 log_debug("session_client_like_run(): Stdout is TTY, using local webcam");
420 log_info("Using local webcam");
421 capture_config.type = MEDIA_SOURCE_WEBCAM;
422 static char webcam_index_str[32];
423 safe_snprintf(webcam_index_str, sizeof(webcam_index_str), "%u", GET_OPTION(webcam_index));
424 capture_config.path = webcam_index_str;
425 log_info("Using webcam device index %u", GET_OPTION(webcam_index));
426 capture_config.target_fps = fps_explicitly_set ? (uint32_t)user_fps : 60;
427 capture_config.loop = false;
428 } else {
429 // Piped output mode: use test pattern (webcam + pipes often fails)
430 log_debug("session_client_like_run(): Stdout is piped, defaulting to test pattern for reliability");
431 log_info("Stdout is piped - using test pattern instead of webcam");
432 capture_config.type = MEDIA_SOURCE_TEST;
433 capture_config.path = NULL;
434 capture_config.target_fps = fps_explicitly_set ? (uint32_t)user_fps : 60;
435 capture_config.loop = false;
436 }
437 }
438
439 // Apply initial seek if specified
440 log_debug("session_client_like_run(): Getting media_seek_timestamp option");
441 capture_config.initial_seek_timestamp = GET_OPTION(media_seek_timestamp);
442 log_debug("session_client_like_run(): Media seek timestamp set");
443
444 // Clean up probe source (don't reuse for actual capture)
445 if (probe_source) {
446 log_debug("session_client_like_run(): Destroying probe source");
447 media_source_destroy(probe_source);
448 probe_source = NULL;
449 log_debug("session_client_like_run(): Probe source destroyed");
450 } else {
451 log_debug("session_client_like_run(): No probe source to destroy");
452 }
453
454 // Pass probed FPS to display context for correct render-file duration
455 if (capture_config.target_fps > 0) {
456 session_display_set_render_fps(display, capture_config.target_fps);
457 log_debug("session_client_like_run(): Set display render FPS to %u for correct video encoding duration",
458 capture_config.target_fps);
459 }
460
461 asciichat_error_t render_init_result = session_display_init_render_file(display, capture_config.target_fps);
462 if (render_init_result != ASCIICHAT_OK) {
463 result = render_init_result;
464 goto cleanup;
465 }
466
467 // ============================================================================
468 // SETUP: Network Transports (TCP/WebSocket)
469 // ============================================================================
470
471 log_debug("session_client_like_run(): Setting up network transports");
472
473 bool networked_mode = session_client_like_is_networked(config->kind);
474
475 switch (config->kind) {
477 log_debug("Mirror mode detected - will use local capture with media source");
478 break;
480 log_debug("Client/Network mode detected - will use network capture without local media source");
481 break;
483 log_debug("Discovery mode detected - discovery session will manage networking");
484 break;
485 }
486
487 // ============================================================================
488 // SETUP: Capture Context
489 // ============================================================================
490
491 // Check for stdin render mode: read ASCII frames from stdin, render to video
492 // Only enable stdin render mode if:
493 // 1. render-file is "-" (output to stdout)
494 // 2. stdin is not a TTY
495 // 3. --height is explicitly specified (user must opt-in to stdin render mode)
496 // 4. NO media source is being used (not reading from file/URL/test-pattern)
497 // This prevents accidental activation when piping to other processes or using mirror mode
498 const char *render_file_opt = GET_OPTION(render_file);
499 int frame_height = GET_OPTION(height);
500 bool height_explicitly_set = (frame_height != OPT_HEIGHT_DEFAULT);
501 bool has_media_source = (media_url_val && strlen(media_url_val) > 0) ||
502 (media_file_val && strlen(media_file_val) > 0) || GET_OPTION(test_pattern);
503
504 // Enable audio capture if render-file is active and media source exists
505 if (render_file_opt && strlen(render_file_opt) > 0 && has_media_source) {
506 capture_config.enable_audio = true;
507 log_debug("Audio capture enabled for render-file encoding");
508 }
509
510 bool stdin_render_mode =
511 (render_file_opt && strcmp(render_file_opt, "-") == 0 && !terminal_is_stdin_tty() && height_explicitly_set &&
512 !has_media_source && config->kind != SESSION_CLIENT_LIKE_KIND_MIRROR);
513
514 if (stdin_render_mode) {
515 // Stdin render mode: read ASCII frames from stdin, output video to stdout
516 log_info("Stdin render mode enabled: reading ASCII frames from stdin, output to stdout");
517
518 // Get frame dimensions (--height must be explicitly set to reach here)
519 int frame_width = GET_OPTION(width);
520
521 // Validate stdin render mode constraints (--height is already validated above since height_explicitly_set is true)
522 bool width_was_explicitly_set = (frame_width != OPT_WIDTH_DEFAULT);
523
524 if (width_was_explicitly_set) {
525 result = SET_ERRNO(ERROR_USAGE, "Stdin render mode does not accept --width (auto-detected from frames).\n"
526 "Only specify: --height <rows>");
527 goto cleanup;
528 }
529
530 asciichat_error_t stdin_err = terminal_fd_reader_create(STDIN_FILENO, frame_height, &g_stdin_reader);
531 if (stdin_err != ASCIICHAT_OK) {
532 log_fatal("Failed to initialize stdin frame reader: %s", asciichat_error_string(stdin_err));
533 result = ERROR_MEDIA_INIT;
534 goto cleanup;
535 }
536
537 // For stdin render mode, create a minimal capture context for compatibility
538 int fps = GET_OPTION(fps);
539 capture = session_network_capture_create((uint32_t)(fps > 0 ? fps : 60));
540 if (!capture) {
541 log_fatal("Failed to initialize capture context for stdin rendering");
542 terminal_fd_reader_destroy(g_stdin_reader);
543 g_stdin_reader = NULL;
544 result = ERROR_MEDIA_INIT;
545 goto cleanup;
546 }
547
548 log_info("stdin render mode: reading %d-line frames from stdin, auto-detecting width", frame_height);
549 }
550
551 // Choose capture type based on mode determined earlier:
552 // - Mirror mode: needs to capture local media (webcam, file, test pattern)
553 // - Network modes (client/discovery): receive frames from network, no local capture
554 // Discovery peers may use a local media source (for example the synthetic
555 // test pattern used by headless WebRTC tests) as their outgoing stream.
556 // Keep the network-only capture for ordinary client mode, but do not discard
557 // an explicitly requested source before the discovery transport is active.
558 bool discovery_has_local_source = config->kind == SESSION_CLIENT_LIKE_KIND_DISCOVERY;
559 if (!stdin_render_mode && networked_mode && !discovery_has_local_source) {
560 // Network mode: create minimal capture context without media source
561 log_debug("Network mode detected - using network capture (no local media source)");
562 int fps = GET_OPTION(fps);
563 uint64_t net_cap_start = time_get_ns();
564 capture = session_network_capture_create((uint32_t)(fps > 0 ? fps : 60));
565 double net_cap_ms = time_ns_to_ms(time_elapsed_ns(net_cap_start, time_get_ns()));
566 log_info("★ INIT_CHECKPOINT: Network capture created in %.1f ms", net_cap_ms);
567 if (!capture) {
568 log_fatal("Failed to initialize network capture context");
569 result = ERROR_MEDIA_INIT;
570 goto cleanup;
571 }
572 if (fps > 0) {
573 log_debug("Network capture FPS set to %d from options", fps);
574 }
575 }
576
577 // ============================================================================
578 // SETUP: Audio Context
579 // ============================================================================
580
581 // Skip audio for immediate snapshots
582 bool should_init_audio = GET_OPTION(audio_enabled);
583 if (GET_OPTION(snapshot_mode) && GET_OPTION(snapshot_delay) == 0.0) {
584 should_init_audio = false;
585 log_debug("Skipping audio initialization for immediate snapshot");
586 }
587
588 // Pass stdin_reader to display if in stdin render mode
589 if (stdin_render_mode && g_stdin_reader) {
590 session_display_set_stdin_reader(display, g_stdin_reader);
591 log_debug("stdin_reader passed to display context");
592 }
593
594 // ============================================================================
595 // SETUP: End Splash Screen
596 // ============================================================================
597 log_debug("[SETUP_SPLASH] About to end splash screen");
598
599 // The splash screen will display during initialization and exit when done.
600 // The initialization process (media probing, display setup, etc.) naturally
601 // takes time, keeping the splash visible. The splash is kept alive during
602 // connection attempts (TCP and WebSocket) to prevent a blank screen during
603 // network operations. splash_intro_done() is called only after a successful
604 // connection is established.
605
606 log_debug("[SETUP_SPLASH] Splash will remain visible during connection attempts");
607
608 // Exit early if shutdown was requested (e.g., user pressed Ctrl-C)
610 log_debug("[SETUP] Shutdown requested, exiting early");
611 log_debug("[SETUP_SPLASH] Ending splash due to early shutdown request");
614 result = ASCIICHAT_OK;
615 goto cleanup;
616 }
617
618 // Wait for splash animation to finish before proceeding to render loop
619 // (splash_anim thread uses the display, must not be disrupted before cleanup)
620 // But skip for immediate snapshots (snapshot-delay=0) which need to output first frame immediately
621 // For discovery mode, keep the splash running - it stays visible while waiting for peers
622 if (config->discovery == NULL) {
624 if (!(GET_OPTION(snapshot_mode) && GET_OPTION(snapshot_delay) == 0.0)) {
626 }
627 } else {
628 log_debug("[SETUP_SPLASH] Discovery mode: keeping splash visible while waiting for peers");
629 }
630
631 // ============================================================================
632 // SETUP: Create Mirror Capture (after splash screen finishes)
633 // ============================================================================
634
635 log_info("[MIRROR_CHECK] stdin_render_mode=%d, capture=%p", stdin_render_mode, (void *)capture);
636 if (!stdin_render_mode && !capture) {
637 // Mirror mode: create capture context with local media source (NOW, after splash finishes)
638 log_info("Mirror mode detected - creating mirror capture after splash screen");
639 uint64_t mirror_cap_start = time_get_ns();
640 capture = session_mirror_capture_create(&capture_config);
641 double mirror_cap_ms = time_ns_to_ms(time_elapsed_ns(mirror_cap_start, time_get_ns()));
642 log_info("★ INIT_CHECKPOINT: Mirror capture created in %.1f ms (after splash)", mirror_cap_ms);
643 if (!capture) {
644 log_fatal("Failed to initialize mirror capture source");
645 result = ERROR_MEDIA_INIT;
646 goto cleanup;
647 }
648 }
649
650 if (should_init_audio && config->kind == SESSION_CLIENT_LIKE_KIND_MIRROR) {
652 bool file_audio = media_source_has_audio(source);
653 bool record_mic = render_file_opt && render_file_opt[0] && !file_audio;
654 if (file_audio || record_mic) {
655 audio_ctx = SAFE_CALLOC(1, sizeof(*audio_ctx), audio_context_t *);
656 if (audio_init(audio_ctx) == ASCIICHAT_OK) {
657 audio_available = true;
658 if (file_audio && GET_OPTION(audio_capture_source) != AUDIO_CAPTURE_SOURCE_MIC)
659 audio_ctx->capture_media_source = source;
660 audio_ctx->monitor_local_media = true;
661 audio_ctx->playback_only = !audio_should_enable_microphone(GET_OPTION(audio_capture_source), file_audio);
662 session_capture_set_audio_context(capture, audio_ctx);
663 media_source_set_audio_context(source, audio_ctx);
664 } else {
665 SAFE_FREE(audio_ctx);
666 }
667 }
668 }
669
670 // ============================================================================
671 // SETUP: Start Audio Playback
672 // ============================================================================
673
674 log_debug("[SETUP_AUDIO] About to check audio context for duplex");
675 if (audio_ctx && audio_available) {
676 log_debug("[SETUP_AUDIO] Starting audio duplex");
677 if (audio_start_duplex(audio_ctx) == ASCIICHAT_OK) {
678 log_info("Audio playback started");
679 } else {
680 log_warn("Failed to start audio duplex");
683 audio_destroy(audio_ctx);
684 SAFE_FREE(audio_ctx);
685 audio_ctx = NULL;
686 audio_available = false;
687 }
688 }
689
690 log_info("[MARKER] Audio setup complete at line 614");
691 log_info("[SETUP_COMPLETE] All setup complete, about to start connection loop");
692
693 // ============================================================================
694 // RUN: Mode-Specific Main Loop with Reconnection
695 // ============================================================================
696
697 // Connection/attempt loop - wraps run_fn with reconnection logic
698 int attempt = 0;
699 int max_attempts = config->max_reconnect_attempts;
700
701 log_info("[CLIENT_LIKE_LOOP] Starting connection loop, max_attempts=%d", max_attempts);
702
703 while (true) {
704 attempt++;
705
706 result = config->run_fn(capture, display, config->run_user_data);
707
708 // Exit immediately if run_fn succeeded
709 if (result == ASCIICHAT_OK) {
710 // Connection succeeded - splash screen cleanup now happens when first frame renders
711 // This ensures the splash stays visible during TCP/WebSocket/datachannel connection attempts
712
713 break;
714 }
715
716 // Check if we should attempt reconnection
717 bool should_retry = false;
718
719 if (max_attempts != 0) { // max_attempts == 0 means no retries
720 // Check custom reconnect logic if provided
721 if (config->should_reconnect_callback) {
722 should_retry = config->should_reconnect_callback(result, attempt, config->reconnect_user_data);
723 } else {
724 should_retry = true; // Default: always retry
725 }
726
727 // Check retry limits if still planning to retry
728 if (should_retry && max_attempts > 0 && attempt >= max_attempts) {
729 log_debug("Reached maximum reconnection attempts (%d), giving up", max_attempts);
730 should_retry = false;
731 }
732 }
733
734 // If not retrying or shutdown requested, exit loop
735 if (!should_retry || APP_CALLBACK_BOOL(should_exit)) {
736 break;
737 }
738
739 // Reconnection will happen - show splash screen during reconnection attempt
740 // Reset first_frame flag so splash cleanup runs on next successful connection
742
743 // splash_intro_start() handles all the logic:
744 // - If splash should display: shows splash and disables console logging
745 // - If splash shouldn't display: restores stderr and returns without changing logging state
746 splash_intro_start(display);
747
748 // Log reconnection message
749 if (max_attempts == -1) {
750 log_info("Connection failed, retrying...");
751 } else if (max_attempts > 0) {
752 log_info("Connection failed, retrying (attempt %d/%d)...", attempt + 1, max_attempts);
753 }
754
755 // Apply reconnection delay if configured
756 // Check APP_CALLBACK_BOOL(should_exit) frequently during sleep so SIGTERM can interrupt reconnection attempts
757 if (config->reconnect_delay_ms > 0) {
758 unsigned int remaining_ms = config->reconnect_delay_ms;
759 const unsigned int check_interval_ms = 100; // Check exit flag every 100ms
760
761 while (remaining_ms > 0 && !APP_CALLBACK_BOOL(should_exit)) {
762 unsigned int sleep_ms = (remaining_ms < check_interval_ms) ? remaining_ms : check_interval_ms;
763 APP_CALLBACK_VOID(platform_pump_events);
765 remaining_ms -= sleep_ms;
766 }
767 }
768
769 // Continue loop to retry
770 }
771
772 // ============================================================================
773 // CLEANUP (always runs, even on error)
774 // ============================================================================
775
776cleanup:
777 // Re-enable terminal output for shutdown logs
779
780 g_current_config = NULL;
781 g_render_should_exit = NULL;
782
783#ifndef NDEBUG
784 // Join the registry inspector before freeing the session's synchronization objects.
786#endif
787
788 // Stop audio thread before destroying audio context to prevent use-after-free
789 // The audio worker thread may still be logging when we destroy the buffer
791
792 // Stop streams before releasing the local media source.
793 if (audio_ctx) {
794 audio_stop_duplex(audio_ctx);
795 audio_destroy(audio_ctx);
796 SAFE_FREE(audio_ctx);
797 audio_ctx = NULL;
798 }
799
800 // Destroy display (check global context to see if discovery mode already destroyed it)
801 // Discovery participant mode may destroy the display early and clear the global context
803 if (display && display == current_global) {
804 // Display is still valid (not destroyed by discovery mode)
806 display = NULL;
807 } else if (display && display != current_global) {
808 // Display was already destroyed (by discovery participant mode)
809 // Just NULL it out, don't call destroy again
810 log_debug("[CLEANUP] Display was already destroyed by discovery mode, skipping destroy");
811 display = NULL;
812 }
813
814 // Destroy capture
815 if (capture) {
817 capture = NULL;
818 }
819
820 // Destroy stdin reader if active (stdin render mode)
821 if (g_stdin_reader) {
822 terminal_fd_reader_destroy(g_stdin_reader);
823 g_stdin_reader = NULL;
824 }
825
826 // Clean up probe source if still allocated (shouldn't happen, but be safe)
827 if (probe_source) {
828 media_source_destroy(probe_source);
829 probe_source = NULL;
830 }
831
832 // Stop splash animation and enforce minimum display time (even on error path)
833 // But skip completely if shutting down - don't interact with splash at all during shutdown
834 // The animation thread will exit on its own when it detects shutdown_is_requested()
835 // For discovery mode, keep the splash running - it stays visible while waiting for peers
836 if (config->discovery != NULL) {
837 log_debug("[CLEANUP] Discovery mode: keeping splash alive for peer waiting UI");
838 } else if (!APP_CALLBACK_BOOL(should_exit)) {
839 log_debug("[CLEANUP] About to call splash_intro_done()");
841 log_debug("[CLEANUP] splash_intro_done() returned");
842
843 // Wait for animation thread to exit before cleanup
844 log_debug("[CLEANUP] About to call splash_wait_for_animation()");
846 log_debug("[CLEANUP] splash_wait_for_animation() returned");
847 } else {
848 log_debug("[CLEANUP] Skipping all splash operations (shutdown in progress)");
849 // During shutdown, don't interact with splash - let animation thread exit naturally
850 // and don't wait for it (prevents blocking on signals)
851 }
852
853 // Disable keepawake (re-allow OS to sleep)
854 log_debug("[CLEANUP] Disabling keepawake");
856 log_debug("[CLEANUP] keepawake disabled");
857
858 // Write newline to separate final frame from prompt (if configured)
859 log_debug("About to check print_newline_on_tty_exit");
861 log_debug("Writing newline");
862 const char newline = '\n';
863 (void)platform_write_all(STDOUT_FILENO, &newline, 1);
864 }
865
867
868 return result;
869}
#define APP_CALLBACK_BOOL(callback_name)
#define APP_CALLBACK_VOID(callback_name)
#define APP_CALLBACK_VOID_UINT(callback_name, value)
void session_capture_set_audio_context(session_capture_ctx_t *ctx, void *audio_ctx)
Set the audio context for playback control.
bool audio_should_enable_microphone(audio_capture_source_t source, bool has_media_audio)
Determine if microphone should be enabled based on audio source setting and media state.
asciichat_error_t audio_init(audio_context_t *ctx)
Initialize audio context and PortAudio.
asciichat_error_t audio_start_duplex(audio_context_t *ctx)
Start full-duplex audio (simultaneous capture and playback)
void audio_destroy(audio_context_t *ctx)
Destroy audio context and clean up resources.
asciichat_error_t audio_stop_duplex(audio_context_t *ctx)
Stop full-duplex audio.
void audio_stop_thread()
Stop audio capture thread.
unsigned int uint32_t
Definition common.h:58
#define SAFE_FREE(ptr)
Definition common.h:376
#define SAFE_CALLOC(count, size, cast)
Definition common.h:274
unsigned long long uint64_t
Definition common.h:59
void debug_sync_cleanup_thread(void)
Stop and clean up background debug thread.
Definition sync.c:674
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ERROR_DISPLAY
@ ERROR_MEDIA_INIT
Definition error_codes.h:70
@ ERROR_USAGE
Definition error_codes.h:53
@ ERROR_INVALID_PARAM
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
void log_set_force_stderr(bool enabled)
Force all terminal log output to stderr.
Definition log/log.c:721
#define log_fatal(...)
Log a FATAL message.
Definition log/log.h:599
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
void log_set_terminal_output(bool enabled)
Control stderr output to terminal.
Definition log/log.c:700
bool media_source_has_audio(media_source_t *source)
Check if media source has audio stream.
Definition source.c:737
void media_source_destroy(media_source_t *source)
Destroy media source and free resources.
Definition source.c:448
double media_source_get_video_fps(media_source_t *source)
Get video frame rate in frames per second.
Definition source.c:978
void media_source_set_audio_context(media_source_t *source, void *audio_ctx)
Set audio context for playback buffer management.
Definition source.c:1041
media_source_t * media_source_create(media_source_type_t type, const char *path)
Create a new media source.
Definition source.c:187
@ MEDIA_SOURCE_STDIN
Piped or redirected input.
Definition source.h:84
@ MEDIA_SOURCE_WEBCAM
Hardware webcam device.
Definition source.h:82
@ MEDIA_SOURCE_FILE
Media file (video/audio)
Definition source.h:83
@ MEDIA_SOURCE_TEST
Test pattern generator.
Definition source.h:85
uint64_t time_get_ns(void)
Get current monotonic time in nanoseconds.
Definition util/time.c:108
uint64_t time_elapsed_ns(uint64_t start_ns, uint64_t end_ns)
Calculate elapsed time with wraparound safety.
Definition util/time.c:150
#define GET_OPTION(field)
Safely get a specific option field (lock-free read)
#define OPT_HEIGHT_DEFAULT
Default terminal height in characters.
#define OPT_WIDTH_DEFAULT
Default terminal width in characters.
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:148
size_t platform_write_all(int fd, const void *buf, size_t count)
Write all bytes to a file descriptor, handling partial writes.
Definition system.c:224
asciichat_error_t terminal_clear_screen(void)
Clear the terminal screen.
void platform_disable_keepawake(void)
Disable system sleep prevention (allow OS to sleep)
void platform_sleep_ms(unsigned int ms)
Sleep for a specified number of milliseconds.
asciichat_error_t platform_enable_keepawake(void)
Enable system sleep prevention (keepawake mode)
void session_display_destroy(session_display_ctx_t *ctx)
Destroy session display context and free resources.
void session_display_set_render_fps(session_display_ctx_t *ctx, uint32_t fps)
Set the render FPS for file output encoding.
session_display_ctx_t * session_display_create(const session_display_config_t *config)
Create a new session display context.
void session_display_set_stdin_reader(session_display_ctx_t *ctx, void *reader)
Set stdin frame reader for ASCII-to-video rendering.
bool splash_is_running(void)
Check if splash screen animation is currently running.
Definition splash.c:725
int splash_intro_done(void)
Signal that intro splash should end (first frame ready to render)
Definition splash.c:693
void update_banner_wait_for_check(void)
Wait for the background update check thread to finish.
bool update_banner_show_prompt(session_display_ctx_t *ctx)
Show the update prompt screen and wait for user input.
void splash_restore_stderr(void)
Restore stderr after splash animation completes.
Definition splash.c:716
int splash_intro_start(session_display_ctx_t *ctx)
Start animated intro screen with rainbow ASCII art (non-blocking)
Definition splash.c:639
session_capture_ctx_t * session_mirror_capture_create(const session_capture_config_t *config)
Create a new session capture 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_reset_first_frame(session_display_ctx_t *ctx)
Reset the first frame flag to allow splash screen to show again on next render.
void session_display_set_global_context(session_display_ctx_t *ctx)
Set the global display context (for signal handlers like Ctrl+C)
bool update_banner_has_update(void)
Check if an update is available (thread-safe)
void splash_wait_for_animation(void)
Wait for splash animation thread to fully exit.
Definition splash.c:731
session_capture_ctx_t * session_network_capture_create(uint32_t target_fps)
Create network mode capture context without media source.
void * session_capture_get_media_source(session_capture_ctx_t *ctx)
Get the underlying media source from capture context.
void session_capture_destroy(session_capture_ctx_t *ctx)
Destroy session capture context and free resources.
void update_banner_print_instructions(void)
Print upgrade instructions to stdout and prepare for clean exit.
bool terminal_is_stdin_tty(void)
Check if stdin is connected to a TTY.
bool terminal_is_stdout_tty(void)
Check if stdout is connected to a TTY.
bool terminal_should_force_stderr(void)
Determine if logs should be forced to stderr.
void terminal_fd_reader_destroy(terminal_fd_reader_t *reader)
Destroy terminal FD reader.
asciichat_error_t terminal_fd_reader_create(int fd, int frame_height, terminal_fd_reader_t **out)
Create a terminal FD reader from a file descriptor.
asciichat_error_t session_display_init_render_file(session_display_ctx_t *ctx, uint32_t fps)
Perform complete terminal reset.
Audio context for full-duplex capture and playback.
bool monitor_local_media
Play local file audio in solo sessions.
void * capture_media_source
Borrowed local media audio; configure before starting duplex.
bool playback_only
True for playback-only mode (mirror), skip microphone capture.
Media source for video and audio capture.
Definition source.c:34
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.
bool enable_audio
Enable audio capture from media source.
Internal session capture context structure.
session_client_like_kind_t kind
bool(* should_reconnect_callback)(asciichat_error_t last_error, int attempt_number, void *user_data)
unsigned int reconnect_delay_ms
session_client_like_run_fn run_fn
Configuration for session display context.
void * callback_data
Opaque data passed to should_exit_callback.
palette_type_t palette_type
Palette type for ASCII rendering.
bool defer_render_file
Defer render-file encoder creation until media timing is known.
const char * custom_palette
Custom palette characters (required if palette_type == PALETTE_CUSTOM)
session_display_should_exit_fn should_exit_callback
Optional: callback to check if initialization should be cancelled (e.g., shutdown signal)
bool snapshot_mode
Enable snapshot mode (single frame capture)
terminal_color_mode_t color_mode
Color mode override (TERM_COLOR_AUTO for auto-detection)
Internal session display context structure.
@ TERM_COLOR_AUTO
Auto-detect color support from terminal capabilities.
Definition terminal.h:580

References APP_CALLBACK_BOOL, APP_CALLBACK_VOID, APP_CALLBACK_VOID_UINT, ASCIICHAT_OK, AUDIO_CAPTURE_SOURCE_MIC, audio_destroy(), audio_init(), audio_should_enable_microphone(), audio_start_duplex(), audio_stop_duplex(), audio_stop_thread(), session_capture_config_t::callback_data, session_display_config_t::callback_data, audio_context_t::capture_media_source, session_display_config_t::color_mode, session_display_config_t::custom_palette, debug_sync_cleanup_thread(), session_display_config_t::defer_render_file, session_client_like_config::discovery, session_capture_config_t::enable_audio, ERROR_DISPLAY, ERROR_INVALID_PARAM, ERROR_MEDIA_INIT, ERROR_USAGE, GET_OPTION, session_capture_config_t::initial_seek_timestamp, session_client_like_config::kind, log_debug, log_fatal, log_info, log_set_force_stderr(), log_set_terminal_output(), log_warn, session_capture_config_t::loop, session_client_like_config::max_reconnect_attempts, media_source_create(), media_source_destroy(), MEDIA_SOURCE_FILE, media_source_get_video_fps(), media_source_has_audio(), media_source_set_audio_context(), MEDIA_SOURCE_STDIN, MEDIA_SOURCE_TEST, MEDIA_SOURCE_WEBCAM, audio_context_t::monitor_local_media, OPT_HEIGHT_DEFAULT, OPT_WIDTH_DEFAULT, session_display_config_t::palette_type, session_capture_config_t::path, platform_disable_keepawake(), platform_enable_keepawake(), platform_sleep_ms(), platform_write_all(), audio_context_t::playback_only, session_client_like_config::print_newline_on_tty_exit, session_client_like_config::reconnect_delay_ms, session_client_like_config::reconnect_user_data, session_capture_config_t::resize_for_network, session_client_like_config::run_fn, session_client_like_config::run_user_data, SAFE_CALLOC, SAFE_FREE, safe_snprintf(), session_capture_destroy(), session_capture_get_media_source(), session_capture_set_audio_context(), SESSION_CLIENT_LIKE_KIND_CLIENT, SESSION_CLIENT_LIKE_KIND_DISCOVERY, SESSION_CLIENT_LIKE_KIND_MIRROR, session_display_create(), session_display_destroy(), session_display_get_global_context(), session_display_init_render_file(), session_display_reset_first_frame(), session_display_set_global_context(), session_display_set_render_fps(), session_display_set_stdin_reader(), session_mirror_capture_create(), session_network_capture_create(), SET_ERRNO, should_exit(), session_capture_config_t::should_exit_callback, session_display_config_t::should_exit_callback, session_client_like_config::should_reconnect_callback, session_display_config_t::snapshot_mode, splash_intro_done(), splash_intro_start(), splash_is_running(), splash_restore_stderr(), splash_wait_for_animation(), session_capture_config_t::target_fps, TERM_COLOR_AUTO, terminal_clear_screen(), terminal_fd_reader_create(), terminal_fd_reader_destroy(), terminal_is_stdin_tty(), terminal_is_stdout_tty(), terminal_should_force_stderr(), time_elapsed_ns(), time_get_ns(), session_capture_config_t::type, update_banner_has_update(), update_banner_print_instructions(), update_banner_show_prompt(), and update_banner_wait_for_check().

Referenced by client_main(), discovery_main(), and mirror_main().

Variable Documentation

◆ session_client_like_get_render_should_exit

bool(*)(void *) session_client_like_get_render_should_exit(void) ( void  )

Get the render loop should_exit callback set by session_client_like_run().

This is used by mode-specific run_fn callbacks to obtain the proper should_exit callback for passing to session_render_loop(). The callback checks both the global should_exit() flag and any mode-specific custom_should_exit condition.

Returns
Function pointer to the render should_exit callback, or NULL if session_client_like_run() has not been called yet.

Definition at line 245 of file client_like.h.