Compare commits

...

8 Commits

Author SHA1 Message Date
dee4073996 feat(bar): 新增绑定模式指示器显示当前 binding mode
- 新增 bar/binding: 监听 binding mode 变更,显示当前模式名
- 支持点击单元循环切换绑定模式(binding_mode_cycle)
- 默认模式下按 show_default 配置决定是否隐藏单元
- 补充 binding 相关默认配置(颜色、内边距、是否显示默认模式)
2026-06-16 02:09:38 +08:00
e8b59e914c feat(bar): layout 颜色未配置时回退到默认背景色和前景色 2026-06-16 01:59:36 +08:00
ac65180203 feat(bar): 按间隔节流 bar_item 更新
- 新增 base/time: time_monotonic_ms 与 time_create_monotonic_timerfd_by_fps
- bar 初始化改用该工具创建 timerfd,移除内联的 itimerspec 拼装
- bar_item_update 按 update_interval_ms 节流,记录 last_updated_time 跳过过早更新
2026-06-16 00:31:07 +08:00
30e9751119 perf(bar): 仅在 bar 需要绘制新内容时才重绘并 flush 后端 2026-06-15 23:27:28 +08:00
41f82d7039 feat(bar): 在 workspace 列表末尾显示当前 workspace 布局符号 2026-06-15 19:50:42 +08:00
d0127f66e4 perf(runtime): bar 不可见时跳过更新避免无效重绘 2026-06-15 18:12:50 +08:00
4cef809f3c feat(policy): 窗口增删改时触发对应的 listeners 通知 2026-06-15 18:08:29 +08:00
d97cfba8dd fix(bar): 修复 workspace bar state 销毁时的内存泄露 2026-06-15 17:03:28 +08:00
14 changed files with 284 additions and 30 deletions

View File

