chroma/src/core.c
NotAShelf fcc080871a
initial commit
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I6a6a6964ee9e6ebe436ca8328c6e4a7ec7c9d8d4
2025-09-30 20:11:40 +03:00

439 lines
11 KiB
C

#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
#include "../include/chroma.h"
// Global logging level
static int log_level = 0; // 0=ERROR, 1=WARN, 2=INFO, 3=DEBUG
// Initialize chroma state
int chroma_init(chroma_state_t *state) {
if (!state) {
return CHROMA_ERROR_INIT;
}
// Initialize all fields to zero
memset(state, 0, sizeof(chroma_state_t));
// Set initial state
state->running = false;
state->initialized = false;
state->egl_display = EGL_NO_DISPLAY;
state->egl_context = EGL_NO_CONTEXT;
// Initialize stb_image
chroma_image_init_stb();
state->initialized = true;
chroma_log("INFO", "Chroma state initialized");
return CHROMA_OK;
}
// Cleanup chroma state
void chroma_cleanup(chroma_state_t *state) {
if (!state || !state->initialized) {
return;
}
chroma_log("INFO", "Cleaning up chroma state");
// Stop the main loop
state->running = false;
// Clean up all images
chroma_images_cleanup(state);
// Clean up EGL
chroma_egl_cleanup(state);
// Clean up Wayland
chroma_wayland_disconnect(state);
// Clean up configuration
chroma_config_free(&state->config);
state->initialized = false;
chroma_log("INFO", "Chroma cleanup complete");
}
// Assign wallpaper to an output
static int assign_wallpaper_to_output(chroma_state_t *state,
chroma_output_t *output) {
if (!state || !output || !output->active) {
return CHROMA_ERROR_INIT;
}
// Get image path for this output
const char *image_path = chroma_config_get_image_for_output(
&state->config, output->name ? output->name : "unknown");
if (!image_path) {
chroma_log("WARN", "No wallpaper configured for output %u (%s)", output->id,
output->name ? output->name : "unknown");
return CHROMA_ERROR_CONFIG;
}
// Load or get cached image
chroma_image_t *image = chroma_image_get_or_load(state, image_path);
if (!image) {
chroma_log("ERROR", "Failed to load image for output %u: %s", output->id,
image_path);
return CHROMA_ERROR_IMAGE;
}
// Assign image to output
output->image = image;
// Create surface if it doesn't exist
if (!output->surface) {
int ret = chroma_surface_create(state, output);
if (ret != CHROMA_OK) {
chroma_log("ERROR", "Failed to create surface for output %u", output->id);
return ret;
}
}
// Render wallpaper
int ret = chroma_render_wallpaper(state, output);
if (ret != CHROMA_OK) {
chroma_log("ERROR", "Failed to render wallpaper for output %u", output->id);
return ret;
}
chroma_log("INFO", "Assigned wallpaper to output %u (%s): %s", output->id,
output->name ? output->name : "unknown", image_path);
return CHROMA_OK;
}
// Assign wallpapers to all active outputs
static int assign_wallpapers_to_all_outputs(chroma_state_t *state) {
if (!state) {
return CHROMA_ERROR_INIT;
}
int success_count = 0;
int error_count = 0;
for (int i = 0; i < state->output_count; i++) {
chroma_output_t *output = &state->outputs[i];
if (!output->active) {
chroma_log("DEBUG", "Skipping inactive output %u", output->id);
continue;
}
if (assign_wallpaper_to_output(state, output) == CHROMA_OK) {
success_count++;
} else {
error_count++;
}
}
chroma_log("INFO", "Wallpaper assignment complete: %d success, %d errors",
success_count, error_count);
return (success_count > 0) ? CHROMA_OK : CHROMA_ERROR_IMAGE;
}
// Handle output configuration complete event
void handle_output_done(chroma_state_t *state, chroma_output_t *output) {
if (!state || !output) {
return;
}
chroma_log("INFO",
"Output %u (%s) configuration complete: %dx%d@%d, scale=%d",
output->id, output->name ? output->name : "unknown", output->width,
output->height, 0, output->scale);
/* Assign wallpaper to this output */
if (assign_wallpaper_to_output(state, output) != CHROMA_OK) {
chroma_log("ERROR", "Failed to assign wallpaper to output %u", output->id);
}
}
// Process Wayland events
static int process_wayland_events(chroma_state_t *state) {
if (!state || !state->display) {
return CHROMA_ERROR_WAYLAND;
}
/* Dispatch pending events */
if (wl_display_dispatch_pending(state->display) == -1) {
chroma_log("ERROR", "Failed to dispatch pending Wayland events: %s",
strerror(errno));
return CHROMA_ERROR_WAYLAND;
}
/* Read events from the server */
if (wl_display_read_events(state->display) == -1) {
chroma_log("ERROR", "Failed to read Wayland events: %s", strerror(errno));
return CHROMA_ERROR_WAYLAND;
}
/* Dispatch the read events */
if (wl_display_dispatch_pending(state->display) == -1) {
chroma_log("ERROR", "Failed to dispatch read Wayland events: %s",
strerror(errno));
return CHROMA_ERROR_WAYLAND;
}
return CHROMA_OK;
}
// Main event loop
int chroma_run(chroma_state_t *state) {
if (!state || !state->initialized) {
return CHROMA_ERROR_INIT;
}
chroma_log("INFO", "Starting main event loop");
state->running = true;
// Initial wallpaper assignment
chroma_log("INFO", "Performing initial wallpaper assignment");
assign_wallpapers_to_all_outputs(state);
// Main event loop
while (state->running && !chroma_should_quit) {
// Dispatch any pending events first
if (wl_display_dispatch_pending(state->display) == -1) {
chroma_log("ERROR", "Failed to dispatch pending events: %s",
strerror(errno));
break;
}
// Prepare to read events
if (wl_display_prepare_read(state->display) == -1) {
chroma_log("ERROR", "Failed to prepare Wayland display for reading");
break;
}
// Flush outgoing requests
if (wl_display_flush(state->display) == -1) {
chroma_log("ERROR", "Failed to flush Wayland display: %s",
strerror(errno));
wl_display_cancel_read(state->display);
break;
}
// Get the display file descriptor
int fd = wl_display_get_fd(state->display);
if (fd == -1) {
chroma_log("ERROR", "Failed to get Wayland display file descriptor");
wl_display_cancel_read(state->display);
break;
}
// Use select() to wait for events with timeout
fd_set readfds;
struct timeval timeout;
FD_ZERO(&readfds);
FD_SET(fd, &readfds);
timeout.tv_sec = 1; // 1 second timeout
timeout.tv_usec = 0;
int select_result = select(fd + 1, &readfds, NULL, NULL, &timeout);
if (select_result == -1) {
if (errno == EINTR) {
// Interrupted by signal, check if we should quit
wl_display_cancel_read(state->display);
continue;
}
chroma_log("ERROR", "select() failed: %s", strerror(errno));
wl_display_cancel_read(state->display);
break;
}
if (select_result == 0) {
// Timeout - no events available
wl_display_cancel_read(state->display);
continue;
}
// Events are available
if (FD_ISSET(fd, &readfds)) {
if (process_wayland_events(state) != CHROMA_OK) {
break;
}
} else {
wl_display_cancel_read(state->display);
}
}
state->running = false;
chroma_log("INFO", "Main event loop ended");
return CHROMA_OK;
}
// Error code to string conversion
const char *chroma_error_string(chroma_error_t error) {
switch (error) {
case CHROMA_OK:
return "Success";
case CHROMA_ERROR_INIT:
return "Initialization error";
case CHROMA_ERROR_WAYLAND:
return "Wayland error";
case CHROMA_ERROR_EGL:
return "EGL error";
case CHROMA_ERROR_IMAGE:
return "Image loading error";
case CHROMA_ERROR_CONFIG:
return "Configuration error";
case CHROMA_ERROR_MEMORY:
return "Memory allocation error";
default:
return "Unknown error";
}
}
// Logging function
void chroma_log(const char *level, const char *format, ...) {
va_list args;
char timestamp[32];
struct timeval tv;
struct tm *tm_info;
// Get current time
gettimeofday(&tv, NULL);
tm_info = localtime(&tv.tv_sec);
// Format timestamp
snprintf(timestamp, sizeof(timestamp), "%04d-%02d-%02d %02d:%02d:%02d.%03d",
tm_info->tm_year + 1900, tm_info->tm_mon + 1, tm_info->tm_mday,
tm_info->tm_hour, tm_info->tm_min, tm_info->tm_sec,
(int)(tv.tv_usec / 1000));
// Print log message
printf("[%s] %s: ", timestamp, level);
va_start(args, format);
vprintf(format, args);
va_end(args);
printf("\n");
fflush(stdout);
}
// Set log level
void chroma_set_log_level(int level) { log_level = level; }
// Get log level
int chroma_get_log_level(void) { return log_level; }
// Handle configuration reload (SIGHUP)
int chroma_reload_config(chroma_state_t *state, const char *config_file) {
if (!state) {
return CHROMA_ERROR_INIT;
}
chroma_log("INFO", "Reloading configuration");
// Free current configuration
chroma_config_free(&state->config);
// Load new configuration
int ret = chroma_config_load(&state->config, config_file);
if (ret != CHROMA_OK) {
chroma_log("ERROR", "Failed to reload configuration: %s",
chroma_error_string(ret));
return ret;
}
// Reassign wallpapers with new configuration
ret = assign_wallpapers_to_all_outputs(state);
if (ret != CHROMA_OK) {
chroma_log("ERROR", "Failed to reassign wallpapers after config reload");
return ret;
}
chroma_log("INFO", "Configuration reloaded successfully");
return CHROMA_OK;
}
// Check if an output needs wallpaper update
static bool output_needs_update(chroma_output_t *output) {
if (!output || !output->active) {
return false;
}
// Check if output has no surface or image assigned
if (!output->surface || !output->image) {
return true;
}
// Check if image is no longer loaded
if (!output->image->loaded) {
return true;
}
return false;
}
// Update outputs that need wallpaper refresh
int chroma_update_outputs(chroma_state_t *state) {
if (!state) {
return CHROMA_ERROR_INIT;
}
int updated_count = 0;
for (int i = 0; i < state->output_count; i++) {
chroma_output_t *output = &state->outputs[i];
if (output_needs_update(output)) {
if (assign_wallpaper_to_output(state, output) == CHROMA_OK) {
updated_count++;
}
}
}
if (updated_count > 0) {
chroma_log("INFO", "Updated wallpapers for %d outputs", updated_count);
}
return CHROMA_OK;
}
// Get statistics
void chroma_get_stats(chroma_state_t *state, int *active_outputs,
int *loaded_images) {
if (!state) {
if (active_outputs)
*active_outputs = 0;
if (loaded_images)
*loaded_images = 0;
return;
}
int active = 0;
for (int i = 0; i < state->output_count; i++) {
if (state->outputs[i].active) {
active++;
}
}
int loaded = 0;
for (int i = 0; i < state->image_count; i++) {
if (state->images[i].loaded) {
loaded++;
}
}
if (active_outputs)
*active_outputs = active;
if (loaded_images)
*loaded_images = loaded;
}