Compare commits

...

6 Commits

Author SHA1 Message Date
1e3a571619 refactor(backend/X11): 合并批量 effect 中的 focus 操作
一批 effect 中可能包含多个 FOCUS_WINDOW,但最终只有一个窗口能获得焦点,
延迟到循环结束后只执行最后一次 focus 调用
2026-04-22 19:50:26 +08:00
d4c03fcfbd feat(core/policy): 完善 manage_window 和 switch_workspace 命令处理 2026-04-22 19:40:09 +08:00
663db3412f feat(backend/X11): 添加从 _NET_WM_STATE 中获取 urgent 属性的功能 2026-04-22 19:37:01 +08:00
af4a45a342 refactor(core/policy): 将接口返回类型从 bool 调整为 void
policy_route_event/policy_apply_command 两个函数的 out 参数中已经能包
含返回值所代表的意义了,因此直接将这两个函数的返回类型改为 void
2026-04-22 19:36:50 +08:00
413e68f572 refactor(core/plan): 使用 need_relayout 简化 dirty_flags 2026-04-22 19:09:21 +08:00
3121b49d1b fix(core/window): 添加设置窗口 floating 状态的限制条件
- 不能取消 fixed_size 和 sticky 状态窗口的 floating 状态
- 简化 window_need_layout 中浮动窗口不参加自动布局判断逻辑
2026-04-22 17:44:51 +08:00
9 changed files with 107 additions and 60 deletions

View File

