ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
main.c File Reference

ascii-chat Unified Binary - Mode Dispatcher and Entry Point More...

Go to the source code of this file.

Data Structures

struct  mode_descriptor_t
 Metadata for a mode. More...
 

Macros

#define APP_NAME   "ascii-chat"
 
#define VERSION   ASCII_CHAT_VERSION_FULL
 
#define REGISTER_GLOBAL_ATOMICS()
 

Typedefs

typedef int(* mode_entry_point_t) (void)
 

Functions

atomic_t g_should_exit ATOMIC_INIT_AUTO (g_should_exit)
 
bool should_exit (void)
 
void signal_exit (void)
 
void set_interrupt_callback (void(*cb)(void))
 
void setup_signal_handlers (void)
 
int main (int argc, char *argv[])
 

Detailed Description

ascii-chat Unified Binary - Mode Dispatcher and Entry Point

This file implements the main entry point for the unified ascii-chat binary, which provides server, client, mirror, and discovery service functionality in a single executable.

The dispatcher now delegates ALL option parsing (including mode detection and binary-level options) to options_init(), then simply dispatches to the appropriate mode-specific entry point based on the detected mode.

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

Fix: CMAKE_OSX_SYSROOT properly configured for macOS builds WebRTC: Pass CMAKE_OSX_SYSROOT to subprocess for Threads discovery Test: Running macOS builds with WebRTC Threads fix

Definition in file main.c.

Macro Definition Documentation

◆ APP_NAME

#define APP_NAME   "ascii-chat"

Definition at line 95 of file main.c.

◆ REGISTER_GLOBAL_ATOMICS

#define REGISTER_GLOBAL_ATOMICS ( )
Value:
do { \
static bool _global_atomics_registered = false; \
if (!_global_atomics_registered) { \
NAMED_REGISTER_ATOMIC(&g_should_exit, "application_exit_flag", NULL); \
_global_atomics_registered = true; \
} \
} while (0)
atomic_t g_should_exit
Global application exit flag (shared across all modes)
Definition globals.c:34

Definition at line 190 of file main.c.

191 { \
192 static bool _global_atomics_registered = false; \
193 if (!_global_atomics_registered) { \
194 NAMED_REGISTER_ATOMIC(&g_should_exit, "application_exit_flag", NULL); \
195 _global_atomics_registered = true; \
196 } \
197 } while (0)

◆ VERSION

#define VERSION   ASCII_CHAT_VERSION_FULL

Definition at line 96 of file main.c.

Typedef Documentation

◆ mode_entry_point_t

typedef int(* mode_entry_point_t) (void)

Definition at line 364 of file main.c.

Function Documentation

◆ ATOMIC_INIT_AUTO()

atomic_t g_should_exit ATOMIC_INIT_AUTO ( g_should_exit  )

Global flag indicating application should exit (used by all modes)

◆ main()

int main ( int  argc,
char *  argv[] 
)

Definition at line 472 of file main.c.

