ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
main.c File Reference

ascii-chat Client Main Entry Point More...

Go to the source code of this file.

Data Structures

struct  client_session_state_t
 Client connection session state for session_client_like integration. More...
 

Functions

int client_main (void)
 Client mode entry point for unified binary.
 

Variables

thread_pool_t * g_client_worker_pool = NULL
 Global client worker thread pool.
 
app_client_t * g_client = NULL
 Global client application context.
 
struct webrtc_peer_managerg_peer_manager = NULL
 Global WebRTC peer manager (legacy compatibility)
 

Detailed Description

ascii-chat Client Main Entry Point

This module serves as the main entry point for the ascii-chat client application. It orchestrates the entire client lifecycle including initialization, connection management, and the primary event loop that manages reconnection logic.

Architecture Overview

The client follows a modular threading architecture:

  • Main thread: Connection management and event coordination
  • Data reception thread: Handles incoming packets from server
  • Ping thread: Maintains connection keepalive
  • Webcam capture thread: Captures and transmits video frames
  • Audio capture thread: Captures and transmits audio data (optional)

Connection Management

The client implements robust reconnection logic with exponential backoff:

  1. Initial connection attempt
  2. On connection loss, attempt reconnection with increasing delays
  3. Maximum delay cap to prevent excessive wait times
  4. Clean thread lifecycle management across reconnections

Thread Lifecycle

Each connection cycle follows this pattern:

  1. Connection Establishment: Socket creation and server handshake
  2. Thread Spawning: Start all worker threads for the connection
  3. Active Monitoring: Monitor connection health and thread status
  4. Connection Loss Detection: Detect broken connections via thread exit
  5. Cleanup Phase: Join all threads and reset connection state
  6. Reconnection Cycle: Repeat from step 1 unless shutdown requested

Integration Points

  • server.c: Connection establishment and socket management
  • protocol.c: Initial capability negotiation and join packets
  • display.c: Terminal initialization and control
  • capture.c: Media capture subsystem initialization
  • audio.c: Audio subsystem initialization (if enabled)
  • keepalive.c: Ping thread management

Error Handling

The main loop implements graceful error recovery:

  • Fatal initialization errors cause immediate exit
  • Network errors trigger reconnection attempts
  • Signal handling for graceful shutdown (SIGINT, SIGWINCH)
  • Resource cleanup on all exit paths

Platform Compatibility

Uses platform abstraction layer for:

  • Socket operations and network initialization
  • Thread management and synchronization
  • Signal handling differences between Unix and Windows
  • Terminal I/O and control sequences
Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
October 2025
Version
2.0

Definition in file client/main.c.

Function Documentation

◆ client_main()

int client_main ( void  )

Client mode entry point for unified binary.

This function implements the complete client lifecycle including:

  • Client-specific initialization (display, capture, audio)
  • Server connection with reconnection logic
  • Media streaming and frame display
  • Graceful shutdown and cleanup

Options are already parsed by the main dispatcher before this function is called, so they are available via global opt_* variables.

Returns
0 on success, non-zero error code on failure
Example
# Invoked by dispatcher after options are parsed:
ascii-chat client --address localhost --audio
# Options parsed in main.c, then client_main() called

Definition at line 569 of file client/main.c.

