From 5a714b756278896d74aae892a58ed740b4c2d27b Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Mon, 5 Jan 2026 22:08:44 +0800
Subject: [PATCH 01/47] opt: optimize sloppyfocus
---
src/mango.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/mango.c b/src/mango.c
index e3e658a..89ee23e 100644
--- a/src/mango.c
+++ b/src/mango.c
@@ -4172,8 +4172,10 @@ void pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy,
uint32_t time) {
struct timespec now;
- if (surface != seat->pointer_state.focused_surface && sloppyfocus && time &&
- c && c->scene->node.enabled && !client_is_unmanaged(c))
+ if (sloppyfocus && c && time && c->scene->node.enabled &&
+ (surface != seat->pointer_state.focused_surface ||
+ (selmon && selmon->sel && c != selmon->sel)) &&
+ !client_is_unmanaged(c))
focusclient(c, 0);
/* If surface is NULL, clear pointer focus */
From 4efb8c5e06debc3a32a711e3508593cfd1e1fb48 Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Thu, 8 Jan 2026 10:13:21 +0800
Subject: [PATCH 02/47] fix: isfloating rule not follow monitor rule
---
src/dispatch/bind_define.h | 4 ++--
src/fetch/client.h | 6 +++---
src/mango.c | 20 ++++++++++++--------
3 files changed, 17 insertions(+), 13 deletions(-)
diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h
index 28879df..19d743e 100644
--- a/src/dispatch/bind_define.h
+++ b/src/dispatch/bind_define.h
@@ -740,7 +740,7 @@ int32_t centerwin(const Arg *arg) {
return 0;
if (c->isfloating) {
- c->float_geom = setclient_coordinate_center(c, c->geom, 0, 0);
+ c->float_geom = setclient_coordinate_center(c, c->mon, c->geom, 0, 0);
c->iscustomsize = 1;
resize(c, c->float_geom, 1);
return 0;
@@ -1055,7 +1055,7 @@ int32_t tagmon(const Arg *arg) {
c->float_geom.height =
(int32_t)(c->float_geom.height * c->mon->w.height / selmon->w.height);
selmon = c->mon;
- c->float_geom = setclient_coordinate_center(c, c->float_geom, 0, 0);
+ c->float_geom = setclient_coordinate_center(c, c->mon, c->float_geom, 0, 0);
// 重新计算居中的坐标
// 重新计算居中的坐标
diff --git a/src/fetch/client.h b/src/fetch/client.h
index 22db029..0af1723 100644
--- a/src/fetch/client.h
+++ b/src/fetch/client.h
@@ -74,12 +74,12 @@ Client *get_client_by_id_or_title(const char *arg_id, const char *arg_title) {
return target_client;
}
struct wlr_box // 计算客户端居中坐标
-setclient_coordinate_center(Client *c, struct wlr_box geom, int32_t offsetx,
- int32_t offsety) {
+setclient_coordinate_center(Client *c, Monitor *tm, struct wlr_box geom,
+ int32_t offsetx, int32_t offsety) {
struct wlr_box tempbox;
int32_t offset = 0;
int32_t len = 0;
- Monitor *m = c->mon ? c->mon : selmon;
+ Monitor *m = tm ? tm : selmon;
uint32_t cbw = check_hit_no_border(c) ? c->bw : 0;
diff --git a/src/mango.c b/src/mango.c
index 89ee23e..a22e8be 100644
--- a/src/mango.c
+++ b/src/mango.c
@@ -689,7 +689,7 @@ static void show_scratchpad(Client *c);
static void show_hide_client(Client *c);
static void tag_client(const Arg *arg, Client *target_client);
-static struct wlr_box setclient_coordinate_center(Client *c,
+static struct wlr_box setclient_coordinate_center(Client *c, Monitor *m,
struct wlr_box geom,
int32_t offsetx,
int32_t offsety);
@@ -952,7 +952,8 @@ void client_change_mon(Client *c, Monitor *m) {
setmon(c, m, c->tags, true);
reset_foreign_tolevel(c);
if (c->isfloating) {
- c->float_geom = c->geom = setclient_coordinate_center(c, c->geom, 0, 0);
+ c->float_geom = c->geom =
+ setclient_coordinate_center(c, c->mon, c->geom, 0, 0);
}
}
@@ -1019,7 +1020,7 @@ void show_scratchpad(Client *c) {
: c->mon->w.height * scratchpad_height_ratio;
// 重新计算居中的坐标
c->float_geom = c->geom = c->animainit_geom = c->animation.current =
- setclient_coordinate_center(c, c->geom, 0, 0);
+ setclient_coordinate_center(c, c->mon, c->geom, 0, 0);
c->iscustomsize = 1;
resize(c, c->geom, 0);
}
@@ -1089,7 +1090,8 @@ bool switch_scratchpad_client_state(Client *c) {
c->float_geom.height = (int32_t)(c->float_geom.height *
c->mon->w.height / oldmon->w.height);
- c->float_geom = setclient_coordinate_center(c, c->float_geom, 0, 0);
+ c->float_geom =
+ setclient_coordinate_center(c, c->mon, c->float_geom, 0, 0);
// 只有显示状态的scratchpad才需要聚焦和返回true
if (c->is_scratchpad_show) {
@@ -1345,7 +1347,7 @@ void applyrules(Client *c) {
if (r->offsetx || r->offsety) {
c->iscustompos = 1;
c->float_geom = c->geom = setclient_coordinate_center(
- c, c->float_geom, r->offsetx, r->offsety);
+ c, mon, c->float_geom, r->offsetx, r->offsety);
}
if (c->isfloating) {
c->geom = c->float_geom.width > 0 && c->float_geom.height > 0
@@ -1363,7 +1365,8 @@ void applyrules(Client *c) {
// the hit size
if (!c->iscustompos &&
(!client_is_x11(c) || (c->geom.x == 0 && c->geom.y == 0))) {
- c->float_geom = c->geom = setclient_coordinate_center(c, c->geom, 0, 0);
+ c->float_geom = c->geom =
+ setclient_coordinate_center(c, mon, c->geom, 0, 0);
} else {
c->float_geom = c->geom;
}
@@ -4537,7 +4540,8 @@ setfloating(Client *c, int32_t floating) {
// 重新计算居中的坐标
if (!client_is_x11(c) && !c->iscustompos)
- target_box = setclient_coordinate_center(c, target_box, 0, 0);
+ target_box =
+ setclient_coordinate_center(c, c->mon, target_box, 0, 0);
else
target_box = c->geom;
@@ -4553,7 +4557,7 @@ setfloating(Client *c, int32_t floating) {
}
if (window_size_outofrange) {
c->float_geom =
- setclient_coordinate_center(c, c->float_geom, 0, 0);
+ setclient_coordinate_center(c, c->mon, c->float_geom, 0, 0);
}
resize(c, c->float_geom, 0);
} else {
From 8e8c513beb79f425d3bb166a96478ba6e87932b2 Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Tue, 13 Jan 2026 08:12:41 +0800
Subject: [PATCH 03/47] opt: sloppyfocus not apply to tagouting client
---
src/mango.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/mango.c b/src/mango.c
index a22e8be..1d776be 100644
--- a/src/mango.c
+++ b/src/mango.c
@@ -4176,9 +4176,10 @@ void pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy,
struct timespec now;
if (sloppyfocus && c && time && c->scene->node.enabled &&
+ !c->animation.tagining &&
(surface != seat->pointer_state.focused_surface ||
(selmon && selmon->sel && c != selmon->sel)) &&
- !client_is_unmanaged(c))
+ !client_is_unmanaged(c) && VISIBLEON(c, c->mon))
focusclient(c, 0);
/* If surface is NULL, clear pointer focus */
From 764eb44b9b7231842aaed18773ad2499e215a3bd Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Tue, 13 Jan 2026 20:25:19 +0800
Subject: [PATCH 04/47] Update README.md
---
README.md | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/README.md b/README.md
index 087bf13..d512f39 100644
--- a/README.md
+++ b/README.md
@@ -285,3 +285,15 @@ Read The Friendly Manual on packaging software in your distribution first.
- https://github.com/swaywm/sway - Sample of Wayland protocol
- https://github.com/wlrfx/scenefx - Make it simple to add window effect.
+
+
+# Sponsor
+At present, I can only accept sponsorship through an encrypted connection.
+If you find this project helpful to you, you can offer sponsorship in the following ways.
+
+
+
+
+Thanks to the following friends for their sponsorship of this project
+
+[@tonybanters](https://github.com/tonybanters)
From 373377eb17cd083cccf9d4e62ff4a5cb20e5d237 Mon Sep 17 00:00:00 2001
From: Ad
Date: Tue, 13 Jan 2026 23:28:44 -0500
Subject: [PATCH 05/47] fix: comment on tag_animation_direction in default
config
---
config.conf | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/config.conf b/config.conf
index 5483a14..6cbb3a2 100644
--- a/config.conf
+++ b/config.conf
@@ -26,7 +26,7 @@ focused_opacity=1.0
unfocused_opacity=1.0
# Animation Configuration(support type:zoom,slide)
-# tag_animation_direction: 0-horizontal,1-vertical
+# tag_animation_direction: 1-horizontal,0-vertical
animations=1
layer_animations=1
animation_type_open=slide
From bc1f310e1cab1b514d509833254c520b91e87a60 Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Thu, 15 Jan 2026 13:15:34 +0800
Subject: [PATCH 06/47] opt: not apply sloppyfocus if the surface is current
pointer-focus surface
---
src/mango.c | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/mango.c b/src/mango.c
index 1d776be..ac5edf9 100644
--- a/src/mango.c
+++ b/src/mango.c
@@ -4177,8 +4177,7 @@ void pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy,
if (sloppyfocus && c && time && c->scene->node.enabled &&
!c->animation.tagining &&
- (surface != seat->pointer_state.focused_surface ||
- (selmon && selmon->sel && c != selmon->sel)) &&
+ (surface != seat->pointer_state.focused_surface) &&
!client_is_unmanaged(c) && VISIBLEON(c, c->mon))
focusclient(c, 0);
From c0f38e8a36360e418e9d50fe645911a76d810612 Mon Sep 17 00:00:00 2001
From: 0xWal
Date: Sat, 17 Jan 2026 09:53:14 +0300
Subject: [PATCH 07/47] =?UTF-8?q?fix:=20fakefullscreen=20toggle=20from=20f?=
=?UTF-8?q?ullscreen=20state=20=F0=9F=A9=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/mango.c | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/src/mango.c b/src/mango.c
index ac5edf9..6760b7b 100644
--- a/src/mango.c
+++ b/src/mango.c
@@ -4661,10 +4661,11 @@ void setfakefullscreen(Client *c, int32_t fakefullscreen) {
c->isfakefullscreen = fakefullscreen;
if (!c->mon)
return;
+
if (c->isfullscreen)
setfullscreen(c, 0);
- else
- client_set_fullscreen(c, fakefullscreen);
+
+ client_set_fullscreen(c, fakefullscreen);
}
void setfullscreen(Client *c, int32_t fullscreen) // 用自定义全屏代理自带全屏
@@ -4687,6 +4688,8 @@ void setfullscreen(Client *c, int32_t fullscreen) // 用自定义全屏代理自
if (c->isfloating)
c->float_geom = c->geom;
+ c->isfakefullscreen = 0;
+
c->bw = 0;
wlr_scene_node_raise_to_top(&c->scene->node); // 将视图提升到顶层
if (!is_scroller_layout(c->mon) || c->isfloating)
@@ -4695,7 +4698,6 @@ void setfullscreen(Client *c, int32_t fullscreen) // 用自定义全屏代理自
} else {
c->bw = c->isnoborder ? 0 : borderpx;
c->isfullscreen = 0;
- c->isfakefullscreen = 0;
if (c->isfloating)
setfloating(c, 1);
}
From e0d69ece59235f6d075da03f093d954fc83b5cdc Mon Sep 17 00:00:00 2001
From: nixpup
Date: Fri, 16 Jan 2026 18:49:35 +0100
Subject: [PATCH 08/47] feat: add scroller stack support
---
src/config/parse_config.h | 3 +
src/dispatch/bind_declare.h | 3 +-
src/dispatch/bind_define.h | 112 +++++++++++++++++++++++++---
src/fetch/client.h | 8 ++
src/layout/arrange.h | 61 ++++++++++++---
src/layout/horizontal.h | 93 ++++++++++++++++++++---
src/layout/vertical.h | 95 ++++++++++++++++++++++--
src/mango.c | 143 +++++++++++++++++++++++++++++++++---
8 files changed, 466 insertions(+), 52 deletions(-)
diff --git a/src/config/parse_config.h b/src/config/parse_config.h
index 9c27872..b2535b1 100644
--- a/src/config/parse_config.h
+++ b/src/config/parse_config.h
@@ -1080,6 +1080,9 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value,
} else if (strcmp(func_name, "toggle_monitor") == 0) {
func = toggle_monitor;
(*arg).v = strdup(arg_value);
+ } else if (strcmp(func_name, "scroller_stack") == 0) {
+ func = scroller_stack;
+ (*arg).i = parse_direction(arg_value);
} else {
return NULL;
}
diff --git a/src/dispatch/bind_declare.h b/src/dispatch/bind_declare.h
index b197778..22ef612 100644
--- a/src/dispatch/bind_declare.h
+++ b/src/dispatch/bind_declare.h
@@ -68,4 +68,5 @@ int32_t toggle_trackpad_enable(const Arg *arg);
int32_t setoption(const Arg *arg);
int32_t disable_monitor(const Arg *arg);
int32_t enable_monitor(const Arg *arg);
-int32_t toggle_monitor(const Arg *arg);
\ No newline at end of file
+int32_t toggle_monitor(const Arg *arg);
+int32_t scroller_stack(const Arg *arg);
\ No newline at end of file
diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h
index 19d743e..3ca7997 100644
--- a/src/dispatch/bind_define.h
+++ b/src/dispatch/bind_define.h
@@ -550,12 +550,14 @@ int32_t set_proportion(const Arg *arg) {
!scroller_ignore_proportion_single)
return 0;
- if (selmon->sel) {
+ Client *tc = selmon->sel;
+
+ if (tc) {
+ tc = get_scroll_stack_head(tc);
uint32_t max_client_width =
selmon->w.width - 2 * scroller_structs - gappih;
- selmon->sel->scroller_proportion = arg->f;
- selmon->sel->geom.width = max_client_width * arg->f;
- // resize(selmon->sel, selmon->sel->geom, 0);
+ tc->scroller_proportion = arg->f;
+ tc->geom.width = max_client_width * arg->f;
arrange(selmon, false, false);
}
return 0;
@@ -971,11 +973,13 @@ int32_t switch_proportion_preset(const Arg *arg) {
!scroller_ignore_proportion_single)
return 0;
- if (selmon->sel) {
+ Client *tc = selmon->sel;
+ if (tc) {
+ tc = get_scroll_stack_head(tc);
for (int32_t i = 0; i < config.scroller_proportion_preset_count; i++) {
if (config.scroller_proportion_preset[i] ==
- selmon->sel->scroller_proportion) {
+ tc->scroller_proportion) {
if (i == config.scroller_proportion_preset_count - 1) {
target_proportion = config.scroller_proportion_preset[0];
break;
@@ -993,9 +997,8 @@ int32_t switch_proportion_preset(const Arg *arg) {
uint32_t max_client_width =
selmon->w.width - 2 * scroller_structs - gappih;
- selmon->sel->scroller_proportion = target_proportion;
- selmon->sel->geom.width = max_client_width * target_proportion;
- // resize(selmon->sel, selmon->sel->geom, 0);
+ tc->scroller_proportion = target_proportion;
+ tc->geom.width = max_client_width * target_proportion;
arrange(selmon, false, false);
}
return 0;
@@ -1093,6 +1096,7 @@ int32_t tagsilent(const Arg *arg) {
clear_fullscreen_flag(fc);
}
}
+ exit_scroller_stack(target_client);
focusclient(focustop(selmon), 1);
arrange(target_client->mon, false, false);
return 0;
@@ -1221,9 +1225,11 @@ int32_t toggleglobal(const Arg *arg) {
selmon->sel->isnamedscratchpad = 0;
}
selmon->sel->isglobal ^= 1;
- // selmon->sel->tags =
- // selmon->sel->isglobal ? TAGMASK : selmon->tagset[selmon->seltags];
- // focustop(selmon);
+ if (selmon->sel->isglobal &&
+ (selmon->sel->prev_in_stack || selmon->sel->next_in_stack)) {
+ exit_scroller_stack(selmon->sel);
+ arrange(selmon, false, false);
+ }
setborder_color(selmon->sel);
return 0;
}
@@ -1585,3 +1591,85 @@ int32_t toggle_monitor(const Arg *arg) {
}
return 0;
}
+
+int32_t scroller_stack(const Arg *arg) {
+ Client *c = selmon->sel;
+ Client *stack_head = NULL;
+ Client *source_stack_head = NULL;
+ if (!c || c->isfloating || !is_scroller_layout(selmon))
+ return 0;
+
+ if (c && (!client_only_in_one_tag(c) || c->isglobal || c->isunglobal))
+ return 0;
+
+ Client *target_client = find_client_by_direction(c, arg, false, true);
+
+ if (target_client && (!client_only_in_one_tag(target_client) ||
+ target_client->isglobal || target_client->isunglobal))
+ return 0;
+
+ if (target_client) {
+ stack_head = get_scroll_stack_head(target_client);
+ }
+
+ if (c) {
+ source_stack_head = get_scroll_stack_head(c);
+ }
+
+ if (stack_head == source_stack_head) {
+ return 0;
+ }
+
+ if (c->isfullscreen) {
+ setfullscreen(c, 0);
+ }
+
+ if (c->ismaximizescreen) {
+ setmaximizescreen(c, 0);
+ }
+
+ if (c->prev_in_stack) {
+ exit_scroller_stack(c);
+ if (arg->i == LEFT || arg->i == UP) {
+ wl_list_remove(&c->link);
+ wl_list_insert(source_stack_head->link.prev, &c->link);
+ } else {
+ wl_list_remove(&c->link);
+ wl_list_insert(&source_stack_head->link, &c->link);
+ }
+ arrange(selmon, false, false);
+ return 0;
+ } else if (c->next_in_stack) {
+ Client *next_in_stack = c->next_in_stack;
+ exit_scroller_stack(c);
+ if (arg->i == LEFT || arg->i == UP) {
+ wl_list_remove(&c->link);
+ wl_list_insert(next_in_stack->link.prev, &c->link);
+ } else {
+ wl_list_remove(&c->link);
+ wl_list_insert(&next_in_stack->link, &c->link);
+ }
+ arrange(selmon, false, false);
+ return 0;
+ }
+
+ if (!target_client || target_client->mon != c->mon) {
+ return 0;
+ }
+
+ exit_scroller_stack(c);
+
+ // Find the tail of target_client's stack
+ Client *stack_tail = target_client;
+ while (stack_tail->next_in_stack) {
+ stack_tail = stack_tail->next_in_stack;
+ }
+
+ // Add c to the stack
+ stack_tail->next_in_stack = c;
+ c->prev_in_stack = stack_tail;
+ c->next_in_stack = NULL;
+
+ arrange(selmon, false, false);
+ return 0;
+}
\ No newline at end of file
diff --git a/src/fetch/client.h b/src/fetch/client.h
index 0af1723..4cd0364 100644
--- a/src/fetch/client.h
+++ b/src/fetch/client.h
@@ -447,4 +447,12 @@ bool client_only_in_one_tag(Client *c) {
} else {
return false;
}
+}
+
+Client *get_scroll_stack_head(Client *c) {
+ Client *scroller_stack_head = c;
+ while (scroller_stack_head->prev_in_stack) {
+ scroller_stack_head = scroller_stack_head->prev_in_stack;
+ }
+ return scroller_stack_head;
}
\ No newline at end of file
diff --git a/src/layout/arrange.h b/src/layout/arrange.h
index d668f30..1f7001f 100644
--- a/src/layout/arrange.h
+++ b/src/layout/arrange.h
@@ -378,6 +378,8 @@ void resize_tile_scroller(Client *grabc, bool isdrag, int32_t offsetx,
int32_t offsety, uint32_t time, bool isvertical) {
float delta_x, delta_y;
float new_scroller_proportion;
+ float new_stack_proportion;
+ Client *stack_head = get_scroll_stack_head(grabc);
if (grabc && grabc->mon->visible_tiling_clients == 1 &&
!scroller_ignore_proportion_single)
@@ -389,7 +391,8 @@ void resize_tile_scroller(Client *grabc, bool isdrag, int32_t offsetx,
start_drag_window = true;
// 记录初始状态
- grabc->old_scroller_pproportion = grabc->scroller_proportion;
+ stack_head->old_scroller_pproportion = stack_head->scroller_proportion;
+ grabc->old_stack_proportion = grabc->stack_proportion;
grabc->cursor_in_left_half =
cursor->x < grabc->geom.x + grabc->geom.width / 2;
@@ -409,15 +412,26 @@ void resize_tile_scroller(Client *grabc, bool isdrag, int32_t offsetx,
grabc->old_master_inner_per = grabc->master_inner_per;
grabc->old_stack_inner_per = grabc->stack_inner_per;
grabc->drag_begin_geom = grabc->geom;
- grabc->old_scroller_pproportion = grabc->scroller_proportion;
+ stack_head->old_scroller_pproportion =
+ stack_head->scroller_proportion;
+ grabc->old_stack_proportion = grabc->stack_proportion;
grabc->cursor_in_upper_half = false;
grabc->cursor_in_left_half = false;
}
- delta_x = (float)(offsetx) * (grabc->old_scroller_pproportion) /
- grabc->drag_begin_geom.width;
- delta_y = (float)(offsety) * (grabc->old_scroller_pproportion) /
- grabc->drag_begin_geom.height;
+ if (isvertical) {
+ delta_y = (float)(offsety) *
+ (stack_head->old_scroller_pproportion) /
+ grabc->drag_begin_geom.height;
+ delta_x = (float)(offsetx) * (grabc->old_stack_proportion) /
+ grabc->drag_begin_geom.width;
+ } else {
+ delta_x = (float)(offsetx) *
+ (stack_head->old_scroller_pproportion) /
+ grabc->drag_begin_geom.width;
+ delta_y = (float)(offsety) * (grabc->old_stack_proportion) /
+ grabc->drag_begin_geom.height;
+ }
bool moving_up;
bool moving_down;
@@ -452,18 +466,36 @@ void resize_tile_scroller(Client *grabc, bool isdrag, int32_t offsetx,
delta_x = -fabsf(delta_x);
}
+ if (isvertical) {
+ if (!grabc->next_in_stack && grabc->prev_in_stack && !isdrag) {
+ delta_x = delta_x * -1.0f;
+ }
+ } else {
+ if (!grabc->next_in_stack && grabc->prev_in_stack && !isdrag) {
+ delta_y = delta_y * -1.0f;
+ }
+ }
+
// 直接设置新的比例,基于初始值 + 变化量
if (isvertical) {
- new_scroller_proportion = grabc->old_scroller_pproportion + delta_y;
+ new_scroller_proportion =
+ stack_head->old_scroller_pproportion + delta_y;
+ new_stack_proportion = grabc->old_stack_proportion + delta_x;
+
} else {
- new_scroller_proportion = grabc->old_scroller_pproportion + delta_x;
+ new_scroller_proportion =
+ stack_head->old_scroller_pproportion + delta_x;
+ new_stack_proportion = grabc->old_stack_proportion + delta_y;
}
// 应用限制,确保比例在合理范围内
new_scroller_proportion =
fmaxf(0.1f, fminf(1.0f, new_scroller_proportion));
+ new_stack_proportion = fmaxf(0.1f, fminf(1.0f, new_stack_proportion));
- grabc->scroller_proportion = new_scroller_proportion;
+ grabc->stack_proportion = new_stack_proportion;
+
+ stack_head->scroller_proportion = new_scroller_proportion;
if (!isdrag) {
arrange(grabc->mon, false, false);
@@ -487,6 +519,9 @@ void resize_tile_client(Client *grabc, bool isdrag, int32_t offsetx,
if (grabc->mon->isoverview)
return;
+ int32_t animations_state_backup = animations;
+ animations = 0;
+
const Layout *current_layout =
grabc->mon->pertag->ltidxs[grabc->mon->pertag->curtag];
if (current_layout->id == TILE || current_layout->id == DECK ||
@@ -505,6 +540,8 @@ void resize_tile_client(Client *grabc, bool isdrag, int32_t offsetx,
} else if (current_layout->id == VERTICAL_SCROLLER) {
resize_tile_scroller(grabc, isdrag, offsetx, offsety, time, true);
}
+
+ animations = animations_state_backup;
}
void reset_size_per_mon(Monitor *m, int32_t tile_cilent_num,
@@ -605,6 +642,10 @@ arrange(Monitor *m, bool want_animation, bool from_view) {
wl_list_for_each(c, &clients, link) {
+ if (!client_only_in_one_tag(c) || c->isglobal || c->isunglobal) {
+ exit_scroller_stack(c);
+ }
+
if (from_view && (c->isglobal || c->isunglobal)) {
set_size_per(m, c);
}
@@ -627,7 +668,7 @@ arrange(Monitor *m, bool want_animation, bool from_view) {
m->visible_tiling_clients++;
}
- if (ISSCROLLTILED(c)) {
+ if (ISSCROLLTILED(c) && !c->prev_in_stack) {
m->visible_scroll_tiling_clients++;
}
}
diff --git a/src/layout/horizontal.h b/src/layout/horizontal.h
index a2f5e5c..87b6033 100644
--- a/src/layout/horizontal.h
+++ b/src/layout/horizontal.h
@@ -212,6 +212,66 @@ void horizontal_scroll_adjust_fullandmax(Client *c,
target_geom->y = m->w.y + (m->w.height - target_geom->height) / 2;
}
+void arrange_stack(Client *scroller_stack_head, struct wlr_box geometry,
+ int32_t gappiv) {
+ int32_t stack_size = 0;
+ Client *iter = scroller_stack_head;
+
+ while (iter) {
+ stack_size++;
+ iter = iter->next_in_stack;
+ }
+
+ if (stack_size == 0)
+ return;
+
+ float total_proportion = 0.0f;
+ iter = scroller_stack_head;
+ while (iter) {
+ if (iter->stack_proportion <= 0.0f || iter->stack_proportion >= 1.0f) {
+ iter->stack_proportion =
+ stack_size == 1 ? 1.0f : 1.0f / (stack_size - 1);
+ }
+ total_proportion += iter->stack_proportion;
+ iter = iter->next_in_stack;
+ }
+
+ iter = scroller_stack_head;
+ while (iter) {
+ iter->stack_proportion = iter->stack_proportion / total_proportion;
+ iter = iter->next_in_stack;
+ }
+
+ int32_t client_height;
+ int32_t current_y = geometry.y;
+ int32_t remain_client_height = geometry.height - (stack_size - 1) * gappiv;
+ float remain_proportion = 1.0f;
+
+ iter = scroller_stack_head;
+ while (iter) {
+
+ client_height =
+ remain_client_height * (iter->stack_proportion / remain_proportion);
+
+ struct wlr_box client_geom = {.x = geometry.x,
+ .y = current_y,
+ .width = geometry.width,
+ .height = client_height};
+ resize(iter, client_geom, 0);
+ remain_proportion -= iter->stack_proportion;
+ remain_client_height -= client_height;
+ current_y += client_height + gappiv;
+ iter = iter->next_in_stack;
+ }
+}
+
+void horizontal_check_scroller_root_inside_mon(Client *c,
+ struct wlr_box *geometry) {
+ if (!GEOMINSIDEMON(geometry, c->mon)) {
+ geometry->x = c->mon->w.x + (c->mon->w.width - geometry->width) / 2;
+ }
+}
+
// 滚动布局
void scroller(Monitor *m) {
int32_t i, n, j;
@@ -225,6 +285,7 @@ void scroller(Monitor *m) {
int32_t cur_gappih = enablegaps ? m->gappih : 0;
int32_t cur_gappoh = enablegaps ? m->gappoh : 0;
int32_t cur_gappov = enablegaps ? m->gappov : 0;
+ int32_t cur_gappiv = enablegaps ? m->gappiv : 0;
cur_gappih =
smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappih;
@@ -251,7 +312,7 @@ void scroller(Monitor *m) {
// 第二次遍历,填充 tempClients
j = 0;
wl_list_for_each(c, &clients, link) {
- if (VISIBLEON(c, m) && ISSCROLLTILED(c)) {
+ if (VISIBLEON(c, m) && ISSCROLLTILED(c) && !c->prev_in_stack) {
tempClients[j] = c;
j++;
}
@@ -269,7 +330,8 @@ void scroller(Monitor *m) {
target_geom.width = (m->w.width - 2 * cur_gappoh) * single_proportion;
target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2;
target_geom.y = m->w.y + (m->w.height - target_geom.height) / 2;
- resize(c, target_geom, 0);
+ horizontal_check_scroller_root_inside_mon(c, &target_geom);
+ arrange_stack(c, target_geom, cur_gappiv);
free(tempClients); // 释放内存
return;
}
@@ -283,6 +345,11 @@ void scroller(Monitor *m) {
root_client = center_tiled_select(m);
}
+ // root_client might be in a stack, find the stack head
+ if (root_client) {
+ root_client = get_scroll_stack_head(root_client);
+ }
+
if (!root_client) {
free(tempClients); // 释放内存
return;
@@ -317,10 +384,14 @@ void scroller(Monitor *m) {
&target_geom);
if (tempClients[focus_client_index]->isfullscreen) {
target_geom.x = m->m.x;
- resize(tempClients[focus_client_index], target_geom, 0);
+ horizontal_check_scroller_root_inside_mon(
+ tempClients[focus_client_index], &target_geom);
+ arrange_stack(tempClients[focus_client_index], target_geom, cur_gappiv);
} else if (tempClients[focus_client_index]->ismaximizescreen) {
target_geom.x = m->w.x + cur_gappoh;
- resize(tempClients[focus_client_index], target_geom, 0);
+ horizontal_check_scroller_root_inside_mon(
+ tempClients[focus_client_index], &target_geom);
+ arrange_stack(tempClients[focus_client_index], target_geom, cur_gappiv);
} else if (need_scroller) {
if (scroller_focus_center ||
((!m->prevsel ||
@@ -338,10 +409,14 @@ void scroller(Monitor *m) {
scroller_structs)
: m->w.x + scroller_structs;
}
- resize(tempClients[focus_client_index], target_geom, 0);
+ horizontal_check_scroller_root_inside_mon(
+ tempClients[focus_client_index], &target_geom);
+ arrange_stack(tempClients[focus_client_index], target_geom, cur_gappiv);
} else {
target_geom.x = c->geom.x;
- resize(tempClients[focus_client_index], target_geom, 0);
+ horizontal_check_scroller_root_inside_mon(
+ tempClients[focus_client_index], &target_geom);
+ arrange_stack(tempClients[focus_client_index], target_geom, cur_gappiv);
}
for (i = 1; i <= focus_client_index; i++) {
@@ -351,7 +426,7 @@ void scroller(Monitor *m) {
target_geom.x = tempClients[focus_client_index - i + 1]->geom.x -
cur_gappih - target_geom.width;
- resize(c, target_geom, 0);
+ arrange_stack(c, target_geom, cur_gappiv);
}
for (i = 1; i < n - focus_client_index; i++) {
@@ -361,7 +436,7 @@ void scroller(Monitor *m) {
target_geom.x = tempClients[focus_client_index + i - 1]->geom.x +
cur_gappih +
tempClients[focus_client_index + i - 1]->geom.width;
- resize(c, target_geom, 0);
+ arrange_stack(c, target_geom, cur_gappiv);
}
free(tempClients); // 最后释放内存
@@ -853,4 +928,4 @@ void tgmix(Monitor *m) {
grid(m);
return;
}
-}
\ No newline at end of file
+}
diff --git a/src/layout/vertical.h b/src/layout/vertical.h
index 9513824..2c2dc66 100644
--- a/src/layout/vertical.h
+++ b/src/layout/vertical.h
@@ -199,6 +199,66 @@ void vertical_scroll_adjust_fullandmax(Client *c, struct wlr_box *target_geom) {
target_geom->x = m->w.x + (m->w.width - target_geom->width) / 2;
}
+void arrange_stack_vertical(Client *scroller_stack_head,
+ struct wlr_box geometry, int32_t gappih) {
+ int32_t stack_size = 0;
+ Client *iter = scroller_stack_head;
+
+ while (iter) {
+ stack_size++;
+ iter = iter->next_in_stack;
+ }
+
+ if (stack_size == 0)
+ return;
+
+ float total_proportion = 0.0f;
+ iter = scroller_stack_head;
+ while (iter) {
+ if (iter->stack_proportion <= 0.0f || iter->stack_proportion >= 1.0f) {
+ iter->stack_proportion =
+ stack_size == 1 ? 1.0f : 1.0f / (stack_size - 1);
+ }
+ total_proportion += iter->stack_proportion;
+ iter = iter->next_in_stack;
+ }
+
+ iter = scroller_stack_head;
+ while (iter) {
+ iter->stack_proportion = iter->stack_proportion / total_proportion;
+ iter = iter->next_in_stack;
+ }
+
+ int32_t client_width;
+ int32_t current_x = geometry.x;
+ int32_t remain_client_width = geometry.width - (stack_size - 1) * gappih;
+ float remain_proportion = 1.0f;
+
+ iter = scroller_stack_head;
+ while (iter) {
+
+ client_width =
+ remain_client_width * (iter->stack_proportion / remain_proportion);
+
+ struct wlr_box client_geom = {.y = geometry.y,
+ .x = current_x,
+ .height = geometry.height,
+ .width = client_width};
+ resize(iter, client_geom, 0);
+ remain_proportion -= iter->stack_proportion;
+ remain_client_width -= client_width;
+ current_x += client_width + gappih;
+ iter = iter->next_in_stack;
+ }
+}
+
+void vertical_check_scroller_root_inside_mon(Client *c,
+ struct wlr_box *geometry) {
+ if (!GEOMINSIDEMON(geometry, c->mon)) {
+ geometry->y = c->mon->w.y + (c->mon->w.height - geometry->height) / 2;
+ }
+}
+
// 竖屏滚动布局
void vertical_scroller(Monitor *m) {
int32_t i, n, j;
@@ -212,6 +272,7 @@ void vertical_scroller(Monitor *m) {
int32_t cur_gappiv = enablegaps ? m->gappiv : 0;
int32_t cur_gappov = enablegaps ? m->gappov : 0;
int32_t cur_gappoh = enablegaps ? m->gappoh : 0;
+ int32_t cur_gappih = enablegaps ? m->gappih : 0;
cur_gappiv =
smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappiv;
@@ -235,7 +296,7 @@ void vertical_scroller(Monitor *m) {
j = 0;
wl_list_for_each(c, &clients, link) {
- if (VISIBLEON(c, m) && ISSCROLLTILED(c)) {
+ if (VISIBLEON(c, m) && ISSCROLLTILED(c) && !c->prev_in_stack) {
tempClients[j] = c;
j++;
}
@@ -253,7 +314,8 @@ void vertical_scroller(Monitor *m) {
target_geom.height = (m->w.height - 2 * cur_gappov) * single_proportion;
target_geom.y = m->w.y + (m->w.height - target_geom.height) / 2;
target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2;
- resize(c, target_geom, 0);
+ vertical_check_scroller_root_inside_mon(c, &target_geom);
+ arrange_stack_vertical(c, target_geom, cur_gappih);
free(tempClients);
return;
}
@@ -267,6 +329,11 @@ void vertical_scroller(Monitor *m) {
root_client = center_tiled_select(m);
}
+ // root_client might be in a stack, find the stack head
+ if (root_client) {
+ root_client = get_scroll_stack_head(root_client);
+ }
+
if (!root_client) {
free(tempClients);
return;
@@ -302,10 +369,16 @@ void vertical_scroller(Monitor *m) {
if (tempClients[focus_client_index]->isfullscreen) {
target_geom.y = m->m.y;
- resize(tempClients[focus_client_index], target_geom, 0);
+ vertical_check_scroller_root_inside_mon(tempClients[focus_client_index],
+ &target_geom);
+ arrange_stack_vertical(tempClients[focus_client_index], target_geom,
+ cur_gappih);
} else if (tempClients[focus_client_index]->ismaximizescreen) {
target_geom.y = m->w.y + cur_gappov;
- resize(tempClients[focus_client_index], target_geom, 0);
+ vertical_check_scroller_root_inside_mon(tempClients[focus_client_index],
+ &target_geom);
+ arrange_stack_vertical(tempClients[focus_client_index], target_geom,
+ cur_gappih);
} else if (need_scroller) {
if (scroller_focus_center ||
((!m->prevsel ||
@@ -323,10 +396,16 @@ void vertical_scroller(Monitor *m) {
scroller_structs)
: m->w.y + scroller_structs;
}
- resize(tempClients[focus_client_index], target_geom, 0);
+ vertical_check_scroller_root_inside_mon(tempClients[focus_client_index],
+ &target_geom);
+ arrange_stack_vertical(tempClients[focus_client_index], target_geom,
+ cur_gappih);
} else {
target_geom.y = c->geom.y;
- resize(tempClients[focus_client_index], target_geom, 0);
+ vertical_check_scroller_root_inside_mon(tempClients[focus_client_index],
+ &target_geom);
+ arrange_stack_vertical(tempClients[focus_client_index], target_geom,
+ cur_gappih);
}
for (i = 1; i <= focus_client_index; i++) {
@@ -336,7 +415,7 @@ void vertical_scroller(Monitor *m) {
target_geom.y = tempClients[focus_client_index - i + 1]->geom.y -
cur_gappiv - target_geom.height;
- resize(c, target_geom, 0);
+ arrange_stack_vertical(c, target_geom, cur_gappih);
}
for (i = 1; i < n - focus_client_index; i++) {
@@ -346,7 +425,7 @@ void vertical_scroller(Monitor *m) {
target_geom.y = tempClients[focus_client_index + i - 1]->geom.y +
cur_gappiv +
tempClients[focus_client_index + i - 1]->geom.height;
- resize(c, target_geom, 0);
+ arrange_stack_vertical(c, target_geom, cur_gappih);
}
free(tempClients);
diff --git a/src/mango.c b/src/mango.c
index 6760b7b..250b89c 100644
--- a/src/mango.c
+++ b/src/mango.c
@@ -21,6 +21,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -101,6 +102,10 @@
(A->geom.x >= A->mon->m.x && A->geom.y >= A->mon->m.y && \
A->geom.x + A->geom.width <= A->mon->m.x + A->mon->m.width && \
A->geom.y + A->geom.height <= A->mon->m.y + A->mon->m.height)
+#define GEOMINSIDEMON(A, M) \
+ (A->x >= M->m.x && A->y >= M->m.y && \
+ A->x + A->width <= M->m.x + M->m.width && \
+ A->y + A->height <= M->m.y + M->m.height)
#define ISTILED(A) \
(A && !(A)->isfloating && !(A)->isminimized && !(A)->iskilling && \
!(A)->ismaximizescreen && !(A)->isfullscreen && !(A)->isunglobal)
@@ -375,6 +380,8 @@ struct Client {
bool is_pending_open_animation;
bool is_restoring_from_ov;
float scroller_proportion;
+ float stack_proportion;
+ float old_stack_proportion;
bool need_output_flush;
struct dwl_animation animation;
struct dwl_opacity_animation opacity_animation;
@@ -407,6 +414,8 @@ struct Client {
int32_t allow_shortcuts_inhibit;
float scroller_proportion_single;
bool isfocusing;
+ struct Client *next_in_stack;
+ struct Client *prev_in_stack;
};
typedef struct {
@@ -762,6 +771,11 @@ static void init_client_properties(Client *c);
static float *get_border_color(Client *c);
static void clear_fullscreen_and_maximized_state(Monitor *m);
static void request_fresh_all_monitors(void);
+static Client *find_client_by_direction(Client *tc, const Arg *arg,
+ bool findfloating, bool ignore_align);
+static void exit_scroller_stack(Client *c);
+static Client *get_scroll_stack_head(Client *c);
+static bool client_only_in_one_tag(Client *c);
#include "data/static_keymap.h"
#include "dispatch/bind_declare.h"
@@ -1053,6 +1067,13 @@ void swallow(Client *c, Client *w) {
c->geom = w->geom;
c->float_geom = w->float_geom;
c->scroller_proportion = w->scroller_proportion;
+ c->next_in_stack = w->next_in_stack;
+ c->prev_in_stack = w->prev_in_stack;
+ if (w->next_in_stack)
+ w->next_in_stack->prev_in_stack = c;
+ if (w->prev_in_stack)
+ w->prev_in_stack->next_in_stack = c;
+ c->stack_proportion = w->stack_proportion;
wl_list_insert(&w->link, &c->link);
wl_list_insert(&w->flink, &c->flink);
@@ -3636,7 +3657,8 @@ void keypressmod(struct wl_listener *listener, void *data) {
}
void pending_kill_client(Client *c) {
- // c->iskilling = 1; //不可以提前标记已经杀掉,因为有些客户端可能拒绝
+ if (!c || c->iskilling)
+ return;
client_send_close(c);
}
@@ -3744,6 +3766,9 @@ void init_client_properties(Client *c) {
c->float_geom.height = 0;
c->float_geom.x = 0;
c->float_geom.y = 0;
+ c->stack_proportion = 0.0f;
+ c->next_in_stack = NULL;
+ c->prev_in_stack = NULL;
}
void // old fix to 0.5
@@ -3823,7 +3848,7 @@ mapnotify(struct wl_listener *listener, void *data) {
if (selmon->sel && ISSCROLLTILED(selmon->sel) &&
VISIBLEON(selmon->sel, selmon)) {
- at_client = selmon->sel;
+ at_client = get_scroll_stack_head(selmon->sel);
} else {
at_client = center_tiled_select(selmon);
}
@@ -4175,8 +4200,8 @@ void pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy,
uint32_t time) {
struct timespec now;
- if (sloppyfocus && c && time && c->scene->node.enabled &&
- !c->animation.tagining &&
+ if (sloppyfocus && !start_drag_window && c && time &&
+ c->scene->node.enabled && !c->animation.tagining &&
(surface != seat->pointer_state.focused_surface) &&
!client_is_unmanaged(c) && VISIBLEON(c, c->mon))
focusclient(c, 0);
@@ -4353,12 +4378,32 @@ void exchange_two_client(Client *c1, Client *c2) {
double master_inner_per = 0.0f;
double master_mfact_per = 0.0f;
double stack_inner_per = 0.0f;
+ float scroller_proportion = 0.0f;
+ float stack_proportion = 0.0f;
if (c1 == NULL || c2 == NULL ||
(!exchange_cross_monitor && c1->mon != c2->mon)) {
return;
}
+ if (c1->mon != c2->mon && (c1->prev_in_stack || c2->prev_in_stack ||
+ c1->next_in_stack || c2->next_in_stack))
+ return;
+
+ Client *c1head = get_scroll_stack_head(c1);
+ Client *c2head = get_scroll_stack_head(c2);
+
+ // 交换布局参数
+ if (c1head == c2head) {
+ scroller_proportion = c1->scroller_proportion;
+ stack_proportion = c1->stack_proportion;
+
+ c1->scroller_proportion = c2->scroller_proportion;
+ c1->stack_proportion = c2->stack_proportion;
+ c2->scroller_proportion = scroller_proportion;
+ c2->stack_proportion = stack_proportion;
+ }
+
master_inner_per = c1->master_inner_per;
master_mfact_per = c1->master_mfact_per;
stack_inner_per = c1->stack_inner_per;
@@ -4371,17 +4416,46 @@ void exchange_two_client(Client *c1, Client *c2) {
c2->master_mfact_per = master_mfact_per;
c2->stack_inner_per = stack_inner_per;
+ // 交换栈链表连接
+ Client *tmp1_next_in_stack = c1->next_in_stack;
+ Client *tmp1_prev_in_stack = c1->prev_in_stack;
+ Client *tmp2_next_in_stack = c2->next_in_stack;
+ Client *tmp2_prev_in_stack = c2->prev_in_stack;
+
+ // 处理相邻节点的情况
+ if (c1->next_in_stack == c2) {
+ c1->next_in_stack = tmp2_next_in_stack;
+ c2->next_in_stack = c1;
+ c1->prev_in_stack = c2;
+ c2->prev_in_stack = tmp1_prev_in_stack;
+ if (tmp1_prev_in_stack)
+ tmp1_prev_in_stack->next_in_stack = c2;
+ if (tmp2_next_in_stack)
+ tmp2_next_in_stack->prev_in_stack = c1;
+ } else if (c2->next_in_stack == c1) {
+ c2->next_in_stack = tmp1_next_in_stack;
+ c1->next_in_stack = c2;
+ c2->prev_in_stack = c1;
+ c1->prev_in_stack = tmp2_prev_in_stack;
+ if (tmp2_prev_in_stack)
+ tmp2_prev_in_stack->next_in_stack = c1;
+ if (tmp1_next_in_stack)
+ tmp1_next_in_stack->prev_in_stack = c2;
+ } else if (c1->prev_in_stack || c2->prev_in_stack) {
+ Client *c1head = get_scroll_stack_head(c1);
+ Client *c2head = get_scroll_stack_head(c2);
+ exchange_two_client(c1head, c2head);
+ focusclient(c1, 0);
+ return;
+ }
+
+ // 交换全局链表连接
struct wl_list *tmp1_prev = c1->link.prev;
struct wl_list *tmp2_prev = c2->link.prev;
struct wl_list *tmp1_next = c1->link.next;
struct wl_list *tmp2_next = c2->link.next;
- // wl_list
- // 是双向链表,其中clients是头部节点,它的下一个节点是第一个客户端的链表节点
- // 最后一个客户端的链表节点的下一个节点也指向clients,但clients本身不是客户端的链表节点
- // 客户端遍历从clients的下一个节点开始,到检测到客户端节点的下一个是clients结束
-
- // 当c1和c2为相邻节点时
+ // 处理相邻节点的情况
if (c1->link.next == &c2->link) {
c1->link.next = c2->link.next;
c1->link.prev = &c2->link;
@@ -4408,6 +4482,7 @@ void exchange_two_client(Client *c1, Client *c2) {
tmp2_next->prev = &c1->link;
}
+ // 处理跨监视器交换
if (exchange_cross_monitor) {
tmp_mon = c2->mon;
tmp_tags = c2->tags;
@@ -4418,7 +4493,6 @@ void exchange_two_client(Client *c1, Client *c2) {
focusclient(c1, 0);
} else {
arrange(c1->mon, false, false);
- focusclient(c1, 0);
}
}
@@ -4538,6 +4612,8 @@ setfloating(Client *c, int32_t floating) {
c->bw = c->isnoborder ? 0 : borderpx;
}
+ exit_scroller_stack(c);
+
// 重新计算居中的坐标
if (!client_is_x11(c) && !c->iscustompos)
target_box =
@@ -4609,6 +4685,27 @@ void reset_maximizescreen_size(Client *c) {
resize(c, c->geom, 0);
}
+void exit_scroller_stack(Client *c) {
+ // If c is already in a stack, remove it.
+ if (c->prev_in_stack) {
+ c->prev_in_stack->next_in_stack = c->next_in_stack;
+ }
+
+ if (!c->prev_in_stack && c->next_in_stack) {
+ c->next_in_stack->scroller_proportion = c->scroller_proportion;
+ wl_list_remove(&c->next_in_stack->link);
+ wl_list_insert(&c->link, &c->next_in_stack->link);
+ }
+
+ if (c->next_in_stack) {
+ c->next_in_stack->prev_in_stack = c->prev_in_stack;
+ }
+
+ c->prev_in_stack = NULL;
+ c->next_in_stack = NULL;
+ c->stack_proportion = 0.0f;
+}
+
void setmaximizescreen(Client *c, int32_t maximizescreen) {
struct wlr_box maximizescreen_box;
if (!c || !c->mon || !client_surface(c)->mapped || c->iskilling)
@@ -4624,6 +4721,8 @@ void setmaximizescreen(Client *c, int32_t maximizescreen) {
if (c->isfullscreen)
setfullscreen(c, 0);
+ exit_scroller_stack(c);
+
if (c->isfloating)
c->float_geom = c->geom;
@@ -4685,6 +4784,8 @@ void setfullscreen(Client *c, int32_t fullscreen) // 用自定义全屏代理自
if (c->ismaximizescreen)
setmaximizescreen(c, 0);
+ exit_scroller_stack(c);
+
if (c->isfloating)
c->float_geom = c->geom;
@@ -5259,6 +5360,7 @@ void tag_client(const Arg *arg, Client *target_client) {
Client *fc = NULL;
if (target_client && arg->ui & TAGMASK) {
+ exit_scroller_stack(target_client);
target_client->tags = arg->ui & TAGMASK;
target_client->istagswitching = 1;
@@ -5406,15 +5508,22 @@ void unmapnotify(struct wl_listener *listener, void *data) {
*/
Client *c = wl_container_of(listener, c, unmap);
Monitor *m = NULL;
+ Client *nextfocus = NULL;
+ Client *next_in_stack = c->next_in_stack;
+ Client *prev_in_stack = c->prev_in_stack;
c->iskilling = 1;
if (animations && !c->is_clip_to_hide && !c->isminimized &&
(!c->mon || VISIBLEON(c, c->mon)))
init_fadeout_client(c);
+ // If the client is in a stack, remove it from the stack
+
if (c->swallowedby) {
c->swallowedby->mon = c->mon;
swallow(c->swallowedby, c);
+ } else {
+ exit_scroller_stack(c);
}
if (c == grabc) {
@@ -5435,7 +5544,13 @@ void unmapnotify(struct wl_listener *listener, void *data) {
}
if (c->mon && c->mon == selmon) {
- Client *nextfocus = focustop(selmon);
+ if (next_in_stack && !c->swallowedby) {
+ nextfocus = next_in_stack;
+ } else if (prev_in_stack && !c->swallowedby) {
+ nextfocus = prev_in_stack;
+ } else {
+ nextfocus = focustop(selmon);
+ }
if (nextfocus) {
focusclient(nextfocus, 0);
@@ -5482,6 +5597,10 @@ void unmapnotify(struct wl_listener *listener, void *data) {
c->swallowing = NULL;
}
+ c->stack_proportion = 0.0f;
+ c->next_in_stack = NULL;
+ c->prev_in_stack = NULL;
+
wlr_scene_node_destroy(&c->scene->node);
printstatus();
motionnotify(0, NULL, 0, 0, 0, 0);
From deaa26c7794885c3125088776ec7319f53aa43b5 Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Sun, 18 Jan 2026 22:54:01 +0800
Subject: [PATCH 09/47] opt: disable animaiton for resize and move window
dispatch
---
src/dispatch/bind_define.h | 17 +++++++++++++++++
src/layout/arrange.h | 5 -----
2 files changed, 17 insertions(+), 5 deletions(-)
diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h
index 3ca7997..c708de8 100644
--- a/src/dispatch/bind_define.h
+++ b/src/dispatch/bind_define.h
@@ -382,6 +382,9 @@ int32_t movewin(const Arg *arg) {
if (!c->isfloating)
togglefloating(NULL);
+ int32_t animations_state_backup = animations;
+ animations = 0;
+
switch (arg->ui) {
case NUM_TYPE_MINUS:
c->geom.x -= arg->i;
@@ -409,6 +412,7 @@ int32_t movewin(const Arg *arg) {
c->iscustomsize = 1;
c->float_geom = c->geom;
resize(c, c->geom, 0);
+ animations = animations_state_backup;
return 0;
}
@@ -425,6 +429,9 @@ int32_t resizewin(const Arg *arg) {
if (!c || c->isfullscreen || c->ismaximizescreen)
return 0;
+ int32_t animations_state_backup = animations;
+ animations = 0;
+
if (ISTILED(c)) {
switch (arg->ui) {
case NUM_TYPE_MINUS:
@@ -450,6 +457,7 @@ int32_t resizewin(const Arg *arg) {
break;
}
resize_tile_client(c, false, offsetx, offsety, 0);
+ animations = animations_state_backup;
return 0;
}
@@ -480,6 +488,7 @@ int32_t resizewin(const Arg *arg) {
c->iscustomsize = 1;
c->float_geom = c->geom;
resize(c, c->geom, 0);
+ animations = animations_state_backup;
return 0;
}
@@ -575,6 +584,9 @@ int32_t smartmovewin(const Arg *arg) {
nx = c->geom.x;
ny = c->geom.y;
+ int32_t animations_state_backup = animations;
+ animations = 0;
+
switch (arg->i) {
case UP:
tar = -99999;
@@ -661,6 +673,7 @@ int32_t smartmovewin(const Arg *arg) {
.x = nx, .y = ny, .width = c->geom.width, .height = c->geom.height};
c->iscustomsize = 1;
resize(c, c->float_geom, 1);
+ animations = animations_state_backup;
return 0;
}
@@ -676,6 +689,9 @@ int32_t smartresizewin(const Arg *arg) {
nw = c->geom.width;
nh = c->geom.height;
+ int32_t animations_state_backup = animations;
+ animations = 0;
+
switch (arg->i) {
case UP:
nh -= selmon->w.height / 8;
@@ -731,6 +747,7 @@ int32_t smartresizewin(const Arg *arg) {
.x = c->geom.x, .y = c->geom.y, .width = nw, .height = nh};
c->iscustomsize = 1;
resize(c, c->float_geom, 1);
+ animations = animations_state_backup;
return 0;
}
diff --git a/src/layout/arrange.h b/src/layout/arrange.h
index 1f7001f..fa8556e 100644
--- a/src/layout/arrange.h
+++ b/src/layout/arrange.h
@@ -519,9 +519,6 @@ void resize_tile_client(Client *grabc, bool isdrag, int32_t offsetx,
if (grabc->mon->isoverview)
return;
- int32_t animations_state_backup = animations;
- animations = 0;
-
const Layout *current_layout =
grabc->mon->pertag->ltidxs[grabc->mon->pertag->curtag];
if (current_layout->id == TILE || current_layout->id == DECK ||
@@ -540,8 +537,6 @@ void resize_tile_client(Client *grabc, bool isdrag, int32_t offsetx,
} else if (current_layout->id == VERTICAL_SCROLLER) {
resize_tile_scroller(grabc, isdrag, offsetx, offsety, time, true);
}
-
- animations = animations_state_backup;
}
void reset_size_per_mon(Monitor *m, int32_t tile_cilent_num,
From 00f56fa3aaa59d2b7ccd727d0b371b618b3d228b Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Sun, 18 Jan 2026 23:13:43 +0800
Subject: [PATCH 10/47] opt: ignore direction arg if the direction not match
the scroller direction
---
src/dispatch/bind_define.h | 31 ++++++++++++++++++++++---------
1 file changed, 22 insertions(+), 9 deletions(-)
diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h
index c708de8..a1f9a3a 100644
--- a/src/dispatch/bind_define.h
+++ b/src/dispatch/bind_define.h
@@ -1613,12 +1613,16 @@ int32_t scroller_stack(const Arg *arg) {
Client *c = selmon->sel;
Client *stack_head = NULL;
Client *source_stack_head = NULL;
- if (!c || c->isfloating || !is_scroller_layout(selmon))
+ if (!c || !c->mon || c->isfloating || !is_scroller_layout(selmon))
return 0;
if (c && (!client_only_in_one_tag(c) || c->isglobal || c->isunglobal))
return 0;
+ bool is_horizontal_layout =
+ c->mon->pertag->ltidxs[c->mon->pertag->curtag]->id == SCROLLER ? true
+ : false;
+
Client *target_client = find_client_by_direction(c, arg, false, true);
if (target_client && (!client_only_in_one_tag(target_client) ||
@@ -1646,27 +1650,36 @@ int32_t scroller_stack(const Arg *arg) {
}
if (c->prev_in_stack) {
- exit_scroller_stack(c);
- if (arg->i == LEFT || arg->i == UP) {
+ if ((is_horizontal_layout && arg->i == LEFT) ||
+ (!is_horizontal_layout && arg->i == UP)) {
+ exit_scroller_stack(c);
wl_list_remove(&c->link);
wl_list_insert(source_stack_head->link.prev, &c->link);
- } else {
+ arrange(selmon, false, false);
+
+ } else if ((is_horizontal_layout && arg->i == RIGHT) ||
+ (!is_horizontal_layout && arg->i == DOWN)) {
+ exit_scroller_stack(c);
wl_list_remove(&c->link);
wl_list_insert(&source_stack_head->link, &c->link);
+ arrange(selmon, false, false);
}
- arrange(selmon, false, false);
return 0;
} else if (c->next_in_stack) {
Client *next_in_stack = c->next_in_stack;
- exit_scroller_stack(c);
- if (arg->i == LEFT || arg->i == UP) {
+ if ((is_horizontal_layout && arg->i == LEFT) ||
+ (!is_horizontal_layout && arg->i == UP)) {
+ exit_scroller_stack(c);
wl_list_remove(&c->link);
wl_list_insert(next_in_stack->link.prev, &c->link);
- } else {
+ arrange(selmon, false, false);
+ } else if ((is_horizontal_layout && arg->i == RIGHT) ||
+ (!is_horizontal_layout && arg->i == DOWN)) {
+ exit_scroller_stack(c);
wl_list_remove(&c->link);
wl_list_insert(&next_in_stack->link, &c->link);
+ arrange(selmon, false, false);
}
- arrange(selmon, false, false);
return 0;
}
From 57d7801df2e8dc21414d710d00c4201c6c77a4c0 Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Sun, 18 Jan 2026 23:31:48 +0800
Subject: [PATCH 11/47] opt: exit stack head client maximize and fullscreen
state when toggle scroller stack
---
src/dispatch/bind_define.h | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h
index a1f9a3a..6cc6670 100644
--- a/src/dispatch/bind_define.h
+++ b/src/dispatch/bind_define.h
@@ -1700,6 +1700,14 @@ int32_t scroller_stack(const Arg *arg) {
c->prev_in_stack = stack_tail;
c->next_in_stack = NULL;
+ if (stack_head->ismaximizescreen) {
+ setmaximizescreen(stack_head, 0);
+ }
+
+ if (stack_head->isfullscreen) {
+ setfullscreen(stack_head, 0);
+ }
+
arrange(selmon, false, false);
return 0;
}
\ No newline at end of file
From 48c466254a9f88303c367f2e7f4a04e1c6976f09 Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Mon, 19 Jan 2026 10:09:35 +0800
Subject: [PATCH 12/47] feat: optimize focusdir to respect focusstack
---
src/dispatch/bind_define.h | 1 +
src/fetch/client.h | 80 +++++++++++++++++++++++++++++++++++++-
src/fetch/monitor.h | 8 ++++
src/mango.c | 3 ++
4 files changed, 90 insertions(+), 2 deletions(-)
diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h
index 6cc6670..0268078 100644
--- a/src/dispatch/bind_define.h
+++ b/src/dispatch/bind_define.h
@@ -129,6 +129,7 @@ int32_t exchange_stack_client(const Arg *arg) {
int32_t focusdir(const Arg *arg) {
Client *c = NULL;
c = direction_select(arg);
+ c = get_focused_stack_client(c);
if (c) {
focusclient(c, 1);
if (warpcursor)
diff --git a/src/fetch/client.h b/src/fetch/client.h
index 4cd0364..c1ca3cf 100644
--- a/src/fetch/client.h
+++ b/src/fetch/client.h
@@ -369,7 +369,9 @@ Client *direction_select(const Arg *arg) {
}
return find_client_by_direction(
- tc, arg, true, is_scroller_layout(selmon) && !selmon->isoverview);
+ tc, arg, true,
+ (is_scroller_layout(selmon) || is_centertile_layout(selmon)) &&
+ !selmon->isoverview);
}
/* We probably should change the name of this, it sounds like
@@ -455,4 +457,78 @@ Client *get_scroll_stack_head(Client *c) {
scroller_stack_head = scroller_stack_head->prev_in_stack;
}
return scroller_stack_head;
-}
\ No newline at end of file
+}
+
+bool client_is_in_same_stack(Client *sc, Client *tc, Client *fc) {
+ if (!sc || !tc)
+ return false;
+
+ uint32_t id = sc->mon->pertag->ltidxs[sc->mon->pertag->curtag]->id;
+
+ if (id != SCROLLER && id != VERTICAL_SCROLLER && id != TILE &&
+ id != VERTICAL_TILE && id != DECK && id != VERTICAL_DECK &&
+ id != CENTER_TILE && id != RIGHT_TILE && id != TGMIX)
+ return false;
+
+ if (id == SCROLLER || id == VERTICAL_SCROLLER) {
+ if (fc->prev_in_stack)
+ return false;
+ Client *source_stack_head = get_scroll_stack_head(sc);
+ Client *target_stack_head = get_scroll_stack_head(tc);
+ if (source_stack_head == target_stack_head)
+ return true;
+ else
+ return false;
+ }
+
+ if (id == TILE || id == VERTICAL_TILE || id == DECK ||
+ id == VERTICAL_DECK || id == RIGHT_TILE) {
+ if (!fc->ismaster)
+ return false;
+ else
+ return true;
+ }
+
+ if (id == TGMIX) {
+ if (!fc->ismaster)
+ return false;
+ if (sc->mon->visible_tiling_clients <= 3)
+ return true;
+ }
+
+ if (id == CENTER_TILE) {
+ if (!fc->ismaster)
+ return false;
+ if (sc->geom.x == tc->geom.x)
+ return true;
+ else
+ return false;
+ }
+
+ return false;
+}
+
+Client *get_focused_stack_client(Client *sc) {
+ if (!sc || sc->isfloating)
+ return sc;
+
+ Client *tc = NULL;
+ Client *fc = focustop(sc->mon);
+
+ if (fc->isfloating || sc->isfloating)
+ return sc;
+
+ wl_list_for_each(tc, &fstack, flink) {
+ if (tc->iskilling || tc->isunglobal)
+ continue;
+ if (!VISIBLEON(tc, sc->mon))
+ continue;
+ if (tc == fc)
+ continue;
+
+ if (client_is_in_same_stack(sc, tc, fc)) {
+ return tc;
+ }
+ }
+ return sc;
+}
diff --git a/src/fetch/monitor.h b/src/fetch/monitor.h
index 47a5b82..7a1ca4d 100644
--- a/src/fetch/monitor.h
+++ b/src/fetch/monitor.h
@@ -26,6 +26,14 @@ bool is_scroller_layout(Monitor *m) {
return false;
}
+bool is_centertile_layout(Monitor *m) {
+
+ if (m->pertag->ltidxs[m->pertag->curtag]->id == CENTER_TILE)
+ return true;
+
+ return false;
+}
+
uint32_t get_tag_status(uint32_t tag, Monitor *m) {
Client *c = NULL;
uint32_t status = 0;
diff --git a/src/mango.c b/src/mango.c
index 250b89c..83f31cc 100644
--- a/src/mango.c
+++ b/src/mango.c
@@ -752,6 +752,7 @@ static struct wlr_scene_tree *
wlr_scene_tree_snapshot(struct wlr_scene_node *node,
struct wlr_scene_tree *parent);
static bool is_scroller_layout(Monitor *m);
+static bool is_centertile_layout(Monitor *m);
static void create_output(struct wlr_backend *backend, void *data);
static void get_layout_abbr(char *abbr, const char *full_name);
static void apply_named_scratchpad(Client *target_client);
@@ -776,6 +777,8 @@ static Client *find_client_by_direction(Client *tc, const Arg *arg,
static void exit_scroller_stack(Client *c);
static Client *get_scroll_stack_head(Client *c);
static bool client_only_in_one_tag(Client *c);
+static Client *get_focused_stack_client(Client *sc);
+static bool client_is_in_same_stack(Client *sc, Client *tc, Client *fc);
#include "data/static_keymap.h"
#include "dispatch/bind_declare.h"
From 34aa2e019e9f206661881144edd2f2f6a55f99f9 Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Mon, 19 Jan 2026 11:50:25 +0800
Subject: [PATCH 13/47] opt: optimize drag resize for scoller
---
src/layout/arrange.h | 45 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 45 insertions(+)
diff --git a/src/layout/arrange.h b/src/layout/arrange.h
index fa8556e..7f81e5b 100644
--- a/src/layout/arrange.h
+++ b/src/layout/arrange.h
@@ -470,10 +470,55 @@ void resize_tile_scroller(Client *grabc, bool isdrag, int32_t offsetx,
if (!grabc->next_in_stack && grabc->prev_in_stack && !isdrag) {
delta_x = delta_x * -1.0f;
}
+ if (!grabc->next_in_stack && grabc->prev_in_stack && isdrag) {
+ if (moving_right) {
+ delta_x = -fabsf(delta_x);
+ } else {
+ delta_x = fabsf(delta_x);
+ }
+ }
+ if (!grabc->prev_in_stack && grabc->next_in_stack && isdrag) {
+ if (moving_left) {
+ delta_x = -fabsf(delta_x);
+ } else {
+ delta_x = fabsf(delta_x);
+ }
+ }
+
+ if (isdrag) {
+ if (moving_up) {
+ delta_y = -fabsf(delta_y);
+ } else {
+ delta_y = fabsf(delta_y);
+ }
+ }
+
} else {
if (!grabc->next_in_stack && grabc->prev_in_stack && !isdrag) {
delta_y = delta_y * -1.0f;
}
+ if (!grabc->next_in_stack && grabc->prev_in_stack && isdrag) {
+ if (moving_down) {
+ delta_y = -fabsf(delta_y);
+ } else {
+ delta_y = fabsf(delta_y);
+ }
+ }
+ if (!grabc->prev_in_stack && grabc->next_in_stack && isdrag) {
+ if (moving_up) {
+ delta_y = -fabsf(delta_y);
+ } else {
+ delta_y = fabsf(delta_y);
+ }
+ }
+
+ if (isdrag) {
+ if (moving_left) {
+ delta_x = -fabsf(delta_x);
+ } else {
+ delta_x = fabsf(delta_x);
+ }
+ }
}
// 直接设置新的比例,基于初始值 + 变化量
From eff11a5912b118f8bace034d9230f2cc3d83acc0 Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Mon, 19 Jan 2026 12:13:42 +0800
Subject: [PATCH 14/47] opt: focusdir miss remember focuslink when between two
different stack client
---
src/fetch/client.h | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/fetch/client.h b/src/fetch/client.h
index c1ca3cf..6294e17 100644
--- a/src/fetch/client.h
+++ b/src/fetch/client.h
@@ -471,10 +471,11 @@ bool client_is_in_same_stack(Client *sc, Client *tc, Client *fc) {
return false;
if (id == SCROLLER || id == VERTICAL_SCROLLER) {
- if (fc->prev_in_stack)
- return false;
Client *source_stack_head = get_scroll_stack_head(sc);
Client *target_stack_head = get_scroll_stack_head(tc);
+ Client *fc_head = get_scroll_stack_head(fc);
+ if (fc->prev_in_stack && fc_head == source_stack_head)
+ return false;
if (source_stack_head == target_stack_head)
return true;
else
From 43257ad49cd76dc42ab4237dc9650d9f96a7fd26 Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Mon, 19 Jan 2026 14:21:28 +0800
Subject: [PATCH 15/47] opt: support center scroller stack in centerwin
dispatch
---
src/dispatch/bind_define.h | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h
index 0268078..2d40c22 100644
--- a/src/dispatch/bind_define.h
+++ b/src/dispatch/bind_define.h
@@ -769,10 +769,13 @@ int32_t centerwin(const Arg *arg) {
if (!is_scroller_layout(selmon))
return 0;
+ Client *stack_head = get_scroll_stack_head(c);
if (selmon->pertag->ltidxs[selmon->pertag->curtag]->id == SCROLLER) {
- c->geom.x = selmon->w.x + (selmon->w.width - c->geom.width) / 2;
+ stack_head->geom.x =
+ selmon->w.x + (selmon->w.width - stack_head->geom.width) / 2;
} else {
- c->geom.y = selmon->w.y + (selmon->w.height - c->geom.height) / 2;
+ stack_head->geom.y =
+ selmon->w.y + (selmon->w.height - stack_head->geom.height) / 2;
}
arrange(selmon, false, false);
From fc13b0ff15973c66e24ab4a06a31748070af2a51 Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Mon, 19 Jan 2026 22:18:05 +0800
Subject: [PATCH 16/47] opt: optimize global client focus logic
---
src/layout/arrange.h | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/layout/arrange.h b/src/layout/arrange.h
index 7f81e5b..6ff1dd2 100644
--- a/src/layout/arrange.h
+++ b/src/layout/arrange.h
@@ -692,8 +692,10 @@ arrange(Monitor *m, bool want_animation, bool from_view) {
if (c->mon == m && (c->isglobal || c->isunglobal)) {
c->tags = m->tagset[m->seltags];
- if (c->mon->sel == NULL)
- focusclient(c, 0);
+ }
+
+ if (from_view && m->sel == NULL && c->isglobal && VISIBLEON(c, m)) {
+ focusclient(c, 1);
}
if (VISIBLEON(c, m)) {
From 04ac4bf99c0682f5a1615adec1b568d55468f547 Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Tue, 20 Jan 2026 11:28:33 +0800
Subject: [PATCH 17/47] opt: find same stack first in direction find
---
src/fetch/client.h | 104 +++++++++++++++++++++++++++++++++++++++++----
1 file changed, 96 insertions(+), 8 deletions(-)
diff --git a/src/fetch/client.h b/src/fetch/client.h
index 6294e17..e843d69 100644
--- a/src/fetch/client.h
+++ b/src/fetch/client.h
@@ -215,6 +215,27 @@ Client *find_client_by_direction(Client *tc, const Arg *arg, bool findfloating,
}
}
}
+ if (!tempFocusClients) {
+ for (int32_t _i = 0; _i <= last; _i++) {
+ if (tempClients[_i]->geom.y < sel_y &&
+ tempClients[_i]->mon == tc->mon &&
+ client_is_in_same_stack(tc, tempClients[_i], NULL)) {
+ int32_t dis_x = tempClients[_i]->geom.x - sel_x;
+ int32_t dis_y = tempClients[_i]->geom.y - sel_y;
+ int64_t tmp_distance =
+ dis_x * dis_x + dis_y * dis_y; // 计算距离
+ if (tmp_distance < distance) {
+ distance = tmp_distance;
+ tempFocusClients = tempClients[_i];
+ }
+ if (tempClients[_i]->mon == tc->mon &&
+ tmp_distance < same_monitor_distance) {
+ same_monitor_distance = tmp_distance;
+ tempSameMonitorFocusClients = tempClients[_i];
+ }
+ }
+ }
+ }
if (!tempFocusClients) {
for (int32_t _i = 0; _i <= last; _i++) {
if (tempClients[_i]->geom.y < sel_y) {
@@ -252,6 +273,27 @@ Client *find_client_by_direction(Client *tc, const Arg *arg, bool findfloating,
}
}
}
+ if (!tempFocusClients) {
+ for (int32_t _i = 0; _i <= last; _i++) {
+ if (tempClients[_i]->geom.y > sel_y &&
+ tempClients[_i]->mon == tc->mon &&
+ client_is_in_same_stack(tc, tempClients[_i], NULL)) {
+ int32_t dis_x = tempClients[_i]->geom.x - sel_x;
+ int32_t dis_y = tempClients[_i]->geom.y - sel_y;
+ int64_t tmp_distance =
+ dis_x * dis_x + dis_y * dis_y; // 计算距离
+ if (tmp_distance < distance) {
+ distance = tmp_distance;
+ tempFocusClients = tempClients[_i];
+ }
+ if (tempClients[_i]->mon == tc->mon &&
+ tmp_distance < same_monitor_distance) {
+ same_monitor_distance = tmp_distance;
+ tempSameMonitorFocusClients = tempClients[_i];
+ }
+ }
+ }
+ }
if (!tempFocusClients) {
for (int32_t _i = 0; _i <= last; _i++) {
if (tempClients[_i]->geom.y > sel_y) {
@@ -289,6 +331,27 @@ Client *find_client_by_direction(Client *tc, const Arg *arg, bool findfloating,
}
}
}
+ if (!tempFocusClients) {
+ for (int32_t _i = 0; _i <= last; _i++) {
+ if (tempClients[_i]->geom.x < sel_x &&
+ tempClients[_i]->mon == tc->mon &&
+ client_is_in_same_stack(tc, tempClients[_i], NULL)) {
+ int32_t dis_x = tempClients[_i]->geom.x - sel_x;
+ int32_t dis_y = tempClients[_i]->geom.y - sel_y;
+ int64_t tmp_distance =
+ dis_x * dis_x + dis_y * dis_y; // 计算距离
+ if (tmp_distance < distance) {
+ distance = tmp_distance;
+ tempFocusClients = tempClients[_i];
+ }
+ if (tempClients[_i]->mon == tc->mon &&
+ tmp_distance < same_monitor_distance) {
+ same_monitor_distance = tmp_distance;
+ tempSameMonitorFocusClients = tempClients[_i];
+ }
+ }
+ }
+ }
if (!tempFocusClients) {
for (int32_t _i = 0; _i <= last; _i++) {
if (tempClients[_i]->geom.x < sel_x) {
@@ -326,6 +389,27 @@ Client *find_client_by_direction(Client *tc, const Arg *arg, bool findfloating,
}
}
}
+ if (!tempFocusClients) {
+ for (int32_t _i = 0; _i <= last; _i++) {
+ if (tempClients[_i]->geom.x > sel_x &&
+ tempClients[_i]->mon == tc->mon &&
+ client_is_in_same_stack(tc, tempClients[_i], NULL)) {
+ int32_t dis_x = tempClients[_i]->geom.x - sel_x;
+ int32_t dis_y = tempClients[_i]->geom.y - sel_y;
+ int64_t tmp_distance =
+ dis_x * dis_x + dis_y * dis_y; // 计算距离
+ if (tmp_distance < distance) {
+ distance = tmp_distance;
+ tempFocusClients = tempClients[_i];
+ }
+ if (tempClients[_i]->mon == tc->mon &&
+ tmp_distance < same_monitor_distance) {
+ same_monitor_distance = tmp_distance;
+ tempSameMonitorFocusClients = tempClients[_i];
+ }
+ }
+ }
+ }
if (!tempFocusClients) {
for (int32_t _i = 0; _i <= last; _i++) {
if (tempClients[_i]->geom.x > sel_x) {
@@ -453,6 +537,10 @@ bool client_only_in_one_tag(Client *c) {
Client *get_scroll_stack_head(Client *c) {
Client *scroller_stack_head = c;
+
+ if (!scroller_stack_head)
+ return NULL;
+
while (scroller_stack_head->prev_in_stack) {
scroller_stack_head = scroller_stack_head->prev_in_stack;
}
@@ -473,8 +561,8 @@ bool client_is_in_same_stack(Client *sc, Client *tc, Client *fc) {
if (id == SCROLLER || id == VERTICAL_SCROLLER) {
Client *source_stack_head = get_scroll_stack_head(sc);
Client *target_stack_head = get_scroll_stack_head(tc);
- Client *fc_head = get_scroll_stack_head(fc);
- if (fc->prev_in_stack && fc_head == source_stack_head)
+ Client *fc_head = fc ? get_scroll_stack_head(fc) : NULL;
+ if (fc && fc->prev_in_stack && fc_head == source_stack_head)
return false;
if (source_stack_head == target_stack_head)
return true;
@@ -484,23 +572,23 @@ bool client_is_in_same_stack(Client *sc, Client *tc, Client *fc) {
if (id == TILE || id == VERTICAL_TILE || id == DECK ||
id == VERTICAL_DECK || id == RIGHT_TILE) {
- if (!fc->ismaster)
+ if (fc && !fc->ismaster)
return false;
- else
+ else if (!sc->ismaster)
return true;
}
if (id == TGMIX) {
- if (!fc->ismaster)
+ if (fc && !fc->ismaster)
return false;
- if (sc->mon->visible_tiling_clients <= 3)
+ if (!sc->ismaster && sc->mon->visible_tiling_clients <= 3)
return true;
}
if (id == CENTER_TILE) {
- if (!fc->ismaster)
+ if (fc && !fc->ismaster)
return false;
- if (sc->geom.x == tc->geom.x)
+ if (!sc->ismaster && sc->geom.x == tc->geom.x)
return true;
else
return false;
From 5942c5d80783c78f0c53f19891d6c4354f54655e Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Tue, 20 Jan 2026 13:50:43 +0800
Subject: [PATCH 18/47] opt: only disable animation for resizewin tiling window
---
src/dispatch/bind_define.h | 15 ++-------------
1 file changed, 2 insertions(+), 13 deletions(-)
diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h
index 2d40c22..95d365c 100644
--- a/src/dispatch/bind_define.h
+++ b/src/dispatch/bind_define.h
@@ -383,9 +383,6 @@ int32_t movewin(const Arg *arg) {
if (!c->isfloating)
togglefloating(NULL);
- int32_t animations_state_backup = animations;
- animations = 0;
-
switch (arg->ui) {
case NUM_TYPE_MINUS:
c->geom.x -= arg->i;
@@ -413,7 +410,6 @@ int32_t movewin(const Arg *arg) {
c->iscustomsize = 1;
c->float_geom = c->geom;
resize(c, c->geom, 0);
- animations = animations_state_backup;
return 0;
}
@@ -431,7 +427,8 @@ int32_t resizewin(const Arg *arg) {
return 0;
int32_t animations_state_backup = animations;
- animations = 0;
+ if (!c->isfloating)
+ animations = 0;
if (ISTILED(c)) {
switch (arg->ui) {
@@ -585,9 +582,6 @@ int32_t smartmovewin(const Arg *arg) {
nx = c->geom.x;
ny = c->geom.y;
- int32_t animations_state_backup = animations;
- animations = 0;
-
switch (arg->i) {
case UP:
tar = -99999;
@@ -674,7 +668,6 @@ int32_t smartmovewin(const Arg *arg) {
.x = nx, .y = ny, .width = c->geom.width, .height = c->geom.height};
c->iscustomsize = 1;
resize(c, c->float_geom, 1);
- animations = animations_state_backup;
return 0;
}
@@ -690,9 +683,6 @@ int32_t smartresizewin(const Arg *arg) {
nw = c->geom.width;
nh = c->geom.height;
- int32_t animations_state_backup = animations;
- animations = 0;
-
switch (arg->i) {
case UP:
nh -= selmon->w.height / 8;
@@ -748,7 +738,6 @@ int32_t smartresizewin(const Arg *arg) {
.x = c->geom.x, .y = c->geom.y, .width = nw, .height = nh};
c->iscustomsize = 1;
resize(c, c->float_geom, 1);
- animations = animations_state_backup;
return 0;
}
From 1124d477861f60d436d15f2d84ae5d0c7ced9091 Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Tue, 20 Jan 2026 14:25:07 +0800
Subject: [PATCH 19/47] fix: fix exchange client error when not in scroller
layout
---
src/mango.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/mango.c b/src/mango.c
index 83f31cc..3a2331d 100644
--- a/src/mango.c
+++ b/src/mango.c
@@ -4444,7 +4444,8 @@ void exchange_two_client(Client *c1, Client *c2) {
tmp2_prev_in_stack->next_in_stack = c1;
if (tmp1_next_in_stack)
tmp1_next_in_stack->prev_in_stack = c2;
- } else if (c1->prev_in_stack || c2->prev_in_stack) {
+ } else if (is_scroller_layout(c1->mon) &&
+ (c1->prev_in_stack || c2->prev_in_stack)) {
Client *c1head = get_scroll_stack_head(c1);
Client *c2head = get_scroll_stack_head(c2);
exchange_two_client(c1head, c2head);
From d78526d1e92acc3c926c1da26d27708f500875aa Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Tue, 20 Jan 2026 20:33:32 +0800
Subject: [PATCH 20/47] bump version to 0.11.0
---
meson.build | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/meson.build b/meson.build
index 83b9513..d71eb59 100644
--- a/meson.build
+++ b/meson.build
@@ -1,5 +1,5 @@
project('mango', ['c', 'cpp'],
- version : '0.10.10',
+ version : '0.11.0',
)
subdir('protocols')
From 49921eadfa251641379c2f2cd824034eb7075e2f Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Wed, 21 Jan 2026 13:43:40 +0800
Subject: [PATCH 21/47] feat: add drag_corner drag_warp_cursor
---
src/config/parse_config.h | 10 ++++++++++
src/config/preset.h | 4 +++-
src/dispatch/bind_define.h | 30 ++++++++++++++++++++++++++----
src/mango.c | 32 ++++++++++++++++++++++++++------
4 files changed, 65 insertions(+), 11 deletions(-)
diff --git a/src/config/parse_config.h b/src/config/parse_config.h
index b2535b1..15857ae 100644
--- a/src/config/parse_config.h
+++ b/src/config/parse_config.h
@@ -243,6 +243,8 @@ typedef struct {
int32_t idleinhibit_ignore_visible;
int32_t sloppyfocus;
int32_t warpcursor;
+ int32_t drag_corner;
+ int32_t drag_warp_cursor;
/* keyboard */
int32_t repeat_rate;
@@ -1475,6 +1477,10 @@ void parse_option(Config *config, char *key, char *value) {
config->sloppyfocus = atoi(value);
} else if (strcmp(key, "warpcursor") == 0) {
config->warpcursor = atoi(value);
+ } else if (strcmp(key, "drag_corner") == 0) {
+ config->drag_corner = atoi(value);
+ } else if (strcmp(key, "drag_warp_cursor") == 0) {
+ config->drag_warp_cursor = atoi(value);
} else if (strcmp(key, "smartgaps") == 0) {
config->smartgaps = atoi(value);
} else if (strcmp(key, "repeat_rate") == 0) {
@@ -2757,6 +2763,8 @@ void override_config(void) {
CLAMP_INT(config.idleinhibit_ignore_visible, 0, 1);
sloppyfocus = CLAMP_INT(config.sloppyfocus, 0, 1);
warpcursor = CLAMP_INT(config.warpcursor, 0, 1);
+ drag_corner = CLAMP_INT(config.drag_corner, 0, 4);
+ drag_warp_cursor = CLAMP_INT(config.drag_warp_cursor, 0, 1);
focus_cross_monitor = CLAMP_INT(config.focus_cross_monitor, 0, 1);
exchange_cross_monitor = CLAMP_INT(config.exchange_cross_monitor, 0, 1);
scratchpad_cross_monitor = CLAMP_INT(config.scratchpad_cross_monitor, 0, 1);
@@ -2954,6 +2962,8 @@ void set_value_default() {
config.cursor_hide_timeout = cursor_hide_timeout;
config.warpcursor = warpcursor; /* Warp cursor to focused client */
+ config.drag_corner = drag_corner;
+ config.drag_warp_cursor = drag_warp_cursor;
config.repeat_rate = repeat_rate;
config.repeat_delay = repeat_delay;
diff --git a/src/config/preset.h b/src/config/preset.h
index 6f3cd89..935ea0c 100644
--- a/src/config/preset.h
+++ b/src/config/preset.h
@@ -101,7 +101,9 @@ int32_t overviewgappo = 30; /* overview时 窗口与窗口 缝隙大小 */
* behavior */
float fullscreen_bg[] = {0.1, 0.1, 0.1, 1.0};
-int32_t warpcursor = 1; /* Warp cursor to focused client */
+int32_t warpcursor = 1;
+int32_t drag_corner = 3;
+int32_t drag_warp_cursor = 1;
int32_t xwayland_persistence = 1; /* xwayland persistence */
int32_t syncobj_enable = 0;
int32_t adaptive_sync = 0;
diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h
index 95d365c..f27ed67 100644
--- a/src/dispatch/bind_define.h
+++ b/src/dispatch/bind_define.h
@@ -338,6 +338,9 @@ int32_t killclient(const Arg *arg) {
}
int32_t moveresize(const Arg *arg) {
+ const char *cursors[] = {"nw-resize", "ne-resize", "sw-resize",
+ "se-resize"};
+
if (cursor_mode != CurNormal && cursor_mode != CurPressed)
return 0;
xytonode(cursor->x, cursor->y, NULL, &grabc, NULL, NULL, NULL);
@@ -363,10 +366,29 @@ int32_t moveresize(const Arg *arg) {
/* Doesn't work for X11 output - the next absolute motion event
* returns the cursor to where it started */
if (grabc->isfloating) {
- wlr_cursor_warp_closest(cursor, NULL,
- grabc->geom.x + grabc->geom.width,
- grabc->geom.y + grabc->geom.height);
- wlr_cursor_set_xcursor(cursor, cursor_mgr, "bottom_right_corner");
+ rzcorner = drag_corner;
+ grabcx = (int)round(cursor->x);
+ grabcy = (int)round(cursor->y);
+ if (rzcorner == 4)
+ /* identify the closest corner index */
+ rzcorner = (grabcx - grabc->geom.x <
+ grabc->geom.x + grabc->geom.width - grabcx
+ ? 0
+ : 1) +
+ (grabcy - grabc->geom.y <
+ grabc->geom.y + grabc->geom.height - grabcy
+ ? 0
+ : 2);
+
+ if (drag_warp_cursor) {
+ grabcx = rzcorner & 1 ? grabc->geom.x + grabc->geom.width
+ : grabc->geom.x;
+ grabcy = rzcorner & 2 ? grabc->geom.y + grabc->geom.height
+ : grabc->geom.y;
+ wlr_cursor_warp_closest(cursor, NULL, grabcx, grabcy);
+ }
+
+ wlr_cursor_set_xcursor(cursor, cursor_mgr, cursors[rzcorner]);
} else {
wlr_cursor_set_xcursor(cursor, cursor_mgr, "grab");
}
diff --git a/src/mango.c b/src/mango.c
index 3a2331d..f61e27f 100644
--- a/src/mango.c
+++ b/src/mango.c
@@ -841,6 +841,7 @@ static struct wl_list inputdevices;
static struct wl_list keyboard_shortcut_inhibitors;
static uint32_t cursor_mode;
static Client *grabc;
+static int32_t rzcorner;
static int32_t grabcx, grabcy; /* client-relative */
static int32_t drag_begin_cursorx, drag_begin_cursory; /* client-relative */
static bool start_drag_window = false;
@@ -3988,6 +3989,30 @@ void motionabsolute(struct wl_listener *listener, void *data) {
motionnotify(event->time_msec, &event->pointer->base, dx, dy, dx, dy);
}
+void resize_floating_window(Client *grabc) {
+ int cdx = (int)round(cursor->x) - grabcx;
+ int cdy = (int)round(cursor->y) - grabcy;
+
+ cdx = !(rzcorner & 1) && grabc->geom.width - 2 * (int)grabc->bw - cdx < 1
+ ? 0
+ : cdx;
+ cdy = !(rzcorner & 2) && grabc->geom.height - 2 * (int)grabc->bw - cdy < 1
+ ? 0
+ : cdy;
+
+ const struct wlr_box box = {
+ .x = grabc->geom.x + (rzcorner & 1 ? 0 : cdx),
+ .y = grabc->geom.y + (rzcorner & 2 ? 0 : cdy),
+ .width = grabc->geom.width + (rzcorner & 1 ? cdx : -cdx),
+ .height = grabc->geom.height + (rzcorner & 2 ? cdy : -cdy)};
+
+ grabc->float_geom = box;
+
+ resize(grabc, box, 1);
+ grabcx += cdx;
+ grabcy += cdy;
+}
+
void motionnotify(uint32_t time, struct wlr_input_device *device, double dx,
double dy, double dx_unaccel, double dy_unaccel) {
double sx = 0, sy = 0, sx_confined, sy_confined;
@@ -4065,14 +4090,9 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx,
} else if (cursor_mode == CurResize) {
if (grabc->isfloating) {
grabc->iscustomsize = 1;
- grabc->float_geom = (struct wlr_box){
- .x = grabc->geom.x,
- .y = grabc->geom.y,
- .width = (int32_t)round(cursor->x) - grabc->geom.x,
- .height = (int32_t)round(cursor->y) - grabc->geom.y};
if (last_apply_drap_time == 0 ||
time - last_apply_drap_time > drag_refresh_interval) {
- resize(grabc, grabc->float_geom, 1);
+ resize_floating_window(grabc);
last_apply_drap_time = time;
}
return;
From 334bc076a0d0b368efef5626f8e6d4b429133c3f Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Sat, 24 Jan 2026 11:43:24 +0800
Subject: [PATCH 22/47] opt: optimize smartgap
---
src/fetch/client.h | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/fetch/client.h b/src/fetch/client.h
index e843d69..bf30e17 100644
--- a/src/fetch/client.h
+++ b/src/fetch/client.h
@@ -12,7 +12,9 @@ bool check_hit_no_border(Client *c) {
}
}
- if (no_border_when_single && c && c->mon && c->mon->visible_clients == 1) {
+ if (no_border_when_single && c && c->mon &&
+ ((ISSCROLLTILED(c) && c->mon->visible_scroll_tiling_clients == 1) ||
+ c->mon->visible_clients == 1)) {
hit_no_border = true;
}
return hit_no_border;
From 0652f99e6e30aaa7e85c6828ba729bf4075a8270 Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Sat, 24 Jan 2026 12:04:17 +0800
Subject: [PATCH 23/47] opt: change preset config prefer
---
src/config/preset.h | 3 +--
src/layout/layout.h | 4 ++--
2 files changed, 3 insertions(+), 4 deletions(-)
diff --git a/src/config/preset.h b/src/config/preset.h
index 935ea0c..5dea6cb 100644
--- a/src/config/preset.h
+++ b/src/config/preset.h
@@ -62,7 +62,7 @@ float scratchpad_height_ratio = 0.9;
int32_t scroller_structs = 20;
float scroller_default_proportion = 0.9;
float scroller_default_proportion_single = 1.0;
-int32_t scroller_ignore_proportion_single = 0;
+int32_t scroller_ignore_proportion_single = 1;
int32_t scroller_focus_center = 0;
int32_t scroller_prefer_center = 0;
int32_t focus_cross_monitor = 0;
@@ -235,4 +235,3 @@ double shadows_blur = 15;
int32_t shadows_position_x = 0;
int32_t shadows_position_y = 0;
float shadowscolor[] = COLOR(0x000000ff);
-;
diff --git a/src/layout/layout.h b/src/layout/layout.h
index 169ab11..f896ac2 100644
--- a/src/layout/layout.h
+++ b/src/layout/layout.h
@@ -17,8 +17,8 @@ static void tgmix(Monitor *m);
Layout overviewlayout = {"", overview, "overview"};
enum {
- SCROLLER,
TILE,
+ SCROLLER,
GRID,
MONOCLE,
DECK,
@@ -34,8 +34,8 @@ enum {
Layout layouts[] = {
// 最少两个,不能删除少于两个
/* symbol arrange function name */
- {"S", scroller, "scroller", SCROLLER}, // 滚动布局
{"T", tile, "tile", TILE}, // 平铺布局
+ {"S", scroller, "scroller", SCROLLER}, // 滚动布局
{"G", grid, "grid", GRID}, // 格子布局
{"M", monocle, "monocle", MONOCLE}, // 单屏布局
{"K", deck, "deck", DECK}, // 卡片布局
From f1cca251b8ef2bfce0f5dadbb0d1d609a51bd0ca Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Sat, 24 Jan 2026 19:48:43 +0800
Subject: [PATCH 24/47] fix: bordercorlor not update when it is in open
animaiton
---
src/animation/client.h | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/animation/client.h b/src/animation/client.h
index 849bc10..3f44727 100644
--- a/src/animation/client.h
+++ b/src/animation/client.h
@@ -1131,6 +1131,7 @@ bool client_apply_focus_opacity(Client *c) {
target_opacity = opacity;
}
client_set_opacity(c, target_opacity);
+ client_set_border_color(c, c->opacity_animation.target_border_color);
} else if (animations && c->opacity_animation.running) {
struct timespec now;
From 00de5230393d75d22416c0fbe9c93a6ec5434dea Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Sat, 24 Jan 2026 23:02:49 +0800
Subject: [PATCH 25/47] fix: border apply error for smartgap
---
src/animation/client.h | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/animation/client.h b/src/animation/client.h
index 3f44727..15a0d16 100644
--- a/src/animation/client.h
+++ b/src/animation/client.h
@@ -363,7 +363,6 @@ void apply_border(Client *c) {
current_corner_location = set_client_corner_location(c);
}
- // Handle no-border cases
if (hit_no_border && smartgaps) {
c->bw = 0;
c->fake_no_border = true;
@@ -981,6 +980,12 @@ void resize(Client *c, struct wlr_box geo, int32_t interact) {
c->bw = 0;
}
+ bool hit_no_border = check_hit_no_border(c);
+ if (hit_no_border && smartgaps) {
+ c->bw = 0;
+ c->fake_no_border = true;
+ }
+
// c->geom 是真实的窗口大小和位置,跟过度的动画无关,用于计算布局
c->configure_serial = client_set_size(c, c->geom.width - 2 * c->bw,
c->geom.height - 2 * c->bw);
From 05010a1114bb66cb7a3d0df0f1939e00c338def2 Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Mon, 26 Jan 2026 18:35:08 +0800
Subject: [PATCH 26/47] fix: crash when some qt app commit a null surface
---
src/mango.c | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/src/mango.c b/src/mango.c
index f61e27f..e285a89 100644
--- a/src/mango.c
+++ b/src/mango.c
@@ -2443,12 +2443,12 @@ void commitpopup(struct wl_listener *listener, void *data) {
struct wlr_box box;
int32_t type = -1;
- if (!popup->base->initial_commit)
- return;
+ if (!popup || !popup->base->initial_commit)
+ goto commitpopup_listen_free;
type = toplevel_from_wlr_surface(popup->base->surface, &c, &l);
- if (!popup->parent || type < 0)
- return;
+ if (!popup->parent || !popup->parent->data || type < 0)
+ goto commitpopup_listen_free;
wlr_scene_node_raise_to_top(popup->parent->data);
@@ -2456,13 +2456,14 @@ void commitpopup(struct wl_listener *listener, void *data) {
wlr_scene_xdg_surface_create(popup->parent->data, popup->base);
if ((l && !l->mon) || (c && !c->mon)) {
wlr_xdg_popup_destroy(popup);
- return;
+ goto commitpopup_listen_free;
}
box = type == LayerShell ? l->mon->m : c->mon->w;
box.x -= (type == LayerShell ? l->scene->node.x : c->geom.x);
box.y -= (type == LayerShell ? l->scene->node.y : c->geom.y);
wlr_xdg_popup_unconstrain_from_box(popup, &box);
+commitpopup_listen_free:
wl_list_remove(&listener->link);
free(listener);
}
From 6624d80522b512a381e88315787e1d5ed8bcdd19 Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Tue, 27 Jan 2026 14:45:28 +0800
Subject: [PATCH 27/47] break change: new monitorrule format
---
src/config/parse_config.h | 237 ++++++++++++++++++++------------------
src/layout/horizontal.h | 9 +-
src/layout/vertical.h | 9 +-
src/mango.c | 42 +++----
4 files changed, 149 insertions(+), 148 deletions(-)
diff --git a/src/config/parse_config.h b/src/config/parse_config.h
index 15857ae..f94e679 100644
--- a/src/config/parse_config.h
+++ b/src/config/parse_config.h
@@ -101,15 +101,13 @@ typedef struct {
} ConfigWinRule;
typedef struct {
- const char *name; // 显示器名称
- float mfact; // 主区域比例
- int32_t nmaster; // 主区域窗口数量
- const char *layout; // 布局名称(字符串)
- int32_t rr; // 旋转和翻转(假设为整数)
- float scale; // 显示器缩放比例
- int32_t x, y; // 显示器位置
- int32_t width, height; // 显示器分辨率
- float refresh; // 刷新率
+ const char *name; // Monitor name
+ int32_t rr; // Rotate and flip (assume integer)
+ float scale; // Monitor scale factor
+ int32_t x, y; // Monitor position
+ int32_t width, height; // Monitor resolution
+ float refresh; // Refresh rate
+ int32_t vrr; // variable refresh rate
} ConfigMonitorRule;
// 修改后的宏定义
@@ -156,9 +154,11 @@ typedef struct {
} GestureBinding;
typedef struct {
- int32_t id; // 标签ID (1-9)
- char *layout_name; // 布局名称
+ int32_t id;
+ char *layout_name;
char *monitor_name;
+ float mfact;
+ int32_t nmaster;
int32_t no_render_border;
int32_t no_hide;
} ConfigTagRule;
@@ -1605,6 +1605,66 @@ void parse_option(Config *config, char *key, char *value) {
} else {
convert_hex_to_rgba(config->overlaycolor, color);
}
+ } else if (strcmp(key, "monitorrule") == 0) {
+ config->monitor_rules =
+ realloc(config->monitor_rules, (config->monitor_rules_count + 1) *
+ sizeof(ConfigMonitorRule));
+ if (!config->monitor_rules) {
+ fprintf(stderr,
+ "Error: Failed to allocate memory for monitor rules\n");
+ return;
+ }
+
+ ConfigMonitorRule *rule =
+ &config->monitor_rules[config->monitor_rules_count];
+ memset(rule, 0, sizeof(ConfigMonitorRule));
+
+ // 设置默认值
+ rule->name = NULL;
+ rule->rr = 0;
+ rule->scale = 1.0f;
+ rule->x = INT32_MAX;
+ rule->y = INT32_MAX;
+ rule->width = -1;
+ rule->height = -1;
+ rule->refresh = 0.0f;
+ rule->vrr = 0;
+
+ char *token = strtok(value, ",");
+ while (token != NULL) {
+ char *colon = strchr(token, ':');
+ if (colon != NULL) {
+ *colon = '\0';
+ char *key = token;
+ char *val = colon + 1;
+
+ trim_whitespace(key);
+ trim_whitespace(val);
+
+ if (strcmp(key, "name") == 0) {
+ rule->name = strdup(val);
+ } else if (strcmp(key, "rr") == 0) {
+ rule->rr = CLAMP_INT(atoi(val), 0, 7);
+ } else if (strcmp(key, "scale") == 0) {
+ rule->scale = CLAMP_FLOAT(atof(val), 0.001f, 1000.0f);
+ } else if (strcmp(key, "x") == 0) {
+ rule->x = atoi(val);
+ } else if (strcmp(key, "y") == 0) {
+ rule->y = atoi(val);
+ } else if (strcmp(key, "width") == 0) {
+ rule->width = CLAMP_INT(atoi(val), 1, INT32_MAX);
+ } else if (strcmp(key, "height") == 0) {
+ rule->height = CLAMP_INT(atoi(val), 1, INT32_MAX);
+ } else if (strcmp(key, "refresh") == 0) {
+ rule->refresh = CLAMP_FLOAT(atof(val), 0.001f, 1000.0f);
+ } else if (strcmp(key, "vrr") == 0) {
+ rule->vrr = CLAMP_INT(atoi(val), 0, 1);
+ }
+ }
+ token = strtok(NULL, ",");
+ }
+
+ config->monitor_rules_count++;
} else if (strcmp(key, "tagrule") == 0) {
config->tag_rules =
realloc(config->tag_rules,
@@ -1621,6 +1681,10 @@ void parse_option(Config *config, char *key, char *value) {
rule->id = 0;
rule->layout_name = NULL;
rule->monitor_name = NULL;
+ rule->nmaster = 0;
+ rule->mfact = 0.0f;
+ rule->no_render_border = 0;
+ rule->no_hide = 0;
char *token = strtok(value, ",");
while (token != NULL) {
@@ -1643,6 +1707,10 @@ void parse_option(Config *config, char *key, char *value) {
rule->no_render_border = CLAMP_INT(atoi(val), 0, 1);
} else if (strcmp(key, "no_hide") == 0) {
rule->no_hide = CLAMP_INT(atoi(val), 0, 1);
+ } else if (strcmp(key, "nmaster") == 0) {
+ rule->nmaster = CLAMP_INT(atoi(val), 1, 99);
+ } else if (strcmp(key, "mfact") == 0) {
+ rule->mfact = CLAMP_FLOAT(atof(val), 0.1f, 0.9f);
}
}
token = strtok(NULL, ",");
@@ -1871,75 +1939,6 @@ void parse_option(Config *config, char *key, char *value) {
token = strtok(NULL, ",");
}
config->window_rules_count++;
- } else if (strcmp(key, "monitorrule") == 0) {
- config->monitor_rules =
- realloc(config->monitor_rules, (config->monitor_rules_count + 1) *
- sizeof(ConfigMonitorRule));
- if (!config->monitor_rules) {
- fprintf(stderr,
- "Error: Failed to allocate memory for monitor rules\n");
- return;
- }
-
- ConfigMonitorRule *rule =
- &config->monitor_rules[config->monitor_rules_count];
- memset(rule, 0, sizeof(ConfigMonitorRule));
-
- // 临时存储每个字段的原始字符串
- char raw_name[256], raw_layout[256];
- char raw_mfact[256], raw_nmaster[256], raw_rr[256];
- char raw_scale[256], raw_x[256], raw_y[256], raw_width[256],
- raw_height[256], raw_refresh[256];
-
- // 先读取所有字段为字符串
- int32_t parsed =
- sscanf(value,
- "%255[^,],%255[^,],%255[^,],%255[^,],%255[^,],%255["
- "^,],%255[^,],%255[^,],%255[^,],%255[^,],%255s",
- raw_name, raw_mfact, raw_nmaster, raw_layout, raw_rr,
- raw_scale, raw_x, raw_y, raw_width, raw_height, raw_refresh);
-
- if (parsed == 11) {
- // 修剪每个字段的空格
- trim_whitespace(raw_name);
- trim_whitespace(raw_mfact);
- trim_whitespace(raw_nmaster);
- trim_whitespace(raw_layout);
- trim_whitespace(raw_rr);
- trim_whitespace(raw_scale);
- trim_whitespace(raw_x);
- trim_whitespace(raw_y);
- trim_whitespace(raw_width);
- trim_whitespace(raw_height);
- trim_whitespace(raw_refresh);
-
- // 转换修剪后的字符串为特定类型
- rule->name = strdup(raw_name);
- rule->layout = strdup(raw_layout);
- rule->mfact = atof(raw_mfact);
- rule->nmaster = atoi(raw_nmaster);
- rule->rr = atoi(raw_rr);
- rule->scale = atof(raw_scale);
- rule->x = atoi(raw_x);
- rule->y = atoi(raw_y);
- rule->width = atoi(raw_width);
- rule->height = atoi(raw_height);
- rule->refresh = atof(raw_refresh);
-
- if (!rule->name || !rule->layout) {
- if (rule->name)
- free((void *)rule->name);
- if (rule->layout)
- free((void *)rule->layout);
- fprintf(stderr,
- "Error: Failed to allocate memory for monitor rule\n");
- return;
- }
-
- config->monitor_rules_count++;
- } else {
- fprintf(stderr, "Error: Invalid monitorrule format: %s\n", value);
- }
} else if (strncmp(key, "env", 3) == 0) {
char env_type[256], env_value[256];
@@ -2483,18 +2482,6 @@ void free_config(void) {
config.window_rules_count = 0;
}
- // 释放 monitor_rules
- if (config.monitor_rules) {
- for (int32_t i = 0; i < config.monitor_rules_count; i++) {
- ConfigMonitorRule *rule = &config.monitor_rules[i];
- free((void *)rule->name);
- free((void *)rule->layout);
- }
- free(config.monitor_rules);
- config.monitor_rules = NULL;
- config.monitor_rules_count = 0;
- }
-
// 释放 key_bindings
if (config.key_bindings) {
for (i = 0; i < config.key_bindings_count; i++) {
@@ -2613,6 +2600,17 @@ void free_config(void) {
config.tag_rules_count = 0;
}
+ // 释放 monitor_rules
+ if (config.monitor_rules) {
+ for (int32_t i = 0; i < config.monitor_rules_count; i++) {
+ if (config.monitor_rules[i].name)
+ free((void *)config.monitor_rules[i].name);
+ }
+ free(config.monitor_rules);
+ config.monitor_rules = NULL;
+ config.monitor_rules_count = 0;
+ }
+
// 释放 layer_rules
if (config.layer_rules) {
for (int32_t i = 0; i < config.layer_rules_count; i++) {
@@ -3164,7 +3162,8 @@ void reset_blur_params(void) {
void reapply_monitor_rules(void) {
ConfigMonitorRule *mr;
Monitor *m = NULL;
- int32_t ji, jk;
+ int32_t ji, vrr;
+ int32_t mx, my;
struct wlr_output_state state;
struct wlr_output_mode *internal_mode = NULL;
wlr_output_state_init(&state);
@@ -3179,20 +3178,11 @@ void reapply_monitor_rules(void) {
break;
mr = &config.monitor_rules[ji];
- if (!mr->name || regex_match(mr->name, m->wlr_output->name)) {
+ if (regex_match(mr->name, m->wlr_output->name)) {
- m->mfact = mr->mfact;
- m->nmaster = mr->nmaster;
- m->m.x = mr->x;
- m->m.y = mr->y;
-
- if (mr->layout) {
- for (jk = 0; jk < LENGTH(layouts); jk++) {
- if (strcmp(layouts[jk].name, mr->layout) == 0) {
- m->lt = &layouts[jk];
- }
- }
- }
+ mx = mr->x == INT32_MAX ? m->m.x : mr->x;
+ my = mr->y == INT32_MAX ? m->m.y : mr->y;
+ vrr = mr->vrr >= 0 ? mr->vrr : 0;
if (mr->width > 0 && mr->height > 0 && mr->refresh > 0) {
internal_mode = get_nearest_output_mode(
@@ -3206,17 +3196,16 @@ void reapply_monitor_rules(void) {
}
}
+ if (vrr) {
+ enable_adaptive_sync(m, &state);
+ }
+
wlr_output_state_set_scale(&state, mr->scale);
wlr_output_state_set_transform(&state, mr->rr);
- wlr_output_layout_add(output_layout, m->wlr_output, mr->x,
- mr->y);
+ wlr_output_layout_add(output_layout, m->wlr_output, mx, my);
}
}
- if (adaptive_sync) {
- enable_adaptive_sync(m, &state);
- }
-
wlr_output_commit_state(m->wlr_output, &state);
wlr_output_state_finish(&state);
updatemons(NULL, NULL);
@@ -3319,6 +3308,12 @@ void reapply_master(void) {
void parse_tagrule(Monitor *m) {
int32_t i, jk;
ConfigTagRule tr;
+ Client *c = NULL;
+
+ for (i = 0; i <= LENGTH(tags); i++) {
+ m->pertag->nmasters[i] = default_nmaster;
+ m->pertag->mfacts[i] = default_mfact;
+ }
for (i = 0; i < config.tag_rules_count; i++) {
@@ -3335,7 +3330,23 @@ void parse_tagrule(Monitor *m) {
}
}
- m->pertag->no_hide[tr.id] = tr.no_hide;
+ if (tr.no_hide >= 0)
+ m->pertag->no_hide[tr.id] = tr.no_hide;
+ if (tr.nmaster >= 1)
+ m->pertag->nmasters[tr.id] = tr.nmaster;
+ if (tr.mfact > 0.0f)
+ m->pertag->mfacts[tr.id] = tr.mfact;
+ if (tr.no_render_border >= 0)
+ m->pertag->no_render_border[tr.id] = tr.no_render_border;
+ }
+ }
+
+ for (i = 1; i <= LENGTH(tags); i++) {
+ wl_list_for_each(c, &clients, link) {
+ if ((c->tags & (1 << (i - 1)) & TAGMASK) && ISTILED(c)) {
+ if (m->pertag->mfacts[i] > 0.0f)
+ c->master_mfact_per = m->pertag->mfacts[i];
+ }
}
}
}
diff --git a/src/layout/horizontal.h b/src/layout/horizontal.h
index 87b6033..e1a335d 100644
--- a/src/layout/horizontal.h
+++ b/src/layout/horizontal.h
@@ -117,6 +117,7 @@ void deck(Monitor *m) {
Client *c = NULL;
Client *fc = NULL;
float mfact;
+ uint32_t nmasters = m->pertag->nmasters[m->pertag->curtag];
int32_t cur_gappih = enablegaps ? m->gappih : 0;
int32_t cur_gappoh = enablegaps ? m->gappoh : 0;
@@ -142,8 +143,8 @@ void deck(Monitor *m) {
: m->pertag->mfacts[m->pertag->curtag];
// Calculate master width including outer gaps
- if (n > m->nmaster)
- mw = m->nmaster ? round((m->w.width - 2 * cur_gappoh) * mfact) : 0;
+ if (n > nmasters)
+ mw = nmasters ? round((m->w.width - 2 * cur_gappoh) * mfact) : 0;
else
mw = m->w.width - 2 * cur_gappoh;
@@ -151,7 +152,7 @@ void deck(Monitor *m) {
wl_list_for_each(c, &clients, link) {
if (!VISIBLEON(c, m) || !ISTILED(c))
continue;
- if (i < m->nmaster) {
+ if (i < nmasters) {
c->master_mfact_per = mfact;
// Master area clients
resize(
@@ -160,7 +161,7 @@ void deck(Monitor *m) {
.y = m->w.y + cur_gappov + my,
.width = mw,
.height = (m->w.height - 2 * cur_gappov - my) /
- (MIN(n, m->nmaster) - i)},
+ (MIN(n, nmasters) - i)},
0);
my += c->geom.height;
} else {
diff --git a/src/layout/vertical.h b/src/layout/vertical.h
index 2c2dc66..f7bd442 100644
--- a/src/layout/vertical.h
+++ b/src/layout/vertical.h
@@ -110,6 +110,7 @@ void vertical_deck(Monitor *m) {
Client *c = NULL;
Client *fc = NULL;
float mfact;
+ uint32_t nmasters = m->pertag->nmasters[m->pertag->curtag];
int32_t cur_gappiv = enablegaps ? m->gappiv : 0;
int32_t cur_gappoh = enablegaps ? m->gappoh : 0;
@@ -134,8 +135,8 @@ void vertical_deck(Monitor *m) {
mfact = fc->master_mfact_per > 0.0f ? fc->master_mfact_per
: m->pertag->mfacts[m->pertag->curtag];
- if (n > m->nmaster)
- mh = m->nmaster ? round((m->w.height - 2 * cur_gappov) * mfact) : 0;
+ if (n > nmasters)
+ mh = nmasters ? round((m->w.height - 2 * cur_gappov) * mfact) : 0;
else
mh = m->w.height - 2 * cur_gappov;
@@ -143,13 +144,13 @@ void vertical_deck(Monitor *m) {
wl_list_for_each(c, &clients, link) {
if (!VISIBLEON(c, m) || !ISTILED(c))
continue;
- if (i < m->nmaster) {
+ if (i < nmasters) {
resize(
c,
(struct wlr_box){.x = m->w.x + cur_gappoh + mx,
.y = m->w.y + cur_gappov,
.width = (m->w.width - 2 * cur_gappoh - mx) /
- (MIN(n, m->nmaster) - i),
+ (MIN(n, nmasters) - i),
.height = mh},
0);
mx += c->geom.width;
diff --git a/src/mango.c b/src/mango.c
index e285a89..ed4e12a 100644
--- a/src/mango.c
+++ b/src/mango.c
@@ -500,11 +500,8 @@ struct Monitor {
struct wlr_box m; /* monitor area, layout-relative */
struct wlr_box w; /* window area, layout-relative */
struct wl_list layers[4]; /* LayerSurface::link */
- const Layout *lt;
uint32_t seltags;
uint32_t tagset[2];
- double mfact;
- int32_t nmaster;
struct wl_list dwl_ipc_outputs;
int32_t gappih; /* horizontal gap between windows */
@@ -898,6 +895,7 @@ struct Pertag {
int32_t nmasters[LENGTH(tags) + 1]; /* number of windows in master area */
float mfacts[LENGTH(tags) + 1]; /* mfacts per tag */
bool no_hide[LENGTH(tags) + 1]; /* no_hide per tag */
+ bool no_render_border[LENGTH(tags) + 1]; /* no_render_border per tag */
const Layout
*ltidxs[LENGTH(tags) + 1]; /* matrix of tags and layouts indexes */
};
@@ -2667,7 +2665,7 @@ void createmon(struct wl_listener *listener, void *data) {
struct wlr_output *wlr_output = data;
const ConfigMonitorRule *r;
uint32_t i;
- int32_t ji, jk;
+ int32_t ji, vrr;
struct wlr_output_state state;
Monitor *m = NULL;
struct wlr_output_mode *internal_mode = NULL;
@@ -2703,30 +2701,19 @@ void createmon(struct wl_listener *listener, void *data) {
m->sel = NULL;
m->is_in_hotarea = 0;
float scale = 1;
- m->mfact = default_mfact;
- m->nmaster = default_nmaster;
enum wl_output_transform rr = WL_OUTPUT_TRANSFORM_NORMAL;
wlr_output_state_set_scale(&state, scale);
wlr_output_state_set_transform(&state, rr);
- m->lt = &layouts[0];
for (ji = 0; ji < config.monitor_rules_count; ji++) {
if (config.monitor_rules_count < 1)
break;
r = &config.monitor_rules[ji];
- if (!r->name || regex_match(r->name, wlr_output->name)) {
- m->mfact = r->mfact;
- m->nmaster = r->nmaster;
- m->m.x = r->x;
- m->m.y = r->y;
- if (r->layout) {
- for (jk = 0; jk < LENGTH(layouts); jk++) {
- if (strcmp(layouts[jk].name, r->layout) == 0) {
- m->lt = &layouts[jk];
- }
- }
- }
+ if (regex_match(r->name, wlr_output->name)) {
+ m->m.x = r->x == INT32_MAX ? INT32_MAX : r->x;
+ m->m.y = r->y == INT32_MAX ? INT32_MAX : r->y;
+ vrr = r->vrr >= 0 ? r->vrr : 0;
scale = r->scale;
rr = r->rr;
@@ -2743,6 +2730,11 @@ void createmon(struct wl_listener *listener, void *data) {
(int32_t)roundf(r->refresh * 1000));
}
}
+
+ if (vrr) {
+ enable_adaptive_sync(m, &state);
+ }
+
wlr_output_state_set_scale(&state, r->scale);
wlr_output_state_set_transform(&state, r->rr);
break;
@@ -2757,10 +2749,6 @@ void createmon(struct wl_listener *listener, void *data) {
wlr_output_state_set_mode(&state,
wlr_output_preferred_mode(wlr_output));
- if (adaptive_sync) {
- enable_adaptive_sync(m, &state);
- }
-
/* Set up event listeners */
LISTEN(&wlr_output->events.frame, &m->frame, rendermon);
LISTEN(&wlr_output->events.destroy, &m->destroy, cleanupmon);
@@ -2785,9 +2773,9 @@ void createmon(struct wl_listener *listener, void *data) {
}
for (i = 0; i <= LENGTH(tags); i++) {
- m->pertag->nmasters[i] = m->nmaster;
- m->pertag->mfacts[i] = m->mfact;
- m->pertag->ltidxs[i] = m->lt;
+ m->pertag->nmasters[i] = default_nmaster;
+ m->pertag->mfacts[i] = default_mfact;
+ m->pertag->ltidxs[i] = &layouts[0];
}
// apply tag rule
@@ -2809,7 +2797,7 @@ void createmon(struct wl_listener *listener, void *data) {
* output (such as DPI, scale factor, manufacturer, etc).
*/
m->scene_output = wlr_scene_output_create(scene, wlr_output);
- if (m->m.x == -1 && m->m.y == -1)
+ if (m->m.x == INT32_MAX || m->m.y == INT32_MAX)
wlr_output_layout_add_auto(output_layout, wlr_output);
else
wlr_output_layout_add(output_layout, wlr_output, m->m.x, m->m.y);
From 2e6e23633e90c52fcf5fe3af1910326918f7baf0 Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Tue, 27 Jan 2026 16:20:45 +0800
Subject: [PATCH 28/47] break change: remove useless option adaptive_sync
---
src/config/parse_config.h | 5 -----
src/config/preset.h | 1 -
2 files changed, 6 deletions(-)
diff --git a/src/config/parse_config.h b/src/config/parse_config.h
index f94e679..81eda9d 100644
--- a/src/config/parse_config.h
+++ b/src/config/parse_config.h
@@ -346,7 +346,6 @@ typedef struct {
int32_t single_scratchpad;
int32_t xwayland_persistence;
int32_t syncobj_enable;
- int32_t adaptive_sync;
int32_t allow_tearing;
int32_t allow_shortcuts_inhibit;
int32_t allow_lock_transparent;
@@ -1278,8 +1277,6 @@ void parse_option(Config *config, char *key, char *value) {
config->xwayland_persistence = atoi(value);
} else if (strcmp(key, "syncobj_enable") == 0) {
config->syncobj_enable = atoi(value);
- } else if (strcmp(key, "adaptive_sync") == 0) {
- config->adaptive_sync = atoi(value);
} else if (strcmp(key, "allow_tearing") == 0) {
config->allow_tearing = atoi(value);
} else if (strcmp(key, "allow_shortcuts_inhibit") == 0) {
@@ -2750,7 +2747,6 @@ void override_config(void) {
// 杂项设置
xwayland_persistence = CLAMP_INT(config.xwayland_persistence, 0, 1);
syncobj_enable = CLAMP_INT(config.syncobj_enable, 0, 1);
- adaptive_sync = CLAMP_INT(config.adaptive_sync, 0, 1);
allow_tearing = CLAMP_INT(config.allow_tearing, 0, 2);
allow_shortcuts_inhibit = CLAMP_INT(config.allow_shortcuts_inhibit, 0, 1);
allow_lock_transparent = CLAMP_INT(config.allow_lock_transparent, 0, 1);
@@ -2937,7 +2933,6 @@ void set_value_default() {
config.single_scratchpad = single_scratchpad;
config.xwayland_persistence = xwayland_persistence;
config.syncobj_enable = syncobj_enable;
- config.adaptive_sync = adaptive_sync;
config.allow_tearing = allow_tearing;
config.allow_shortcuts_inhibit = allow_shortcuts_inhibit;
config.allow_lock_transparent = allow_lock_transparent;
diff --git a/src/config/preset.h b/src/config/preset.h
index 5dea6cb..c8474dd 100644
--- a/src/config/preset.h
+++ b/src/config/preset.h
@@ -106,7 +106,6 @@ int32_t drag_corner = 3;
int32_t drag_warp_cursor = 1;
int32_t xwayland_persistence = 1; /* xwayland persistence */
int32_t syncobj_enable = 0;
-int32_t adaptive_sync = 0;
int32_t allow_lock_transparent = 0;
double drag_refresh_interval = 16.0;
int32_t allow_tearing = TEARING_DISABLED;
From 30692f6da035b69a17ef59c48b1f6a4e1cb4c4ba Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Tue, 27 Jan 2026 19:02:16 +0800
Subject: [PATCH 29/47] feat: add option hotarea_corner
---
src/config/parse_config.h | 9 ++++--
src/config/preset.h | 5 ++--
src/mango.c | 58 ++++++++++++++++++++++++++++++++++-----
3 files changed, 61 insertions(+), 11 deletions(-)
diff --git a/src/config/parse_config.h b/src/config/parse_config.h
index 81eda9d..c345836 100644
--- a/src/config/parse_config.h
+++ b/src/config/parse_config.h
@@ -232,6 +232,7 @@ typedef struct {
int32_t center_when_single_stack;
uint32_t hotarea_size;
+ uint32_t hotarea_corner;
uint32_t enable_hotarea;
uint32_t ov_tab_mode;
int32_t overviewgappi;
@@ -1452,6 +1453,8 @@ void parse_option(Config *config, char *key, char *value) {
config->center_when_single_stack = atoi(value);
} else if (strcmp(key, "hotarea_size") == 0) {
config->hotarea_size = atoi(value);
+ } else if (strcmp(key, "hotarea_corner") == 0) {
+ config->hotarea_corner = atoi(value);
} else if (strcmp(key, "enable_hotarea") == 0) {
config->enable_hotarea = atoi(value);
} else if (strcmp(key, "ov_tab_mode") == 0) {
@@ -2739,6 +2742,7 @@ void override_config(void) {
// 概述模式设置
hotarea_size = CLAMP_INT(config.hotarea_size, 1, 1000);
+ hotarea_corner = CLAMP_INT(config.hotarea_corner, 0, 3);
enable_hotarea = CLAMP_INT(config.enable_hotarea, 0, 1);
ov_tab_mode = CLAMP_INT(config.ov_tab_mode, 0, 1);
overviewgappi = CLAMP_INT(config.overviewgappi, 0, 1000);
@@ -2901,8 +2905,9 @@ void set_value_default() {
config.numlockon = numlockon; // 是否打开右边小键盘
- config.ov_tab_mode = ov_tab_mode; // alt tab切换模式
- config.hotarea_size = hotarea_size; // 热区大小,10x10
+ config.ov_tab_mode = ov_tab_mode; // alt tab切换模式
+ config.hotarea_size = hotarea_size; // 热区大小,10x10
+ config.hotarea_corner = hotarea_corner;
config.enable_hotarea = enable_hotarea; // 是否启用鼠标热区
config.smartgaps =
smartgaps; /* 1 means no outer gap when there is only one window */
diff --git a/src/config/preset.h b/src/config/preset.h
index c8474dd..3d05161 100644
--- a/src/config/preset.h
+++ b/src/config/preset.h
@@ -47,8 +47,9 @@ int32_t log_level = WLR_ERROR;
uint32_t numlockon = 0; // 是否打开右边小键盘
uint32_t capslock = 0; // 是否启用快捷键
-uint32_t ov_tab_mode = 0; // alt tab切换模式
-uint32_t hotarea_size = 10; // 热区大小,10x10
+uint32_t ov_tab_mode = 0; // alt tab切换模式
+uint32_t hotarea_size = 10; // 热区大小,10x10
+uint32_t hotarea_corner = BOTTOM_LEFT;
uint32_t enable_hotarea = 1; // 是否启用鼠标热区
int32_t smartgaps = 0; /* 1 means no outer gap when there is only one window */
int32_t sloppyfocus = 1; /* focus follows mouse */
diff --git a/src/mango.c b/src/mango.c
index ed4e12a..a32be83 100644
--- a/src/mango.c
+++ b/src/mango.c
@@ -143,6 +143,8 @@
#define BAKED_POINTS_COUNT 256
/* enums */
+enum { TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT };
+
enum { VERTICAL, HORIZONTAL };
enum { SWIPE_UP, SWIPE_DOWN, SWIPE_LEFT, SWIPE_RIGHT };
enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */
@@ -1215,17 +1217,59 @@ void toggle_hotarea(int32_t x_root, int32_t y_root) {
if (grabc)
return;
- unsigned hx = selmon->m.x + hotarea_size;
- unsigned hy = selmon->m.y + selmon->m.height - hotarea_size;
+ // 根据热角位置计算不同的热区坐标
+ unsigned hx, hy;
- if (enable_hotarea == 1 && selmon->is_in_hotarea == 0 && y_root > hy &&
- x_root < hx && x_root >= selmon->m.x &&
- y_root <= (selmon->m.y + selmon->m.height)) {
+ switch (hotarea_corner) {
+ case BOTTOM_RIGHT: // 右下角
+ hx = selmon->m.x + selmon->m.width - hotarea_size;
+ hy = selmon->m.y + selmon->m.height - hotarea_size;
+ break;
+ case TOP_LEFT: // 左上角
+ hx = selmon->m.x + hotarea_size;
+ hy = selmon->m.y + hotarea_size;
+ break;
+ case TOP_RIGHT: // 右上角
+ hx = selmon->m.x + selmon->m.width - hotarea_size;
+ hy = selmon->m.y + hotarea_size;
+ break;
+ case BOTTOM_LEFT: // 左下角(默认)
+ default:
+ hx = selmon->m.x + hotarea_size;
+ hy = selmon->m.y + selmon->m.height - hotarea_size;
+ break;
+ }
+
+ // 判断鼠标是否在热区内
+ int in_hotarea = 0;
+
+ switch (hotarea_corner) {
+ case BOTTOM_RIGHT: // 右下角
+ in_hotarea = (y_root > hy && x_root > hx &&
+ x_root <= (selmon->m.x + selmon->m.width) &&
+ y_root <= (selmon->m.y + selmon->m.height));
+ break;
+ case TOP_LEFT: // 左上角
+ in_hotarea = (y_root < hy && x_root < hx && x_root >= selmon->m.x &&
+ y_root >= selmon->m.y);
+ break;
+ case TOP_RIGHT: // 右上角
+ in_hotarea = (y_root < hy && x_root > hx &&
+ x_root <= (selmon->m.x + selmon->m.width) &&
+ y_root >= selmon->m.y);
+ break;
+ case BOTTOM_LEFT: // 左下角(默认)
+ default:
+ in_hotarea = (y_root > hy && x_root < hx && x_root >= selmon->m.x &&
+ y_root <= (selmon->m.y + selmon->m.height));
+ break;
+ }
+
+ if (enable_hotarea == 1 && selmon->is_in_hotarea == 0 && in_hotarea) {
toggleoverview(&arg);
selmon->is_in_hotarea = 1;
} else if (enable_hotarea == 1 && selmon->is_in_hotarea == 1 &&
- (y_root <= hy || x_root >= hx || x_root < selmon->m.x ||
- y_root > (selmon->m.y + selmon->m.height))) {
+ !in_hotarea) {
selmon->is_in_hotarea = 0;
}
}
From e535aea28bccaad3dd6dde66fa2e80a68f90f844 Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Wed, 28 Jan 2026 14:50:17 +0800
Subject: [PATCH 30/47] fix: avoid redundant frame requests
---
src/animation/client.h | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/animation/client.h b/src/animation/client.h
index 15a0d16..7bc47ac 100644
--- a/src/animation/client.h
+++ b/src/animation/client.h
@@ -1061,9 +1061,9 @@ void client_set_focused_opacity_animation(Client *c) {
c->opacity_animation.initial_opacity =
c->opacity_animation.current_opacity;
} else {
- memcpy(c->opacity_animation.initial_border_color, border_color,
+ memcpy(c->opacity_animation.initial_border_color, bordercolor,
sizeof(c->opacity_animation.initial_border_color));
- memcpy(c->opacity_animation.current_border_color, border_color,
+ memcpy(c->opacity_animation.current_border_color, bordercolor,
sizeof(c->opacity_animation.current_border_color));
c->opacity_animation.initial_opacity = c->unfocused_opacity;
c->opacity_animation.current_opacity = c->unfocused_opacity;
@@ -1167,7 +1167,7 @@ bool client_apply_focus_opacity(Client *c) {
eased_progress;
}
client_set_border_color(c, c->opacity_animation.current_border_color);
- if (linear_progress == 1.0f) {
+ if (linear_progress >= 1.0f) {
c->opacity_animation.running = false;
} else {
return true;
From 4591b69e4d8e212b2b4986abbb1598f968b557d0 Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Thu, 29 Jan 2026 09:34:49 +0800
Subject: [PATCH 31/47] fix: accel_profile can't set to 0
---
src/mango.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/mango.c b/src/mango.c
index a32be83..0360d4f 100644
--- a/src/mango.c
+++ b/src/mango.c
@@ -2958,9 +2958,14 @@ void configure_pointer(struct libinput_device *device) {
if (libinput_device_config_send_events_get_modes(device))
libinput_device_config_send_events_set_mode(device, send_events_mode);
- if (libinput_device_config_accel_is_available(device)) {
+ if (accel_profile && libinput_device_config_accel_is_available(device)) {
libinput_device_config_accel_set_profile(device, accel_profile);
libinput_device_config_accel_set_speed(device, accel_speed);
+ } else {
+ // profile cannot be directly applied to 0, need to set to 1 first
+ libinput_device_config_accel_set_profile(device, 1);
+ libinput_device_config_accel_set_profile(device, 0);
+ libinput_device_config_accel_set_speed(device, 0);
}
}
From d9f679a8e371991785e5a8a18f6f5cac11fcd601 Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Thu, 29 Jan 2026 09:42:54 +0800
Subject: [PATCH 32/47] fix: tagcrossmon not apply in current monitor
---
src/dispatch/bind_define.h | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h
index f27ed67..c0e51dc 100644
--- a/src/dispatch/bind_define.h
+++ b/src/dispatch/bind_define.h
@@ -1444,9 +1444,14 @@ int32_t viewcrossmon(const Arg *arg) {
}
int32_t tagcrossmon(const Arg *arg) {
- if (!selmon->sel)
+ if (!selmon || !selmon->sel)
return 0;
+ if (regex_match(selmon->wlr_output->name, arg->v)) {
+ tag_client(arg, selmon->sel);
+ return 0;
+ }
+
tagmon(&(Arg){.ui = arg->ui, .i = UNDIR, .v = arg->v});
return 0;
}
From 672706c71f4775dba304af1700451e0eadf8cea1 Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Thu, 29 Jan 2026 11:58:28 +0800
Subject: [PATCH 33/47] fix: not apply vrr disalbe when enable it at the
beginning
---
src/config/parse_config.h | 2 ++
src/mango.c | 2 ++
2 files changed, 4 insertions(+)
diff --git a/src/config/parse_config.h b/src/config/parse_config.h
index c345836..baef79d 100644
--- a/src/config/parse_config.h
+++ b/src/config/parse_config.h
@@ -3198,6 +3198,8 @@ void reapply_monitor_rules(void) {
if (vrr) {
enable_adaptive_sync(m, &state);
+ } else {
+ wlr_output_state_set_adaptive_sync_enabled(&state, false);
}
wlr_output_state_set_scale(&state, mr->scale);
diff --git a/src/mango.c b/src/mango.c
index 0360d4f..86ea8a9 100644
--- a/src/mango.c
+++ b/src/mango.c
@@ -2777,6 +2777,8 @@ void createmon(struct wl_listener *listener, void *data) {
if (vrr) {
enable_adaptive_sync(m, &state);
+ } else {
+ wlr_output_state_set_adaptive_sync_enabled(&state, false);
}
wlr_output_state_set_scale(&state, r->scale);
From aa5241fb7b0d5059fdceb3688309cf07fb94fbf2 Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Thu, 29 Jan 2026 21:59:39 +0800
Subject: [PATCH 34/47] fix: allow layer surface attach null buffer
---
src/mango.c | 26 +++++++++++++++++++++-----
1 file changed, 21 insertions(+), 5 deletions(-)
diff --git a/src/mango.c b/src/mango.c
index 86ea8a9..ae49a73 100644
--- a/src/mango.c
+++ b/src/mango.c
@@ -480,6 +480,7 @@ typedef struct {
char *animation_type_open;
char *animation_type_close;
bool need_output_flush;
+ bool being_unmapped;
} LayerSurface;
typedef struct {
@@ -592,7 +593,7 @@ static void cursorwarptohint(void);
static void destroydecoration(struct wl_listener *listener, void *data);
static void destroydragicon(struct wl_listener *listener, void *data);
static void destroyidleinhibitor(struct wl_listener *listener, void *data);
-static void destroylayersurfacenotify(struct wl_listener *listener, void *data);
+static void destroylayernodenotify(struct wl_listener *listener, void *data);
static void destroylock(SessionLock *lock, int32_t unlocked);
static void destroylocksurface(struct wl_listener *listener, void *data);
static void destroynotify(struct wl_listener *listener, void *data);
@@ -1521,6 +1522,9 @@ void arrangelayer(Monitor *m, struct wl_list *list, struct wlr_box *usable_area,
!layer_surface->initialized)
continue;
+ if (l->being_unmapped)
+ continue;
+
wlr_scene_layer_surface_v1_configure(l->scene_layer, &full_area,
usable_area);
wlr_scene_node_set_position(&l->popups->node, l->scene->node.x,
@@ -2337,6 +2341,15 @@ void commitlayersurfacenotify(struct wl_listener *listener, void *data) {
return;
}
+ // 检查surface是否有buffer
+ if (!layer_surface->surface->buffer) {
+ // 空buffer,只是隐藏,不改变mapped状态
+ wlr_scene_node_set_enabled(&l->scene->node, false);
+ return;
+ } else {
+ wlr_scene_node_set_enabled(&l->scene->node, true);
+ }
+
get_layer_target_geometry(l, &box);
if (animations && layer_animations && !l->noanim && l->mapped &&
@@ -2633,8 +2646,6 @@ void createlayersurface(struct wl_listener *listener, void *data) {
LISTEN(&surface->events.commit, &l->surface_commit,
commitlayersurfacenotify);
LISTEN(&surface->events.unmap, &l->unmap, unmaplayersurfacenotify);
- LISTEN(&layer_surface->events.destroy, &l->destroy,
- destroylayersurfacenotify);
l->layer_surface = layer_surface;
l->mon = layer_surface->output->data;
@@ -2647,6 +2658,8 @@ void createlayersurface(struct wl_listener *listener, void *data) {
: scene_layer);
l->scene->node.data = l->popups->node.data = l;
+ LISTEN(&l->scene->node.events.destroy, &l->destroy, destroylayernodenotify);
+
wl_list_insert(&l->mon->layers[layer_surface->pending.layer], &l->link);
wlr_surface_send_enter(surface, layer_surface->output);
}
@@ -3112,7 +3125,7 @@ void destroyidleinhibitor(struct wl_listener *listener, void *data) {
free(listener);
}
-void destroylayersurfacenotify(struct wl_listener *listener, void *data) {
+void destroylayernodenotify(struct wl_listener *listener, void *data) {
LayerSurface *l = wl_container_of(listener, l, destroy);
wl_list_remove(&l->link);
@@ -3120,7 +3133,6 @@ void destroylayersurfacenotify(struct wl_listener *listener, void *data) {
wl_list_remove(&l->map.link);
wl_list_remove(&l->unmap.link);
wl_list_remove(&l->surface_commit.link);
- wlr_scene_node_destroy(&l->scene->node);
wlr_scene_node_destroy(&l->popups->node);
free(l);
}
@@ -5554,6 +5566,7 @@ void unmaplayersurfacenotify(struct wl_listener *listener, void *data) {
LayerSurface *l = wl_container_of(listener, l, unmap);
l->mapped = 0;
+ l->being_unmapped = true;
init_fadeout_layers(l);
@@ -5565,6 +5578,9 @@ void unmaplayersurfacenotify(struct wl_listener *listener, void *data) {
if (l->layer_surface->surface == seat->keyboard_state.focused_surface)
focusclient(focustop(selmon), 1);
motionnotify(0, NULL, 0, 0, 0, 0);
+ l->being_unmapped = false;
+ wlr_scene_node_destroy(&l->shadow->node);
+ l->shadow = NULL;
}
void unmapnotify(struct wl_listener *listener, void *data) {
From ca09aa69db29adf7e9cbaab74885724b4d469c66 Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Fri, 30 Jan 2026 08:47:12 +0800
Subject: [PATCH 35/47] opt: not hide null buffer layer if is not mapped
---
src/mango.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/mango.c b/src/mango.c
index ae49a73..1d332c4 100644
--- a/src/mango.c
+++ b/src/mango.c
@@ -2342,8 +2342,8 @@ void commitlayersurfacenotify(struct wl_listener *listener, void *data) {
}
// 检查surface是否有buffer
- if (!layer_surface->surface->buffer) {
- // 空buffer,只是隐藏,不改变mapped状态
+ // 空buffer,只是隐藏,不改变mapped状态
+ if (l->mapped && !layer_surface->surface->buffer) {
wlr_scene_node_set_enabled(&l->scene->node, false);
return;
} else {
From 5172444e08b2c2be24bef0b5ccff2d2367332118 Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Sun, 1 Feb 2026 15:33:05 +0800
Subject: [PATCH 36/47] bump version to 0.12.0
---
meson.build | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/meson.build b/meson.build
index d71eb59..0565609 100644
--- a/meson.build
+++ b/meson.build
@@ -1,5 +1,5 @@
project('mango', ['c', 'cpp'],
- version : '0.11.0',
+ version : '0.12.0',
)
subdir('protocols')
From bcee63fa76245c900800c21eac0e999b45b86251 Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Sun, 1 Feb 2026 19:21:01 +0800
Subject: [PATCH 37/47] feat: add config check in mango cli
---
src/config/parse_config.h | 441 ++++++++++++++++++++++++++++----------
src/mango.c | 30 ++-
2 files changed, 354 insertions(+), 117 deletions(-)
diff --git a/src/config/parse_config.h b/src/config/parse_config.h
index baef79d..f7ce848 100644
--- a/src/config/parse_config.h
+++ b/src/config/parse_config.h
@@ -405,7 +405,10 @@ int32_t parse_double_array(const char *input, double *output,
char *endptr;
double val = strtod(token, &endptr);
if (endptr == token || *endptr != '\0') {
- fprintf(stderr, "Error: Invalid number in array: %s\n", token);
+ fprintf(
+ stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Invalid number in array: %s\n",
+ token);
free(dup);
return -1;
}
@@ -457,7 +460,9 @@ void parse_bind_flags(const char *str, KeyBinding *kb) {
kb->ispassapply = true;
break;
default:
- // 忽略其他字符或可根据需要处理错误
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Unknown bind flag: %c\n",
+ suffix[i]);
break;
}
}
@@ -547,13 +552,14 @@ static bool starts_with_ignore_case(const char *str, const char *prefix) {
uint32_t parse_mod(const char *mod_str) {
if (!mod_str || !*mod_str) {
- return 0;
+ return UINT32_MAX;
}
uint32_t mod = 0;
char input_copy[256];
char *token;
char *saveptr = NULL;
+ bool match_success = false;
// 复制并转换为小写
strncpy(input_copy, mod_str, sizeof(input_copy) - 1);
@@ -591,35 +597,56 @@ uint32_t parse_mod(const char *mod_str) {
case 108:
mod |= WLR_MODIFIER_ALT;
break;
+ default:
+ fprintf(stderr,
+ "unknown modifier keycode: \033[1m\033[31m%s\n",
+ token);
+ break;
}
}
} else {
// 完整的 modifier 检查(保留原始所有检查项)
- if (strstr(token, "super") || strstr(token, "super_l") ||
- strstr(token, "super_r")) {
+ if (!strcmp(token, "super") || !strcmp(token, "super_l") ||
+ !strcmp(token, "super_r")) {
mod |= WLR_MODIFIER_LOGO;
+ match_success = true;
}
- if (strstr(token, "ctrl") || strstr(token, "ctrl_l") ||
- strstr(token, "ctrl_r")) {
+ if (!strcmp(token, "ctrl") || !strcmp(token, "ctrl_l") ||
+ !strcmp(token, "ctrl_r")) {
mod |= WLR_MODIFIER_CTRL;
+ match_success = true;
}
- if (strstr(token, "shift") || strstr(token, "shift_l") ||
- strstr(token, "shift_r")) {
+ if (!strcmp(token, "shift") || !strcmp(token, "shift_l") ||
+ !strcmp(token, "shift_r")) {
mod |= WLR_MODIFIER_SHIFT;
+ match_success = true;
}
- if (strstr(token, "alt") || strstr(token, "alt_l") ||
- strstr(token, "alt_r")) {
+ if (!strcmp(token, "alt") || !strcmp(token, "alt_l") ||
+ !strcmp(token, "alt_r")) {
mod |= WLR_MODIFIER_ALT;
+ match_success = true;
}
- if (strstr(token, "hyper") || strstr(token, "hyper_l") ||
- strstr(token, "hyper_r")) {
+ if (!strcmp(token, "hyper") || !strcmp(token, "hyper_l") ||
+ !strcmp(token, "hyper_r")) {
mod |= WLR_MODIFIER_MOD3;
+ match_success = true;
+ }
+ if (!strcmp(token, "none")) {
+ match_success = true;
}
}
token = strtok_r(NULL, "+", &saveptr);
}
+ if (!match_success) {
+ mod = UINT32_MAX;
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Unknown modifier: "
+ "\033[1m\033[31m%s\n",
+ mod_str);
+ }
+
return mod;
}
@@ -735,13 +762,17 @@ KeySymCode parse_key(const char *key_str, bool isbindsym) {
// 无法解析的键名
kc.type = KEY_TYPE_SYM;
kc.keysym = XKB_KEY_NoSymbol;
+ fprintf(
+ stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Unknown key: \033[1m\033[31m%s\n",
+ key_str);
// keycode 字段保持为0
}
return kc;
}
-int32_t parse_button(const char *str) {
+uint32_t parse_button(const char *str) {
// 将输入字符串转换为小写
char lowerStr[20];
int32_t i = 0;
@@ -769,7 +800,11 @@ int32_t parse_button(const char *str) {
} else if (strcmp(lowerStr, "btn_task") == 0) {
return BTN_TASK;
} else {
- return 0;
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Unknown button: "
+ "\033[1m\033[31m%s\n",
+ str);
+ return UINT32_MAX;
}
}
@@ -1115,7 +1150,7 @@ void run_exec_once() {
}
}
-void parse_option(Config *config, char *key, char *value) {
+bool parse_option(Config *config, char *key, char *value) {
if (strcmp(key, "keymode") == 0) {
snprintf(config->keymode, sizeof(config->keymode), "%.27s", value);
} else if (strcmp(key, "animations") == 0) {
@@ -1166,53 +1201,70 @@ void parse_option(Config *config, char *key, char *value) {
int32_t num =
parse_double_array(value, config->animation_curve_move, 4);
if (num != 4) {
- fprintf(stderr, "Error: Failed to parse animation_curve_move: %s\n",
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Failed to parse "
+ "animation_curve_move: %s\n",
value);
+ return false;
}
} else if (strcmp(key, "animation_curve_open") == 0) {
int32_t num =
parse_double_array(value, config->animation_curve_open, 4);
if (num != 4) {
- fprintf(stderr, "Error: Failed to parse animation_curve_open: %s\n",
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Failed to parse "
+ "animation_curve_open: %s\n",
value);
+ return false;
}
} else if (strcmp(key, "animation_curve_tag") == 0) {
int32_t num = parse_double_array(value, config->animation_curve_tag, 4);
if (num != 4) {
- fprintf(stderr, "Error: Failed to parse animation_curve_tag: %s\n",
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Failed to parse "
+ "animation_curve_tag: %s\n",
value);
+ return false;
}
} else if (strcmp(key, "animation_curve_close") == 0) {
int32_t num =
parse_double_array(value, config->animation_curve_close, 4);
if (num != 4) {
fprintf(stderr,
- "Error: Failed to parse animation_curve_close: %s\n",
+ "\033[1m\033[31m[ERROR]:\033[33m Failed to parse "
+ "animation_curve_close: %s\n",
value);
+ return false;
}
} else if (strcmp(key, "animation_curve_focus") == 0) {
int32_t num =
parse_double_array(value, config->animation_curve_focus, 4);
if (num != 4) {
fprintf(stderr,
- "Error: Failed to parse animation_curve_focus: %s\n",
+ "\033[1m\033[31m[ERROR]:\033[33m Failed to parse "
+ "animation_curve_focus: %s\n",
value);
+ return false;
}
} else if (strcmp(key, "animation_curve_opafadein") == 0) {
int32_t num =
parse_double_array(value, config->animation_curve_opafadein, 4);
if (num != 4) {
fprintf(stderr,
- "Error: Failed to parse animation_curve_opafadein: %s\n",
+ "\033[1m\033[31m[ERROR]:\033[33m Failed to parse "
+ "animation_curve_opafadein: %s\n",
value);
+ return false;
}
} else if (strcmp(key, "animation_curve_opafadeout") == 0) {
int32_t num =
parse_double_array(value, config->animation_curve_opafadeout, 4);
if (num != 4) {
fprintf(stderr,
- "Error: Failed to parse animation_curve_opafadeout: %s\n",
+ "\033[1m\033[31m[ERROR]:\033[33m Failed to parse "
+ "animation_curve_opafadeout: %s\n",
value);
+ return false;
}
} else if (strcmp(key, "scroller_structs") == 0) {
config->scroller_structs = atoi(value);
@@ -1333,8 +1385,10 @@ void parse_option(Config *config, char *key, char *value) {
config->scroller_proportion_preset =
(float *)malloc(float_count * sizeof(float));
if (!config->scroller_proportion_preset) {
- fprintf(stderr, "Error: Memory allocation failed\n");
- return;
+ fprintf(
+ stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Memory allocation failed\n");
+ return false;
}
// 3. 解析 value 中的浮点数
@@ -1346,14 +1400,15 @@ void parse_option(Config *config, char *key, char *value) {
while (token != NULL && i < float_count) {
if (sscanf(token, "%f", &value_set) != 1) {
- fprintf(stderr,
- "Error: Invalid float value in "
- "scroller_proportion_preset: %s\n",
- token);
+ fprintf(
+ stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Invalid float value in "
+ "scroller_proportion_preset: %s\n",
+ token);
free(value_copy);
free(config->scroller_proportion_preset);
config->scroller_proportion_preset = NULL;
- return;
+ return false;
}
// Clamp the value between 0.0 and 1.0 (or your desired range)
@@ -1367,13 +1422,14 @@ void parse_option(Config *config, char *key, char *value) {
// 4. 检查解析的浮点数数量是否匹配
if (i != float_count) {
fprintf(stderr,
- "Error: Invalid scroller_proportion_preset format: %s\n",
+ "\033[1m\033[31m[ERROR]:\033[33m Invalid "
+ "scroller_proportion_preset format: %s\n",
value);
free(value_copy);
free(config->scroller_proportion_preset); // 释放已分配的内存
config->scroller_proportion_preset = NULL; // 防止野指针
config->scroller_proportion_preset_count = 0;
- return;
+ return false;
}
config->scroller_proportion_preset_count = float_count;
@@ -1392,8 +1448,10 @@ void parse_option(Config *config, char *key, char *value) {
config->circle_layout = (char **)malloc(string_count * sizeof(char *));
memset(config->circle_layout, 0, string_count * sizeof(char *));
if (!config->circle_layout) {
- fprintf(stderr, "Error: Memory allocation failed\n");
- return;
+ fprintf(
+ stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Memory allocation failed\n");
+ return false;
}
// 3. 解析 value 中的字符串
@@ -1408,7 +1466,9 @@ void parse_option(Config *config, char *key, char *value) {
config->circle_layout[i] = strdup(cleaned_token);
if (!config->circle_layout[i]) {
fprintf(stderr,
- "Error: Memory allocation failed for string: %s\n",
+ "\033[1m\033[31m[ERROR]:\033[33m Memory allocation "
+ "failed for "
+ "string: %s\n",
token);
// 释放之前分配的内存
for (int32_t j = 0; j < i; j++) {
@@ -1418,7 +1478,7 @@ void parse_option(Config *config, char *key, char *value) {
free(value_copy);
config->circle_layout = NULL; // 防止野指针
config->circle_layout_count = 0;
- return;
+ return false;
}
token = strtok(NULL, ",");
i++;
@@ -1426,7 +1486,10 @@ void parse_option(Config *config, char *key, char *value) {
// 4. 检查解析的字符串数量是否匹配
if (i != string_count) {
- fprintf(stderr, "Error: Invalid circle_layout format: %s\n", value);
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Invalid circle_layout "
+ "format: %s\n",
+ value);
// 释放之前分配的内存
for (int32_t j = 0; j < i; j++) {
free(config->circle_layout[j]);
@@ -1435,7 +1498,7 @@ void parse_option(Config *config, char *key, char *value) {
free(value_copy);
config->circle_layout = NULL; // 防止野指针
config->circle_layout_count = 0;
- return;
+ return false;
}
config->circle_layout_count = string_count;
@@ -1542,7 +1605,11 @@ void parse_option(Config *config, char *key, char *value) {
} else if (strcmp(key, "rootcolor") == 0) {
int64_t color = parse_color(value);
if (color == -1) {
- fprintf(stderr, "Error: Invalid rootcolor format: %s\n", value);
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Invalid rootcolor format: "
+ "%s\n",
+ value);
+ return false;
} else {
convert_hex_to_rgba(config->rootcolor, color);
}
@@ -1550,58 +1617,89 @@ void parse_option(Config *config, char *key, char *value) {
} else if (strcmp(key, "shadowscolor") == 0) {
int64_t color = parse_color(value);
if (color == -1) {
- fprintf(stderr, "Error: Invalid shadowscolor format: %s\n", value);
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Invalid shadowscolor "
+ "format: %s\n",
+ value);
+ return false;
} else {
convert_hex_to_rgba(config->shadowscolor, color);
}
} else if (strcmp(key, "bordercolor") == 0) {
int64_t color = parse_color(value);
if (color == -1) {
- fprintf(stderr, "Error: Invalid bordercolor format: %s\n", value);
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Invalid bordercolor "
+ "format: %s\n",
+ value);
+ return false;
} else {
convert_hex_to_rgba(config->bordercolor, color);
}
} else if (strcmp(key, "focuscolor") == 0) {
int64_t color = parse_color(value);
if (color == -1) {
- fprintf(stderr, "Error: Invalid focuscolor format: %s\n", value);
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Invalid focuscolor "
+ "format: %s\n",
+ value);
+ return false;
} else {
convert_hex_to_rgba(config->focuscolor, color);
}
} else if (strcmp(key, "maximizescreencolor") == 0) {
int64_t color = parse_color(value);
if (color == -1) {
- fprintf(stderr, "Error: Invalid maximizescreencolor format: %s\n",
- value);
+ fprintf(
+ stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Invalid maximizescreencolor "
+ "format: %s\n",
+ value);
+ return false;
} else {
convert_hex_to_rgba(config->maximizescreencolor, color);
}
} else if (strcmp(key, "urgentcolor") == 0) {
int64_t color = parse_color(value);
if (color == -1) {
- fprintf(stderr, "Error: Invalid urgentcolor format: %s\n", value);
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Invalid urgentcolor "
+ "format: %s\n",
+ value);
+ return false;
} else {
convert_hex_to_rgba(config->urgentcolor, color);
}
} else if (strcmp(key, "scratchpadcolor") == 0) {
int64_t color = parse_color(value);
if (color == -1) {
- fprintf(stderr, "Error: Invalid scratchpadcolor format: %s\n",
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Invalid scratchpadcolor "
+ "format: %s\n",
value);
+ return false;
} else {
convert_hex_to_rgba(config->scratchpadcolor, color);
}
} else if (strcmp(key, "globalcolor") == 0) {
int64_t color = parse_color(value);
if (color == -1) {
- fprintf(stderr, "Error: Invalid globalcolor format: %s\n", value);
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Invalid globalcolor "
+ "format: %s\n",
+ value);
+ return false;
} else {
convert_hex_to_rgba(config->globalcolor, color);
}
} else if (strcmp(key, "overlaycolor") == 0) {
int64_t color = parse_color(value);
if (color == -1) {
- fprintf(stderr, "Error: Invalid overlaycolor format: %s\n", value);
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Invalid overlaycolor "
+ "format: %s\n",
+ value);
+ return false;
} else {
convert_hex_to_rgba(config->overlaycolor, color);
}
@@ -1611,8 +1709,9 @@ void parse_option(Config *config, char *key, char *value) {
sizeof(ConfigMonitorRule));
if (!config->monitor_rules) {
fprintf(stderr,
- "Error: Failed to allocate memory for monitor rules\n");
- return;
+ "\033[1m\033[31m[ERROR]:\033[33m Failed to allocate "
+ "memory for monitor rules\n");
+ return false;
}
ConfigMonitorRule *rule =
@@ -1630,6 +1729,7 @@ void parse_option(Config *config, char *key, char *value) {
rule->refresh = 0.0f;
rule->vrr = 0;
+ bool parse_error = false;
char *token = strtok(value, ",");
while (token != NULL) {
char *colon = strchr(token, ':');
@@ -1659,19 +1759,29 @@ void parse_option(Config *config, char *key, char *value) {
rule->refresh = CLAMP_FLOAT(atof(val), 0.001f, 1000.0f);
} else if (strcmp(key, "vrr") == 0) {
rule->vrr = CLAMP_INT(atoi(val), 0, 1);
+ } else {
+ fprintf(
+ stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Unknown monitor rule "
+ "option:\033[1m\033[31m %s\n",
+ key);
+ parse_error = true;
}
}
token = strtok(NULL, ",");
}
config->monitor_rules_count++;
+ return !parse_error;
} else if (strcmp(key, "tagrule") == 0) {
config->tag_rules =
realloc(config->tag_rules,
(config->tag_rules_count + 1) * sizeof(ConfigTagRule));
if (!config->tag_rules) {
- fprintf(stderr, "Error: Failed to allocate memory for tag rules\n");
- return;
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Failed to allocate "
+ "memory for tag rules\n");
+ return false;
}
ConfigTagRule *rule = &config->tag_rules[config->tag_rules_count];
@@ -1686,6 +1796,7 @@ void parse_option(Config *config, char *key, char *value) {
rule->no_render_border = 0;
rule->no_hide = 0;
+ bool parse_error = false;
char *token = strtok(value, ",");
while (token != NULL) {
char *colon = strchr(token, ':');
@@ -1711,20 +1822,28 @@ void parse_option(Config *config, char *key, char *value) {
rule->nmaster = CLAMP_INT(atoi(val), 1, 99);
} else if (strcmp(key, "mfact") == 0) {
rule->mfact = CLAMP_FLOAT(atof(val), 0.1f, 0.9f);
+ } else {
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Unknown tag rule "
+ "option:\033[1m\033[31m %s\n",
+ key);
+ parse_error = true;
}
}
token = strtok(NULL, ",");
}
config->tag_rules_count++;
+ return !parse_error;
} else if (strcmp(key, "layerrule") == 0) {
config->layer_rules =
realloc(config->layer_rules,
(config->layer_rules_count + 1) * sizeof(ConfigLayerRule));
if (!config->layer_rules) {
fprintf(stderr,
- "Error: Failed to allocate memory for layer rules\n");
- return;
+ "\033[1m\033[31m[ERROR]:\033[33m Failed to allocate "
+ "memory for layer rules\n");
+ return false;
}
ConfigLayerRule *rule = &config->layer_rules[config->layer_rules_count];
@@ -1738,6 +1857,7 @@ void parse_option(Config *config, char *key, char *value) {
rule->noanim = 0;
rule->noshadow = 0;
+ bool parse_error = false;
char *token = strtok(value, ",");
while (token != NULL) {
char *colon = strchr(token, ':');
@@ -1761,6 +1881,13 @@ void parse_option(Config *config, char *key, char *value) {
rule->noanim = CLAMP_INT(atoi(val), 0, 1);
} else if (strcmp(key, "noshadow") == 0) {
rule->noshadow = CLAMP_INT(atoi(val), 0, 1);
+ } else {
+ fprintf(
+ stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Unknown layer rule "
+ "option:\033[1m\033[31m %s\n",
+ key);
+ parse_error = true;
}
}
token = strtok(NULL, ",");
@@ -1772,14 +1899,16 @@ void parse_option(Config *config, char *key, char *value) {
}
config->layer_rules_count++;
+ return !parse_error;
} else if (strcmp(key, "windowrule") == 0) {
config->window_rules =
realloc(config->window_rules,
(config->window_rules_count + 1) * sizeof(ConfigWinRule));
if (!config->window_rules) {
fprintf(stderr,
- "Error: Failed to allocate memory for window rules\n");
- return;
+ "\033[1m\033[31m[ERROR]:\033[33m Failed to allocate "
+ "memory for window rules\n");
+ return false;
}
ConfigWinRule *rule = &config->window_rules[config->window_rules_count];
@@ -1835,6 +1964,7 @@ void parse_option(Config *config, char *key, char *value) {
rule->globalkeybinding = (KeyBinding){0};
+ bool parse_error = false;
char *token = strtok(value, ",");
while (token != NULL) {
char *colon = strchr(token, ':');
@@ -1934,17 +2064,37 @@ void parse_option(Config *config, char *key, char *value) {
rule->globalkeybinding.mod = parse_mod(mod_str);
rule->globalkeybinding.keysymcode =
parse_key(keysym_str, false);
+ if (rule->globalkeybinding.mod == UINT32_MAX) {
+ return false;
+ }
+ if (rule->globalkeybinding.keysymcode.type ==
+ KEY_TYPE_SYM &&
+ rule->globalkeybinding.keysymcode.keysym ==
+ XKB_KEY_NoSymbol) {
+ return false;
+ }
+ } else {
+ fprintf(
+ stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Unknown window rule "
+ "option:\033[1m\033[31m %s\n",
+ key);
+ parse_error = true;
}
}
token = strtok(NULL, ",");
}
config->window_rules_count++;
+ return !parse_error;
} else if (strncmp(key, "env", 3) == 0) {
char env_type[256], env_value[256];
if (sscanf(value, "%255[^,],%255[^\n]", env_type, env_value) < 2) {
- fprintf(stderr, "Error: Invalid bind format: %s\n", value);
- return;
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Invalid bind format: "
+ "\033[1m\033[31m%s\n",
+ value);
+ return false;
}
trim_whitespace(env_type);
trim_whitespace(env_value);
@@ -1959,8 +2109,9 @@ void parse_option(Config *config, char *key, char *value) {
free(env->type);
free(env->value);
free(env);
- fprintf(stderr, "Error: Failed to allocate memory for env\n");
- return;
+ fprintf(stderr, "\033[1m\033[31m[ERROR]:\033[33m Failed to "
+ "allocate memory for env\n");
+ return false;
}
config->env[config->env_count] = env;
@@ -1970,15 +2121,18 @@ void parse_option(Config *config, char *key, char *value) {
char **new_exec =
realloc(config->exec, (config->exec_count + 1) * sizeof(char *));
if (!new_exec) {
- fprintf(stderr, "Error: Failed to allocate memory for exec\n");
- return;
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Failed to allocate "
+ "memory for exec\n");
+ return false;
}
config->exec = new_exec;
config->exec[config->exec_count] = strdup(value);
if (!config->exec[config->exec_count]) {
- fprintf(stderr, "Error: Failed to duplicate exec string\n");
- return;
+ fprintf(stderr, "\033[1m\033[31m[ERROR]:\033[33m Failed to "
+ "duplicate exec string\n");
+ return false;
}
config->exec_count++;
@@ -1988,15 +2142,19 @@ void parse_option(Config *config, char *key, char *value) {
char **new_exec_once = realloc(
config->exec_once, (config->exec_once_count + 1) * sizeof(char *));
if (!new_exec_once) {
- fprintf(stderr, "Error: Failed to allocate memory for exec_once\n");
- return;
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Failed to allocate "
+ "memory for exec_once\n");
+ return false;
}
config->exec_once = new_exec_once;
config->exec_once[config->exec_once_count] = strdup(value);
if (!config->exec_once[config->exec_once_count]) {
- fprintf(stderr, "Error: Failed to duplicate exec_once string\n");
- return;
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Failed to duplicate "
+ "exec_once string\n");
+ return false;
}
config->exec_once_count++;
@@ -2007,8 +2165,9 @@ void parse_option(Config *config, char *key, char *value) {
(config->key_bindings_count + 1) * sizeof(KeyBinding));
if (!config->key_bindings) {
fprintf(stderr,
- "Error: Failed to allocate memory for key bindings\n");
- return;
+ "\033[1m\033[31m[ERROR]:\033[33m Failed to allocate "
+ "memory for key bindings\n");
+ return false;
}
KeyBinding *binding = &config->key_bindings[config->key_bindings_count];
@@ -2023,8 +2182,11 @@ void parse_option(Config *config, char *key, char *value) {
"^,],%255[^\n]",
mod_str, keysym_str, func_name, arg_value, arg_value2,
arg_value3, arg_value4, arg_value5) < 3) {
- fprintf(stderr, "Error: Invalid bind format: %s\n", value);
- return;
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Invalid bind format: "
+ "\033[1m\033[31m%s\n",
+ value);
+ return false;
}
trim_whitespace(mod_str);
trim_whitespace(keysym_str);
@@ -2057,7 +2219,9 @@ void parse_option(Config *config, char *key, char *value) {
binding->func =
parse_func_name(func_name, &binding->arg, arg_value, arg_value2,
arg_value3, arg_value4, arg_value5);
- if (!binding->func) {
+ if (!binding->func || binding->mod == UINT32_MAX ||
+ (binding->keysymcode.type == KEY_TYPE_SYM &&
+ binding->keysymcode.keysym == XKB_KEY_NoSymbol)) {
if (binding->arg.v) {
free(binding->arg.v);
binding->arg.v = NULL;
@@ -2070,7 +2234,13 @@ void parse_option(Config *config, char *key, char *value) {
free(binding->arg.v3);
binding->arg.v3 = NULL;
}
- fprintf(stderr, "Error: Unknown function in bind: %s\n", func_name);
+ if (!binding->func)
+ fprintf(
+ stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Unknown dispatch in bind: "
+ "\033[1m\033[31m%s\n",
+ func_name);
+ return false;
} else {
config->key_bindings_count++;
}
@@ -2081,8 +2251,9 @@ void parse_option(Config *config, char *key, char *value) {
(config->mouse_bindings_count + 1) * sizeof(MouseBinding));
if (!config->mouse_bindings) {
fprintf(stderr,
- "Error: Failed to allocate memory for mouse bindings\n");
- return;
+ "\033[1m\033[31m[ERROR]:\033[33m Failed to allocate "
+ "memory for mouse bindings\n");
+ return false;
}
MouseBinding *binding =
@@ -2098,8 +2269,11 @@ void parse_option(Config *config, char *key, char *value) {
"^,],%255[^\n]",
mod_str, button_str, func_name, arg_value, arg_value2,
arg_value3, arg_value4, arg_value5) < 3) {
- fprintf(stderr, "Error: Invalid mousebind format: %s\n", value);
- return;
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Invalid mousebind format: "
+ "%s\n",
+ value);
+ return false;
}
trim_whitespace(mod_str);
trim_whitespace(button_str);
@@ -2118,7 +2292,8 @@ void parse_option(Config *config, char *key, char *value) {
binding->func =
parse_func_name(func_name, &binding->arg, arg_value, arg_value2,
arg_value3, arg_value4, arg_value5);
- if (!binding->func) {
+ if (!binding->func || binding->mod == UINT32_MAX ||
+ binding->button == UINT32_MAX) {
if (binding->arg.v) {
free(binding->arg.v);
binding->arg.v = NULL;
@@ -2131,8 +2306,12 @@ void parse_option(Config *config, char *key, char *value) {
free(binding->arg.v3);
binding->arg.v3 = NULL;
}
- fprintf(stderr, "Error: Unknown function in mousebind: %s\n",
- func_name);
+ if (!binding->func)
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Unknown dispatch in "
+ "mousebind: \033[1m\033[31m%s\n",
+ func_name);
+ return false;
} else {
config->mouse_bindings_count++;
}
@@ -2142,8 +2321,9 @@ void parse_option(Config *config, char *key, char *value) {
(config->axis_bindings_count + 1) * sizeof(AxisBinding));
if (!config->axis_bindings) {
fprintf(stderr,
- "Error: Failed to allocate memory for axis bindings\n");
- return;
+ "\033[1m\033[31m[ERROR]:\033[33m Failed to allocate "
+ "memory for axis bindings\n");
+ return false;
}
AxisBinding *binding =
@@ -2159,8 +2339,11 @@ void parse_option(Config *config, char *key, char *value) {
"^,],%255[^\n]",
mod_str, dir_str, func_name, arg_value, arg_value2,
arg_value3, arg_value4, arg_value5) < 3) {
- fprintf(stderr, "Error: Invalid axisbind format: %s\n", value);
- return;
+ fprintf(
+ stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Invalid axisbind format: %s\n",
+ value);
+ return false;
}
trim_whitespace(mod_str);
@@ -2181,7 +2364,7 @@ void parse_option(Config *config, char *key, char *value) {
parse_func_name(func_name, &binding->arg, arg_value, arg_value2,
arg_value3, arg_value4, arg_value5);
- if (!binding->func) {
+ if (!binding->func || binding->mod == UINT32_MAX) {
if (binding->arg.v) {
free(binding->arg.v);
binding->arg.v = NULL;
@@ -2194,8 +2377,12 @@ void parse_option(Config *config, char *key, char *value) {
free(binding->arg.v3);
binding->arg.v3 = NULL;
}
- fprintf(stderr, "Error: Unknown function in axisbind: %s\n",
- func_name);
+ if (!binding->func)
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Unknown dispatch in "
+ "axisbind: \033[1m\033[31m%s\n",
+ func_name);
+ return false;
} else {
config->axis_bindings_count++;
}
@@ -2206,8 +2393,9 @@ void parse_option(Config *config, char *key, char *value) {
sizeof(SwitchBinding));
if (!config->switch_bindings) {
fprintf(stderr,
- "Error: Failed to allocate memory for switch bindings\n");
- return;
+ "\033[1m\033[31m[ERROR]:\033[33m Failed to allocate "
+ "memory for switch bindings\n");
+ return false;
}
SwitchBinding *binding =
@@ -2223,8 +2411,11 @@ void parse_option(Config *config, char *key, char *value) {
"^\n]",
fold_str, func_name, arg_value, arg_value2, arg_value3,
arg_value4, arg_value5) < 3) {
- fprintf(stderr, "Error: Invalid switchbind format: %s\n", value);
- return;
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Invalid switchbind "
+ "format: %s\n",
+ value);
+ return false;
}
trim_whitespace(fold_str);
trim_whitespace(func_name);
@@ -2252,8 +2443,13 @@ void parse_option(Config *config, char *key, char *value) {
free(binding->arg.v3);
binding->arg.v3 = NULL;
}
- fprintf(stderr, "Error: Unknown function in switchbind: %s\n",
+
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Unknown dispatch in "
+ "switchbind: "
+ "\033[1m\033[31m%s\n",
func_name);
+ return false;
} else {
config->switch_bindings_count++;
}
@@ -2264,8 +2460,9 @@ void parse_option(Config *config, char *key, char *value) {
(config->gesture_bindings_count + 1) * sizeof(GestureBinding));
if (!config->gesture_bindings) {
fprintf(stderr,
- "Error: Failed to allocate memory for axis gesturebind\n");
- return;
+ "\033[1m\033[31m[ERROR]:\033[33m Failed to allocate "
+ "memory for axis gesturebind\n");
+ return false;
}
GestureBinding *binding =
@@ -2281,8 +2478,11 @@ void parse_option(Config *config, char *key, char *value) {
"^,],%255[^,],%255[^\n]",
mod_str, motion_str, fingers_count_str, func_name, arg_value,
arg_value2, arg_value3, arg_value4, arg_value5) < 4) {
- fprintf(stderr, "Error: Invalid gesturebind format: %s\n", value);
- return;
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Invalid gesturebind "
+ "format: %s\n",
+ value);
+ return false;
}
trim_whitespace(mod_str);
@@ -2305,7 +2505,7 @@ void parse_option(Config *config, char *key, char *value) {
parse_func_name(func_name, &binding->arg, arg_value, arg_value2,
arg_value3, arg_value4, arg_value5);
- if (!binding->func) {
+ if (!binding->func || binding->mod == UINT32_MAX) {
if (binding->arg.v) {
free(binding->arg.v);
binding->arg.v = NULL;
@@ -2318,8 +2518,12 @@ void parse_option(Config *config, char *key, char *value) {
free(binding->arg.v3);
binding->arg.v3 = NULL;
}
- fprintf(stderr, "Error: Unknown function in axisbind: %s\n",
- func_name);
+ if (!binding->func)
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Unknown dispatch in "
+ "axisbind: \033[1m\033[31m%s\n",
+ func_name);
+ return false;
} else {
config->gesture_bindings_count++;
}
@@ -2327,22 +2531,30 @@ void parse_option(Config *config, char *key, char *value) {
} else if (strncmp(key, "source", 6) == 0) {
parse_config_file(config, value);
} else {
- fprintf(stderr, "Error: Unknown key: %s\n", key);
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Unknown keyword: "
+ "\033[1m\033[31m%s\n",
+ key);
+ return false;
}
+
+ return true;
}
-void parse_config_line(Config *config, const char *line) {
+bool parse_config_line(Config *config, const char *line) {
char key[256], value[256];
if (sscanf(line, "%255[^=]=%255[^\n]", key, value) != 2) {
- // fprintf(stderr, "Error: Invalid line format: %s\n", line);
- return;
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Invalid line format: %s",
+ line);
+ return false;
}
// Then trim each part separately
trim_whitespace(key);
trim_whitespace(value);
- parse_option(config, key, value);
+ return parse_option(config, key, value);
}
void parse_config_file(Config *config, const char *file_path) {
@@ -2361,7 +2573,9 @@ void parse_config_file(Config *config, const char *file_path) {
} else {
const char *home = getenv("HOME");
if (!home) {
- fprintf(stderr, "Error: HOME environment variable not set.\n");
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m HOME environment "
+ "variable not set.\n");
return;
}
snprintf(full_path, sizeof(full_path), "%s/.config/mango/%s", home,
@@ -2375,7 +2589,8 @@ void parse_config_file(Config *config, const char *file_path) {
const char *home = getenv("HOME");
if (!home) {
- fprintf(stderr, "Error: HOME environment variable not set.\n");
+ fprintf(stderr, "\033[1m\033[31m[ERROR]:\033[33m HOME environment "
+ "variable not set.\n");
return;
}
snprintf(full_path, sizeof(full_path), "%s%s", home, file_path + 1);
@@ -2392,11 +2607,21 @@ void parse_config_file(Config *config, const char *file_path) {
}
char line[512];
+ bool parse_correct = true;
+ uint32_t line_count = 0;
while (fgets(line, sizeof(line), file)) {
+ line_count++;
if (line[0] == '#' || line[0] == '\n') {
continue;
}
- parse_config_line(config, line);
+ parse_correct = parse_config_line(config, line);
+ if (!parse_correct) {
+ fprintf(stderr,
+ "\033[1;31m╰─\033[1;33m[Index]\033[0m "
+ "\033[1;36m%s\033[0m:\033[1;35m%d\033[0m\n"
+ " \033[1;36m╰─\033[0;33m%s\033[0m\n\n",
+ file_path, line_count, line);
+ }
}
fclose(file);
diff --git a/src/mango.c b/src/mango.c
index 1d332c4..5a8b6fc 100644
--- a/src/mango.c
+++ b/src/mango.c
@@ -6160,17 +6160,22 @@ int32_t main(int32_t argc, char *argv[]) {
char *startup_cmd = NULL;
int32_t c;
- while ((c = getopt(argc, argv, "s:c:hdv")) != -1) {
- if (c == 's')
+ while ((c = getopt(argc, argv, "s:c:hdvp")) != -1) {
+ if (c == 's') {
startup_cmd = optarg;
- else if (c == 'd')
+ } else if (c == 'd') {
log_level = WLR_DEBUG;
- else if (c == 'v')
- die("mango " VERSION);
- else if (c == 'c')
+ } else if (c == 'v') {
+ printf("mango " VERSION "\n");
+ return EXIT_SUCCESS;
+ } else if (c == 'c') {
cli_config_path = optarg;
- else
+ } else if (c == 'p') {
+ parse_config();
+ return EXIT_SUCCESS;
+ } else {
goto usage;
+ }
}
if (optind < argc)
goto usage;
@@ -6184,7 +6189,14 @@ int32_t main(int32_t argc, char *argv[]) {
run(startup_cmd);
cleanup();
return EXIT_SUCCESS;
-
usage:
- die("Usage: %s [-v] [-d] [-c config file] [-s startup command]", argv[0]);
+ printf("Usage: mango [OPTIONS]\n"
+ "\n"
+ "Options:\n"
+ " -v Show mango version\n"
+ " -d Enable debug log\n"
+ " -c Use custom configuration file\n"
+ " -s Execute startup command\n"
+ " -p Check configuration file error\n");
+ return EXIT_SUCCESS;
}
From eb0607501d3cdfa352dcc29ba35c46b79bc2fba8 Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Mon, 2 Feb 2026 14:47:13 +0800
Subject: [PATCH 38/47] opt: change drag resize request limit to 120hz for
floating window
---
src/config/preset.h | 3 ++-
src/layout/arrange.h | 6 +++---
src/mango.c | 2 +-
3 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/src/config/preset.h b/src/config/preset.h
index 3d05161..ae4424f 100644
--- a/src/config/preset.h
+++ b/src/config/preset.h
@@ -108,7 +108,8 @@ int32_t drag_warp_cursor = 1;
int32_t xwayland_persistence = 1; /* xwayland persistence */
int32_t syncobj_enable = 0;
int32_t allow_lock_transparent = 0;
-double drag_refresh_interval = 16.0;
+double drag_tile_refresh_interval = 16.0;
+double drag_floating_refresh_interval = 8.0;
int32_t allow_tearing = TEARING_DISABLED;
int32_t allow_shortcuts_inhibit = SHORTCUTS_INHIBIT_ENABLE;
diff --git a/src/layout/arrange.h b/src/layout/arrange.h
index 6ff1dd2..3721364 100644
--- a/src/layout/arrange.h
+++ b/src/layout/arrange.h
@@ -210,7 +210,7 @@ void resize_tile_master_horizontal(Client *grabc, bool isdrag, int32_t offsetx,
}
if (last_apply_drap_time == 0 ||
- time - last_apply_drap_time > drag_refresh_interval) {
+ time - last_apply_drap_time > drag_tile_refresh_interval) {
arrange(grabc->mon, false, false);
last_apply_drap_time = time;
}
@@ -367,7 +367,7 @@ void resize_tile_master_vertical(Client *grabc, bool isdrag, int32_t offsetx,
}
if (last_apply_drap_time == 0 ||
- time - last_apply_drap_time > drag_refresh_interval) {
+ time - last_apply_drap_time > drag_tile_refresh_interval) {
arrange(grabc->mon, false, false);
last_apply_drap_time = time;
}
@@ -548,7 +548,7 @@ void resize_tile_scroller(Client *grabc, bool isdrag, int32_t offsetx,
}
if (last_apply_drap_time == 0 ||
- time - last_apply_drap_time > drag_refresh_interval) {
+ time - last_apply_drap_time > drag_tile_refresh_interval) {
arrange(grabc->mon, false, false);
last_apply_drap_time = time;
}
diff --git a/src/mango.c b/src/mango.c
index 5a8b6fc..f5583c3 100644
--- a/src/mango.c
+++ b/src/mango.c
@@ -4143,7 +4143,7 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx,
if (grabc->isfloating) {
grabc->iscustomsize = 1;
if (last_apply_drap_time == 0 ||
- time - last_apply_drap_time > drag_refresh_interval) {
+ time - last_apply_drap_time > drag_floating_refresh_interval) {
resize_floating_window(grabc);
last_apply_drap_time = time;
}
From 0546a2d4c472390f23171f7750b5702f1acb8582 Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Tue, 3 Feb 2026 10:06:26 +0800
Subject: [PATCH 39/47] opt: allow use comma symbol in spawn shell value
---
src/config/parse_config.h | 171 +++++++++++++++++++++++++-------------
1 file changed, 114 insertions(+), 57 deletions(-)
diff --git a/src/config/parse_config.h b/src/config/parse_config.h
index f7ce848..1a025e9 100644
--- a/src/config/parse_config.h
+++ b/src/config/parse_config.h
@@ -550,6 +550,48 @@ static bool starts_with_ignore_case(const char *str, const char *prefix) {
return true;
}
+static char *combine_args_until_empty(char *values[], int count) {
+ // find the first empty string
+ int first_empty = count;
+ for (int i = 0; i < count; i++) {
+ // check if it's empty: empty string or only contains "0" (initialized)
+ if (values[i][0] == '\0' ||
+ (strlen(values[i]) == 1 && values[i][0] == '0')) {
+ first_empty = i;
+ break;
+ }
+ }
+
+ // if there are no valid parameters, return an empty string
+ if (first_empty == 0) {
+ return strdup("");
+ }
+
+ // calculate the total length
+ size_t total_len = 0;
+ for (int i = 0; i < first_empty; i++) {
+ total_len += strlen(values[i]);
+ }
+ // plus the number of commas (first_empty-1 commas)
+ total_len += (first_empty - 1);
+
+ // allocate memory and concatenate
+ char *combined = malloc(total_len + 1);
+ if (combined == NULL) {
+ return strdup("");
+ }
+
+ combined[0] = '\0';
+ for (int i = 0; i < first_empty; i++) {
+ if (i > 0) {
+ strcat(combined, ",");
+ }
+ strcat(combined, values[i]);
+ }
+
+ return combined;
+}
+
uint32_t parse_mod(const char *mod_str) {
if (!mod_str || !*mod_str) {
return UINT32_MAX;
@@ -1005,10 +1047,14 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value,
(*arg).ui = atoi(arg_value);
} else if (strcmp(func_name, "spawn") == 0) {
func = spawn;
- (*arg).v = strdup(arg_value);
+ char *values[] = {arg_value, arg_value2, arg_value3, arg_value4,
+ arg_value5};
+ (*arg).v = combine_args_until_empty(values, 5);
} else if (strcmp(func_name, "spawn_shell") == 0) {
func = spawn_shell;
- (*arg).v = strdup(arg_value);
+ char *values[] = {arg_value, arg_value2, arg_value3, arg_value4,
+ arg_value5};
+ (*arg).v = combine_args_until_empty(values, 5);
} else if (strcmp(func_name, "spawn_on_empty") == 0) {
func = spawn_on_empty;
(*arg).v = strdup(arg_value); // 注意:之后需要释放这个内存
@@ -1385,9 +1431,8 @@ bool parse_option(Config *config, char *key, char *value) {
config->scroller_proportion_preset =
(float *)malloc(float_count * sizeof(float));
if (!config->scroller_proportion_preset) {
- fprintf(
- stderr,
- "\033[1m\033[31m[ERROR]:\033[33m Memory allocation failed\n");
+ fprintf(stderr, "\033[1m\033[31m[ERROR]:\033[33m Memory "
+ "allocation failed\n");
return false;
}
@@ -1400,18 +1445,19 @@ bool parse_option(Config *config, char *key, char *value) {
while (token != NULL && i < float_count) {
if (sscanf(token, "%f", &value_set) != 1) {
- fprintf(
- stderr,
- "\033[1m\033[31m[ERROR]:\033[33m Invalid float value in "
- "scroller_proportion_preset: %s\n",
- token);
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Invalid float "
+ "value in "
+ "scroller_proportion_preset: %s\n",
+ token);
free(value_copy);
free(config->scroller_proportion_preset);
config->scroller_proportion_preset = NULL;
return false;
}
- // Clamp the value between 0.0 and 1.0 (or your desired range)
+ // Clamp the value between 0.0 and 1.0 (or your desired
+ // range)
config->scroller_proportion_preset[i] =
CLAMP_FLOAT(value_set, 0.1f, 1.0f);
@@ -1448,9 +1494,8 @@ bool parse_option(Config *config, char *key, char *value) {
config->circle_layout = (char **)malloc(string_count * sizeof(char *));
memset(config->circle_layout, 0, string_count * sizeof(char *));
if (!config->circle_layout) {
- fprintf(
- stderr,
- "\033[1m\033[31m[ERROR]:\033[33m Memory allocation failed\n");
+ fprintf(stderr, "\033[1m\033[31m[ERROR]:\033[33m Memory "
+ "allocation failed\n");
return false;
}
@@ -1606,7 +1651,8 @@ bool parse_option(Config *config, char *key, char *value) {
int64_t color = parse_color(value);
if (color == -1) {
fprintf(stderr,
- "\033[1m\033[31m[ERROR]:\033[33m Invalid rootcolor format: "
+ "\033[1m\033[31m[ERROR]:\033[33m Invalid rootcolor "
+ "format: "
"%s\n",
value);
return false;
@@ -1650,11 +1696,11 @@ bool parse_option(Config *config, char *key, char *value) {
} else if (strcmp(key, "maximizescreencolor") == 0) {
int64_t color = parse_color(value);
if (color == -1) {
- fprintf(
- stderr,
- "\033[1m\033[31m[ERROR]:\033[33m Invalid maximizescreencolor "
- "format: %s\n",
- value);
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Invalid "
+ "maximizescreencolor "
+ "format: %s\n",
+ value);
return false;
} else {
convert_hex_to_rgba(config->maximizescreencolor, color);
@@ -1674,7 +1720,8 @@ bool parse_option(Config *config, char *key, char *value) {
int64_t color = parse_color(value);
if (color == -1) {
fprintf(stderr,
- "\033[1m\033[31m[ERROR]:\033[33m Invalid scratchpadcolor "
+ "\033[1m\033[31m[ERROR]:\033[33m Invalid "
+ "scratchpadcolor "
"format: %s\n",
value);
return false;
@@ -1760,11 +1807,11 @@ bool parse_option(Config *config, char *key, char *value) {
} else if (strcmp(key, "vrr") == 0) {
rule->vrr = CLAMP_INT(atoi(val), 0, 1);
} else {
- fprintf(
- stderr,
- "\033[1m\033[31m[ERROR]:\033[33m Unknown monitor rule "
- "option:\033[1m\033[31m %s\n",
- key);
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Unknown "
+ "monitor rule "
+ "option:\033[1m\033[31m %s\n",
+ key);
parse_error = true;
}
}
@@ -1824,7 +1871,8 @@ bool parse_option(Config *config, char *key, char *value) {
rule->mfact = CLAMP_FLOAT(atof(val), 0.1f, 0.9f);
} else {
fprintf(stderr,
- "\033[1m\033[31m[ERROR]:\033[33m Unknown tag rule "
+ "\033[1m\033[31m[ERROR]:\033[33m Unknown "
+ "tag rule "
"option:\033[1m\033[31m %s\n",
key);
parse_error = true;
@@ -1882,11 +1930,11 @@ bool parse_option(Config *config, char *key, char *value) {
} else if (strcmp(key, "noshadow") == 0) {
rule->noshadow = CLAMP_INT(atoi(val), 0, 1);
} else {
- fprintf(
- stderr,
- "\033[1m\033[31m[ERROR]:\033[33m Unknown layer rule "
- "option:\033[1m\033[31m %s\n",
- key);
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Unknown "
+ "layer rule "
+ "option:\033[1m\033[31m %s\n",
+ key);
parse_error = true;
}
}
@@ -2074,11 +2122,11 @@ bool parse_option(Config *config, char *key, char *value) {
return false;
}
} else {
- fprintf(
- stderr,
- "\033[1m\033[31m[ERROR]:\033[33m Unknown window rule "
- "option:\033[1m\033[31m %s\n",
- key);
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Unknown "
+ "window rule "
+ "option:\033[1m\033[31m %s\n",
+ key);
parse_error = true;
}
}
@@ -2178,7 +2226,8 @@ bool parse_option(Config *config, char *key, char *value) {
arg_value3[256] = "0\0", arg_value4[256] = "0\0",
arg_value5[256] = "0\0";
if (sscanf(value,
- "%255[^,],%255[^,],%255[^,],%255[^,],%255[^,],%255[^,],%255["
+ "%255[^,],%255[^,],%255[^,],%255[^,],%255[^,],%255[^"
+ ",],%255["
"^,],%255[^\n]",
mod_str, keysym_str, func_name, arg_value, arg_value2,
arg_value3, arg_value4, arg_value5) < 3) {
@@ -2235,11 +2284,11 @@ bool parse_option(Config *config, char *key, char *value) {
binding->arg.v3 = NULL;
}
if (!binding->func)
- fprintf(
- stderr,
- "\033[1m\033[31m[ERROR]:\033[33m Unknown dispatch in bind: "
- "\033[1m\033[31m%s\n",
- func_name);
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Unknown "
+ "dispatch in bind: "
+ "\033[1m\033[31m%s\n",
+ func_name);
return false;
} else {
config->key_bindings_count++;
@@ -2265,12 +2314,14 @@ bool parse_option(Config *config, char *key, char *value) {
arg_value3[256] = "0\0", arg_value4[256] = "0\0",
arg_value5[256] = "0\0";
if (sscanf(value,
- "%255[^,],%255[^,],%255[^,],%255[^,],%255[^,],%255[^,],%255["
+ "%255[^,],%255[^,],%255[^,],%255[^,],%255[^,],%255[^"
+ ",],%255["
"^,],%255[^\n]",
mod_str, button_str, func_name, arg_value, arg_value2,
arg_value3, arg_value4, arg_value5) < 3) {
fprintf(stderr,
- "\033[1m\033[31m[ERROR]:\033[33m Invalid mousebind format: "
+ "\033[1m\033[31m[ERROR]:\033[33m Invalid mousebind "
+ "format: "
"%s\n",
value);
return false;
@@ -2308,7 +2359,8 @@ bool parse_option(Config *config, char *key, char *value) {
}
if (!binding->func)
fprintf(stderr,
- "\033[1m\033[31m[ERROR]:\033[33m Unknown dispatch in "
+ "\033[1m\033[31m[ERROR]:\033[33m Unknown "
+ "dispatch in "
"mousebind: \033[1m\033[31m%s\n",
func_name);
return false;
@@ -2335,14 +2387,15 @@ bool parse_option(Config *config, char *key, char *value) {
arg_value3[256] = "0\0", arg_value4[256] = "0\0",
arg_value5[256] = "0\0";
if (sscanf(value,
- "%255[^,],%255[^,],%255[^,],%255[^,],%255[^,],%255[^,],%255["
+ "%255[^,],%255[^,],%255[^,],%255[^,],%255[^,],%255[^"
+ ",],%255["
"^,],%255[^\n]",
mod_str, dir_str, func_name, arg_value, arg_value2,
arg_value3, arg_value4, arg_value5) < 3) {
- fprintf(
- stderr,
- "\033[1m\033[31m[ERROR]:\033[33m Invalid axisbind format: %s\n",
- value);
+ fprintf(stderr,
+ "\033[1m\033[31m[ERROR]:\033[33m Invalid axisbind "
+ "format: %s\n",
+ value);
return false;
}
@@ -2379,7 +2432,8 @@ bool parse_option(Config *config, char *key, char *value) {
}
if (!binding->func)
fprintf(stderr,
- "\033[1m\033[31m[ERROR]:\033[33m Unknown dispatch in "
+ "\033[1m\033[31m[ERROR]:\033[33m Unknown "
+ "dispatch in "
"axisbind: \033[1m\033[31m%s\n",
func_name);
return false;
@@ -2407,7 +2461,8 @@ bool parse_option(Config *config, char *key, char *value) {
arg_value3[256] = "0\0", arg_value4[256] = "0\0",
arg_value5[256] = "0\0";
if (sscanf(value,
- "%255[^,],%255[^,],%255[^,],%255[^,],%255[^,],%255[^,],%255["
+ "%255[^,],%255[^,],%255[^,],%255[^,],%255[^,],%255[^"
+ ",],%255["
"^\n]",
fold_str, func_name, arg_value, arg_value2, arg_value3,
arg_value4, arg_value5) < 3) {
@@ -2474,7 +2529,8 @@ bool parse_option(Config *config, char *key, char *value) {
arg_value3[256] = "0\0", arg_value4[256] = "0\0",
arg_value5[256] = "0\0";
if (sscanf(value,
- "%255[^,],%255[^,],%255[^,],%255[^,],%255[^,],%255[^,],%255["
+ "%255[^,],%255[^,],%255[^,],%255[^,],%255[^,],%255[^"
+ ",],%255["
"^,],%255[^,],%255[^\n]",
mod_str, motion_str, fingers_count_str, func_name, arg_value,
arg_value2, arg_value3, arg_value4, arg_value5) < 4) {
@@ -2520,7 +2576,8 @@ bool parse_option(Config *config, char *key, char *value) {
}
if (!binding->func)
fprintf(stderr,
- "\033[1m\033[31m[ERROR]:\033[33m Unknown dispatch in "
+ "\033[1m\033[31m[ERROR]:\033[33m Unknown "
+ "dispatch in "
"axisbind: \033[1m\033[31m%s\n",
func_name);
return false;
@@ -3134,8 +3191,8 @@ void set_value_default() {
config.hotarea_size = hotarea_size; // 热区大小,10x10
config.hotarea_corner = hotarea_corner;
config.enable_hotarea = enable_hotarea; // 是否启用鼠标热区
- config.smartgaps =
- smartgaps; /* 1 means no outer gap when there is only one window */
+ config.smartgaps = smartgaps; /* 1 means no outer gap when there is
+ only one window */
config.sloppyfocus = sloppyfocus; /* focus follows mouse */
config.gappih = gappih; /* horiz inner gap between windows */
config.gappiv = gappiv; /* vert inner gap between windows */
From 50b67ac539e65f68ae177de258c7f2fca59bc350 Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Tue, 3 Feb 2026 10:15:42 +0800
Subject: [PATCH 40/47] fix: miss init arg value
---
src/config/parse_config.h | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/src/config/parse_config.h b/src/config/parse_config.h
index 1a025e9..e0193c4 100644
--- a/src/config/parse_config.h
+++ b/src/config/parse_config.h
@@ -2262,6 +2262,12 @@ bool parse_option(Config *config, char *key, char *value) {
binding->keysymcode =
parse_key(keysym_str, binding->keysymcode.type == KEY_TYPE_SYM);
binding->mod = parse_mod(mod_str);
+ binding->arg.i = 0;
+ binding->arg.i2 = 0;
+ binding->arg.f = 0.0f;
+ binding->arg.f2 = 0.0f;
+ binding->arg.ui = 0;
+ binding->arg.ui2 = 0;
binding->arg.v = NULL;
binding->arg.v2 = NULL;
binding->arg.v3 = NULL;
@@ -2337,6 +2343,12 @@ bool parse_option(Config *config, char *key, char *value) {
binding->mod = parse_mod(mod_str);
binding->button = parse_button(button_str);
+ binding->arg.i = 0;
+ binding->arg.i2 = 0;
+ binding->arg.f = 0.0f;
+ binding->arg.f2 = 0.0f;
+ binding->arg.ui = 0;
+ binding->arg.ui2 = 0;
binding->arg.v = NULL;
binding->arg.v2 = NULL;
binding->arg.v3 = NULL;
@@ -2554,6 +2566,12 @@ bool parse_option(Config *config, char *key, char *value) {
binding->mod = parse_mod(mod_str);
binding->motion = parse_direction(motion_str);
binding->fingers_count = atoi(fingers_count_str);
+ binding->arg.i = 0;
+ binding->arg.i2 = 0;
+ binding->arg.f = 0.0f;
+ binding->arg.f2 = 0.0f;
+ binding->arg.ui = 0;
+ binding->arg.ui2 = 0;
binding->arg.v = NULL;
binding->arg.v2 = NULL;
binding->arg.v3 = NULL;
From 336f873d83582a41188893fc0c0849867d28d589 Mon Sep 17 00:00:00 2001
From: quadratic
Date: Tue, 3 Feb 2026 21:11:41 +0100
Subject: [PATCH 41/47] fix: uninitialised argument fields when calling mmsg -d
---
src/config/parse_config.h | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/src/config/parse_config.h b/src/config/parse_config.h
index e0193c4..3fcd518 100644
--- a/src/config/parse_config.h
+++ b/src/config/parse_config.h
@@ -897,6 +897,12 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value,
char *arg_value5) {
FuncType func = NULL;
+ (*arg).i = 0;
+ (*arg).i2 = 0;
+ (*arg).f = 0.0f;
+ (*arg).f2 = 0.0f;
+ (*arg).ui = 0;
+ (*arg).ui2 = 0;
(*arg).v = NULL;
(*arg).v2 = NULL;
(*arg).v3 = NULL;
From 8ba259fbb7737e4cef29ca20c731ed0a93e4017d Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Wed, 4 Feb 2026 09:45:26 +0800
Subject: [PATCH 42/47] opt: avoid opacity flickering in focus animation when
open new window
---
src/animation/client.h | 41 +++++++++++++--------------------------
src/config/parse_config.h | 12 ++++++------
src/mango.c | 6 ++++++
3 files changed, 25 insertions(+), 34 deletions(-)
diff --git a/src/animation/client.h b/src/animation/client.h
index 7bc47ac..8de9b06 100644
--- a/src/animation/client.h
+++ b/src/animation/client.h
@@ -1054,20 +1054,11 @@ void client_set_focused_opacity_animation(Client *c) {
sizeof(c->opacity_animation.target_border_color));
c->opacity_animation.target_opacity = c->focused_opacity;
c->opacity_animation.time_started = get_now_in_ms();
- if (c->opacity_animation.running) {
- memcpy(c->opacity_animation.initial_border_color,
- c->opacity_animation.current_border_color,
- sizeof(c->opacity_animation.initial_border_color));
- c->opacity_animation.initial_opacity =
- c->opacity_animation.current_opacity;
- } else {
- memcpy(c->opacity_animation.initial_border_color, bordercolor,
- sizeof(c->opacity_animation.initial_border_color));
- memcpy(c->opacity_animation.current_border_color, bordercolor,
- sizeof(c->opacity_animation.current_border_color));
- c->opacity_animation.initial_opacity = c->unfocused_opacity;
- c->opacity_animation.current_opacity = c->unfocused_opacity;
- }
+ memcpy(c->opacity_animation.initial_border_color,
+ c->opacity_animation.current_border_color,
+ sizeof(c->opacity_animation.initial_border_color));
+ c->opacity_animation.initial_opacity = c->opacity_animation.current_opacity;
+
c->opacity_animation.running = true;
}
@@ -1087,20 +1078,10 @@ void client_set_unfocused_opacity_animation(Client *c) {
c->opacity_animation.target_opacity = c->unfocused_opacity;
c->opacity_animation.time_started = get_now_in_ms();
- if (c->opacity_animation.running) {
- memcpy(c->opacity_animation.initial_border_color,
- c->opacity_animation.current_border_color,
- sizeof(c->opacity_animation.initial_border_color));
- c->opacity_animation.initial_opacity =
- c->opacity_animation.current_opacity;
- } else {
- memcpy(c->opacity_animation.initial_border_color, border_color,
- sizeof(c->opacity_animation.initial_border_color));
- memcpy(c->opacity_animation.current_border_color, border_color,
- sizeof(c->opacity_animation.current_border_color));
- c->opacity_animation.initial_opacity = c->focused_opacity;
- c->opacity_animation.current_opacity = c->focused_opacity;
- }
+ memcpy(c->opacity_animation.initial_border_color,
+ c->opacity_animation.current_border_color,
+ sizeof(c->opacity_animation.initial_border_color));
+ c->opacity_animation.initial_opacity = c->opacity_animation.current_opacity;
c->opacity_animation.running = true;
}
@@ -1135,6 +1116,10 @@ bool client_apply_focus_opacity(Client *c) {
if (target_opacity > opacity) {
target_opacity = opacity;
}
+ memcpy(c->opacity_animation.current_border_color,
+ c->opacity_animation.target_border_color,
+ sizeof(c->opacity_animation.current_border_color));
+ c->opacity_animation.current_opacity = target_opacity;
client_set_opacity(c, target_opacity);
client_set_border_color(c, c->opacity_animation.target_border_color);
} else if (animations && c->opacity_animation.running) {
diff --git a/src/config/parse_config.h b/src/config/parse_config.h
index 3fcd518..efc9c96 100644
--- a/src/config/parse_config.h
+++ b/src/config/parse_config.h
@@ -897,12 +897,12 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value,
char *arg_value5) {
FuncType func = NULL;
- (*arg).i = 0;
- (*arg).i2 = 0;
- (*arg).f = 0.0f;
- (*arg).f2 = 0.0f;
- (*arg).ui = 0;
- (*arg).ui2 = 0;
+ (*arg).i = 0;
+ (*arg).i2 = 0;
+ (*arg).f = 0.0f;
+ (*arg).f2 = 0.0f;
+ (*arg).ui = 0;
+ (*arg).ui2 = 0;
(*arg).v = NULL;
(*arg).v2 = NULL;
(*arg).v3 = NULL;
diff --git a/src/mango.c b/src/mango.c
index f5583c3..2584613 100644
--- a/src/mango.c
+++ b/src/mango.c
@@ -3825,6 +3825,12 @@ void init_client_properties(Client *c) {
c->stack_proportion = 0.0f;
c->next_in_stack = NULL;
c->prev_in_stack = NULL;
+ memcpy(c->opacity_animation.initial_border_color, bordercolor,
+ sizeof(c->opacity_animation.initial_border_color));
+ memcpy(c->opacity_animation.current_border_color, bordercolor,
+ sizeof(c->opacity_animation.current_border_color));
+ c->opacity_animation.initial_opacity = c->unfocused_opacity;
+ c->opacity_animation.current_opacity = c->unfocused_opacity;
}
void // old fix to 0.5
From b0f839468c6b4a9bd5a037d380f2ed4ee2a89dfe Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Wed, 4 Feb 2026 17:42:13 +0800
Subject: [PATCH 43/47] opt: don't change stack inner per when swallow
---
src/mango.c | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/src/mango.c b/src/mango.c
index 2584613..b37cc1f 100644
--- a/src/mango.c
+++ b/src/mango.c
@@ -1071,6 +1071,9 @@ void swallow(Client *c, Client *w) {
c->tags = w->tags;
c->geom = w->geom;
c->float_geom = w->float_geom;
+ c->stack_inner_per = w->stack_inner_per;
+ c->master_inner_per = w->master_inner_per;
+ c->master_mfact_per = w->master_mfact_per;
c->scroller_proportion = w->scroller_proportion;
c->next_in_stack = w->next_in_stack;
c->prev_in_stack = w->prev_in_stack;
@@ -4678,6 +4681,7 @@ setfloating(Client *c, int32_t floating) {
Client *fc = NULL;
struct wlr_box target_box;
+ int32_t old_floating_state = c->isfloating;
c->isfloating = floating;
bool window_size_outofrange = false;
@@ -4747,7 +4751,7 @@ setfloating(Client *c, int32_t floating) {
layers[c->isfloating ? LyrTop : LyrTile]);
}
- if (!c->isfloating) {
+ if (!c->isfloating && old_floating_state) {
set_size_per(c->mon, c);
}
@@ -4796,6 +4800,7 @@ void setmaximizescreen(Client *c, int32_t maximizescreen) {
if (c->mon->isoverview)
return;
+ int32_t old_maximizescreen_state = c->ismaximizescreen;
c->ismaximizescreen = maximizescreen;
if (maximizescreen) {
@@ -4825,7 +4830,7 @@ void setmaximizescreen(Client *c, int32_t maximizescreen) {
wlr_scene_node_reparent(&c->scene->node,
layers[c->isfloating ? LyrTop : LyrTile]);
- if (!c->ismaximizescreen) {
+ if (!c->ismaximizescreen && old_maximizescreen_state) {
set_size_per(c->mon, c);
}
@@ -4858,6 +4863,7 @@ void setfullscreen(Client *c, int32_t fullscreen) // 用自定义全屏代理自
if (c->mon->isoverview)
return;
+ int32_t old_fullscreen_state = c->isfullscreen;
c->isfullscreen = fullscreen;
client_set_fullscreen(c, fullscreen);
@@ -4895,7 +4901,7 @@ void setfullscreen(Client *c, int32_t fullscreen) // 用自定义全屏代理自
layers[fullscreen || c->isfloating ? LyrTop : LyrTile]);
}
- if (!c->isfullscreen) {
+ if (!c->isfullscreen && old_fullscreen_state) {
set_size_per(c->mon, c);
}
From a4faf2c494cc09b95ff222959d7f3566d8234c1d Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Wed, 4 Feb 2026 21:29:37 +0800
Subject: [PATCH 44/47] opt: avoid stack inner per change when switch tty
---
src/mango.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/mango.c b/src/mango.c
index b37cc1f..171401e 100644
--- a/src/mango.c
+++ b/src/mango.c
@@ -5810,7 +5810,8 @@ void updatemons(struct wl_listener *listener, void *data) {
if (selmon && selmon->wlr_output->enabled) {
wl_list_for_each(c, &clients, link) {
if (!c->mon && client_surface(c)->mapped) {
- client_change_mon(c, selmon);
+ c->mon = selmon;
+ reset_foreign_tolevel(c);
}
}
focusclient(focustop(selmon), 1);
From 2bda8e3bf15b82676dc8765c7cf25c67e5041952 Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Wed, 4 Feb 2026 21:42:05 +0800
Subject: [PATCH 45/47] opt: avoid double reset foreign toplevel handle
---
src/mango.c | 18 +-----------------
1 file changed, 1 insertion(+), 17 deletions(-)
diff --git a/src/mango.c b/src/mango.c
index 171401e..591d4f6 100644
--- a/src/mango.c
+++ b/src/mango.c
@@ -969,7 +969,6 @@ static struct wlr_xwayland *xwayland;
void client_change_mon(Client *c, Monitor *m) {
setmon(c, m, c->tags, true);
- reset_foreign_tolevel(c);
if (c->isfloating) {
c->float_geom = c->geom =
setclient_coordinate_center(c, c->mon, c->geom, 0, 0);
@@ -2026,7 +2025,6 @@ buttonpress(struct wl_listener *listener, void *data) {
selmon = xytomon(cursor->x, cursor->y);
client_update_oldmonname_record(grabc, selmon);
setmon(grabc, selmon, 0, true);
- reset_foreign_tolevel(grabc);
selmon->prevsel = ISTILED(selmon->sel) ? selmon->sel : NULL;
selmon->sel = grabc;
tmpc = grabc;
@@ -5030,6 +5028,7 @@ void setmon(Client *c, Monitor *m, uint32_t newtags, bool focus) {
arrange(oldmon, false, false);
if (m) {
/* Make sure window actually overlaps with the monitor */
+ reset_foreign_tolevel(c);
resize(c, c->geom, 0);
c->tags =
newtags ? newtags
@@ -5041,21 +5040,6 @@ void setmon(Client *c, Monitor *m, uint32_t newtags, bool focus) {
if (focus && !client_is_x11_popup(c)) {
focusclient(focustop(selmon), 1);
}
-
- if (m) {
-
- if (c->foreign_toplevel) {
- remove_foreign_topleve(c);
- }
-
- add_foreign_toplevel(c);
- if (m->sel && m->sel->foreign_toplevel)
- wlr_foreign_toplevel_handle_v1_set_activated(
- m->sel->foreign_toplevel, false);
- if (c->foreign_toplevel)
- wlr_foreign_toplevel_handle_v1_set_activated(c->foreign_toplevel,
- true);
- }
}
void setpsel(struct wl_listener *listener, void *data) {
From 65fcd58949ce55eca3195bdbc6f935058fc3b33c Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Wed, 4 Feb 2026 21:50:44 +0800
Subject: [PATCH 46/47] opt: optimize file detect message
---
src/config/parse_config.h | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/config/parse_config.h b/src/config/parse_config.h
index efc9c96..a559432 100644
--- a/src/config/parse_config.h
+++ b/src/config/parse_config.h
@@ -2683,7 +2683,10 @@ void parse_config_file(Config *config, const char *file_path) {
}
if (!file) {
- perror("Error opening file");
+ fprintf(stderr,
+ "\033[1;31m\033[1;33m[ERROR]:\033[0m Failed to open "
+ "config file: %s\n",
+ file_path);
return;
}
From 8e898417a75bc48cdc79409dba2b415f692d7ea6 Mon Sep 17 00:00:00 2001
From: DreamMaoMao <2523610504@qq.com>
Date: Thu, 5 Feb 2026 11:06:40 +0800
Subject: [PATCH 47/47] opt: key name case insensitive in keybind
---
src/config/parse_config.h | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/config/parse_config.h b/src/config/parse_config.h
index a559432..b795a81 100644
--- a/src/config/parse_config.h
+++ b/src/config/parse_config.h
@@ -779,8 +779,9 @@ KeySymCode parse_key(const char *key_str, bool isbindsym) {
return kc;
}
- // 普通键名直接转换
- xkb_keysym_t sym = xkb_keysym_from_name(key_str, XKB_KEYSYM_NO_FLAGS);
+ // change key string to keysym, case insensitive
+ xkb_keysym_t sym =
+ xkb_keysym_from_name(key_str, XKB_KEYSYM_CASE_INSENSITIVE);
if (isbindsym) {
kc.type = KEY_TYPE_SYM;