/* * DWN - Desktop Window Manager * retoor * Utility functions implementation */ #include "util.h" #include "dwn.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define LOG_RING_SIZE 256 #define LOG_MSG_MAX_LEN 512 #define LOG_MAX_FILE_SIZE (5 * 1024 * 1024) #define LOG_FLUSH_INTERVAL_MS 100 typedef struct { char message[LOG_MSG_MAX_LEN]; LogLevel level; _Atomic int ready; } LogEntry; static LogEntry log_ring[LOG_RING_SIZE]; static _Atomic size_t log_write_idx = 0; static _Atomic size_t log_read_idx = 0; static _Atomic int log_running = 0; static pthread_t log_thread; static int log_fd = -1; static char *log_path = NULL; static LogLevel min_level = LOG_INFO; static _Atomic size_t log_file_size = 0; static const char *level_names[] = { "DEBUG", "INFO", "WARN", "ERROR" }; static const char *level_colors[] = { "\033[36m", "\033[32m", "\033[33m", "\033[31m" }; static void log_rotate_if_needed(void); static void *log_writer_thread(void *arg); static void log_rotate_if_needed(void) { if (log_fd < 0 || log_path == NULL) { return; } size_t current_size = atomic_load(&log_file_size); if (current_size < LOG_MAX_FILE_SIZE) { return; } close(log_fd); log_fd = -1; size_t path_len = strlen(log_path); char *backup_path = malloc(path_len + 8); if (backup_path != NULL) { snprintf(backup_path, path_len + 8, "%s.old", log_path); unlink(backup_path); rename(log_path, backup_path); free(backup_path); } log_fd = open(log_path, O_WRONLY | O_CREAT | O_APPEND | O_CLOEXEC, 0644); atomic_store(&log_file_size, 0); } static void *log_writer_thread(void *arg) { (void)arg; while (atomic_load(&log_running)) { size_t read_idx = atomic_load(&log_read_idx); size_t write_idx = atomic_load(&log_write_idx); while (read_idx != write_idx) { size_t idx = read_idx % LOG_RING_SIZE; LogEntry *entry = &log_ring[idx]; int attempts = 0; while (!atomic_load(&entry->ready) && attempts < 100) { attempts++; struct timespec ts = {0, 1000}; nanosleep(&ts, NULL); } if (atomic_load(&entry->ready)) { if (log_fd >= 0) { log_rotate_if_needed(); if (log_fd >= 0) { ssize_t written = write(log_fd, entry->message, strlen(entry->message)); if (written > 0) { atomic_fetch_add(&log_file_size, (size_t)written); } } } atomic_store(&entry->ready, 0); } read_idx++; atomic_store(&log_read_idx, read_idx); } struct timespec ts = {0, LOG_FLUSH_INTERVAL_MS * 1000000L}; nanosleep(&ts, NULL); } return NULL; } void log_init(const char *path) { memset(log_ring, 0, sizeof(log_ring)); atomic_store(&log_write_idx, 0); atomic_store(&log_read_idx, 0); if (path != NULL) { char *expanded = expand_path(path); if (expanded != NULL) { char *dir = strdup(expanded); if (dir != NULL) { char *last_slash = strrchr(dir, '/'); if (last_slash != NULL) { *last_slash = '\0'; struct stat st; if (stat(dir, &st) != 0) { char cmd[512]; snprintf(cmd, sizeof(cmd), "mkdir -p '%s' 2>/dev/null", dir); int ret = system(cmd); (void)ret; } } free(dir); } log_fd = open(expanded, O_WRONLY | O_CREAT | O_APPEND | O_CLOEXEC, 0644); if (log_fd < 0) { fprintf(stderr, "Warning: Could not open log file: %s\n", expanded); } else { struct stat st; if (fstat(log_fd, &st) == 0) { atomic_store(&log_file_size, (size_t)st.st_size); } } log_path = expanded; } } atomic_store(&log_running, 1); if (pthread_create(&log_thread, NULL, log_writer_thread, NULL) != 0) { fprintf(stderr, "Warning: Could not create log writer thread\n"); atomic_store(&log_running, 0); } } void log_close(void) { if (!atomic_load(&log_running)) { goto cleanup; } atomic_store(&log_running, 0); pthread_join(log_thread, NULL); cleanup: if (log_fd >= 0) { close(log_fd); log_fd = -1; } if (log_path != NULL) { free(log_path); log_path = NULL; } } void log_set_level(LogLevel level) { min_level = level; } void log_flush(void) { if (!atomic_load(&log_running) || log_fd < 0) { return; } size_t read_idx = atomic_load(&log_read_idx); size_t write_idx = atomic_load(&log_write_idx); while (read_idx != write_idx) { size_t idx = read_idx % LOG_RING_SIZE; LogEntry *entry = &log_ring[idx]; if (atomic_load(&entry->ready)) { ssize_t written = write(log_fd, entry->message, strlen(entry->message)); if (written > 0) { atomic_fetch_add(&log_file_size, (size_t)written); } atomic_store(&entry->ready, 0); } read_idx++; atomic_store(&log_read_idx, read_idx); } fsync(log_fd); } void log_msg(LogLevel level, const char *fmt, ...) { if (level < min_level) { return; } time_t now = time(NULL); struct tm tm_info; localtime_r(&now, &tm_info); char time_buf[32]; strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", &tm_info); char msg_buf[LOG_MSG_MAX_LEN - 64]; va_list args; va_start(args, fmt); vsnprintf(msg_buf, sizeof(msg_buf), fmt, args); va_end(args); fprintf(stderr, "%s[%s] %s: %s%s\n", level_colors[level], time_buf, level_names[level], "\033[0m", msg_buf); if (atomic_load(&log_running)) { size_t write_idx = atomic_fetch_add(&log_write_idx, 1); size_t idx = write_idx % LOG_RING_SIZE; LogEntry *entry = &log_ring[idx]; if (!atomic_load(&entry->ready)) { entry->level = level; snprintf(entry->message, sizeof(entry->message), "[%s] %s: %s\n", time_buf, level_names[level], msg_buf); atomic_store(&entry->ready, 1); } } } void *dwn_malloc(size_t size) { void *ptr = malloc(size); if (ptr == NULL && size > 0) { LOG_ERROR("Memory allocation failed for %zu bytes", size); exit(EXIT_FAILURE); } return ptr; } void *dwn_calloc(size_t nmemb, size_t size) { void *ptr = calloc(nmemb, size); if (ptr == NULL && nmemb > 0 && size > 0) { LOG_ERROR("Memory allocation failed for %zu elements", nmemb); exit(EXIT_FAILURE); } return ptr; } void *dwn_realloc(void *ptr, size_t size) { void *new_ptr = realloc(ptr, size); if (new_ptr == NULL && size > 0) { LOG_ERROR("Memory reallocation failed for %zu bytes", size); exit(EXIT_FAILURE); } return new_ptr; } char *dwn_strdup(const char *s) { if (s == NULL) { return NULL; } char *dup = strdup(s); if (dup == NULL) { LOG_ERROR("String duplication failed"); exit(EXIT_FAILURE); } return dup; } void dwn_free(void *ptr) { free(ptr); } void secure_wipe(void *ptr, size_t size) { if (ptr == NULL || size == 0) { return; } #if defined(__GLIBC__) && (__GLIBC__ >= 2) && (__GLIBC_MINOR__ >= 25) explicit_bzero(ptr, size); #else volatile unsigned char *p = (volatile unsigned char *)ptr; while (size--) { *p++ = 0; } #endif } char *str_trim(char *str) { if (str == NULL) { return NULL; } while (isspace((unsigned char)*str)) { str++; } if (*str == '\0') { return str; } char *end = str + strlen(str) - 1; while (end > str && isspace((unsigned char)*end)) { end--; } end[1] = '\0'; return str; } bool str_starts_with(const char *str, const char *prefix) { if (str == NULL || prefix == NULL) { return false; } return strncmp(str, prefix, strlen(prefix)) == 0; } bool str_ends_with(const char *str, const char *suffix) { if (str == NULL || suffix == NULL) { return false; } size_t str_len = strlen(str); size_t suffix_len = strlen(suffix); if (suffix_len > str_len) { return false; } return strcmp(str + str_len - suffix_len, suffix) == 0; } int str_split(char *str, char delim, char **parts, int max_parts) { if (str == NULL || parts == NULL || max_parts <= 0) { return 0; } int count = 0; char *start = str; while (*str && count < max_parts) { if (*str == delim) { *str = '\0'; parts[count++] = start; start = str + 1; } str++; } if (count < max_parts && *start) { parts[count++] = start; } return count; } char *shell_escape(const char *str) { if (str == NULL) { return dwn_strdup(""); } size_t quotes = 0; for (const char *p = str; *p; p++) { if (*p == '\'') quotes++; } size_t len = strlen(str) + quotes * 3 + 3; char *escaped = dwn_malloc(len); char *dst = escaped; *dst++ = '\''; for (const char *src = str; *src; src++) { if (*src == '\'') { *dst++ = '\''; *dst++ = '\\'; *dst++ = '\''; *dst++ = '\''; } else { *dst++ = *src; } } *dst++ = '\''; *dst = '\0'; return escaped; } bool file_exists(const char *path) { if (path == NULL) { return false; } struct stat st; return stat(path, &st) == 0; } char *file_read_all(const char *path) { FILE *f = fopen(path, "r"); if (f == NULL) { return NULL; } fseek(f, 0, SEEK_END); long size = ftell(f); fseek(f, 0, SEEK_SET); char *content = dwn_malloc(size + 1); size_t read = fread(content, 1, size, f); content[read] = '\0'; fclose(f); return content; } bool file_write_all(const char *path, const char *content) { FILE *f = fopen(path, "w"); if (f == NULL) { return false; } size_t len = strlen(content); size_t written = fwrite(content, 1, len, f); fclose(f); return written == len; } char *expand_path(const char *path) { if (path == NULL) { return NULL; } if (path[0] == '~') { const char *home = getenv("HOME"); if (home == NULL) { struct passwd *pw = getpwuid(getuid()); if (pw != NULL) { home = pw->pw_dir; } } if (home != NULL) { size_t len = strlen(home) + strlen(path); char *expanded = dwn_malloc(len); snprintf(expanded, len, "%s%s", home, path + 1); return expanded; } } return dwn_strdup(path); } unsigned long parse_color(const char *color_str) { if (color_str == NULL || dwn == NULL || dwn->display == NULL) { return 0; } XColor color, exact; if (XAllocNamedColor(dwn->display, dwn->colormap, color_str, &color, &exact)) { return color.pixel; } if (color_str[0] == '#') { unsigned int r, g, b; if (sscanf(color_str + 1, "%02x%02x%02x", &r, &g, &b) == 3) { color.red = r << 8; color.green = g << 8; color.blue = b << 8; color.flags = DoRed | DoGreen | DoBlue; if (XAllocColor(dwn->display, dwn->colormap, &color)) { return color.pixel; } } } LOG_WARN("Could not parse color: %s", color_str); return 0; } void color_to_rgb(unsigned long color, int *r, int *g, int *b) { XColor xc; xc.pixel = color; XQueryColor(dwn->display, dwn->colormap, &xc); *r = xc.red >> 8; *g = xc.green >> 8; *b = xc.blue >> 8; } long get_time_ms(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (long)(ts.tv_sec * 1000 + ts.tv_nsec / 1000000); } void sleep_ms(int ms) { struct timespec ts; ts.tv_sec = ms / 1000; ts.tv_nsec = (ms % 1000) * 1000000L; nanosleep(&ts, NULL); } int spawn(const char *cmd) { if (cmd == NULL) { return -1; } LOG_DEBUG("Spawning: %s", cmd); pid_t pid = fork(); if (pid == 0) { setsid(); execl("/bin/sh", "sh", "-c", cmd, NULL); _exit(EXIT_FAILURE); } else if (pid < 0) { LOG_ERROR("Fork failed: %s", strerror(errno)); return -1; } int status; waitpid(pid, &status, 0); return WEXITSTATUS(status); } int spawn_async(const char *cmd) { if (cmd == NULL) { return -1; } LOG_DEBUG("Spawning async: %s", cmd); pid_t pid = fork(); if (pid == 0) { setsid(); pid_t pid2 = fork(); if (pid2 == 0) { execl("/bin/sh", "sh", "-c", cmd, NULL); _exit(EXIT_FAILURE); } _exit(EXIT_SUCCESS); } else if (pid < 0) { LOG_ERROR("Fork failed: %s", strerror(errno)); return -1; } int status; waitpid(pid, &status, 0); return 0; } char *spawn_capture(const char *cmd) { if (cmd == NULL) { return NULL; } LOG_DEBUG("Spawning capture: %s", cmd); FILE *fp = popen(cmd, "r"); if (fp == NULL) { LOG_ERROR("popen failed: %s", strerror(errno)); return NULL; } size_t buf_size = 1024; size_t len = 0; char *output = dwn_malloc(buf_size); output[0] = '\0'; char line[256]; while (fgets(line, sizeof(line), fp) != NULL) { size_t line_len = strlen(line); if (len + line_len + 1 > buf_size) { buf_size *= 2; output = dwn_realloc(output, buf_size); } memcpy(output + len, line, line_len + 1); len += line_len; } int status = pclose(fp); if (status != 0) { LOG_DEBUG("Command exited with status %d", status); } if (len > 0 && output[len - 1] == '\n') { output[len - 1] = '\0'; } return output; } static void hsl_to_rgb(double h, double s, double l, int *r, int *g, int *b) { double c = (1.0 - fabs(2.0 * l - 1.0)) * s; double x = c * (1.0 - fabs(fmod(h / 60.0, 2.0) - 1.0)); double m = l - c / 2.0; double rp = 0, gp = 0, bp = 0; if (h < 60) { rp = c; gp = x; bp = 0; } else if (h < 120) { rp = x; gp = c; bp = 0; } else if (h < 180) { rp = 0; gp = c; bp = x; } else if (h < 240) { rp = 0; gp = x; bp = c; } else if (h < 300) { rp = x; gp = 0; bp = c; } else { rp = c; gp = 0; bp = x; } *r = (int)((rp + m) * 255); *g = (int)((gp + m) * 255); *b = (int)((bp + m) * 255); } unsigned long rgb_to_pixel(int r, int g, int b) { if (dwn == NULL || dwn->display == NULL) { return 0; } XColor color; color.red = (unsigned short)(r << 8); color.green = (unsigned short)(g << 8); color.blue = (unsigned short)(b << 8); color.flags = DoRed | DoGreen | DoBlue; if (XAllocColor(dwn->display, dwn->colormap, &color)) { return color.pixel; } return (unsigned long)((r << 16) | (g << 8) | b); } unsigned long generate_unique_color(void) { static unsigned int color_index = 0; double golden_angle = 137.5; double hue = fmod(color_index * golden_angle, 360.0); color_index++; double saturation = 0.75; double lightness = 0.65; int r, g, b; hsl_to_rgb(hue, saturation, lightness, &r, &g, &b); return rgb_to_pixel(r, g, b); } static double calculate_luminance(int r, int g, int b) { double rs = r / 255.0; double gs = g / 255.0; double bs = b / 255.0; rs = (rs <= 0.03928) ? rs / 12.92 : pow((rs + 0.055) / 1.055, 2.4); gs = (gs <= 0.03928) ? gs / 12.92 : pow((gs + 0.055) / 1.055, 2.4); bs = (bs <= 0.03928) ? bs / 12.92 : pow((bs + 0.055) / 1.055, 2.4); return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs; } unsigned long adjust_color_for_background(unsigned long color, unsigned long bg) { int cr, cg, cb; int br, bg_g, bb; color_to_rgb(color, &cr, &cg, &cb); color_to_rgb(bg, &br, &bg_g, &bb); double color_lum = calculate_luminance(cr, cg, cb); double bg_lum = calculate_luminance(br, bg_g, bb); double lighter = (color_lum > bg_lum) ? color_lum : bg_lum; double darker = (color_lum > bg_lum) ? bg_lum : color_lum; double contrast = (lighter + 0.05) / (darker + 0.05); if (contrast < 3.0) { double factor = (bg_lum > 0.5) ? 0.3 : 1.8; cr = (int)(cr * factor); cg = (int)(cg * factor); cb = (int)(cb * factor); if (cr > 255) cr = 255; if (cg > 255) cg = 255; if (cb > 255) cb = 255; if (cr < 0) cr = 0; if (cg < 0) cg = 0; if (cb < 0) cb = 0; } return rgb_to_pixel(cr, cg, cb); } unsigned long interpolate_color(unsigned long from, unsigned long to, float progress) { if (progress <= 0.0f) return from; if (progress >= 1.0f) return to; int fr, fg_c, fb; int tr, tg, tb; color_to_rgb(from, &fr, &fg_c, &fb); color_to_rgb(to, &tr, &tg, &tb); int r = fr + (int)((tr - fr) * progress); int g = fg_c + (int)((tg - fg_c) * progress); int b = fb + (int)((tb - fb) * progress); return rgb_to_pixel(r, g, b); } unsigned long dim_color(unsigned long color, float factor) { int r, g, b; color_to_rgb(color, &r, &g, &b); r = (int)(r * factor); g = (int)(g * factor); b = (int)(b * factor); if (r > 255) r = 255; if (g > 255) g = 255; if (b > 255) b = 255; if (r < 0) r = 0; if (g < 0) g = 0; if (b < 0) b = 0; return rgb_to_pixel(r, g, b); } unsigned long glow_color(unsigned long base, float phase) { int r, g, b; color_to_rgb(base, &r, &g, &b); float mod_r = 0.25f * sinf(phase); float mod_g = 0.25f * sinf(phase + 2.094f); float mod_b = 0.25f * sinf(phase + 4.189f); r = r + (int)(r * mod_r); g = g + (int)(g * mod_g); b = b + (int)(b * mod_b); if (r > 255) r = 255; if (r < 0) r = 0; if (g > 255) g = 255; if (g < 0) g = 0; if (b > 255) b = 255; if (b < 0) b = 0; return rgb_to_pixel(r, g, b); }