chroma/src/main.c
NotAShelf bc77b887ad
various: log memory events
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I6a6a69643b6d00277bb9bcfeb4cd01dc78d7cd3d
2025-10-02 21:52:08 +03:00

248 lines
6 KiB
C

#include <getopt.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#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;
}