@@ -30,6 +30,7 @@ add_executable(${APP_NAME}
${SOURCE_DIR}/backend/x11/window.c
${SOURCE_DIR}/bar/bar.c
${SOURCE_DIR}/bar/binding.c
${SOURCE_DIR}/bar/cell.c
${SOURCE_DIR}/bar/text.c
${SOURCE_DIR}/bar/workspaces.c
@@ -37,6 +38,7 @@ add_executable(${APP_NAME}
${SOURCE_DIR}/base/color.c
${SOURCE_DIR}/base/log.c
${SOURCE_DIR}/base/process.c
${SOURCE_DIR}/base/time.c
${SOURCE_DIR}/base/window_list.c
${SOURCE_DIR}/config/defaults.c

View File

@@ -33,8 +33,12 @@ typedef struct zdwm_bar_config_t {
const char *layout_bg;
const char *layout_fg;
bool binding_show_default;
uint32_t binding_padding_x;
const char *binding_mode_bg;
const char *binding_mode_fg;
const char *window_bg;
const char *window_fg;
const char *window_focused_bg;

View File

@@ -1,16 +1,14 @@
#include "bar/bar.h"
#include <assert.h>
#include <bits/time.h>
#include <bits/types/struct_itimerspec.h>
#include <cairo.h>
#include <stddef.h>
#include <stdint.h>
#include <sys/timerfd.h>
#include <unistd.h>
#include <zdwm/bar.h>
#include <zdwm/types.h>
#include "bar/binding.h"
#include "bar/cell.h"
#include "bar/text.h"
#include "bar/types.h"
@@ -18,6 +16,7 @@
#include "base/array.h"
#include "base/macros.h"
#include "base/memory.h"
#include "base/time.h"
#include "core/listeners.h"
static zdwm_bar_item_t *bar_add_item(
@@ -65,6 +64,8 @@ void bar_output_add_workspace(
.active_fg = config->tag_active_fg,
.urgent_bg = config->tag_urgent_bg,
.urgent_fg = config->tag_urgent_fg,
.layout_bg = VALUE(config->layout_bg, config->bg),
.layout_fg = VALUE(config->layout_fg, config->fg),
};
auto item = bar_add_item(
bar_output->output_id,
@@ -78,6 +79,28 @@ void bar_output_add_workspace(
bar_workspace_add_listeners(listeners, item->state);
}
static void bar_output_add_binding(
bar_output_t *bar_output,
zdwm_bar_config_t *config,
listeners_t *listeners
) {
bar_binding_config_t binding_config = {
.show_default = config->binding_show_default,
.cell_padding = VALUE(config->binding_padding_x, config->padding_x),
.bg = VALUE(config->binding_mode_bg, config->bg),
.fg = VALUE(config->binding_mode_fg, config->fg),
};
auto item = bar_add_item(
bar_output->output_id,
&bar_output->left,
bar_binding,
&binding_config
);
item->cell_padding = binding_config.cell_padding;
bar_binding_add_listeners(listeners, item->state);
}
void bar_init(bar_t *bar, listeners_t *listeners) {
auto c = &bar->config;
bar->ctx = text_context_create(c->font_family, c->font_size, c->dpi);
@@ -87,23 +110,10 @@ void bar_init(bar_t *bar, listeners_t *listeners) {
bar_output->height = bar->config.height;
bar_output_add_workspace(bar_output, &bar->config, listeners);
bar_output_add_binding(bar_output, &bar->config, listeners);
}
bar->timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
auto interval_ns = 1'000'000'000ULL / bar->config.fps;
struct itimerspec timer_spec = {
.it_interval =
{
.tv_sec = interval_ns / 1'000'000'000,
.tv_nsec = interval_ns % 1'000'000'000,
},
.it_value = {
.tv_sec = 0,
.tv_nsec = interval_ns % 1'000'000'000,
},
};
timerfd_settime(bar->timerfd, 0, &timer_spec, nullptr);
bar->timerfd = time_create_monotonic_timerfd_by_fps(bar->config.fps);
}
static void bar_side_cleanup(bar_side_t *side) {
@@ -137,7 +147,15 @@ void bar_cleanup(bar_t *bar) {
static inline void bar_item_update(zdwm_bar_item_t *item) {
auto update = item->api.update;
if (update) update(item, &bar_cell_api, item->state);
if (!update) return;
auto update_interval_ms = item->api.update_interval_ms;
auto last_updated_time = item->last_updated_time;
auto now = time_monotonic_ms();
if (now < last_updated_time + update_interval_ms) return;
item->last_updated_time = now;
update(item, &bar_cell_api, item->state);
}
static inline void bar_side_update(bar_side_t *side) {
@@ -300,8 +318,6 @@ static inline bool bar_output_is_dirty(bar_output_t *bar_output) {
}
static void bar_output_draw(bar_output_t *bar_output, text_context_t *ctx) {
if (!bar_output_is_dirty(bar_output)) return;
auto cr = bar_output->cr;
auto left = &bar_output->left;
@@ -316,12 +332,19 @@ static void bar_output_draw(bar_output_t *bar_output, text_context_t *ctx) {
bar_item_draw(cr, &bar_output->center, ctx, bar_output->height);
}
void bar_draw(bar_t *bar) {
bool bar_draw(bar_t *bar) {
auto ctx = bar->ctx;
bool changed = false;
for (size_t i = 0; i < bar->count; ++i) {
auto bar_output = &bar->bars[i];
if (!bar_output_is_dirty(bar_output)) continue;
bar_output_layout(bar_output, ctx);
bar_output_draw(bar_output, ctx);
changed = true;
}
return changed;
}

View File

@@ -41,4 +41,4 @@ typedef struct bar_t {
void bar_init(bar_t *bar, listeners_t *listeners);
void bar_cleanup(bar_t *bar);
void bar_update(bar_t *bar);
void bar_draw(bar_t *bar);
bool bar_draw(bar_t *bar);

104
src/bar/binding.c Normal file
View File

@@ -0,0 +1,104 @@
#include "bar/binding.h"
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <zdwm/action.h>
#include <zdwm/bar.h>
#include <zdwm/listeners.h>
#include <zdwm/types.h>
#include "base/memory.h"
#include "core/listeners.h"
typedef struct bar_binding_state_t {
zdwm_binding_mode_notify_t mode;
bar_binding_config_t config;
bool inited;
bool dirty;
} bar_binding_state_t;
static void *
bar_binding_create_state(zdwm_output_id_t output_id, void *config) {
const bar_binding_config_t *bar_config = config;
assert(bar_config);
auto state = p_new(bar_binding_state_t, 1);
state->config = *bar_config;
return state;
}
static void bar_binding_update(
zdwm_bar_item_t *item,
const zdwm_bar_cell_api_t *cells,
void *state
) {
auto data = (bar_binding_state_t *)state;
if (!data->inited || !data->dirty) return;
auto config = &data->config;
auto mode = &data->mode;
if (!config->show_default && mode->is_default_mode) {
cells->set_cell_count(item, 0);
} else {
constexpr auto index = 0;
cells->set_cell_count(item, 1);
cells->cell_set_text(item, index, mode->name);
cells->cell_set_bg(item, index, config->bg);
cells->cell_set_fg(item, index, config->fg);
}
data->dirty = false;
}
static zdwm_action_t bar_binding_on_click(
zdwm_bar_item_t *item,
size_t cell_index,
int32_t x,
void *state
) {
return (zdwm_action_t){
.type = ZDWM_ACTION_BINDING_MODE_CYCLE,
.as.binding_mode_cycle = {.delta = 1},
};
}
static void bar_binding_destroy_state(void *state) {
if (!state) return;
auto data = (bar_binding_state_t *)state;
p_delete(&data);
}
zdwm_bar_item_type_t bar_binding = {
.create_state = bar_binding_create_state,
.update = bar_binding_update,
.on_click = bar_binding_on_click,
.destroy_state = bar_binding_destroy_state,
.update_interval_ms = 0,
};
static void
bar_binding_mode_notify(zdwm_binding_mode_notify_t mode, void *user_data) {
auto state = (bar_binding_state_t *)user_data;
if (!state->inited) {
state->mode = mode;
state->inited = true;
state->dirty = true;
return;
}
if (state->mode.id == mode.id) return;
state->mode = mode;
state->dirty = true;
}
void bar_binding_add_listeners(listeners_t *listeners, void *state) {
listeners_add_binding_mode_notify(listeners, bar_binding_mode_notify, state);
}

17
src/bar/binding.h Normal file
View File

@@ -0,0 +1,17 @@
#pragma once
#include <stdint.h>
#include <zdwm/bar.h>
#include "core/listeners.h"
typedef struct bar_binding_config_t {
bool show_default;
uint32_t cell_padding;
const char *bg;
const char *fg;
} bar_binding_config_t;
extern zdwm_bar_item_type_t bar_binding;
void bar_binding_add_listeners(listeners_t *listeners, void *state);

View File

@@ -32,6 +32,7 @@ struct zdwm_bar_item_t {
void *state;
zdwm_bar_item_type_t api;
bool dirty;
uint64_t last_updated_time;
};
typedef struct bar_side_t {

View File

@@ -14,6 +14,7 @@
typedef struct bar_workspace_t {
zdwm_workspace_t info;
zdwm_layout_notify_t layout;
size_t window_count;
size_t urgent_window_count;
} bar_workspace_t;
@@ -101,9 +102,9 @@ static void bar_workspaces_update(
auto data = (bar_workspace_state_t *)state;
if (!data->list_inited || !data->active_inited || !data->dirty) return;
if (data->count != cell_api->get_cell_count(item)) {
cell_api->set_cell_count(item, data->count);
}
/* 当前 workspace 的 layout 指示器放在最后 */
cell_api->set_cell_count(item, data->count + 1);
auto layout_index = data->count;
auto config = &data->config;
for (size_t i = 0; i < data->count; ++i) {
@@ -121,6 +122,11 @@ static void bar_workspaces_update(
if (info->id == data->workspace_id) {
cell_api->cell_set_bg(item, i, config->active_bg);
cell_api->cell_set_fg(item, i, config->active_fg);
cell_api->cell_set_bg(item, layout_index, config->layout_bg);
cell_api->cell_set_fg(item, layout_index, config->layout_fg);
cell_api->cell_set_text(item, layout_index, workspace->layout.symbol);
continue;
}
@@ -161,6 +167,7 @@ static void bar_workspace_destroy_state(void *state) {
auto data = (bar_workspace_state_t *)state;
p_delete(&data->workspaces);
p_delete(&data->windows);
p_delete(&data);
}
@@ -212,6 +219,20 @@ static void bar_workspace_active_updated(
state->dirty = true;
}
static void
bar_workspace_layout_notify(zdwm_layout_notify_t layout, void *user_data) {
auto state = (bar_workspace_state_t *)user_data;
for (size_t i = 0; i < state->count; ++i) {
auto workspace = &state->workspaces[i];
if (workspace->info.id == layout.workspace) {
workspace->layout = layout;
state->dirty = true;
return;
}
}
}
void bar_workspace_window_added(const zdwm_window_t *window, void *user_data) {
auto state = (bar_workspace_state_t *)user_data;
@@ -270,6 +291,7 @@ void bar_workspace_add_listeners(listeners_t *listeners, void *state) {
ADD(listeners_add_initial_workspace_listener, bar_workspace_list_filter);
ADD(listeners_add_active_workspace_listener, bar_workspace_active_updated);
ADD(listeners_add_layout_notify, bar_workspace_layout_notify);
ADD(listeners_add_initial_window_listener, bar_workspace_initial_windows);
ADD(listeners_add_window_added_listener, bar_workspace_window_added);
ADD(listeners_add_window_updated_listener, bar_workspace_window_updated);

View File

@@ -14,6 +14,8 @@ typedef struct bar_workspace_config_t {
const char *active_fg;
const char *urgent_bg;
const char *urgent_fg;
const char *layout_bg;
const char *layout_fg;
} bar_workspace_config_t;
extern zdwm_bar_item_type_t bar_workspace;

56
src/base/time.c Normal file
View File

@@ -0,0 +1,56 @@
#include "base/time.h"
#include <bits/time.h>
#include <bits/types/struct_itimerspec.h>
#include <errno.h>
#include <stdint.h>
#include <string.h>
#include <sys/timerfd.h>
#include <time.h>
#include "base/log.h"
static int time_create_monotonic_timerfd(const struct itimerspec *itimerspec) {
auto timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
if (timerfd == -1) {
auto err = errno;
warn("time_create_timerfd failed: %s", strerror(err));
return timerfd;
}
timerfd_settime(timerfd, 0, itimerspec, nullptr);
return timerfd;
}
static constexpr auto NANOSECONDS_OF_ONE_SECOND = 1'000'000'000ULL;
int time_create_monotonic_timerfd_by_fps(uint32_t fps) {
if (fps == 0) fps = 30;
auto interval_ns = NANOSECONDS_OF_ONE_SECOND / fps;
/* clang-format off */
struct itimerspec timer_spec = {
.it_value = {
.tv_sec = 0,
.tv_nsec = interval_ns % NANOSECONDS_OF_ONE_SECOND,
},
.it_interval = {
.tv_sec = interval_ns / NANOSECONDS_OF_ONE_SECOND,
.tv_nsec = interval_ns % NANOSECONDS_OF_ONE_SECOND,
},
};
/* clang-format on */
return time_create_monotonic_timerfd(&timer_spec);
}
static uint64_t time_monotonic_ns(void) {
struct timespec timestamp = {0};
if (clock_gettime(CLOCK_MONOTONIC, &timestamp) == -1) {
warn("clock_gettime failed: %s", strerror(errno));
}
return timestamp.tv_sec * NANOSECONDS_OF_ONE_SECOND + timestamp.tv_nsec;
}
uint64_t time_monotonic_ms(void) { return time_monotonic_ns() / 1'000'000U; }

6
src/base/time.h Normal file
View File

@@ -0,0 +1,6 @@
#pragma once
#include <stdint.h>
int time_create_monotonic_timerfd_by_fps(uint32_t fps);
uint64_t time_monotonic_ms(void);

View File

@@ -187,6 +187,13 @@ bool config_defaults_build(
.tag_active_fg = "#eeeeee",
.tag_urgent_bg = "#222222",
.tag_urgent_fg = "#ff0000",
.layout_bg = "#222222",
.layout_fg = "#bbbbbb",
.binding_show_default = true,
.binding_padding_x = 2,
.binding_mode_bg = "#900000",
.binding_mode_fg = "#ffffff",
};
api->set_bar_config(builder, bar_config);

View File

@@ -772,6 +772,7 @@ static void manage_window(
state_window_set_floating(state, window_id, command->floating);
set_foucs_window(ctx, workspace_id, window_id, plan);
push_window_list_effect(state, plan);
listeners_notify_window_added(ctx->listeners, state, window_id);
auto need_layout = window_need_layout(window);
if (need_layout) {
@@ -847,6 +848,7 @@ unmanage_window(const policy_context_t *ctx, window_id_t window, plan_t *plan) {
state_window_remove(state, window);
push_window_list_effect(state, plan);
listeners_notify_window_removed(ctx->listeners, window);
if (need_layout) {
adjust_layout_windows_border_width(state, ctx->border->width, workspace_id);
@@ -874,6 +876,7 @@ focus_window(const policy_context_t *ctx, window_id_t window, plan_t *plan) {
if (old_focused_window == workspace->focused_window_id) return;
plan_push_focus_effect(plan, workspace->focused_window_id);
listeners_notify_window_updated(ctx->listeners, state, window);
}
static void kill_window(state_t *state, window_id_t window, plan_t *plan) {
@@ -903,11 +906,15 @@ static void raise_window(state_t *state, window_id_t window, plan_t *plan) {
plan_push_effect(plan, &restack_windows_effect);
}
static void withdraw_window(state_t *state, window_id_t window, plan_t *plan) {
static void
withdraw_window(const policy_context_t *ctx, window_id_t window, plan_t *plan) {
auto state = ctx->state;
auto win = state_window_get(state, window);
if (!win) return;
plan_push_withdraw_effect(plan, window);
listeners_notify_window_removed(ctx->listeners, window);
}
static void
@@ -1183,6 +1190,8 @@ static void change_window_state(
state_window_set_fixed_size(state, window->id, value);
break;
}
listeners_notify_window_updated(ctx->listeners, state, command->window);
}
static void send_window_to_workspace(
@@ -1449,7 +1458,7 @@ void policy_apply_command(
raise_window(state, cmd->as.raise.window, plan);
break;
case ZDWM_COMMAND_WITHDRAW_WINDOW:
withdraw_window(state, cmd->as.withdraw.window, plan);
withdraw_window(ctx, cmd->as.withdraw.window, plan);
break;
case ZDWM_COMMAND_CONFIGURE_WINDOW:
configure_window(state, &cmd->as.configure, plan);

View File

@@ -484,9 +484,10 @@ static void runtime_handle_event(runtime_t *runtime, short int revents) {
static inline void runtime_update_bar(runtime_t *runtime) {
auto bar = &runtime->bar;
if (!bar->visible) return;
bar_update(bar);
bar_draw(bar);
backend_flush(runtime->backend);
if (bar_draw(bar)) backend_flush(runtime->backend);
}
static void runtime_run_event_loop(runtime_t *runtime) {