#include #include #include #include #include #include #include #include #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; }