569 {
570 log_debug("client_main() starting");
571
572 // Initialize client-specific systems (NOT shared with session_client_like)
573 // This includes: thread pool, display layer, app client context, server connection
574 int init_result = initialize_client_systems();
575 if (init_result != 0) {
576#ifndef NDEBUG
577 // Debug builds: automatically fall back to test pattern if webcam is in use
578 if (init_result == ERROR_WEBCAM_IN_USE && !GET_OPTION(test_pattern)) {
579 log_warn("Webcam is in use - automatically falling back to test pattern mode (debug build only)");
580
581 // Enable test pattern mode via RCU update
582 asciichat_error_t update_result = options_set_bool("test_pattern", true);
583 if (update_result != ASCIICHAT_OK) {
584 log_error("Failed to update options for test pattern fallback");
585 FATAL(init_result, "%s", asciichat_error_string(init_result));
586 }
587
588 // Retry initialization with test pattern enabled
589 init_result = initialize_client_systems();
590 if (init_result != 0) {
591 log_error("Failed to initialize even with test pattern fallback");
592 webcam_print_init_error_help(init_result);
593 FATAL(init_result, "%s", asciichat_error_string(init_result));
594 }
595 log_debug("Successfully initialized with test pattern fallback");
596
597 // Clear the error state since we successfully recovered
598 CLEAR_ERRNO();
599 } else
600#endif
601 {
602 // Release builds or other errors: print help and exit
603 if (init_result == ERROR_WEBCAM || init_result == ERROR_WEBCAM_IN_USE || init_result == ERROR_WEBCAM_PERMISSION) {
604 webcam_print_init_error_help(init_result);
605 FATAL(init_result, "%s", asciichat_error_string(init_result));
606 }
607 // For other errors, just exit with the error code
608 return init_result;
609 }
610 }
611
612 // Register cleanup function for graceful shutdown
613 (void)atexit(shutdown_client);
614
615 // Register client interrupt callback (socket shutdown on SIGTERM/Ctrl+C)
616 // Global signal handlers (SIGTERM, SIGPIPE, Ctrl+C) are set up in setup_signal_handlers() in src/main.c
618
619#ifndef _WIN32
620 // Register SIGWINCH for terminal resize handling (client-specific, not in framework)
621 platform_signal(SIGWINCH, client_handle_sigwinch);
622#endif
623
624 /* ========================================================================
625 * Client-Specific: LAN/Session Discovery
626 * ========================================================================
627 * This phase discovers the server to connect to via:
628 * - LAN discovery (mDNS)
629 * - Session string lookup (ACDS)
630 * - Direct address/port
631 */
632
633 // LAN Discovery: If --scan flag is set, discover servers on local network
634 const options_t *opts = options_get();
635 if (opts && opts->lan_discovery &&
636 (opts->address[0] == '\0' || is_localhost_ipv4(opts->address) || strcmp(opts->address, "localhost") == 0)) {
637 log_debug("LAN discovery: --scan flag set, querying for available servers");
638
639 discovery_tui_config_t lan_config;
640 memset(&lan_config, 0, sizeof(lan_config));
641 lan_config.timeout_ms = 2 * MS_PER_SEC_INT; // Wait up to 2 seconds for responses
642 lan_config.max_servers = 20; // Support up to 20 servers on LAN
643 lan_config.quiet = true; // Quiet during discovery, TUI will show status
644
645 int discovered_count = 0;
646 discovery_tui_server_t *discovered_servers = discovery_tui_query(&lan_config, &discovered_count);
647
648 // Use TUI for server selection
649 int selected_index = discovery_tui_select(discovered_servers, discovered_count);
650
651 if (selected_index < 0) {
652 // User cancelled or no servers found
653 if (discovered_count == 0) {
654 // No servers found - log message and prevent any further output
655 // Lock the terminal so other threads can't write and our error
656 // will be the last message
658
659 // Log single message with embedded newlines to prevent multiple log entries
660 log_error("No ascii-chat servers found on the local network.\nUse 'ascii-chat client <address>' to connect "
661 "manually.");
662
663 // Exit without cleanup
664 platform_force_exit(1);
665 }
666 // User cancelled (had servers to choose from but pressed cancel)
667 log_debug("LAN discovery: User cancelled server selection");
668 if (discovered_servers) {
669 discovery_tui_free_results(discovered_servers);
670 }
671 return 1; // User cancelled
672 }
673
674 // Update options with discovered server's address and port
675 discovery_tui_server_t *selected = &discovered_servers[selected_index];
676 const char *selected_address = discovery_tui_get_best_address(selected);
677
678 // We need to modify options, but they're immutable via RCU
679 // Create a new options copy with updated address/port
680 options_t *opts_new = SAFE_MALLOC(sizeof(options_t), options_t *);
681 if (opts_new) {
682 memcpy(opts_new, opts, sizeof(options_t));
683 SAFE_STRNCPY(opts_new->address, selected_address, sizeof(opts_new->address));
684
685 opts_new->port = (int)selected->port;
686
687 log_debug("LAN discovery: Selected server '%s' at %s:%d", selected->name, opts_new->address, opts_new->port);
688
689 // Note: In a real scenario, we'd update the global options via RCU
690 // For now, we'll use the updated values directly for connection
691 // This is a known limitation - proper RCU update requires more infrastructure
692 }
693
694 discovery_tui_free_results(discovered_servers);
695 }
696
697 // =========================================================================
698 // Client-Specific: Server Address Resolution
699 // =========================================================================
700 // Client mode supports:
701 // 1. Direct address/port (--address HOST --port PORT) - handled by options
702 // 2. LAN discovery (--scan) - handled above
703 // 3. WebSocket URL (direct ws:// or wss:// connection string)
704 //
705 // Note: Session string discovery via ACDS is handled by discovery mode only.
706 // Client mode does NOT use ACDS or session strings.
707
708 const char *discovered_address = NULL;
709 int discovered_port = 0;
710
711 // Check if user provided a WebSocket URL as the server address
712 const options_t *opts_websocket = options_get();
713 const char *provided_address = opts_websocket && opts_websocket->address[0] != '\0' ? opts_websocket->address : NULL;
714
715 if (provided_address && url_is_websocket(provided_address)) {
716 // Direct WebSocket connection
717 log_debug("Client: Direct WebSocket URL: %s", provided_address);
718 discovered_address = provided_address;
719 discovered_port = 0; // Port is embedded in the URL
720 }
721
722 log_debug("Client: discovered_address=%s, discovered_port=%d", discovered_address ? discovered_address : "NULL",
723 discovered_port);
724
725 // Store discovered address/port in session state for client_run() callback
726 static char address_storage[BUFFER_SIZE_SMALL];
727 if (discovered_address) {
728 SAFE_STRNCPY(address_storage, discovered_address, sizeof(address_storage));
729 g_client_session.discovered_address = address_storage;
730 g_client_session.discovered_port = discovered_port;
731 } else {
732 const options_t *opts_fallback = options_get();
733 if (opts_fallback && opts_fallback->address[0] != '\0') {
734 SAFE_STRNCPY(address_storage, opts_fallback->address, sizeof(address_storage));
735 g_client_session.discovered_address = address_storage;
736 g_client_session.discovered_port = opts_fallback->port;
737 } else {
738 g_client_session.discovered_address = "localhost";
739 g_client_session.discovered_port = 27224;
740 }
741 }
742
743 // Initialize connection context for first attempt
744 asciichat_error_t ctx_init_result = connection_context_init(&g_client_session.connection_ctx);
745 if (ctx_init_result != ASCIICHAT_OK) {
746 log_error("Failed to initialize connection context");
747 return 1;
748 }
749
750 /* ========================================================================
751 * Configure and Run Shared Client-Like Session Framework
752 * ========================================================================
753 * session_client_like_run() handles all shared initialization:
754 * - Terminal output management (force stderr if piped)
755 * - Keepawake system (platform sleep prevention)
756 * - Splash screen lifecycle
757 * - Media source selection (webcam, file, URL, test pattern)
758 * - FPS probing for media files
759 * - Audio initialization and lifecycle
760 * - Display context creation
761 * - Proper cleanup ordering (critical for PortAudio)
762 *
763 * Client mode provides:
764 * - client_run() callback: connection loop, protocol startup, monitoring
765 * - client_should_reconnect() callback: reconnection policy
766 * - Reconnection configuration: attempts, delay, callbacks
767 */
768
769 // Get reconnect attempts setting (-1 = unlimited, 0 = no retry, >0 = retry N times)
770 int reconnect_attempts = GET_OPTION(reconnect_attempts);
771
772 // Configure session_client_like with client-specific settings
773 session_client_like_config_t config = {
774 .run_fn = client_run,
775 .run_user_data = NULL,
776 .tcp_client = NULL,
777 .websocket_client = NULL,
778 .discovery = NULL,
779 .custom_should_exit = NULL,
780 .exit_user_data = NULL,
781 .keyboard_handler = NULL, // Client mode: server drives display
782 .max_reconnect_attempts = reconnect_attempts,
783 .should_reconnect_callback = client_should_reconnect,
784 .reconnect_user_data = NULL,
785 .reconnect_delay_ms = 1000, // 1 second delay between reconnection attempts
786 .print_newline_on_tty_exit = false, // Server/client manages cursor
787 };
788
789 log_debug("Client: calling session_client_like_run() with %d max reconnection attempts", reconnect_attempts);
790 asciichat_error_t session_result = session_client_like_run(&config);
791 log_debug("Client: session_client_like_run() returned %d", session_result);
792
793 // Cleanup connection context
795
796 // Cleanup session log buffer (used by splash screen in session_client_like)
798
799 log_debug("ascii-chat client shutting down");
800
801 // IMPORTANT: Stop worker threads and join them BEFORE memory report
802 // atexit(shutdown_client) won't run if interrupted by SIGTERM, so call explicitly
803 shutdown_client();
804
805 // Cleanup remaining shared subsystems (buffer pool, platform, etc.)
806 // Note: atexit(asciichat_shared_destroy) is registered in main.c,
807 // but won't run if interrupted by signals (SIGTERM from timeout/killall)
809
810 return (session_result == ASCIICHAT_OK) ? 0 : 1;
811}
asciichat_error_t session_client_like_run(const session_client_like_config_t *config)
Definition client_like.c:96
void asciichat_shared_destroy(void)
Clean up all shared library subsystems.
Definition common.c:164
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.
void discovery_tui_free_results(discovery_tui_server_t *servers)
Free results from mDNS discovery.
discovery_tui_server_t * discovery_tui_query(const discovery_tui_config_t *config, int *out_count)
TUI wrapper around core mDNS discovery.
int discovery_tui_select(const discovery_tui_server_t *servers, int count)
TUI-based server selection with formatted display.
const char * discovery_tui_get_best_address(const discovery_tui_server_t *server)
Get best address for a server.
void server_connection_shutdown()
Emergency connection shutdown for signal handlers.
int is_localhost_ipv4(const char *ip)
Definition ip.c:1299
bool log_lock_terminal(void)
void set_interrupt_callback(void(*cb)(void))
Definition main.c:102
const options_t * options_get(void)
Definition rcu.c:347
asciichat_error_t options_set_bool(const char *field_name, bool value)
Definition rcu.c:562
void session_log_buffer_destroy(void)
const char * discovered_address
connection_attempt_context_t connection_ctx
bool url_is_websocket(const char *url)
Definition url.c:307
void webcam_print_init_error_help(asciichat_error_t error_code)

