ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
client/main.c
Go to the documentation of this file.
1
66#include "main.h"
67#include "../main.h" // Global exit API
68#include "server.h"
69#include "protocol.h"
70#include "crypto.h"
71#include "display.h"
72#include "capture.h"
73#include "audio.h"
77#include "session/client_like.h"
80#include <ascii-chat/ui/mdns.h>
82
88#include <ascii-chat/common.h>
90#include <ascii-chat/log/log.h>
91#include <ascii-chat/log/json.h>
93#include <ascii-chat/options/rcu.h> // For RCU-based options access
94#include <ascii-chat/util/time.h> // For time macros
95#include <ascii-chat/util/url.h> // For URL parsing and WebSocket detection
102#include <ascii-chat/util/ip.h>
105#include "connection_state.h"
106#include <ascii-chat/util/path.h>
108
109#ifndef NDEBUG
111#ifdef DEBUG_MEMORY
113#endif
114#endif
115
116#include <signal.h>
117#include <stdlib.h>
118#include <stdio.h>
119#include <string.h>
120#include <ascii-chat/atomic.h>
121#ifndef _WIN32
122#include <unistd.h>
123#endif
124#include <fcntl.h>
125
126/* ============================================================================
127 * Global State Variables
128 * ============================================================================ */
129
141
153
161
173typedef struct {
174 connection_attempt_context_t connection_ctx; // Fallback connection context (embedded)
175 const char *discovered_address; // From LAN/session discovery
176 int discovered_port; // From LAN/session discovery
177 int reconnect_attempt; // Current reconnection attempt number
178 bool has_ever_connected; // Track if connection ever succeeded
180
181static client_session_state_t g_client_session = {0};
182
183/* Signal handling is now centralized in src/main.c via setup_signal_handlers()
184 * Client mode uses set_interrupt_callback(server_connection_shutdown) to register
185 * its network shutdown as the interrupt handler. SIGWINCH is still client-specific.
186 */
187
197#ifndef _WIN32
198static void client_handle_sigwinch(int sigwinch) {
199 (void)(sigwinch);
200
201 // Terminal was resized, update dimensions and recalculate aspect ratio
202 // ONLY if both width and height are auto (not manually set)
204 // Get terminal size and update via proper RCU setters
205 unsigned short int term_width, term_height;
206 if (get_terminal_size(&term_width, &term_height) == ASCIICHAT_OK) {
207 options_set_int("width", (int)term_width);
208 options_set_int("height", (int)term_height);
209 }
210
211 // Send new size to server if connected
214 (int)terminal_get_effective_height()) < 0) {
215 log_warn("Failed to send terminal capabilities to server: %s", network_error_string());
216 } else {
219 }
220 }
221 }
222}
223#endif
224
234static void shutdown_client() {
235 // Guard against double cleanup (can be called explicitly + via atexit)
236 static bool shutdown_done = false;
237 if (shutdown_done) {
238 return;
239 }
240 shutdown_done = true;
241 log_debug("[SHUTDOWN] 1. Starting shutdown");
242
243 // Set global shutdown flag to stop all threads
244 signal_exit();
245
246 // Stop splash animation thread before any resource cleanup
248
249 // IMPORTANT: Stop all protocol threads BEFORE cleaning up resources
250 // protocol_stop_connection() shuts down the socket to interrupt blocking recv(),
251 // then waits for the data reception thread and capture thread to exit.
252 // This prevents race conditions where threads access freed resources.
253 log_debug("[SHUTDOWN] 4. About to call protocol_stop_connection()");
255
256 // Destroy client worker thread pool (all threads already stopped by protocol_stop_connection)
260 }
261
262 // Destroy application client context
263 log_debug("[SHUTDOWN] 8. About to destroy app_client");
264 if (g_client) {
266 }
267
268 // Now safe to cleanup server connection (socket already closed by protocol_stop_connection)
269 // Legacy cleanup - will be removed after full migration to tcp_client
270 log_debug("[SHUTDOWN] 10. About to cleanup server connection");
272
273 // Cleanup capture subsystems (capture thread already stopped by protocol_stop_connection)
274 log_debug("[SHUTDOWN] 12. About to cleanup capture");
277
278 // Cleanup H.265 encoder
279 if (g_h265_encoder) {
280 log_debug("[SHUTDOWN] 14a. About to cleanup H.265 encoder");
282 g_h265_encoder = NULL;
283 }
284
285 // Print audio analysis report if enabled
286 // Print audio analysis report if enabled
287 if (GET_OPTION(audio_analysis_enabled)) {
290 }
291
292 // Cleanup display and terminal state
293 // WORKAROUND: Skip cleanup during shutdown to avoid hanging
294 // display_cleanup();
295
296 // Cleanup core systems
297 // WORKAROUND: Skip cleanup during shutdown to avoid hanging
298 // buffer_pool_cleanup_global();
299
300 // Disable keepawake mode (re-allow OS to sleep)
302
303 log_debug("Client shutdown complete");
304 log_destroy();
305}
306
316static int initialize_client_systems(void) {
317 // All shared subsystem initialization (timer, logging, platform, buffer pool)
318 // is now done by asciichat_shared_init() in src/main.c BEFORE options_init()
319
320 // Initialize client worker thread pool (needed for network protocol threads)
321 // This is required for all modes that connect to the server, including snapshot mode
323 g_client_worker_pool = thread_pool_create("client_workers");
325 log_fatal("Failed to create client worker thread pool");
326 return ERROR_THREAD;
327 }
328 }
329
330 // Ensure logging output is available for connection attempts (unless it was disabled with --quiet)
331 if (!GET_OPTION(quiet)) {
333 }
335
336 // Display subsystem is now initialized by session_client_like_run()
337 // No need to call display_init() here
338
339 // Initialize application client context
340 if (!g_client) {
342 if (!g_client) {
343 log_fatal("Failed to create application client context");
344 return ERROR_NETWORK;
345 }
346 log_debug("Application client context created successfully");
347 }
348
349 // Initialize server connection management (legacy - will be migrated to tcp_client)
350 if (server_connection_init() != 0) {
351 log_fatal("Failed to initialize server connection");
352 return ERROR_NETWORK;
353 }
354
355 // Initialize capture subsystems
356 int capture_result = capture_init();
357 if (capture_result != 0) {
358 log_fatal("Failed to initialize capture subsystem");
359 return capture_result;
360 }
361
362 // Initialize H.265 encoder for video streaming
363 // Use reasonable default dimensions (will adapt on first frame)
364 if (!g_h265_encoder) {
366 if (!g_h265_encoder) {
367 log_warn("Failed to initialize H.265 encoder (will fall back to raw frames)");
368 // Don't fail - we can still use raw frames
369 } else {
370 log_debug("H.265 encoder initialized successfully");
371 }
372 }
373
374 // Timed recordings capture call audio; immediate snapshots need only one frame.
375 if (GET_OPTION(audio_enabled) && (!GET_OPTION(snapshot_mode) || GET_OPTION(snapshot_delay) > 0)) {
376 if (audio_client_init() != 0) {
377 log_warn("Failed to initialize audio system");
378 // Continue without audio instead of crashing (e.g., ARM systems with audio device incompatibility)
379 }
380
381 // Initialize audio analysis if requested
382 if (GET_OPTION(audio_analysis_enabled)) {
383 if (audio_analysis_init() != 0) {
384 log_warn("Failed to initialize audio analysis");
385 }
386 }
387 }
388
389 return 0;
390}
391
403/* ============================================================================
404 * Client Connection/Reconnection Loop (run_fn for session_client_like)
405 * ============================================================================
406 *
407 * This callback is executed after shared initialization (media/audio/display)
408 * is complete. It manages the entire client connection lifecycle:
409 * 1. Connect to server (with fallback stages: TCP, WebRTC+STUN, WebRTC+TURN)
410 * 2. Exchange media/audio with server
411 * 3. On disconnection, attempt reconnection based on policy
412 * 4. Exit on user request or max reconnection limit
413 */
414
426static bool client_should_reconnect(asciichat_error_t last_error, int attempt_number, void *user_data) {
427 (void)last_error;
428 (void)attempt_number;
429 (void)user_data;
430
431 // In snapshot mode, don't reconnect - exit after first failure
432 if (GET_OPTION(snapshot_mode)) {
433 log_error("Connection lost in snapshot mode - not retrying");
434 return false;
435 }
436
437 // Otherwise, allow reconnection (framework handles max_reconnect_attempts limit)
438 return true;
439}
440
457static asciichat_error_t client_run(session_capture_ctx_t *capture, session_display_ctx_t *display, void *user_data) {
458 (void)capture; // Provided by session_client_like, used by protocol threads
459 (void)user_data; // Unused
460
461 // Make the framework-created display context available to protocol threads
462 display_set_context(display);
463
464 // Get the render loop's should_exit callback for monitoring
465 bool (*render_should_exit)(void *) = session_client_like_get_render_should_exit();
466 if (!render_should_exit) {
467 return SET_ERRNO(ERROR_INVALID_STATE, "Render should_exit callback not initialized");
468 }
469
470 // Attempt connection with fallback stages (TCP, WebRTC+STUN, WebRTC+TURN)
471 asciichat_error_t connection_result = connection_attempt_tcp(
472 &g_client_session.connection_ctx,
473 g_client_session.discovered_address != NULL ? g_client_session.discovered_address : "",
474 (uint16_t)(g_client_session.discovered_port > 0 ? g_client_session.discovered_port : 27224));
475
476 // Check if shutdown was requested during connection attempt
477 if (should_exit()) {
478 log_info("Shutdown requested during connection attempt");
479 return ERROR_NETWORK;
480 }
481
482 if (connection_result != ASCIICHAT_OK) {
483 // Connection failed - need to stop audio threads that were initialized early
484 // (even though protocol_start_connection was never called)
486 // Framework will handle retry based on config
487 log_error("Connection attempt failed");
488 return connection_result;
489 }
490
491 // Connection successful - integrate transport into server layer
492 if (g_client_session.connection_ctx.connection.transport) {
494 g_client_session.connection_ctx.connection.transport = NULL;
495 } else {
496 log_error("Connection succeeded but no active transport");
497 return ERROR_NETWORK;
498 }
499
500 // Log connection status
501 if (!g_client_session.has_ever_connected) {
502 log_info("Connected");
503 g_client_session.has_ever_connected = true;
504 } else {
505 log_info("Reconnected");
506 }
507
508 // Start protocol worker threads for this connection
509 if (protocol_start_connection() != 0) {
510 log_error("Failed to start connection protocols");
513 return ERROR_NETWORK;
514 }
515
516 // Monitor connection until it breaks or shutdown is requested
517 static bool snapshot_check_logged_once = false;
520 log_debug("Connection lost detected");
521 break;
522 }
523
524 // Check if snapshot_delay timer has fired (in case frames stopped arriving)
525 // This is a safety check for when frame reception is sparse or stops entirely
527 bool snapshot_mode_enabled = GET_OPTION(snapshot_mode);
528
529 if (!snapshot_check_logged_once) {
530 log_info("CLIENT_MAIN_LOOP: snapshot_mode=%d first_frame_time=%llu snapshot_delay=%.1f", snapshot_mode_enabled,
531 (unsigned long long)g_snapshot_first_frame_time_ns, GET_OPTION(snapshot_delay));
532 snapshot_check_logged_once = true;
533 }
534
535 if (snapshot_mode_enabled && g_snapshot_first_frame_time_ns > 0) {
536 uint64_t current_time_ns = time_get_ns();
537 double elapsed = time_ns_to_s(time_elapsed_ns(g_snapshot_first_frame_time_ns, current_time_ns));
538 double snapshot_delay = GET_OPTION(snapshot_delay);
539
540 // Log progress every 500ms
541 static uint64_t last_progress_log_ns = 0;
542 if (current_time_ns - last_progress_log_ns > 500 * 1000 * 1000) {
543 log_debug("[MAIN_LOOP_SNAPSHOT_CHECK] elapsed=%.3f target=%.3f first_frame_ns=%llu current_ns=%llu", elapsed,
544 snapshot_delay, (unsigned long long)g_snapshot_first_frame_time_ns,
545 (unsigned long long)current_time_ns);
546 last_progress_log_ns = current_time_ns;
547 }
548
549 if (elapsed >= snapshot_delay) {
550 char duration_str[32];
551 time_pretty((uint64_t)(elapsed * 1e9), -1, duration_str, sizeof(duration_str));
552 log_info("🎬 SNAPSHOT TIMEOUT FIRED (IN_MAIN_LOOP): Snapshot captured after %s! (target=%.3f)", duration_str,
553 snapshot_delay);
554 signal_exit();
555 break; // Exit loop, proper shutdown will follow
556 }
557 }
558
560 }
561
562 if (should_exit()) {
563 log_debug("Shutdown requested, cleaning up connection");
564 } else {
565 log_info("[ELSE_BRANCH] Connection lost path (should_exit=0, connection_active=0)");
566 // Connection lost - check if snapshot_delay timer has fired before exiting
568 if (GET_OPTION(snapshot_mode) && g_snapshot_first_frame_time_ns > 0) {
569 uint64_t current_time_ns = time_get_ns();
570 double elapsed = time_ns_to_s(time_elapsed_ns(g_snapshot_first_frame_time_ns, current_time_ns));
571 double snapshot_delay = GET_OPTION(snapshot_delay);
572
573 log_info("[CONNECTION_LOSS_SNAPSHOT_CHECK] elapsed=%.3f target=%.3f (checking if timer fired)", elapsed,
574 snapshot_delay);
575
576 if (elapsed >= snapshot_delay) {
577 char duration_str[32];
578 time_pretty((uint64_t)(elapsed * 1e9), -1, duration_str, sizeof(duration_str));
579 log_info("🎬 SNAPSHOT TIMEOUT FIRED (ON_CONNECTION_LOSS): Snapshot captured after %s! (target=%.3f)",
580 duration_str, snapshot_delay);
581 signal_exit();
582 // Don't return yet, allow normal cleanup to proceed below
583 }
584 }
585
586 if (!should_exit()) {
587 log_debug("Connection lost, preparing for reconnection attempt");
588 }
589 }
590
591 // Clean up this connection for potential reconnection
594
595 // Clear transport reference from connection context since server_connection_close() already destroyed it
596 // This prevents double-free when connection_context_cleanup() is called below
597 g_client_session.connection_ctx.connection.transport = NULL;
598
599 // Recreate thread pool for clean reconnection
603 }
604 g_client_worker_pool = thread_pool_create("client_reconnect");
606 log_error("Failed to recreate worker thread pool");
607 return ERROR_THREAD;
608 }
609
610 // Reset connection context for next attempt
612 memset(&g_client_session.connection_ctx, 0, sizeof(g_client_session.connection_ctx));
613 if (connection_context_init(&g_client_session.connection_ctx) != ASCIICHAT_OK) {
614 log_error("Failed to re-initialize connection context");
615 return ERROR_NETWORK;
616 }
617
618 // A snapshot is complete once its first frame has been rendered. The peer
619 // may close the connection while the snapshot shutdown is propagating, but
620 // that is a successful snapshot rather than a reconnectable network error.
622 if (GET_OPTION(snapshot_mode) && g_snapshot_first_frame_time_ns > 0 && should_exit()) {
623 return ASCIICHAT_OK;
624 }
625
626 // Return error to signal reconnection needed (framework handles the retry)
627 return ERROR_NETWORK;
628}
629
630int client_main(void) {
631 log_debug("client_main() starting");
632
633 // Register client crypto callbacks for TCP connection path
634 // The TCP path in lib/network/connection_attempt.c relies on these callbacks
635 // to handle cryptographic operations and key exchange
636 static const app_callbacks_t client_crypto_callbacks = {
638 .signal_exit = signal_exit,
639 .platform_get_monotonic_time_us = platform_get_monotonic_time_us,
640 .platform_sleep_ms = platform_sleep_ms,
641 .server_connection_set_ip = server_connection_set_ip,
642 .server_connection_set_port = server_connection_set_port,
643 .client_crypto_set_mode = client_crypto_set_mode,
644 .client_crypto_init = client_crypto_init,
645 .client_crypto_handshake = client_crypto_handshake,
646 .crypto_client_is_ready = crypto_client_is_ready,
647 .crypto_client_get_context = crypto_client_get_context,
648 };
649 app_callbacks_register(&client_crypto_callbacks);
650
651 // Initialize client-specific systems (NOT shared with session_client_like)
652 // This includes: thread pool, display layer, app client context, server connection
653 int init_result = initialize_client_systems();
654 if (init_result != 0) {
655#ifndef NDEBUG
656 // Debug builds: automatically fall back to test pattern if webcam is in use
657 if (init_result == ERROR_WEBCAM_IN_USE && !GET_OPTION(test_pattern)) {
658 log_warn("Webcam is in use - automatically falling back to test pattern mode (debug build only)");
659
660 // Enable test pattern mode via RCU update
661 asciichat_error_t update_result = options_set_bool("test_pattern", true);
662 if (update_result != ASCIICHAT_OK) {
663 log_error("Failed to update options for test pattern fallback");
664 FATAL(init_result, "%s", asciichat_error_string(init_result));
665 }
666
667 // Retry initialization with test pattern enabled
668 init_result = initialize_client_systems();
669 if (init_result != 0) {
670 log_error("Failed to initialize even with test pattern fallback");
671 webcam_print_init_error_help(init_result);
672 FATAL(init_result, "%s", asciichat_error_string(init_result));
673 }
674 log_debug("Successfully initialized with test pattern fallback");
675
676 // Clear the error state since we successfully recovered
677 CLEAR_ERRNO();
678 } else
679#endif
680 {
681 // Release builds or other errors: print help and exit
682 if (init_result == ERROR_WEBCAM || init_result == ERROR_WEBCAM_IN_USE || init_result == ERROR_WEBCAM_PERMISSION) {
683 webcam_print_init_error_help(init_result);
684 FATAL(init_result, "%s", asciichat_error_string(init_result));
685 }
686 // For other errors, just exit with the error code
687 return init_result;
688 }
689 }
690
691 // Register cleanup function for graceful shutdown
692 (void)atexit(shutdown_client);
693
694 // Register client interrupt callback (socket shutdown on SIGTERM/Ctrl+C)
695 // Global signal handlers (SIGTERM, SIGPIPE, Ctrl+C) are set up in setup_signal_handlers() in src/main.c
697
698#ifndef _WIN32
699 // Register SIGWINCH for terminal resize handling (client-specific, not in framework)
700 platform_signal(SIGWINCH, client_handle_sigwinch);
701#endif
702
703 /* ========================================================================
704 * Client-Specific: LAN/Session Discovery
705 * ========================================================================
706 * This phase discovers the server to connect to via:
707 * - LAN discovery (mDNS)
708 * - Session string lookup (ACDS)
709 * - Direct address/port
710 */
711
712 // LAN Discovery: If --scan flag is set, discover servers on local network
713 const options_t *opts = options_get();
714 if (opts && opts->lan_discovery &&
715 (opts->address[0] == '\0' || is_localhost_ipv4(opts->address) || strcmp(opts->address, "localhost") == 0)) {
716 log_debug("LAN discovery: --scan flag set, querying for available servers");
717
718 ui_mdns_config_t lan_config;
719 memset(&lan_config, 0, sizeof(lan_config));
720 lan_config.timeout_ms = 2 * MS_PER_SEC_INT; // Wait up to 2 seconds for responses
721 lan_config.max_servers = 20; // Support up to 20 servers on LAN
722 lan_config.quiet = true; // Quiet during discovery, TUI will show status
723
724 int discovered_count = 0;
725 ui_mdns_server_t *discovered_servers = ui_mdns_query(&lan_config, &discovered_count);
726
727 // Use TUI for server selection
728 int selected_index = ui_mdns_select(discovered_servers, discovered_count);
729
730 if (selected_index < 0) {
731 // User cancelled or no servers found
732 if (discovered_count == 0) {
733 // No servers found - log message and prevent any further output
734 // Lock the terminal so other threads can't write and our error
735 // will be the last message
737
738 // Log single message with embedded newlines to prevent multiple log entries
739 log_error("No ascii-chat servers found on the local network.\nUse 'ascii-chat client <address>' to connect "
740 "manually.");
741
742 // Exit without cleanup
744 }
745 // User cancelled (had servers to choose from but pressed cancel)
746 log_debug("LAN discovery: User cancelled server selection");
747 if (discovered_servers) {
748 ui_mdns_free_results(discovered_servers);
749 }
750 return 1; // User cancelled
751 }
752
753 // Update options with discovered server's address and port
754 ui_mdns_server_t *selected = &discovered_servers[selected_index];
755 const char *selected_address = ui_mdns_get_best_address(selected);
756
757 // We need to modify options, but they're immutable via RCU
758 // Create a new options copy with updated address/port
759 options_t *opts_new = SAFE_MALLOC(sizeof(options_t), options_t *);
760 if (opts_new) {
761 memcpy(opts_new, opts, sizeof(options_t));
762 SAFE_STRNCPY(opts_new->address, selected_address, sizeof(opts_new->address));
763
764 opts_new->port = (int)selected->port;
765
766 log_debug("LAN discovery: Selected server '%s' at %s:%d", selected->name, opts_new->address, opts_new->port);
767
768 // Note: In a real scenario, we'd update the global options via RCU
769 // For now, we'll use the updated values directly for connection
770 // This is a known limitation - proper RCU update requires more infrastructure
771 }
772
773 ui_mdns_free_results(discovered_servers);
774 }
775
776 // =========================================================================
777 // Client-Specific: Server Address Resolution
778 // =========================================================================
779 // Client mode supports:
780 // 1. Direct address/port (--address HOST --port PORT) - handled by options
781 // 2. LAN discovery (--scan) - handled above
782 // 3. WebSocket URL (direct ws:// or wss:// connection string)
783 //
784 // Note: Session string discovery via ACDS is handled by discovery mode only.
785 // Client mode does NOT use ACDS or session strings.
786
787 const char *discovered_address = NULL;
788 int discovered_port = 0;
789
790 // Check if user provided a WebSocket URL as the server address
791 const options_t *opts_websocket = options_get();
792 const char *provided_address = opts_websocket && opts_websocket->address[0] != '\0' ? opts_websocket->address : NULL;
793
794 if (provided_address && url_is_websocket(provided_address)) {
795 // Direct WebSocket connection
796 log_debug("Client: Direct WebSocket URL: %s", provided_address);
797 discovered_address = provided_address;
798 discovered_port = 0; // Port is embedded in the URL
799 }
800
801 if (discovered_port > 0) {
802 log_debug("Client: discovered_address=%s, discovered_port=%d", discovered_address ? discovered_address : "NULL",
803 discovered_port);
804 } else {
805 log_debug("Client: discovered_address=%s", discovered_address ? discovered_address : "NULL");
806 }
807
808 // Store discovered address/port in session state for client_run() callback
809 static char address_storage[BUFFER_SIZE_SMALL];
810 if (discovered_address) {
811 SAFE_STRNCPY(address_storage, discovered_address, sizeof(address_storage));
812 g_client_session.discovered_address = address_storage;
813 g_client_session.discovered_port = discovered_port;
814 } else {
815 const options_t *opts_fallback = options_get();
816 if (opts_fallback && opts_fallback->address[0] != '\0') {
817 SAFE_STRNCPY(address_storage, opts_fallback->address, sizeof(address_storage));
818 g_client_session.discovered_address = address_storage;
819 g_client_session.discovered_port = opts_fallback->port;
820 } else {
821 g_client_session.discovered_address = "localhost";
822 g_client_session.discovered_port = 27224;
823 }
824 }
825
826 // Initialize connection context for first attempt
827 asciichat_error_t ctx_init_result = connection_context_init(&g_client_session.connection_ctx);
828 if (ctx_init_result != ASCIICHAT_OK) {
829 log_error("Failed to initialize connection context");
830 return 1;
831 }
832
833 /* ========================================================================
834 * Configure and Run Shared Client-Like Session Framework
835 * ========================================================================
836 * session_client_like_run() handles all shared initialization:
837 * - Terminal output management (force stderr if piped)
838 * - Keepawake system (platform sleep prevention)
839 * - Splash screen lifecycle
840 * - Media source selection (webcam, file, URL, test pattern)
841 * - FPS probing for media files
842 * - Audio initialization and lifecycle
843 * - Display context creation
844 * - Proper cleanup ordering (critical for PortAudio)
845 *
846 * Client mode provides:
847 * - client_run() callback: connection loop, protocol startup, monitoring
848 * - client_should_reconnect() callback: reconnection policy
849 * - Reconnection configuration: attempts, delay, callbacks
850 */
851
852 // Get reconnect attempts setting (-1 = unlimited, 0 = no retry, >0 = retry N times)
853 int reconnect_attempts = GET_OPTION(reconnect_attempts);
854
855 // Configure session_client_like with client-specific settings
857 .run_fn = client_run,
858 .run_user_data = NULL,
860 .discovery = NULL,
861 .custom_should_exit = NULL,
862 .exit_user_data = NULL,
863 .keyboard_handler = NULL, // Client mode: server drives display
864 .max_reconnect_attempts = reconnect_attempts,
865 .should_reconnect_callback = client_should_reconnect,
866 .reconnect_user_data = NULL,
867 .reconnect_delay_ms = 1000, // 1 second delay between reconnection attempts
868 .print_newline_on_tty_exit = false, // Server/client manages cursor
869 };
870
871 log_debug("[CLIENT_MAIN] About to call session_client_like_run() with %d attempts", reconnect_attempts);
872 asciichat_error_t session_result = session_client_like_run(&config);
873 log_debug("[CLIENT_MAIN] session_client_like_run() returned %d", session_result);
874
875 // Network client lifecycle is owned by connection_context_cleanup() after the session returns.
876
877 // Cleanup connection context
878 log_debug("[CLIENT_MAIN] About to call connection_context_cleanup()");
880 log_debug("[CLIENT_MAIN] connection_context_cleanup() returned");
881
882 // Cleanup session log buffer (used by splash screen in session_client_like)
883 // NOTE: session_log_buffer_destroy() is already called in session_client_like_run()
884 // during its cleanup phase, so we don't call it here to avoid double-destroy of mutex
885
886 log_debug("[CLIENT_MAIN] ascii-chat client shutting down");
887
888 // IMPORTANT: Stop worker threads and join them BEFORE memory report
889 // atexit(shutdown_client) won't run if interrupted by SIGTERM, so call explicitly
890 log_debug("[CLIENT_MAIN] About to call shutdown_client()");
891 fflush(stderr);
892 shutdown_client();
893 log_debug("[CLIENT_MAIN] shutdown_client() returned");
894 fflush(stderr);
895
896 // Cleanup remaining shared subsystems (buffer pool, platform, etc.)
897 log_debug("[CLIENT_MAIN] About to call asciichat_shared_destroy()");
899
900 // Log final session statistics before exit
901 // Note: Frame count is logged in protocol_stop_connection() if connection was established
902 // This is a fallback in case the connection never started
903 return (session_result == ASCIICHAT_OK) ? 0 : 1;
904}
Platform abstraction layer umbrella header providing unified cross-platform API.
ascii-chat Discovery Service (ACDS) Protocol Message Formats
ACIP client-side protocol library and ACDS client connection API.
int audio_analysis_init(void)
Initialize audio analysis.
Definition analysis.c:114
void audio_analysis_print_report(void)
Print audio analysis report.
Definition analysis.c:519
void audio_analysis_destroy(void)
Cleanup audio analysis.
Definition analysis.c:881
Audio Analysis and Debugging Interface.
void app_callbacks_register(const app_callbacks_t *callbacks)
Application-level callbacks for library code.
⚛️ Atomic operations abstraction layer with debug tracking
🗃️ Lock-Free Unified Memory Buffer Pool with Lazy Allocation
Common buffer size definitions.
app_client_t * g_client
Global client application context.
int client_main(void)
Client mode entry point for unified binary.
struct webrtc_peer_manager * g_peer_manager
Global WebRTC peer manager (legacy compatibility)
thread_pool_t * g_client_worker_pool
Global client worker thread pool.
h265_encoder_t *volatile g_h265_encoder
Global H.265 encoder context for client video streaming.
uint64_t g_snapshot_first_frame_time_ns
asciichat_error_t session_client_like_run(const session_client_like_config_t *config)
bool(*)(void *) session_client_like_get_render_should_exit(void)
Definition client_like.c:89
Shared initialization and teardown for client-like modes (mirror, client, discovery)
@ SESSION_CLIENT_LIKE_KIND_CLIENT
Definition client_like.h:65
📹 Unified media capture abstraction for session-based video sources
⚙️ Common definitions, error codes, macros, and types shared throughout the application
void h265_encoder_destroy(h265_encoder_t *encoder)
asciichat_error_t connection_attempt_tcp(connection_attempt_context_t *ctx, const char *server_address, uint16_t server_port)
Attempt direct TCP connection.
asciichat_error_t connection_context_init(connection_attempt_context_t *ctx)
Initialize connection attempt context.
void connection_context_cleanup(connection_attempt_context_t *ctx)
Cleanup connection attempt context.
Connection state machine for client TCP/WebSocket connections.
🔍 Memory debugging helpers for tracking allocations in debug builds
#define BUFFER_SIZE_SMALL
Small buffer size (256 bytes)
void audio_stop_thread()
Stop audio capture thread.
int audio_client_init()
Initialize audio subsystem.
void audio_cleanup()
Cleanup audio subsystem.
int capture_init()
Initialize capture subsystem.
void capture_cleanup()
Cleanup capture subsystem.
void server_connection_close()
Close the server connection gracefully.
bool server_connection_is_active()
Check if server connection is currently active.
void server_connection_shutdown()
Emergency connection shutdown for signal handlers.
asciichat_error_t threaded_send_terminal_size_with_auto_detect(unsigned short width, unsigned short height)
Thread-safe terminal size packet transmission with auto-detection.
void server_connection_set_transport(acip_transport_t *transport)
Set ACIP transport instance from connection fallback.
void server_connection_set_ip(const char *ip)
Set the server IP address.
int server_connection_init()
Initialize the server connection management subsystem.
void server_connection_set_port(int port)
Set the server port.
void server_connection_cleanup()
Cleanup connection management subsystem.
const crypto_context_t * crypto_client_get_context(void)
Get crypto context for encryption/decryption.
int client_crypto_handshake(acip_transport_t *transport)
Perform crypto handshake with server.
void client_crypto_set_mode(uint8_t mode)
Set crypto mode for handshake (encryption + authentication)
int client_crypto_init(void)
Initialize client crypto handshake.
bool crypto_client_is_ready(void)
Check if crypto handshake is ready.
void display_set_context(session_display_ctx_t *display_ctx)
Set the display context created by session_client_like framework.
void display_full_reset()
Perform full display reset.
void protocol_stop_connection()
Stop protocol connection handling.
int protocol_start_connection()
Start protocol connection handling.
bool protocol_connection_lost()
Check if connection has been lost.
unsigned short uint16_t
Definition common.h:57
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:414
#define SAFE_MALLOC(size, cast)
Definition common.h:264
#define FATAL(code,...)
Exit with error code and custom message, with stack trace in debug builds.
Definition common.h:158
unsigned long long uint64_t
Definition common.h:59
void asciichat_shared_destroy(void)
Clean up all shared library subsystems.
Definition common.c:195
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
#define CLEAR_ERRNO()
Clear the current error state.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ERROR_INVALID_STATE
@ ERROR_NETWORK
Definition error_codes.h:77
@ ERROR_WEBCAM_IN_USE
Definition error_codes.h:65
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_WEBCAM_PERMISSION
Definition error_codes.h:66
@ ERROR_WEBCAM
Definition error_codes.h:64
@ ERROR_THREAD
void log_truncate_if_large(void)
Manually truncate large log files.
Definition log/log.c:807
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
void log_destroy(void)
Destroy the logging system and close log file.
Definition log/log.c:624
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
#define log_fatal(...)
Log a FATAL message.
Definition log/log.h:599
bool log_lock_terminal(void)
Lock terminal output for exclusive access by the calling thread.
Definition log/log.c:787
#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
uint64_t time_get_ns(void)
Get current monotonic time in nanoseconds.
Definition util/time.c:108
int time_pretty(uint64_t nanoseconds, int decimals, char *buffer, size_t buffer_size)
Format nanoseconds as pretty duration with spaces and configurable precision.
Definition util/time.c:424
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 US_PER_MS_INT
Definition time.h:160
#define MS_PER_SEC_INT
Definition time.h:164
const char * network_error_string()
Get human-readable error string for network errors.
#define GET_OPTION(field)
Safely get a specific option field (lock-free read)
asciichat_error_t options_set_int(const char *field_name, int value)
Check if an option was explicitly set via command-line.
Definition rcu.c:761
const options_t * options_get(void)
Get current options (lock-free read)
Definition rcu.c:496
asciichat_error_t options_set_bool(const char *field_name, bool value)
Definition rcu.c:969
signal_handler_t platform_signal(int sig, signal_handler_t handler)
Set a signal handler.
Definition misc.c:115
void platform_disable_keepawake(void)
Disable system sleep prevention (allow OS to sleep)
void platform_force_exit(int exit_code)
Forcefully terminate the process immediately without cleanup.
Definition misc.c:121
uint64_t platform_get_monotonic_time_us(void)
Get monotonic time in microseconds.
void platform_sleep_ms(unsigned int ms)
Sleep for a specified number of milliseconds.
void platform_sleep_us(unsigned int us)
High-precision sleep function with microsecond precision.
int splash_intro_done(void)
Signal that intro splash should end (first frame ready to render)
Definition splash.c:693
bool url_is_websocket(const char *url)
Check if a URL string is a valid WebSocket URL.
Definition url.c:307
int is_localhost_ipv4(const char *ip)
Check if IPv4 address is localhost.
Definition ip.c:1299
void webcam_print_init_error_help(asciichat_error_t error_code)
Print helpful error diagnostics for webcam initialization failures.
📝 JSON structured logging output
ACIP client-side protocol API.
⚙️ Unified options parsing system for ascii-chat with builder pattern and lock-free access
Platform initialization and static synchronization helpers.
Thread-safe circular log buffer for session screens (status, splash)
🌍 IP Address Parsing and Formatting Utilities
void app_client_destroy(app_client_t **client_ptr)
Destroy client application context and free all resources.
app_client_t * app_client_create(void)
Create and initialize client application context.
📝 Logging API with multiple log levels and terminal output control
void signal_exit(void)
Definition misc.c:129
void set_interrupt_callback(void(*callback)(void))
Definition misc.c:125
mdns_result_t * ui_mdns_get_best_address(mdns_query_t *query)
Definition misc.c:144
void ui_mdns_free_results(mdns_result_t *results)
Definition misc.c:155
mdns_query_t * ui_mdns_query(const char *service)
Definition misc.c:139
h265_encoder_t * h265_encoder_create(int width, int height, double fps)
Definition misc.c:105
bool should_exit(void)
Definition misc.c:131
int ui_mdns_select(const ui_mdns_server_t *servers, int count)
TUI-based server selection with formatted display.
Definition misc.c:149
🌐 Core network I/O operations with timeout support
bool auto_height
bool auto_width
ASCII Palette Management for Video-to-ASCII Conversion.
📂 Path Manipulation Utilities
unsigned short int terminal_get_effective_width(void)
Get effective terminal width with fallback priority.
unsigned short int terminal_get_effective_height(void)
Get effective terminal height with fallback priority.
asciichat_error_t get_terminal_size(unsigned short int *width, unsigned short int *height)
Get terminal size with multiple fallback methods.
Interactive search filtering for terminal screens.
ascii-chat Server Mode Entry Point Header
Cross-platform crash signal and exception handling.
Intro and status screen display for ascii-chat with animated rainbow effects.
🔊 Session-level audio coordination wrapper
🖥️ Unified terminal display abstraction for session-based rendering
Server cryptographic operations and per-client handshake management.
H.265 codec transport protocol definitions.
Server-side H.265 decoding pipeline for multi-client streams.
#define bool
Definition stdbool.h:61
bool(* should_exit)(void)
Client connection session state for session_client_like integration.
const char * discovered_address
connection_attempt_context_t connection_ctx
Context for connection attempts (TCP or WebSocket)
connection_handle_t connection
Shared transport/client ownership handle.
acip_transport_t * transport
Consolidated options structure.
bool lan_discovery
Enable LAN service discovery via mDNS (client only)
char address[256]
Server address (client) or bind address (server)
Internal session capture context structure.
session_client_like_run_fn run_fn
Internal session display context structure.
Configuration for TUI discovery.
Definition ui/mdns.h:69
int max_servers
Maximum servers to collect (default: 20)
Definition ui/mdns.h:71
int timeout_ms
Maximum time to wait for responses (default: 2000)
Definition ui/mdns.h:70
bool quiet
Suppress discovery messages (default: false)
Definition ui/mdns.h:72
Discovered server information from mDNS.
Definition ui/mdns.h:57
char name[256]
Service instance name (e.g., "swift-river-canyon")
Definition ui/mdns.h:58
uint16_t port
Server port number.
Definition ui/mdns.h:60
WebRTC peer manager structure.
void thread_pool_destroy(thread_pool_t *pool)
Destroy a thread pool.
void thread_pool_t
thread_pool_t * thread_pool_create(int num_threads)
Symbol Resolution Cache for Backtrace Addresses.
🔒 Synchronization primitive debugging (mutexes, rwlocks, condition variables)
Cross-platform system functions interface for ascii-chat.
🖥️ Cross-platform terminal interface for ascii-chat
⏱️ High-precision timing utilities using sokol_time.h and uthash
mDNS service discovery UI with terminal_screen rendering
🌐 Production-Grade URL Validation and Parsing