Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I6a6a6964ee9e6ebe436ca8328c6e4a7ec7c9d8d4
439 lines
11 KiB
C
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;
|
|
}
|