diff --git a/CMakeLists.txt b/CMakeLists.txt index af38cf6..ed5c4e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,6 +45,9 @@ add_executable(${APP_NAME} ${SOURCE_DIR}/audio.c ${SOURCE_DIR}/rect.c ${SOURCE_DIR}/wallpaper.c + ${SOURCE_DIR}/config.c + ${SOURCE_DIR}/renderer.c + ${SOURCE_DIR}/image.c ) find_package(PkgConfig REQUIRED) diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..84ada86 --- /dev/null +++ b/src/config.c @@ -0,0 +1,81 @@ +#include "config.h" + +#include + +#include "default_config.h" +#include "utils.h" + +void config_status_set_gap(config_status_t *status_config, uint32_t gap, + uint32_t item_gap) { + if (!status_config) fatal("no valid status config"); + status_config->status_gap = gap; + status_config->status_item_gap = item_gap; +} + +void config_status_add_item(config_status_t **status_config, + config_status_item_t status_item) { + const auto item_count = (*status_config)->status_count + 1; + const auto new_size = + sizeof(config_status_t) + item_count * sizeof(config_status_item_t); + xrealloc((void **)status_config, (ssize_t)new_size); + (*status_config)->list[item_count - 1] = status_item; + (*status_config)->status_count = item_count; +} + +void config_status_filter_item(config_status_t **status_config, + status_item_filter filter) { + if (!status_config || !*status_config) fatal("no valid status config"); + if (!filter) return; + + config_status_t *config = *status_config; + size_t write_idx = 0; + size_t item_count = config->status_count; + + for (size_t read_idx = 0; read_idx < item_count; read_idx++) { + config_status_item_t *item = &config->list[read_idx]; + if (!filter(item)) continue; + if (write_idx != read_idx) { + config->list[write_idx] = *item; + } + write_idx++; + } + + if (write_idx == item_count) return; + + config->status_count = write_idx; + size_t new_size = + sizeof(config_status_t) + write_idx * sizeof(config_status_item_t); + xrealloc((void **)status_config, (ssize_t)new_size); +} + +static config_status_t *config_create_default_status(void) { + size_t item_count = countof(status_list); + size_t size = sizeof(config_status_t) + item_count * sizeof(status_list[0]); + config_status_t *status = xmalloc((ssize_t)size); + + status->status_gap = status_config.status_gap; + status->status_item_gap = status_config.status_item_gap; + status->status_count = item_count; + memcpy(status->list, status_list, item_count * sizeof(status_list[0])); + + return status; +} + +static config_t runtime_config = {}; + +const config_t *init_config(void) { + if (!runtime_config.status) { + runtime_config.status = config_create_default_status(); + } + if (!runtime_config.status_renderer) { + runtime_config.status_renderer = renderer_render_status; + } + return &runtime_config; +} + +void config_set_custom_status_renderer(config_t *config, + status_renderer_t status_renderer) { + if (!config) fatal("no valid config"); + config->status_renderer = + status_renderer ? status_renderer : renderer_render_status; +} diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..658ec8e --- /dev/null +++ b/src/config.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include + +#include "renderer.h" +#include "status.h" + +typedef struct config_status_item_t { + status_type_t type; + icon_type_t icon_type; + const char *icon; + const char *color; +} config_status_item_t; + +typedef struct config_status_t { + uint32_t status_gap; + uint32_t status_item_gap; + size_t status_count; + config_status_item_t list[]; +} config_status_t; + +void config_status_set_gap(config_status_t *status_config, uint32_t gap, + uint32_t item_gap); + +void config_status_add_item(config_status_t **status_config, + config_status_item_t status_item); + +typedef bool (*status_item_filter)(config_status_item_t *item); +/** + * @brief 按过滤条件原地筛选 status 项并保持原有顺序 + * + * 当 filter(item) 返回 true 时保留该项,返回 false 时删除该项。 + * 筛选后会压缩 list、更新 status_count,并收缩配置对象内存。 + * + * @param status_config 配置指针地址(函数内部会重分配内存) + * @param filter 过滤函数 + */ +void config_status_filter_item(config_status_t **status_config, + status_item_filter filter); + +typedef void (*status_renderer_t)(config_status_t *config, size_t config_count, + status_t *status, renderer_status_t *output); + +typedef struct config_t { + config_status_t *status; + status_renderer_t status_renderer; +} config_t; + +/** + * @brief 初始化配置对象 + * @return 返回静态变量指针,不需要释放 + */ +const config_t *init_config(void); + +void config_set_custom_status_renderer(config_t *config, + status_renderer_t status_renderer); diff --git a/src/default_config.h b/src/default_config.h index 8611316..0b5e138 100644 --- a/src/default_config.h +++ b/src/default_config.h @@ -5,7 +5,10 @@ #include #include "action.h" +#include "config.h" #include "layout.h" +#include "renderer.h" +#include "status.h" #include "types.h" static const char *const font_family = "monospace"; @@ -27,6 +30,55 @@ static const char *const active_tag_color = "#eeeeee"; static const char *const border_color = "#444444"; static const char *const active_border_color = "#005577"; +static const config_status_item_t status_list[] = { + { + .type = status_net_down, + .icon_type = icon_type_image, + .icon = "/home/zedhugh/develop/zswm/src/resources/icons/" + "corner-left-down-line.png", + .color = "#87af5f", + }, + { + .type = status_net_up, + .icon_type = icon_type_image, + .icon = + "/home/zedhugh/develop/zswm/src/resources/icons/corner-right-up-line.png", + .color = "#e54c62", + }, + { + .type = status_audio, + .icon_type = icon_type_image, + .icon = "/home/zedhugh/develop/zswm/src/resources/icons/volume-up-fill.png", + .color = "#7493d2", + }, + { + .type = status_memory, + .icon_type = icon_type_image, + .icon = "/home/zedhugh/develop/zswm/src/resources/icons/ram-line.png", + .color = "#e0da37", + }, + { + .type = status_cpu, + .icon_type = icon_type_image, + .icon = "/home/zedhugh/develop/zswm/src/resources/icons/cpu-line.png", + .color = "#e33a6e", + }, + { + .type = status_time, + .icon_type = icon_type_image, + .icon = "/home/zedhugh/develop/zswm/src/resources/icons/time-line.png", + .color = "#7788af", + }, +}; +static const config_status_t status_config = { + .status_gap = 10, + .status_item_gap = 5, +}; + +static const config_t config = { + .status = (config_status_t *)&status_config, +}; + static const button_t button_list[] = { {click_tag, modifier_none, button_left, select_tag_of_current_monitor, {0}}, { @@ -39,9 +91,9 @@ static const button_t button_list[] = { }; #define TAGKEYS(KEY, TAG) \ - {modifier_super, KEY, select_tag_of_current_monitor, {.ui = TAG}}, { \ - modifier_super | modifier_shift, KEY, send_client_to_tag, {.ui = TAG}, \ - } +{modifier_super, KEY, select_tag_of_current_monitor, {.ui = TAG}}, { \ +modifier_super | modifier_shift, KEY, send_client_to_tag, {.ui = TAG}, \ +} static const char launcher[] = "rofi -show combi -modes combi -combi-modes window,drun,run,ssh,windowcd"; @@ -140,6 +192,6 @@ static const rule_t rules[] = { {.class = "Emacs", .tag_index = 10, .switch_to_tag = true, .maximize = true}, }; -/* static const char *const wallpapers[] = {"~/bg/\*", "~/Downloads/bg/\*"}; */ -static const char *const wallpapers[] = {}; -static const uint32_t wallpaper_interval = 10; +static const char *const wallpapers[] = {"~/bg/*", "~/Downloads/bg/*"}; +/* static const char *const wallpapers[] = {}; */ +static const uint32_t wallpaper_interval = 0; diff --git a/src/image.c b/src/image.c new file mode 100644 index 0000000..f2d7080 --- /dev/null +++ b/src/image.c @@ -0,0 +1,170 @@ +#include "image.h" + +#include +#include +#include +#include + +#include "utils.h" + +typedef struct image_cache_entry_t { + char *path; + cairo_surface_t *surface; + int width; + int height; +} image_cache_entry_t; + +static image_cache_entry_t *image_cache = nullptr; +static size_t image_cache_count = 0; +static cairo_user_data_key_t image_surface_data_key; + +static inline uint32_t premultiply_argb(uint32_t pixel) { + uint8_t a = (pixel >> 24) & 0xff; + uint8_t r = (pixel >> 16) & 0xff; + uint8_t g = (pixel >> 8) & 0xff; + uint8_t b = pixel & 0xff; + r = (uint8_t)((r * a + 127) / 255); + g = (uint8_t)((g * a + 127) / 255); + b = (uint8_t)((b * a + 127) / 255); + return ((uint32_t)a << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | + (uint32_t)b; +} + +static bool image_load_surface(const char *path, cairo_surface_t **surface, + int *width, int *height) { + if (!path || !*path || !surface || !width || !height) return false; + + Imlib_Image image = imlib_load_image(path); + if (!image) return false; + + imlib_context_set_image(image); + int img_width = imlib_image_get_width(); + int img_height = imlib_image_get_height(); + if (img_width <= 0 || img_height <= 0) { + imlib_free_image(); + return false; + } + + DATA32 *src = imlib_image_get_data_for_reading_only(); + if (!src) { + imlib_free_image(); + return false; + } + + size_t pixel_count = (size_t)img_width * (size_t)img_height; + uint32_t *dst = xmalloc((ssize_t)(pixel_count * sizeof(uint32_t))); + for (size_t i = 0; i < pixel_count; i++) { + dst[i] = premultiply_argb(src[i]); + } + + cairo_surface_t *img_surface = cairo_image_surface_create_for_data( + (unsigned char *)dst, CAIRO_FORMAT_ARGB32, img_width, img_height, + img_width * (int)sizeof(uint32_t)); + cairo_status_t status = cairo_surface_status(img_surface); + if (status != CAIRO_STATUS_SUCCESS) { + cairo_surface_destroy(img_surface); + p_delete(&dst); + imlib_free_image(); + return false; + } + + cairo_surface_set_user_data(img_surface, &image_surface_data_key, dst, free); + *surface = img_surface; + *width = img_width; + *height = img_height; + + imlib_free_image(); + return true; +} + +static image_cache_entry_t *image_cache_find(const char *path) { + if (!path || !*path) return nullptr; + for (size_t i = 0; i < image_cache_count; i++) { + image_cache_entry_t *entry = &image_cache[i]; + if (!entry->path) continue; + if (strcmp(entry->path, path) == 0) return entry; + } + return nullptr; +} + +static image_cache_entry_t *image_cache_add(const char *path) { + if (!path || !*path) return nullptr; + + size_t new_count = image_cache_count + 1; + xrealloc((void **)&image_cache, + (ssize_t)(new_count * sizeof(image_cache_entry_t))); + image_cache_entry_t *entry = &image_cache[image_cache_count]; + entry->path = strdup(path); + entry->surface = nullptr; + entry->width = 0; + entry->height = 0; + image_cache_count = new_count; + return entry; +} + +static image_cache_entry_t *image_cache_get(const char *path) { + image_cache_entry_t *entry = image_cache_find(path); + if (entry && entry->surface) return entry; + if (!entry) entry = image_cache_add(path); + if (!entry) return nullptr; + + if (entry->surface) { + cairo_surface_destroy(entry->surface); + entry->surface = nullptr; + } + + if (!image_load_surface(path, &entry->surface, &entry->width, &entry->height)) { + return nullptr; + } + return entry; +} + +bool image_get_scaled_size(const char *path, int32_t target_height, int *width, + int *height) { + if (!width || !height || target_height <= 0) return false; + image_cache_entry_t *entry = image_cache_get(path); + if (!entry || !entry->surface || entry->width <= 0 || entry->height <= 0) { + return false; + } + + *height = target_height; + *width = (entry->width * target_height + entry->height / 2) / entry->height; + if (*width <= 0) *width = 1; + return true; +} + +bool image_draw(cairo_t *cr, const char *path, int32_t x, int32_t y, + int32_t width, int32_t height) { + if (!cr || width <= 0 || height <= 0) return false; + image_cache_entry_t *entry = image_cache_get(path); + if (!entry || !entry->surface || entry->width <= 0 || entry->height <= 0) { + return false; + } + + cairo_save(cr); + cairo_translate(cr, (double)x, (double)y); + cairo_scale(cr, (double)width / (double)entry->width, + (double)height / (double)entry->height); + cairo_set_source_surface(cr, entry->surface, 0, 0); + cairo_paint(cr); + cairo_restore(cr); + return true; +} + +void image_cache_clean(void) { + if (!image_cache) return; + + for (size_t i = 0; i < image_cache_count; i++) { + image_cache_entry_t *entry = &image_cache[i]; + if (entry->surface) { + cairo_surface_destroy(entry->surface); + entry->surface = nullptr; + } + p_delete(&entry->path); + entry->width = 0; + entry->height = 0; + } + + p_delete(&image_cache); + image_cache_count = 0; +} diff --git a/src/image.h b/src/image.h new file mode 100644 index 0000000..ee56d44 --- /dev/null +++ b/src/image.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include + +/** + * @brief 获取图片按目标高度缩放后的尺寸 + * + * @param path 图片路径 + * @param target_height 目标高度(像素) + * @param width 返回缩放后的宽度 + * @param height 返回缩放后的高度 + * @return 加载并计算成功返回 true + */ +bool image_get_scaled_size(const char *path, int32_t target_height, int *width, + int *height); + +/** + * @brief 在 cairo 上下文中绘制图片 + * + * @param cr cairo 上下文 + * @param path 图片路径 + * @param x 绘制起始 x + * @param y 绘制起始 y + * @param width 绘制宽度 + * @param height 绘制高度 + * @return 绘制成功返回 true + */ +bool image_draw(cairo_t *cr, const char *path, int32_t x, int32_t y, + int32_t width, int32_t height); + +/** + * @brief 释放图片缓存 + */ +void image_cache_clean(void); diff --git a/src/monitor.c b/src/monitor.c index 43e6572..ad93c93 100644 --- a/src/monitor.c +++ b/src/monitor.c @@ -12,6 +12,9 @@ #include "base.h" #include "client.h" #include "color.h" +#include "config.h" +#include "image.h" +#include "renderer.h" #include "status.h" #include "text.h" #include "types.h" @@ -237,47 +240,106 @@ static void monitor_draw_layout_symbol(monitor_t *monitor) { } static void monitor_draw_status(monitor_t *monitor, status_t *status) { + static renderer_status_t *rendered_status = nullptr; + static size_t rendered_status_capacity = 0; + monitor->status_extent.end = monitor->geometry.width; monitor->status_extent.start = monitor->geometry.width; - int16_t end = monitor->layout_symbol_extent.end; + int32_t end = monitor->layout_symbol_extent.end; - if (status == nullptr) return; + if (status == nullptr || !wm.config || !wm.config->status) return; - char cpu[16], mem[64], volume[300]; - snprintf(cpu, sizeof(cpu), "%.1lf", status->cpu_usage_percent); - snprintf(mem, sizeof(mem), "%s(%.1f%%)", status->mem_usage.mem_used_text, - status->mem_usage.mem_percent); - if (status->pulse) { - snprintf(volume, sizeof(volume), "%d%%%s%s%s", - status->pulse->volume_percent, status->pulse->mute ? "M" : "", - strlen(status->pulse->device_name) ? "|" : "", - status->pulse->device_name); - } else { - snprintf(volume, sizeof(volume), "0%%"); + config_status_t *status_config = wm.config->status; + size_t item_count = status_config->status_count; + if (item_count == 0) return; + + if (rendered_status_capacity < item_count) { + size_t size = + sizeof(renderer_status_t) + item_count * sizeof(renderer_status_item_t); + xrealloc((void **)&rendered_status, (ssize_t)size); + rendered_status_capacity = item_count; } - char *status_items[] = { - status->net_speed.down, status->net_speed.up, volume, mem, cpu, - status->time, - }; - int16_t start = monitor->status_extent.start; - for (int i = countof(status_items) - 1; i >= 0; i--) { - const char *text = status_items[i]; - if (!text || !strlen(text)) continue; + status_renderer_t renderer = wm.config->status_renderer; + if (!renderer) renderer = renderer_render_status; + + renderer(status_config, item_count, status, rendered_status); + item_count = MIN(item_count, rendered_status->item_count); + + int32_t start = monitor->status_extent.start; + int32_t icon_target_height = + (int32_t)wm.bar_height - (int32_t)wm.padding.bar_y * 2; + if (icon_target_height <= 0) icon_target_height = wm.bar_height; + + for (size_t ri = item_count; ri > 0; ri--) { + size_t i = ri - 1; + renderer_status_item_t *item = &rendered_status->item_list[i]; + bool has_text = item->text[0] != '\0'; + int text_width = 0; + if (has_text) text_get_size(item->text, &text_width, nullptr); + + bool has_text_icon = false; + int icon_text_width = 0; + if (item->icon_type == icon_type_text && item->icon_text && + item->icon_text[0]) { + has_text_icon = true; + text_get_size(item->icon_text, &icon_text_width, nullptr); + } + + bool has_image_icon = false; + int icon_width = 0; + int icon_height = 0; + if (item->icon_type == icon_type_image && item->icon_path && + item->icon_path[0]) { + has_image_icon = image_get_scaled_size( + item->icon_path, icon_target_height, &icon_width, &icon_height); + } + bool has_icon = has_text_icon || has_image_icon; int width = 0; - text_get_size(text, &width, nullptr); + if (has_text_icon) width += icon_text_width; + if (has_image_icon) width += icon_width; + if (has_text) width += text_width; + if (has_icon && has_text) width += (int)rendered_status->item_gap; if (!width) continue; start -= width; if (start <= end) return; - color_t *color = &wm.color_set.tag_color; - area_t rect = {.x = start, .y = 0, .width = width, .height = wm.bar_height}; - draw_text(monitor->bar_cr, text, color, rect, true); - monitor->status_extent.start = start; - start -= wm.padding.tag_x; - if (start < end) return; + color_t *color = item->color ? item->color : &wm.color_set.tag_color; + int32_t draw_x = start; + if (has_text_icon && icon_text_width > 0) { + area_t icon_rect = { + .x = (int16_t)draw_x, + .y = 0, + .width = (uint16_t)icon_text_width, + .height = wm.bar_height, + }; + draw_text(monitor->bar_cr, item->icon_text, color, icon_rect, true); + draw_x += icon_text_width; + } else if (has_image_icon) { + int32_t icon_y = ((int32_t)wm.bar_height - icon_height) / 2; + image_draw(monitor->bar_cr, item->icon_path, draw_x, icon_y, icon_width, + icon_height); + draw_x += icon_width; + } + if (has_icon && has_text) draw_x += (int32_t)rendered_status->item_gap; + + if (has_text && text_width > 0) { + area_t rect = { + .x = (int16_t)draw_x, + .y = 0, + .width = (uint16_t)text_width, + .height = wm.bar_height, + }; + draw_text(monitor->bar_cr, item->text, color, rect, true); + } + monitor->status_extent.start = (int16_t)start; + + if (ri > 1) { + start -= (int32_t)rendered_status->gap; + if (start < end) return; + } } } diff --git a/src/renderer.c b/src/renderer.c new file mode 100644 index 0000000..b8e8b94 --- /dev/null +++ b/src/renderer.c @@ -0,0 +1,90 @@ +#include "renderer.h" + +#include +#include + +#include "color.h" +#include "config.h" +#include "status.h" +#include "utils.h" + +static void status_text_from_data(status_type_t type, const status_t *status, + char *text, size_t text_size) { + if (!text || text_size == 0) return; + text[0] = '\0'; + if (!status) return; + + switch (type) { + case status_net_down: + snprintf(text, text_size, "%s", status->net_speed.down); + break; + case status_net_up: + snprintf(text, text_size, "%s", status->net_speed.up); + break; + case status_audio: + if (status->pulse) { + snprintf(text, text_size, "%d%%%s%s%s", status->pulse->volume_percent, + status->pulse->mute ? "M" : "", + strlen(status->pulse->device_name) ? "|" : "", + status->pulse->device_name); + } else { + snprintf(text, text_size, "0%%"); + } + break; + case status_memory: + snprintf(text, text_size, "%s(%.1f%%)", status->mem_usage.mem_used_text, + status->mem_usage.mem_percent); + break; + case status_cpu: + snprintf(text, text_size, "%.1lf", status->cpu_usage_percent); + break; + case status_time: + snprintf(text, text_size, "%s", status->time); + break; + default: + break; + } +} + +void renderer_render_status(config_status_t *config, size_t config_count, + status_t *status, renderer_status_t *output) { + static color_t *color_cache = nullptr; + static size_t color_cache_count = 0; + + if (!config || !output) return; + + size_t item_count = config->status_count; + if (config_count && config_count < item_count) item_count = config_count; + + output->gap = config->status_gap; + output->item_gap = config->status_item_gap; + output->item_count = item_count; + + if (item_count == 0) return; + + if (color_cache_count < item_count) { + xrealloc((void **)&color_cache, (ssize_t)(sizeof(color_t) * item_count)); + color_cache_count = item_count; + } + + for (size_t i = 0; i < item_count; i++) { + const config_status_item_t *config_item = &config->list[i]; + renderer_status_item_t *output_item = &output->item_list[i]; + memset(output_item, 0, sizeof(*output_item)); + + output_item->icon_type = config_item->icon_type; + if (config_item->icon_type == icon_type_text) { + output_item->icon_text = config_item->icon; + } else if (config_item->icon_type == icon_type_image) { + output_item->icon_path = config_item->icon; + } + + status_text_from_data(config_item->type, status, output_item->text, + sizeof(output_item->text)); + + if (config_item->color && *config_item->color) { + color_parse(config_item->color, &color_cache[i]); + output_item->color = &color_cache[i]; + } + } +} diff --git a/src/renderer.h b/src/renderer.h new file mode 100644 index 0000000..c68a9ce --- /dev/null +++ b/src/renderer.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include + +#include "color.h" + +typedef struct config_status_t config_status_t; +typedef struct status_t status_t; + +typedef enum icon_type_t { + icon_type_none, + icon_type_text, + icon_type_image, +} icon_type_t; + +typedef struct renderer_status_item_t { + icon_type_t icon_type; + char text[124]; + const char *icon_text; + const char *icon_path; + color_t *color; +} renderer_status_item_t; + +typedef struct renderer_status_t { + uint32_t gap; + uint32_t item_gap; + size_t item_count; + renderer_status_item_t item_list[]; +} renderer_status_t; + +/** + * @brief 根据 status 配置和实时状态生成可绘制的 status 数据 + * + * @param config status 配置 + * @param config_count 最大输出项数,传 0 表示使用 config->status_count + * @param status 实时状态数据 + * @param output 渲染输出缓冲 + */ +void renderer_render_status(config_status_t *config, size_t config_count, + status_t *status, renderer_status_t *output); diff --git a/src/status.h b/src/status.h index 92d0051..d87242d 100644 --- a/src/status.h +++ b/src/status.h @@ -30,6 +30,15 @@ typedef struct net_speed_t { char down[64]; } net_speed_t; +typedef enum status_type_t { + status_net_down, + status_net_up, + status_audio, + status_memory, + status_cpu, + status_time, +} status_type_t; + typedef struct status_t { net_speed_t net_speed; memory_usage_t mem_usage; diff --git a/src/wm.c b/src/wm.c index 0441f36..d2a2ebf 100644 --- a/src/wm.c +++ b/src/wm.c @@ -26,8 +26,10 @@ #include "buffer.h" #include "client.h" #include "color.h" +#include "config.h" #include "default_config.h" #include "event.h" +#include "image.h" #include "monitor.h" #include "status.h" #include "text.h" @@ -384,6 +386,7 @@ void wm_clean(void) { clean_status(); text_clean_pango_layout(); + image_cache_clean(); { client_t *c = wm.client_stack_list; @@ -417,6 +420,9 @@ void wm_clean(void) { } static void wm_setup(void) { + wm.config = init_config(); + if (!wm.config) fatal("cannot init config"); + wm.layout_list = layout_list; wm.layout_count = (uint16_t)countof(layout_list); diff --git a/src/wm.h b/src/wm.h index 5c0cf20..a7185d6 100644 --- a/src/wm.h +++ b/src/wm.h @@ -10,6 +10,8 @@ #include "types.h" #include "wallpaper.h" +typedef struct config_t config_t; + typedef struct wm_t { padding_t padding; uint16_t border_width; @@ -19,6 +21,7 @@ typedef struct wm_t { bool need_restart; GMainLoop *loop; status_t *status; + const config_t *config; client_t *client_list; client_t *client_stack_list;