Compare commits

...

3 Commits

Author SHA1 Message Date
6f4690705a 修正浮动窗口和全屏窗口配合行为 2026-01-29 09:18:45 +08:00
a5ce85be78 支持窗口全屏操作 2026-01-29 07:46:06 +08:00
a772d0f56f 支持 client 窗口浮动 2026-01-28 23:40:15 +08:00
11 changed files with 222 additions and 20 deletions

View File

@@ -75,3 +75,15 @@ void raise_or_run(const user_action_arg_t *arg) {
user_action_arg_t arguments = {.ptr = command};
spawn(&arguments);
}
void toggle_client_floating(const user_action_arg_t *arg) {
if (!wm.client_focused) return;
client_set_floating(wm.client_focused, !wm.client_focused->floating);
}
void toggle_client_fullscreen(const user_action_arg_t *arg) {
if (!wm.client_focused) return;
client_set_fullscreen(wm.client_focused, !wm.client_focused->fullscreen);
}

View File

@@ -55,3 +55,5 @@ void send_client_to_next_monitor(const user_action_arg_t *arg);
void spawn(const user_action_arg_t *arg);
void quit(const user_action_arg_t *arg);
void raise_or_run(const user_action_arg_t *arg);
void toggle_client_floating(const user_action_arg_t *arg);
void toggle_client_fullscreen(const user_action_arg_t *arg);

View File

@@ -14,4 +14,9 @@ _NET_CLIENT_LIST
_NET_WM_DESKTOP
_NET_WM_NAME
_NET_WM_ICON_NAME
_NET_WM_STATE
_NET_WM_STATE_FULLSCREEN
_NET_WM_WINDOW_TYPE
_NET_WM_WINDOW_TYPE_DIALOG
_NET_SUPPORTED
_NET_SUPPORTING_WM_CHECK

View File

@@ -1,9 +1,12 @@
#include "atoms.h"
#include <stdint.h>
#include <string.h>
#include <xcb/xproto.h>
#include "atoms-intern.h"
#include "utils.h"
#include "wm.h"
void atoms_init(xcb_connection_t *conn) {
xcb_intern_atom_cookie_t cookies[countof(ATOM_LIST)];
@@ -12,12 +15,25 @@ void atoms_init(xcb_connection_t *conn) {
cookies[i] = xcb_intern_atom_unchecked(conn, false, atom.len, atom.name);
}
atom_item_t *atom = nullptr;
uint32_t supported_length = 0;
xcb_atom_t supported_list[countof(ATOM_LIST)] = {XCB_ATOM_NONE};
xcb_intern_atom_reply_t *reply = nullptr;
for (int i = 0; i < countof(ATOM_LIST); i++) {
reply = xcb_intern_atom_reply(conn, cookies[i], nullptr);
if (reply) {
*ATOM_LIST[i].atom = reply->atom;
atom = &ATOM_LIST[i];
*atom->atom = reply->atom;
p_delete(&reply);
if (strstr(atom->name, "_NET_") == atom->name) {
supported_list[supported_length++] = *atom->atom;
}
}
}
xcb_change_property(wm.xcb_conn, XCB_PROP_MODE_REPLACE, wm.screen->root,
_NET_SUPPORTED, XCB_ATOM_ATOM, 32, supported_length,
supported_list);
}

View File