472 {
473 // Set global argc/argv for early argv inspection (e.g., in terminal.c)
474 g_argc = argc;
475 g_argv = argv;
476
477 // Validate basic argument structure
478 if (argc < 1 || argv == NULL || argv[0] == NULL) {
479 return 1;
480 }
481
482 // Show cursor early in case a previous session crashed with it hidden
483 (void)terminal_cursor_show();
484
485 // Initialize the named registry for debugging (allows --debug-state to show registered synchronization primitives)
486#ifndef NDEBUG
487 named_init();
488 // Initialize atomic operations debug tracking
490 // Register global atomics with descriptive names for debug sync state monitoring
492 // Static atomics are now auto-registered via ATOMIC_INIT_AUTO macro at definition time
493 // Register all packet types from the packet_type_t enum
495#endif
496
497 // Register the main thread IMMEDIATELY after named_init() to ensure it's available for all subsequent allocations
498#ifndef NDEBUG
499#ifdef _WIN32
500 NAMED_REGISTER_THREAD(GetCurrentThread(), "main", NULL);
501#else
503#endif
504 // Also save main thread ID for memory reporting (must be very early)
506#endif
507
508 // VERY FIRST: Scan for --color BEFORE ANY logging initialization
509 // This sets global flags that persist through cleanup, enabling --color to force colors
510 for (int i = 1; i < argc; i++) {
511 if (strcmp(argv[i], "--color") == 0 || strcmp(argv[i], "--color=true") == 0) {
512 g_color_flag_passed = true;
513 g_color_flag_value = true;
514 break;
515 } else if (strncmp(argv[i], "--color=", 8) == 0) {
516 g_color_flag_passed = true;
517 g_color_flag_value = strcmp(argv[i], "--color=false") != 0;
518 break;
519 }
520 }
521
522 // SECOND: Scan for --grep BEFORE ANY logging initialization
523 // This ensures ALL logs (including from shared_init) can be filtered
524 // Supports multiple --grep patterns (ORed together)
525 for (int i = 1; i < argc - 1; i++) {
526 if (strcmp(argv[i], "--grep") == 0) {
527 const char *pattern = argv[i + 1];
528 asciichat_error_t filter_result = grep_init(pattern);
529 if (filter_result != ASCIICHAT_OK) {
530 log_error("Invalid --grep pattern or invalid flags: \"%s\" - use /pattern/flags format (e.g., \"/query/ig\" or "
531 "\"/literal/F\")",
532 pattern);
533 return 1;
534 }
535 i++; // Skip the pattern argument
536 }
537 }
538
539 // Detect terminal capabilities early so colored help output works
540 // Logging will be initialized by asciichat_shared_init() before options_init()
543 caps = apply_color_mode_override(caps);
544
545 // Warn if Release build was built from dirty working tree
546#if ASCII_CHAT_GIT_IS_DIRTY
547 if (strcmp(ASCII_CHAT_BUILD_TYPE, "Release") == 0) {
548 // Dirty git warning will be logged after logging initialization
549 }
550#endif
551
552 // Load color scheme early (from config files and CLI) before logging initialization
553 // This allows logging to use the correct colors from the start
555
556 // Parse --color-scheme from argv early to set logging colors for help output
557 const char *colorscheme_name = "pastel"; // default
558 for (int i = 1; i < argc - 1; i++) {
559 if (strcmp(argv[i], "--color-scheme") == 0) {
560 colorscheme_name = argv[i + 1];
561 break;
562 }
563 }
564
565 // Load and apply colorscheme to logging BEFORE options_init() so help gets colors
566 color_scheme_t scheme;
567 if (colorscheme_load_builtin(colorscheme_name, &scheme) == ASCIICHAT_OK) {
568 log_set_color_scheme(&scheme);
569 } else {
570 // Fall back to default pastel if scheme not found
571 if (colorscheme_load_builtin("pastel", &scheme) == ASCIICHAT_OK) {
572 log_set_color_scheme(&scheme);
573 }
574 }
575
576 // Initialize logging colors so they're ready for help output
578
579 // EARLY PARSE: Find the mode position (first positional argument)
580 // Binary-level options must appear BEFORE the mode
581 int mode_position = -1;
582 for (int i = 1; i < argc; i++) {
583 if (argv[i][0] != '-') {
584 // Found a positional argument - this is the mode or session string
585 mode_position = i;
586 break;
587 }
588
589 // Skip option and its argument if needed
590 // Check if this is an option that takes a required argument
591 const char *arg = argv[i];
592 const char *opt_name = arg;
593 if (arg[0] == '-') {
594 opt_name = arg + (arg[1] == '-' ? 2 : 1);
595 }
596
597 // Handle --option=value format
598 if (strchr(opt_name, '=')) {
599 continue; // Value is part of this arg, no need to skip next
600 }
601
602 // Options that take required arguments
603 if (strcmp(arg, "--log-file") == 0 || strcmp(arg, "-L") == 0 || strcmp(arg, "--log-level") == 0 ||
604 strcmp(arg, "--config") == 0 || strcmp(arg, "--color-scheme") == 0 || strcmp(arg, "--log-template") == 0) {
605 if (i + 1 < argc) {
606 i++; // Skip the argument value
607 }
608 }
609 }
610
611 // EARLY PARSE: Determine mode from argv to know if this is client-like mode
612 // The first positional argument (after options) is the mode: client, server, mirror, discovery, acds
613 bool is_client_like_mode = false;
614 if (mode_position > 0) {
615 const char *first_arg = argv[mode_position];
616 if (strcmp(first_arg, "client") == 0 || strcmp(first_arg, "mirror") == 0 || strcmp(first_arg, "discovery") == 0) {
617 is_client_like_mode = true;
618 }
619 }
620
621 // EARLY PARSE: Extract log file from argv (--log-file or -L)
622 // Must appear BEFORE the mode
623 // Generate default log path based on build type and mode
624 char default_log_path[PATH_MAX];
625 asciichat_mode_t detected_mode = MODE_INVALID;
626 if (mode_position > 0) {
627 const char *mode_str = argv[mode_position];
628 // Convert mode string to enum
629 if (strcmp(mode_str, "server") == 0) {
630 detected_mode = MODE_SERVER;
631 } else if (strcmp(mode_str, "client") == 0) {
632 detected_mode = MODE_CLIENT;
633 } else if (strcmp(mode_str, "mirror") == 0) {
634 detected_mode = MODE_MIRROR;
635 } else if (strcmp(mode_str, "acds") == 0 || strcmp(mode_str, "discovery-service") == 0) {
636 detected_mode = MODE_DISCOVERY_SERVICE;
637 } else if (strcmp(mode_str, "discovery") == 0) {
638 detected_mode = MODE_DISCOVERY;
639 }
640 }
641 generate_default_log_path(detected_mode, default_log_path, sizeof(default_log_path));
642
643 const char *log_file = default_log_path;
644 int max_search = (mode_position > 0) ? mode_position : argc;
645 for (int i = 1; i < max_search - 1; i++) {
646 if ((strcmp(argv[i], "--log-file") == 0 || strcmp(argv[i], "-L") == 0)) {
647 log_file = argv[i + 1];
648 break;
649 }
650 }
651
652 // EARLY PARSE: Check for --json (JSON logging format)
653 // If JSON format is enabled, we'll use the JSON filename during early init
654 // --json MUST appear BEFORE the mode
655 bool early_json_format = false;
656 if (mode_position > 0) {
657 for (int i = 1; i < mode_position; i++) {
658 if (strcmp(argv[i], "--json") == 0) {
659 early_json_format = true;
660 break;
661 }
662 }
663 } else {
664 // If no mode found, search entire argv
665 for (int i = 1; i < argc; i++) {
666 if (strcmp(argv[i], "--json") == 0) {
667 early_json_format = true;
668 break;
669 }
670 }
671 }
672
673 // EARLY PARSE: Extract log template from argv (--log-template)
674 // Note: This is for format templates, different from --log-format which is the output format
675 // Binary-level options must appear BEFORE the mode
676 const char *early_log_template = NULL;
677 bool early_log_template_console_only = false;
678 for (int i = 1; i < max_search - 1; i++) {
679 if (strcmp(argv[i], "--log-template") == 0) {
680 early_log_template = argv[i + 1];
681 break;
682 }
683 }
684 for (int i = 1; i < max_search; i++) {
685 if (strcmp(argv[i], "--log-format-console-only") == 0) {
686 early_log_template_console_only = true;
687 break;
688 }
689 }
690
691 // Documentation actions should not be preceded by startup diagnostics.
692 bool early_quiet_output = false;
693 for (int i = 1; i < argc; i++) {
694 if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--version") == 0 ||
695 strcmp(argv[i], "-V") == 0) {
696 early_quiet_output = true;
697 break;
698 }
699 }
700
701 // Initialize shared subsystems BEFORE options_init()
702 // This ensures options parsing can use properly configured logging with colors
703 // If JSON format is requested, don't write text logs to file
704 // All logs will be JSON formatted later once options_init() runs
705 const char *early_log_file = early_json_format ? NULL : log_file;
706 asciichat_error_t init_result = asciichat_shared_init(early_log_file, is_client_like_mode, early_quiet_output);
707 if (init_result != ASCIICHAT_OK) {
708 return init_result;
709 }
710
711 // Route logs to stderr if stdout is piped (MUST happen early, before options_init logs)
712 // This keeps stdout clean for data output (e.g., --snapshot mode piped to file)
715 }
716
717 // Keep documentation output free of diagnostics while parsing the options.
718 if (early_quiet_output) {
720 }
721
722 // Register cleanup of shared subsystems to run on normal exit
723 // Library code doesn't call atexit() - the application is responsible
724 // IMPORTANT: atexit handlers run in LIFO order - last registered runs first
725 // So register order (top to bottom) = execution order (bottom to top):
726 // 1. PCRE2 cleanup (runs last) - frees compiled regexes after all code stops using them
727 // 2. Path cleanup (runs 3rd) - now a no-op but kept for API compatibility
728 // 3. Shared destroy (runs 2nd) - frees all shared resources
729 // 4. Cursor show (runs 1st) - shows cursor before exit
730 (void)atexit(asciichat_pcre2_cleanup_all);
731 (void)atexit(path_cleanup_thread_locals);
732 (void)atexit(asciichat_shared_destroy);
733 (void)atexit(on_exit_show_cursor);
734
735 // SECRET: Check for --backtrace (debug builds only) BEFORE options_init()
736 // Prints a backtrace and exits immediately - useful for debugging hangs
737#ifndef NDEBUG
738 for (int i = 1; i < argc; i++) {
739 if (strcmp(argv[i], "--backtrace") == 0) {
740 log_info("=== Backtrace at startup ===");
742 log_info("=== End Backtrace ===");
744 return 0;
745 }
746 }
747#endif
748
749 // NOW parse all options - can use logging with colors!
750 asciichat_error_t options_result = options_init(argc, argv);
751 if (options_result != ASCIICHAT_OK) {
753 if (HAS_ERRNO(&error_ctx)) {
754 log_error("Error: %s", error_ctx.context_message);
755 } else {
756 log_error("Error: Failed to initialize options (error code: %d)", options_result);
757 }
758
759 // Clean up options state before exiting
762
763 exit(options_result);
764 }
765
766 // Get parsed options
767 const options_t *opts = options_get();
768 if (!opts) {
769 log_error("Error: Options not initialized");
770 return 1;
771 }
772
773 // Apply the parsed level after the early logger has initialized.
775 // Determine output format early to decide on filename and logging strategy
776 bool use_json_logging = GET_OPTION(json);
777
778 // Determine the log filename
779 // Check if user explicitly passed --log-file (not just the mode-specific default from options_init)
780 bool user_specified_log_file = false;
781 for (int i = 1; i < argc - 1; i++) {
782 if (strcmp(argv[i], "--log-file") == 0 || strcmp(argv[i], "-L") == 0) {
783 user_specified_log_file = true;
784 break;
785 }
786 }
787
788 const char *final_log_file = opts->log_file;
789 char json_filename_buf[256];
790
791 if (use_json_logging) {
792 // When JSON logging is enabled, determine JSON output filename
793 if (user_specified_log_file) {
794 // User explicitly specified a log file - use it exactly for JSON output
795 SAFE_STRNCPY(json_filename_buf, final_log_file, sizeof(json_filename_buf) - 1);
796 } else {
797 // Using default: replace .log with .json in the mode-specific default
798 // e.g., server.log -> server.json, mirror.log -> mirror.json, etc.
799 size_t len = strlen(final_log_file);
800 if (len > 4 && strcmp(&final_log_file[len - 4], ".log") == 0) {
801 // File ends with .log - replace with .json
802 SAFE_STRNCPY(json_filename_buf, final_log_file, sizeof(json_filename_buf) - 1);
803 // Replace .log with .json
804 asciichat_error_t strcpy_result =
805 SAFE_STRCPY(&json_filename_buf[len - 4], sizeof(json_filename_buf) - (len - 4), ".json");
806 if (strcpy_result != ASCIICHAT_OK) {
807 log_error("Failed to write .json extension: %s", asciichat_error_string(strcpy_result));
808 }
809 } else {
810 // File doesn't end with .log - just append .json
811 snprintf(json_filename_buf, sizeof(json_filename_buf), "%s.json", final_log_file);
812 }
813 }
814 final_log_file = json_filename_buf;
815 // For JSON mode: Initialize logging without file (text will be disabled)
816 // We'll set up JSON output separately below
817 // Note: log_init() was already called in asciichat_shared_init()
818 } else {
819 // Text logging mode: use the file from options (which is mode-specific default or user-specified)
820 // Note: log_init() was already called in asciichat_shared_init()
821 }
822
823 // Apply custom log template if specified (use early parsed value if available, otherwise use options)
824 const char *final_format = early_log_template ? early_log_template : GET_OPTION(log_template);
825 bool final_format_console_only =
826 early_log_template ? early_log_template_console_only : GET_OPTION(log_format_console_only);
827 if (final_format && final_format[0] != '\0') {
828 asciichat_error_t fmt_result = log_set_format(final_format, final_format_console_only);
829 if (fmt_result != ASCIICHAT_OK) {
830 log_error("Failed to apply custom log format");
831 }
832 }
833
834 // Configure JSON output if requested
835 if (use_json_logging) {
836 // Open the JSON file for output
837 int json_fd = platform_open("main_log_file", final_log_file, O_CREAT | O_RDWR | O_TRUNC, FILE_PERM_PRIVATE);
838 if (json_fd >= 0) {
839 log_set_json_output(json_fd);
840 } else {
841 // Failed to open JSON file - report error and exit
842 SET_ERRNO(ERROR_CONFIG, "Failed to open JSON output file: %s", final_log_file);
843 return ERROR_CONFIG;
844 }
845 }
846
847 // Initialize colors now that logging is fully initialized
848 // This must happen after log_init() since log_init_colors() checks if g_log.initialized
850
851 // Apply quiet mode - disables terminal output
852 // Status screen mode only disables terminal output if terminal is interactive
853 // In non-interactive mode (piped output), logs go to stdout/stderr normally
854 if (GET_OPTION(quiet) ||
855 (opts->detected_mode == MODE_SERVER && GET_OPTION(status_screen) && terminal_is_interactive())) {
857 }
858
859 // Initialize palette based on command line options
860 const char *custom_chars = opts && opts->palette_custom_set ? opts->palette_custom : NULL;
861 if (apply_palette_config(GET_OPTION(palette_type), custom_chars) != 0) {
862 FATAL(ERROR_CONFIG, "Failed to apply palette configuration");
863 }
864
865 // Set quiet mode for memory debugging
866#if defined(DEBUG_MEMORY) && !defined(USE_MIMALLOC_DEBUG) && !defined(NDEBUG)
868#endif
869
870 // Truncate log if it's already too large
872 fflush(stderr);
873
874 // Handle --help and --version (these are detected and flagged by options_init)
875 // Terminal capabilities already initialized before options_init() at startup
876 if (opts->help) {
878 print_usage(opts->detected_mode);
879 fflush(NULL);
880 _Exit(0);
881 }
882
883 if (opts->version) {
886 // action_show_version() calls _Exit(), so we don't reach here
887 }
888
889#ifndef NDEBUG
890 // Handle --debug-state (debug builds only)
891 // Sleep for specified time AFTER mode initialization so locks are created
892 if (IS_OPTION_EXPLICIT(debug_sync_state_time, opts) && opts->debug_sync_state_time > 0.0) {
893 log_info("Will print sync state after %f seconds", opts->debug_sync_state_time);
894 }
895#endif
896
897 // For server mode with status screen: disable terminal output only if interactive
898 // In non-interactive mode (piped output), logs go to stdout/stderr normally
899 // The status screen (when shown) will capture and display logs in its buffer instead
902 }
903
904 log_dev("Logging initialized to %s", final_log_file);
905
906 // Note: We do NOT auto-disable colors when stdout appears to be piped, because:
907 // 1. Tools like ripgrep can display ANSI colors when piped
908 // 2. Sandboxed/containerized environments may report false positives for isatty()
909 // 3. Users can explicitly disable colors with --color=false if needed
910 // Color behavior is now fully controlled by --color and --color-mode options.
911
912#ifndef NDEBUG
913 // Initialize lock debugging system after logging is fully set up
914 log_debug("Initializing lock debug system...");
915 int debug_sync_result = debug_sync_init();
916 if (debug_sync_result != 0) {
917 LOG_ERRNO_IF_SET("Debug sync system initialization failed");
918 FATAL(ERROR_PLATFORM_INIT, "Debug sync system initialization failed");
919 }
920 log_debug("Debug sync system initialized successfully");
921
922 // Initialize memory debug system
923 // TEMPORARILY DISABLED: these calls hang in non-interactive mode
924 // log_debug("Initializing memory debug system...");
925 // int debug_memory_result = debug_memory_thread_init();
926 // if (debug_memory_result != 0) {
927 // LOG_ERRNO_IF_SET("Memory debug system initialization failed");
928 // FATAL(ERROR_PLATFORM_INIT, "Memory debug system initialization failed");
929 // }
930 // log_debug("Memory debug system initialized successfully");
931
932 // Start memory debug (now just prints report directly)
933 // if (debug_memory_thread_start() != 0) {
934 // LOG_ERRNO_IF_SET("Memory debug startup failed");
935 // FATAL(ERROR_THREAD, "Memory debug startup failed");
936 // }
937
938#ifndef _WIN32
939 // Unblock SIGUSR1 and SIGUSR2 at process level to ensure delivery
940 sigset_t set;
941 sigemptyset(&set);
942 sigaddset(&set, SIGUSR1);
943 sigaddset(&set, SIGUSR2);
944 sigprocmask(SIG_UNBLOCK, &set, NULL);
945
946 // Register SIGUSR1 to trigger synchronization debugging output in all modes
947 signal_handler_t old_usr1 = platform_signal(SIGUSR1, common_handle_sigusr1);
948 if (old_usr1 == SIG_ERR) {
949 log_warn("Failed to register SIGUSR1 handler");
950 } else {
951 log_debug("SIGUSR1 handler registered successfully");
952 }
953
954 // Register SIGUSR2 to trigger memory report in all modes
955 signal_handler_t old_usr2 = platform_signal(SIGUSR2, common_handle_sigusr2);
956 if (old_usr2 == SIG_ERR) {
957 log_warn("Failed to register SIGUSR2 handler");
958 } else {
959 log_debug("SIGUSR2 handler registered successfully");
960 }
961#endif
962#endif
963
964 if (opts->fps > 0) {
965 if (opts->fps < 1 || opts->fps > 144) {
966 log_warn("FPS value %d out of range (1-144), using default", opts->fps);
967 } else {
968 log_debug("FPS set from command line: %d", opts->fps);
969 }
970 }
971
972 // Automatic update check in background thread (once per week maximum)
973 if (!GET_OPTION(no_check_update)) {
975 }
976
977 // Set up global signal handlers BEFORE mode dispatch
978 // All modes use the same centralized exit mechanism
980
981 // Register application callbacks so lib code can check exit flags
982 // This connects the render loop's should_exit check to signal_exit()
983 static const app_callbacks_t app_callbacks = {
985 .signal_exit = signal_exit,
986 .platform_get_monotonic_time_us = platform_get_monotonic_time_us,
987 .platform_sleep_ms = platform_sleep_ms,
988 };
989 app_callbacks_register(&app_callbacks);
990
991 // Register shutdown callback so splash thread and other code can check for exit
993
994#ifndef NDEBUG
995 // Start debug threads now, after initialization but before mode entry
996 // This avoids lock contention during critical initialization phase
997 // Note: debug_sync_start_thread() and debug_memory_thread_start() now use
998 // direct pthread calls instead of mutex_init/cond_init to avoid named registry
999 // deadlock during thread startup
1000 if (debug_sync_start_thread() != 0) {
1001 LOG_ERRNO_IF_SET("Debug sync thread startup failed");
1002 FATAL(ERROR_THREAD, "Debug sync thread startup failed");
1003 }
1004 log_debug("Debug sync thread started");
1005
1006#endif
1007
1008 // Find and dispatch to mode entry point
1009 const mode_descriptor_t *mode = find_mode(opts->detected_mode);
1010 if (!mode) {
1011 log_error("Error: Mode not found for detected_mode=%d", opts->detected_mode);
1012 return 1;
1013 }
1014
1015 // Call the mode-specific entry point
1016 // Mode entry points use options_get() to access parsed options
1017 int exit_code = mode->entry_point();
1018
1019 // Named registry cleanup is handled by asciichat_shared_destroy()
1020 // (called via atexit handler), which runs AFTER the memory report
1021
1022 if (exit_code == ERROR_USAGE) {
1023 exit(ERROR_USAGE);
1024 }
1025
1026 return exit_code;
1027}
void app_callbacks_register(const app_callbacks_t *callbacks)
#define LOG_ERRNO_IF_SET(message)
Check if any error occurred and log it if so.
asciichat_error_t options_colorscheme_init_early(int argc, const char *const argv[])
Initialize color scheme early (before logging)
asciichat_error_t colorscheme_load_builtin(const char *name, color_scheme_t *scheme)
Load a built-in color scheme.
void debug_atomic_init(void)
Lifecycle initialization for debug atomic tracking.
void debug_memory_set_quiet_mode(bool quiet)
#define IS_OPTION_EXPLICIT(name, opts)
Definition explicit.h:57
asciichat_error_t grep_init(const char *pattern)
Initialize log filtering with PCRE2 regex pattern.
Definition grep.c:498
bool g_color_flag_value
Value of –color flag (true if –color was in argv) Set by options_init() before RCU is initialized,...
Definition common.c:58
char ** g_argv
Global argv for early argument inspection (e.g., –color flag detection) Set by main() for access from...
Definition common.c:54
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:414
int g_argc
Global argc for early argument inspection (e.g., –color flag detection) Set by main() for access from...
Definition common.c:53
bool g_color_flag_passed
Was –color explicitly passed in command-line arguments? Set by options_init() before RCU is initializ...
Definition common.c:57
asciichat_error_t asciichat_shared_init(const char *log_file, bool is_client, bool quiet_startup)
Initialize common subsystems shared by client and server.
Definition common.c:115
#define SAFE_STRCPY(dest, dest_size, src)
Definition common.h:471
#define FATAL(code,...)
Exit with error code and custom message, with stack trace in debug builds.
Definition common.h:158
void asciichat_shared_destroy(void)
Clean up all shared library subsystems.
Definition common.c:195
#define NAMED_REGISTER_THREAD(thread, name, parent_ptr)
Register a thread handle with name and automatic format specifier.
void named_registry_register_packet_types(void)
Register all packet types from packet_type_t enum.
asciichat_error_t named_init(void)
Initialize the named object registry.
Definition debug/named.c:89
void debug_sync_set_main_thread_id(void)
Initialize debug synchronization system.
Definition sync.c:639
int debug_sync_start_thread(void)
Start background debug thread for scheduled operations.
Definition sync.c:657
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
#define HAS_ERRNO(var)
Check if an error occurred and get full context.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ERROR_PLATFORM_INIT
Definition error_codes.h:60
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_CONFIG
Definition error_codes.h:57
@ ERROR_USAGE
Definition error_codes.h:53
@ 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_init_colors(void)
Initialize logging color system with current terminal capabilities.
Definition log/log.c:1671
#define log_dev(...)
Log a DEV message (most verbose, development only)
Definition log/log.h:534
void log_set_force_stderr(bool enabled)
Force all terminal log output to stderr.
Definition log/log.c:721
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
void log_set_level(log_level_t level)
Set the minimum log level.
Definition log/log.c:691
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
asciichat_error_t log_set_format(const char *format_str, bool console_only)
Set a custom log format string.
Definition log/log.c:744
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
#define log_file(...)
File-only logging - writes to log file only, no stderr output.
Definition log/log.h:646
void log_set_terminal_output(bool enabled)
Control stderr output to terminal.
Definition log/log.c:700
void log_set_color_scheme(const color_scheme_t *scheme)
Set the color scheme for logging output.
Definition log/log.c:1728
void log_redetect_terminal_capabilities(void)
Re-detect terminal capabilities after logging is initialized.
Definition log/log.c:1602
#define GET_OPTION(field)
Safely get a specific option field (lock-free read)
asciichat_mode_t
Mode type for options parsing.
const options_t * options_get(void)
Get current options (lock-free read)
Definition rcu.c:496
asciichat_error_t options_init(int argc, char **argv)
Initialize options by parsing command-line arguments with unified mode detection.
@ MODE_CLIENT
Client mode - network client options.
@ MODE_DISCOVERY_SERVICE
Discovery server mode - session management and WebRTC signaling.
@ MODE_INVALID
Invalid mode.
@ MODE_SERVER
Server mode - network server options.
@ MODE_MIRROR
Mirror mode - local webcam viewing (no network)
@ MODE_DISCOVERY
Discovery mode - participant that can dynamically become host.
int apply_palette_config(palette_type_t type, const char *custom_chars)
Apply palette configuration (set global palette)
Definition palette.c:250
signal_handler_t platform_signal(int sig, signal_handler_t handler)
Set a signal handler.
Definition misc.c:115
void backtrace_print_simple(int skip_frames)
Print backtrace of the current call stack.
int platform_open(const char *name, const char *pathname, int flags,...)
Safe file open (open replacement)
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.
#define FILE_PERM_PRIVATE
File permission: Private (owner read/write only)
Definition filesystem.h:187
asciichat_error_t terminal_cursor_show(void)
Show terminal cursor.
void update_banner_start_check(void)
Start the background update check thread.
bool(* shutdown_check_fn)(void)
Shutdown check callback function type.
Definition shutdown.h:32
void shutdown_register_callback(shutdown_check_fn callback)
Register application's shutdown check function.
Definition common.c:69
void path_cleanup_thread_locals(void)
Cleanup thread-local path resources.
Definition path.c:85
#define asciichat_thread_self()
void * asciichat_thread_t
void log_set_json_output(int fd)
Set the JSON output file descriptor.
Definition log/log.c:729
void signal_exit(void)
Definition main.c:215
#define REGISTER_GLOBAL_ATOMICS()
Definition main.c:190
void setup_signal_handlers(void)
Definition main.c:346
bool should_exit(void)
Definition main.c:211
terminal_capabilities_t apply_color_mode_override(terminal_capabilities_t caps)
Apply command-line overrides to detected capabilities.
Definition misc.c:29
void(* signal_handler_t)(int)
Definition misc.c:113
void action_show_version(void)
Show version information and exit.
void asciichat_pcre2_cleanup_all(void)
Free all PCRE2 singletons in the global registry.
Definition pcre2.c:214
bool terminal_is_interactive(void)
Check if the session is fully interactive.
bool terminal_should_force_stderr(void)
Determine if logs should be forced to stderr.
terminal_capabilities_t detect_terminal_capabilities(void)
Detect terminal capabilities.
void options_state_destroy(void)
Shutdown RCU options system.
Definition rcu.c:461
void options_cleanup_schema(void)
Clean up schema resources.
Definition rcu.c:492
bool(* should_exit)(void)
Error context structure.
char * context_message
Optional custom message (dynamically allocated, owned by system)
Color scheme definition.
Definition colorscheme.h:62
Metadata for a mode.
Definition main.c:366
mode_entry_point_t entry_point
Definition main.c:369
Consolidated options structure.
double debug_sync_state_time
Time parameter for –sync-state option (debug builds only)
asciichat_mode_t detected_mode
Mode detected from command-line arguments.
log_level_t log_level
Log level threshold.
char palette_custom[256]
Custom palette characters.
bool status_screen
Show status screen (default: true = show, use –no-status-screen to hide)
bool palette_custom_set
True if custom palette was set.
bool version
Show version information.
int fps
Target framerate (1-144, default: 60)
char log_file[256]
Log file path.
Complete terminal capabilities structure.
Definition terminal.h:709
int debug_sync_init(void)
Definition sync.c:644
#define ASCII_CHAT_BUILD_TYPE
Definition version.h:14

