feat: 状态栏配置接入配置系统

This commit is contained in:
2026-03-09 00:47:18 +08:00
parent 35f0ea9680
commit d8cd91ea48
12 changed files with 644 additions and 34 deletions

View File

@@ -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)

81
src/config.c Normal file
View File

@@ -0,0 +1,81 @@
#include "config.h"
#include <string.h>
#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;
}

57
src/config.h Normal file
View File

@@ -0,0 +1,57 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#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);

View File

@@ -5,7 +5,10 @@
#include <stdint.h>
#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}},
{
@@ -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;

170
src/image.c Normal file
View File

@@ -0,0 +1,170 @@
#include "image.h"
#include <Imlib2.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#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;
}

36
src/image.h Normal file
View File

@@ -0,0 +1,36 @@
#pragma once
#include <cairo.h>
#include <stdbool.h>
#include <stdint.h>
/**
* @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);

View File

@@ -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,49 +240,108 @@ 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;
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;
}
}
}
static void monitor_draw_tasks(monitor_t *monitor) {
task_in_tag_t *task_list = monitor->selected_tag->task_list;

90
src/renderer.c Normal file
View File

@@ -0,0 +1,90 @@
#include "renderer.h"
#include <stdio.h>
#include <string.h>
#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];
}
}
}

41
src/renderer.h Normal file
View File

@@ -0,0 +1,41 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#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);

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;