References asciichat_shared_destroy(), connection_context_cleanup(), connection_context_init(), client_session_state_t::connection_ctx, client_session_state_t::discovered_address, client_session_state_t::discovered_port, discovery_tui_free_results(), discovery_tui_get_best_address(), discovery_tui_query(), discovery_tui_select(), is_localhost_ipv4(), log_lock_terminal(), options_get(), options_set_bool(), server_connection_shutdown(), session_client_like_run(), session_log_buffer_destroy(), set_interrupt_callback(), url_is_websocket(), and webcam_print_init_error_help().

Variable Documentation

◆ g_client

app_client_t* g_client = NULL

Global client application context.

Global application client context

Central connection and application state for the client. Contains transport-agnostic state: audio, threads, display, crypto. Network-specific state (socket, connection flags) is in the active transport client.

Initialized by app_client_create() in client_main(). Destroyed by app_client_destroy() at cleanup.

Definition at line 151 of file client/main.c.

◆ g_client_worker_pool

thread_pool_t* g_client_worker_pool = NULL

Global client worker thread pool.

Global client worker thread pool

Manages all client worker threads including:

  • Data reception thread (protocol.c)
  • Webcam capture thread (capture.c)
  • Ping/keepalive thread (keepalive.c)
  • Audio capture thread (audio.c)
  • Audio sender thread (audio.c)

Definition at line 139 of file client/main.c.

Referenced by audio_start_thread(), capture_start_thread(), keepalive_start_thread(), protocol_start_connection(), and protocol_stop_connection().

◆ g_peer_manager

struct webrtc_peer_manager* g_peer_manager = NULL

Global WebRTC peer manager (legacy compatibility)

Global WebRTC peer manager for P2P connections.

Client mode no longer uses WebRTC, but protocol.c still references this. Always NULL in client mode.

Definition at line 159 of file client/main.c.