notashelf /
edae535674ff44a39642d96e343ed421c314fbb1
chroma
publicLightweight wallpaper daemon for Wayland
clone
ssh://git@git.notashelf.dev:33/notashelf/chroma.gitCommit edae535674ff
tarballunverified4 files changed+108-40
@@ -4,7 +4,7 @@ #include "wlr-layer-shell-unstable-v1.h" #include "xdg-shell.h" #include <EGL/egl.h>-#include <GL/gl.h>+#include <GLES2/gl2.h> #include <signal.h> #include <stdbool.h> #include <stdint.h>@@ -16,7 +16,7 @@ #define MAX_OUTPUTS 16 #define MAX_PATH_LEN 4096-#define CONFIG_FILE_NAME "chroma.conf"+#define CONFIG_FILE_NAME "chroma.toml" // Log levels #define CHROMA_LOG_ERROR 0@@ -73,6 +73,7 @@ typedef struct { int channels; char path[MAX_PATH_LEN]; bool loaded;+ int ref_count; // Number of outputs using this image } chroma_image_t; // Wayland output information@@ -116,6 +117,7 @@ typedef struct { bool gl_resources_initialized; bool texture_uploaded; bool vbo_dirty; // track VBO needs update+ bool configured; // track if initial configure received } chroma_output_t; // Config mapping structure@@ -162,6 +164,9 @@ typedef struct chroma_state { EGLDisplay egl_display; EGLContext egl_context; EGLConfig egl_config;+ + // Shared OpenGL resources+ GLuint shader_program; // Outputs chroma_output_t outputs[MAX_OUTPUTS];@@ -235,12 +240,15 @@ void chroma_layer_surface_closed(void *data, // Image loading void chroma_image_init_stb(void); int chroma_image_load(chroma_image_t *image, const char *path,- const chroma_config_t *config);+ const chroma_config_t *config, int output_width,+ int output_height); void chroma_image_free(chroma_image_t *image);+void chroma_image_release(chroma_image_t *image); chroma_image_t *chroma_image_find_by_path(chroma_state_t *state, const char *path); chroma_image_t *chroma_image_get_or_load(chroma_state_t *state,- const char *path);+ const char *path, int output_width,+ int output_height); int chroma_image_validate(const char *path); int chroma_image_get_info(const char *path, int *width, int *height, int *channels);@@ -248,6 +256,7 @@ void chroma_images_cleanup(chroma_state_t *state); // Configuration int chroma_config_load(chroma_config_t *config, const char *config_file);+int chroma_config_load_toml(chroma_config_t *config, const char *config_file); void chroma_config_free(chroma_config_t *config); const char *chroma_config_get_image_for_output(chroma_config_t *config, const char *output_name,@@ -85,8 +85,19 @@ static int assign_wallpaper_to_output(chroma_state_t *state, return CHROMA_ERROR_CONFIG; } - // Load or get cached image- chroma_image_t *image = chroma_image_get_or_load(state, image_path);+ // Check if image path is empty (no default configured)+ if (strlen(image_path) == 0) {+ chroma_log("WARN",+ "No wallpaper image configured for output %u (%s). "+ "Set default_image in config or provide -c config.toml",+ output->id, output->name ? output->name : "unknown");+ return CHROMA_ERROR_CONFIG;+ }++ // Load or get cached image with output dimensions for intelligent+ // downsampling+ chroma_image_t *image = chroma_image_get_or_load(+ state, image_path, output->width, output->height); if (!image) { chroma_log("ERROR", "Failed to load image for output %u: %s", output->id, image_path);@@ -96,6 +107,7 @@ static int assign_wallpaper_to_output(chroma_state_t *state, // Check if image changed and invalidate texture cache if neceessary bool image_changed = (output->image != image); if (image_changed && output->image) {+ chroma_image_release(output->image); chroma_output_invalidate_texture(output); output->vbo_dirty = true; // VBO needs update for new image chroma_log("DEBUG", "Image changed for output %u, invalidated texture",@@ -161,7 +173,8 @@ static int assign_wallpaper_to_output(chroma_state_t *state, // 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);+ chroma_log("ERROR", "Failed to render wallpaper for output %u: %s", output->id,+ chroma_error_string(ret)); return ret; } @@ -58,8 +58,9 @@ static void calculate_optimal_size(int original_width, int original_height, // being*. Must be revisited in the future to see how it stands as the program // evolves. static int downsample_image(unsigned char *src_data, int src_width,- int src_height, unsigned char *dst_data,- int dst_width, int dst_height) {+ int src_height, int src_channels,+ unsigned char *dst_data, int dst_width,+ int dst_height, int dst_channels) { if (!src_data || !dst_data) { return -1; }@@ -77,14 +78,18 @@ static int downsample_image(unsigned char *src_data, int src_width, src_x = (src_x >= src_width) ? src_width - 1 : src_x; src_y = (src_y >= src_height) ? src_height - 1 : src_y; - // Copy pixel (RGBA)- int src_idx = (src_y * src_width + src_x) * 4;- int dst_idx = (y * dst_width + x) * 4;+ // Copy pixel data+ int src_idx = (src_y * src_width + src_x) * src_channels;+ int dst_idx = (y * dst_width + x) * dst_channels; dst_data[dst_idx + 0] = src_data[src_idx + 0]; // R dst_data[dst_idx + 1] = src_data[src_idx + 1]; // G dst_data[dst_idx + 2] = src_data[src_idx + 2]; // B- dst_data[dst_idx + 3] = src_data[src_idx + 3]; // A+ if (dst_channels == 4 && src_channels == 4) {+ dst_data[dst_idx + 3] = src_data[src_idx + 3]; // A+ } else if (dst_channels == 4) {+ dst_data[dst_idx + 3] = 255; // Full alpha for RGB source+ } } } @@ -92,8 +97,11 @@ static int downsample_image(unsigned char *src_data, int src_width, } // Load image from file with configurable downsampling+// output_width/output_height: actual output dimensions for intelligent+// downsampling int chroma_image_load(chroma_image_t *image, const char *path,- const chroma_config_t *config) {+ const chroma_config_t *config, int output_width,+ int output_height) { if (!image || !path) { chroma_log("ERROR", "Invalid parameters for image loading"); return CHROMA_ERROR_INIT;@@ -117,12 +125,24 @@ int chroma_image_load(chroma_image_t *image, const char *path, (double)file_size / (1024.0 * 1024.0)); } - // Load image data using stb_image, force RGBA format to avoid conversion+ // Load image data using stb_image+ // First, check actual channels to decide if we need alpha stbi_set_flip_vertically_on_load(0); // keep images right-side up - image->data =- stbi_load(path, &image->width, &image->height, &image->channels, 4);- image->channels = 4; // always RGBA after forced conversion+ int actual_channels = 0;+ if (!stbi_info(path, &image->width, &image->height, &actual_channels)) {+ chroma_log("ERROR", "Failed to get image info for %s: %s", path,+ stbi_failure_reason());+ return CHROMA_ERROR_IMAGE;+ }++ // Load with actual channels or force RGBA if image has alpha+ // For wallpapers, we typically don't need alpha unless the image has it+ int desired_channels = (actual_channels == 4 || actual_channels == 2) ? 4 : 3;++ image->data = stbi_load(path, &image->width, &image->height, &image->channels,+ desired_channels);+ image->channels = desired_channels; if (!image->data) { chroma_log("ERROR", "Failed to load image %s: %s", path, stbi_failure_reason());@@ -137,13 +157,8 @@ int chroma_image_load(chroma_image_t *image, const char *path, return CHROMA_ERROR_IMAGE; } - // Validate we have RGBA data (should always be true with forced conversion)- if (image->channels != 4) {- chroma_log("ERROR", "Failed to load image as RGBA: got %d channels",- image->channels);- chroma_image_free(image);- return CHROMA_ERROR_IMAGE;- }+ chroma_log("DEBUG", "Loaded image %s with %d channels (original had %d)",+ path, image->channels, actual_channels); // Store original dimensions before potential downsampling int original_width = image->width;@@ -155,9 +170,14 @@ int chroma_image_load(chroma_image_t *image, const char *path, int optimal_height = original_height; if (config && config->enable_downsampling) {- calculate_optimal_size(original_width, original_height,- config->max_output_width, config->max_output_height,- &optimal_width, &optimal_height);+ // Use output dimensions if provided, otherwise fall back to config defaults+ int max_width =+ (output_width > 0) ? output_width : config->max_output_width;+ int max_height =+ (output_height > 0) ? output_height : config->max_output_height;++ calculate_optimal_size(original_width, original_height, max_width,+ max_height, &optimal_width, &optimal_height); // Apply minimum scale factor constraint float scale_x = (float)optimal_width / (float)original_width;@@ -178,7 +198,7 @@ int chroma_image_load(chroma_image_t *image, const char *path, (optimal_width < original_width || optimal_height < original_height); } - // Downsamp if needed and enabled+ // Downsample if needed and enabled if (should_downsample) { double reduction_ratio = (double)(optimal_width * optimal_height) / (double)(original_width * original_height) * 100.0;@@ -187,7 +207,8 @@ int chroma_image_load(chroma_image_t *image, const char *path, original_width, original_height, optimal_width, optimal_height, reduction_ratio); - size_t optimal_size = (size_t)optimal_width * (size_t)optimal_height * 4;+ size_t optimal_size =+ (size_t)optimal_width * (size_t)optimal_height * image->channels; unsigned char *downsampled_data = malloc(optimal_size); if (!downsampled_data) { chroma_log("ERROR", "Failed to allocate memory for downsampled image");@@ -196,8 +217,8 @@ int chroma_image_load(chroma_image_t *image, const char *path, } if (downsample_image(image->data, original_width, original_height,- downsampled_data, optimal_width,- optimal_height) != 0) {+ image->channels, downsampled_data, optimal_width,+ optimal_height, image->channels) != 0) { chroma_log("ERROR", "Failed to downsample image"); free(downsampled_data); chroma_image_free(image);@@ -218,6 +239,7 @@ int chroma_image_load(chroma_image_t *image, const char *path, } image->loaded = true;+ image->ref_count = 1; // Initial reference from the first output // Calculate and log memory allocation size_t image_size =@@ -258,6 +280,7 @@ void chroma_image_free(chroma_image_t *image) { image->height = 0; image->channels = 0; image->loaded = false;+ image->ref_count = 0; if (strlen(image->path) > 0) { chroma_log("DEBUG", "Freed image: %s", image->path);@@ -266,6 +289,18 @@ void chroma_image_free(chroma_image_t *image) { memset(image->path, 0, sizeof(image->path)); } +// Release a reference to an image; free when ref_count reaches zero+void chroma_image_release(chroma_image_t *image) {+ if (!image) {+ return;+ }++ image->ref_count--;+ if (image->ref_count <= 0) {+ chroma_image_free(image);+ }+}+ // Find image by path in state chroma_image_t *chroma_image_find_by_path(chroma_state_t *state, const char *path) {@@ -283,8 +318,11 @@ chroma_image_t *chroma_image_find_by_path(chroma_state_t *state, } // Load image if not already loaded+// output_width/output_height: actual output dimensions for intelligent+// downsampling chroma_image_t *chroma_image_get_or_load(chroma_state_t *state,- const char *path) {+ const char *path, int output_width,+ int output_height) { if (!state || !path) { return NULL; }@@ -292,7 +330,9 @@ chroma_image_t *chroma_image_get_or_load(chroma_state_t *state, // Check if already loaded chroma_image_t *existing = chroma_image_find_by_path(state, path); if (existing && existing->loaded) {- chroma_log("DEBUG", "Using cached image: %s", path);+ chroma_log("DEBUG", "Using cached image: %s (ref_count: %d)", path,+ existing->ref_count);+ existing->ref_count++; return existing; } @@ -307,8 +347,9 @@ chroma_image_t *chroma_image_get_or_load(chroma_state_t *state, state->image_count++; } - // Load the image with configuration- if (chroma_image_load(image, path, &state->config) != CHROMA_OK) {+ // Load the image with configuration and output dimensions+ if (chroma_image_load(image, path, &state->config, output_width,+ output_height) != CHROMA_OK) { // If this was a new slot, decrement count if (!existing) { state->image_count--;@@ -376,7 +417,7 @@ void chroma_images_cleanup(chroma_state_t *state) { chroma_log("DEBUG", "Cleaning up %d images", state->image_count); for (int i = 0; i < state->image_count; i++) {- chroma_image_free(&state->images[i]);+ chroma_image_release(&state->images[i]); } state->image_count = 0;@@ -91,9 +91,8 @@ static void layer_surface_configure(void *data, chroma_log("TRACE", "Sent configure acknowledgment for output %u serial %u", output->id, serial); - // Commit the surface to apply the acknowledgment- wl_surface_commit(output->surface);- chroma_log("TRACE", "Surface committed for output %u", output->id);+ // Mark as configured - actual commit happens in render via eglSwapBuffers+ output->configured = true; chroma_log("DEBUG", "Acknowledged layer surface configure for output %u", output->id);@@ -382,6 +381,12 @@ void chroma_output_remove(chroma_state_t *state, uint32_t id) { chroma_log("INFO", "Removing output %u (%s)", id, output->name ? output->name : "unknown"); + // Release image reference if the output holds one+ if (output->image) {+ chroma_image_release(output->image);+ output->image = NULL;+ }+ // Clean up surface if it exists if (output->surface) { chroma_surface_destroy(output);