References action_show_version(), app_callbacks_register(), apply_color_mode_override(), apply_palette_config(), ASCII_CHAT_BUILD_TYPE, ASCIICHAT_OK, asciichat_pcre2_cleanup_all(), asciichat_shared_destroy(), asciichat_shared_init(), asciichat_thread_self, backtrace_print_simple(), colorscheme_load_builtin(), asciichat_error_context_t::context_message, debug_atomic_init(), debug_memory_set_quiet_mode(), debug_sync_init(), debug_sync_set_main_thread_id(), debug_sync_start_thread(), options_state::debug_sync_state_time, detect_terminal_capabilities(), options_state::detected_mode, mode_descriptor_t::entry_point, ERROR_CONFIG, ERROR_PLATFORM_INIT, ERROR_THREAD, ERROR_USAGE, FATAL, FILE_PERM_PRIVATE, options_state::fps, g_argc, g_argv, g_color_flag_passed, g_color_flag_value, GET_OPTION, grep_init(), HAS_ERRNO, options_state::help, IS_OPTION_EXPLICIT, log_debug, log_dev, LOG_ERRNO_IF_SET, log_error, log_file, options_state::log_file, log_info, log_init_colors(), options_state::log_level, log_redetect_terminal_capabilities(), log_set_color_scheme(), log_set_force_stderr(), log_set_format(), log_set_json_output(), log_set_level(), log_set_terminal_output(), log_truncate_if_large(), log_warn, MODE_CLIENT, MODE_DISCOVERY, MODE_DISCOVERY_SERVICE, MODE_INVALID, MODE_MIRROR, MODE_SERVER, named_init(), NAMED_REGISTER_THREAD, named_registry_register_packet_types(), options_cleanup_schema(), options_colorscheme_init_early(), options_get(), options_init(), options_state_destroy(), options_state::palette_custom, options_state::palette_custom_set, path_cleanup_thread_locals(), platform_get_monotonic_time_us(), platform_open(), platform_signal(), platform_sleep_ms(), REGISTER_GLOBAL_ATOMICS, SAFE_STRCPY, SAFE_STRNCPY, SET_ERRNO, setup_signal_handlers(), should_exit(), app_callbacks_t::should_exit, shutdown_register_callback(), signal_exit(), options_state::status_screen, terminal_cursor_show(), terminal_is_interactive(), terminal_should_force_stderr(), update_banner_start_check(), and options_state::version.