@@ -453,6 +453,9 @@ bool backend_apply_effect(
window_configure_t *cfgs = p_new(window_configure_t, effect_count); window_configure_t *cfgs = p_new(window_configure_t, effect_count);
size_t cfg_count = 0; size_t cfg_count = 0;
bool focus_window_changed = false;
xcb_window_t focus_window = XCB_WINDOW_NONE;
for (size_t i = 0; i < effect_count; ++i) { for (size_t i = 0; i < effect_count; ++i) {
const effect_t *e = &effects[i]; const effect_t *e = &effects[i];
switch (e->type) { switch (e->type) {
@@ -463,7 +466,8 @@ bool backend_apply_effect(
xcb_unmap_window(conn, e->as.unmap.window); xcb_unmap_window(conn, e->as.unmap.window);
break; break;
case ZDWM_EFFECT_FOCUS_WINDOW: case ZDWM_EFFECT_FOCUS_WINDOW:
backend_focus_window(backend, e->as.focus.window); focus_window = e->as.focus.window;
focus_window_changed = true;
break; break;
case ZDWM_EFFECT_MOVE_WINDOW: { case ZDWM_EFFECT_MOVE_WINDOW: {
window_configure_t *cfg = window_configure_t *cfg =
@@ -514,6 +518,8 @@ bool backend_apply_effect(
} }
p_delete(&cfgs); p_delete(&cfgs);
if (focus_window_changed) backend_focus_window(backend, focus_window);
xcb_flush(conn); xcb_flush(conn);
return true; return true;
} }

View File

@@ -1,5 +1,6 @@
#include "core/event.h" #include "core/event.h"
#include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <xcb/xcb.h> #include <xcb/xcb.h>
#include <xcb/xcb_event.h> #include <xcb/xcb_event.h>
@@ -71,6 +72,18 @@ static window_geometry_mode_t geometry_mode_from_states(
return ZDWM_GEOMETRY_NORMAL; return ZDWM_GEOMETRY_NORMAL;
} }
static bool urgent_from_states(
const atoms_t *atoms,
const xcb_atom_t *state_atoms,
uint32_t state_count
) {
for (uint32_t i = 0; i < state_count; ++i) {
if (state_atoms[i] == atoms->_NET_WM_STATE_DEMANDS_ATTENTION) return true;
}
return false;
}
static bool handle_map_request( static bool handle_map_request(
backend_t *backend, backend_t *backend,
event_t *event, event_t *event,
@@ -127,6 +140,9 @@ static bool handle_map_request(
state_count, state_count,
&ev->props.state_count &ev->props.state_count
); );
if (!ev->urgent) {
ev->urgent = urgent_from_states(atoms, state_atoms, state_count);
}
} }
p_delete(&state_atoms); p_delete(&state_atoms);

View File

@@ -30,6 +30,8 @@
X(_NET_WM_STATE_MAXIMIZED_VERT) \ X(_NET_WM_STATE_MAXIMIZED_VERT) \
X(_NET_WM_STATE_MAXIMIZED_HORZ) \ X(_NET_WM_STATE_MAXIMIZED_HORZ) \
\ \
X(_NET_WM_STATE_DEMANDS_ATTENTION) \
\
X(_NET_WM_WINDOW_TYPE) \ X(_NET_WM_WINDOW_TYPE) \
X(_NET_WM_WINDOW_TYPE_NORMAL) \ X(_NET_WM_WINDOW_TYPE_NORMAL) \
X(_NET_WM_WINDOW_TYPE_DESKTOP) \ X(_NET_WM_WINDOW_TYPE_DESKTOP) \

View File

@@ -5,14 +5,6 @@
#include "core/types.h" #include "core/types.h"
typedef uint32_t dirty_mask_t;
typedef enum dirty_flags_t : dirty_mask_t {
ZDWM_DIRTY_NONE = 0,
ZDWM_DIRTY_LAYOUT = 1u << 0, /* 需要重新计算布局 */
ZDWM_DIRTY_OUTPUT = 1u << 1, /* 当前 output 有变更 */
} dirty_flags_t;
typedef enum effect_type_t { typedef enum effect_type_t {
ZDWM_EFFECT_MAP_WINDOW = 1, ZDWM_EFFECT_MAP_WINDOW = 1,
ZDWM_EFFECT_UNMAP_WINDOW, ZDWM_EFFECT_UNMAP_WINDOW,
@@ -74,7 +66,7 @@ typedef struct plan_t {
effect_t *effects; effect_t *effects;
size_t count; size_t count;
size_t capacity; size_t capacity;
dirty_mask_t dirty_flags; bool need_relayout;
} plan_t; } plan_t;
void plan_reset(plan_t *plan); void plan_reset(plan_t *plan);

View File

@@ -18,13 +18,13 @@ static workspace_id_t derive_window_workspace(const state_t *state) {
return state->outputs[state->current_output_index].current_workspace_id; return state->outputs[state->current_output_index].current_workspace_id;
} }
static bool route_map_request( static void route_map_request(
const state_t *state, const state_t *state,
const rules_t *rules, const rules_t *rules,
const window_map_request_event_t *e, const window_map_request_event_t *e,
command_buffer_t *out command_buffer_t *out
) { ) {
if (state_window_get(state, e->window)) return false; if (state_window_get(state, e->window)) return;
window_layer_type_t layer_type = window_classify_layer(&e->props); window_layer_type_t layer_type = window_classify_layer(&e->props);
if (e->transient_for != ZDWM_WINDOW_ID_INVALID) { if (e->transient_for != ZDWM_WINDOW_ID_INVALID) {
@@ -91,11 +91,9 @@ static bool route_map_request(
command_buffer_push(out, &switch_workspace_cmd); command_buffer_push(out, &switch_workspace_cmd);
} }
} }
return true;
} }
bool policy_route_event( void policy_route_event(
const policy_context_t *ctx, const policy_context_t *ctx,
const event_t *event, const event_t *event,
command_buffer_t *out command_buffer_t *out
@@ -104,14 +102,14 @@ bool policy_route_event(
switch (event->type) { switch (event->type) {
case ZDWM_EVENT_WINDOW_MAP_REQUEST: { case ZDWM_EVENT_WINDOW_MAP_REQUEST: {
auto rules = ctx->rules; auto rules = ctx->rules;
return route_map_request(state, rules, &event->as.window_map_request, out); route_map_request(state, rules, &event->as.window_map_request, out);
break;
} }
default: default:
return false;
} }
} }
static bool manage_window( static void manage_window(
const policy_context_t *ctx, const policy_context_t *ctx,
const manage_window_command_t *command, const manage_window_command_t *command,
plan_t *plan plan_t *plan
@@ -120,66 +118,100 @@ static bool manage_window(
auto window = state_window_add(state, &command->info); auto window = state_window_add(state, &command->info);
auto window_id = window->id; auto window_id = window->id;
state_window_set_workspace(state, window_id, command->workspace); state_window_set_workspace(state, window_id, command->workspace);
state_workspace_set_focused_window(state, command->workspace, window_id);
state_window_set_floating(state, window_id, command->floating); state_window_set_floating(state, window_id, command->floating);
if (!state_workspace_show(state, command->workspace)) return false; if (!state_workspace_show(state, command->workspace)) return;
effect_t map_effect = { effect_t map_effect = {
.type = ZDWM_EFFECT_MAP_WINDOW, .type = ZDWM_EFFECT_MAP_WINDOW,
.as.map.window = window_id, .as.map.window = window_id,
}; };
effect_t focus_effect = {
.type = ZDWM_EFFECT_FOCUS_WINDOW,
.as.focus.window = window_id,
};
plan_push_effect(plan, &map_effect); plan_push_effect(plan, &map_effect);
plan_push_effect(plan, &focus_effect);
if (window_need_layout(window)) { if (window_need_layout(window)) {
plan->dirty_flags |= ZDWM_DIRTY_LAYOUT; plan->need_relayout = true;
} }
return true;
} }
static bool switch_workspace( static void add_switch_workspace_effects(
const state_t *state,
workspace_id_t old_workspace,
workspace_id_t new_workspace,
plan_t *plan
) {
for (size_t i = 0; i < state->window_count; ++i) {
window_t *window = &state->windows[i];
if (window->sticky) continue;
if (window->workspace_id == old_workspace) {
effect_t unmap_effect = {
.type = ZDWM_EFFECT_UNMAP_WINDOW,
.as.unmap.window = window->id,
};
plan_push_effect(plan, &unmap_effect);
} else if (window->workspace_id == new_workspace) {
effect_t map_effect = {
.type = ZDWM_EFFECT_MAP_WINDOW,
.as.unmap.window = window->id,
};
plan_push_effect(plan, &map_effect);
}
}
const workspace_t *workspace = state_workspace_get(state, new_workspace);
if (!workspace) return;
effect_t focus_effect = {
.type = ZDWM_EFFECT_FOCUS_WINDOW,
.as.focus.window = workspace->focused_window_id
};
plan_push_effect(plan, &focus_effect);
}
static void switch_workspace(
state_t *state, state_t *state,
const switch_workspace_command_t *command, const switch_workspace_command_t *command,
plan_t *plan plan_t *plan
) { ) {
auto output_id = command->output; auto output_id = command->output;
auto workspace_id = command->workspace; auto workspace_id = command->workspace;
workspace_id_t old_workspace = ZDWM_WORKSPACE_ID_INVALID;
if (state_output_set_current_workspace(state, output_id, workspace_id)) { if (state_output_set_current_workspace(
plan->dirty_flags |= ZDWM_DIRTY_LAYOUT; state,
output_id,
workspace_id,
&old_workspace
)) {
plan->need_relayout = true;
add_switch_workspace_effects(state, old_workspace, workspace_id, plan);
} }
if (state_set_current_output(state, output_id)) { state_set_current_output(state, output_id);
plan->dirty_flags |= ZDWM_DIRTY_OUTPUT;
}
/* TODO: 后续考虑加上 ewmh 相关副作用 */
return false;
} }
bool policy_apply_command( void policy_apply_command(
const policy_context_t *ctx, const policy_context_t *ctx,
const command_buffer_t *command_buffer, const command_buffer_t *command_buffer,
plan_t *plan plan_t *plan
) { ) {
bool has_effect = false; auto state = ctx->state;
auto state = ctx->state;
for (size_t i = 0; i < command_buffer->count; ++i) { for (size_t i = 0; i < command_buffer->count; ++i) {
auto cmd = &command_buffer->items[i]; auto cmd = &command_buffer->items[i];
switch (cmd->type) { switch (cmd->type) {
case ZDWM_COMMAND_MANAGE_WINDOW: case ZDWM_COMMAND_MANAGE_WINDOW:
if (manage_window(ctx, &cmd->as.manage_window, plan)) { manage_window(ctx, &cmd->as.manage_window, plan);
has_effect = true;
}
break; break;
case ZDWM_COMMAND_SWITCH_WORKSPACE: case ZDWM_COMMAND_SWITCH_WORKSPACE:
if (switch_workspace(state, &cmd->as.switch_workspace, plan)) { switch_workspace(state, &cmd->as.switch_workspace, plan);
has_effect = true;
}
break; break;
default: default:
} }
} }
return has_effect;
} }

View File

@@ -17,11 +17,8 @@ typedef struct policy_context_t {
* @param ctx 策略上下文 * @param ctx 策略上下文
* @param event 需要翻译的事件 * @param event 需要翻译的事件
* @param out 命令输出缓冲区,路由产生的命令追加到此处 * @param out 命令输出缓冲区,路由产生的命令追加到此处
*
* @return true 产生了至少一条需要执行的命令
* @return false 产生了至少一条需要执行的命令
*/ */
bool policy_route_event( void policy_route_event(
const policy_context_t *ctx, const policy_context_t *ctx,
const event_t *event, const event_t *event,
command_buffer_t *out command_buffer_t *out
@@ -33,11 +30,8 @@ bool policy_route_event(
* @param ctx 策略上下文 * @param ctx 策略上下文
* @param command_buffer 待应用的命令序列 * @param command_buffer 待应用的命令序列
* @param plan 副作用累积器,记录后端需要执行的操作 * @param plan 副作用累积器,记录后端需要执行的操作
*
* @return true 产生了至少一项后端副作用
* @return false 无副作用产生
*/ */
bool policy_apply_command( void policy_apply_command(
const policy_context_t *ctx, const policy_context_t *ctx,
const command_buffer_t *command_buffer, const command_buffer_t *command_buffer,
plan_t *plan plan_t *plan

View File

@@ -265,7 +265,8 @@ void state_output_set_workarea(
bool state_output_set_current_workspace( bool state_output_set_current_workspace(
state_t *state, state_t *state,
output_id_t output_id, output_id_t output_id,
workspace_id_t workspace_id workspace_id_t workspace_id,
workspace_id_t *old_workspace_id
) { ) {
output_t *output = (output_t *)state_output_get(state, output_id); output_t *output = (output_t *)state_output_get(state, output_id);
if (!output) return false; if (!output) return false;
@@ -274,6 +275,7 @@ bool state_output_set_current_workspace(
if (output->current_workspace_id == workspace_id) return false; if (output->current_workspace_id == workspace_id) return false;
if (old_workspace_id) *old_workspace_id = output->current_workspace_id;
output->current_workspace_id = workspace_id; output->current_workspace_id = workspace_id;
return true; return true;
} }

View File

@@ -123,16 +123,19 @@ void state_output_set_workarea(
/** /**
* @brief 设置 output 的当前 workspace * @brief 设置 output 的当前 workspace
* *
* @param state 状态实例指针 * @param state 状态实例指针
* @param output_id 需要切换的当前 workspace 的 output 的 id * @param output_id 需要切换的当前 workspace 的 output 的 id
* @param workspace_id 目标 workspace 的 id * @param workspace_id 目标 workspace 的 id
* @param old_workspace_id 用于输出变更之前 output 显示的 workspace
* 如果不关心,可以传 nullptr
* *
* @return 若 workspace 需要切换返回 true ,否则返回 false * @return 若 workspace 需要切换返回 true ,否则返回 false
*/ */
bool state_output_set_current_workspace( bool state_output_set_current_workspace(
state_t *state, state_t *state,
output_id_t output_id, output_id_t output_id,
workspace_id_t workspace_id workspace_id_t workspace_id,
workspace_id_t *old_workspace_id
); );
size_t state_output_count(const state_t *state); size_t state_output_count(const state_t *state);
bool state_output_valid(const state_t *state, output_id_t id); bool state_output_valid(const state_t *state, output_id_t id);

View File

@@ -43,6 +43,8 @@ void window_set_geometry_mode(
} }
void window_set_floating(window_t *window, bool floating) { void window_set_floating(window_t *window, bool floating) {
if (!floating && (window->fixed_size || window->sticky)) return;
window->floating = floating; window->floating = floating;
if (floating) window->float_rect = window->frame_rect; if (floating) window->float_rect = window->frame_rect;
} }
@@ -121,9 +123,7 @@ void window_take_metadata(
} }
bool window_need_layout(const window_t *window) { bool window_need_layout(const window_t *window) {
if (window->sticky || window->floating) { if (window->floating) return false;
return false;
}
switch (window->geometry_mode) { switch (window->geometry_mode) {
case ZDWM_GEOMETRY_FULLSCREEN: case ZDWM_GEOMETRY_FULLSCREEN: