notashelf /
5ba4407367ee95b5861d396a7e175731fd6b47bc
chroma
publicLightweight wallpaper daemon for Wayland
clone
ssh://git@git.notashelf.dev:33/notashelf/chroma.gitCommit 5ba4407367ee
tarballunverified6 files changed+323-739
@@ -34,7 +34,7 @@ DEBUG_CFLAGS += -D_GNU_SOURCE -DCHROMA_VERSION=\"$(VERSION)-debug\" DEBUG_CFLAGS += -fsanitize=address -fsanitize=undefined -fno-omit-frame-pointer # Libraries using pkg-config-PKG_DEPS = wayland-client wayland-egl egl glesv2 wayland-protocols+PKG_DEPS = wayland-client wayland-protocols CFLAGS += $(shell pkg-config --cflags $(PKG_DEPS)) LDFLAGS += $(shell pkg-config --libs $(PKG_DEPS)) @@ -117,7 +117,7 @@ static: $(TARGET) # Check if required dependencies are available check-deps: @echo "Checking dependencies..."- @pkg-config --exists $(PKG_DEPS) || (echo "Missing dependencies. Install: wayland-protocols libwayland-dev libegl1-mesa-dev libgl1-mesa-dev wayland-scanner" && exit 1)+ @pkg-config --exists $(PKG_DEPS) || (echo "Missing dependencies. Install: wayland-protocols libwayland-dev wayland-scanner" && exit 1) @which wayland-scanner >/dev/null || (echo "wayland-scanner not found. Please install wayland-scanner." && exit 1) @echo "All dependencies found." @@ -3,14 +3,11 @@ #include "wlr-layer-shell-unstable-v1.h" #include "xdg-shell.h"-#include <EGL/egl.h>-#include <GLES2/gl2.h> #include <signal.h> #include <stdbool.h> #include <stdint.h> #include <sys/types.h> #include <wayland-client.h>-#include <wayland-egl.h> #include "chroma_version.h" @@ -73,6 +70,7 @@ typedef struct { int channels; char path[MAX_PATH_LEN]; bool loaded;+ bool data_from_stbi; // true if data was allocated by stbi_load int ref_count; // Number of outputs using this image } chroma_image_t; @@ -94,10 +92,14 @@ typedef struct { // Rendering context struct wl_surface *surface; struct zwlr_layer_surface_v1 *layer_surface;- struct wl_egl_window *egl_window;- EGLSurface egl_surface; uint32_t configure_serial; + // Shared memory buffer+ struct wl_buffer *shm_buffer;+ void *shm_data;+ size_t shm_size;+ int shm_stride;+ // Associated wallpaper chroma_image_t *image; @@ -108,15 +110,6 @@ typedef struct { float anchor_x; // custom X offset (0-100, 50=center) float anchor_y; // custom Y offset (0-100, 50=center) bool config_loaded;-- // OpenGL resource cache- GLuint texture_id;- GLuint shader_program;- GLuint vbo;- GLuint ebo;- bool gl_resources_initialized;- bool texture_uploaded;- bool vbo_dirty; // track VBO needs update bool configured; // track if initial configure received } chroma_output_t; @@ -159,14 +152,7 @@ typedef struct chroma_state { struct wl_registry *registry; struct wl_compositor *compositor; struct zwlr_layer_shell_v1 *layer_shell;-- // EGL context- EGLDisplay egl_display;- EGLContext egl_context;- EGLConfig egl_config;- - // Shared OpenGL resources- GLuint shader_program;+ struct wl_shm *shm; // Outputs chroma_output_t outputs[MAX_OUTPUTS];@@ -221,13 +207,12 @@ void chroma_output_description(void *data, struct wl_output *output, const char *description); void chroma_output_done(void *data, struct wl_output *output); -// EGL and rendering-int chroma_egl_init(chroma_state_t *state);-void chroma_egl_cleanup(chroma_state_t *state);+// Rendering and surface management int chroma_surface_create(chroma_state_t *state, chroma_output_t *output); void chroma_surface_destroy(chroma_output_t *output);+int chroma_buffer_create(chroma_state_t *state, chroma_output_t *output);+void chroma_buffer_destroy(chroma_output_t *output); int chroma_render_wallpaper(chroma_state_t *state, chroma_output_t *output);-void chroma_output_invalidate_texture(chroma_output_t *output); // Layer shell functions void chroma_layer_surface_configure(void *data,@@ -24,8 +24,6 @@ int chroma_init(chroma_state_t *state) { // 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();@@ -53,9 +51,6 @@ void chroma_cleanup(chroma_state_t *state) { // Clean up all images chroma_images_cleanup(state); - // Clean up EGL- chroma_egl_cleanup(state);- // Clean up Wayland chroma_wayland_disconnect(state); @@ -104,24 +99,16 @@ static int assign_wallpaper_to_output(chroma_state_t *state, return CHROMA_ERROR_IMAGE; } - // Check if image changed and invalidate texture cache if neceessary+ // Check if image changed 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",- output->id);+ chroma_log("DEBUG", "Image changed for output %u", output->id); } // Assign image to output output->image = image; - // Mark VBO as dirty if image changed- if (image_changed) {- output->vbo_dirty = true;- }- // Store old configuration values for comparison chroma_scale_mode_t old_scale_mode = output->scale_mode; chroma_filter_quality_t old_filter_quality = output->filter_quality;@@ -144,16 +131,14 @@ static int assign_wallpaper_to_output(chroma_state_t *state, output->anchor, (double)output->anchor_x, (double)output->anchor_y); - // Check if configuration changed and invalidate texture if needed+ // Check if configuration changed if (had_config && (old_scale_mode != output->scale_mode || old_filter_quality != output->filter_quality || old_anchor != output->anchor || old_anchor_x != output->anchor_x || old_anchor_y != output->anchor_y)) {- chroma_output_invalidate_texture(output);- output->vbo_dirty = true; // VBO needs update for new scale mode chroma_log("DEBUG",- "Configuration changed for output %u, invalidated texture",+ "Configuration changed for output %u", output->id); } } else {@@ -178,16 +178,6 @@ int main(int argc, char *argv[]) { } 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"); @@ -1,588 +1,309 @@+#include <errno.h>+#include <fcntl.h>+#include <math.h>+#include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h>--#include <EGL/egl.h>-#include <GLES2/gl2.h>+#include <sys/mman.h>+#include <sys/stat.h>+#include <unistd.h> #include "../include/chroma.h" #include "../include/vendor/stb_image.h" -// Convert filter quality enum to OpenGL parameters-static void get_gl_filter_params(chroma_filter_quality_t quality,- GLint *min_filter, GLint *mag_filter,- bool *needs_mipmaps) {- switch (quality) {- case CHROMA_FILTER_NEAREST:- *min_filter = GL_NEAREST;- *mag_filter = GL_NEAREST;- *needs_mipmaps = false;- break;- case CHROMA_FILTER_LINEAR:- *min_filter = GL_LINEAR;- *mag_filter = GL_LINEAR;- *needs_mipmaps = false;- break;- case CHROMA_FILTER_BILINEAR:- *min_filter = GL_LINEAR;- *mag_filter = GL_LINEAR;- *needs_mipmaps = false;- break;- case CHROMA_FILTER_TRILINEAR:- *min_filter = GL_LINEAR_MIPMAP_LINEAR;- *mag_filter = GL_LINEAR;- *needs_mipmaps = true;- break;- default:- *min_filter = GL_LINEAR;- *mag_filter = GL_LINEAR;- *needs_mipmaps = false;- break;- }-}--// Calculate texture coordinates based on scaling mode and anchor position-static void calculate_texture_coords(chroma_scale_mode_t scale_mode,- float anchor_x, float anchor_y,- int image_width, int image_height,- int output_width, int output_height,- float tex_coords[8]) {- // Default texture coordinates (full texture)- float u1 = 0.0f, v1 = 0.0f; // top-left- float u2 = 1.0f, v2 = 1.0f; // bottom-right-- switch (scale_mode) {- case CHROMA_SCALE_STRETCH:- // Use full texture, stretch to fit- u1 = 0.0f;- v1 = 0.0f;- u2 = 1.0f;- v2 = 1.0f;- break;-- case CHROMA_SCALE_CENTER:- // Center image at original size- // Calculate how much of the texture to show- {- float image_aspect = (float)image_width / (float)image_height;- float output_aspect = (float)output_width / (float)output_height;-- if (image_aspect > output_aspect) {- // Image is wider - fit width, show center portion vertically- float visible_height = (float)image_width / output_aspect;- float v_offset = ((float)image_height - visible_height) /- (2.0f * (float)image_height);- u1 = 0.0f;- v1 = v_offset;- u2 = 1.0f;- v2 = 1.0f - v_offset;- } else {- // Image is taller - fit height, show center portion horizontally- float visible_width = (float)image_height * output_aspect;- float u_offset =- ((float)image_width - visible_width) / (2.0f * (float)image_width);- u1 = u_offset;- v1 = 0.0f;- u2 = 1.0f - u_offset;- v2 = 1.0f;- }- }- break;-- case CHROMA_SCALE_FIT:- // Fit image within output, maintaining aspect ratio- {- float image_aspect = (float)image_width / (float)image_height;- float output_aspect = (float)output_width / (float)output_height;-- if (image_aspect > output_aspect) {- // Image is wider - fit width, add borders top/bottom- float scaled_height = (float)output_width / image_aspect;- float v_border = ((float)output_height - scaled_height) /- (2.0f * (float)output_height);- u1 = 0.0f;- v1 = v_border;- u2 = 1.0f;- v2 = 1.0f - v_border;- } else {- // Image is taller - fit height, add borders left/right- float scaled_width = (float)output_height * image_aspect;- float u_border =- ((float)output_width - scaled_width) / (2.0f * (float)output_width);- u1 = u_border;- v1 = 0.0f;- u2 = 1.0f - u_border;- v2 = 1.0f;- }- }- break;-- case CHROMA_SCALE_FILL:- default:- // Fill entire output, crop if necessary- {- float image_aspect = (float)image_width / (float)image_height;- float output_aspect = (float)output_width / (float)output_height;-- if (image_aspect > output_aspect) {- // Image is wider - crop left/right- float crop_width = (float)image_height * output_aspect;- float u_crop =- ((float)image_width - crop_width) / (2.0f * (float)image_width);- u1 = u_crop;- v1 = 0.0f;- u2 = 1.0f - u_crop;- v2 = 1.0f;- } else {- // Image is taller - crop top/bottom- float crop_height = (float)image_width / output_aspect;- float v_crop =- ((float)image_height - crop_height) / (2.0f * (float)image_height);- u1 = 0.0f;- v1 = v_crop;- u2 = 1.0f;- v2 = 1.0f - v_crop;- }- }- break;- }-- // Apply anchor-based offset using anchor_x and anchor_y (0-100, 50=center)- // anchor_x: 0=left edge, 50=center, 100=right edge- // anchor_y: 0=top edge, 50=center, 100=bottom edge- // u_shift: negative moves view left (shows more right side of image)- // v_shift: negative moves view up (shows more top of image)- float u_shift = (anchor_x - 50.0f) / 50.0f; // -1 to 1- float v_shift = (50.0f - anchor_y) / 50.0f; // -1 to 1 (inverted for top-down)-- // Calculate the shift amount based on crop/border space available- float u_crop = 1.0f - (u2 - u1);- float v_crop = 1.0f - (v2 - v1);- u1 += u_shift * u_crop;- u2 += u_shift * u_crop;- v1 += v_shift * v_crop;- v2 += v_shift * v_crop;-- // Clamp to valid range [0, 1]- if (u1 < 0.0f)- u1 = 0.0f;- if (u2 > 1.0f)- u2 = 1.0f;- if (v1 < 0.0f)- v1 = 0.0f;- if (v2 > 1.0f)- v2 = 1.0f;-- // Set texture coordinates for quad (bottom-left, bottom-right, top-right,- // top-left)- tex_coords[0] = u1;- tex_coords[1] = v2; // bottom-left- tex_coords[2] = u2;- tex_coords[3] = v2; // bottom-right- tex_coords[4] = u2;- tex_coords[5] = v1; // top-right- tex_coords[6] = u1;- tex_coords[7] = v1; // top-left-}--// Vertex shader for simple texture rendering-static const char *vertex_shader_source =- "attribute vec2 position;\n"- "attribute vec2 texcoord;\n"- "varying vec2 v_texcoord;\n"- "void main() {\n"- " gl_Position = vec4(position, 0.0, 1.0);\n"- " v_texcoord = texcoord;\n"- "}\n";--// Fragment shader for simple texture rendering-static const char *fragment_shader_source =- "precision mediump float;\n"- "varying vec2 v_texcoord;\n"- "uniform sampler2D texture;\n"- "void main() {\n"- " gl_FragColor = texture2D(texture, v_texcoord);\n"- "}\n";--// Vertices for a fullscreen quad-static const float vertices[] = {- // Position Texcoord- -1.0f, -1.0f, 0.0f, 1.0f, // bottom left- 1.0f, -1.0f, 1.0f, 1.0f, // bottom right- 1.0f, 1.0f, 1.0f, 0.0f, // top right- -1.0f, 1.0f, 0.0f, 0.0f, // top left-};--static const unsigned short indices[] = {- 0, 1, 2, // first triangle- 2, 3, 0 // second triangle-};--static GLuint compile_shader(GLenum type, const char *source) {- GLuint shader = glCreateShader(type);- if (!shader) {- chroma_log("ERROR", "Failed to create shader");- return 0;- }-- glShaderSource(shader, 1, &source, NULL);- glCompileShader(shader);-- GLint status;- glGetShaderiv(shader, GL_COMPILE_STATUS, &status);- if (status != GL_TRUE) {- char log[512];- glGetShaderInfoLog(shader, sizeof(log), NULL, log);- chroma_log("ERROR", "Shader compilation failed: %s", log);- glDeleteShader(shader);- return 0;- }-- return shader;-}--static GLuint create_shader_program(void) {- GLuint vertex_shader = compile_shader(GL_VERTEX_SHADER, vertex_shader_source);- if (!vertex_shader) {- return 0;- }-- GLuint fragment_shader =- compile_shader(GL_FRAGMENT_SHADER, fragment_shader_source);- if (!fragment_shader) {- glDeleteShader(vertex_shader);- return 0;- }-- GLuint program = glCreateProgram();- if (!program) {- chroma_log("ERROR", "Failed to create shader program");- glDeleteShader(vertex_shader);- glDeleteShader(fragment_shader);- return 0;+// Destroy shared memory buffer for an output+void chroma_buffer_destroy(chroma_output_t *output) {+ if (!output) {+ return; } - glAttachShader(program, vertex_shader);- glAttachShader(program, fragment_shader);- glLinkProgram(program);-- GLint status;- glGetProgramiv(program, GL_LINK_STATUS, &status);- if (status != GL_TRUE) {- char log[512];- glGetProgramInfoLog(program, sizeof(log), NULL, log);- chroma_log("ERROR", "Shader program linking failed: %s", log);- glDeleteProgram(program);- program = 0;+ if (output->shm_buffer) {+ wl_buffer_destroy(output->shm_buffer);+ output->shm_buffer = NULL; } - glDeleteShader(vertex_shader);- glDeleteShader(fragment_shader);-- return program;-}--// Initialize OpenGL resources for output-static int init_gl_resources(chroma_state_t *state, chroma_output_t *output) {- if (!output || output->gl_resources_initialized) {- return CHROMA_OK;- }-- // Use shared shader program from state if available- if (state && state->shader_program != 0) {- output->shader_program = state->shader_program;- chroma_log("DEBUG", "Using shared shader program for output %u",- output->id);- } else {- // Create shader program- output->shader_program = create_shader_program();- if (!output->shader_program) {- chroma_log("ERROR", "Failed to create shader program for output %u",- output->id);- return CHROMA_ERROR_EGL;- }- // Store in state for sharing- if (state) {- state->shader_program = output->shader_program;- chroma_log("DEBUG", "Created shared shader program");+ if (output->shm_data) {+ if (munmap(output->shm_data, output->shm_size) < 0) {+ chroma_log("WARN", "munmap failed: %s", strerror(errno)); }+ output->shm_data = NULL;+ output->shm_size = 0;+ output->shm_stride = 0; }-- // Create and setup VBO/EBO- glGenBuffers(1, &output->vbo);- glGenBuffers(1, &output->ebo);-- glBindBuffer(GL_ARRAY_BUFFER, output->vbo);- glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_DYNAMIC_DRAW);-- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, output->ebo);- glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices,- GL_STATIC_DRAW);-- // Unbind buffers- glBindBuffer(GL_ARRAY_BUFFER, 0);- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);-- output->texture_id = 0; // will be created when image is assigned- output->vbo_dirty = true; // VBO needs initial update- output->gl_resources_initialized = true;-- chroma_log("DEBUG", "Initialized GL resources for output %u", output->id);- return CHROMA_OK; } -// Create or update texture from image data-static int update_texture_from_image(chroma_output_t *output,- chroma_image_t *image,- chroma_filter_quality_t filter_quality) {- if (!output || !image || !image->loaded) {+// Create shared memory buffer for an output+int chroma_buffer_create(chroma_state_t *state, chroma_output_t *output) {+ if (!state || !output || !state->shm) { return CHROMA_ERROR_INIT; } - // If image data was already freed after previous GPU upload, we can't upload- // again- if (!image->data) {- chroma_log("ERROR",- "Cannot create texture: image data already freed for %s",- image->path);- return CHROMA_ERROR_IMAGE;+ int width = output->width;+ int height = output->height;+ if (width <= 0 || height <= 0) {+ return CHROMA_ERROR_INIT; } - // Delete existing texture if it exists- if (output->texture_id != 0) {- // Estimate texture size for logging- // FIXME: Unfortunately this only works if we have previous image info.- // Could this b made more accurate?- if (output->image && output->image->loaded) {- size_t texture_size = (size_t)output->image->width *- (size_t)output->image->height *- (size_t)output->image->channels;- chroma_log_resource_deallocation("gpu_texture", texture_size,- "texture replacement");+ int stride = width * 4;+ size_t size = (size_t)stride * height;++ int fd = memfd_create("chroma-shm", MFD_CLOEXEC | MFD_ALLOW_SEALING);+ if (fd < 0) {+ // Fallback to shm_open if memfd_create is unavailable+ char name[64];+ snprintf(name, sizeof(name), "/chroma-shm-%d-%d", getpid(), rand());+ fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);+ if (fd >= 0) {+ shm_unlink(name); }- glDeleteTextures(1, &output->texture_id);- output->texture_id = 0; }-- // Create new texture- glGenTextures(1, &output->texture_id);- glBindTexture(GL_TEXTURE_2D, output->texture_id);-- // Log GPU texture allocation- size_t texture_size =- (size_t)image->width * (size_t)image->height * (size_t)image->channels;- chroma_log_resource_allocation("gpu_texture", texture_size, image->path);-- // Set texture parameters- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);-- // Use configured filter quality- GLint min_filter, mag_filter;- bool needs_mipmaps;- get_gl_filter_params(filter_quality, &min_filter, &mag_filter,- &needs_mipmaps);- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter);- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter);-- // Upload texture data with appropriate format based on channels- GLint internal_format;- GLenum format;- if (image->channels == 4) {- internal_format = GL_RGBA;- format = GL_RGBA;- } else if (image->channels == 3) {- internal_format = GL_RGB;- format = GL_RGB;- } else if (image->channels == 2) {- internal_format = GL_LUMINANCE_ALPHA;- format = GL_LUMINANCE_ALPHA;- } else {- internal_format = GL_LUMINANCE;- format = GL_LUMINANCE;+ if (fd < 0) {+ chroma_log("ERROR", "Failed to create shared memory fd: %s",+ strerror(errno));+ return CHROMA_ERROR_MEMORY; }- glTexImage2D(GL_TEXTURE_2D, 0, internal_format, image->width, image->height,- 0, format, GL_UNSIGNED_BYTE, image->data); - // Generate mipmaps for trilinear filtering if they're needed- if (needs_mipmaps) {- glGenerateMipmap(GL_TEXTURE_2D);- chroma_log("DEBUG", "Generated mipmaps for trilinear filtering");+ if (ftruncate(fd, (off_t)size) < 0) {+ chroma_log("ERROR", "ftruncate failed: %s", strerror(errno));+ close(fd);+ return CHROMA_ERROR_MEMORY; } - glBindTexture(GL_TEXTURE_2D, 0);-- // Mark this output as having uploaded its texture- output->texture_uploaded = true;-- // Decrement reference count and free system RAM when all outputs- // have uploaded their textures- image->ref_count--;- if (image->ref_count <= 0 && image->data) {- size_t freed_bytes =- (size_t)image->width * (size_t)image->height * (size_t)image->channels;- stbi_image_free(image->data);- image->data = NULL;- chroma_log("INFO", "Freed %.2f MB of image data after GPU upload: %s",- (double)freed_bytes / (1024.0 * 1024.0), image->path);- chroma_log_resource_deallocation("image_data", freed_bytes,- "post-gpu-upload");- chroma_log_memory_stats("post-gpu-upload");- } else {- chroma_log("DEBUG",- "Keeping image data for %s (ref_count: %d, waiting for %d more "- "outputs)",- image->path, image->ref_count, image->ref_count);+ void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);+ if (data == MAP_FAILED) {+ chroma_log("ERROR", "mmap failed: %s", strerror(errno));+ close(fd);+ return CHROMA_ERROR_MEMORY; } - chroma_log("DEBUG", "Updated texture for output %u (%dx%d)", output->id,- image->width, image->height);- return CHROMA_OK;-}--// Cleanup OpenGL resources for output-static void cleanup_gl_resources(chroma_state_t *state,- chroma_output_t *output) {- if (!output || !output->gl_resources_initialized) {- return;- }+ // Fill with black+ memset(data, 0, size); - if (output->texture_id != 0) {- chroma_log_resource_deallocation("gpu_texture", 0, "cleanup");- glDeleteTextures(1, &output->texture_id);- output->texture_id = 0;+ struct wl_shm_pool *pool =+ wl_shm_create_pool(state->shm, fd, (int32_t)size);+ if (!pool) {+ chroma_log("ERROR", "wl_shm_create_pool failed");+ munmap(data, size);+ close(fd);+ return CHROMA_ERROR_WAYLAND; } - // Don't delete shared shader program here, it's managed by state- if (output->shader_program != 0) {- // Only delete if this output owns the program (not shared)- if (state && state->shader_program != output->shader_program) {- glDeleteProgram(output->shader_program);- }- output->shader_program = 0;- }+ struct wl_buffer *buffer = wl_shm_pool_create_buffer(+ pool, 0, width, height, stride, WL_SHM_FORMAT_ARGB8888);+ wl_shm_pool_destroy(pool);+ close(fd); - if (output->vbo != 0) {- glDeleteBuffers(1, &output->vbo);- output->vbo = 0;+ if (!buffer) {+ chroma_log("ERROR", "wl_shm_pool_create_buffer failed");+ munmap(data, size);+ return CHROMA_ERROR_WAYLAND; } - if (output->ebo != 0) {- glDeleteBuffers(1, &output->ebo);- output->ebo = 0;- }+ output->shm_buffer = buffer;+ output->shm_data = data;+ output->shm_size = size;+ output->shm_stride = stride; - output->gl_resources_initialized = false;- chroma_log("DEBUG", "Cleaned up GL resources for output %u", output->id);+ return CHROMA_OK; } -// EGL configuration selection-static int choose_egl_config(EGLDisplay display, EGLConfig *config) {- EGLint attributes[] = {EGL_SURFACE_TYPE,- EGL_WINDOW_BIT,- EGL_RED_SIZE,- 8,- EGL_GREEN_SIZE,- 8,- EGL_BLUE_SIZE,- 8,- EGL_ALPHA_SIZE,- 0,- EGL_RENDERABLE_TYPE,- EGL_OPENGL_ES2_BIT,- EGL_NONE};-- EGLint num_configs;- if (!eglChooseConfig(display, attributes, config, 1, &num_configs)) {- chroma_log("ERROR", "Failed to choose EGL config: 0x%04x", eglGetError());- return -1;+// Sample a pixel from image data (grayscale, grayscale+alpha, RGB, or RGBA)+static inline uint32_t sample_pixel(const chroma_image_t *image, int x,+ int y) {+ if (!image || !image->data || x < 0 || y < 0 || x >= image->width ||+ y >= image->height) {+ return 0xFF000000; // black with full alpha } - if (num_configs == 0) {- chroma_log("ERROR", "No suitable EGL configs found");- return -1;+ int idx = (y * image->width + x) * image->channels;+ unsigned char r, g, b, a;++ switch (image->channels) {+ case 1:+ r = g = b = image->data[idx];+ a = 0xFF;+ break;+ case 2:+ r = g = b = image->data[idx];+ a = image->data[idx + 1];+ break;+ case 3:+ r = image->data[idx];+ g = image->data[idx + 1];+ b = image->data[idx + 2];+ a = 0xFF;+ break;+ default: // 4 or more+ r = image->data[idx];+ g = image->data[idx + 1];+ b = image->data[idx + 2];+ a = image->data[idx + 3];+ break; } - return 0;+ return ((uint32_t)a << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) |+ (uint32_t)b; } -// EGL initialization-int chroma_egl_init(chroma_state_t *state) {- if (!state || !state->display) {- return CHROMA_ERROR_INIT;- }-- // Get EGL display- state->egl_display = eglGetDisplay((EGLNativeDisplayType)state->display);- if (state->egl_display == EGL_NO_DISPLAY) {- chroma_log("ERROR", "Failed to get EGL display: 0x%04x", eglGetError());- return CHROMA_ERROR_EGL;+// Draw scaled image into the shared memory buffer+static void draw_scaled_image(chroma_output_t *output,+ chroma_image_t *image) {+ if (!output || !image || !output->shm_data) {+ return; } - // Initialize EGL- EGLint major, minor;- if (!eglInitialize(state->egl_display, &major, &minor)) {- chroma_log("ERROR", "Failed to initialize EGL: 0x%04x", eglGetError());- return CHROMA_ERROR_EGL;- }+ int out_w = output->width;+ int out_h = output->height;+ int img_w = image->width;+ int img_h = image->height;+ uint32_t *dst = (uint32_t *)output->shm_data; - chroma_log("INFO", "EGL initialized: version %d.%d", major, minor);- chroma_log_memory_stats("post-egl-init");+ // Pre-fill with black+ memset(output->shm_data, 0, output->shm_size); - // Bind OpenGL ES API for better efficiency on embedded/GPU drivers- if (!eglBindAPI(EGL_OPENGL_ES_API)) {- chroma_log("ERROR", "Failed to bind OpenGL ES API: 0x%04x", eglGetError());- chroma_egl_cleanup(state);- return CHROMA_ERROR_EGL;- }+ float anchor_x = output->anchor_x;+ float anchor_y = output->anchor_y; - // Choose EGL config- if (choose_egl_config(state->egl_display, &state->egl_config) != 0) {- chroma_egl_cleanup(state);- return CHROMA_ERROR_EGL;+ switch (output->scale_mode) {+ case CHROMA_SCALE_STRETCH: {+ for (int y = 0; y < out_h; y++) {+ for (int x = 0; x < out_w; x++) {+ int sx = (int)((float)x * img_w / out_w);+ int sy = (int)((float)y * img_h / out_h);+ sx = (sx < 0) ? 0 : (sx >= img_w) ? img_w - 1 : sx;+ sy = (sy < 0) ? 0 : (sy >= img_h) ? img_h - 1 : sy;+ dst[y * out_w + x] = sample_pixel(image, sx, sy);+ }+ }+ break; } - // Create EGL context for GLES 2.0- EGLint context_attributes[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};+ case CHROMA_SCALE_FILL: {+ float img_aspect = (float)img_w / (float)img_h;+ float out_aspect = (float)out_w / (float)out_h; - state->egl_context = eglCreateContext(state->egl_display, state->egl_config,- EGL_NO_CONTEXT, context_attributes);- if (state->egl_context == EGL_NO_CONTEXT) {- chroma_log("ERROR", "Failed to create EGL context: 0x%04x", eglGetError());- chroma_egl_cleanup(state);- return CHROMA_ERROR_EGL;- }+ float crop_w, crop_h, crop_x, crop_y; - chroma_log("INFO", "EGL context created successfully");- return CHROMA_OK;-}+ if (img_aspect > out_aspect) {+ // Image is wider - crop left/right+ crop_h = (float)img_h;+ crop_w = crop_h * out_aspect;+ } else {+ // Image is taller - crop top/bottom+ crop_w = (float)img_w;+ crop_h = crop_w / out_aspect;+ } -// EGL cleanup-void chroma_egl_cleanup(chroma_state_t *state) {- if (!state) {- return;+ crop_x = (anchor_x / 100.0f) * ((float)img_w - crop_w);+ crop_y = (anchor_y / 100.0f) * ((float)img_h - crop_h);++ // Clamp crop region+ if (crop_x < 0)+ crop_x = 0;+ if (crop_y < 0)+ crop_y = 0;+ if (crop_x > (float)img_w - crop_w)+ crop_x = (float)img_w - crop_w;+ if (crop_y > (float)img_h - crop_h)+ crop_y = (float)img_h - crop_h;++ for (int y = 0; y < out_h; y++) {+ for (int x = 0; x < out_w; x++) {+ float sx = crop_x + (float)x * crop_w / (float)out_w;+ float sy = crop_y + (float)y * crop_h / (float)out_h;+ int ix = (int)sx;+ int iy = (int)sy;+ dst[y * out_w + x] = sample_pixel(image, ix, iy);+ }+ }+ break; } - // Clean up shared shader program while context is still current- if (state->shader_program != 0) {- glDeleteProgram(state->shader_program);- state->shader_program = 0;+ case CHROMA_SCALE_FIT: {+ float scale_x = (float)out_w / (float)img_w;+ float scale_y = (float)out_h / (float)img_h;+ float scale = (scale_x < scale_y) ? scale_x : scale_y;++ float scaled_w = (float)img_w * scale;+ float scaled_h = (float)img_h * scale;++ float offset_x = (anchor_x / 100.0f) * ((float)out_w - scaled_w);+ float offset_y = (anchor_y / 100.0f) * ((float)out_h - scaled_h);++ int start_x = (int)offset_x;+ int start_y = (int)offset_y;+ int end_x = (int)(offset_x + scaled_w + 0.5f);+ int end_y = (int)(offset_y + scaled_h + 0.5f);++ if (start_x < 0)+ start_x = 0;+ if (start_y < 0)+ start_y = 0;+ if (end_x > out_w)+ end_x = out_w;+ if (end_y > out_h)+ end_y = out_h;++ for (int y = start_y; y < end_y; y++) {+ for (int x = start_x; x < end_x; x++) {+ float sx = ((float)x - offset_x) / scale;+ float sy = ((float)y - offset_y) / scale;+ int ix = (int)sx;+ int iy = (int)sy;+ ix = (ix < 0) ? 0 : (ix >= img_w) ? img_w - 1 : ix;+ iy = (iy < 0) ? 0 : (iy >= img_h) ? img_h - 1 : iy;+ dst[y * out_w + x] = sample_pixel(image, ix, iy);+ }+ }+ break; } - if (state->egl_display != EGL_NO_DISPLAY) {- eglMakeCurrent(state->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,- EGL_NO_CONTEXT);-- if (state->egl_context != EGL_NO_CONTEXT) {- eglDestroyContext(state->egl_display, state->egl_context);- state->egl_context = EGL_NO_CONTEXT;+ case CHROMA_SCALE_CENTER:+ default: {+ if (img_w <= out_w && img_h <= out_h) {+ // Image fits entirely - position with anchor+ float offset_x =+ (anchor_x / 100.0f) * ((float)out_w - (float)img_w);+ float offset_y =+ (anchor_y / 100.0f) * ((float)out_h - (float)img_h);++ for (int y = 0; y < img_h; y++) {+ for (int x = 0; x < img_w; x++) {+ int dx = (int)(offset_x + x);+ int dy = (int)(offset_y + y);+ if (dx >= 0 && dx < out_w && dy >= 0 && dy < out_h) {+ dst[dy * out_w + dx] = sample_pixel(image, x, y);+ }+ }+ }+ } else {+ // Image is larger than output - clip with anchor+ float src_offset_x =+ (anchor_x / 100.0f) * ((float)img_w - (float)out_w);+ float src_offset_y =+ (anchor_y / 100.0f) * ((float)img_h - (float)out_h);++ if (src_offset_x < 0)+ src_offset_x = 0;+ if (src_offset_y < 0)+ src_offset_y = 0;+ if (src_offset_x > (float)img_w - (float)out_w)+ src_offset_x = (float)img_w - (float)out_w;+ if (src_offset_y > (float)img_h - (float)out_h)+ src_offset_y = (float)img_h - (float)out_h;++ for (int y = 0; y < out_h; y++) {+ for (int x = 0; x < out_w; x++) {+ int sx = (int)(src_offset_x + x);+ int sy = (int)(src_offset_y + y);+ dst[y * out_w + x] = sample_pixel(image, sx, sy);+ }+ } }-- eglTerminate(state->egl_display);- state->egl_display = EGL_NO_DISPLAY;+ break;+ } }-- chroma_log("INFO", "EGL cleaned up"); } // Create surface for output@@ -612,16 +333,19 @@ int chroma_surface_create(chroma_state_t *state, chroma_output_t *output) { } // Configure layer surface- zwlr_layer_surface_v1_set_size(output->layer_surface, (uint32_t)output->width,+ zwlr_layer_surface_v1_set_size(output->layer_surface,+ (uint32_t)output->width, (uint32_t)output->height);- zwlr_layer_surface_v1_set_anchor(output->layer_surface,- ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP |- ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT |- ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM |- ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT);+ zwlr_layer_surface_v1_set_anchor(+ output->layer_surface,+ ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP |+ ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT |+ ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM |+ ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT); zwlr_layer_surface_v1_set_exclusive_zone(output->layer_surface, -1); zwlr_layer_surface_v1_set_keyboard_interactivity(- output->layer_surface, ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE);+ output->layer_surface,+ ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE); // Add layer surface listener zwlr_layer_surface_v1_add_listener(@@ -633,34 +357,9 @@ int chroma_surface_create(chroma_state_t *state, chroma_output_t *output) { // Wait for configure event wl_display_roundtrip(state->display); - // Create EGL window- output->egl_window =- wl_egl_window_create(output->surface, output->width, output->height);- if (!output->egl_window) {- chroma_log("ERROR", "Failed to create EGL window for output %u",- output->id);- chroma_surface_destroy(output);- return CHROMA_ERROR_EGL;- }-- // Create EGL surface- output->egl_surface =- eglCreateWindowSurface(state->egl_display, state->egl_config,- (EGLNativeWindowType)output->egl_window, NULL);- if (output->egl_surface == EGL_NO_SURFACE) {- chroma_log("ERROR", "Failed to create EGL surface for output %u: 0x%04x",- output->id, eglGetError());- chroma_surface_destroy(output);- return CHROMA_ERROR_EGL;- }- chroma_log("INFO", "Created surface for output %u (%dx%d)", output->id, output->width, output->height); - // Log surface creation resource allocation- size_t surface_size = (size_t)output->width * (size_t)output->height * 4;- chroma_log_resource_allocation("egl_surface", surface_size, "output surface");- return CHROMA_OK; } @@ -670,18 +369,7 @@ void chroma_surface_destroy(chroma_output_t *output) { return; } - // Clean up OpenGL resources first. We use output's back-reference to state- cleanup_gl_resources(output->state, output);-- if (output->egl_surface != EGL_NO_SURFACE) {- eglDestroySurface(eglGetCurrentDisplay(), output->egl_surface);- output->egl_surface = EGL_NO_SURFACE;- }-- if (output->egl_window) {- wl_egl_window_destroy(output->egl_window);- output->egl_window = NULL;- }+ chroma_buffer_destroy(output); if (output->layer_surface) { zwlr_layer_surface_v1_destroy(output->layer_surface);@@ -695,134 +383,51 @@ void chroma_surface_destroy(chroma_output_t *output) { output->configured = false; - // Log surface destruction- size_t surface_size = (size_t)output->width * (size_t)output->height * 4;- chroma_log_resource_deallocation("egl_surface", surface_size,- "output surface cleanup");- chroma_log("DEBUG", "Destroyed surface for output %u", output->id); } -// Render wallpaper to output using cached resources+// Render wallpaper to output int chroma_render_wallpaper(chroma_state_t *state, chroma_output_t *output) { if (!state || !output || !output->image || !output->image->loaded) { return CHROMA_ERROR_INIT; } - // Make context current- if (!eglMakeCurrent(state->egl_display, output->egl_surface,- output->egl_surface, state->egl_context)) {- chroma_log("ERROR", "Failed to make EGL context current: 0x%04x",- eglGetError());- return CHROMA_ERROR_EGL;- }-- if (init_gl_resources(state, output) != CHROMA_OK) {- return CHROMA_ERROR_EGL;+ if (!output->configured) {+ chroma_log("WARN", "Output %u not yet configured, skipping render",+ output->id);+ return CHROMA_ERROR_INIT; } - if (output->texture_id == 0) {- if (update_texture_from_image(output, output->image,- output->filter_quality) != CHROMA_OK) {- chroma_log("ERROR", "Failed to update texture for output %u", output->id);- return CHROMA_ERROR_EGL;- }- }+ // Clean up old buffer+ chroma_buffer_destroy(output); - // Set viewport- glViewport(0, 0, output->width, output->height);-- // Clear screen- glClearColor(0.0f, 0.0f, 0.0f, 1.0f);- glClear(GL_COLOR_BUFFER_BIT);-- // Use cached shader program- glUseProgram(output->shader_program);-- // Bind cached texture- glActiveTexture(GL_TEXTURE0);- glBindTexture(GL_TEXTURE_2D, output->texture_id);- glUniform1i(glGetUniformLocation(output->shader_program, "texture"), 0);-- // Update VBO only if needed. E.g, image changed, scale mode changed, or first- // render- if (output->vbo_dirty) {- // Calculate texture coordinates based on scaling mode, anchor, and anchor- // coords- float tex_coords[8];- calculate_texture_coords(output->scale_mode, output->anchor_x,- output->anchor_y, output->image->width,- output->image->height, output->width,- output->height, tex_coords);-- // Create dynamic vertex data with calculated texture coordinates- float dynamic_vertices[] = {- // Position Texcoord- -1.0f, -1.0f, tex_coords[0], tex_coords[1], // bottom-left- 1.0f, -1.0f, tex_coords[2], tex_coords[3], // bottom-right- 1.0f, 1.0f, tex_coords[4], tex_coords[5], // top-right- -1.0f, 1.0f, tex_coords[6], tex_coords[7] // top-left- };-- // Update VBO with dynamic texture coordinates- glBindBuffer(GL_ARRAY_BUFFER, output->vbo);- glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(dynamic_vertices),- dynamic_vertices);- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, output->ebo);-- output->vbo_dirty = false; // mark VBO as up to date- } else {- // Just bind the existing buffers- glBindBuffer(GL_ARRAY_BUFFER, output->vbo);- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, output->ebo);+ // Create new shared memory buffer+ int ret = chroma_buffer_create(state, output);+ if (ret != CHROMA_OK) {+ chroma_log("ERROR", "Failed to create shm buffer for output %u",+ output->id);+ return ret; } - // Set vertex attributes- GLint position_attr = glGetAttribLocation(output->shader_program, "position");- GLint texcoord_attr = glGetAttribLocation(output->shader_program, "texcoord");-- glEnableVertexAttribArray((GLuint)position_attr);- glVertexAttribPointer((GLuint)position_attr, 2, GL_FLOAT, GL_FALSE,- 4 * sizeof(float), (void *)0);+ // Draw scaled image into buffer+ draw_scaled_image(output, output->image); - glEnableVertexAttribArray((GLuint)texcoord_attr);- glVertexAttribPointer((GLuint)texcoord_attr, 2, GL_FLOAT, GL_FALSE,- 4 * sizeof(float), (void *)(2 * sizeof(float)));-- // Draw- glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0);+ // Attach buffer to surface+ wl_surface_attach(output->surface, output->shm_buffer, 0, 0);+ wl_surface_damage_buffer(output->surface, 0, 0, output->width,+ output->height);+ wl_surface_commit(output->surface);+ wl_display_flush(state->display); - // Unbind resources- glBindBuffer(GL_ARRAY_BUFFER, 0);- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);- glBindTexture(GL_TEXTURE_2D, 0);- glUseProgram(0);+ chroma_log("INFO", "Rendered wallpaper to output %u (%dx%d)", output->id,+ output->width, output->height); - // Swap buffers; this implicitly commits the surface for EGL on Wayland- if (!eglSwapBuffers(state->egl_display, output->egl_surface)) {- chroma_log("ERROR", "Failed to swap buffers for output %u: 0x%04x",- output->id, eglGetError());- return CHROMA_ERROR_EGL;+ // Release image reference and free CPU data if no longer needed+ if (output->image) {+ chroma_image_t *image = output->image;+ chroma_image_release(image);+ output->image = NULL; } - chroma_log("DEBUG", "Rendered wallpaper to output %u (cached resources)",- output->id); return CHROMA_OK; }--// Invalidate texture cache for output-void chroma_output_invalidate_texture(chroma_output_t *output) {- if (!output || !output->gl_resources_initialized) {- return;- }-- if (output->texture_id != 0) {- glDeleteTextures(1, &output->texture_id);- output->texture_id = 0;- output->texture_uploaded = false; // reset upload flag- chroma_log("DEBUG", "Invalidated texture cache for output %u", output->id);- }-- // Mark VBO as dirty since texture coordinates may need recalculation- output->vbo_dirty = true;-}@@ -37,6 +37,13 @@ static void registry_global(void *data, struct wl_registry *registry, "TRACE", "wlr-layer-shell protocol available, wallpaper support enabled"); }+ } else if (strcmp(interface, wl_shm_interface.name) == 0) {+ state->shm = wl_registry_bind(registry, id, &wl_shm_interface, 1);+ if (!state->shm) {+ chroma_log("ERROR", "Failed to bind shm");+ } else {+ chroma_log("INFO", "Bound shm (version %u)", version);+ } } else if (strcmp(interface, wl_output_interface.name) == 0) { struct wl_output *output = wl_registry_bind( registry, id, &wl_output_interface, version < 4 ? version : 4);@@ -91,7 +98,7 @@ static void layer_surface_configure(void *data, chroma_log("TRACE", "Sent configure acknowledgment for output %u serial %u", output->id, serial); - // Mark as configured - actual commit happens in render via eglSwapBuffers+ // Mark as configured - actual commit happens in render via wl_surface_commit output->configured = true; chroma_log("DEBUG", "Acknowledged layer surface configure for output %u",@@ -284,6 +291,13 @@ int chroma_wayland_connect(chroma_state_t *state) { return CHROMA_ERROR_WAYLAND; } + // Check if we got shm+ if (!state->shm) {+ chroma_log("ERROR", "No shm available");+ chroma_wayland_disconnect(state);+ return CHROMA_ERROR_WAYLAND;+ }+ chroma_log("INFO", "Connected to Wayland display, found %d outputs", state->output_count); return CHROMA_OK;@@ -316,6 +330,11 @@ void chroma_wayland_disconnect(chroma_state_t *state) { state->layer_shell = NULL; } + if (state->shm) {+ wl_shm_destroy(state->shm);+ state->shm = NULL;+ }+ if (state->compositor) { wl_compositor_destroy(state->compositor); state->compositor = NULL;