◆ set_interrupt_callback()

void set_interrupt_callback ( void(*)(void)  cb)

Register a mode-specific interrupt callback

Called by mode-specific code to register a callback that will be invoked when signal_exit() is called. Used to shut down network sockets so threads blocked in recv() unblock quickly instead of waiting for timeouts.

The callback is called synchronously from signal_exit(), so it must be async-signal-safe (only atomics, socket_shutdown(), no malloc/etc).

Only one callback can be registered at a time. Setting a new callback replaces the previous one.

Parameters
cbFunction to call on exit signal, or NULL to unregister

Definition at line 227 of file main.c.

227 {
228 g_interrupt_callback = cb;
229}

◆ setup_signal_handlers()

void setup_signal_handlers ( void  )

Set up global signal handlers Called once at startup before mode dispatch

Definition at line 346 of file main.c.

346 {
347 platform_set_console_ctrl_handler(console_ctrl_handler);
348
349#ifndef _WIN32
350 platform_signal_handler_t handlers[] = {
351 {SIGINT, handle_sigterm},
352 {SIGTERM, handle_sigterm},
353 {SIGPIPE, SIG_IGN},
354 };
356 log_debug("Signal handlers registered");
357#endif
358}
asciichat_error_t platform_register_signal_handlers(const platform_signal_handler_t *handlers, int count)
Register multiple signal handlers at once.
Signal handler descriptor for bulk registration.
Definition system.h:295
bool platform_set_console_ctrl_handler(console_ctrl_handler_t handler)
Register a console control handler (for Ctrl+C, etc.)