@@ -178,11 +178,12 @@ void client_manage(xcb_window_t window,
xcb_get_geometry_reply_t *geometry_reply) {
client_t *c = p_new(client_t, 1);
c->window = window;
c->geometry.x = geometry_reply->x;
c->geometry.y = geometry_reply->y;
c->geometry.width = geometry_reply->width;
c->geometry.height = geometry_reply->height;
c->border_width = geometry_reply->border_width;
c->old_geometry.x = geometry_reply->x;
c->old_geometry.y = geometry_reply->y;
c->old_geometry.width = geometry_reply->width;
c->old_geometry.height = geometry_reply->height;
c->geometry = c->old_geometry;
c->old_border_width = geometry_reply->border_width;
{
const xcb_params_cw_t params = {
@@ -233,6 +234,8 @@ void client_manage(xcb_window_t window,
client_attach_list(c);
client_attach_stack(c);
client_update_window_type(c);
client_update_size_hints(c);
xcb_change_property(wm.xcb_conn, XCB_PROP_MODE_APPEND, wm.screen->root,
_NET_CLIENT_LIST, XCB_ATOM_WINDOW, 32, 1, &window);
@@ -256,7 +259,7 @@ char **client_get_task_title(client_t *client) {
bool client_need_layout(client_t *client) {
if (client->floating || client->fullscreen || client->maximize ||
client->minimize) {
client->minimize || client->size_freeze) {
return false;
}
return true;
@@ -330,6 +333,35 @@ void client_change_border_width(client_t *client, uint16_t border_width) {
logger("== client 0x%x border width: %u\n", client->window, border_width);
}
void client_update_window_type(client_t *client) {
xcb_window_t window = client->window;
xcb_get_property_cookie_t state_cookie = xcb_get_property_unchecked(
wm.xcb_conn, false, window, _NET_WM_STATE, XCB_ATOM_ATOM, 0, 1);
xcb_get_property_cookie_t window_type_cookie = xcb_get_property_unchecked(
wm.xcb_conn, false, window, _NET_WM_WINDOW_TYPE, XCB_ATOM_ATOM, 0, 1);
xcb_get_property_reply_t *state_reply =
xcb_get_property_reply(wm.xcb_conn, state_cookie, nullptr);
xcb_get_property_reply_t *window_type_reply =
xcb_get_property_reply(wm.xcb_conn, window_type_cookie, nullptr);
if (state_reply) {
xcb_atom_t state = *(xcb_atom_t *)xcb_get_property_value(state_reply);
if (state == _NET_WM_STATE_FULLSCREEN) {
client_set_fullscreen(client, true);
}
p_delete(&state_reply);
}
if (window_type_reply) {
xcb_atom_t window_type =
*(xcb_atom_t *)xcb_get_property_value(window_type_reply);
if (window_type == _NET_WM_WINDOW_TYPE_DIALOG) {
client_set_floating(client, true);
}
p_delete(&window_type_reply);
}
}
void client_update_wm_hints(client_t *client) {
xcb_get_property_cookie_t cookie =
xcb_icccm_get_wm_hints(wm.xcb_conn, client->window);
@@ -345,6 +377,69 @@ void client_update_wm_hints(client_t *client) {
}
}
void client_update_size_hints(client_t *client) {
xcb_connection_t *conn = wm.xcb_conn;
xcb_size_hints_t hints;
xcb_get_property_cookie_t cookie =
xcb_icccm_get_wm_normal_hints_unchecked(conn, client->window);
if (!xcb_icccm_get_wm_normal_hints_reply(conn, cookie, &hints, nullptr)) {
return;
}
int32_t min_width = 0, min_height = 0, max_width = 0, max_height = 0;
if (hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE) {
min_width = hints.min_width;
min_height = hints.min_height;
}
if (hints.flags & XCB_ICCCM_SIZE_HINT_P_MAX_SIZE) {
max_width = hints.max_width;
max_height = hints.max_height;
}
if (min_width == max_width && min_height == max_height) {
client->size_freeze = true;
client_set_floating(client, true);
}
}
void client_set_floating(client_t *client, bool floating) {
if (client->floating == floating || client->fullscreen) return;
logger("== client set floating: %s\n", floating ? "true" : "false");
logger("== old geometry: x -> %d, y -> %d, width -> %u, height -> %u\n",
client->old_geometry.x, client->old_geometry.y,
client->old_geometry.width, client->old_geometry.height);
client->floating = floating;
if (floating) {
client_apply_workarea_geometry(client, client->old_geometry);
client->old_geometry = client->geometry;
}
monitor_arrange(client->monitor);
}
void client_set_fullscreen(client_t *client, bool fullscreen) {
if (client->fullscreen == fullscreen) return;
client->fullscreen = fullscreen;
if (fullscreen) {
xcb_change_property(wm.xcb_conn, XCB_PROP_MODE_REPLACE, client->window,
_NET_WM_STATE, XCB_ATOM_ATOM, 32, 1,
&_NET_WM_STATE_FULLSCREEN);
client->old_border_width = client->border_width;
if (client->floating) client->old_geometry = client->geometry;
client_change_border_width(client, 0);
client_apply_geometry(client, client->monitor->geometry);
client_stack_raise(client);
} else {
xcb_change_property(wm.xcb_conn, XCB_PROP_MODE_REPLACE, client->window,
_NET_WM_STATE, XCB_ATOM_ATOM, 32, 0, 0);
client_change_border_width(client, client->old_border_width);
client_apply_geometry(client, client->old_geometry);
monitor_arrange(client->monitor);
}
client_focus(client);
}
static void update_client_list(void) {
xcb_connection_t *conn = wm.xcb_conn;
xcb_window_t root = wm.screen->root;
@@ -379,17 +474,36 @@ void client_unmanage(client_t *client) {
xcb_flush(wm.xcb_conn);
}
void client_apply_task_geometry(client_t *client, task_in_tag_t *task) {
if (!client || !task || client != task->client) return;
void client_apply_geometry(client_t *client, area_t geometry) {
if (client->geometry.x != geometry.x || client->geometry.y != geometry.y) {
client_move_to(client, geometry.x, geometry.y);
}
if (client->geometry.width != geometry.width ||
client->geometry.height != geometry.height) {
client_resize(client, geometry.width, geometry.height);
}
}
if (task->geometry.x != client->geometry.x ||
task->geometry.y != client->geometry.y) {
client_move_to(client, task->geometry.x, task->geometry.y);
/**
* @brief 调整 client 的几何信息但需让 client 任在其 monitor 的 workarea 中
* @param client 要调整几何信息的 client
* @param geometry 目标几何信息
*/
void client_apply_workarea_geometry(client_t *client, area_t geometry) {
area_t workarea = client->monitor->workarea;
uint16_t width = MIN(geometry.width, workarea.width);
uint16_t height = MIN(geometry.height, workarea.height);
int16_t x = MAX(geometry.x, workarea.x);
int16_t y = MAX(geometry.y, workarea.y);
if (x + width + client->border_width * 2 > workarea.x + workarea.width) {
x = workarea.x + workarea.width - width - client->border_width * 2;
}
if (task->geometry.width != client->geometry.width ||
task->geometry.height != client->geometry.height) {
client_resize(client, task->geometry.width, task->geometry.height);
if (y + height + client->border_width * 2 > workarea.y + workarea.height) {
y = workarea.y + workarea.width - height - client->border_width * 2;
}
area_t rect = {.x = x, .y = y, .width = width, .height = height};
client_apply_geometry(client, rect);
}
void client_focus(client_t *client) {

View File

@@ -6,7 +6,7 @@
#include "types.h"
client_t *client_get_by_window(xcb_window_t window);
client_t *client_get_next_by_class(client_t *current,const char *class);
client_t *client_get_next_by_class(client_t *current, const char *class);
task_in_tag_t *client_get_task_in_tag(client_t *client, tag_t *tag);
task_in_tag_t *client_get_next_task_in_tag(client_t *client, tag_t *tag);
task_in_tag_t *client_get_previous_task_in_tag(client_t *client, tag_t *tag);
@@ -23,7 +23,9 @@ void client_resize(client_t *client, uint16_t width, uint16_t height);
void client_change_border_color(client_t *client, color_t *color);
void client_change_border_width(client_t *client, uint16_t border_width);
void client_update_window_type(client_t *client);
void client_update_wm_hints(client_t *client);
void client_update_size_hints(client_t *client);
void client_set_floating(client_t *client, bool floating);
void client_set_fullscreen(client_t *client, bool fullscreen);
@@ -48,7 +50,8 @@ void client_set_leader_window(client_t *client, xcb_window_t leader_window);
void client_kill(client_t *client);
void client_unmanage(client_t *client);
void client_apply_task_geometry(client_t *client, task_in_tag_t *task);
void client_apply_geometry(client_t *client, area_t geometry);
void client_apply_workarea_geometry(client_t *client, area_t geometry);
void client_focus(client_t *client);
void client_stack_raise(client_t *client);

View File

@@ -86,6 +86,9 @@ static const keyboard_t key_list[] = {
TAGKEYS(XK_9, 1 << 8),
{modifier_super, XK_o, send_client_to_next_monitor, {0}},
{modifier_super | modifier_control, XK_space, toggle_client_floating, {0}},
{modifier_super, XK_f, toggle_client_fullscreen, {0}},
};
static const layout_t layout_list[] = {

View File

@@ -68,7 +68,7 @@ static void configure_request(xcb_configure_request_event_t *ev) {
task->geometry.width = ev->width;
if (ev->value_mask & XCB_CONFIG_WINDOW_HEIGHT)
task->geometry.height = ev->height;
client_apply_task_geometry(client, task);
client_apply_geometry(client, task->geometry);
}
} else {
uint16_t value_mask = ev->value_mask;
@@ -144,6 +144,27 @@ static void client_message(xcb_client_message_event_t *ev) {
client_focus(c);
return;
}
} else if (ev->type == _NET_WM_STATE) {
/**
* _NET_WM_STATE 协议数据格式为
* data.data32 = {
* action, // 操作类型(添加/移除/切换)
* property1, // 第一个状态原子如_NET_WM_STATE_FULLSCREEN
* property2, // 第二个状态原子可选通常为0
* 0, // 预留字段固定为0
* 0 // 预留字段固定为0
* };
* action 值可能为
* _NET_WM_STATE_ADD1添加全屏状态窗口进入全屏模式
* _NET_WM_STATE_REMOVE0移除全屏状态窗口退出全屏模式
* _NET_WM_STATE_TOGGLE2切换全屏状态若当前全屏则退出反之进入
*/
if (ev->data.data32[1] == _NET_WM_STATE_FULLSCREEN ||
ev->data.data32[2] == _NET_WM_STATE_FULLSCREEN) {
bool fullscreen = ev->data.data32[0];
if (ev->data.data32[0] == 2) fullscreen = !c->fullscreen;
client_set_fullscreen(c, fullscreen);
}
}
}

View File

@@ -67,6 +67,17 @@ void monitor_deal_focus(monitor_t *monitor) {
void monitor_select_tag(monitor_t *monitor, uint32_t tag_mask) {
if (monitor->selected_tag->mask == tag_mask) return;
for (task_in_tag_t *task = monitor->selected_tag->task_list; task;
task = task->next) {
if (task->client->floating || task->client->size_freeze) {
task->geometry = task->client->geometry;
}
if (task->client->fullscreen || task->client->maximize ||
task->client->minimize) {
task->geometry = task->client->old_geometry;
}
}
for (tag_t *tag = monitor->tag_list; tag; tag = tag->next) {
if (tag->mask == tag_mask) {
monitor->selected_tag = tag;
@@ -274,7 +285,14 @@ void monitor_arrange(monitor_t *monitor) {
task_in_tag_t *task = client_get_task_in_tag(c, tag);
if (task) {
client_apply_task_geometry(c, task);
if (c->minimize) continue;
if (c->fullscreen) {
client_apply_geometry(c, c->monitor->geometry);
} else if (c->maximize) {
client_apply_geometry(c, c->monitor->workarea);
} else {
client_apply_geometry(c, task->geometry);
}
} else {
int16_t x = -client_width(c);
int16_t y = -client_height(c);

View File

@@ -26,9 +26,11 @@ struct client_t {
monitor_t *monitor;
uint32_t tags;
area_t old_geometry;
area_t geometry;
color_t *border_color;
uint16_t border_width;
uint16_t old_border_width;
color_t *border_color;
bool floating;
bool fullscreen;
@@ -36,6 +38,7 @@ struct client_t {
bool minimize;
bool sticky;
bool urgent;
bool size_freeze; /* 尺寸冻结窗口一定是浮动窗口 */
char *class, *instance;
char *name, *icon_name, *net_name, *net_icon_name;

View File

@@ -88,6 +88,11 @@ static inline void logger(const char *format, ...) {
#define logger(format, ...) printf(format, ##__VA_ARGS__)
#endif
#ifdef MIN
#undef MIN
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#endif
#ifdef MAX
#undef MAX
#define MAX(a, b) (((a) > (b)) ? (a) : (b))