#include #include #include #include #include #include #include #include #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"); } }