References log_debug, platform_register_signal_handlers(), and platform_set_console_ctrl_handler().

Referenced by main().

◆ should_exit()

bool should_exit ( void  )

Check if the application should exit

Called by mode main loops to detect shutdown requests from signals or errors.

Returns
true if shutdown has been requested, false otherwise

Definition at line 211 of file main.c.

211 {
213}
bool atomic_load_bool(atomic_t *a)
Atomically load a boolean value.
Definition atomic.c:169

References atomic_load_bool(), and g_should_exit.

Referenced by main().

◆ signal_exit()

void signal_exit ( void  )

Signal that the application should exit

Sets the global exit flag and calls the registered interrupt callback if set. Safe to call from signal handlers (uses only atomics and function pointers). Called by signal handlers (SIGTERM, Ctrl+C) and normal code (errors, timeouts).

Definition at line 215 of file main.c.

215 {
216 // Note: This function may be called from a signal handler context where
217 // other threads may hold mutex locks. We avoid log_console() here to prevent
218 // deadlock (render loop holds terminal lock, signal handler can't acquire it).
219 // The shutdown will be logged by normal thread context later.
221 void (*cb)(void) = g_interrupt_callback;
222 if (cb) {
223 cb();
224 }
225}
void atomic_store_bool(atomic_t *a, bool value)
Atomically store a boolean value.
Definition atomic.c:177

References atomic_store_bool(), and g_should_exit.

Referenced by main().