#include #include #include #include #include #include #include #include "../include/chroma.h" // Global state for signal handling volatile sig_atomic_t chroma_should_quit = 0; static void print_usage(const char *program_name) { printf("Usage: %s [OPTIONS]\n", program_name); printf("Minimal Wayland Multi-Monitor Wallpaper Daemon\n\n"); printf("Options:\n"); printf(" -c, --config FILE Configuration file path\n"); printf(" -d, --daemon Run as daemon\n"); printf(" -v, --verbose Increase verbosity (can be used multiple " "times)\n"); printf(" -v: INFO, -vv: DEBUG, -vvv: TRACE\n"); printf(" -h, --help Show this help\n"); printf(" --version Show version information\n"); printf("\nExamples:\n"); printf(" %s -c ~/.config/chroma/chroma.conf\n", program_name); printf(" %s --daemon\n", program_name); } static void print_version(void) { printf("chroma %s\n", CHROMA_VERSION); printf("Minimal Wayland Multi-Monitor Wallpaper Daemon\n"); } static void signal_handler(int sig) { switch (sig) { case SIGTERM: case SIGINT: chroma_should_quit = 1; break; case SIGHUP: // TODO: Implement config reload break; } } static int setup_signals(void) { struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = signal_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; if (sigaction(SIGTERM, &sa, NULL) == -1) { perror("sigaction(SIGTERM)"); return -1; } if (sigaction(SIGINT, &sa, NULL) == -1) { perror("sigaction(SIGINT)"); return -1; } if (sigaction(SIGHUP, &sa, NULL) == -1) { perror("sigaction(SIGHUP)"); return -1; } return 0; } static int daemonize(void) { pid_t pid = fork(); if (pid < 0) { perror("fork"); return -1; } if (pid > 0) { // Parent process exits exit(0); } // Child process continues if (setsid() < 0) { perror("setsid"); return -1; } // Change working directory to root if (chdir("/") < 0) { perror("chdir"); return -1; } // Close standard file descriptors close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); return 0; } static char *get_default_config_path(void) { static char config_path[MAX_PATH_LEN]; const char *home = getenv("HOME"); const char *xdg_config = getenv("XDG_CONFIG_HOME"); if (xdg_config) { snprintf(config_path, sizeof(config_path), "%s/chroma/%s", xdg_config, CONFIG_FILE_NAME); } else if (home) { snprintf(config_path, sizeof(config_path), "%s/.config/chroma/%s", home, CONFIG_FILE_NAME); } else { strcpy(config_path, CONFIG_FILE_NAME); } return config_path; } int main(int argc, char *argv[]) { chroma_state_t state; char *config_file = NULL; bool daemon_mode = false; int verbose_level = 0; int opt; int ret = 0; static struct option long_options[] = { {"config", required_argument, 0, 'c'}, {"daemon", no_argument, 0, 'd'}, {"verbose", no_argument, 0, 'v'}, {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'V'}, {0, 0, 0, 0}}; // Parse command line arguments while ((opt = getopt_long(argc, argv, "c:dvhV", long_options, NULL)) != -1) { switch (opt) { case 'c': config_file = optarg; break; case 'd': daemon_mode = true; break; case 'v': verbose_level++; break; case 'h': print_usage(argv[0]); return 0; case 'V': print_version(); return 0; default: print_usage(argv[0]); return 1; } } // Initialize state memset(&state, 0, sizeof(state)); state.config.daemon_mode = daemon_mode; // Set log level based on verbosity count // 0: ERROR+WARN only, 1: +INFO, 2: +DEBUG, 3+: +TRACE chroma_set_log_level(verbose_level); // Set up signal handlers if (setup_signals() != 0) { fprintf(stderr, "Failed to set up signal handlers\n"); return 1; } // Load configuration if (!config_file) { config_file = get_default_config_path(); } // Daemonize if requested if (daemon_mode) { chroma_log("INFO", "Starting daemon mode"); if (daemonize() != 0) { fprintf(stderr, "Failed to daemonize\n"); return 1; } } // Initialize chroma chroma_log("INFO", "Initializing chroma wallpaper daemon v%s", CHROMA_VERSION); chroma_log_memory_stats("startup"); ret = chroma_init(&state); if (ret != CHROMA_OK) { chroma_log("ERROR", "Failed to initialize chroma: %s", chroma_error_string(ret)); chroma_cleanup(&state); return 1; } chroma_log_memory_stats("post-init"); // Load configuration chroma_log("INFO", "Loading configuration from: %s", config_file); if (chroma_config_load(&state.config, config_file) != CHROMA_OK) { chroma_log("WARN", "Failed to load config file, using defaults"); // Continue with default configuration } chroma_log_memory_stats("post-config-load"); // Connect to Wayland ret = chroma_wayland_connect(&state); if (ret != CHROMA_OK) { chroma_log("ERROR", "Failed to connect to Wayland: %s", chroma_error_string(ret)); chroma_cleanup(&state); return 1; } chroma_log_memory_stats("post-wayland-connect"); // Initialize EGL ret = chroma_egl_init(&state); if (ret != CHROMA_OK) { chroma_log("ERROR", "Failed to initialize EGL: %s", chroma_error_string(ret)); chroma_cleanup(&state); return 1; } chroma_log_memory_stats("post-egl-init"); chroma_log("INFO", "Chroma daemon initialized successfully"); chroma_log_memory_stats("pre-main-loop"); // Main event loop ret = chroma_run(&state); if (ret != CHROMA_OK) { chroma_log("ERROR", "Main loop failed: %s", chroma_error_string(ret)); } chroma_log_memory_stats("post-main-loop"); // Cleanup chroma_log("INFO", "Shutting down chroma daemon"); chroma_log_memory_stats("pre-cleanup"); chroma_cleanup(&state); chroma_log_memory_stats("post-cleanup"); return (ret == CHROMA_OK) ? 0 : 1; }