Compare commits

..

10 Commits

Author SHA1 Message Date
9f9adf7e2e feat(backend): 写入 _NET_SUPPORTED 属性声明 EWMH 支持能力 2026-06-16 23:33:05 +08:00
00df685184 fix(backend): 修复 backend_poll_event 返回值意义不明导致的事件循环 bug
原因:之前的 backend_poll_event 返回 false 有两种情况
1. 当前就绪的事件已全部处理,没有事件可读了
2. 没有可以转化为感兴趣归一化的事件

当返回值为 false 并且时第二种情况时,底层的 xcb 原始事件未处理完,但不
再处理事件。导致有一部分事件一直无法处理,直到下一批事件待处理时通过文
件描述符状态变更唤醒新一轮的事件读取。这就会导致有些事件处理不及时,造
成卡顿的情况。

通过如下措施解决这个问题:
1. backend_poll_event 返回值只表示是否还有事件未处理完
2. event_t 引入 ZDWM_EVENT_NONE 表示事件为空,无人对该事件感兴趣也不需
   要清理
2026-06-16 23:16:55 +08:00
6d06b61134 fix(bar): workspaces 移除窗口时找到后立即 break 避免不必要的遍历 2026-06-16 19:13:30 +08:00
30dfb19a7b fix(config): 补全默认全景色 2026-06-16 19:11:49 +08:00
195733e008 fix(core): 补全窗口属性变更通知 2026-06-16 19:00:19 +08:00
6f1421f5b5 fix(bar): 重绘前清空内容并填充背景以消除脏区域残留 2026-06-16 17:49:34 +08:00
9bbdb26f06 fix(bar): 绘制 cell 文本前校验其文本避免渲染空内容 2026-06-16 17:22:52 +08:00
6e3e7ee937 fix(bar): 修复窗口列表布局 bug 导致的窗口列表 cell 重叠 2026-06-16 17:19:21 +08:00
9d35d17493 fix(bar): 修复 cell text/fg/bg 字符串内存泄露和 p_delete 参数错误 2026-06-16 17:17:00 +08:00
a4bd0b53de feat(bar): 添加窗口列表显示当前 workspace 的窗口列表 2026-06-16 17:14:59 +08:00
15 changed files with 308 additions and 23 deletions

View File

