Compare commits

..

9 Commits

Author SHA1 Message Date
9275d14c5f feat(runtime): 启动时扫描并接管已有窗口
从 handle_map_request 中提取 populate_window_event 供复用,
新增 backend_scan_windows 扫描 X11 根窗口子树,
runtime_scan 将扫描到的窗口走完整 policy 流程纳入管理。
route_map_request 增加 override_redirect 过滤。
2026-05-07 06:36:58 +08:00
c1a4795a85 feat(state): 初始化 float_rect 并在切换 floating 状态时双向同步 2026-05-07 04:11:44 +08:00
8fcf46979a feat(action): 绑定 toggle_fullscreen/maximize/floating 快捷键 2026-05-07 03:54:30 +08:00
56f8380028 feat(action): 实现 switch_workspace cycle_layout 和 cycle_output 操作 2026-05-07 03:21:27 +08:00
2c3c264ddd feat(action): 实现 toggle_fullscreen/maximize/floating/sticky 操作 2026-05-07 03:20:24 +08:00
3031adf3cb feat(policy): 处理窗口状态变更事件 2026-05-07 02:22:16 +08:00
1b6a4788be feat(policy): 处理窗口激活请求事件 2026-05-07 02:14:38 +08:00
474ed8dc69 fix(policy): 窗口移除前获取其是否需要自动布局结果 2026-05-07 01:59:56 +08:00
964de33b23 feat(policy): 处理窗口元数据变更事件 2026-05-07 01:58:00 +08:00
13 changed files with 400 additions and 37 deletions

View File

