brewery
notashelf /
dadba853e886c4fe1473481ff48f88da9b03c7da

chroma

public

Lightweight wallpaper daemon for Wayland

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

Commit dadba853e886

tarball

NotAShelf <raf@notashelf.dev> · 2026-04-15 09:44 UTC

unverified4 files changed+168-85
render: implement coordinate-based anchor positioning

Not to be confused with Minecraft coordinates.

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: Ifdb90fc92a1565ba1d30b85c91d6e1ab6a6a6964
diff --git a/include/chroma.h b/include/chroma.hindex 76afc43..7ff7ead 100644--- a/include/chroma.h+++ b/include/chroma.h@@ -104,6 +104,8 @@ typedef struct {   chroma_scale_mode_t scale_mode;   chroma_filter_quality_t filter_quality;   chroma_anchor_t anchor;+  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@@ -123,6 +125,8 @@ typedef struct {   chroma_scale_mode_t scale_mode;   chroma_filter_quality_t filter_quality;   chroma_anchor_t anchor;+  float anchor_x; // custom X offset (0-100, 50=center, 0=left, 100=right)+  float anchor_y; // custom Y offset (0-100, 50=center, 0=top, 100=bottom) } chroma_config_mapping_t;  // Application configuration@@ -136,6 +140,8 @@ typedef struct {   chroma_scale_mode_t default_scale_mode;   chroma_filter_quality_t default_filter_quality;   chroma_anchor_t default_anchor;+  float default_anchor_x; // custom anchor X offset (0-100, 50=center)+  float default_anchor_y; // custom anchor Y offset (0-100, 50=center)    // Image downsampling settings   bool enable_downsampling; // enable automatic downsampling@@ -248,7 +254,7 @@ const char *chroma_config_get_image_for_output(chroma_config_t *config, int chroma_config_get_mapping_for_output(     chroma_config_t *config, const char *output_name,     chroma_scale_mode_t *scale_mode, chroma_filter_quality_t *filter_quality,-    chroma_anchor_t *anchor);+    chroma_anchor_t *anchor, float *anchor_x, float *anchor_y);  void chroma_config_print(const chroma_config_t *config); diff --git a/src/config.c b/src/config.cindex 0a35467..9bae27c 100644--- a/src/config.c+++ b/src/config.c@@ -184,7 +184,8 @@ static int add_output_mapping(chroma_config_t *config, const char *output_name,                               const char *image_path,                               chroma_scale_mode_t scale_mode,                               chroma_filter_quality_t filter_quality,-                              chroma_anchor_t anchor) {+                              chroma_anchor_t anchor, float anchor_x,+                              float anchor_y) {   if (!config || !output_name || !image_path) {     return CHROMA_ERROR_INIT;   }@@ -218,13 +219,17 @@ static int add_output_mapping(chroma_config_t *config, const char *output_name,   mapping->scale_mode = scale_mode;   mapping->filter_quality = filter_quality;   mapping->anchor = anchor;+  mapping->anchor_x = anchor_x;+  mapping->anchor_y = anchor_y;    config->mapping_count++;    chroma_log(-      "DEBUG", "Added mapping: %s -> %s (scale: %s, filter: %s, anchor: %s)",+      "DEBUG",+      "Added mapping: %s -> %s (scale: %s, filter: %s, anchor: %s @ %.1f,%.1f)",       output_name, image_path, scale_mode_to_string(scale_mode),-      filter_quality_to_string(filter_quality), anchor_to_string(anchor));+      filter_quality_to_string(filter_quality), anchor_to_string(anchor),+      anchor_x, anchor_y);   chroma_log("TRACE", "Output mapping %d: '%s' -> '%s' (path length: %zu)",              config->mapping_count, output_name, image_path, path_len);   return CHROMA_OK;@@ -244,6 +249,8 @@ static void init_default_config(chroma_config_t *config) {   config->default_scale_mode = CHROMA_SCALE_FILL;   config->default_filter_quality = CHROMA_FILTER_LINEAR;   config->default_anchor = CHROMA_ANCHOR_CENTER;+  config->default_anchor_x = 50.0f; // center+  config->default_anchor_y = 50.0f; // center    // Set default downsampling settings   config->enable_downsampling = true; // enable by default, performance etc.@@ -351,6 +358,34 @@ static int parse_config_line(chroma_config_t *config, char *line,     config->default_anchor = parse_anchor(value);     chroma_log("DEBUG", "Set default anchor: %s",                anchor_to_string(config->default_anchor));+  } else if (strcasecmp(key, "anchor_x") == 0) {+    char *endptr = NULL;+    float ax = strtof(value, &endptr);+    if (endptr == value || *endptr != '\0') {+      chroma_log("WARN", "Invalid anchor_x: %s (not a number, using 50)",+                 value);+      config->default_anchor_x = 50.0f;+    } else if (ax < 0.0f || ax > 100.0f) {+      chroma_log("WARN", "Invalid anchor_x: %s (range 0-100, using 50)", value);+      config->default_anchor_x = 50.0f;+    } else {+      config->default_anchor_x = ax;+      chroma_log("DEBUG", "Set default anchor_x: %.1f", ax);+    }+  } else if (strcasecmp(key, "anchor_y") == 0) {+    char *endptr = NULL;+    float ay = strtof(value, &endptr);+    if (endptr == value || *endptr != '\0') {+      chroma_log("WARN", "Invalid anchor_y: %s (not a number, using 50)",+                 value);+      config->default_anchor_y = 50.0f;+    } else if (ay < 0.0f || ay > 100.0f) {+      chroma_log("WARN", "Invalid anchor_y: %s (range 0-100, using 50)", value);+      config->default_anchor_y = 50.0f;+    } else {+      config->default_anchor_y = ay;+      chroma_log("DEBUG", "Set default anchor_y: %.1f", ay);+    }   } else if (strcasecmp(key, "max_output_width") == 0) {     int width = atoi(value);     if (width > 0 && width <= 16384) { // Reasonable limits@@ -418,8 +453,70 @@ static int parse_config_line(chroma_config_t *config, char *line,                    filter_quality_to_string(mapping->filter_quality));       } else if (strcasecmp(property, "anchor") == 0) {         mapping->anchor = parse_anchor(value);-        chroma_log("DEBUG", "Set anchor for output %s: %s", output_name,-                   anchor_to_string(mapping->anchor));+        // Set anchor_x/anchor_y based on named anchor+        switch (mapping->anchor) {+        case CHROMA_ANCHOR_TOP:+          mapping->anchor_x = 50.0f;+          mapping->anchor_y = 0.0f;+          break;+        case CHROMA_ANCHOR_BOTTOM:+          mapping->anchor_x = 50.0f;+          mapping->anchor_y = 100.0f;+          break;+        case CHROMA_ANCHOR_LEFT:+          mapping->anchor_x = 0.0f;+          mapping->anchor_y = 50.0f;+          break;+        case CHROMA_ANCHOR_RIGHT:+          mapping->anchor_x = 100.0f;+          mapping->anchor_y = 50.0f;+          break;+        case CHROMA_ANCHOR_TOP_LEFT:+          mapping->anchor_x = 0.0f;+          mapping->anchor_y = 0.0f;+          break;+        case CHROMA_ANCHOR_TOP_RIGHT:+          mapping->anchor_x = 100.0f;+          mapping->anchor_y = 0.0f;+          break;+        case CHROMA_ANCHOR_BOTTOM_LEFT:+          mapping->anchor_x = 0.0f;+          mapping->anchor_y = 100.0f;+          break;+        case CHROMA_ANCHOR_BOTTOM_RIGHT:+          mapping->anchor_x = 100.0f;+          mapping->anchor_y = 100.0f;+          break;+        default:+          mapping->anchor_x = 50.0f;+          mapping->anchor_y = 50.0f;+          break;+        }+        chroma_log("DEBUG", "Set anchor for output %s: %s (x=%.1f, y=%.1f)",+                   output_name, anchor_to_string(mapping->anchor),+                   mapping->anchor_x, mapping->anchor_y);+      } else if (strcasecmp(property, "anchor_x") == 0) {+        float ax = atof(value);+        if (ax >= 0.0f && ax <= 100.0f) {+          mapping->anchor_x = ax;+          chroma_log("DEBUG", "Set anchor_x for output %s: %.1f", output_name,+                     ax);+        } else {+          mapping->anchor_x = 50.0f;+          chroma_log("WARN", "Invalid anchor_x: %s (range 0-100, using 50)",+                     value);+        }+      } else if (strcasecmp(property, "anchor_y") == 0) {+        float ay = atof(value);+        if (ay >= 0.0f && ay <= 100.0f) {+          mapping->anchor_y = ay;+          chroma_log("DEBUG", "Set anchor_y for output %s: %.1f", output_name,+                     ay);+        } else {+          mapping->anchor_y = 50.0f;+          chroma_log("WARN", "Invalid anchor_y: %s (range 0-100, using 50)",+                     value);+        }       } else {         chroma_log("WARN", "Unknown output property: %s (line %d)", property,                    line_number);@@ -442,10 +539,10 @@ static int parse_config_line(chroma_config_t *config, char *line,       return CHROMA_OK;     } -    if (add_output_mapping(config, output_name, path_to_validate,-                           config->default_scale_mode,-                           config->default_filter_quality,-                           config->default_anchor) != CHROMA_OK) {+    if (add_output_mapping(+            config, output_name, path_to_validate, config->default_scale_mode,+            config->default_filter_quality, config->default_anchor,+            config->default_anchor_x, config->default_anchor_y) != CHROMA_OK) {       chroma_log("ERROR", "Failed to add output mapping: %s -> %s", output_name,                  path_to_validate);       if (expanded_path) {@@ -589,12 +686,13 @@ const char *chroma_config_get_image_for_output(chroma_config_t *config, }  // Get configuration mapping for output, including scale mode, filter-// quality, and anchor+// quality, anchor, and custom anchor coordinates int chroma_config_get_mapping_for_output(     chroma_config_t *config, const char *output_name,     chroma_scale_mode_t *scale_mode, chroma_filter_quality_t *filter_quality,-    chroma_anchor_t *anchor) {-  if (!config || !output_name || !scale_mode || !filter_quality || !anchor) {+    chroma_anchor_t *anchor, float *anchor_x, float *anchor_y) {+  if (!config || !output_name || !scale_mode || !filter_quality || !anchor ||+      !anchor_x || !anchor_y) {     return CHROMA_ERROR_INIT;   } @@ -604,12 +702,14 @@ int chroma_config_get_mapping_for_output(       *scale_mode = config->mappings[i].scale_mode;       *filter_quality = config->mappings[i].filter_quality;       *anchor = config->mappings[i].anchor;+      *anchor_x = config->mappings[i].anchor_x;+      *anchor_y = config->mappings[i].anchor_y;       chroma_log("DEBUG",                  "Found specific mapping for output %s: scale=%s, filter=%s, "-                 "anchor=%s",+                 "anchor=%s @ %.1f,%.1f",                  output_name, scale_mode_to_string(*scale_mode),                  filter_quality_to_string(*filter_quality),-                 anchor_to_string(*anchor));+                 anchor_to_string(*anchor), *anchor_x, *anchor_y);       return CHROMA_OK;     }   }@@ -618,10 +718,14 @@ int chroma_config_get_mapping_for_output(   *scale_mode = config->default_scale_mode;   *filter_quality = config->default_filter_quality;   *anchor = config->default_anchor;-  chroma_log(-      "DEBUG", "Using defaults for output %s: scale=%s, filter=%s, anchor=%s",-      output_name, scale_mode_to_string(*scale_mode),-      filter_quality_to_string(*filter_quality), anchor_to_string(*anchor));+  *anchor_x = config->default_anchor_x;+  *anchor_y = config->default_anchor_y;+  chroma_log("DEBUG",+             "Using defaults for output %s: scale=%s, filter=%s, anchor=%s @ "+             "%.1f,%.1f",+             output_name, scale_mode_to_string(*scale_mode),+             filter_quality_to_string(*filter_quality),+             anchor_to_string(*anchor), *anchor_x, *anchor_y);   return CHROMA_OK; } diff --git a/src/core.c b/src/core.cindex a8890c1..b7a5029 100644--- a/src/core.c+++ b/src/core.c@@ -113,20 +113,29 @@ static int assign_wallpaper_to_output(chroma_state_t *state,   chroma_scale_mode_t old_scale_mode = output->scale_mode;   chroma_filter_quality_t old_filter_quality = output->filter_quality;   chroma_anchor_t old_anchor = output->anchor;+  float old_anchor_x = output->anchor_x;+  float old_anchor_y = output->anchor_y;   bool had_config = output->config_loaded; -  // Load configuration for this output (scale mode, filter quality, anchor)+  // Load configuration for this output (scale mode, filter quality, anchor,+  // anchor coords)   if (chroma_config_get_mapping_for_output(           &state->config, output->name ? output->name : "unknown",-          &output->scale_mode, &output->filter_quality, &output->anchor) == CHROMA_OK) {+          &output->scale_mode, &output->filter_quality, &output->anchor,+          &output->anchor_x, &output->anchor_y) == CHROMA_OK) {     output->config_loaded = true;-    chroma_log("DEBUG", "Loaded config for output %u: scale=%d, filter=%d, anchor=%d",-               output->id, output->scale_mode, output->filter_quality, output->anchor);+    chroma_log("DEBUG",+               "Loaded config for output %u: scale=%d, filter=%d, anchor=%d @ "+               "%.1f,%.1f",+               output->id, output->scale_mode, output->filter_quality,+               output->anchor, output->anchor_x, output->anchor_y);      // Check if configuration changed and invalidate texture if needed-    if (had_config && (old_scale_mode != output->scale_mode ||-                       old_filter_quality != output->filter_quality ||-                       old_anchor != output->anchor)) {+    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",@@ -380,14 +389,16 @@ void chroma_log(const char *level, const char *format, ...) {   gettimeofday(&tv, NULL);   tm_info = localtime(&tv.tv_sec); -  truncation_check = 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));+  truncation_check =+      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)); -  if(truncation_check > 32 || truncation_check < 0) {-    // Something went seriously wrong with the snprintf, this is a fairly serious error as-    // the timestamp may be incomplete or corrupted, so print a warning+  if (truncation_check > 32 || truncation_check < 0) {+    // Something went seriously wrong with the snprintf, this is a fairly+    // serious error as the timestamp may be incomplete or corrupted, so print a+    // warning     printf("Following timestamp may be incomplete, truncated or corrupted!\n");   }   printf("[%s] %s: ", timestamp, level);diff --git a/src/render.c b/src/render.cindex 281489f..13444f3 100644--- a/src/render.c+++ b/src/render.c@@ -43,7 +43,7 @@ static void get_gl_filter_params(chroma_filter_quality_t quality,  // Calculate texture coordinates based on scaling mode and anchor position static void calculate_texture_coords(chroma_scale_mode_t scale_mode,-                                     chroma_anchor_t anchor,+                                     float anchor_x, float anchor_y,                                      int image_width, int image_height,                                      int output_width, int output_height,                                      float tex_coords[8]) {@@ -143,53 +143,13 @@ static void calculate_texture_coords(chroma_scale_mode_t scale_mode,     break;   } -  // Apply anchor-based offset by shifting the visible crop region-  // For fill: shift which part of image is shown-  // For fit/center: shift where the image is positioned-  // -  // Positive shift reveals left/bottom portion-  // Negative shift reveals right/top portion-  float u_shift = 0.0f;-  float v_shift = 0.0f;--  switch (anchor) {-  case CHROMA_ANCHOR_CENTER:-    u_shift = 0.0f;-    v_shift = 0.0f;-    break;-  case CHROMA_ANCHOR_TOP:-    u_shift = 0.0f;-    v_shift = -0.5f; // shift up to show top of image-    break;-  case CHROMA_ANCHOR_BOTTOM:-    u_shift = 0.0f;-    v_shift = 0.5f; // shift down to show bottom of image-    break;-  case CHROMA_ANCHOR_LEFT:-    u_shift = -0.5f; // shift left to show left of image-    v_shift = 0.0f;-    break;-  case CHROMA_ANCHOR_RIGHT:-    u_shift = 0.5f; // shift right to show right of image-    v_shift = 0.0f;-    break;-  case CHROMA_ANCHOR_TOP_LEFT:-    u_shift = -0.5f;-    v_shift = -0.5f;-    break;-  case CHROMA_ANCHOR_TOP_RIGHT:-    u_shift = 0.5f;-    v_shift = -0.5f;-    break;-  case CHROMA_ANCHOR_BOTTOM_LEFT:-    u_shift = -0.5f;-    v_shift = 0.5f;-    break;-  case CHROMA_ANCHOR_BOTTOM_RIGHT:-    u_shift = 0.5f;-    v_shift = 0.5f;-    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);@@ -755,11 +715,13 @@ int chroma_render_wallpaper(chroma_state_t *state, chroma_output_t *output) {   // 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 and anchor+    // Calculate texture coordinates based on scaling mode, anchor, and anchor+    // coords     float tex_coords[8];-    calculate_texture_coords(output->scale_mode, output->anchor,-                             output->image->width, output->image->height,-                             output->width, output->height, tex_coords);+    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[] = {