brewery
notashelf /
a838f8673118a493c9618804f424df1a682cd147

chroma

public

Lightweight wallpaper daemon for Wayland

Star0tarball
clone
ssh://git@git.notashelf.dev:33/notashelf/chroma.git

Commit a838f8673118

tarball

NotAShelf <raf@notashelf.dev> · 2026-05-17 21:23 UTC

unverified3 files changed+42-30
render: replace EGL/OpenGL pipeline with `wl_shm` shared memory

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: If73c9ac16c2a31e211c2cda2b0ce2c586a6a6964
diff --git a/include/chroma.h b/include/chroma.hindex 393c62b..843c2d1 100644--- a/include/chroma.h+++ b/include/chroma.h@@ -212,6 +212,7 @@ 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);+void chroma_buffer_unmap(chroma_output_t *output); int chroma_render_wallpaper(chroma_state_t *state, chroma_output_t *output);  // Layer shell functionsdiff --git a/src/core.c b/src/core.cindex 6974ec4..4b3a75a 100644--- a/src/core.c+++ b/src/core.c@@ -137,9 +137,7 @@ static int assign_wallpaper_to_output(chroma_state_t *state,          old_filter_quality != output->filter_quality ||          old_anchor != output->anchor || old_anchor_x != output->anchor_x ||          old_anchor_y != output->anchor_y)) {-      chroma_log("DEBUG",-                 "Configuration changed for output %u",-                 output->id);+      chroma_log("DEBUG", "Configuration changed for output %u", output->id);     }   } else {     output->config_loaded = false;@@ -158,8 +156,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: %s", output->id,-               chroma_error_string(ret));+    chroma_log("ERROR", "Failed to render wallpaper for output %u: %s",+               output->id, chroma_error_string(ret));     return ret;   } diff --git a/src/render.c b/src/render.cindex 626d693..12eb6a7 100644--- a/src/render.c+++ b/src/render.c@@ -33,6 +33,22 @@ void chroma_buffer_destroy(chroma_output_t *output) {   } } +// Unmap client-side memory while keeping the wl_buffer proxy alive.+// The compositor retains its own fd reference and mapping, so the+// wallpaper remains visible after this call.+void chroma_buffer_unmap(chroma_output_t *output) {+  if (!output || !output->shm_data) {+    return;+  }++  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 shared memory buffer for an output int chroma_buffer_create(chroma_state_t *state, chroma_output_t *output) {   if (!state || !output || !state->shm) {@@ -80,8 +96,7 @@ int chroma_buffer_create(chroma_state_t *state, chroma_output_t *output) {   // Fill with black   memset(data, 0, size); -  struct wl_shm_pool *pool =-      wl_shm_create_pool(state->shm, fd, (int32_t)size);+  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);@@ -109,8 +124,7 @@ int chroma_buffer_create(chroma_state_t *state, chroma_output_t *output) { }  // 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) {+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@@ -147,8 +161,7 @@ static inline uint32_t sample_pixel(const chroma_image_t *image, int x, }  // Draw scaled image into the shared memory buffer-static void draw_scaled_image(chroma_output_t *output,-                              chroma_image_t *image) {+static void draw_scaled_image(chroma_output_t *output, chroma_image_t *image) {   if (!output || !image || !output->shm_data) {     return;   }@@ -263,10 +276,8 @@ static void draw_scaled_image(chroma_output_t *output,   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);+      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++) {@@ -279,10 +290,8 @@ static void draw_scaled_image(chroma_output_t *output,       }     } 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);+      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;@@ -333,19 +342,16 @@ 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(@@ -417,11 +423,18 @@ int chroma_render_wallpaper(chroma_state_t *state, chroma_output_t *output) {   wl_surface_damage_buffer(output->surface, 0, 0, output->width,                            output->height);   wl_surface_commit(output->surface);-  wl_display_flush(state->display);+  if (wl_display_flush(state->display) < 0) {+    chroma_log("ERROR", "Failed to flush Wayland display for output %u: %s",+               output->id, strerror(errno));+    return CHROMA_ERROR_WAYLAND;+  }    chroma_log("INFO", "Rendered wallpaper to output %u (%dx%d)", output->id,              output->width, output->height); +  // Unmap client-side memory; compositor keeps its own fd reference+  chroma_buffer_unmap(output);+   // Release image reference and free CPU data if no longer needed   if (output->image) {     chroma_image_t *image = output->image;