@@ -22,8 +22,10 @@
#include "base/macros.h"
#include "base/memory.h"
#include "base/window_list.h"
#include "core/event.h"
#include "core/plan.h"
#include "core/types.h"
#include "core/window.h"
#include "internal.h"
typedef struct atom_item_t {
@@ -625,3 +627,54 @@ bool backend_apply_effect(
xcb_flush(backend->conn);
return true;
}
backend_scan_result_t *backend_scan_windows(backend_t *backend) {
auto conn = backend->conn;
auto root = backend->screen->root;
auto cookie = xcb_query_tree_unchecked(conn, root);
auto reply = xcb_query_tree_reply(conn, cookie, nullptr);
if (!reply) return nullptr;
auto length = xcb_query_tree_children_length(reply);
if (!length) {
p_delete(&reply);
return nullptr;
}
auto list = xcb_query_tree_children(reply);
backend_scan_result_t *result = p_new(backend_scan_result_t, 1);
for (int i = 0; i < length; i++) {
auto slot = (window_map_request_event_t *)
array_push(result->windows, result->count, result->capacity);
if (!populate_window_event(backend, list[i], slot)) {
window_layer_props_cleanup(&slot->props);
window_metadata_cleanup(&slot->metadata);
result->count--;
}
}
p_delete(&reply);
if (result->count == 0) {
backend_scan_result_destroy(result);
result = nullptr;
return nullptr;
}
return result;
}
void backend_scan_result_destroy(backend_scan_result_t *result) {
if (!result) return;
for (size_t i = 0; i < result->count; i++) {
window_map_request_event_t *ev = &result->windows[i];
window_layer_props_cleanup(&ev->props);
window_metadata_cleanup(&ev->metadata);
}
p_delete(&result->windows);
p_delete(&result);
}

View File

@@ -91,18 +91,13 @@ static bool urgent_from_states(
return false;
}
static bool handle_map_request(
bool populate_window_event(
backend_t *backend,
event_t *event,
const xcb_map_request_event_t *xcb_event
xcb_window_t window,
window_map_request_event_t *ev
) {
xcb_window_t window = xcb_event->window;
event->type = ZDWM_EVENT_WINDOW_MAP_REQUEST;
window_map_request_event_t *ev = &event->as.window_map_request;
ev->window = (window_id_t)window;
ev->transient_for = window_get_transient_for(backend, window);
ev->window = (window_id_t)window;
ev->transient_for = window_get_transient_for(backend, window);
xcb_get_window_attributes_reply_t *wa =
window_get_attributes(backend, window);
@@ -167,6 +162,19 @@ static bool handle_map_request(
return true;
}
static bool handle_map_request(
backend_t *backend,
event_t *event,
const xcb_map_request_event_t *xcb_event
) {
event->type = ZDWM_EVENT_WINDOW_MAP_REQUEST;
return populate_window_event(
backend,
xcb_event->window,
&event->as.window_map_request
);
}
static bool handle_unmap_notify(
backend_t *backend,
event_t *event,

View File

@@ -7,6 +7,7 @@
#include <xcb/xproto.h>
#include "base/window_list.h"
#include "core/event.h"
#define ATOM_LIST(X) \
X(COMPOUND_TEXT) \
@@ -94,3 +95,9 @@ struct backend_t {
window_list_t map;
window_list_t kill;
};
bool populate_window_event(
struct backend_t *backend,
xcb_window_t window,
window_map_request_event_t *ev
);

View File

@@ -3,7 +3,6 @@
#include <zdwm/action.h>
#include <zdwm/types.h>
#include "base/log.h"
#include "base/macros.h"
static constexpr char launcher[] =
@@ -32,7 +31,6 @@ static void quit(
const zdwm_action_api_t *api,
const zdwm_action_arg_t *arg
) {
logger("quit: restart: %s\n", arg->b ? "true" : "false");
api->quit(runtime, arg->b);
}
@@ -45,6 +43,30 @@ static void raise_or_run(
api->raise_or_run(runtime, args->class_name, args->command);
}
static void toggle_fullscreen(
zdwm_runtime_t *runtime,
const zdwm_action_api_t *api,
const zdwm_action_arg_t *arg
) {
api->toggle_fullscreen(runtime);
}
static void toggle_maximize(
zdwm_runtime_t *runtime,
const zdwm_action_api_t *api,
const zdwm_action_arg_t *arg
) {
api->toggle_maximize(runtime);
}
static void toggle_floating(
zdwm_runtime_t *runtime,
const zdwm_action_api_t *api,
const zdwm_action_arg_t *arg
) {
api->toggle_floating(runtime);
}
bool config_defaults_build(
const zdwm_api_t *api,
zdwm_config_builder_t *builder,
@@ -118,6 +140,9 @@ bool config_defaults_build(
BIND(default_mode, Super "+e", raise_or_run, {.ptr = &editor});
BIND(default_mode, Super "+q", raise_or_run, {.ptr = &browser});
BIND(default_mode, Super "+a", raise_or_run, {.ptr = &chrome});
BIND(default_mode, Super "+f", toggle_fullscreen, {0});
BIND(default_mode, Super "+m", toggle_maximize, {0});
BIND(default_mode, Super "+Control+space", toggle_floating, {0});
#undef BIND

View File

@@ -2,6 +2,7 @@
#include <paths.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
@@ -10,6 +11,7 @@
#include "base/macros.h"
#include "core/command.h"
#include "core/command_buffer.h"
#include "core/runtime.h"
#include "core/state.h"
#include "core/types.h"
#include "core/window.h"
@@ -66,7 +68,7 @@ get_next_window_by_class(state_t *state, const char *class_name) {
}
void raise_or_run(
zdwm_runtime_t *runtime,
runtime_t *runtime,
const char *class_name,
const char *command
) {
@@ -88,3 +90,90 @@ void raise_or_run(
};
command_buffer_push(&runtime->command_buffer, &focus_cmd);
}
static const window_t *get_current_focused_window(const state_t *state) {
auto output = state_output_at(state, state->current_output_index);
auto workspace = state_workspace_get(state, output->current_workspace_id);
return state_window_get(state, workspace->focused_window_id);
}
void toggle_fullscreen(runtime_t *runtime) {
auto window = get_current_focused_window(&runtime->state);
if (!window) return;
command_t window_set_fullscreen_cmd = {
.type = ZDWM_COMMAND_WINDOW_SET_FULLSCREEN,
.as.fullscreen = {
.window = window->id,
.state = !window->fullscreen,
}
};
command_buffer_push(&runtime->command_buffer, &window_set_fullscreen_cmd);
}
void toggle_maximize(runtime_t *runtime) {
auto window = get_current_focused_window(&runtime->state);
if (!window) return;
command_t window_set_maximized_cmd = {
.type = ZDWM_COMMAND_WINDOW_SET_MAXIMIZED,
.as.maximized = {
.window = window->id,
.state = !window->maximized,
}
};
command_buffer_push(&runtime->command_buffer, &window_set_maximized_cmd);
}
void toggle_floating(runtime_t *runtime) {
auto window = get_current_focused_window(&runtime->state);
if (!window) return;
command_t window_set_floating_cmd = {
.type = ZDWM_COMMAND_WINDOW_SET_FLOATING,
.as.floating = {
.window = window->id,
.state = !window->floating,
}
};
command_buffer_push(&runtime->command_buffer, &window_set_floating_cmd);
}
void toggle_sticky(runtime_t *runtime) {
auto window = get_current_focused_window(&runtime->state);
if (!window) return;
command_t window_set_sticky_cmd = {
.type = ZDWM_COMMAND_WINDOW_SET_STICKY,
.as.sticky = {
.window = window->id,
.state = !window->sticky,
}
};
command_buffer_push(&runtime->command_buffer, &window_set_sticky_cmd);
}
void switch_workspace(runtime_t *runtime, workspace_id_t workspace_id) {
auto workspace = state_workspace_get(&runtime->state, workspace_id);
if (!workspace) return;
command_t switch_workspace_cmd = {
.type = ZDWM_COMMAND_SWITCH_WORKSPACE,
.as.switch_workspace.workspace = workspace_id
};
command_buffer_push(&runtime->command_buffer, &switch_workspace_cmd);
}
void cycle_layout(runtime_t *runtime, int32_t delta) {
auto state = &runtime->state;
auto output = state_output_at(state, state->current_output_index);
auto workspace = state_workspace_get(state, output->current_workspace_id);
if (!state_workspace_cycle_layout(state, workspace->id, delta)) return;
runtime->plan.need_relayout = true;
}
void cycle_current_output(runtime_t *runtime, int32_t delta) {
auto state = &runtime->state;
state_cycle_current_output(state, delta);
}

View File

@@ -1,11 +1,23 @@
#pragma once
#include "core/runtime.h"
#include <stdint.h>
#include <zdwm/action.h>
void spawn(const char *command);
void quit(runtime_t *runtime, bool restart);
void quit(zdwm_runtime_t *runtime, bool restart);
void raise_or_run(
zdwm_runtime_t *runtime,
const char *class_name,
const char *command
);
void toggle_fullscreen(zdwm_runtime_t *runtime);
void toggle_maximize(zdwm_runtime_t *runtime);
void toggle_floating(zdwm_runtime_t *runtime);
void toggle_sticky(zdwm_runtime_t *runtime);
void switch_workspace(
zdwm_runtime_t *runtime,
zdwm_workspace_id_t workspace_id
);
void cycle_layout(zdwm_runtime_t *runtime, int32_t delta);
void cycle_current_output(zdwm_runtime_t *runtime, int32_t delta);

View File

@@ -37,3 +37,17 @@ bool backend_apply_effect(
const effect_t *effects,
size_t effect_count
);
typedef struct backend_scan_result_t {
window_map_request_event_t *windows;
size_t count;
size_t capacity;
} backend_scan_result_t;
/**
* @brief 扫描已有窗口
*
* @details 仅扫描窗口并获取其基础信息
*/
backend_scan_result_t *backend_scan_windows(backend_t *backend);
void backend_scan_result_destroy(backend_scan_result_t *result);

View File

@@ -53,6 +53,7 @@ static void route_map_request(
const window_map_request_event_t *e,
command_buffer_t *out
) {
if (e->override_redirect) return;
if (state_window_get(state, e->window)) return;
window_layer_type_t layer_type = window_classify_layer(&e->props);
@@ -140,6 +141,90 @@ static void route_window_remove(
}
}
static void route_window_metadata_changed(
state_t *state,
const window_metadata_change_event_t *e
) {
auto window = state_window_get(state, e->window);
if (!window) return;
auto window_id = e->window;
auto metadata = &e->metadata;
if (e->changed_fields & ZDWM_WINDOW_METADATA_CHANGE_TITLE) {
state_window_set_title(state, window_id, metadata->title);
}
if (e->changed_fields & ZDWM_WINDOW_METADATA_CHANGE_APP_ID) {
state_window_set_app_id(state, window_id, metadata->app_id);
}
if (e->changed_fields & ZDWM_WINDOW_METADATA_CHANGE_ROLE) {
state_window_set_role(state, window_id, metadata->role);
}
if (e->changed_fields & ZDWM_WINDOW_METADATA_CHANGE_CLASS) {
state_window_set_class(state, window_id, metadata->class_name);
}
if (e->changed_fields & ZDWM_WINDOW_METADATA_CHANGE_INSTANCE) {
state_window_set_instance(state, window_id, metadata->instance_name);
}
}
static void route_window_activate_request(
state_t *state,
const window_activate_request_event_t *e,
command_buffer_t *out
) {
auto window = state_window_get(state, e->window);
if (!window) return;
switch (e->source) {
case ZDWM_WINDOW_ACTIVATION_SOURCE_LEGACY:
break;
case ZDWM_WINDOW_ACTIVATION_SOURCE_APPLICATION: {
command_t change_window_state_cmd = {
.type = ZDWM_COMMAND_CHANGE_WINDOW_STATE,
.as.state_change = {
.type = ZDWM_WINDOW_STATE_REQUEST_URGENT,
.window = window->id,
.action = ZDWM_WINDOW_STATE_ACTION_ADD,
}
};
command_buffer_push(out, &change_window_state_cmd);
} break;
case ZDWM_WINDOW_ACTIVATION_SOURCE_PAGER: {
command_t switch_workspace_cmd = {
.type = ZDWM_COMMAND_SWITCH_WORKSPACE,
.as.switch_workspace.workspace = window->workspace_id,
};
command_buffer_push(out, &switch_workspace_cmd);
command_t focus_cmd = {
.type = ZDWM_COMMAND_FOCUS_WINDOW,
.as.focus.window = window->id,
};
command_buffer_push(out, &focus_cmd);
} break;
}
}
static void route_window_state_request(
state_t *state,
const window_state_request_event_t *e,
command_buffer_t *out
) {
auto window = state_window_get(state, e->window);
if (!window) return;
command_t change_window_state_cmd = {
.type = ZDWM_COMMAND_CHANGE_WINDOW_STATE,
.as.state_change = {
.type = e->type,
.window = e->window,
.action = e->action,
}
};
command_buffer_push(out, &change_window_state_cmd);
}
static void route_configure_request(
state_t *state,
const configure_data_t *data,
@@ -190,6 +275,16 @@ void policy_route_event(
case ZDWM_EVENT_WINDOW_REMOVE:
route_window_remove(state, &event->as.window_remove, out);
break;
case ZDWM_EVENT_WINDOW_METADATA_CHANGED:
route_window_metadata_changed(state, &event->as.window_metadata_change);
break;
case ZDWM_EVENT_WINDOW_ACTIVATE_REQUEST: {
auto data = &event->as.window_activate_request;
route_window_activate_request(state, data, out);
} break;
case ZDWM_EVENT_WINDOW_STATE_REQUEST:
route_window_state_request(state, &event->as.window_state_request, out);
break;
case ZDWM_EVENT_CONFIGURE_REQUEST: {
auto data = &event->as.configure_request;
route_configure_request(state, data, ctx->layouts, out);
@@ -339,13 +434,14 @@ unmanage_window(const policy_context_t *ctx, window_id_t window, plan_t *plan) {
auto workspace_id = win->workspace_id;
auto workspace = state_workspace_get(state, workspace_id);
auto output = state_output_get(state, workspace->output_id);
auto need_layout = window_need_layout(win);
auto old_focused_window = workspace->focused_window_id;
state_window_remove(state, window);
push_window_list_effect(state, plan);
if (window_need_layout(win)) {
if (need_layout) {
adjust_layout_windows_border_width(state, ctx->border->width, workspace_id);
plan->need_relayout = true;
}

View File

@@ -257,6 +257,59 @@ static void runtime_arrange(runtime_t *runtime) {
}
}
policy_context_t policy_context_init(runtime_t *runtime) {
policy_context_t ctx = {
.bind_table = (runtime)->binding_table,
.state = &(runtime)->state,
.rules = &(runtime)->rules,
.border = &(runtime)->border,
.layouts = &(runtime)->layouts,
.runtime = (runtime),
.action_api = {
.spawn = spawn,
.quit = quit,
.raise_or_run = raise_or_run,
.toggle_fullscreen = toggle_fullscreen,
.toggle_maximize = toggle_maximize,
.toggle_floating = toggle_floating,
.toggle_sticky = toggle_sticky,
.switch_workspace = switch_workspace,
.cycle_layout = cycle_layout,
.cycle_current_output = cycle_current_output,
},
};
return ctx;
}
void runtime_scan(runtime_t *runtime) {
auto result = backend_scan_windows(runtime->backend);
if (!result) return;
policy_context_t ctx = policy_context_init(runtime);
auto backend = runtime->backend;
auto command_buffer = &runtime->command_buffer;
auto plan = &runtime->plan;
command_buffer_reset(command_buffer);
plan_reset(plan);
for (size_t i = 0; i < result->count; i++) {
event_t event = {
.type = ZDWM_EVENT_WINDOW_MAP_REQUEST,
.as.window_map_request = result->windows[i],
};
policy_route_event(&ctx, &event, command_buffer);
}
policy_apply_command(&ctx, command_buffer, plan);
if (plan->need_relayout) runtime_arrange(runtime);
if (plan->count) backend_apply_effect(backend, plan->effects, plan->count);
backend_scan_result_destroy(result);
result = nullptr;
}
void runtime_run(runtime_t *runtime) {
runtime->running = true;
@@ -264,19 +317,7 @@ void runtime_run(runtime_t *runtime) {
command_buffer_t *command_buffer = &runtime->command_buffer;
plan_t *plan = &runtime->plan;
policy_context_t ctx = {
.bind_table = runtime->binding_table,
.state = &runtime->state,
.rules = &runtime->rules,
.border = &runtime->border,
.layouts = &runtime->layouts,
.runtime = runtime,
.action_api = {
.spawn = spawn,
.quit = quit,
.raise_or_run = raise_or_run,
},
};
policy_context_t ctx = policy_context_init(runtime);
while (runtime->running) {
event_t event = {0};

View File

@@ -46,4 +46,5 @@ bool runtime_init(runtime_t *runtime, runtime_init_desc_t *desc);
void runtime_init_desc_cleanup(runtime_init_desc_t *desc);
void runtime_shutdown(runtime_t *runtime);
void runtime_setup(runtime_t *runtime);
void runtime_scan(runtime_t *runtime);
void runtime_run(runtime_t *runtime);

View File

@@ -145,24 +145,35 @@ static void state_workspace_adjust_focused_window(
workspace->focused_window_id = ZDWM_WINDOW_ID_INVALID;
}
bool state_workspace_cycle_layout(state_t *state, workspace_id_t workspace_id) {
bool state_workspace_cycle_layout(
state_t *state,
workspace_id_t workspace_id,
int32_t delta
) {
workspace_t *workspace =
(workspace_t *)state_workspace_get(state, workspace_id);
if (!workspace) return false;
size_t next_index = 0;
int32_t index = 0;
bool matched = false;
for (size_t i = 0; i < workspace->layout_count; i++) {
auto layout_count = (int32_t)workspace->layout_count;
for (int32_t i = 0; i < layout_count; i++) {
if (workspace->layout_id == workspace->available_layouts[i]) {
next_index = (i + 1) % workspace->layout_count;
matched = true;
index = i;
matched = true;
break;
}
}
if (!matched) return false;
auto next_layout_id = workspace->available_layouts[next_index];
auto new_index = index + delta;
new_index %= layout_count;
if (new_index < 0) new_index += layout_count;
if (new_index == index) return false;
auto next_layout_id = workspace->available_layouts[new_index];
workspace->layout_id = next_layout_id;
return true;
}
@@ -344,6 +355,7 @@ const window_t *state_window_add(state_t *state, const window_info_t *info) {
window_set_urgent(window, info->urgent);
window_set_fixed_size(window, info->fixed_size);
window_set_frame_rect(window, info->frame_rect);
window_set_float_rect(window, info->frame_rect);
window_set_title(window, info->title);
window_set_app_id(window, info->app_id);
window_set_role(window, info->role);

View File

@@ -81,7 +81,11 @@ void state_cleanup(state_t *state);
*/
const workspace_t *state_workspace_get(const state_t *state, workspace_id_t id);
const workspace_t *state_workspace_at(const state_t *state, size_t index);
bool state_workspace_cycle_layout(state_t *state, workspace_id_t workspace_id);
bool state_workspace_cycle_layout(
state_t *state,
workspace_id_t workspace_id,
int32_t delta
);
bool state_workspace_set_layout_by_index(
state_t *state,
workspace_id_t workspace_id,

View File

@@ -54,7 +54,8 @@ void window_set_floating(window_t *window, bool floating) {
if (floating == window->floating) return;
window->floating = floating;
if (floating) window->float_rect = window->frame_rect;
if (floating) window_set_frame_rect(window, window->float_rect);
else window_set_float_rect(window, window->frame_rect);
}
void window_set_sticky(window_t *window, bool sticky) {