@@ -33,6 +33,7 @@ add_executable(${APP_NAME}
${SOURCE_DIR}/bar/binding.c ${SOURCE_DIR}/bar/binding.c
${SOURCE_DIR}/bar/cell.c ${SOURCE_DIR}/bar/cell.c
${SOURCE_DIR}/bar/text.c ${SOURCE_DIR}/bar/text.c
${SOURCE_DIR}/bar/windows.c
${SOURCE_DIR}/bar/workspaces.c ${SOURCE_DIR}/bar/workspaces.c
${SOURCE_DIR}/base/color.c ${SOURCE_DIR}/base/color.c

View File

@@ -39,6 +39,7 @@ typedef struct zdwm_bar_config_t {
const char *binding_mode_bg; const char *binding_mode_bg;
const char *binding_mode_fg; const char *binding_mode_fg;
uint32_t window_padding_x;
const char *window_bg; const char *window_bg;
const char *window_fg; const char *window_fg;
const char *window_focused_bg; const char *window_focused_bg;

View File

@@ -60,6 +60,21 @@ static void atoms_init(backend_t *backend) {
*atom->atom = reply->atom; *atom->atom = reply->atom;
p_delete(&reply); p_delete(&reply);
} }
#define EWMH_ITEM(name) atoms->name,
xcb_atom_t ewmh_supported_atoms[] = {EWMH_ATOMS(EWMH_ITEM)};
#undef EWMH_ITEM
auto root = backend->screen->root;
xcb_change_property(
conn,
XCB_PROP_MODE_REPLACE,
root,
atoms->_NET_SUPPORTED,
XCB_ATOM_ATOM,
32,
countof(ewmh_supported_atoms),
ewmh_supported_atoms
);
} }
static void create_wm_check_window(backend_t *backend) { static void create_wm_check_window(backend_t *backend) {

View File

@@ -492,7 +492,8 @@ bool backend_poll_event(backend_t *backend, event_t *event) {
auto raw_event = xcb_poll_for_event(backend->conn); auto raw_event = xcb_poll_for_event(backend->conn);
if (!raw_event) return false; if (!raw_event) return false;
return handle_event(backend, raw_event, event); handle_event(backend, raw_event, event);
return true;
} }
bool backend_next_event(backend_t *backend, event_t *event) { bool backend_next_event(backend_t *backend, event_t *event) {

View File

@@ -9,21 +9,9 @@
#include "base/window_list.h" #include "base/window_list.h"
#include "core/event.h" #include "core/event.h"
#define ATOM_LIST(X) \ #define EWMH_ATOMS(X) \
X(COMPOUND_TEXT) \
X(UTF8_STRING) \
\
X(WM_WINDOW_ROLE) \
X(WM_NAME) \
X(WM_CHANGE_STATE) \
X(_NET_WM_NAME) \ X(_NET_WM_NAME) \
\
X(WM_PROTOCOLS) \
X(WM_TAKE_FOCUS) \
X(WM_DELETE_WINDOW) \
X(_NET_ACTIVE_WINDOW) \ X(_NET_ACTIVE_WINDOW) \
\
X(_NET_SUPPORTING_WM_CHECK) \
X(_NET_WM_PID) \ X(_NET_WM_PID) \
\ \
X(_NET_CLIENT_LIST) \ X(_NET_CLIENT_LIST) \
@@ -56,6 +44,22 @@
X(_NET_WM_WINDOW_TYPE_DND) \ X(_NET_WM_WINDOW_TYPE_DND) \
X(_NET_WM_WINDOW_TYPE_NOTIFICATION) X(_NET_WM_WINDOW_TYPE_NOTIFICATION)
#define ATOM_LIST(X) \
X(COMPOUND_TEXT) \
X(UTF8_STRING) \
\
X(WM_WINDOW_ROLE) \
X(WM_NAME) \
X(WM_CHANGE_STATE) \
\
X(WM_PROTOCOLS) \
X(WM_TAKE_FOCUS) \
X(WM_DELETE_WINDOW) \
\
X(_NET_SUPPORTING_WM_CHECK) \
X(_NET_SUPPORTED) \
EWMH_ATOMS(X)
typedef struct atoms_t { typedef struct atoms_t {
#define DECLARATION_ATOM(name) xcb_atom_t name; #define DECLARATION_ATOM(name) xcb_atom_t name;
ATOM_LIST(DECLARATION_ATOM); ATOM_LIST(DECLARATION_ATOM);

View File

@@ -12,8 +12,10 @@
#include "bar/cell.h" #include "bar/cell.h"
#include "bar/text.h" #include "bar/text.h"
#include "bar/types.h" #include "bar/types.h"
#include "bar/windows.h"
#include "bar/workspaces.h" #include "bar/workspaces.h"
#include "base/array.h" #include "base/array.h"
#include "base/color.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/memory.h" #include "base/memory.h"
#include "base/time.h" #include "base/time.h"
@@ -103,6 +105,31 @@ static void bar_output_add_binding(
bar_binding_add_listeners(listeners, item->state); bar_binding_add_listeners(listeners, item->state);
} }
static void bar_output_add_windows(
bar_output_t *bar_output,
zdwm_bar_config_t *config,
listeners_t *listeners
) {
auto item = &bar_output->center;
bar_windows_config_t window_config = {
.cell_padding = VALUE(config->window_padding_x, config->padding_x),
.bg = VALUE(config->window_bg, config->bg),
.fg = VALUE(config->window_fg, config->fg),
.focused_bg = VALUE(config->window_focused_bg, config->bg),
.focused_fg = VALUE(config->window_focused_fg, config->fg),
};
item->cell_padding = window_config.cell_padding;
item->api = bar_windows;
auto create_state = item->api.create_state;
auto output_id = bar_output->output_id;
if (create_state) item->state = create_state(output_id, &window_config);
bar_windows_add_listeners(listeners, item->state);
}
void bar_init(bar_t *bar, listeners_t *listeners) { void bar_init(bar_t *bar, listeners_t *listeners) {
auto c = &bar->config; auto c = &bar->config;
bar->ctx = text_context_create(c->font_family, c->font_size, c->dpi); bar->ctx = text_context_create(c->font_family, c->font_size, c->dpi);
@@ -113,6 +140,7 @@ void bar_init(bar_t *bar, listeners_t *listeners) {
bar_output->height = bar->config.height; bar_output->height = bar->config.height;
bar_output_add_workspace(bar_output, &bar->config, listeners); bar_output_add_workspace(bar_output, &bar->config, listeners);
bar_output_add_binding(bar_output, &bar->config, listeners); bar_output_add_binding(bar_output, &bar->config, listeners);
bar_output_add_windows(bar_output, &bar->config, listeners);
} }
bar->timerfd = time_create_monotonic_timerfd_by_fps(bar->config.fps); bar->timerfd = time_create_monotonic_timerfd_by_fps(bar->config.fps);
@@ -233,6 +261,7 @@ static void bar_output_layout(bar_output_t *bar_output, text_context_t *ctx) {
.start = start, .start = start,
.end = start + width + center->cell_padding * 2, .end = start + width + center->cell_padding * 2,
}; };
start = region.end;
bar_cell_set_region(center, i, region); bar_cell_set_region(center, i, region);
} }
} }
@@ -281,7 +310,7 @@ static void bar_item_draw(
.width = cell_area.width - item->cell_padding * 2, .width = cell_area.width - item->cell_padding * 2,
.height = height, .height = height,
}; };
if (text_area.width > 0) { if (text_area.width > 0 && cell->text && cell->text[0] != '\0') {
draw_text(cr, ctx, cell->text, &cell->fg, text_area); draw_text(cr, ctx, cell->text, &cell->fg, text_area);
} }
@@ -317,8 +346,25 @@ static inline bool bar_output_is_dirty(bar_output_t *bar_output) {
return false; return false;
} }
static void bar_output_draw(bar_output_t *bar_output, text_context_t *ctx) { static inline void clean_cairo_context(cairo_t *cr) {
cairo_save(cr);
cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
cairo_paint(cr);
cairo_restore(cr);
}
static void
bar_output_draw(bar_output_t *bar_output, text_context_t *ctx, color_t *bg) {
auto cr = bar_output->cr; auto cr = bar_output->cr;
clean_cairo_context(cr);
zdwm_rect_t output_area = {
.x = 0,
.y = 0,
.width = bar_output->width,
.height = bar_output->height,
};
draw_background(cr, bg, output_area);
auto left = &bar_output->left; auto left = &bar_output->left;
for (size_t i = 0; i < left->count; ++i) { for (size_t i = 0; i < left->count; ++i) {
@@ -342,7 +388,7 @@ bool bar_draw(bar_t *bar) {
if (!bar_output_is_dirty(bar_output)) continue; if (!bar_output_is_dirty(bar_output)) continue;
bar_output_layout(bar_output, ctx); bar_output_layout(bar_output, ctx);
bar_output_draw(bar_output, ctx); bar_output_draw(bar_output, ctx, &bar->palette.bg);
changed = true; changed = true;
} }

View File

@@ -17,7 +17,7 @@ static void bar_cell_set_count(zdwm_bar_item_t *item, size_t count) {
for (size_t i = 0; i < item->count; ++i) { for (size_t i = 0; i < item->count; ++i) {
auto cell = &item->cells[i]; auto cell = &item->cells[i];
p_delete(cell->text); p_delete(&cell->text);
p_delete(&cell->fg_text); p_delete(&cell->fg_text);
p_delete(&cell->bg_text); p_delete(&cell->bg_text);
} }
@@ -51,6 +51,7 @@ bar_cell_set_bg(zdwm_bar_item_t *item, size_t index, const char *color) {
if (cell->bg_text && strcmp(color, cell->bg_text) == 0) return; if (cell->bg_text && strcmp(color, cell->bg_text) == 0) return;
p_delete(&cell->bg_text);
cell->bg_text = p_strdup(color); cell->bg_text = p_strdup(color);
color_parse(color, &cell->bg); color_parse(color, &cell->bg);
@@ -65,6 +66,7 @@ bar_cell_set_fg(zdwm_bar_item_t *item, size_t index, const char *color) {
auto cell = &item->cells[index]; auto cell = &item->cells[index];
if (cell->fg_text && strcmp(color, cell->fg_text) == 0) return; if (cell->fg_text && strcmp(color, cell->fg_text) == 0) return;
p_delete(&cell->fg_text);
cell->fg_text = p_strdup(color); cell->fg_text = p_strdup(color);
color_parse(color, &cell->fg); color_parse(color, &cell->fg);

175
src/bar/windows.c Normal file
View File

@@ -0,0 +1,175 @@
#include "bar/windows.h"
#include <assert.h>
#include <stddef.h>
#include <zdwm/bar.h>
#include <zdwm/listeners.h>
#include <zdwm/types.h>
#include "base/array.h"
#include "base/memory.h"
#include "core/listeners.h"
typedef struct bar_windows_state_t {
zdwm_output_id_t output_id;
zdwm_workspace_id_t workspace_id;
zdwm_window_t *windows;
size_t count;
size_t capacity;
bar_windows_config_t config;
bool workspace_inited;
bool dirty;
} bar_windows_state_t;
static zdwm_window_t *
state_get_window(bar_windows_state_t *state, zdwm_window_id_t window_id) {
for (size_t i = 0; i < state->count; ++i) {
auto window = &state->windows[i];
if (window->id == window_id) return window;
}
return nullptr;
}
static void *
bar_windows_create_state(zdwm_output_id_t output_id, void *config) {
const bar_windows_config_t *windows_config = config;
assert(windows_config);
auto state = p_new(bar_windows_state_t, 1);
state->output_id = output_id;
state->workspace_id = ZDWM_WORKSPACE_ID_INVALID;
state->config = *windows_config;
return state;
}
static void bar_windows_update(
zdwm_bar_item_t *item,
const zdwm_bar_cell_api_t *cells,
void *state
) {
bar_windows_state_t *data = state;
if (!data->workspace_inited || !data->dirty) return;
zdwm_window_t *windows = nullptr;
size_t count = 0, capacity = 0;
for (size_t i = 0; i < data->count; ++i) {
auto win = &data->windows[i];
if (win->workspace != data->workspace_id || win->skip_taskbar) continue;
auto window = array_push(windows, count, capacity);
*window = *win;
}
cells->set_cell_count(item, count);
for (size_t i = 0; i < count; ++i) {
auto window = &windows[i];
auto bg = window->focused ? data->config.focused_bg : data->config.bg;
auto fg = window->focused ? data->config.focused_fg : data->config.fg;
cells->cell_set_text(item, i, window->title);
cells->cell_set_bg(item, i, bg);
cells->cell_set_fg(item, i, fg);
}
p_delete(&windows);
data->dirty = false;
}
static void bar_windows_destroy_state(void *state) {
bar_windows_state_t *data = state;
p_delete(&data->windows);
p_delete(&data);
}
zdwm_bar_item_type_t bar_windows = {
.create_state = bar_windows_create_state,
.update = bar_windows_update,
.destroy_state = bar_windows_destroy_state,
.update_interval_ms = 0,
};
static void bar_windows_workspace_active(
zdwm_output_id_t output_id,
zdwm_workspace_id_t workspace_id,
void *user_data
) {
bar_windows_state_t *state = user_data;
if (state->output_id != output_id) return;
if (state->workspace_id == workspace_id) return;
state->workspace_id = workspace_id;
state->workspace_inited = true;
state->dirty = true;
}
static void
bar_windows_add_window(const zdwm_window_t *window, void *user_data) {
bar_windows_state_t *state = user_data;
auto win = array_push(state->windows, state->count, state->capacity);
*win = *window;
state->dirty = true;
}
static void
bar_windows_initial(const zdwm_window_t *list, size_t count, void *user_data) {
bar_windows_state_t *state = user_data;
for (size_t i = 0; i < count; ++i) {
auto window = &list[i];
bar_windows_add_window(window, state);
}
state->dirty = true;
}
static void
bar_windows_update_window(const zdwm_window_t *window, void *user_data) {
bar_windows_state_t *state = user_data;
auto old_window = state_get_window(state, window->id);
*old_window = *window;
state->dirty = true;
}
static void
bar_windows_remove_window(zdwm_window_id_t window_id, void *user_data) {
bar_windows_state_t *state = user_data;
bool found = false;
size_t index = 0;
for (size_t i = 0; i < state->count; ++i) {
auto window = &state->windows[i];
if (window->id == window_id) {
found = true;
index = i;
break;
}
}
if (!found) return;
if (array_erase(state->windows, state->count, index)) {
state->dirty = true;
}
}
void bar_windows_add_listeners(listeners_t *listeners, void *state) {
#define ADD(ADD_FN, LISTENER) ADD_FN(listeners, LISTENER, state)
ADD(listeners_add_active_workspace_listener, bar_windows_workspace_active);
ADD(listeners_add_initial_window_listener, bar_windows_initial);
ADD(listeners_add_window_added_listener, bar_windows_add_window);
ADD(listeners_add_window_updated_listener, bar_windows_update_window);
ADD(listeners_add_window_removed_listener, bar_windows_remove_window);
#undef ADD
}

18
src/bar/windows.h Normal file
View File

@@ -0,0 +1,18 @@
#pragma once
#include <stdint.h>
#include <zdwm/bar.h>
#include "core/listeners.h"
typedef struct bar_windows_config_t {
uint32_t cell_padding;
const char *bg;
const char *fg;
const char *focused_bg;
const char *focused_fg;
} bar_windows_config_t;
extern zdwm_bar_item_type_t bar_windows;
void bar_windows_add_listeners(listeners_t *listeners, void *state);

View File

@@ -276,6 +276,7 @@ void bar_workspace_window_removed(zdwm_window_id_t window_id, void *user_data) {
if (window->id == window_id) { if (window->id == window_id) {
found = true; found = true;
index = i; index = i;
break;
} }
} }

View File

@@ -178,6 +178,7 @@ bool config_defaults_build(
.dpi = 144, .dpi = 144,
.bg = "#222222", .bg = "#222222",
.fg = "#bbbbbb",
.tag_cell_padding_x = 10, .tag_cell_padding_x = 10,
.tag_indicator_width = 4, .tag_indicator_width = 4,
@@ -194,6 +195,9 @@ bool config_defaults_build(
.binding_padding_x = 2, .binding_padding_x = 2,
.binding_mode_bg = "#900000", .binding_mode_bg = "#900000",
.binding_mode_fg = "#ffffff", .binding_mode_fg = "#ffffff",
.window_focused_bg = "#005577",
.window_focused_fg = "#eeeeee",
}; };
api->set_bar_config(builder, bar_config); api->set_bar_config(builder, bar_config);

View File

@@ -21,6 +21,16 @@ backend_detect_t *backend_detect(backend_t *backend);
void backend_detect_destroy(backend_detect_t *detect); void backend_detect_destroy(backend_detect_t *detect);
int backend_get_fd(backend_t *backend); int backend_get_fd(backend_t *backend);
/**
* @brief 非阻塞地从 backend 中读取下一个归一化的事件
*
* @details
* 1. 函数立即返回
* 2. 只要取到了事件,函数就返回 true ,事件填充到 event 参数中
*
* @returns 如果还有下一个事件待读取,返回 true ,否则返回 false
*/
bool backend_poll_event(backend_t *backend, event_t *event); bool backend_poll_event(backend_t *backend, event_t *event);
/** /**

View File

@@ -4,7 +4,7 @@
#include "core/window.h" #include "core/window.h"
void event_cleanup(event_t *event) { void event_cleanup(event_t *event) {
if (!event) return; if (!event || event->type == ZDWM_EVENT_NONE) return;
switch (event->type) { switch (event->type) {
case ZDWM_EVENT_WINDOW_MAP_REQUEST: case ZDWM_EVENT_WINDOW_MAP_REQUEST:

View File

@@ -6,6 +6,7 @@
#include "core/window.h" #include "core/window.h"
typedef enum event_type_t { typedef enum event_type_t {
ZDWM_EVENT_NONE,
ZDWM_EVENT_KEY_PRESS, ZDWM_EVENT_KEY_PRESS,
ZDWM_EVENT_POINTER_BUTTON_PRESS, ZDWM_EVENT_POINTER_BUTTON_PRESS,
ZDWM_EVENT_POINTER_BUTTON_RELEASE, ZDWM_EVENT_POINTER_BUTTON_RELEASE,

View File

@@ -558,9 +558,11 @@ static void route_window_remove(
} }
static void route_window_metadata_changed( static void route_window_metadata_changed(
state_t *state, const policy_context_t *ctx,
const window_metadata_change_event_t *e const window_metadata_change_event_t *e
) { ) {
auto state = ctx->state;
auto window = state_window_get(state, e->window); auto window = state_window_get(state, e->window);
if (!window) return; if (!window) return;
@@ -582,6 +584,7 @@ static void route_window_metadata_changed(
if (e->changed_fields & ZDWM_WINDOW_METADATA_CHANGE_INSTANCE) { if (e->changed_fields & ZDWM_WINDOW_METADATA_CHANGE_INSTANCE) {
state_window_set_instance(state, window_id, metadata->instance_name); state_window_set_instance(state, window_id, metadata->instance_name);
} }
listeners_notify_window_updated(ctx->listeners, state, window_id);
} }
static void route_window_activate_request( static void route_window_activate_request(
@@ -683,7 +686,7 @@ void policy_route_event(
route_window_remove(state, &event->as.window_remove, out); route_window_remove(state, &event->as.window_remove, out);
break; break;
case ZDWM_EVENT_WINDOW_METADATA_CHANGED: case ZDWM_EVENT_WINDOW_METADATA_CHANGED:
route_window_metadata_changed(state, &event->as.window_metadata_change); route_window_metadata_changed(ctx, &event->as.window_metadata_change);
break; break;
case ZDWM_EVENT_WINDOW_ACTIVATE_REQUEST: { case ZDWM_EVENT_WINDOW_ACTIVATE_REQUEST: {
auto data = &event->as.window_activate_request; auto data = &event->as.window_activate_request;
@@ -857,8 +860,10 @@ unmanage_window(const policy_context_t *ctx, window_id_t window, plan_t *plan) {
if (output->current_workspace_id != workspace_id) return; if (output->current_workspace_id != workspace_id) return;
if (old_focused_window != workspace->focused_window_id) { auto focused_window_id = workspace->focused_window_id;
plan_push_focus_effect(plan, workspace->focused_window_id); if (old_focused_window != focused_window_id) {
plan_push_focus_effect(plan, focused_window_id);
listeners_notify_window_updated(ctx->listeners, state, focused_window_id);
} }
} }
@@ -877,6 +882,7 @@ focus_window(const policy_context_t *ctx, window_id_t window, plan_t *plan) {
plan_push_focus_effect(plan, workspace->focused_window_id); plan_push_focus_effect(plan, workspace->focused_window_id);
listeners_notify_window_updated(ctx->listeners, state, window); listeners_notify_window_updated(ctx->listeners, state, window);
listeners_notify_window_updated(ctx->listeners, state, old_focused_window);
} }
static void kill_window(state_t *state, window_id_t window, plan_t *plan) { static void kill_window(state_t *state, window_id_t window, plan_t *plan) {