Compare commits
7 commits
ca468ce677
...
74f46f45bf
| Author | SHA1 | Date | |
|---|---|---|---|
|
74f46f45bf |
|||
|
5fd2e5660f |
|||
|
1a366d2445 |
|||
|
e7f107a8fe |
|||
|
9b42e70054 |
|||
|
3d42f75052 |
|||
|
74fed80a26 |
9 changed files with 680 additions and 52 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -9,3 +9,5 @@ src/wlr-layer-shell-unstable-v1.c
|
||||||
|
|
||||||
# Ignore test stuff that I create to... test stuff.
|
# Ignore test stuff that I create to... test stuff.
|
||||||
test_*
|
test_*
|
||||||
|
*.jpg
|
||||||
|
*.conf
|
||||||
|
|
|
||||||
63
Makefile
63
Makefile
|
|
@ -1,5 +1,5 @@
|
||||||
PROJECT_NAME = chroma
|
PROJECT_NAME = chroma
|
||||||
VERSION = 1.0.0
|
VERSION = 1.0.1
|
||||||
|
|
||||||
# Directories
|
# Directories
|
||||||
SRCDIR = src
|
SRCDIR = src
|
||||||
|
|
@ -69,7 +69,7 @@ $(INCDIR):
|
||||||
@mkdir -p $(INCDIR)
|
@mkdir -p $(INCDIR)
|
||||||
|
|
||||||
# Build main executable
|
# Build main executable
|
||||||
$(TARGET): $(PROTOCOL_HEADERS) $(OBJECTS) | $(BINDIR)
|
$(TARGET): version-header $(PROTOCOL_HEADERS) $(OBJECTS) | $(BINDIR)
|
||||||
@echo " LINK $@"
|
@echo " LINK $@"
|
||||||
@$(CC) $(OBJECTS) -o $@ $(LDFLAGS)
|
@$(CC) $(OBJECTS) -o $@ $(LDFLAGS)
|
||||||
|
|
||||||
|
|
@ -110,6 +110,16 @@ uninstall:
|
||||||
rm -f $(DESTDIR)$(SYSTEMD_INSTALL)/$(PROJECT_NAME).service
|
rm -f $(DESTDIR)$(SYSTEMD_INSTALL)/$(PROJECT_NAME).service
|
||||||
@echo "Uninstall complete."
|
@echo "Uninstall complete."
|
||||||
|
|
||||||
|
# Create version header
|
||||||
|
version-header:
|
||||||
|
@echo "Generating version header..."
|
||||||
|
@echo "#ifndef CHROMA_VERSION_H" > $(INCDIR)/chroma_version.h
|
||||||
|
@echo "#define CHROMA_VERSION_H" >> $(INCDIR)/chroma_version.h
|
||||||
|
@echo "" >> $(INCDIR)/chroma_version.h
|
||||||
|
@echo "#define CHROMA_VERSION \"$(VERSION)\"" >> $(INCDIR)/chroma_version.h
|
||||||
|
@echo "" >> $(INCDIR)/chroma_version.h
|
||||||
|
@echo "#endif // CHROMA_VERSION_H" >> $(INCDIR)/chroma_version.h
|
||||||
|
|
||||||
# Create systemd service file
|
# Create systemd service file
|
||||||
systemd-service: $(SYSTEMD_DIR)/$(PROJECT_NAME).service
|
systemd-service: $(SYSTEMD_DIR)/$(PROJECT_NAME).service
|
||||||
|
|
||||||
|
|
@ -140,33 +150,68 @@ test: $(TARGET)
|
||||||
@echo "Running tests..."
|
@echo "Running tests..."
|
||||||
@echo "Tests not implemented yet."
|
@echo "Tests not implemented yet."
|
||||||
|
|
||||||
|
# Version management targets
|
||||||
|
bump-patch:
|
||||||
|
@echo "Bumping patch version..."
|
||||||
|
@$(eval NEW_VERSION := $(shell echo $(VERSION) | awk -F. '{print $$1"."$$2"."$$3+1}'))
|
||||||
|
@sed -i 's/^VERSION = .*/VERSION = $(NEW_VERSION)/' Makefile
|
||||||
|
@echo "Version bumped to $(NEW_VERSION)"
|
||||||
|
|
||||||
|
bump-minor:
|
||||||
|
@echo "Bumping minor version..."
|
||||||
|
@$(eval NEW_VERSION := $(shell echo $(VERSION) | awk -F. '{print $$1"."$$2+1".0"}'))
|
||||||
|
@sed -i 's/^VERSION = .*/VERSION = $(NEW_VERSION)/' Makefile
|
||||||
|
@echo "Version bumped to $(NEW_VERSION)"
|
||||||
|
|
||||||
|
bump-major:
|
||||||
|
@echo "Bumping major version..."
|
||||||
|
@$(eval NEW_VERSION := $(shell echo $(VERSION) | awk -F. '{print $$1+1".0.0"}'))
|
||||||
|
@sed -i 's/^VERSION = .*/VERSION = $(NEW_VERSION)/' Makefile
|
||||||
|
@echo "Version bumped to $(NEW_VERSION)"
|
||||||
|
|
||||||
|
set-version:
|
||||||
|
@if [ -z "$(NEW_VER)" ]; then \
|
||||||
|
echo "Usage: make set-version NEW_VER=X.Y.Z"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
@echo "Setting version to $(NEW_VER)..."
|
||||||
|
@sed -i 's/^VERSION = .*/VERSION = $(NEW_VER)/' Makefile
|
||||||
|
@echo "Version set to $(NEW_VER)"
|
||||||
|
|
||||||
# Help target
|
# Help target
|
||||||
help:
|
help:
|
||||||
@echo "Available targets:"
|
@echo "Available targets:"
|
||||||
@echo " all - Build the main executable (default)"
|
@echo " all - Build main executable (default)"
|
||||||
@echo " debug - Build with debug symbols and sanitizers"
|
@echo " debug - Build with debug symbols and sanitizers"
|
||||||
@echo " static - Build statically linked executable"
|
@echo " static - Build statically linked executable"
|
||||||
@echo " check-deps - Check if all dependencies are available"
|
@echo " check-deps - Check if all dependencies are available"
|
||||||
@echo " install - Install the executable and systemd service"
|
@echo " install - Install executable and systemd service"
|
||||||
@echo " uninstall - Remove installed files"
|
@echo " uninstall - Remove installed files"
|
||||||
@echo " clean - Remove build artifacts"
|
@echo " clean - Remove build artifacts"
|
||||||
@echo " distclean - Remove all generated files"
|
@echo " distclean - Remove all generated files"
|
||||||
@echo " format - Format source code (requires clang-format)"
|
@echo " format - Format source code (requires clang-format)"
|
||||||
@echo " analyze - Run static analysis (requires cppcheck)"
|
@echo " analyze - Run static analysis (requires cppcheck)"
|
||||||
@echo " test - Run tests"
|
@echo " test - Run tests"
|
||||||
|
@echo " version-header - Generate version header from Makefile"
|
||||||
|
@echo " bump-patch - Increment patch version (X.Y.Z+1)"
|
||||||
|
@echo " bump-minor - Increment minor version (X.Y+1.0)"
|
||||||
|
@echo " bump-major - Increment major version (X+1.0.0)"
|
||||||
|
@echo " set-version - Set specific version (use NEW_VER=X.Y.Z)"
|
||||||
@echo " help - Show this help message"
|
@echo " help - Show this help message"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "Examples:"
|
@echo "Examples:"
|
||||||
@echo " make # Build with default settings"
|
@echo " make # Build with default settings"
|
||||||
@echo " make debug # Build debug version"
|
@echo " make debug # Build debug version"
|
||||||
@echo " make PREFIX=/usr install # Install to /usr instead of /usr/local"
|
@echo " make PREFIX=/usr install # Install to /usr instead of /usr/local"
|
||||||
@echo " make CC=clang # Use clang instead of gcc"
|
@echo " make CC=clang # Use clang instead of gcc"
|
||||||
|
@echo " make bump-patch # Bump to 1.0.2"
|
||||||
|
@echo " make set-version NEW_VER=2.0.0 # Set to 2.0.0"
|
||||||
|
|
||||||
# Include dependency files
|
# Include dependency files
|
||||||
-include $(DEPENDS)
|
-include $(DEPENDS)
|
||||||
|
|
||||||
# Phony targets
|
# Phony targets
|
||||||
.PHONY: all debug static check-deps install uninstall systemd-service sample-config clean distclean format analyze test help
|
.PHONY: all debug static check-deps install uninstall systemd-service version-header sample-config clean distclean format analyze test help bump-patch bump-minor bump-major set-version
|
||||||
|
|
||||||
# Print variables
|
# Print variables
|
||||||
print-%:
|
print-%:
|
||||||
|
|
|
||||||
35
include/chroma.h
vendored
35
include/chroma.h
vendored
|
|
@ -12,7 +12,8 @@
|
||||||
#include <wayland-client.h>
|
#include <wayland-client.h>
|
||||||
#include <wayland-egl.h>
|
#include <wayland-egl.h>
|
||||||
|
|
||||||
#define CHROMA_VERSION "1.0.0"
|
#include "chroma_version.h"
|
||||||
|
|
||||||
#define MAX_OUTPUTS 16
|
#define MAX_OUTPUTS 16
|
||||||
#define MAX_PATH_LEN 4096
|
#define MAX_PATH_LEN 4096
|
||||||
#define CONFIG_FILE_NAME "chroma.conf"
|
#define CONFIG_FILE_NAME "chroma.conf"
|
||||||
|
|
@ -35,6 +36,22 @@ typedef enum {
|
||||||
CHROMA_ERROR_MEMORY = -6
|
CHROMA_ERROR_MEMORY = -6
|
||||||
} chroma_error_t;
|
} chroma_error_t;
|
||||||
|
|
||||||
|
// Scaling modes for wallpaper display
|
||||||
|
typedef enum {
|
||||||
|
CHROMA_SCALE_FILL = 0, // fill entire output, crop if necessary
|
||||||
|
CHROMA_SCALE_FIT = 1, // fit image within output, add borders if needed
|
||||||
|
CHROMA_SCALE_STRETCH = 2, // stretch to fill output, may distort aspect ratio
|
||||||
|
CHROMA_SCALE_CENTER = 3 // center image at original size
|
||||||
|
} chroma_scale_mode_t;
|
||||||
|
|
||||||
|
// Image filtering quality settings
|
||||||
|
typedef enum {
|
||||||
|
CHROMA_FILTER_NEAREST = 0, // nearest neighbor filtering (pixelated)
|
||||||
|
CHROMA_FILTER_LINEAR = 1, // linear filtering (smooth)
|
||||||
|
CHROMA_FILTER_BILINEAR = 2, // bilinear filtering (smoother)
|
||||||
|
CHROMA_FILTER_TRILINEAR = 3 // trilinear filtering (smoothest)
|
||||||
|
} chroma_filter_quality_t;
|
||||||
|
|
||||||
// Image data structure
|
// Image data structure
|
||||||
typedef struct {
|
typedef struct {
|
||||||
unsigned char *data; // RGBA pixel data
|
unsigned char *data; // RGBA pixel data
|
||||||
|
|
@ -70,6 +87,11 @@ typedef struct {
|
||||||
// Associated wallpaper
|
// Associated wallpaper
|
||||||
chroma_image_t *image;
|
chroma_image_t *image;
|
||||||
|
|
||||||
|
// Configuration for this output
|
||||||
|
chroma_scale_mode_t scale_mode;
|
||||||
|
chroma_filter_quality_t filter_quality;
|
||||||
|
bool config_loaded;
|
||||||
|
|
||||||
// OpenGL resource cache
|
// OpenGL resource cache
|
||||||
GLuint texture_id;
|
GLuint texture_id;
|
||||||
GLuint shader_program;
|
GLuint shader_program;
|
||||||
|
|
@ -83,6 +105,8 @@ typedef struct {
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char output_name[256];
|
char output_name[256];
|
||||||
char image_path[MAX_PATH_LEN];
|
char image_path[MAX_PATH_LEN];
|
||||||
|
chroma_scale_mode_t scale_mode;
|
||||||
|
chroma_filter_quality_t filter_quality;
|
||||||
} chroma_config_mapping_t;
|
} chroma_config_mapping_t;
|
||||||
|
|
||||||
// Application configuration
|
// Application configuration
|
||||||
|
|
@ -91,6 +115,10 @@ typedef struct {
|
||||||
int mapping_count;
|
int mapping_count;
|
||||||
char default_image[MAX_PATH_LEN];
|
char default_image[MAX_PATH_LEN];
|
||||||
bool daemon_mode;
|
bool daemon_mode;
|
||||||
|
|
||||||
|
// Global scaling and filtering settings (used as defaults)
|
||||||
|
chroma_scale_mode_t default_scale_mode;
|
||||||
|
chroma_filter_quality_t default_filter_quality;
|
||||||
} chroma_config_t;
|
} chroma_config_t;
|
||||||
|
|
||||||
// Main application state
|
// Main application state
|
||||||
|
|
@ -193,6 +221,11 @@ int chroma_config_load(chroma_config_t *config, const char *config_file);
|
||||||
void chroma_config_free(chroma_config_t *config);
|
void chroma_config_free(chroma_config_t *config);
|
||||||
const char *chroma_config_get_image_for_output(chroma_config_t *config,
|
const char *chroma_config_get_image_for_output(chroma_config_t *config,
|
||||||
const char *output_name);
|
const char *output_name);
|
||||||
|
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);
|
||||||
|
int chroma_config_create_sample(const char *config_file);
|
||||||
|
void chroma_config_print(const chroma_config_t *config);
|
||||||
|
|
||||||
// Main loop and events
|
// Main loop and events
|
||||||
int chroma_run(chroma_state_t *state);
|
int chroma_run(chroma_state_t *state);
|
||||||
|
|
|
||||||
8
include/chroma_version.h
Normal file
8
include/chroma_version.h
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#ifndef CHROMA_VERSION_H
|
||||||
|
#define CHROMA_VERSION_H
|
||||||
|
|
||||||
|
#ifndef CHROMA_VERSION
|
||||||
|
#define CHROMA_VERSION "1.0.1"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // CHROMA_VERSION_H
|
||||||
272
src/config.c
272
src/config.c
|
|
@ -52,11 +52,81 @@ static bool parse_bool(const char *value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse integer value from string
|
// Parse scaling mode from string
|
||||||
|
static chroma_scale_mode_t parse_scale_mode(const char *value) {
|
||||||
|
if (!value)
|
||||||
|
return CHROMA_SCALE_FILL; // default
|
||||||
|
|
||||||
|
if (strcasecmp(value, "fill") == 0) {
|
||||||
|
return CHROMA_SCALE_FILL;
|
||||||
|
} else if (strcasecmp(value, "fit") == 0) {
|
||||||
|
return CHROMA_SCALE_FIT;
|
||||||
|
} else if (strcasecmp(value, "stretch") == 0) {
|
||||||
|
return CHROMA_SCALE_STRETCH;
|
||||||
|
} else if (strcasecmp(value, "center") == 0) {
|
||||||
|
return CHROMA_SCALE_CENTER;
|
||||||
|
}
|
||||||
|
|
||||||
|
chroma_log("WARN", "Unknown scaling mode: %s (using fill)", value);
|
||||||
|
return CHROMA_SCALE_FILL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse filter quality from string
|
||||||
|
static chroma_filter_quality_t parse_filter_quality(const char *value) {
|
||||||
|
if (!value)
|
||||||
|
return CHROMA_FILTER_LINEAR; // default
|
||||||
|
|
||||||
|
if (strcasecmp(value, "nearest") == 0) {
|
||||||
|
return CHROMA_FILTER_NEAREST;
|
||||||
|
} else if (strcasecmp(value, "linear") == 0) {
|
||||||
|
return CHROMA_FILTER_LINEAR;
|
||||||
|
} else if (strcasecmp(value, "bilinear") == 0) {
|
||||||
|
return CHROMA_FILTER_BILINEAR;
|
||||||
|
} else if (strcasecmp(value, "trilinear") == 0) {
|
||||||
|
return CHROMA_FILTER_TRILINEAR;
|
||||||
|
}
|
||||||
|
|
||||||
|
chroma_log("WARN", "Unknown filter quality: %s (using linear)", value);
|
||||||
|
return CHROMA_FILTER_LINEAR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get string representation of scaling mode
|
||||||
|
static const char *scale_mode_to_string(chroma_scale_mode_t mode) {
|
||||||
|
switch (mode) {
|
||||||
|
case CHROMA_SCALE_FILL:
|
||||||
|
return "fill";
|
||||||
|
case CHROMA_SCALE_FIT:
|
||||||
|
return "fit";
|
||||||
|
case CHROMA_SCALE_STRETCH:
|
||||||
|
return "stretch";
|
||||||
|
case CHROMA_SCALE_CENTER:
|
||||||
|
return "center";
|
||||||
|
default:
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get string representation of filter quality
|
||||||
|
static const char *filter_quality_to_string(chroma_filter_quality_t quality) {
|
||||||
|
switch (quality) {
|
||||||
|
case CHROMA_FILTER_NEAREST:
|
||||||
|
return "nearest";
|
||||||
|
case CHROMA_FILTER_LINEAR:
|
||||||
|
return "linear";
|
||||||
|
case CHROMA_FILTER_BILINEAR:
|
||||||
|
return "bilinear";
|
||||||
|
case CHROMA_FILTER_TRILINEAR:
|
||||||
|
return "trilinear";
|
||||||
|
default:
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Output-to-image mapping
|
// Output-to-image mapping
|
||||||
static int add_output_mapping(chroma_config_t *config, const char *output_name,
|
static int add_output_mapping(chroma_config_t *config, const char *output_name,
|
||||||
const char *image_path) {
|
const char *image_path,
|
||||||
|
chroma_scale_mode_t scale_mode,
|
||||||
|
chroma_filter_quality_t filter_quality) {
|
||||||
if (!config || !output_name || !image_path) {
|
if (!config || !output_name || !image_path) {
|
||||||
return CHROMA_ERROR_INIT;
|
return CHROMA_ERROR_INIT;
|
||||||
}
|
}
|
||||||
|
|
@ -67,20 +137,36 @@ static int add_output_mapping(chroma_config_t *config, const char *output_name,
|
||||||
return CHROMA_ERROR_MEMORY;
|
return CHROMA_ERROR_MEMORY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate string lengths to prevent buffer overflow
|
||||||
|
size_t output_len = strlen(output_name);
|
||||||
|
size_t path_len = strlen(image_path);
|
||||||
|
|
||||||
|
if (output_len >= sizeof(config->mappings[0].output_name)) {
|
||||||
|
chroma_log("ERROR", "Output name too long: %s (max %zu)", output_name,
|
||||||
|
sizeof(config->mappings[0].output_name) - 1);
|
||||||
|
return CHROMA_ERROR_CONFIG;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path_len >= sizeof(config->mappings[0].image_path)) {
|
||||||
|
chroma_log("ERROR", "Image path too long: %s (max %zu)", image_path,
|
||||||
|
sizeof(config->mappings[0].image_path) - 1);
|
||||||
|
return CHROMA_ERROR_CONFIG;
|
||||||
|
}
|
||||||
|
|
||||||
chroma_config_mapping_t *mapping = &config->mappings[config->mapping_count];
|
chroma_config_mapping_t *mapping = &config->mappings[config->mapping_count];
|
||||||
|
|
||||||
strncpy(mapping->output_name, output_name, sizeof(mapping->output_name) - 1);
|
strcpy(mapping->output_name, output_name);
|
||||||
mapping->output_name[sizeof(mapping->output_name) - 1] = '\0';
|
strcpy(mapping->image_path, image_path);
|
||||||
|
mapping->scale_mode = scale_mode;
|
||||||
strncpy(mapping->image_path, image_path, sizeof(mapping->image_path) - 1);
|
mapping->filter_quality = filter_quality;
|
||||||
mapping->image_path[sizeof(mapping->image_path) - 1] = '\0';
|
|
||||||
|
|
||||||
config->mapping_count++;
|
config->mapping_count++;
|
||||||
|
|
||||||
chroma_log("DEBUG", "Added mapping: %s -> %s", output_name, image_path);
|
chroma_log("DEBUG", "Added mapping: %s -> %s (scale: %s, filter: %s)",
|
||||||
|
output_name, image_path, scale_mode_to_string(scale_mode),
|
||||||
|
filter_quality_to_string(filter_quality));
|
||||||
chroma_log("TRACE", "Output mapping %d: '%s' -> '%s' (path length: %zu)",
|
chroma_log("TRACE", "Output mapping %d: '%s' -> '%s' (path length: %zu)",
|
||||||
config->mapping_count, output_name, image_path,
|
config->mapping_count, output_name, image_path, path_len);
|
||||||
strlen(image_path));
|
|
||||||
return CHROMA_OK;
|
return CHROMA_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,6 +180,10 @@ static void init_default_config(chroma_config_t *config) {
|
||||||
config->daemon_mode = false;
|
config->daemon_mode = false;
|
||||||
config->mapping_count = 0;
|
config->mapping_count = 0;
|
||||||
|
|
||||||
|
// Set default scaling and filtering
|
||||||
|
config->default_scale_mode = CHROMA_SCALE_FILL;
|
||||||
|
config->default_filter_quality = CHROMA_FILTER_LINEAR;
|
||||||
|
|
||||||
// Set default image path (can be overridden)
|
// Set default image path (can be overridden)
|
||||||
const char *home = getenv("HOME");
|
const char *home = getenv("HOME");
|
||||||
if (home) {
|
if (home) {
|
||||||
|
|
@ -138,11 +228,29 @@ static int parse_config_line(chroma_config_t *config, char *line,
|
||||||
|
|
||||||
// Parse configuration options
|
// Parse configuration options
|
||||||
if (strcasecmp(key, "default_image") == 0) {
|
if (strcasecmp(key, "default_image") == 0) {
|
||||||
strncpy(config->default_image, value, sizeof(config->default_image) - 1);
|
char *expanded_path = chroma_expand_path(value);
|
||||||
config->default_image[sizeof(config->default_image) - 1] = '\0';
|
const char *path_to_use = expanded_path ? expanded_path : value;
|
||||||
chroma_log("DEBUG", "Set default image: %s", value);
|
size_t path_len = strlen(path_to_use);
|
||||||
chroma_log("TRACE", "Default image path set: length=%zu, expanded='%s'",
|
|
||||||
strlen(value), value);
|
if (path_len >= sizeof(config->default_image)) {
|
||||||
|
chroma_log("ERROR", "Default image path too long: %s (max %zu)",
|
||||||
|
path_to_use, sizeof(config->default_image) - 1);
|
||||||
|
if (expanded_path) {
|
||||||
|
free(expanded_path);
|
||||||
|
}
|
||||||
|
return CHROMA_ERROR_CONFIG;
|
||||||
|
}
|
||||||
|
|
||||||
|
strcpy(config->default_image, path_to_use);
|
||||||
|
|
||||||
|
if (expanded_path) {
|
||||||
|
chroma_log("DEBUG", "Set default image: %s -> %s", value, expanded_path);
|
||||||
|
chroma_log("TRACE", "Default image path set: length=%zu, expanded='%s'",
|
||||||
|
path_len, expanded_path);
|
||||||
|
free(expanded_path);
|
||||||
|
} else {
|
||||||
|
chroma_log("WARN", "Failed to expand path, using original: %s", value);
|
||||||
|
}
|
||||||
} else if (strcasecmp(key, "daemon") == 0 ||
|
} else if (strcasecmp(key, "daemon") == 0 ||
|
||||||
strcasecmp(key, "daemon_mode") == 0) {
|
strcasecmp(key, "daemon_mode") == 0) {
|
||||||
config->daemon_mode = parse_bool(value);
|
config->daemon_mode = parse_bool(value);
|
||||||
|
|
@ -151,6 +259,23 @@ static int parse_config_line(chroma_config_t *config, char *line,
|
||||||
chroma_log("TRACE",
|
chroma_log("TRACE",
|
||||||
"Daemon mode configuration: key='%s', value='%s', parsed=%s",
|
"Daemon mode configuration: key='%s', value='%s', parsed=%s",
|
||||||
key, value, config->daemon_mode ? "true" : "false");
|
key, value, config->daemon_mode ? "true" : "false");
|
||||||
|
} else if (strcasecmp(key, "scale_mode") == 0 ||
|
||||||
|
strcasecmp(key, "default_scale_mode") == 0) {
|
||||||
|
config->default_scale_mode = parse_scale_mode(value);
|
||||||
|
chroma_log("DEBUG", "Set default scale mode: %s",
|
||||||
|
scale_mode_to_string(config->default_scale_mode));
|
||||||
|
chroma_log("TRACE",
|
||||||
|
"Scale mode configuration: key='%s', value='%s', parsed=%s", key,
|
||||||
|
value, scale_mode_to_string(config->default_scale_mode));
|
||||||
|
} else if (strcasecmp(key, "filter_quality") == 0 ||
|
||||||
|
strcasecmp(key, "default_filter_quality") == 0) {
|
||||||
|
config->default_filter_quality = parse_filter_quality(value);
|
||||||
|
chroma_log("DEBUG", "Set default filter quality: %s",
|
||||||
|
filter_quality_to_string(config->default_filter_quality));
|
||||||
|
chroma_log("TRACE",
|
||||||
|
"Filter quality configuration: key='%s', value='%s', parsed=%s",
|
||||||
|
key, value,
|
||||||
|
filter_quality_to_string(config->default_filter_quality));
|
||||||
} else if (strncasecmp(key, "output.", 7) == 0) {
|
} else if (strncasecmp(key, "output.", 7) == 0) {
|
||||||
// Output-specific mapping: e.g., output.DP-1=/path/to/image.jpg
|
// Output-specific mapping: e.g., output.DP-1=/path/to/image.jpg
|
||||||
const char *output_name = key + 7;
|
const char *output_name = key + 7;
|
||||||
|
|
@ -160,18 +285,74 @@ static int parse_config_line(chroma_config_t *config, char *line,
|
||||||
return CHROMA_OK;
|
return CHROMA_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate image path
|
// Check for extended output configuration with properties
|
||||||
if (chroma_image_validate(value) != CHROMA_OK) {
|
// Format: output.DP-1.scale = fill
|
||||||
chroma_log("WARN", "Invalid image path for output %s: %s", output_name,
|
// Format: output.DP-1.filter = linear
|
||||||
value);
|
char *dot = strchr(output_name, '.');
|
||||||
|
if (dot) {
|
||||||
|
// This is an output property (scale or filter)
|
||||||
|
*dot = '\0';
|
||||||
|
const char *property = dot + 1;
|
||||||
|
|
||||||
|
// Find existing mapping for this output
|
||||||
|
chroma_config_mapping_t *mapping = NULL;
|
||||||
|
for (int i = 0; i < config->mapping_count; i++) {
|
||||||
|
if (strcmp(config->mappings[i].output_name, output_name) == 0) {
|
||||||
|
mapping = &config->mappings[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mapping) {
|
||||||
|
chroma_log("WARN", "Output %s not found for property %s (line %d)",
|
||||||
|
output_name, property, line_number);
|
||||||
|
return CHROMA_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcasecmp(property, "scale") == 0) {
|
||||||
|
mapping->scale_mode = parse_scale_mode(value);
|
||||||
|
chroma_log("DEBUG", "Set scale mode for output %s: %s", output_name,
|
||||||
|
scale_mode_to_string(mapping->scale_mode));
|
||||||
|
} else if (strcasecmp(property, "filter") == 0) {
|
||||||
|
mapping->filter_quality = parse_filter_quality(value);
|
||||||
|
chroma_log("DEBUG", "Set filter quality for output %s: %s", output_name,
|
||||||
|
filter_quality_to_string(mapping->filter_quality));
|
||||||
|
} else {
|
||||||
|
chroma_log("WARN", "Unknown output property: %s (line %d)", property,
|
||||||
|
line_number);
|
||||||
|
}
|
||||||
|
|
||||||
return CHROMA_OK;
|
return CHROMA_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (add_output_mapping(config, output_name, value) != CHROMA_OK) {
|
// Expand path before validation and storage
|
||||||
|
char *expanded_path = chroma_expand_path(value);
|
||||||
|
const char *path_to_validate = expanded_path ? expanded_path : value;
|
||||||
|
|
||||||
|
// Validate image path
|
||||||
|
if (chroma_image_validate(path_to_validate) != CHROMA_OK) {
|
||||||
|
chroma_log("WARN", "Invalid image path for output %s: %s (expanded: %s)",
|
||||||
|
output_name, value, path_to_validate);
|
||||||
|
if (expanded_path) {
|
||||||
|
free(expanded_path);
|
||||||
|
}
|
||||||
|
return CHROMA_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (add_output_mapping(config, output_name, path_to_validate,
|
||||||
|
config->default_scale_mode,
|
||||||
|
config->default_filter_quality) != CHROMA_OK) {
|
||||||
chroma_log("ERROR", "Failed to add output mapping: %s -> %s", output_name,
|
chroma_log("ERROR", "Failed to add output mapping: %s -> %s", output_name,
|
||||||
value);
|
path_to_validate);
|
||||||
|
if (expanded_path) {
|
||||||
|
free(expanded_path);
|
||||||
|
}
|
||||||
return CHROMA_ERROR_CONFIG;
|
return CHROMA_ERROR_CONFIG;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (expanded_path) {
|
||||||
|
free(expanded_path);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
chroma_log("WARN", "Unknown configuration key line %d: %s", line_number,
|
chroma_log("WARN", "Unknown configuration key line %d: %s", line_number,
|
||||||
key);
|
key);
|
||||||
|
|
@ -303,6 +484,37 @@ const char *chroma_config_get_image_for_output(chroma_config_t *config,
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get configuration mapping for output, including scale mode and filter
|
||||||
|
// quality
|
||||||
|
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) {
|
||||||
|
if (!config || !output_name || !scale_mode || !filter_quality) {
|
||||||
|
return CHROMA_ERROR_INIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look for specific output mapping
|
||||||
|
for (int i = 0; i < config->mapping_count; i++) {
|
||||||
|
if (strcmp(config->mappings[i].output_name, output_name) == 0) {
|
||||||
|
*scale_mode = config->mappings[i].scale_mode;
|
||||||
|
*filter_quality = config->mappings[i].filter_quality;
|
||||||
|
chroma_log("DEBUG",
|
||||||
|
"Found specific mapping for output %s: scale=%s, filter=%s",
|
||||||
|
output_name, scale_mode_to_string(*scale_mode),
|
||||||
|
filter_quality_to_string(*filter_quality));
|
||||||
|
return CHROMA_OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return defaults if no specific mapping found
|
||||||
|
*scale_mode = config->default_scale_mode;
|
||||||
|
*filter_quality = config->default_filter_quality;
|
||||||
|
chroma_log("DEBUG", "Using defaults for output %s: scale=%s, filter=%s",
|
||||||
|
output_name, scale_mode_to_string(*scale_mode),
|
||||||
|
filter_quality_to_string(*filter_quality));
|
||||||
|
return CHROMA_OK;
|
||||||
|
}
|
||||||
|
|
||||||
// Create a sample configuration file
|
// Create a sample configuration file
|
||||||
int chroma_config_create_sample(const char *config_file) {
|
int chroma_config_create_sample(const char *config_file) {
|
||||||
if (!config_file) {
|
if (!config_file) {
|
||||||
|
|
@ -346,14 +558,24 @@ void chroma_config_print(const chroma_config_t *config) {
|
||||||
chroma_log("INFO", "=== Configuration ===");
|
chroma_log("INFO", "=== Configuration ===");
|
||||||
chroma_log("INFO", "Default image: %s", config->default_image);
|
chroma_log("INFO", "Default image: %s", config->default_image);
|
||||||
chroma_log("INFO", "Daemon mode: %s", config->daemon_mode ? "true" : "false");
|
chroma_log("INFO", "Daemon mode: %s", config->daemon_mode ? "true" : "false");
|
||||||
|
chroma_log("INFO", "Default scale mode: %s",
|
||||||
|
scale_mode_to_string(config->default_scale_mode));
|
||||||
|
chroma_log("INFO", "Default filter quality: %s",
|
||||||
|
filter_quality_to_string(config->default_filter_quality));
|
||||||
chroma_log("INFO", "Output mappings: %d", config->mapping_count);
|
chroma_log("INFO", "Output mappings: %d", config->mapping_count);
|
||||||
|
|
||||||
for (int i = 0; i < config->mapping_count; i++) {
|
for (int i = 0; i < config->mapping_count; i++) {
|
||||||
chroma_log("INFO", " %s -> %s", config->mappings[i].output_name,
|
chroma_log("INFO", " %s -> %s (scale: %s, filter: %s)",
|
||||||
config->mappings[i].image_path);
|
config->mappings[i].output_name, config->mappings[i].image_path,
|
||||||
|
scale_mode_to_string(config->mappings[i].scale_mode),
|
||||||
|
filter_quality_to_string(config->mappings[i].filter_quality));
|
||||||
chroma_log(
|
chroma_log(
|
||||||
"TRACE", " Mapping %d: output='%s', image='%s', path_exists=%s", i,
|
"TRACE",
|
||||||
config->mappings[i].output_name, config->mappings[i].image_path,
|
" Mapping %d: output='%s', image='%s', scale='%s', filter='%s', "
|
||||||
|
"path_exists=%s",
|
||||||
|
i, config->mappings[i].output_name, config->mappings[i].image_path,
|
||||||
|
scale_mode_to_string(config->mappings[i].scale_mode),
|
||||||
|
filter_quality_to_string(config->mappings[i].filter_quality),
|
||||||
chroma_path_exists(config->mappings[i].image_path) ? "yes" : "no");
|
chroma_path_exists(config->mappings[i].image_path) ? "yes" : "no");
|
||||||
}
|
}
|
||||||
chroma_log("INFO", "====================");
|
chroma_log("INFO", "====================");
|
||||||
|
|
|
||||||
26
src/core.c
26
src/core.c
|
|
@ -103,6 +103,32 @@ static int assign_wallpaper_to_output(chroma_state_t *state,
|
||||||
// Assign image to output
|
// Assign image to output
|
||||||
output->image = image;
|
output->image = image;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
bool had_config = output->config_loaded;
|
||||||
|
|
||||||
|
// Load configuration for this output (scale mode and filter quality)
|
||||||
|
if (chroma_config_get_mapping_for_output(
|
||||||
|
&state->config, output->name ? output->name : "unknown",
|
||||||
|
&output->scale_mode, &output->filter_quality) == CHROMA_OK) {
|
||||||
|
output->config_loaded = true;
|
||||||
|
chroma_log("DEBUG", "Loaded config for output %u: scale=%d, filter=%d",
|
||||||
|
output->id, output->scale_mode, output->filter_quality);
|
||||||
|
|
||||||
|
// Check if configuration changed and invalidate texture if needed
|
||||||
|
if (had_config && (old_scale_mode != output->scale_mode ||
|
||||||
|
old_filter_quality != output->filter_quality)) {
|
||||||
|
chroma_output_invalidate_texture(output);
|
||||||
|
chroma_log("DEBUG",
|
||||||
|
"Configuration changed for output %u, invalidated texture",
|
||||||
|
output->id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
output->config_loaded = false;
|
||||||
|
chroma_log("WARN", "Failed to load config for output %u", output->id);
|
||||||
|
}
|
||||||
|
|
||||||
// Create surface if it doesn't exist
|
// Create surface if it doesn't exist
|
||||||
if (!output->surface) {
|
if (!output->surface) {
|
||||||
int ret = chroma_surface_create(state, output);
|
int ret = chroma_surface_create(state, output);
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <strings.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
#include "../include/chroma.h"
|
#include "../include/chroma.h"
|
||||||
|
|
|
||||||
175
src/render.c
175
src/render.c
|
|
@ -8,6 +8,146 @@
|
||||||
#include "../include/chroma.h"
|
#include "../include/chroma.h"
|
||||||
#include "../include/stb_image.h"
|
#include "../include/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) {
|
||||||
|
switch (quality) {
|
||||||
|
case CHROMA_FILTER_NEAREST:
|
||||||
|
*min_filter = GL_NEAREST;
|
||||||
|
*mag_filter = GL_NEAREST;
|
||||||
|
break;
|
||||||
|
case CHROMA_FILTER_LINEAR:
|
||||||
|
*min_filter = GL_LINEAR;
|
||||||
|
*mag_filter = GL_LINEAR;
|
||||||
|
break;
|
||||||
|
case CHROMA_FILTER_BILINEAR:
|
||||||
|
*min_filter = GL_LINEAR_MIPMAP_LINEAR;
|
||||||
|
*mag_filter = GL_LINEAR;
|
||||||
|
break;
|
||||||
|
case CHROMA_FILTER_TRILINEAR:
|
||||||
|
*min_filter = GL_LINEAR_MIPMAP_LINEAR;
|
||||||
|
*mag_filter = GL_LINEAR;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
*min_filter = GL_LINEAR;
|
||||||
|
*mag_filter = GL_LINEAR;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate texture coordinates based on scaling mode
|
||||||
|
static void calculate_texture_coords(chroma_scale_mode_t scale_mode,
|
||||||
|
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 / image_height;
|
||||||
|
float output_aspect = (float)output_width / 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 =
|
||||||
|
(image_height - visible_height) / (2.0f * 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 = (image_width - visible_width) / (2.0f * 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 / image_height;
|
||||||
|
float output_aspect = (float)output_width / 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 =
|
||||||
|
(output_height - scaled_height) / (2.0f * 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 = (output_width - scaled_width) / (2.0f * 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 / image_height;
|
||||||
|
float output_aspect = (float)output_width / output_height;
|
||||||
|
|
||||||
|
if (image_aspect > output_aspect) {
|
||||||
|
// Image is wider - crop left/right
|
||||||
|
float crop_width = image_height * output_aspect;
|
||||||
|
float u_crop = (image_width - crop_width) / (2.0f * 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 = image_width / output_aspect;
|
||||||
|
float v_crop = (image_height - crop_height) / (2.0f * image_height);
|
||||||
|
u1 = 0.0f;
|
||||||
|
v1 = v_crop;
|
||||||
|
u2 = 1.0f;
|
||||||
|
v2 = 1.0f - v_crop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
// Vertex shader for simple texture rendering
|
||||||
static const char *vertex_shader_source =
|
static const char *vertex_shader_source =
|
||||||
"#version 120\n"
|
"#version 120\n"
|
||||||
|
|
@ -125,7 +265,7 @@ static int init_gl_resources(chroma_output_t *output) {
|
||||||
glGenBuffers(1, &output->ebo);
|
glGenBuffers(1, &output->ebo);
|
||||||
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, output->vbo);
|
glBindBuffer(GL_ARRAY_BUFFER, output->vbo);
|
||||||
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
|
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_DYNAMIC_DRAW);
|
||||||
|
|
||||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, output->ebo);
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, output->ebo);
|
||||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices,
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices,
|
||||||
|
|
@ -140,7 +280,8 @@ static int init_gl_resources(chroma_output_t *output) {
|
||||||
|
|
||||||
// Create or update texture from image data
|
// Create or update texture from image data
|
||||||
static int update_texture_from_image(chroma_output_t *output,
|
static int update_texture_from_image(chroma_output_t *output,
|
||||||
chroma_image_t *image) {
|
chroma_image_t *image,
|
||||||
|
chroma_filter_quality_t filter_quality) {
|
||||||
if (!output || !image || !image->loaded) {
|
if (!output || !image || !image->loaded) {
|
||||||
return CHROMA_ERROR_INIT;
|
return CHROMA_ERROR_INIT;
|
||||||
}
|
}
|
||||||
|
|
@ -180,8 +321,12 @@ static int update_texture_from_image(chroma_output_t *output,
|
||||||
// Set texture parameters
|
// Set texture parameters
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
// Use configured filter quality
|
||||||
|
GLint min_filter, mag_filter;
|
||||||
|
get_gl_filter_params(filter_quality, &min_filter, &mag_filter);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter);
|
||||||
|
|
||||||
// Upload texture data (always RGBA now)
|
// Upload texture data (always RGBA now)
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image->width, image->height, 0,
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image->width, image->height, 0,
|
||||||
|
|
@ -503,7 +648,8 @@ int chroma_render_wallpaper(chroma_state_t *state, chroma_output_t *output) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (output->texture_id == 0) {
|
if (output->texture_id == 0) {
|
||||||
if (update_texture_from_image(output, output->image) != CHROMA_OK) {
|
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);
|
chroma_log("ERROR", "Failed to update texture for output %u", output->id);
|
||||||
return CHROMA_ERROR_EGL;
|
return CHROMA_ERROR_EGL;
|
||||||
}
|
}
|
||||||
|
|
@ -524,8 +670,25 @@ int chroma_render_wallpaper(chroma_state_t *state, chroma_output_t *output) {
|
||||||
glBindTexture(GL_TEXTURE_2D, output->texture_id);
|
glBindTexture(GL_TEXTURE_2D, output->texture_id);
|
||||||
glUniform1i(glGetUniformLocation(output->shader_program, "texture"), 0);
|
glUniform1i(glGetUniformLocation(output->shader_program, "texture"), 0);
|
||||||
|
|
||||||
// Use cached VBO/EBO
|
// Calculate texture coordinates based on scaling mode
|
||||||
|
float tex_coords[8];
|
||||||
|
calculate_texture_coords(output->scale_mode, 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);
|
glBindBuffer(GL_ARRAY_BUFFER, output->vbo);
|
||||||
|
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(dynamic_vertices),
|
||||||
|
dynamic_vertices);
|
||||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, output->ebo);
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, output->ebo);
|
||||||
|
|
||||||
// Set vertex attributes
|
// Set vertex attributes
|
||||||
|
|
|
||||||
148
src/utils.c
148
src/utils.c
|
|
@ -94,18 +94,138 @@ void chroma_cleanup_signals(void) {
|
||||||
g_config_file = NULL;
|
g_config_file = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expand tilde in path
|
// Expand environment variables in a string
|
||||||
|
static char *expand_env_vars(const char *str) {
|
||||||
|
if (!str || strchr(str, '$') == NULL) {
|
||||||
|
return strdup(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *result = strdup("");
|
||||||
|
if (!result) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *p = str;
|
||||||
|
while (*p) {
|
||||||
|
if (*p == '$') {
|
||||||
|
p++;
|
||||||
|
if (*p == '{') {
|
||||||
|
// ${VAR} format
|
||||||
|
p++;
|
||||||
|
const char *end = strchr(p, '}');
|
||||||
|
if (!end) {
|
||||||
|
// No closing brace, treat as literal
|
||||||
|
char *tmp = realloc(result, strlen(result) + 2);
|
||||||
|
if (!tmp) {
|
||||||
|
free(result);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
result = tmp;
|
||||||
|
strcat(result, "${");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t var_len = end - p;
|
||||||
|
char *var_name = malloc(var_len + 1);
|
||||||
|
if (!var_name) {
|
||||||
|
free(result);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
strncpy(var_name, p, var_len);
|
||||||
|
var_name[var_len] = '\0';
|
||||||
|
|
||||||
|
const char *var_value = getenv(var_name);
|
||||||
|
if (var_value) {
|
||||||
|
char *tmp = realloc(result, strlen(result) + strlen(var_value) + 1);
|
||||||
|
if (!tmp) {
|
||||||
|
free(var_name);
|
||||||
|
free(result);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
result = tmp;
|
||||||
|
strcat(result, var_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(var_name);
|
||||||
|
p = end + 1;
|
||||||
|
} else {
|
||||||
|
// $VAR format
|
||||||
|
const char *start = p;
|
||||||
|
while (*p && (isalnum((unsigned char)*p) || *p == '_')) {
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p == start) {
|
||||||
|
// Not a valid variable name, treat $ as literal
|
||||||
|
char *tmp = realloc(result, strlen(result) + 2);
|
||||||
|
if (!tmp) {
|
||||||
|
free(result);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
result = tmp;
|
||||||
|
strcat(result, "$");
|
||||||
|
} else {
|
||||||
|
size_t var_len = p - start;
|
||||||
|
char *var_name = malloc(var_len + 1);
|
||||||
|
if (!var_name) {
|
||||||
|
free(result);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
strncpy(var_name, start, var_len);
|
||||||
|
var_name[var_len] = '\0';
|
||||||
|
|
||||||
|
const char *var_value = getenv(var_name);
|
||||||
|
if (var_value) {
|
||||||
|
char *tmp = realloc(result, strlen(result) + strlen(var_value) + 1);
|
||||||
|
if (!tmp) {
|
||||||
|
free(var_name);
|
||||||
|
free(result);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
result = tmp;
|
||||||
|
strcat(result, var_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(var_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Regular character
|
||||||
|
size_t len = strlen(result);
|
||||||
|
char *tmp = realloc(result, len + 2);
|
||||||
|
if (!tmp) {
|
||||||
|
free(result);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
result = tmp;
|
||||||
|
result[len] = *p;
|
||||||
|
result[len + 1] = '\0';
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expand tilde and environment variables in path
|
||||||
char *chroma_expand_path(const char *path) {
|
char *chroma_expand_path(const char *path) {
|
||||||
if (!path) {
|
if (!path) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path[0] != '~') {
|
// First expand environment variables
|
||||||
return strdup(path);
|
char *env_expanded = expand_env_vars(path);
|
||||||
|
if (!env_expanded) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then expand tilde if present
|
||||||
|
if (env_expanded[0] != '~') {
|
||||||
|
return env_expanded;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *home;
|
const char *home;
|
||||||
if (path[1] == '/' || path[1] == '\0') {
|
if (env_expanded[1] == '/' || env_expanded[1] == '\0') {
|
||||||
// ~/... or just ~
|
// ~/... or just ~
|
||||||
home = getenv("HOME");
|
home = getenv("HOME");
|
||||||
if (!home) {
|
if (!home) {
|
||||||
|
|
@ -116,34 +236,39 @@ char *chroma_expand_path(const char *path) {
|
||||||
}
|
}
|
||||||
if (!home) {
|
if (!home) {
|
||||||
chroma_log("ERROR", "Could not determine home directory");
|
chroma_log("ERROR", "Could not determine home directory");
|
||||||
|
free(env_expanded);
|
||||||
return strdup(path); // Return original path as fallback
|
return strdup(path); // Return original path as fallback
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t home_len = strlen(home);
|
size_t home_len = strlen(home);
|
||||||
size_t path_len = strlen(path);
|
size_t path_len = strlen(env_expanded);
|
||||||
char *expanded = malloc(home_len + path_len); // -1 for ~ +1 for \0
|
char *expanded = malloc(home_len + path_len); // -1 for ~ +1 for \0
|
||||||
if (!expanded) {
|
if (!expanded) {
|
||||||
chroma_log("ERROR", "Failed to allocate memory for path expansion");
|
chroma_log("ERROR", "Failed to allocate memory for path expansion");
|
||||||
|
free(env_expanded);
|
||||||
return strdup(path);
|
return strdup(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
strcpy(expanded, home);
|
strcpy(expanded, home);
|
||||||
if (path[1] == '/') {
|
if (env_expanded[1] == '/') {
|
||||||
strcat(expanded, path + 1);
|
strcat(expanded, env_expanded + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
free(env_expanded);
|
||||||
return expanded;
|
return expanded;
|
||||||
} else {
|
} else {
|
||||||
// ~user/...
|
// ~user/...
|
||||||
const char *slash = strchr(path, '/');
|
const char *slash = strchr(env_expanded, '/');
|
||||||
size_t user_len = slash ? (size_t)(slash - path - 1) : strlen(path) - 1;
|
size_t user_len =
|
||||||
|
slash ? (size_t)(slash - env_expanded - 1) : strlen(env_expanded) - 1;
|
||||||
|
|
||||||
char *username = malloc(user_len + 1);
|
char *username = malloc(user_len + 1);
|
||||||
if (!username) {
|
if (!username) {
|
||||||
|
free(env_expanded);
|
||||||
return strdup(path);
|
return strdup(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
strncpy(username, path + 1, user_len);
|
strncpy(username, env_expanded + 1, user_len);
|
||||||
username[user_len] = '\0';
|
username[user_len] = '\0';
|
||||||
|
|
||||||
struct passwd *pw = getpwnam(username);
|
struct passwd *pw = getpwnam(username);
|
||||||
|
|
@ -151,6 +276,7 @@ char *chroma_expand_path(const char *path) {
|
||||||
if (!pw) {
|
if (!pw) {
|
||||||
chroma_log("ERROR", "User not found: %s", username);
|
chroma_log("ERROR", "User not found: %s", username);
|
||||||
free(username);
|
free(username);
|
||||||
|
free(env_expanded);
|
||||||
return strdup(path);
|
return strdup(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -160,6 +286,7 @@ char *chroma_expand_path(const char *path) {
|
||||||
size_t remaining_len = slash ? strlen(slash) : 0;
|
size_t remaining_len = slash ? strlen(slash) : 0;
|
||||||
char *expanded = malloc(home_len + remaining_len + 1);
|
char *expanded = malloc(home_len + remaining_len + 1);
|
||||||
if (!expanded) {
|
if (!expanded) {
|
||||||
|
free(env_expanded);
|
||||||
return strdup(path);
|
return strdup(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -168,6 +295,7 @@ char *chroma_expand_path(const char *path) {
|
||||||
strcat(expanded, slash);
|
strcat(expanded, slash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
free(env_expanded);
|
||||||
return expanded;
|
return expanded;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue