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.
575 lines
16 KiB
C
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");
|
|
}
|
|
}
|