chroma/src/core.c
amr 1f9050b69e fix: this solves the warning during compilation
This not only solves the warning/error brought up during compilation, it's also just a sort of belt'n'braces approach to make absolutely sure the timestamp length is correct and didn't get truncated or some other error. This was a new warning/error introduced back in gcc 7.1, and it's one of those "you *should* do it this way because it's technically the correct way" things that most developers roll their eyes at but, logically, they're technically correct.
2025-11-02 08:31:16 +00:00

575 lines
16 KiB
C

#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
#include "../include/chroma.h"
// Global logging level (using CHROMA_LOG_* constants)
static int log_level = CHROMA_LOG_WARN; // Default to WARN and ERROR only
// Initialize chroma state
int chroma_init(chroma_state_t *state) {
if (!state) {
return CHROMA_ERROR_INIT;
}
// Initialize all fields to zero
memset(state, 0, sizeof(chroma_state_t));
// 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();
state->initialized = true;
chroma_log("INFO", "Chroma state initialized");
chroma_log_resource_allocation("chroma_state", sizeof(chroma_state_t),
"main application state");
return CHROMA_OK;
}
// Cleanup chroma state
void chroma_cleanup(chroma_state_t *state) {
if (!state || !state->initialized) {
return;
}
chroma_log("INFO", "Cleaning up chroma state");
chroma_log_memory_stats("pre-cleanup");
// Stop the main loop
state->running = false;
// Clean up all images
chroma_images_cleanup(state);
// Clean up EGL
chroma_egl_cleanup(state);
// Clean up Wayland
chroma_wayland_disconnect(state);
// Clean up configuration
chroma_config_free(&state->config);
state->initialized = false;
chroma_log_resource_deallocation("chroma_state", sizeof(chroma_state_t),
"main application state");
chroma_log("INFO", "Chroma cleanup complete");
}
// Assign wallpaper to an output
static int assign_wallpaper_to_output(chroma_state_t *state,
chroma_output_t *output) {
if (!state || !output || !output->active) {
return CHROMA_ERROR_INIT;
}
// Get image path for this output
const char *image_path = chroma_config_get_image_for_output(
&state->config, output->name ? output->name : "unknown");
if (!image_path) {
chroma_log("WARN", "No wallpaper configured for output %u (%s)", output->id,
output->name ? output->name : "unknown");
return CHROMA_ERROR_CONFIG;
}
// Load or get cached image
chroma_image_t *image = chroma_image_get_or_load(state, image_path);
if (!image) {
chroma_log("ERROR", "Failed to load image for output %u: %s", output->id,
image_path);
return CHROMA_ERROR_IMAGE;
}
// Check if image changed and invalidate texture cache if neceessary
bool image_changed = (output->image != image);
if (image_changed && output->image) {
chroma_output_invalidate_texture(output);
chroma_log("DEBUG", "Image changed for output %u, invalidated texture",
output->id);
}
// Assign image to output
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
if (!output->surface) {
int ret = chroma_surface_create(state, output);
if (ret != CHROMA_OK) {
chroma_log("ERROR", "Failed to create surface for output %u", output->id);
return ret;
}
}
// Render wallpaper
int ret = chroma_render_wallpaper(state, output);
if (ret != CHROMA_OK) {
chroma_log("ERROR", "Failed to render wallpaper for output %u", output->id);
return ret;
}
chroma_log("INFO", "Assigned wallpaper to output %u (%s): %s", output->id,
output->name ? output->name : "unknown", image_path);
chroma_log_memory_stats("post-wallpaper-assignment");
return CHROMA_OK;
}
// Assign wallpapers to all active outputs
static int assign_wallpapers_to_all_outputs(chroma_state_t *state) {
if (!state) {
return CHROMA_ERROR_INIT;
}
int success_count = 0;
int error_count = 0;
for (int i = 0; i < state->output_count; i++) {
chroma_output_t *output = &state->outputs[i];
if (!output->active) {
chroma_log("DEBUG", "Skipping inactive output %u", output->id);
continue;
}
if (assign_wallpaper_to_output(state, output) == CHROMA_OK) {
success_count++;
} else {
error_count++;
}
}
chroma_log("INFO", "Wallpaper assignment complete: %d success, %d errors",
success_count, error_count);
return (success_count > 0) ? CHROMA_OK : CHROMA_ERROR_IMAGE;
}
// Handle output configuration complete event
void handle_output_done(chroma_state_t *state, chroma_output_t *output) {
if (!state || !output) {
return;
}
chroma_log("INFO",
"Output %u (%s) configuration complete: %dx%d@%d, scale=%d",
output->id, output->name ? output->name : "unknown", output->width,
output->height, 0, output->scale);
/* Assign wallpaper to this output */
if (assign_wallpaper_to_output(state, output) != CHROMA_OK) {
chroma_log("ERROR", "Failed to assign wallpaper to output %u", output->id);
}
}
// Process Wayland events
static int process_wayland_events(chroma_state_t *state) {
if (!state || !state->display) {
return CHROMA_ERROR_WAYLAND;
}
// Dispatch pending events
if (wl_display_dispatch_pending(state->display) == -1) {
chroma_log("ERROR", "Failed to dispatch pending Wayland events: %s",
strerror(errno));
return CHROMA_ERROR_WAYLAND;
}
// Read events from the server
if (wl_display_read_events(state->display) == -1) {
chroma_log("ERROR", "Failed to read Wayland events: %s", strerror(errno));
return CHROMA_ERROR_WAYLAND;
}
// Dispatch the read events
if (wl_display_dispatch_pending(state->display) == -1) {
chroma_log("ERROR", "Failed to dispatch read Wayland events: %s",
strerror(errno));
return CHROMA_ERROR_WAYLAND;
}
return CHROMA_OK;
}
// Main event loop
int chroma_run(chroma_state_t *state) {
if (!state || !state->initialized) {
return CHROMA_ERROR_INIT;
}
chroma_log("INFO", "Starting main event loop");
chroma_log_memory_stats("main-loop-start");
state->running = true;
// Initial wallpaper assignment
chroma_log("INFO", "Performing initial wallpaper assignment");
assign_wallpapers_to_all_outputs(state);
// Main event loop
while (state->running && !chroma_should_quit) {
// Dispatch any pending events first
if (wl_display_dispatch_pending(state->display) == -1) {
chroma_log("ERROR", "Failed to dispatch pending events: %s",
strerror(errno));
break;
}
// Prepare to read events
if (wl_display_prepare_read(state->display) == -1) {
chroma_log("ERROR", "Failed to prepare Wayland display for reading");
break;
}
// Flush outgoing requests
if (wl_display_flush(state->display) == -1) {
chroma_log("ERROR", "Failed to flush Wayland display: %s",
strerror(errno));
wl_display_cancel_read(state->display);
break;
}
// Get the display file descriptor
int fd = wl_display_get_fd(state->display);
if (fd == -1) {
chroma_log("ERROR", "Failed to get Wayland display file descriptor");
wl_display_cancel_read(state->display);
break;
}
// Use select() to wait for events with timeout
fd_set readfds;
struct timeval timeout;
FD_ZERO(&readfds);
FD_SET(fd, &readfds);
timeout.tv_sec = 1; // 1s timeout
timeout.tv_usec = 0;
int select_result = select(fd + 1, &readfds, NULL, NULL, &timeout);
if (select_result == -1) {
if (errno == EINTR) {
// Interrupted by signal, check if we should quit
wl_display_cancel_read(state->display);
continue;
}
chroma_log("ERROR", "select() failed: %s", strerror(errno));
wl_display_cancel_read(state->display);
break;
}
if (select_result == 0) {
// Timeout - no events available
wl_display_cancel_read(state->display);
continue;
}
// Events are available
if (FD_ISSET(fd, &readfds)) {
if (process_wayland_events(state) != CHROMA_OK) {
break;
}
} else {
wl_display_cancel_read(state->display);
}
}
state->running = false;
chroma_log("INFO", "Main event loop ended");
return CHROMA_OK;
}
// Error code to string conversion
const char *chroma_error_string(chroma_error_t error) {
switch (error) {
case CHROMA_OK:
return "Success";
case CHROMA_ERROR_INIT:
return "Initialization error";
case CHROMA_ERROR_WAYLAND:
return "Wayland error";
case CHROMA_ERROR_EGL:
return "EGL error";
case CHROMA_ERROR_IMAGE:
return "Image loading error";
case CHROMA_ERROR_CONFIG:
return "Configuration error";
case CHROMA_ERROR_MEMORY:
return "Memory allocation error";
default:
return "Unknown error";
}
}
// Convert log level string to numeric level
static int log_level_from_string(const char *level) {
if (strcmp(level, "ERROR") == 0)
return CHROMA_LOG_ERROR;
if (strcmp(level, "WARN") == 0)
return CHROMA_LOG_WARN;
if (strcmp(level, "INFO") == 0)
return CHROMA_LOG_INFO;
if (strcmp(level, "DEBUG") == 0)
return CHROMA_LOG_DEBUG;
if (strcmp(level, "TRACE") == 0)
return CHROMA_LOG_TRACE;
return CHROMA_LOG_ERROR; // default to ERROR for unknown levels
}
// Logging function with level filtering
void chroma_log(const char *level, const char *format, ...) {
int msg_level = log_level_from_string(level);
if (msg_level > log_level) {
return;
}
va_list args;
char timestamp[32];
int truncation_check = 0;
struct timeval tv;
struct tm *tm_info;
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));
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);
va_start(args, format);
vprintf(format, args);
va_end(args);
printf("\n");
fflush(stdout);
}
// Set log level
void chroma_set_log_level(int level) { log_level = level; }
// Get log level
int chroma_get_log_level(void) { return log_level; }
// Handle configuration reload (SIGHUP)
int chroma_reload_config(chroma_state_t *state, const char *config_file) {
if (!state) {
return CHROMA_ERROR_INIT;
}
chroma_log("INFO", "Reloading configuration");
// Free current configuration
chroma_config_free(&state->config);
// Load new configuration
int ret = chroma_config_load(&state->config, config_file);
if (ret != CHROMA_OK) {
chroma_log("ERROR", "Failed to reload configuration: %s",
chroma_error_string(ret));
return ret;
}
// Reassign wallpapers with new configuration
ret = assign_wallpapers_to_all_outputs(state);
if (ret != CHROMA_OK) {
chroma_log("ERROR", "Failed to reassign wallpapers after config reload");
return ret;
}
chroma_log("INFO", "Configuration reloaded successfully");
return CHROMA_OK;
}
// Check if an output needs wallpaper update
static bool output_needs_update(chroma_output_t *output) {
if (!output || !output->active) {
return false;
}
// Check if output has no surface or image assigned
if (!output->surface || !output->image) {
return true;
}
// Check if image is no longer loaded
if (!output->image->loaded) {
return true;
}
return false;
}
// Update outputs that need wallpaper refresh
int chroma_update_outputs(chroma_state_t *state) {
if (!state) {
return CHROMA_ERROR_INIT;
}
int updated_count = 0;
for (int i = 0; i < state->output_count; i++) {
chroma_output_t *output = &state->outputs[i];
if (output_needs_update(output)) {
if (assign_wallpaper_to_output(state, output) == CHROMA_OK) {
updated_count++;
}
}
}
if (updated_count > 0) {
chroma_log("INFO", "Updated wallpapers for %d outputs", updated_count);
}
return CHROMA_OK;
}
// Get statistics
void chroma_get_stats(chroma_state_t *state, int *active_outputs,
int *loaded_images) {
if (!state) {
if (active_outputs)
*active_outputs = 0;
if (loaded_images)
*loaded_images = 0;
return;
}
int active = 0;
for (int i = 0; i < state->output_count; i++) {
if (state->outputs[i].active) {
active++;
}
}
int loaded = 0;
for (int i = 0; i < state->image_count; i++) {
if (state->images[i].loaded) {
loaded++;
}
}
if (active_outputs)
*active_outputs = active;
if (loaded_images)
*loaded_images = loaded;
}
// Get current memory usage from /proc/self/status
// FIXME: some users may choose to confine /proc, we need a cleaner
// way of getting this data in the future
size_t chroma_get_memory_usage(void) {
FILE *file = fopen("/proc/self/status", "r");
if (!file) {
return 0;
}
size_t vm_rss = 0;
char line[256];
while (fgets(line, sizeof(line), file)) {
if (strncmp(line, "VmRSS:", 6) == 0) {
// Parse VmRSS line: "VmRSS: 1234 kB"
char *ptr = line + 6;
while (*ptr == ' ' || *ptr == '\t')
ptr++; // Skip whitespace
vm_rss = (size_t)strtoul(ptr, NULL, 10) * 1024; // Convert kB to bytes
break;
}
}
fclose(file);
return vm_rss;
}
// Log memory statistics with context
void chroma_log_memory_stats(const char *context) {
size_t memory_usage = chroma_get_memory_usage();
char mem_str[64];
chroma_format_memory_size(memory_usage, mem_str, sizeof(mem_str));
chroma_log("INFO", "Memory usage [%s]: %s", context, mem_str);
}
// Log resource allocation
void chroma_log_resource_allocation(const char *resource_type, size_t size,
const char *description) {
if (size > 0) {
char size_str[64];
chroma_format_memory_size(size, size_str, sizeof(size_str));
chroma_log("DEBUG", "Allocated %s: %s (%s)", resource_type, size_str,
description);
} else {
chroma_log("DEBUG", "Allocated %s: %s", resource_type, description);
}
// Log memory stats after significant allocations (>1MB)
if (size > 1024 * 1024) {
chroma_log_memory_stats("post-allocation");
}
}
// Log resource deallocation
void chroma_log_resource_deallocation(const char *resource_type, size_t size,
const char *description) {
if (size > 0) {
char size_str[64];
chroma_format_memory_size(size, size_str, sizeof(size_str));
chroma_log("DEBUG", "Deallocated %s: %s (%s)", resource_type, size_str,
description);
} else {
chroma_log("DEBUG", "Deallocated %s: %s", resource_type, description);
}
// Log memory stats after significant deallocations (>1MB)
if (size > 1024 * 1024) {
chroma_log_memory_stats("post-deallocation");
}
}