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

ascii-chat Client Mode Entry Point Header More...

Go to the source code of this file.

Functions

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

Variables

crypto_handshake_context_t g_crypto_ctx
 Global crypto handshake context for this client connection.
 
thread_pool_t * g_client_worker_pool
 Global client worker thread pool.
 
app_client_t * g_client
 Global client application context.
 
struct webrtc_peer_managerg_peer_manager
 Global WebRTC peer manager for P2P connections.
 

Detailed Description

ascii-chat Client Mode Entry Point Header

This header exposes the client mode entry point for the unified binary architecture. The unified binary dispatches to client_main() when invoked as ascii-chat client.

Unified Binary Architecture

The ascii-chat application uses a single binary with multiple operating modes:

  • ascii-chat server - Run as server (multi-client connection manager)
  • ascii-chat client - Run as client (connects to server, streams video/audio)

This design provides several benefits:

  • Simplified distribution (single binary to install)
  • Reduced disk space (shared library code)
  • Easier testing (one binary to build and deploy)
  • Consistent versioning across modes

Mode Entry Point Contract

Each mode entry point (server_main, client_main) must:

  • Accept no arguments: int mode_main(void)
  • Options are already parsed by main dispatcher (available via global opt_* variables)
  • Return 0 on success, non-zero error code on failure
  • Perform mode-specific initialization and main loop
  • Perform complete cleanup before returning

Implementation Notes

The client_main() function is the original main() from src/client/main.c, adapted to the new dispatcher pattern. Common initialization (options parsing, logging setup, lock debugging, –show-capabilities) now happens in src/main.c before dispatch.

Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
2025
Version
2.0

Definition in file client/main.h.

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
extern

Global client application context.

Central state management structure for the client, containing both network and application-layer state.

This instance provides:

  • Connection state management via active_transport (TCP or WebSocket)
  • Audio queue management for async audio streaming
  • Thread handles for worker threads (data reception, capture, ping, audio)
  • Display state and terminal capability tracking
  • Crypto handshake context for encrypted connections
  • Protocol state (client ID, server initialization status)

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

All client modules (protocol.c, audio.c, capture.c, display.c) should use this instance instead of accessing global connection state.

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
extern

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)

This pool is initialized in client_main() and accessible from all client modules for spawning and managing worker threads.

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
extern

Global WebRTC peer manager for P2P connections.

Manages WebRTC peer connections for ACDS session participants. Handles SDP/ICE exchange and DataChannel lifecycle for P2P ACIP transport.

NULL if WebRTC not initialized (TCP-only mode). Initialized when joining/creating ACDS sessions with WebRTC transport.

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.