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 001/170] 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 c2a7146168dfe4d122beec7e20f36d7742d81e1f Mon Sep 17 00:00:00 2001 From: werapi Date: Wed, 7 Jan 2026 15:46:25 +0100 Subject: [PATCH 002/170] fix: pointer events being one event behind --- src/mango.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/mango.c b/src/mango.c index 89ee23e..f4f7517 100644 --- a/src/mango.c +++ b/src/mango.c @@ -3966,19 +3966,6 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, struct wlr_pointer_constraint_v1 *constraint; bool should_lock = false; - /* Find the client under the pointer and send the event along. */ - xytonode(cursor->x, cursor->y, &surface, &c, NULL, &sx, &sy); - - if (cursor_mode == CurPressed && !seat->drag && - surface != seat->pointer_state.focused_surface && - toplevel_from_wlr_surface(seat->pointer_state.focused_surface, &w, - &l) >= 0) { - c = w; - surface = seat->pointer_state.focused_surface; - sx = cursor->x - (l ? l->scene->node.x : w->geom.x); - sy = cursor->y - (l ? l->scene->node.y : w->geom.y); - } - /* time is 0 in internal calls meant to restore pointer focus. */ if (time) { wlr_relative_pointer_manager_v1_send_relative_motion( @@ -4016,6 +4003,19 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, selmon = xytomon(cursor->x, cursor->y); } + /* Find the client under the pointer and send the event along. */ + xytonode(cursor->x, cursor->y, &surface, &c, NULL, &sx, &sy); + + if (cursor_mode == CurPressed && !seat->drag && + surface != seat->pointer_state.focused_surface && + toplevel_from_wlr_surface(seat->pointer_state.focused_surface, &w, + &l) >= 0) { + c = w; + surface = seat->pointer_state.focused_surface; + sx = cursor->x - (l ? l->scene->node.x : w->geom.x); + sy = cursor->y - (l ? l->scene->node.y : w->geom.y); + } + /* Update drag icon's position */ wlr_scene_node_set_position(&drag_icon->node, (int32_t)round(cursor->x), (int32_t)round(cursor->y)); 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 003/170] 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 004/170] 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 005/170] 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. + +image + + +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 006/170] 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 007/170] 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 008/170] =?UTF-8?q?fix:=20fakefullscreen=20toggle=20from?= =?UTF-8?q?=20fullscreen=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 009/170] 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 010/170] 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 011/170] 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 012/170] 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 013/170] 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 014/170] 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 015/170] 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 016/170] 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 017/170] 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 018/170] 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 019/170] 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 020/170] 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 021/170] 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 022/170] 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 023/170] 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 024/170] 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 025/170] 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 026/170] 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 027/170] 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 028/170] 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 029/170] 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 030/170] 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 031/170] 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 032/170] 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 033/170] 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 034/170] 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 035/170] 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 036/170] 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 037/170] 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 038/170] 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 039/170] 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 040/170] 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 041/170] 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 042/170] 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 043/170] 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 044/170] 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 045/170] 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 046/170] 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 047/170] 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 048/170] 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; From 00a7e579c94bd786cf1fd379b5b666e475dd4735 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 6 Feb 2026 07:43:09 +0800 Subject: [PATCH 049/170] fix: miss reset root color when reload config --- src/config/parse_config.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index b795a81..7e24d43 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -3558,6 +3558,8 @@ void reapply_cursor_style(void) { } } +void reapply_rootbg(void) { wlr_scene_rect_set_color(root_bg, rootcolor); } + void reapply_border(void) { Client *c = NULL; @@ -3683,6 +3685,7 @@ void reset_option(void) { reapply_cursor_style(); reapply_border(); + reapply_rootbg(); reapply_keyboard(); reapply_pointer(); reapply_master(); From 080dfaa3ae41cca9d02df9a529ed6a65edb97b41 Mon Sep 17 00:00:00 2001 From: Talmed Date: Fri, 6 Feb 2026 19:04:16 +1100 Subject: [PATCH 050/170] Add DesktopNames entry to mango.desktop --- mango.desktop | 1 + 1 file changed, 1 insertion(+) diff --git a/mango.desktop b/mango.desktop index 0c109ce..37c4fad 100644 --- a/mango.desktop +++ b/mango.desktop @@ -1,6 +1,7 @@ [Desktop Entry] Encoding=UTF-8 Name=Mango +DesktopNames=mango;wlroots Comment=mango WM Exec=mango Icon=mango From 818652d20f43598b245199cac9329edebbbbbae4 Mon Sep 17 00:00:00 2001 From: Lin Xianyi Date: Sat, 7 Feb 2026 12:12:13 +0800 Subject: [PATCH 051/170] fix: -p exits with failure if there are parse failures --- src/config/parse_config.h | 25 +++++++++++++++---------- src/mango.c | 3 +-- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 7e24d43..b8426e2 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -362,7 +362,7 @@ typedef struct { typedef int32_t (*FuncType)(const Arg *); Config config; -void parse_config_file(Config *config, const char *file_path); +bool parse_config_file(Config *config, const char *file_path); // Helper function to trim whitespace from start and end of a string void trim_whitespace(char *str) { @@ -2639,7 +2639,7 @@ bool parse_config_line(Config *config, const char *line) { return parse_option(config, key, value); } -void parse_config_file(Config *config, const char *file_path) { +bool parse_config_file(Config *config, const char *file_path) { FILE *file; char full_path[1024]; @@ -2658,7 +2658,7 @@ void parse_config_file(Config *config, const char *file_path) { fprintf(stderr, "\033[1m\033[31m[ERROR]:\033[33m HOME environment " "variable not set.\n"); - return; + return false; } snprintf(full_path, sizeof(full_path), "%s/.config/mango/%s", home, file_path + 1); @@ -2673,7 +2673,7 @@ void parse_config_file(Config *config, const char *file_path) { if (!home) { fprintf(stderr, "\033[1m\033[31m[ERROR]:\033[33m HOME environment " "variable not set.\n"); - return; + return false; } snprintf(full_path, sizeof(full_path), "%s%s", home, file_path + 1); file = fopen(full_path, "r"); @@ -2688,19 +2688,21 @@ void parse_config_file(Config *config, const char *file_path) { "\033[1;31m\033[1;33m[ERROR]:\033[0m Failed to open " "config file: %s\n", file_path); - return; + return false; } char line[512]; bool parse_correct = true; + bool parse_line_correct = true; uint32_t line_count = 0; while (fgets(line, sizeof(line), file)) { line_count++; if (line[0] == '#' || line[0] == '\n') { continue; } - parse_correct = parse_config_line(config, line); - if (!parse_correct) { + parse_line_correct = parse_config_line(config, line); + if (!parse_line_correct) { + parse_correct = false; fprintf(stderr, "\033[1;31m╰─\033[1;33m[Index]\033[0m " "\033[1;36m%s\033[0m:\033[1;35m%d\033[0m\n" @@ -2710,6 +2712,7 @@ void parse_config_file(Config *config, const char *file_path) { } fclose(file); + return parse_correct; } void free_circle_layout(Config *config) { @@ -3369,7 +3372,7 @@ void set_default_key_bindings(Config *config) { config->key_bindings_count += default_key_bindings_count; } -void parse_config(void) { +bool parse_config(void) { char filename[1024]; @@ -3422,7 +3425,7 @@ void parse_config(void) { const char *homedir = getenv("HOME"); if (!homedir) { // 如果获取失败,则无法继续 - return; + return false; } // 构建日志文件路径 snprintf(filename, sizeof(filename), "%s/.config/mango/config.conf", @@ -3436,10 +3439,12 @@ void parse_config(void) { } } + bool parse_correct = true; set_value_default(); - parse_config_file(&config, filename); + parse_correct = parse_config_file(&config, filename); set_default_key_bindings(&config); override_config(); + return parse_correct; } void reset_blur_params(void) { diff --git a/src/mango.c b/src/mango.c index 591d4f6..6b8a390 100644 --- a/src/mango.c +++ b/src/mango.c @@ -6168,8 +6168,7 @@ int32_t main(int32_t argc, char *argv[]) { } else if (c == 'c') { cli_config_path = optarg; } else if (c == 'p') { - parse_config(); - return EXIT_SUCCESS; + return parse_config() ? EXIT_SUCCESS : EXIT_FAILURE; } else { goto usage; } From 2734d91e6aec9992d0647b447cd0bf23f2bda84a Mon Sep 17 00:00:00 2001 From: Lin Xianyi Date: Sat, 7 Feb 2026 13:31:30 +0800 Subject: [PATCH 052/170] fix: improve help message for mmsg --- mmsg/mmsg.c | 47 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/mmsg/mmsg.c b/mmsg/mmsg.c index ba073aa..ab87b2b 100644 --- a/mmsg/mmsg.c +++ b/mmsg/mmsg.c @@ -500,12 +500,47 @@ static const struct wl_registry_listener registry_listener = { static void usage(void) { fprintf(stderr, - "usage:" - "\t%s [-OTLq]\n" - "\t%s [-o ] -s [-t ] [-l ] [-c ] [-d " - ",,,,,]\n" - "\t%s [-o ] (-g | -w) [-OotlcvmfxekbA]\n", - argv0, argv0, argv0); + "mmsg - MangoWC IPC\n" + "\n" + "SYNOPSIS:\n" + "\tmmsg [-OTLq]\n" + "\tmmsg [-o ] -s [-t ] [-l ] [-c ] [-d ,,,,,]\n" + "\tmmsg [-o ] (-g | -w) [-OotlcvmfxekbA]\n" + "\n" + "OPERATION MODES:\n" + "\t-g Get values (tags, layout, focused client)\n" + "\t-s Set values (switch tags, layouts)\n" + "\t-w Watch mode (stream events)\n" + "\n" + "GENERAL OPTIONS:\n" + "\t-O Get all output (monitor) information\n" + "\t-T Get number of tags\n" + "\t-L Get all available layouts\n" + "\t-q Quit MangoWC\n" + "\t-o Select output (monitor)\n" + "\n" + "GET OPTIONS (used with -g or -w):\n" + "\t-O Get output name\n" + "\t-o Get output (monitor) focus information\n" + "\t-t Get selected tags\n" + "\t-l Get current layout\n" + "\t-c Get title and appid of focused clients\n" + "\t-v Get visibility of statusbar\n" + "\t-m Get fullscreen status\n" + "\t-f Get floating status\n" + "\t-x Get focused client geometry\n" + "\t-e Get name of last focused layer\n" + "\t-k Get current keyboard layout\n" + "\t-b Get current keybind mode\n" + "\t-A Get scale factor of monitor\n" + "\n" + "SET OPTIONS (used with -s):\n" + "\t-o Select output (monitor)\n" + "\t-t Set selected tags (can be used with [+-^.] modifiers)\n" + "\t-l Set current layout\n" + "\t-c Get title and appid of focused client\n" + "\t-d , Dispatch internal command (max 5 args)\n" + ); exit(2); } From f8dfeedff1f88e16338c392f50baa7f4f965726e Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 7 Feb 2026 14:04:42 +0800 Subject: [PATCH 053/170] fix: headless backend cant use keybarod and pointer --- src/mango.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/mango.c b/src/mango.c index 6b8a390..dcd2753 100644 --- a/src/mango.c +++ b/src/mango.c @@ -3419,7 +3419,9 @@ void requestmonstate(struct wl_listener *listener, void *data) { void inputdevice(struct wl_listener *listener, void *data) { /* This event is raised by the backend when a new input device becomes - * available. */ + * available. + * when the backend is a headless backend, this event will never be triggered. + */ struct wlr_input_device *device = data; uint32_t caps; @@ -5973,6 +5975,8 @@ void handle_keyboard_shortcuts_inhibit_new_inhibitor( void virtualkeyboard(struct wl_listener *listener, void *data) { struct wlr_virtual_keyboard_v1 *kb = data; /* virtual keyboards shouldn't share keyboard group */ + wlr_seat_set_capabilities(seat, + seat->capabilities | WL_SEAT_CAPABILITY_KEYBOARD); KeyboardGroup *group = createkeyboardgroup(); /* Set the keymap to match the group keymap */ wlr_keyboard_set_keymap(&kb->keyboard, group->wlr_group->keyboard.keymap); @@ -6003,7 +6007,8 @@ void warp_cursor_to_selmon(Monitor *m) { void virtualpointer(struct wl_listener *listener, void *data) { struct wlr_virtual_pointer_v1_new_pointer_event *event = data; struct wlr_input_device *device = &event->new_pointer->pointer.base; - + wlr_seat_set_capabilities(seat, + seat->capabilities | WL_SEAT_CAPABILITY_POINTER); wlr_cursor_attach_input_device(cursor, device); if (event->suggested_output) wlr_cursor_map_input_to_output(cursor, device, event->suggested_output); From 3db2ac58b4220dcf1b2e5e2266ed661c4ea31578 Mon Sep 17 00:00:00 2001 From: Yujonpradhananga Date: Fri, 6 Feb 2026 15:18:31 +0545 Subject: [PATCH 054/170] opt: fix spelling mistake --- README.md | 2 +- src/animation/client.h | 74 +++++++++++++++++++++--------------------- src/client/client.h | 4 +-- 3 files changed, 40 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index d512f39..8aaaeb9 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ https://github.com/user-attachments/assets/014c893f-115c-4ae9-8342-f9ae3e9a0df0 - libxcb ## Arch Linux -The package is in the Arch User Repository and is availble for manual download [here](https://aur.archlinux.org/packages/mangowc-git) or through a AUR helper like yay: +The package is in the Arch User Repository and is available for manual download [here](https://aur.archlinux.org/packages/mangowc-git) or through a AUR helper like yay: ```bash yay -S mangowc-git diff --git a/src/animation/client.h b/src/animation/client.h index 8de9b06..2588fb1 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -47,7 +47,7 @@ bool is_horizontal_right_stack_layout(Monitor *m) { return false; } -int32_t is_special_animaiton_rule(Client *c) { +int32_t is_special_animation_rule(Client *c) { if (is_scroller_layout(c->mon) && !c->isfloating) { return DOWN; @@ -70,7 +70,7 @@ int32_t is_special_animaiton_rule(Client *c) { } } -void set_client_open_animaiton(Client *c, struct wlr_box geo) { +void set_client_open_animation(Client *c, struct wlr_box geo) { int32_t slide_direction; int32_t horizontal, horizontal_value; int32_t vertical, vertical_value; @@ -96,7 +96,7 @@ void set_client_open_animaiton(Client *c, struct wlr_box geo) { geo.y + (geo.height - c->animainit_geom.height) / 2; return; } else { - special_direction = is_special_animaiton_rule(c); + special_direction = is_special_animation_rule(c); center_x = c->geom.x + c->geom.width / 2; center_y = c->geom.y + c->geom.height / 2; if (special_direction == UNDIR) { @@ -772,71 +772,71 @@ void init_fadeout_client(Client *c) { return; } - Client *fadeout_cient = ecalloc(1, sizeof(*fadeout_cient)); + Client *fadeout_client = ecalloc(1, sizeof(*fadeout_client)); wlr_scene_node_set_enabled(&c->scene->node, true); client_set_border_color(c, bordercolor); - fadeout_cient->scene = + fadeout_client->scene = wlr_scene_tree_snapshot(&c->scene->node, layers[LyrFadeOut]); wlr_scene_node_set_enabled(&c->scene->node, false); - if (!fadeout_cient->scene) { - free(fadeout_cient); + if (!fadeout_client->scene) { + free(fadeout_client); return; } - fadeout_cient->animation.duration = animation_duration_close; - fadeout_cient->geom = fadeout_cient->current = - fadeout_cient->animainit_geom = fadeout_cient->animation.initial = + fadeout_client->animation.duration = animation_duration_close; + fadeout_client->geom = fadeout_client->current = + fadeout_client->animainit_geom = fadeout_client->animation.initial = c->animation.current; - fadeout_cient->mon = c->mon; - fadeout_cient->animation_type_close = c->animation_type_close; - fadeout_cient->animation.action = CLOSE; - fadeout_cient->bw = c->bw; - fadeout_cient->nofadeout = c->nofadeout; + fadeout_client->mon = c->mon; + fadeout_client->animation_type_close = c->animation_type_close; + fadeout_client->animation.action = CLOSE; + fadeout_client->bw = c->bw; + fadeout_client->nofadeout = c->nofadeout; // 这里snap节点的坐标设置是使用的相对坐标,所以不能加上原来坐标 // 这跟普通node有区别 - fadeout_cient->animation.initial.x = 0; - fadeout_cient->animation.initial.y = 0; + fadeout_client->animation.initial.x = 0; + fadeout_client->animation.initial.y = 0; if ((!c->animation_type_close && strcmp(animation_type_close, "fade") == 0) || (c->animation_type_close && strcmp(c->animation_type_close, "fade") == 0)) { - fadeout_cient->current.x = 0; - fadeout_cient->current.y = 0; - fadeout_cient->current.width = 0; - fadeout_cient->current.height = 0; + fadeout_client->current.x = 0; + fadeout_client->current.y = 0; + fadeout_client->current.width = 0; + fadeout_client->current.height = 0; } else if ((c->animation_type_close && strcmp(c->animation_type_close, "slide") == 0) || (!c->animation_type_close && strcmp(animation_type_close, "slide") == 0)) { - fadeout_cient->current.y = + fadeout_client->current.y = c->geom.y + c->geom.height / 2 > c->mon->m.y + c->mon->m.height / 2 ? c->mon->m.height - (c->animation.current.y - c->mon->m.y) // down out : c->mon->m.y - c->geom.height; // up out - fadeout_cient->current.x = 0; // x无偏差,垂直划出 + fadeout_client->current.x = 0; // x无偏差,垂直划出 } else { - fadeout_cient->current.y = - (fadeout_cient->geom.height - - fadeout_cient->geom.height * zoom_end_ratio) / + fadeout_client->current.y = + (fadeout_client->geom.height - + fadeout_client->geom.height * zoom_end_ratio) / 2; - fadeout_cient->current.x = - (fadeout_cient->geom.width - - fadeout_cient->geom.width * zoom_end_ratio) / + fadeout_client->current.x = + (fadeout_client->geom.width - + fadeout_client->geom.width * zoom_end_ratio) / 2; - fadeout_cient->current.width = - fadeout_cient->geom.width * zoom_end_ratio; - fadeout_cient->current.height = - fadeout_cient->geom.height * zoom_end_ratio; + fadeout_client->current.width = + fadeout_client->geom.width * zoom_end_ratio; + fadeout_client->current.height = + fadeout_client->geom.height * zoom_end_ratio; } - fadeout_cient->animation.time_started = get_now_in_ms(); - wlr_scene_node_set_enabled(&fadeout_cient->scene->node, true); - wl_list_insert(&fadeout_clients, &fadeout_cient->fadeout_link); + fadeout_client->animation.time_started = get_now_in_ms(); + wlr_scene_node_set_enabled(&fadeout_client->scene->node, true); + wl_list_insert(&fadeout_clients, &fadeout_client->fadeout_link); // 请求刷新屏幕 request_fresh_all_monitors(); @@ -971,7 +971,7 @@ void resize(Client *c, struct wlr_box geo, int32_t interact) { c->animainit_geom.height = c->animation.current.height; c->animainit_geom.width = c->animation.current.width; } else if (c->is_pending_open_animation) { - set_client_open_animaiton(c, c->geom); + set_client_open_animation(c, c->geom); } else { c->animainit_geom = c->animation.current; } diff --git a/src/client/client.h b/src/client/client.h index 8995a5d..49ab398 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -254,7 +254,7 @@ static inline int32_t client_is_stopped(Client *c) { wl_client_get_credentials(c->surface.xdg->client->client, &pid, NULL, NULL); if (waitid(P_PID, pid, &in, WNOHANG | WCONTINUED | WSTOPPED | WNOWAIT) < 0) { - /* This process is not our child process, while is very unluckely that + /* This process is not our child process, while is very unlikely that * it is stopped, in order to do not skip frames assume that it is. */ if (errno == ECHILD) return 1; @@ -547,4 +547,4 @@ static inline void client_set_size_bound(Client *c) { state.max_height > 0) { c->geom.height = state.max_height + 2 * c->bw; } -} \ No newline at end of file +} From e658f5c90d7e5337bd90cf818bc7428cae44bcd3 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 7 Feb 2026 19:57:07 +0800 Subject: [PATCH 055/170] opt: fix format --- mmsg/mmsg.c | 9 +++++---- src/mango.c | 5 +++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/mmsg/mmsg.c b/mmsg/mmsg.c index ab87b2b..fb1d04f 100644 --- a/mmsg/mmsg.c +++ b/mmsg/mmsg.c @@ -504,7 +504,8 @@ static void usage(void) { "\n" "SYNOPSIS:\n" "\tmmsg [-OTLq]\n" - "\tmmsg [-o ] -s [-t ] [-l ] [-c ] [-d ,,,,,]\n" + "\tmmsg [-o ] -s [-t ] [-l ] [-c ] [-d " + ",,,,,]\n" "\tmmsg [-o ] (-g | -w) [-OotlcvmfxekbA]\n" "\n" "OPERATION MODES:\n" @@ -536,11 +537,11 @@ static void usage(void) { "\n" "SET OPTIONS (used with -s):\n" "\t-o Select output (monitor)\n" - "\t-t Set selected tags (can be used with [+-^.] modifiers)\n" + "\t-t Set selected tags (can be used with [+-^.] " + "modifiers)\n" "\t-l Set current layout\n" "\t-c Get title and appid of focused client\n" - "\t-d , Dispatch internal command (max 5 args)\n" - ); + "\t-d , Dispatch internal command (max 5 args)\n"); exit(2); } diff --git a/src/mango.c b/src/mango.c index dcd2753..8883181 100644 --- a/src/mango.c +++ b/src/mango.c @@ -3419,8 +3419,9 @@ void requestmonstate(struct wl_listener *listener, void *data) { void inputdevice(struct wl_listener *listener, void *data) { /* This event is raised by the backend when a new input device becomes - * available. - * when the backend is a headless backend, this event will never be triggered. + * available. + * when the backend is a headless backend, this event will never be + * triggered. */ struct wlr_input_device *device = data; uint32_t caps; From 9b92f139c06d319d9fc6066ee194b967b672e369 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 7 Feb 2026 21:46:18 +0800 Subject: [PATCH 056/170] fix: correct dmabuf version --- src/mango.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mango.c b/src/mango.c index 8883181..d75faed 100644 --- a/src/mango.c +++ b/src/mango.c @@ -5180,7 +5180,7 @@ void setup(void) { if (wlr_renderer_get_texture_formats(drw, WLR_BUFFER_CAP_DMABUF)) { wlr_drm_create(dpy, drw); wlr_scene_set_linux_dmabuf_v1( - scene, wlr_linux_dmabuf_v1_create_with_renderer(dpy, 5, drw)); + scene, wlr_linux_dmabuf_v1_create_with_renderer(dpy, 4, drw)); } if (syncobj_enable && (drm_fd = wlr_renderer_get_drm_fd(drw)) >= 0 && From 5de87db8ca5870d37a181a4eaf4e8559f04397dc Mon Sep 17 00:00:00 2001 From: Lin Xianyi Date: Sun, 8 Feb 2026 10:46:18 +0800 Subject: [PATCH 057/170] feat: add a source-optional keyword --- src/config/parse_config.h | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index b8426e2..a795961 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -362,7 +362,7 @@ typedef struct { typedef int32_t (*FuncType)(const Arg *); Config config; -bool parse_config_file(Config *config, const char *file_path); +bool parse_config_file(Config *config, const char *file_path, bool must_exist); // Helper function to trim whitespace from start and end of a string void trim_whitespace(char *str) { @@ -2610,8 +2610,10 @@ bool parse_option(Config *config, char *key, char *value) { config->gesture_bindings_count++; } + } else if (strncmp(key, "source-optional", 15) == 0) { + parse_config_file(config, value, false); } else if (strncmp(key, "source", 6) == 0) { - parse_config_file(config, value); + parse_config_file(config, value, true); } else { fprintf(stderr, "\033[1m\033[31m[ERROR]:\033[33m Unknown keyword: " @@ -2639,7 +2641,7 @@ bool parse_config_line(Config *config, const char *line) { return parse_option(config, key, value); } -bool parse_config_file(Config *config, const char *file_path) { +bool parse_config_file(Config *config, const char *file_path, bool must_exist) { FILE *file; char full_path[1024]; @@ -2684,11 +2686,15 @@ bool parse_config_file(Config *config, const char *file_path) { } if (!file) { - fprintf(stderr, - "\033[1;31m\033[1;33m[ERROR]:\033[0m Failed to open " - "config file: %s\n", - file_path); - return false; + if (must_exist) { + fprintf(stderr, + "\033[1;31m\033[1;33m[ERROR]:\033[0m Failed to open " + "config file: %s\n", + file_path); + return false; + } else { + return true; + } } char line[512]; @@ -3441,7 +3447,7 @@ bool parse_config(void) { bool parse_correct = true; set_value_default(); - parse_correct = parse_config_file(&config, filename); + parse_correct = parse_config_file(&config, filename, true); set_default_key_bindings(&config); override_config(); return parse_correct; From 241afb4b9770a3bb7de2d28966a4298b059f61c0 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 8 Feb 2026 11:18:38 +0800 Subject: [PATCH 058/170] fix: warpcursor not apply in some case --- src/mango.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/mango.c b/src/mango.c index d75faed..5426281 100644 --- a/src/mango.c +++ b/src/mango.c @@ -5989,8 +5989,7 @@ void virtualkeyboard(struct wl_listener *listener, void *data) { } void warp_cursor(const Client *c) { - if (cursor->x < c->geom.x || cursor->x > c->geom.x + c->geom.width || - cursor->y < c->geom.y || cursor->y > c->geom.y + c->geom.height) { + if (INSIDEMON(c)) { wlr_cursor_warp_closest(cursor, NULL, c->geom.x + c->geom.width / 2.0, c->geom.y + c->geom.height / 2.0); motionnotify(0, NULL, 0, 0, 0, 0); From faf2e1e9da2290d08592f3f625640487b2eeb494 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 8 Feb 2026 12:13:04 +0800 Subject: [PATCH 059/170] opt: sync keymap to xwayland after xwayland ready --- src/dispatch/bind_define.h | 5 ----- src/mango.c | 16 +++++++++++++++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index c0e51dc..1d29bc9 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -907,9 +907,7 @@ int32_t switch_keyboard_layout(const Arg *arg) { uint32_t latched = keyboard->modifiers.latched; uint32_t locked = keyboard->modifiers.locked; - wlr_keyboard_set_keymap(keyboard, keyboard->keymap); wlr_keyboard_notify_modifiers(keyboard, depressed, latched, locked, next); - keyboard->modifiers.group = 0; // 7. 更新 seat wlr_seat_set_keyboard(seat, keyboard); @@ -923,10 +921,7 @@ int32_t switch_keyboard_layout(const Arg *arg) { struct wlr_keyboard *tkb = (struct wlr_keyboard *)id->device_data; - wlr_keyboard_set_keymap(tkb, keyboard->keymap); wlr_keyboard_notify_modifiers(tkb, depressed, latched, locked, next); - tkb->modifiers.group = 0; - // 7. 更新 seat wlr_seat_set_keyboard(seat, tkb); wlr_seat_keyboard_notify_modifiers(seat, &tkb->modifiers); diff --git a/src/mango.c b/src/mango.c index 5426281..6609270 100644 --- a/src/mango.c +++ b/src/mango.c @@ -730,6 +730,7 @@ static void set_rect_size(struct wlr_scene_rect *rect, int32_t width, static Client *center_tiled_select(Monitor *m); static void handlecursoractivity(void); static int32_t hidecursor(void *data); +static int32_t synckeymap(void *data); static bool check_hit_no_border(Client *c); static void reset_keyboard_layout(void); static void client_update_oldmonname_record(Client *c, Monitor *m); @@ -876,6 +877,7 @@ struct dvec2 *baked_points_opafadein; struct dvec2 *baked_points_opafadeout; static struct wl_event_source *hide_source; +static struct wl_event_source *sync_keymap; static bool cursor_hidden = false; static bool tag_combo = false; static const char *cli_config_path = NULL; @@ -5327,7 +5329,8 @@ void setup(void) { &request_set_cursor_shape); hide_source = wl_event_loop_add_timer(wl_display_get_event_loop(dpy), hidecursor, cursor); - + sync_keymap = wl_event_loop_add_timer(wl_display_get_event_loop(dpy), + synckeymap, NULL); /* * Configures a seat, which is a single "seat" at which a user sits and * operates the computer. This conceptually includes up to one keyboard, @@ -5556,6 +5559,13 @@ int32_t hidecursor(void *data) { return 1; } +int32_t synckeymap(void *data) { + reset_keyboard_layout(); + // we only need to sync keymap once + wl_event_source_timer_update(sync_keymap, 0); + return 1; +} + void unlocksession(struct wl_listener *listener, void *data) { SessionLock *lock = wl_container_of(listener, lock, unlock); destroylock(lock, 1); @@ -6147,6 +6157,10 @@ void xwaylandready(struct wl_listener *listener, void *data) { xwayland, xcursor->images[0]->buffer, xcursor->images[0]->width * 4, xcursor->images[0]->width, xcursor->images[0]->height, xcursor->images[0]->hotspot_x, xcursor->images[0]->hotspot_y); + /* xwayland can't auto sync the keymap, so we do it manually + and we need to wait the xwayland completely inited + */ + wl_event_source_timer_update(sync_keymap, 500); } static void setgeometrynotify(struct wl_listener *listener, void *data) { From 4820b7a8abe95c9319ccc76642607a7ab9e7992a Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 8 Feb 2026 12:15:44 +0800 Subject: [PATCH 060/170] feat: allow single mod keybind --- src/mango.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/mango.c b/src/mango.c index 6609270..e15b08f 100644 --- a/src/mango.c +++ b/src/mango.c @@ -3495,11 +3495,6 @@ keybinding(uint32_t state, bool locked, uint32_t mods, xkb_keysym_t sym, int32_t ji; int32_t isbreak = 0; - // not allow modifier keys to be used as a keybinding - if (keycode == 50 || keycode == 37 || keycode == 133 || keycode == 64 || - keycode == 62 || keycode == 108 || keycode == 105 || keycode == 134) - return false; - if (is_keyboard_shortcut_inhibitor(seat->keyboard_state.focused_surface)) { return false; } From e8bf6380fba8b7acf7b29f178d9dba8e3481eb16 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 8 Feb 2026 12:44:56 +0800 Subject: [PATCH 061/170] opt: turn keymap sync into XWAYLAND macro --- src/mango.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/mango.c b/src/mango.c index e15b08f..d6876c5 100644 --- a/src/mango.c +++ b/src/mango.c @@ -730,7 +730,6 @@ static void set_rect_size(struct wlr_scene_rect *rect, int32_t width, static Client *center_tiled_select(Monitor *m); static void handlecursoractivity(void); static int32_t hidecursor(void *data); -static int32_t synckeymap(void *data); static bool check_hit_no_border(Client *c); static void reset_keyboard_layout(void); static void client_update_oldmonname_record(Client *c, Monitor *m); @@ -877,7 +876,6 @@ struct dvec2 *baked_points_opafadein; struct dvec2 *baked_points_opafadeout; static struct wl_event_source *hide_source; -static struct wl_event_source *sync_keymap; static bool cursor_hidden = false; static bool tag_combo = false; static const char *cli_config_path = NULL; @@ -944,6 +942,7 @@ static struct wl_listener keyboard_shortcuts_inhibit_new_inhibitor = { .notify = handle_keyboard_shortcuts_inhibit_new_inhibitor}; #ifdef XWAYLAND +static int32_t synckeymap(void *data); static void activatex11(struct wl_listener *listener, void *data); static void configurex11(struct wl_listener *listener, void *data); static void createnotifyx11(struct wl_listener *listener, void *data); @@ -955,6 +954,7 @@ static void setgeometrynotify(struct wl_listener *listener, void *data); static struct wl_listener new_xwayland_surface = {.notify = createnotifyx11}; static struct wl_listener xwayland_ready = {.notify = xwaylandready}; static struct wlr_xwayland *xwayland; +static struct wl_event_source *sync_keymap; #endif #include "animation/client.h" @@ -5324,8 +5324,6 @@ void setup(void) { &request_set_cursor_shape); hide_source = wl_event_loop_add_timer(wl_display_get_event_loop(dpy), hidecursor, cursor); - sync_keymap = wl_event_loop_add_timer(wl_display_get_event_loop(dpy), - synckeymap, NULL); /* * Configures a seat, which is a single "seat" at which a user sits and * operates the computer. This conceptually includes up to one keyboard, @@ -5417,6 +5415,8 @@ void setup(void) { fprintf(stderr, "failed to setup XWayland X server, continuing without it\n"); } + sync_keymap = wl_event_loop_add_timer(wl_display_get_event_loop(dpy), + synckeymap, NULL); #endif } @@ -5554,13 +5554,6 @@ int32_t hidecursor(void *data) { return 1; } -int32_t synckeymap(void *data) { - reset_keyboard_layout(); - // we only need to sync keymap once - wl_event_source_timer_update(sync_keymap, 0); - return 1; -} - void unlocksession(struct wl_listener *listener, void *data) { SessionLock *lock = wl_container_of(listener, lock, unlock); destroylock(lock, 1); @@ -6022,6 +6015,13 @@ void virtualpointer(struct wl_listener *listener, void *data) { } #ifdef XWAYLAND +int32_t synckeymap(void *data) { + reset_keyboard_layout(); + // we only need to sync keymap once + wl_event_source_timer_update(sync_keymap, 0); + return 1; +} + void activatex11(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, activate); bool need_arrange = false; From 69703158223d53e4d40f43a055c6af7d5bc98b6f Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 8 Feb 2026 18:17:55 +0800 Subject: [PATCH 062/170] Update README.md --- README.md | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/README.md b/README.md index 8aaaeb9..b78ccfa 100644 --- a/README.md +++ b/README.md @@ -24,18 +24,7 @@ This project's development is based on [dwl](https://codeberg.org/dwl/dwl/). - Hycov-like overview - Window effects from scenefx (blur, shadow, corner radius, opacity) -Master-Stack Layout - -https://github.com/user-attachments/assets/a9d4776e-b50b-48fb-94ce-651d8a749b8a - -Scroller Layout - -https://github.com/user-attachments/assets/c9bf9415-fad1-4400-bcdc-3ad2d76de85a - -Layer animation - -https://github.com/user-attachments/assets/014c893f-115c-4ae9-8342-f9ae3e9a0df0 - +https://github.com/user-attachments/assets/bb83004a-0563-4b48-ad89-6461a9b78b1f # Our discord [mangowc](https://discord.gg/CPjbDxesh5) From 454145f6e03a462b743c64776d2b17121c2637ea Mon Sep 17 00:00:00 2001 From: Daniel Jampen Date: Sun, 8 Feb 2026 17:56:26 +0100 Subject: [PATCH 063/170] support isfakefullscreen as windowrule property --- src/config/parse_config.h | 4 ++++ src/mango.c | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 7e24d43..49936de 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -58,6 +58,7 @@ typedef struct { uint32_t tags; int32_t isfloating; int32_t isfullscreen; + int32_t isfakefullscreen; float scroller_proportion; const char *animation_type_open; const char *animation_type_close; @@ -1972,6 +1973,7 @@ bool parse_option(Config *config, char *key, char *value) { // int32_t rule value, relay to a client property rule->isfloating = -1; rule->isfullscreen = -1; + rule->isfakefullscreen = -1; rule->isnoborder = -1; rule->isnoshadow = -1; rule->isnoradius = -1; @@ -2111,6 +2113,8 @@ bool parse_option(Config *config, char *key, char *value) { rule->scroller_proportion = atof(val); } else if (strcmp(key, "isfullscreen") == 0) { rule->isfullscreen = atoi(val); + } else if (strcmp(key, "isfakefullscreen") == 0) { + rule->isfakefullscreen = atoi(val); } else if (strcmp(key, "globalkeybinding") == 0) { char mod_str[256], keysym_str[256]; sscanf(val, "%255[^-]-%255[a-zA-Z]", mod_str, keysym_str); diff --git a/src/mango.c b/src/mango.c index 591d4f6..8ec1b5b 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1289,6 +1289,7 @@ static void apply_rule_properties(Client *c, const ConfigWinRule *r) { APPLY_INT_PROP(c, r, no_force_center); APPLY_INT_PROP(c, r, isfloating); APPLY_INT_PROP(c, r, isfullscreen); + APPLY_INT_PROP(c, r, isfakefullscreen); APPLY_INT_PROP(c, r, isnoborder); APPLY_INT_PROP(c, r, isnoshadow); APPLY_INT_PROP(c, r, isnoradius); @@ -1482,6 +1483,10 @@ void applyrules(Client *c) { setfullscreen(c, fullscreen_state_backup); + if (c->isfakefullscreen) { + setfakefullscreen(c, 1); + } + /* if there is a new non-floating window in the current tag, the fullscreen window in the current tag will exit fullscreen and participate in tiling From 6b79a432a4e81e757775ce9edf89acc1b4859d74 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 9 Feb 2026 07:52:10 +0800 Subject: [PATCH 064/170] opt: allow none mode in some mouse button --- config.conf | 5 ++--- src/mango.c | 19 +++++++++++++------ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/config.conf b/config.conf index 6cbb3a2..15b654c 100644 --- a/config.conf +++ b/config.conf @@ -240,12 +240,11 @@ bind=CTRL+ALT,Left,resizewin,-50,+0 bind=CTRL+ALT,Right,resizewin,+50,+0 # Mouse Button Bindings -# NONE mode key only work in ov mode +# btn_left and btn_right can't bind none mod key mousebind=SUPER,btn_left,moveresize,curmove mousebind=NONE,btn_middle,togglemaximizescreen,0 mousebind=SUPER,btn_right,moveresize,curresize -mousebind=NONE,btn_left,toggleoverview,1 -mousebind=NONE,btn_right,killclient,0 + # Axis Bindings axisbind=SUPER,UP,viewtoleft_have_client diff --git a/src/mango.c b/src/mango.c index d6876c5..44f6728 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1998,14 +1998,21 @@ buttonpress(struct wl_listener *listener, void *data) { if (config.mouse_bindings_count < 1) break; m = &config.mouse_bindings[ji]; + + if (selmon->isoverview && event->button == BTN_LEFT && c) { + toggleoverview(&(Arg){.i = 1}); + return; + } + + if (selmon->isoverview && event->button == BTN_RIGHT && c) { + pending_kill_client(c); + return; + } + if (CLEANMASK(mods) == CLEANMASK(m->mod) && event->button == m->button && m->func && - (selmon->isoverview == 1 || m->button == BTN_MIDDLE) && c) { - m->func(&m->arg); - return; - } else if (CLEANMASK(mods) == CLEANMASK(m->mod) && - event->button == m->button && m->func && - CLEANMASK(m->mod) != 0) { + (CLEANMASK(m->mod) != 0 || + (event->button != BTN_LEFT && event->button != BTN_RIGHT))) { m->func(&m->arg); return; } From b05bc1ce65bdb32b9d2db10ef4e1514c34bf10dd Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 9 Feb 2026 11:50:54 +0800 Subject: [PATCH 065/170] opt: add btn_left and btn_right bind check in config check --- src/config/parse_config.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index a795961..1ab243c 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -2359,6 +2359,17 @@ bool parse_option(Config *config, char *key, char *value) { binding->arg.v = NULL; binding->arg.v2 = NULL; binding->arg.v3 = NULL; + + // TODO: remove this in next version + if (binding->mod == 0 && + (binding->button == BTN_LEFT || binding->button == BTN_RIGHT)) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m \033[31m%s\033[33m can't " + "bind to \033[31m%s\033[33m mod key\n", + button_str, mod_str); + return false; + } + binding->func = parse_func_name(func_name, &binding->arg, arg_value, arg_value2, arg_value3, arg_value4, arg_value5); @@ -2376,6 +2387,7 @@ bool parse_option(Config *config, char *key, char *value) { free(binding->arg.v3); binding->arg.v3 = NULL; } + if (!binding->func) fprintf(stderr, "\033[1m\033[31m[ERROR]:\033[33m Unknown " From c7f90cbc6901cd2bed2de079c75717052515ef87 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 9 Feb 2026 18:08:28 +0800 Subject: [PATCH 066/170] bump version to 0.12.1 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 0565609..478ef0e 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('mango', ['c', 'cpp'], - version : '0.12.0', + version : '0.12.1', ) subdir('protocols') From 783cb86c56f2c0c433df1e3cd4c0bdeb93db6973 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 10 Feb 2026 10:03:08 +0800 Subject: [PATCH 067/170] feat: support match monitor make model serial --- src/config/parse_config.h | 117 ++++++++++++++++++++++++++++++++++---- src/mango.c | 35 +++++++++++- 2 files changed, 140 insertions(+), 12 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 1ab243c..556fc35 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -101,13 +101,14 @@ typedef struct { } ConfigWinRule; typedef struct { - 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 + const char *name; // Monitor name + char *make, *model, *serial; // may be NULL + 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; // 修改后的宏定义 @@ -157,6 +158,9 @@ typedef struct { int32_t id; char *layout_name; char *monitor_name; + char *monitor_make; + char *monitor_model; + char *monitor_serial; float mfact; int32_t nmaster; int32_t no_render_border; @@ -1774,6 +1778,9 @@ bool parse_option(Config *config, char *key, char *value) { // 设置默认值 rule->name = NULL; + rule->make = NULL; + rule->model = NULL; + rule->serial = NULL; rule->rr = 0; rule->scale = 1.0f; rule->x = INT32_MAX; @@ -1797,6 +1804,12 @@ bool parse_option(Config *config, char *key, char *value) { if (strcmp(key, "name") == 0) { rule->name = strdup(val); + } else if (strcmp(key, "make") == 0) { + rule->make = strdup(val); + } else if (strcmp(key, "model") == 0) { + rule->model = strdup(val); + } else if (strcmp(key, "serial") == 0) { + rule->serial = strdup(val); } else if (strcmp(key, "rr") == 0) { rule->rr = CLAMP_INT(atoi(val), 0, 7); } else if (strcmp(key, "scale") == 0) { @@ -1845,6 +1858,9 @@ bool parse_option(Config *config, char *key, char *value) { rule->id = 0; rule->layout_name = NULL; rule->monitor_name = NULL; + rule->monitor_make = NULL; + rule->monitor_model = NULL; + rule->monitor_serial = NULL; rule->nmaster = 0; rule->mfact = 0.0f; rule->no_render_border = 0; @@ -1868,6 +1884,12 @@ bool parse_option(Config *config, char *key, char *value) { rule->layout_name = strdup(val); } else if (strcmp(key, "monitor_name") == 0) { rule->monitor_name = strdup(val); + } else if (strcmp(key, "monitor_make") == 0) { + rule->monitor_make = strdup(val); + } else if (strcmp(key, "monitor_model") == 0) { + rule->monitor_model = strdup(val); + } else if (strcmp(key, "monitor_serial") == 0) { + rule->monitor_serial = strdup(val); } else if (strcmp(key, "no_render_border") == 0) { rule->no_render_border = CLAMP_INT(atoi(val), 0, 1); } else if (strcmp(key, "no_hide") == 0) { @@ -2925,6 +2947,12 @@ void free_config(void) { free((void *)config.tag_rules[i].layout_name); if (config.tag_rules[i].monitor_name) free((void *)config.tag_rules[i].monitor_name); + if (config.tag_rules[i].monitor_make) + free((void *)config.tag_rules[i].monitor_make); + if (config.tag_rules[i].monitor_model) + free((void *)config.tag_rules[i].monitor_model); + if (config.tag_rules[i].monitor_serial) + free((void *)config.tag_rules[i].monitor_serial); } free(config.tag_rules); config.tag_rules = NULL; @@ -2936,6 +2964,12 @@ void free_config(void) { for (int32_t i = 0; i < config.monitor_rules_count; i++) { if (config.monitor_rules[i].name) free((void *)config.monitor_rules[i].name); + if (config.monitor_rules[i].make) + free((void *)config.monitor_rules[i].make); + if (config.monitor_rules[i].model) + free((void *)config.monitor_rules[i].model); + if (config.monitor_rules[i].serial) + free((void *)config.monitor_rules[i].serial); } free(config.monitor_rules); config.monitor_rules = NULL; @@ -3500,6 +3534,7 @@ void reapply_monitor_rules(void) { struct wlr_output_state state; struct wlr_output_mode *internal_mode = NULL; wlr_output_state_init(&state); + bool match_rule = false; wl_list_for_each(m, &mons, link) { if (!m->wlr_output->enabled) { @@ -3511,8 +3546,40 @@ void reapply_monitor_rules(void) { break; mr = &config.monitor_rules[ji]; - if (regex_match(mr->name, m->wlr_output->name)) { + // 检查是否匹配的变量 + match_rule = true; + + // 检查四个标识字段的匹配 + if (mr->name != NULL) { + if (!regex_match(mr->name, m->wlr_output->name)) { + match_rule = false; + } + } + + if (mr->make != NULL) { + if (m->wlr_output->make == NULL || + strcmp(mr->make, m->wlr_output->make) != 0) { + match_rule = false; + } + } + + if (mr->model != NULL) { + if (m->wlr_output->model == NULL || + strcmp(mr->model, m->wlr_output->model) != 0) { + match_rule = false; + } + } + + if (mr->serial != NULL) { + if (m->wlr_output->serial == NULL || + strcmp(mr->serial, m->wlr_output->serial) != 0) { + match_rule = false; + } + } + + // 只有当所有指定的标识都匹配时才应用规则 + if (match_rule) { 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; @@ -3646,6 +3713,7 @@ void parse_tagrule(Monitor *m) { int32_t i, jk; ConfigTagRule tr; Client *c = NULL; + bool match_rule = false; for (i = 0; i <= LENGTH(tags); i++) { m->pertag->nmasters[i] = default_nmaster; @@ -3656,9 +3724,36 @@ void parse_tagrule(Monitor *m) { tr = config.tag_rules[i]; - if (config.tag_rules_count > 0 && - (!tr.monitor_name || - regex_match(tr.monitor_name, m->wlr_output->name))) { + match_rule = true; + + if (tr.monitor_name != NULL) { + if (!regex_match(tr.monitor_name, m->wlr_output->name)) { + match_rule = false; + } + } + + if (tr.monitor_make != NULL) { + if (m->wlr_output->make == NULL || + strcmp(tr.monitor_make, m->wlr_output->make) != 0) { + match_rule = false; + } + } + + if (tr.monitor_model != NULL) { + if (m->wlr_output->model == NULL || + strcmp(tr.monitor_model, m->wlr_output->model) != 0) { + match_rule = false; + } + } + + if (tr.monitor_serial != NULL) { + if (m->wlr_output->serial == NULL || + strcmp(tr.monitor_serial, m->wlr_output->serial) != 0) { + match_rule = false; + } + } + + if (config.tag_rules_count > 0 && match_rule) { for (jk = 0; jk < LENGTH(layouts); jk++) { if (tr.layout_name && diff --git a/src/mango.c b/src/mango.c index 44f6728..495cba0 100644 --- a/src/mango.c +++ b/src/mango.c @@ -2737,6 +2737,7 @@ void createmon(struct wl_listener *listener, void *data) { Monitor *m = NULL; struct wlr_output_mode *internal_mode = NULL; bool custom_monitor_mode = false; + bool match_rule = false; if (!wlr_output_init_render(wlr_output, alloc, drw)) return; @@ -2777,7 +2778,39 @@ void createmon(struct wl_listener *listener, void *data) { break; r = &config.monitor_rules[ji]; - if (regex_match(r->name, wlr_output->name)) { + + // 检查是否匹配的变量 + match_rule = true; + + // 检查四个标识字段的匹配 + if (r->name != NULL) { + if (!regex_match(r->name, m->wlr_output->name)) { + match_rule = false; + } + } + + if (r->make != NULL) { + if (m->wlr_output->make == NULL || + strcmp(r->make, m->wlr_output->make) != 0) { + match_rule = false; + } + } + + if (r->model != NULL) { + if (m->wlr_output->model == NULL || + strcmp(r->model, m->wlr_output->model) != 0) { + match_rule = false; + } + } + + if (r->serial != NULL) { + if (m->wlr_output->serial == NULL || + strcmp(r->serial, m->wlr_output->serial) != 0) { + match_rule = false; + } + } + + if (match_rule) { 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; From b5a157038caea46fc1fb710cf7d93a093db0cd25 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 11 Feb 2026 08:31:22 +0800 Subject: [PATCH 068/170] opt: tell the synckeymap timer not need to call anymore --- src/mango.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mango.c b/src/mango.c index 495cba0..31d38ea 100644 --- a/src/mango.c +++ b/src/mango.c @@ -6058,8 +6058,9 @@ void virtualpointer(struct wl_listener *listener, void *data) { int32_t synckeymap(void *data) { reset_keyboard_layout(); // we only need to sync keymap once + wlr_log(WLR_INFO, "timer to synckeymap done"); wl_event_source_timer_update(sync_keymap, 0); - return 1; + return 0; } void activatex11(struct wl_listener *listener, void *data) { From 8484093e324a5e305e35dbf4c369d9065a0ad26c Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 11 Feb 2026 20:44:44 +0800 Subject: [PATCH 069/170] fix: crash when pointerfocus to a null scene client --- src/mango.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mango.c b/src/mango.c index 31d38ea..3ce7422 100644 --- a/src/mango.c +++ b/src/mango.c @@ -4322,7 +4322,7 @@ void pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, uint32_t time) { struct timespec now; - if (sloppyfocus && !start_drag_window && c && time && + if (sloppyfocus && !start_drag_window && c && time && c->scene && c->scene->node.enabled && !c->animation.tagining && (surface != seat->pointer_state.focused_surface) && !client_is_unmanaged(c) && VISIBLEON(c, c->mon)) From 53ee82a726ba1974ddfb67e182cec486678bbf1b Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 12 Feb 2026 11:19:39 +0800 Subject: [PATCH 070/170] feat: make force_tiled_state as a option --- src/config/parse_config.h | 4 ++++ src/mango.c | 27 ++++++++++++++++++++++----- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 556fc35..7e6db02 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -89,6 +89,7 @@ typedef struct { int32_t isterm; int32_t allow_csd; int32_t force_maximize; + int32_t force_tiled_state; int32_t force_tearing; int32_t noswallow; int32_t noblur; @@ -2011,6 +2012,7 @@ bool parse_option(Config *config, char *key, char *value) { rule->isterm = -1; rule->allow_csd = -1; rule->force_maximize = -1; + rule->force_tiled_state = -1; rule->force_tearing = -1; rule->noswallow = -1; rule->noblur = -1; @@ -2123,6 +2125,8 @@ bool parse_option(Config *config, char *key, char *value) { rule->allow_csd = atoi(val); } else if (strcmp(key, "force_maximize") == 0) { rule->force_maximize = atoi(val); + } else if (strcmp(key, "force_tiled_state") == 0) { + rule->force_tiled_state = atoi(val); } else if (strcmp(key, "force_tearing") == 0) { rule->force_tearing = atoi(val); } else if (strcmp(key, "noswallow") == 0) { diff --git a/src/mango.c b/src/mango.c index 3ce7422..63f0e27 100644 --- a/src/mango.c +++ b/src/mango.c @@ -3,6 +3,7 @@ */ #include "wlr-layer-shell-unstable-v1-protocol.h" #include "wlr/util/box.h" +#include "wlr/util/edges.h" #include #include #include @@ -390,6 +391,7 @@ struct Client { int32_t isterm, noswallow; int32_t allow_csd; int32_t force_maximize; + int32_t force_tiled_state; pid_t pid; Client *swallowing, *swallowedby; bool is_clip_to_hide; @@ -1283,6 +1285,7 @@ static void apply_rule_properties(Client *c, const ConfigWinRule *r) { APPLY_INT_PROP(c, r, isterm); APPLY_INT_PROP(c, r, allow_csd); APPLY_INT_PROP(c, r, force_maximize); + APPLY_INT_PROP(c, r, force_tiled_state); APPLY_INT_PROP(c, r, force_tearing); APPLY_INT_PROP(c, r, noswallow); APPLY_INT_PROP(c, r, nofocus); @@ -2438,9 +2441,6 @@ void commitnotify(struct wl_listener *listener, void *data) { setmon(c, NULL, 0, true); /* Make sure to reapply rules in mapnotify() */ - client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | - WLR_EDGE_RIGHT); - uint32_t serial = wlr_xdg_surface_schedule_configure(c->surface.xdg); if (serial > 0) { c->configure_serial = serial; @@ -3856,6 +3856,7 @@ void init_client_properties(Client *c) { c->isterm = 0; c->allow_csd = 0; c->force_maximize = 0; + c->force_tiled_state = 1; c->force_tearing = 0; c->allow_shortcuts_inhibit = SHORTCUTS_INHIBIT_ENABLE; c->scroller_proportion_single = 0.0f; @@ -3970,8 +3971,10 @@ mapnotify(struct wl_listener *listener, void *data) { applyrules(c); - client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | - WLR_EDGE_RIGHT); + if (!c->isfloating || c->force_tiled_state) { + client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | + WLR_EDGE_RIGHT); + } // apply buffer effects of client wlr_scene_node_for_each_buffer(&c->scene_surface->node, @@ -4796,6 +4799,13 @@ setfloating(Client *c, int32_t floating) { if (!c->force_maximize) client_set_maximized(c, false); + if (!c->isfloating || c->force_tiled_state) { + client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | + WLR_EDGE_RIGHT); + } else { + client_set_tiled(c, WLR_EDGE_NONE); + } + arrange(c->mon, false, false); setborder_color(c); printstatus(); @@ -5531,6 +5541,9 @@ void overview_backup(Client *c) { c->ismaximizescreen = 0; } c->bw = c->isnoborder ? 0 : borderpx; + + client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | + WLR_EDGE_RIGHT); } // overview切回到普通视图还原窗口的状态 @@ -5570,6 +5583,10 @@ void overview_restore(Client *c, const Arg *arg) { !c->isfullscreen) { // 如果是在ov模式中创建的窗口,没有bw记录 c->bw = c->isnoborder ? 0 : borderpx; } + + if (c->isfloating && !c->force_tiled_state) { + client_set_tiled(c, WLR_EDGE_NONE); + } } void handlecursoractivity(void) { From bc52b95c1ef885127c1aa1a63c847db9d2b0c818 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 12 Feb 2026 16:38:19 +0800 Subject: [PATCH 071/170] opt: make x11 unmanaged window coordinate auto ajust the monitor change --- src/mango.c | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/src/mango.c b/src/mango.c index 63f0e27..e84c498 100644 --- a/src/mango.c +++ b/src/mango.c @@ -944,6 +944,7 @@ static struct wl_listener keyboard_shortcuts_inhibit_new_inhibitor = { .notify = handle_keyboard_shortcuts_inhibit_new_inhibitor}; #ifdef XWAYLAND +static void fix_xwayland_unmanaged_coordinate(struct wlr_box *box); static int32_t synckeymap(void *data); static void activatex11(struct wl_listener *listener, void *data); static void configurex11(struct wl_listener *listener, void *data); @@ -3914,18 +3915,19 @@ mapnotify(struct wl_listener *listener, void *data) { */ if (client_is_unmanaged(c)) { /* Unmanaged clients always are floating */ +#ifdef XWAYLAND + if (client_is_x11(c)) { + fix_xwayland_unmanaged_coordinate(&c->geom); + LISTEN(&c->surface.xwayland->events.set_geometry, &c->set_geometry, + setgeometrynotify); + } +#endif wlr_scene_node_reparent(&c->scene->node, layers[LyrOverlay]); wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); if (client_wants_focus(c)) { focusclient(c, 1); exclusive_focus = c; } -#ifdef XWAYLAND - if (client_is_x11(c)) { - LISTEN(&c->surface.xwayland->events.set_geometry, &c->set_geometry, - setgeometrynotify); - } -#endif return; } @@ -6072,6 +6074,25 @@ void virtualpointer(struct wl_listener *listener, void *data) { } #ifdef XWAYLAND +void fix_xwayland_unmanaged_coordinate(struct wlr_box *box) { + if (!selmon) + return; + if (box->x >= selmon->m.x && box->x <= selmon->m.x + selmon->m.width && + box->y >= selmon->m.y && box->y <= selmon->m.y + selmon->m.height) + return; + + Monitor *source_monitor = xytomon(box->x, box->y); + + if (!source_monitor) + return; + + int xoffset = box->x - source_monitor->m.x; + int yoffset = box->y - source_monitor->m.y; + + box->x = selmon->m.x + xoffset; + box->y = selmon->m.y + yoffset; +} + int32_t synckeymap(void *data) { reset_keyboard_layout(); // we only need to sync keymap once From 17acdae69c2fbda70dfdd22f0cfa268dc75ba5c9 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 12 Feb 2026 18:12:01 +0800 Subject: [PATCH 072/170] opt: make x11 floating window coordinate auto ajust the monitor change --- src/mango.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/mango.c b/src/mango.c index e84c498..8633dcd 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1375,6 +1375,14 @@ void applyrules(Client *c) { Monitor *mon = parent && parent->mon ? parent->mon : selmon; c->isfloating = client_is_float_type(c) || parent; + +#ifdef XWAYLAND + if (c->isfloating && client_is_x11(c)) { + fix_xwayland_unmanaged_coordinate(&c->geom); + c->float_geom = c->geom; + } +#endif + if (!(appid = client_get_appid(c))) appid = broken; if (!(title = client_get_title(c))) From 313adefd10c6de9671aef85fc715df3f7564cf21 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 12 Feb 2026 18:44:56 +0800 Subject: [PATCH 073/170] opt: better x11 coordinate adjust --- src/mango.c | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/mango.c b/src/mango.c index 8633dcd..ca25dbc 100644 --- a/src/mango.c +++ b/src/mango.c @@ -944,7 +944,7 @@ static struct wl_listener keyboard_shortcuts_inhibit_new_inhibitor = { .notify = handle_keyboard_shortcuts_inhibit_new_inhibitor}; #ifdef XWAYLAND -static void fix_xwayland_unmanaged_coordinate(struct wlr_box *box); +static void fix_xwayland_unmanaged_coordinate(Client *c); static int32_t synckeymap(void *data); static void activatex11(struct wl_listener *listener, void *data); static void configurex11(struct wl_listener *listener, void *data); @@ -1378,7 +1378,7 @@ void applyrules(Client *c) { #ifdef XWAYLAND if (c->isfloating && client_is_x11(c)) { - fix_xwayland_unmanaged_coordinate(&c->geom); + fix_xwayland_unmanaged_coordinate(c); c->float_geom = c->geom; } #endif @@ -3925,7 +3925,7 @@ mapnotify(struct wl_listener *listener, void *data) { /* Unmanaged clients always are floating */ #ifdef XWAYLAND if (client_is_x11(c)) { - fix_xwayland_unmanaged_coordinate(&c->geom); + fix_xwayland_unmanaged_coordinate(c); LISTEN(&c->surface.xwayland->events.set_geometry, &c->set_geometry, setgeometrynotify); } @@ -6082,23 +6082,16 @@ void virtualpointer(struct wl_listener *listener, void *data) { } #ifdef XWAYLAND -void fix_xwayland_unmanaged_coordinate(struct wlr_box *box) { +void fix_xwayland_unmanaged_coordinate(Client *c) { if (!selmon) return; - if (box->x >= selmon->m.x && box->x <= selmon->m.x + selmon->m.width && - box->y >= selmon->m.y && box->y <= selmon->m.y + selmon->m.height) + + // 1. 如果窗口已经在当前活动显示器内,直接返回 + if (c->geom.x >= selmon->m.x && c->geom.x < selmon->m.x + selmon->m.width && + c->geom.y >= selmon->m.y && c->geom.y < selmon->m.y + selmon->m.height) return; - Monitor *source_monitor = xytomon(box->x, box->y); - - if (!source_monitor) - return; - - int xoffset = box->x - source_monitor->m.x; - int yoffset = box->y - source_monitor->m.y; - - box->x = selmon->m.x + xoffset; - box->y = selmon->m.y + yoffset; + c->geom = setclient_coordinate_center(c, selmon, c->geom, 0, 0); } int32_t synckeymap(void *data) { From 5ae8975b11395dd73594fa7ea187fd5798510e30 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 13 Feb 2026 10:44:17 +0800 Subject: [PATCH 074/170] bump version to 0.12.2 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 478ef0e..e7979bd 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('mango', ['c', 'cpp'], - version : '0.12.1', + version : '0.12.2', ) subdir('protocols') From f25161552455d076f0f15cadf522a8b57b829e2f Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 13 Feb 2026 11:06:12 +0800 Subject: [PATCH 075/170] opt: flush the blur background cache when unmap a background layer --- src/mango.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/mango.c b/src/mango.c index ca25dbc..e8fd2e6 100644 --- a/src/mango.c +++ b/src/mango.c @@ -2275,6 +2275,19 @@ static void iter_layer_scene_buffers(struct wlr_scene_buffer *buffer, } } +void layer_flush_blur_background(LayerSurface *l) { + if (!blur) + return; + + // 如果背景层发生变化,标记优化的模糊背景缓存需要更新 + if (l->layer_surface->current.layer == + ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND) { + if (l->mon) { + wlr_scene_optimized_blur_mark_dirty(l->mon->blur); + } + } +} + void maplayersurfacenotify(struct wl_listener *listener, void *data) { LayerSurface *l = wl_container_of(listener, l, map); struct wlr_layer_surface_v1 *layer_surface = l->layer_surface; @@ -2402,15 +2415,7 @@ void commitlayersurfacenotify(struct wl_listener *listener, void *data) { } } - if (blur) { - // 如果背景层发生变化,标记优化的模糊背景缓存需要更新 - if (layer_surface->current.layer == - ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND) { - if (l->mon) { - wlr_scene_optimized_blur_mark_dirty(l->mon->blur); - } - } - } + layer_flush_blur_background(l); if (layer_surface == exclusive_focus && layer_surface->current.keyboard_interactive != @@ -5643,6 +5648,7 @@ void unmaplayersurfacenotify(struct wl_listener *listener, void *data) { focusclient(focustop(selmon), 1); motionnotify(0, NULL, 0, 0, 0, 0); l->being_unmapped = false; + layer_flush_blur_background(l); wlr_scene_node_destroy(&l->shadow->node); l->shadow = NULL; } From 711498490bdf3119ac5cb4debb60f6379e5d5df1 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 13 Feb 2026 18:11:18 +0800 Subject: [PATCH 076/170] opt: not back to ov tag when view prev tag --- src/dispatch/bind_define.h | 15 ++++++++++++++- src/mango.c | 2 ++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 1d29bc9..ea213e4 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1521,6 +1521,16 @@ int32_t minimized(const Arg *arg) { return 0; } +void fix_mon_tagset_from_overview(Monitor *m) { + if (m->tagset[m->seltags] == (m->ovbk_prev_tagset & TAGMASK)) { + m->tagset[m->seltags ^ 1] = m->ovbk_current_tagset; + m->pertag->prevtag = get_tags_first_tag_num(m->ovbk_current_tagset); + } else { + m->tagset[m->seltags ^ 1] = m->ovbk_prev_tagset; + m->pertag->prevtag = get_tags_first_tag_num(m->ovbk_prev_tagset); + } +} + int32_t toggleoverview(const Arg *arg) { Client *c = NULL; @@ -1542,6 +1552,8 @@ int32_t toggleoverview(const Arg *arg) { visible_client_number++; } if (visible_client_number > 0) { + selmon->ovbk_current_tagset = selmon->tagset[selmon->seltags]; + selmon->ovbk_prev_tagset = selmon->tagset[selmon->seltags ^ 1]; target = ~0 & TAGMASK; } else { selmon->isoverview ^= 1; @@ -1552,6 +1564,7 @@ int32_t toggleoverview(const Arg *arg) { } else if (!selmon->isoverview && !selmon->sel) { target = (1 << (selmon->pertag->prevtag - 1)); view(&(Arg){.ui = target}, false); + fix_mon_tagset_from_overview(selmon); refresh_monitors_workspaces_status(selmon); return 0; } @@ -1574,7 +1587,7 @@ int32_t toggleoverview(const Arg *arg) { } view(&(Arg){.ui = target}, false); - + fix_mon_tagset_from_overview(selmon); refresh_monitors_workspaces_status(selmon); return 0; } diff --git a/src/mango.c b/src/mango.c index e8fd2e6..5c0a610 100644 --- a/src/mango.c +++ b/src/mango.c @@ -514,6 +514,8 @@ struct Monitor { int32_t gappoh; /* horizontal outer gaps */ int32_t gappov; /* vertical outer gaps */ Pertag *pertag; + uint32_t ovbk_current_tagset; + uint32_t ovbk_prev_tagset; Client *sel, *prevsel; int32_t isoverview; int32_t is_in_hotarea; From 8a924494c6693529eb83b043bf56cbff696e78e1 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 13 Feb 2026 18:16:06 +0800 Subject: [PATCH 077/170] opt: the tagset is current tagset when open window in ov mode --- src/mango.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/mango.c b/src/mango.c index 5c0a610..b0fde12 100644 --- a/src/mango.c +++ b/src/mango.c @@ -5097,9 +5097,15 @@ void setmon(Client *c, Monitor *m, uint32_t newtags, bool focus) { /* Make sure window actually overlaps with the monitor */ reset_foreign_tolevel(c); resize(c, c->geom, 0); - c->tags = - newtags ? newtags - : m->tagset[m->seltags]; /* assign tags of target monitor */ + if (!newtags && !m->isoverview) { + c->tags = m->tagset[m->seltags]; + } else if (!newtags && m->isoverview) { + c->tags = m->ovbk_current_tagset; + } else if (newtags) { + c->tags = newtags; + } else { + c->tags = m->tagset[m->seltags]; + } setfloating(c, c->isfloating); setfullscreen(c, c->isfullscreen); /* This will call arrange(c->mon) */ } From c05eec7f53a8f1af25826cdc5bb3104b791e8cbb Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 13 Feb 2026 20:02:23 +0800 Subject: [PATCH 078/170] feat: support restore stack from non-tile state --- src/dispatch/bind_define.h | 8 +++++--- src/layout/arrange.h | 39 ++++++++++++++++++++++++++++++++++++++ src/mango.c | 6 +++--- 3 files changed, 47 insertions(+), 6 deletions(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index ea213e4..0bfab15 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1217,13 +1217,15 @@ int32_t togglefloating(const Arg *arg) { if (!sel) return 0; + bool isfloating = sel->isfloating; + if ((sel->isfullscreen || sel->ismaximizescreen)) { - sel->isfloating = 1; + isfloating = 1; } else { - sel->isfloating = !sel->isfloating; + isfloating = !sel->isfloating; } - setfloating(sel, sel->isfloating); + setfloating(sel, isfloating); return 0; } diff --git a/src/layout/arrange.h b/src/layout/arrange.h index 3721364..cc4bc07 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -1,3 +1,42 @@ +void restore_size_per(Monitor *m, Client *c) { + Client *fc = NULL; + double total_master_inner_per = 0; + double total_stack_inner_per = 0; + + if (!m || !c) + return; + + const Layout *current_layout = m->pertag->ltidxs[m->pertag->curtag]; + + if (current_layout->id == SCROLLER || + current_layout->id == VERTICAL_SCROLLER || current_layout->id == GRID || + current_layout->id == VERTICAL_GRID || current_layout->id == DECK || + current_layout->id == VERTICAL_DECK || + current_layout->id == CENTER_TILE || current_layout->id == MONOCLE) { + return; + } + + if (current_layout->id == CENTER_TILE || c->ismaster) { + set_size_per(m, c); + return; + } + + wl_list_for_each(fc, &clients, link) { + if (VISIBLEON(fc, m) && ISTILED(fc) && fc != c) { + if (fc->ismaster) { + total_master_inner_per += fc->master_inner_per; + } else { + total_stack_inner_per += fc->stack_inner_per; + } + } + } + + if (!c->ismaster && total_stack_inner_per) { + c->stack_inner_per = total_stack_inner_per * c->stack_inner_per / + (1 - c->stack_inner_per); + } +} + void set_size_per(Monitor *m, Client *c) { Client *fc = NULL; bool found = false; diff --git a/src/mango.c b/src/mango.c index b0fde12..45e0289 100644 --- a/src/mango.c +++ b/src/mango.c @@ -4810,7 +4810,7 @@ setfloating(Client *c, int32_t floating) { } if (!c->isfloating && old_floating_state) { - set_size_per(c->mon, c); + restore_size_per(c->mon, c); } if (!c->force_maximize) @@ -4896,7 +4896,7 @@ void setmaximizescreen(Client *c, int32_t maximizescreen) { wlr_scene_node_reparent(&c->scene->node, layers[c->isfloating ? LyrTop : LyrTile]); if (!c->ismaximizescreen && old_maximizescreen_state) { - set_size_per(c->mon, c); + restore_size_per(c->mon, c); } if (!c->force_maximize && !c->ismaximizescreen) { @@ -4967,7 +4967,7 @@ void setfullscreen(Client *c, int32_t fullscreen) // 用自定义全屏代理自 } if (!c->isfullscreen && old_fullscreen_state) { - set_size_per(c->mon, c); + restore_size_per(c->mon, c); } arrange(c->mon, false, false); From 0fe87e6286f2a6b899fa8d7683a982f831c983af Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 13 Feb 2026 20:23:03 +0800 Subject: [PATCH 079/170] fix: fix multi master focus record error --- src/fetch/client.h | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/fetch/client.h b/src/fetch/client.h index bf30e17..8fccb26 100644 --- a/src/fetch/client.h +++ b/src/fetch/client.h @@ -574,23 +574,29 @@ 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 && !fc->ismaster) + if (tc->ismaster ^ sc->ismaster) return false; - else if (!sc->ismaster) + if (fc && !(fc->ismaster ^ sc->ismaster)) + return false; + else return true; } if (id == TGMIX) { - if (fc && !fc->ismaster) + if (tc->ismaster ^ sc->ismaster) + return false; + if (fc && !(fc->ismaster ^ sc->ismaster)) return false; if (!sc->ismaster && sc->mon->visible_tiling_clients <= 3) return true; } if (id == CENTER_TILE) { - if (fc && !fc->ismaster) + if (tc->ismaster ^ sc->ismaster) return false; - if (!sc->ismaster && sc->geom.x == tc->geom.x) + if (fc && !(fc->ismaster ^ sc->ismaster)) + return false; + if (sc->geom.x == tc->geom.x) return true; else return false; From 89413aacf53e11340d0558e7d18576720a067158 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 14 Feb 2026 08:35:30 +0800 Subject: [PATCH 080/170] fix: fix center tile size per reset --- src/layout/arrange.h | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/layout/arrange.h b/src/layout/arrange.h index cc4bc07..1ef89c3 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -11,13 +11,16 @@ void restore_size_per(Monitor *m, Client *c) { if (current_layout->id == SCROLLER || current_layout->id == VERTICAL_SCROLLER || current_layout->id == GRID || current_layout->id == VERTICAL_GRID || current_layout->id == DECK || - current_layout->id == VERTICAL_DECK || - current_layout->id == CENTER_TILE || current_layout->id == MONOCLE) { + current_layout->id == VERTICAL_DECK || current_layout->id == MONOCLE) { return; } if (current_layout->id == CENTER_TILE || c->ismaster) { - set_size_per(m, c); + wl_list_for_each(fc, &clients, link) { + if (VISIBLEON(fc, m) && ISTILED(fc) && !c->ismaster) { + set_size_per(m, fc); + } + } return; } @@ -44,8 +47,13 @@ void set_size_per(Monitor *m, Client *c) { if (!m || !c) return; + const Layout *current_layout = m->pertag->ltidxs[m->pertag->curtag]; + wl_list_for_each(fc, &clients, link) { if (VISIBLEON(fc, m) && ISTILED(fc) && fc != c) { + if (current_layout->id == CENTER_TILE && + !(fc->isleftstack ^ c->isleftstack)) + continue; c->master_mfact_per = fc->master_mfact_per; c->master_inner_per = fc->master_inner_per; c->stack_inner_per = fc->stack_inner_per; From fdd54afb7e49d0dfdf3976b1824a660a83e09713 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 15 Feb 2026 08:00:44 +0800 Subject: [PATCH 081/170] fix: some app frame skip fail when disable animaitons --- src/mango.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/mango.c b/src/mango.c index 2bc21c8..87d2f7d 100644 --- a/src/mango.c +++ b/src/mango.c @@ -3835,6 +3835,7 @@ static void iter_xdg_scene_buffers(struct wlr_scene_buffer *buffer, int32_t sx, void init_client_properties(Client *c) { c->isfocusing = false; + c->isfloating = 0; c->ismaximizescreen = 0; c->isfullscreen = 0; c->need_float_size_reduce = 0; @@ -4441,8 +4442,8 @@ void rendermon(struct wl_listener *listener, void *data) { wl_list_for_each(c, &clients, link) { need_more_frames = client_draw_frame(c) || need_more_frames; if (!animations && !(allow_tearing && frame_allow_tearing) && - c->configure_serial && !c->isfloating && - client_is_rendered_on_mon(c, m) && !client_is_stopped(c)) { + c->configure_serial && client_is_rendered_on_mon(c, m) && + !client_is_stopped(c)) { goto skip; } } From 02067e3b1e00b568b63dabb4c1c57b188911c349 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 15 Feb 2026 08:07:00 +0800 Subject: [PATCH 082/170] fix: some client property missing init --- src/mango.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/mango.c b/src/mango.c index 87d2f7d..6cdd554 100644 --- a/src/mango.c +++ b/src/mango.c @@ -3836,6 +3836,22 @@ static void iter_xdg_scene_buffers(struct wlr_scene_buffer *buffer, int32_t sx, void init_client_properties(Client *c) { c->isfocusing = false; c->isfloating = 0; + c->isfakefullscreen = 0; + c->isnoanimation = 0; + c->isopensilent = 0; + c->istagsilent = 0; + c->noswallow = 0; + c->isterm = 0; + c->noblur = 0; + c->tearing_hint = 0; + c->overview_isfullscreenbak = 0; + c->overview_ismaximizescreenbak = 0; + c->overview_isfloatingbak = 0; + c->pid = 0; + c->swallowing = NULL; + c->swallowedby = NULL; + c->ismaster = 0; + c->isleftstack = 0; c->ismaximizescreen = 0; c->isfullscreen = 0; c->need_float_size_reduce = 0; @@ -3889,6 +3905,7 @@ void init_client_properties(Client *c) { c->stack_proportion = 0.0f; c->next_in_stack = NULL; c->prev_in_stack = NULL; + memset(c->oldmonname, 0, sizeof(c->oldmonname)); memcpy(c->opacity_animation.initial_border_color, bordercolor, sizeof(c->opacity_animation.initial_border_color)); memcpy(c->opacity_animation.current_border_color, bordercolor, From 842b45b584bd4b9409e474757e79ab7f08e05b21 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 15 Feb 2026 08:32:49 +0800 Subject: [PATCH 083/170] feat: add skip timer to avoid rermanently block render --- src/mango.c | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/mango.c b/src/mango.c index 6cdd554..ded5958 100644 --- a/src/mango.c +++ b/src/mango.c @@ -502,6 +502,7 @@ struct Monitor { struct wl_listener request_state; struct wl_listener destroy_lock_surface; struct wlr_session_lock_surface_v1 *lock_surface; + struct wl_event_source *skip_timeout; struct wlr_box m; /* monitor area, layout-relative */ struct wlr_box w; /* window area, layout-relative */ struct wl_list layers[4]; /* LayerSurface::link */ @@ -783,6 +784,7 @@ 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); +static void destroy_monitor_skip_timer(Monitor *m); #include "data/static_keymap.h" #include "dispatch/bind_declare.h" @@ -2223,6 +2225,9 @@ void cleanupmon(struct wl_listener *listener, void *data) { wlr_scene_node_destroy(&m->blur->node); m->blur = NULL; } + if (m->skip_timeout) { + destroy_monitor_skip_timer(m); + } m->wlr_output->data = NULL; free(m->pertag); free(m); @@ -2774,6 +2779,7 @@ void createmon(struct wl_listener *listener, void *data) { m = wlr_output->data = ecalloc(1, sizeof(*m)); m->wlr_output = wlr_output; m->wlr_output->data = m; + m->skip_timeout = NULL; wl_list_init(&m->dwl_ipc_outputs); @@ -4419,6 +4425,37 @@ void client_set_opacity(Client *c, double opacity) { scene_buffer_apply_opacity, &opacity); } +void destroy_monitor_skip_timer(Monitor *m) { + if (m->skip_timeout) { + wl_event_source_timer_update(m->skip_timeout, 0); + wl_event_source_remove(m->skip_timeout); + m->skip_timeout = NULL; + } +} + +static int skip_timeout_callback(void *data) { + Monitor *m = data; + Client *c, *tmp; + + wl_list_for_each_safe(c, tmp, &clients, link) { c->configure_serial = 0; } + + if (m->skip_timeout) { + destroy_monitor_skip_timer(m); + } + return 0; +} + +void check_skip_timeout(Monitor *m) { + if (m->skip_timeout) { + return; + } + struct wl_event_loop *loop = wl_display_get_event_loop(dpy); + m->skip_timeout = wl_event_loop_add_timer(loop, skip_timeout_callback, m); + if (m->skip_timeout) { + wl_event_source_timer_update(m->skip_timeout, 100); // 100ms + } +} + void rendermon(struct wl_listener *listener, void *data) { Monitor *m = wl_container_of(listener, m, frame); Client *c = NULL, *tmp = NULL; @@ -4461,10 +4498,15 @@ void rendermon(struct wl_listener *listener, void *data) { if (!animations && !(allow_tearing && frame_allow_tearing) && c->configure_serial && client_is_rendered_on_mon(c, m) && !client_is_stopped(c)) { + check_skip_timeout(m); goto skip; } } + if (m->skip_timeout) { + destroy_monitor_skip_timer(m); + } + // 只有在需要帧时才构建和提交状态 if (allow_tearing && frame_allow_tearing) { apply_tear_state(m); From 0696fe964de6b820e172ecbed6d3af942996b6bd Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 15 Feb 2026 10:31:23 +0800 Subject: [PATCH 084/170] opt: optimize frame skip logic --- src/mango.c | 47 +++++++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/src/mango.c b/src/mango.c index ded5958..8ae2603 100644 --- a/src/mango.c +++ b/src/mango.c @@ -508,6 +508,7 @@ struct Monitor { struct wl_list layers[4]; /* LayerSurface::link */ uint32_t seltags; uint32_t tagset[2]; + bool skiping_frame; struct wl_list dwl_ipc_outputs; int32_t gappih; /* horizontal gap between windows */ @@ -784,7 +785,8 @@ 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); -static void destroy_monitor_skip_timer(Monitor *m); +static void monitor_stop_skip_timer(Monitor *m); +static int monitor_skip_frame_timeout_callback(void *data); #include "data/static_keymap.h" #include "dispatch/bind_declare.h" @@ -2226,7 +2228,9 @@ void cleanupmon(struct wl_listener *listener, void *data) { m->blur = NULL; } if (m->skip_timeout) { - destroy_monitor_skip_timer(m); + monitor_stop_skip_timer(m); + wl_event_source_remove(m->skip_timeout); + m->skip_timeout = NULL; } m->wlr_output->data = NULL; free(m->pertag); @@ -2776,10 +2780,15 @@ void createmon(struct wl_listener *listener, void *data) { return; } + struct wl_event_loop *loop = wl_display_get_event_loop(dpy); m = wlr_output->data = ecalloc(1, sizeof(*m)); + + m->skip_timeout = + wl_event_loop_add_timer(loop, monitor_skip_frame_timeout_callback, m); + m->skiping_frame = false; + m->wlr_output = wlr_output; m->wlr_output->data = m; - m->skip_timeout = NULL; wl_list_init(&m->dwl_ipc_outputs); @@ -4425,34 +4434,32 @@ void client_set_opacity(Client *c, double opacity) { scene_buffer_apply_opacity, &opacity); } -void destroy_monitor_skip_timer(Monitor *m) { - if (m->skip_timeout) { +void monitor_stop_skip_timer(Monitor *m) { + if (m->skip_timeout) wl_event_source_timer_update(m->skip_timeout, 0); - wl_event_source_remove(m->skip_timeout); - m->skip_timeout = NULL; - } + m->skiping_frame = false; } -static int skip_timeout_callback(void *data) { +static int monitor_skip_frame_timeout_callback(void *data) { Monitor *m = data; Client *c, *tmp; wl_list_for_each_safe(c, tmp, &clients, link) { c->configure_serial = 0; } - if (m->skip_timeout) { - destroy_monitor_skip_timer(m); - } - return 0; + monitor_stop_skip_timer(m); + wlr_output_schedule_frame(m->wlr_output); + + return 1; } -void check_skip_timeout(Monitor *m) { - if (m->skip_timeout) { +void monitor_check_skip_frame_timeout(Monitor *m) { + if (m->skiping_frame) { return; } - struct wl_event_loop *loop = wl_display_get_event_loop(dpy); - m->skip_timeout = wl_event_loop_add_timer(loop, skip_timeout_callback, m); + if (m->skip_timeout) { wl_event_source_timer_update(m->skip_timeout, 100); // 100ms + m->skiping_frame = true; } } @@ -4498,13 +4505,13 @@ void rendermon(struct wl_listener *listener, void *data) { if (!animations && !(allow_tearing && frame_allow_tearing) && c->configure_serial && client_is_rendered_on_mon(c, m) && !client_is_stopped(c)) { - check_skip_timeout(m); + monitor_check_skip_frame_timeout(m); goto skip; } } - if (m->skip_timeout) { - destroy_monitor_skip_timer(m); + if (m->skiping_frame) { + monitor_stop_skip_timer(m); } // 只有在需要帧时才构建和提交状态 From 49cb5a9d7efb03e726e4eea1579242e3549f6d8b Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 15 Feb 2026 12:29:12 +0800 Subject: [PATCH 085/170] feat: support frame skip for x11 app resize --- src/client/client.h | 16 +++++++++++++++- src/mango.c | 18 ++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/client/client.h b/src/client/client.h index 49ab398..fbb7a24 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -319,9 +319,23 @@ static inline uint32_t client_set_size(Client *c, uint32_t width, uint32_t height) { #ifdef XWAYLAND if (client_is_x11(c)) { + + struct wlr_surface_state *state = + &c->surface.xwayland->surface->current; + struct wlr_box new_geo = {0}; + new_geo.width = state->width; + new_geo.height = state->height; + if (c->geom.width - 2 * c->bw == new_geo.width && + c->geom.height - 2 * c->bw == new_geo.height && + c->surface.xwayland->x == c->geom.x + c->bw && + c->surface.xwayland->y == c->geom.y + c->bw) { + c->configure_serial = 0; + return 0; + } + wlr_xwayland_surface_configure(c->surface.xwayland, c->geom.x + c->bw, c->geom.y + c->bw, width, height); - return 0; + return 1; } #endif if ((int32_t)width == c->surface.xdg->toplevel->current.width && diff --git a/src/mango.c b/src/mango.c index 8ae2603..f0aefec 100644 --- a/src/mango.c +++ b/src/mango.c @@ -338,6 +338,7 @@ struct Client { struct wl_listener configure; struct wl_listener set_hints; struct wl_listener set_geometry; + struct wl_listener commmitx11; #endif uint32_t bw; uint32_t tags, oldtags, mini_restore_tag; @@ -956,6 +957,7 @@ static void activatex11(struct wl_listener *listener, void *data); static void configurex11(struct wl_listener *listener, void *data); static void createnotifyx11(struct wl_listener *listener, void *data); static void dissociatex11(struct wl_listener *listener, void *data); +static void commitx11(struct wl_listener *listener, void *data); static void associatex11(struct wl_listener *listener, void *data); static void sethints(struct wl_listener *listener, void *data); static void xwaylandready(struct wl_listener *listener, void *data); @@ -6280,17 +6282,33 @@ void createnotifyx11(struct wl_listener *listener, void *data) { LISTEN(&xsurface->events.request_minimize, &c->minimize, minimizenotify); } +void commitx11(struct wl_listener *listener, void *data) { + Client *c = wl_container_of(listener, c, commmitx11); + struct wlr_surface_state *state = &c->surface.xwayland->surface->current; + struct wlr_box new_geo = {0}; + new_geo.width = state->width; + new_geo.height = state->height; + if (c->geom.width - 2 * c->bw == new_geo.width && + c->geom.height - 2 * c->bw == new_geo.height && + c->surface.xwayland->x == c->geom.x + c->bw && + c->surface.xwayland->y == c->geom.y + c->bw) { + c->configure_serial = 0; + } +} + void associatex11(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, associate); LISTEN(&client_surface(c)->events.map, &c->map, mapnotify); LISTEN(&client_surface(c)->events.unmap, &c->unmap, unmapnotify); + LISTEN(&client_surface(c)->events.commit, &c->commmitx11, commitx11); } void dissociatex11(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, dissociate); wl_list_remove(&c->map.link); wl_list_remove(&c->unmap.link); + wl_list_remove(&c->commmitx11.link); } void sethints(struct wl_listener *listener, void *data) { From 7ccbeae8b8bcf0c9b506579758c6ac499f3d5dab Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 15 Feb 2026 14:08:12 +0800 Subject: [PATCH 086/170] fix: if the progress not the child of main, not assume it is stop --- src/client/client.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/client/client.h b/src/client/client.h index fbb7a24..e951910 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -254,10 +254,13 @@ static inline int32_t client_is_stopped(Client *c) { wl_client_get_credentials(c->surface.xdg->client->client, &pid, NULL, NULL); if (waitid(P_PID, pid, &in, WNOHANG | WCONTINUED | WSTOPPED | WNOWAIT) < 0) { - /* This process is not our child process, while is very unlikely that - * it is stopped, in order to do not skip frames assume that it is. */ + /* This process is not our child process. We cannot determine its + * stopped state; assume it is not stopped to avoid blocking frame skip. + */ if (errno == ECHILD) - return 1; + return 0; // if not our child, assume not stopped + /* Other errors, also assume not stopped. */ + return 0; } else if (in.si_pid) { if (in.si_code == CLD_STOPPED || in.si_code == CLD_TRAPPED) return 1; From 62ab00a7a3656fcdc39008d048b13369ffe1ad11 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 15 Feb 2026 16:43:02 +0800 Subject: [PATCH 087/170] update readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b78ccfa..9d14d06 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ This project's development is based on [dwl](https://codeberg.org/dwl/dwl/). - Ipc support(get/send message from/to compositor by external program) - Hycov-like overview - Window effects from scenefx (blur, shadow, corner radius, opacity) + - Zero flickering - every frame is perfect. https://github.com/user-attachments/assets/bb83004a-0563-4b48-ad89-6461a9b78b1f From f75efa13124e6017fbb935d7e3b8d86077660669 Mon Sep 17 00:00:00 2001 From: Ricardo Squassina Lee <8495707+squassina@users.noreply.github.com> Date: Sun, 15 Feb 2026 07:08:35 -0300 Subject: [PATCH 088/170] Fix wayland protocol directory variable retrieval --- protocols/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocols/meson.build b/protocols/meson.build index cafab64..922a76e 100644 --- a/protocols/meson.build +++ b/protocols/meson.build @@ -1,6 +1,6 @@ wayland_scanner = find_program('wayland-scanner') wayland_protos_dep = dependency('wayland-protocols') -wl_protocol_dir = wayland_protos_dep.get_pkgconfig_variable('pkgdatadir') +wl_protocol_dir = wayland_protos_dep.get_variable(pkgconfig:'pkgdatadir') wayland_scanner_code = generator( wayland_scanner, output: '@BASENAME@-protocol.c', From 26a616e3d5a99811708b8b0afa62eafd0ca490be Mon Sep 17 00:00:00 2001 From: Yappaholic Date: Sun, 15 Feb 2026 16:50:07 +0300 Subject: [PATCH 089/170] docs: add guix installation instructions --- README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/README.md b/README.md index 9d14d06..9b558fd 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,34 @@ Then, install the package: dnf install mangowc ``` +## GuixSD +The package definition is described in the source repository. +First, add `mangowc` channel to `channels.scm` file: + +```scheme +;; In $HOME/.config/guix/channels.scm +(cons (channel + (name 'mangowc) + (url "https://github.com/DreamMaoMao/mangowc.git")) + ... ;; Your other channels + %default-channels) +``` + +Then, run `guix pull` and after update you can either run +`guix install mangowc` or add it to your configuration via: + +```scheme +(use-modules (mangowc)) ;; Add mangowc module + +;; Add mangowc to packages list +(packages (cons + mangowc + ... ;; Other packages you specified + %base-packages)) +``` + +And then rebuild your system. + ## Other ```bash From 1158fb2e3c09424e1d0f3aaf9b77968743cc235e Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 16 Feb 2026 07:46:42 +0800 Subject: [PATCH 090/170] opt: don't skip frame when grab client --- src/mango.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mango.c b/src/mango.c index f0aefec..cbdc53a 100644 --- a/src/mango.c +++ b/src/mango.c @@ -4506,7 +4506,7 @@ void rendermon(struct wl_listener *listener, void *data) { need_more_frames = client_draw_frame(c) || need_more_frames; if (!animations && !(allow_tearing && frame_allow_tearing) && c->configure_serial && client_is_rendered_on_mon(c, m) && - !client_is_stopped(c)) { + !client_is_stopped(c) && !grabc) { monitor_check_skip_frame_timeout(m); goto skip; } From 112fb5c0074f6f2110e70ae4765fe0a46ff04adb Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 16 Feb 2026 10:06:16 +0800 Subject: [PATCH 091/170] opt: optimize code struct --- src/client/client.h | 16 +++++++++------- src/mango.c | 15 ++++++++------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/client/client.h b/src/client/client.h index e951910..2174c6f 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -325,13 +325,15 @@ static inline uint32_t client_set_size(Client *c, uint32_t width, struct wlr_surface_state *state = &c->surface.xwayland->surface->current; - struct wlr_box new_geo = {0}; - new_geo.width = state->width; - new_geo.height = state->height; - if (c->geom.width - 2 * c->bw == new_geo.width && - c->geom.height - 2 * c->bw == new_geo.height && - c->surface.xwayland->x == c->geom.x + c->bw && - c->surface.xwayland->y == c->geom.y + c->bw) { + + if ((int32_t)c->geom.width - 2 * (int32_t)c->bw == + (int32_t)state->width && + (int32_t)c->geom.height - 2 * (int32_t)c->bw == + (int32_t)state->height && + (int32_t)c->surface.xwayland->x == + (int32_t)c->geom.x + (int32_t)c->bw && + (int32_t)c->surface.xwayland->y == + (int32_t)c->geom.y + (int32_t)c->bw) { c->configure_serial = 0; return 0; } diff --git a/src/mango.c b/src/mango.c index cbdc53a..540395a 100644 --- a/src/mango.c +++ b/src/mango.c @@ -6285,13 +6285,14 @@ void createnotifyx11(struct wl_listener *listener, void *data) { void commitx11(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, commmitx11); struct wlr_surface_state *state = &c->surface.xwayland->surface->current; - struct wlr_box new_geo = {0}; - new_geo.width = state->width; - new_geo.height = state->height; - if (c->geom.width - 2 * c->bw == new_geo.width && - c->geom.height - 2 * c->bw == new_geo.height && - c->surface.xwayland->x == c->geom.x + c->bw && - c->surface.xwayland->y == c->geom.y + c->bw) { + + if ((int32_t)c->geom.width - 2 * (int32_t)c->bw == (int32_t)state->width && + (int32_t)c->geom.height - 2 * (int32_t)c->bw == + (int32_t)state->height && + (int32_t)c->surface.xwayland->x == + (int32_t)c->geom.x + (int32_t)c->bw && + (int32_t)c->surface.xwayland->y == + (int32_t)c->geom.y + (int32_t)c->bw) { c->configure_serial = 0; } } From fa88ebace0cb352cb25fe5f989388df06d709e9c Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 16 Feb 2026 11:56:42 +0800 Subject: [PATCH 092/170] project: version not use latest tag --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index e7979bd..5b19f9f 100644 --- a/meson.build +++ b/meson.build @@ -56,7 +56,7 @@ endif if is_git_repo # 如果是 Git 目录,获取 Commit Hash 和最新的 tag commit_hash = run_command(git, 'rev-parse', '--short', 'HEAD', check : false).stdout().strip() - latest_tag = run_command(git, 'describe', '--tags', '--abbrev=0', check : false).stdout().strip() + latest_tag = meson.project_version() version_with_hash = '@0@(@1@)'.format(latest_tag, commit_hash) else # 如果不是 Git 目录,使用项目版本号和 "release" 字符串 From c3dcee2c8e3ae4e2839fda699a25e2d0a836b341 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 17 Feb 2026 08:33:45 +0800 Subject: [PATCH 093/170] opt: remove useless code --- src/client/client.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/client/client.h b/src/client/client.h index 2174c6f..fd81a80 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -334,7 +334,6 @@ static inline uint32_t client_set_size(Client *c, uint32_t width, (int32_t)c->geom.x + (int32_t)c->bw && (int32_t)c->surface.xwayland->y == (int32_t)c->geom.y + (int32_t)c->bw) { - c->configure_serial = 0; return 0; } From 259fdb3a875e4c5842f75579c3e0dc85d897a4f9 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 18 Feb 2026 13:26:24 +0800 Subject: [PATCH 094/170] bump versiont to 0.12.3 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 5b19f9f..06ccfd7 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('mango', ['c', 'cpp'], - version : '0.12.2', + version : '0.12.3', ) subdir('protocols') From 6924ca8512090c37d64386bb5e6fabb4a6410ecb Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 18 Feb 2026 23:07:23 +0800 Subject: [PATCH 095/170] feat: add global option prefer_scroller_overspread --- src/config/parse_config.h | 6 ++++ src/config/preset.h | 1 + src/layout/horizontal.h | 66 +++++++++++++++++++++++++++++++++------ src/layout/vertical.h | 62 +++++++++++++++++++++++++++++++----- 4 files changed, 118 insertions(+), 17 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index b7f89d5..64afd88 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -211,6 +211,7 @@ typedef struct { int32_t scroller_ignore_proportion_single; int32_t scroller_focus_center; int32_t scroller_prefer_center; + int32_t scroller_prefer_overspread; int32_t edge_scroller_pointer_focus; int32_t focus_cross_monitor; int32_t exchange_cross_monitor; @@ -1337,6 +1338,8 @@ bool parse_option(Config *config, char *key, char *value) { config->scroller_focus_center = atoi(value); } else if (strcmp(key, "scroller_prefer_center") == 0) { config->scroller_prefer_center = atoi(value); + } else if (strcmp(key, "scroller_prefer_overspread") == 0) { + config->scroller_prefer_overspread = atoi(value); } else if (strcmp(key, "edge_scroller_pointer_focus") == 0) { config->edge_scroller_pointer_focus = atoi(value); } else if (strcmp(key, "focus_cross_monitor") == 0) { @@ -3102,6 +3105,8 @@ void override_config(void) { CLAMP_INT(config.scroller_ignore_proportion_single, 0, 1); scroller_focus_center = CLAMP_INT(config.scroller_focus_center, 0, 1); scroller_prefer_center = CLAMP_INT(config.scroller_prefer_center, 0, 1); + scroller_prefer_overspread = + CLAMP_INT(config.scroller_prefer_overspread, 0, 1); edge_scroller_pointer_focus = CLAMP_INT(config.edge_scroller_pointer_focus, 0, 1); scroller_structs = CLAMP_INT(config.scroller_structs, 0, 1000); @@ -3301,6 +3306,7 @@ void set_value_default() { scroller_ignore_proportion_single; config.scroller_focus_center = scroller_focus_center; config.scroller_prefer_center = scroller_prefer_center; + config.scroller_prefer_overspread = scroller_prefer_overspread; config.edge_scroller_pointer_focus = edge_scroller_pointer_focus; config.focus_cross_monitor = focus_cross_monitor; config.exchange_cross_monitor = exchange_cross_monitor; diff --git a/src/config/preset.h b/src/config/preset.h index ae4424f..d982458 100644 --- a/src/config/preset.h +++ b/src/config/preset.h @@ -66,6 +66,7 @@ float scroller_default_proportion_single = 1.0; int32_t scroller_ignore_proportion_single = 1; int32_t scroller_focus_center = 0; int32_t scroller_prefer_center = 0; +int32_t scroller_prefer_overspread = 1; int32_t focus_cross_monitor = 0; int32_t focus_cross_tag = 0; int32_t exchange_cross_monitor = 0; diff --git a/src/layout/horizontal.h b/src/layout/horizontal.h index e1a335d..b8016d5 100644 --- a/src/layout/horizontal.h +++ b/src/layout/horizontal.h @@ -283,6 +283,7 @@ void scroller(Monitor *m) { struct wlr_box target_geom; int32_t focus_client_index = 0; bool need_scroller = false; + bool over_overspread_to_left = false; int32_t cur_gappih = enablegaps ? m->gappih : 0; int32_t cur_gappoh = enablegaps ? m->gappoh : 0; int32_t cur_gappov = enablegaps ? m->gappov : 0; @@ -371,6 +372,45 @@ void scroller(Monitor *m) { } } + bool need_apply_overspread = + scroller_prefer_overspread && m->visible_scroll_tiling_clients > 1 && + tempClients[focus_client_index]->scroller_proportion < 1.0f; + + if (need_apply_overspread) { + + if (focus_client_index == 0) { + over_overspread_to_left = true; + } else { + over_overspread_to_left = false; + } + + if (over_overspread_to_left && + (!INSIDEMON(tempClients[1]) || + (tempClients[1]->scroller_proportion + + tempClients[focus_client_index]->scroller_proportion >= + 1.0f))) { + need_scroller = true; + } else if (!over_overspread_to_left && + (!INSIDEMON(tempClients[focus_client_index - 1]) || + (tempClients[focus_client_index - 1]->scroller_proportion + + tempClients[focus_client_index]->scroller_proportion >= + 1.0f))) { + need_scroller = true; + } else { + need_apply_overspread = false; + } + } + + bool need_apply_center = + scroller_focus_center || m->visible_scroll_tiling_clients == 1 || + (scroller_prefer_center && !need_apply_overspread && + (!m->prevsel || + (ISSCROLLTILED(m->prevsel) && + (m->prevsel->scroller_proportion * max_client_width) + + (tempClients[focus_client_index]->scroller_proportion * + max_client_width) > + m->w.width - 2 * scroller_structs - cur_gappih))); + if (n == 1 && scroller_ignore_proportion_single) { need_scroller = true; } @@ -394,18 +434,26 @@ void scroller(Monitor *m) { 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 || - (ISSCROLLTILED(m->prevsel) && - (m->prevsel->scroller_proportion * max_client_width) + - (root_client->scroller_proportion * max_client_width) > - m->w.width - 2 * scroller_structs - cur_gappih)) && - scroller_prefer_center)) { + if (need_apply_center) { target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2; + } else if (need_apply_overspread) { + if (over_overspread_to_left) { + target_geom.x = m->w.x + scroller_structs; + } else { + target_geom.x = + m->w.x + + (m->w.width - + tempClients[focus_client_index]->scroller_proportion * + max_client_width - + scroller_structs); + } + } else { - target_geom.x = root_client->geom.x > m->w.x + (m->w.width) / 2 + target_geom.x = tempClients[focus_client_index]->geom.x > + m->w.x + (m->w.width) / 2 ? m->w.x + (m->w.width - - root_client->scroller_proportion * + tempClients[focus_client_index] + ->scroller_proportion * max_client_width - scroller_structs) : m->w.x + scroller_structs; diff --git a/src/layout/vertical.h b/src/layout/vertical.h index f7bd442..f036ca4 100644 --- a/src/layout/vertical.h +++ b/src/layout/vertical.h @@ -270,6 +270,7 @@ void vertical_scroller(Monitor *m) { struct wlr_box target_geom; int32_t focus_client_index = 0; bool need_scroller = false; + bool over_overspread_to_up = false; int32_t cur_gappiv = enablegaps ? m->gappiv : 0; int32_t cur_gappov = enablegaps ? m->gappov : 0; int32_t cur_gappoh = enablegaps ? m->gappoh : 0; @@ -355,6 +356,45 @@ void vertical_scroller(Monitor *m) { } } + bool need_apply_overspread = + scroller_prefer_overspread && m->visible_scroll_tiling_clients > 1 && + tempClients[focus_client_index]->scroller_proportion < 1.0f; + + if (need_apply_overspread) { + + if (focus_client_index == 0) { + over_overspread_to_up = true; + } else { + over_overspread_to_up = false; + } + + if (over_overspread_to_up && + (!INSIDEMON(tempClients[1]) || + (tempClients[1]->scroller_proportion + + tempClients[focus_client_index]->scroller_proportion >= + 1.0f))) { + need_scroller = true; + } else if (!over_overspread_to_up && + (!INSIDEMON(tempClients[focus_client_index - 1]) || + (tempClients[focus_client_index - 1]->scroller_proportion + + tempClients[focus_client_index]->scroller_proportion >= + 1.0f))) { + need_scroller = true; + } else { + need_apply_overspread = false; + } + } + + bool need_apply_center = + scroller_focus_center || m->visible_scroll_tiling_clients == 1 || + (scroller_prefer_center && !need_apply_overspread && + (!m->prevsel || + (ISSCROLLTILED(m->prevsel) && + (m->prevsel->scroller_proportion * max_client_height) + + (tempClients[focus_client_index]->scroller_proportion * + max_client_height) > + m->w.height - 2 * scroller_structs - cur_gappiv))); + if (n == 1 && scroller_ignore_proportion_single) { need_scroller = true; } @@ -381,18 +421,24 @@ void vertical_scroller(Monitor *m) { arrange_stack_vertical(tempClients[focus_client_index], target_geom, cur_gappih); } else if (need_scroller) { - if (scroller_focus_center || - ((!m->prevsel || - (ISSCROLLTILED(m->prevsel) && - (m->prevsel->scroller_proportion * max_client_height) + - (root_client->scroller_proportion * max_client_height) > - m->w.height - 2 * scroller_structs - cur_gappiv)) && - scroller_prefer_center)) { + if (need_apply_center) { target_geom.y = m->w.y + (m->w.height - target_geom.height) / 2; + } else if (need_apply_overspread) { + if (over_overspread_to_up) { + target_geom.y = m->w.y + scroller_structs; + } else { + target_geom.y = + m->w.y + + (m->w.height - + tempClients[focus_client_index]->scroller_proportion * + max_client_height - + scroller_structs); + } } else { target_geom.y = root_client->geom.y > m->w.y + (m->w.height) / 2 ? m->w.y + (m->w.height - - root_client->scroller_proportion * + tempClients[focus_client_index] + ->scroller_proportion * max_client_height - scroller_structs) : m->w.y + scroller_structs; From 23d7b11e27a1078dba9492943662a1fa444a82d7 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 19 Feb 2026 08:41:42 +0800 Subject: [PATCH 096/170] fix: only apply scroller overspread to head and tail client --- src/layout/horizontal.h | 1 + src/layout/vertical.h | 1 + 2 files changed, 2 insertions(+) diff --git a/src/layout/horizontal.h b/src/layout/horizontal.h index b8016d5..8140934 100644 --- a/src/layout/horizontal.h +++ b/src/layout/horizontal.h @@ -374,6 +374,7 @@ void scroller(Monitor *m) { bool need_apply_overspread = scroller_prefer_overspread && m->visible_scroll_tiling_clients > 1 && + (focus_client_index == 0 || focus_client_index == n - 1) && tempClients[focus_client_index]->scroller_proportion < 1.0f; if (need_apply_overspread) { diff --git a/src/layout/vertical.h b/src/layout/vertical.h index f036ca4..4759e7a 100644 --- a/src/layout/vertical.h +++ b/src/layout/vertical.h @@ -358,6 +358,7 @@ void vertical_scroller(Monitor *m) { bool need_apply_overspread = scroller_prefer_overspread && m->visible_scroll_tiling_clients > 1 && + (focus_client_index == 0 || focus_client_index == n - 1) && tempClients[focus_client_index]->scroller_proportion < 1.0f; if (need_apply_overspread) { From 68075c00445e23028ec49fa1008bec8c9bb1cdb7 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 19 Feb 2026 09:59:58 +0800 Subject: [PATCH 097/170] feat: support index arg in switch_keyboard_layout --- src/config/parse_config.h | 1 + src/dispatch/bind_define.h | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 64afd88..830d22b 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -1017,6 +1017,7 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value, (*arg).v = strdup(arg_value); } else if (strcmp(func_name, "switch_keyboard_layout") == 0) { func = switch_keyboard_layout; + (*arg).i = CLAMP_INT(atoi(arg_value), 0, 100); } else if (strcmp(func_name, "setlayout") == 0) { func = setlayout; (*arg).v = strdup(arg_value); diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 0bfab15..bd06514 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -900,7 +900,13 @@ int32_t switch_keyboard_layout(const Arg *arg) { wlr_log(WLR_INFO, "Only one layout available"); return 0; } - xkb_layout_index_t next = (current + 1) % num_layouts; + + xkb_layout_index_t next = 0; + if (arg->i > 0 && arg->i <= num_layouts) { + next = arg->i - 1; + } else { + next = (current + 1) % num_layouts; + } // 6. 应用新 keymap uint32_t depressed = keyboard->modifiers.depressed; From 2f12f46919a3e224156f6c83e07aeff1f48b72e2 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 19 Feb 2026 19:07:36 +0800 Subject: [PATCH 098/170] opt: use base surface of client when xytonode in rect node --- src/fetch/common.h | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/fetch/common.h b/src/fetch/common.h index 58e69dc..57a1a8e 100644 --- a/src/fetch/common.h +++ b/src/fetch/common.h @@ -100,10 +100,6 @@ void xytonode(double x, double y, struct wlr_surface **psurface, Client **pc, surface = wlr_scene_surface_try_from_buffer( wlr_scene_buffer_from_node(node)) ->surface; - else if (node->type == WLR_SCENE_NODE_RECT) { - surface = NULL; - break; - } /* start from the topmost layer, find a sureface that can be focused by pointer, @@ -119,6 +115,13 @@ void xytonode(double x, double y, struct wlr_surface **psurface, Client **pc, l = pnode->data; } } + + if (node->type == WLR_SCENE_NODE_RECT) { + if (c) { + surface = client_surface(c); + } + break; + } } if (psurface) From f0259c6285a8a1f1ca5759414fb1bcbe54799956 Mon Sep 17 00:00:00 2001 From: Emil Miler Date: Thu, 19 Feb 2026 15:02:03 +0100 Subject: [PATCH 099/170] Include packaging status in README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 9b558fd..05eaaae 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,8 @@ https://github.com/user-attachments/assets/bb83004a-0563-4b48-ad89-6461a9b78b1f # Installation +[![Packaging status](https://repology.org/badge/vertical-allrepos/mangowc.svg)](https://repology.org/project/mangowc/versions) + ## Dependencies - glibc From 595f9e34327a65828984aa8df1f4f9251d8504e9 Mon Sep 17 00:00:00 2001 From: Baba Date: Thu, 19 Feb 2026 14:24:56 -0600 Subject: [PATCH 100/170] include tgmix in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9b558fd..d468e7f 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ https://github.com/user-attachments/assets/bb83004a-0563-4b48-ad89-6461a9b78b1f - vertical_tile - vertical_grid - vertical_scroller - +- tgmix (tile-grid mix) # Installation ## Dependencies From 07aed60245440fe2b4c6add0d50f7dfbffa550a2 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 21 Feb 2026 16:37:37 +0800 Subject: [PATCH 101/170] opt: improve some risk judgments --- src/dispatch/bind_define.h | 105 +++++++++++++++++++++++++++++++++++-- src/fetch/client.h | 3 ++ src/mango.c | 11 ++-- 3 files changed, 113 insertions(+), 6 deletions(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index bd06514..94d3d4f 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1,5 +1,6 @@ int32_t bind_to_view(const Arg *arg) { - + if (!selmon) + return 0; uint32_t target = arg->ui; if (view_current_to_back && selmon->pertag->curtag && @@ -100,6 +101,8 @@ int32_t defaultgaps(const Arg *arg) { } int32_t exchange_client(const Arg *arg) { + if (!selmon) + return 0; Client *c = selmon->sel; if (!c || c->isfloating) return 0; @@ -112,6 +115,9 @@ int32_t exchange_client(const Arg *arg) { } int32_t exchange_stack_client(const Arg *arg) { + if (!selmon) + return 0; + Client *c = selmon->sel; Client *tc = NULL; if (!c || c->isfloating || c->isfullscreen || c->ismaximizescreen) @@ -265,42 +271,56 @@ int32_t incnmaster(const Arg *arg) { } int32_t incgaps(const Arg *arg) { + if (!selmon) + return 0; setgaps(selmon->gappoh + arg->i, selmon->gappov + arg->i, selmon->gappih + arg->i, selmon->gappiv + arg->i); return 0; } int32_t incigaps(const Arg *arg) { + if (!selmon) + return 0; setgaps(selmon->gappoh, selmon->gappov, selmon->gappih + arg->i, selmon->gappiv + arg->i); return 0; } int32_t incogaps(const Arg *arg) { + if (!selmon) + return 0; setgaps(selmon->gappoh + arg->i, selmon->gappov + arg->i, selmon->gappih, selmon->gappiv); return 0; } int32_t incihgaps(const Arg *arg) { + if (!selmon) + return 0; setgaps(selmon->gappoh, selmon->gappov, selmon->gappih + arg->i, selmon->gappiv); return 0; } int32_t incivgaps(const Arg *arg) { + if (!selmon) + return 0; setgaps(selmon->gappoh, selmon->gappov, selmon->gappih, selmon->gappiv + arg->i); return 0; } int32_t incohgaps(const Arg *arg) { + if (!selmon) + return 0; setgaps(selmon->gappoh + arg->i, selmon->gappov, selmon->gappih, selmon->gappiv); return 0; } int32_t incovgaps(const Arg *arg) { + if (!selmon) + return 0; setgaps(selmon->gappoh, selmon->gappov + arg->i, selmon->gappih, selmon->gappiv); return 0; @@ -330,6 +350,8 @@ int32_t setmfact(const Arg *arg) { int32_t killclient(const Arg *arg) { Client *c = NULL; + if (!selmon) + return 0; c = selmon->sel; if (c) { pending_kill_client(c); @@ -399,6 +421,8 @@ int32_t moveresize(const Arg *arg) { int32_t movewin(const Arg *arg) { Client *c = NULL; + if (!selmon) + return 0; c = selmon->sel; if (!c || c->isfullscreen) return 0; @@ -442,6 +466,8 @@ int32_t quit(const Arg *arg) { int32_t resizewin(const Arg *arg) { Client *c = NULL; + if (!selmon) + return 0; c = selmon->sel; int32_t offsetx = 0, offsety = 0; @@ -546,6 +572,8 @@ int32_t restore_minimized(const Arg *arg) { int32_t setlayout(const Arg *arg) { int32_t jk; + if (!selmon) + return 0; for (jk = 0; jk < LENGTH(layouts); jk++) { if (strcmp(layouts[jk].name, arg->v) == 0) { @@ -571,6 +599,8 @@ int32_t setkeymode(const Arg *arg) { } int32_t set_proportion(const Arg *arg) { + if (!selmon) + return 0; if (selmon->isoverview || !is_scroller_layout(selmon)) return 0; @@ -596,6 +626,8 @@ int32_t smartmovewin(const Arg *arg) { Client *c = NULL, *tc = NULL; int32_t nx, ny; int32_t buttom, top, left, right, tar; + if (!selmon) + return 0; c = selmon->sel; if (!c || c->isfullscreen) return 0; @@ -697,6 +729,8 @@ int32_t smartresizewin(const Arg *arg) { Client *c = NULL, *tc = NULL; int32_t nw, nh; int32_t buttom, top, left, right, tar; + if (!selmon) + return 0; c = selmon->sel; if (!c || c->isfullscreen) return 0; @@ -765,6 +799,8 @@ int32_t smartresizewin(const Arg *arg) { int32_t centerwin(const Arg *arg) { Client *c = NULL; + if (!selmon) + return 0; c = selmon->sel; if (!c || c->isfullscreen || c->ismaximizescreen) @@ -943,6 +979,9 @@ int32_t switch_layout(const Arg *arg) { char *target_layout_name = NULL; uint32_t len; + if (!selmon) + return 0; + if (config.circle_layout_count != 0) { for (jk = 0; jk < config.circle_layout_count; jk++) { @@ -994,6 +1033,8 @@ int32_t switch_layout(const Arg *arg) { int32_t switch_proportion_preset(const Arg *arg) { float target_proportion = 0; + if (!selmon) + return 0; if (config.scroller_proportion_preset_count == 0) { return 0; @@ -1038,6 +1079,8 @@ int32_t switch_proportion_preset(const Arg *arg) { } int32_t tag(const Arg *arg) { + if (!selmon) + return 0; Client *target_client = selmon->sel; tag_client(arg, target_client); return 0; @@ -1045,6 +1088,8 @@ int32_t tag(const Arg *arg) { int32_t tagmon(const Arg *arg) { Monitor *m = NULL, *cm = NULL; + if (!selmon) + return 0; Client *c = focustop(selmon); if (!c) @@ -1136,6 +1181,9 @@ int32_t tagsilent(const Arg *arg) { } int32_t tagtoleft(const Arg *arg) { + if (!selmon) + return 0; + if (selmon->sel != NULL && __builtin_popcount(selmon->tagset[selmon->seltags] & TAGMASK) == 1 && selmon->tagset[selmon->seltags] > 1) { @@ -1145,6 +1193,9 @@ int32_t tagtoleft(const Arg *arg) { } int32_t tagtoright(const Arg *arg) { + if (!selmon) + return 0; + if (selmon->sel != NULL && __builtin_popcount(selmon->tagset[selmon->seltags] & TAGMASK) == 1 && selmon->tagset[selmon->seltags] & (TAGMASK >> 1)) { @@ -1173,6 +1224,8 @@ int32_t toggle_named_scratchpad(const Arg *arg) { } int32_t toggle_render_border(const Arg *arg) { + if (!selmon) + return 0; render_border = !render_border; arrange(selmon, false, false); return 0; @@ -1208,6 +1261,8 @@ int32_t toggle_scratchpad(const Arg *arg) { } int32_t togglefakefullscreen(const Arg *arg) { + if (!selmon) + return 0; Client *sel = focustop(selmon); if (sel) setfakefullscreen(sel, !sel->isfakefullscreen); @@ -1215,6 +1270,9 @@ int32_t togglefakefullscreen(const Arg *arg) { } int32_t togglefloating(const Arg *arg) { + if (!selmon) + return 0; + Client *sel = focustop(selmon); if (selmon && selmon->isoverview) @@ -1236,6 +1294,9 @@ int32_t togglefloating(const Arg *arg) { } int32_t togglefullscreen(const Arg *arg) { + if (!selmon) + return 0; + Client *sel = focustop(selmon); if (!sel) return 0; @@ -1252,6 +1313,9 @@ int32_t togglefullscreen(const Arg *arg) { } int32_t toggleglobal(const Arg *arg) { + if (!selmon) + return 0; + if (!selmon->sel) return 0; if (selmon->sel->is_in_scratchpad) { @@ -1270,12 +1334,18 @@ int32_t toggleglobal(const Arg *arg) { } int32_t togglegaps(const Arg *arg) { + if (!selmon) + return 0; + enablegaps ^= 1; arrange(selmon, false, false); return 0; } int32_t togglemaximizescreen(const Arg *arg) { + if (!selmon) + return 0; + Client *sel = focustop(selmon); if (!sel) return 0; @@ -1294,6 +1364,9 @@ int32_t togglemaximizescreen(const Arg *arg) { } int32_t toggleoverlay(const Arg *arg) { + if (!selmon) + return 0; + if (!selmon->sel || !selmon->sel->mon || selmon->sel->isfullscreen) { return 0; } @@ -1315,6 +1388,9 @@ int32_t toggleoverlay(const Arg *arg) { } int32_t toggletag(const Arg *arg) { + if (!selmon) + return 0; + uint32_t newtags; Client *sel = focustop(selmon); if (!sel) @@ -1338,13 +1414,15 @@ int32_t toggletag(const Arg *arg) { } int32_t toggleview(const Arg *arg) { + if (!selmon) + return 0; + uint32_t newtagset; uint32_t target; target = arg->ui == 0 ? ~0 & TAGMASK : arg->ui; - newtagset = - selmon ? selmon->tagset[selmon->seltags] ^ (target & TAGMASK) : 0; + newtagset = selmon->tagset[selmon->seltags] ^ (target & TAGMASK); if (newtagset) { selmon->tagset[selmon->seltags] = newtagset; @@ -1356,6 +1434,9 @@ int32_t toggleview(const Arg *arg) { } int32_t viewtoleft(const Arg *arg) { + if (!selmon) + return 0; + uint32_t target = selmon->tagset[selmon->seltags]; if (selmon->isoverview || selmon->pertag->curtag == 0) { @@ -1376,6 +1457,9 @@ int32_t viewtoleft(const Arg *arg) { } int32_t viewtoright(const Arg *arg) { + if (!selmon) + return 0; + if (selmon->isoverview || selmon->pertag->curtag == 0) { return 0; } @@ -1393,6 +1477,9 @@ int32_t viewtoright(const Arg *arg) { } int32_t viewtoleft_have_client(const Arg *arg) { + if (!selmon) + return 0; + uint32_t n; uint32_t current = get_tags_first_tag_num(selmon->tagset[selmon->seltags]); bool found = false; @@ -1417,6 +1504,9 @@ int32_t viewtoleft_have_client(const Arg *arg) { } int32_t viewtoright_have_client(const Arg *arg) { + if (!selmon) + return 0; + uint32_t n; uint32_t current = get_tags_first_tag_num(selmon->tagset[selmon->seltags]); bool found = false; @@ -1441,6 +1531,9 @@ int32_t viewtoright_have_client(const Arg *arg) { } int32_t viewcrossmon(const Arg *arg) { + if (!selmon) + return 0; + focusmon(&(Arg){.v = arg->v, .i = UNDIR}); view_in_mon(arg, true, selmon, true); return 0; @@ -1519,6 +1612,8 @@ int32_t setoption(const Arg *arg) { } int32_t minimized(const Arg *arg) { + if (!selmon) + return 0; if (selmon && selmon->isoverview) return 0; @@ -1541,6 +1636,8 @@ void fix_mon_tagset_from_overview(Monitor *m) { int32_t toggleoverview(const Arg *arg) { Client *c = NULL; + if (!selmon) + return 0; if (selmon->isoverview && ov_tab_mode && arg->i != 1 && selmon->sel) { focusstack(&(Arg){.i = 1}); @@ -1646,6 +1743,8 @@ int32_t toggle_monitor(const Arg *arg) { } int32_t scroller_stack(const Arg *arg) { + if (!selmon) + return 0; Client *c = selmon->sel; Client *stack_head = NULL; Client *source_stack_head = NULL; diff --git a/src/fetch/client.h b/src/fetch/client.h index 8fccb26..11edb76 100644 --- a/src/fetch/client.h +++ b/src/fetch/client.h @@ -83,6 +83,9 @@ setclient_coordinate_center(Client *c, Monitor *tm, struct wlr_box geom, int32_t len = 0; Monitor *m = tm ? tm : selmon; + if (!m) + return geom; + uint32_t cbw = check_hit_no_border(c) ? c->bw : 0; if (!c->no_force_center && m) { diff --git a/src/mango.c b/src/mango.c index 540395a..45d7288 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1379,6 +1379,9 @@ void applyrules(Client *c) { Client *fc = NULL; Client *parent = NULL; + if (!c) + return; + parent = client_get_parent(c); Monitor *mon = parent && parent->mon ? parent->mon : selmon; @@ -1467,7 +1470,8 @@ void applyrules(Client *c) { /*-----------------------apply rule action-------------------------*/ // rule action only apply after map not apply in the init commit - if (!client_surface(c)->mapped) + struct wlr_surface *surface = client_surface(c); + if (!surface || !surface->mapped) return; // apply swallow rule @@ -1493,6 +1497,7 @@ void applyrules(Client *c) { setmon(c, mon, newtags, !c->isopensilent && !(client_is_x11_popup(c) && client_should_ignore_focus(c)) && + mon && (!c->istagsilent || !newtags || newtags & mon->tagset[mon->seltags])); @@ -1514,7 +1519,7 @@ void applyrules(Client *c) { window in the current tag will exit fullscreen and participate in tiling */ wl_list_for_each(fc, &clients, - link) if (fc && fc != c && c->tags & fc->tags && + link) if (fc && fc != c && c->tags & fc->tags && c->mon && VISIBLEON(fc, c->mon) && ISFULLSCREEN(fc) && !c->isfloating) { clear_fullscreen_flag(fc); @@ -1533,7 +1538,7 @@ void applyrules(Client *c) { } // apply overlay rule - if (c->isoverlay) { + if (c->isoverlay && c->scene) { wlr_scene_node_reparent(&c->scene->node, layers[LyrOverlay]); wlr_scene_node_raise_to_top(&c->scene->node); } From 3d680523d6d1d9d2d9ccbb95794bd7affee97121 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 21 Feb 2026 16:53:21 +0800 Subject: [PATCH 102/170] opt: if app open when no monitor, init tags and size in updatemons --- src/mango.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/mango.c b/src/mango.c index 45d7288..93f5980 100644 --- a/src/mango.c +++ b/src/mango.c @@ -5958,6 +5958,10 @@ void updatemons(struct wl_listener *listener, void *data) { c->mon = selmon; reset_foreign_tolevel(c); } + if(c->tags ==0 && !c->is_in_scratchpad) { + c->tags = selmon->tagset[selmon->seltags]; + set_size_per(selmon,c); + } } focusclient(focustop(selmon), 1); if (selmon->lock_surface) { From d1fd12898153853e2e2e77b709f4ea1ea7aa0d7f Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 21 Feb 2026 18:52:54 +0800 Subject: [PATCH 103/170] fix: auto set monitor coordinate when no match monitor rule --- src/mango.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mango.c b/src/mango.c index 93f5980..2c4fc43 100644 --- a/src/mango.c +++ b/src/mango.c @@ -2811,6 +2811,8 @@ void createmon(struct wl_listener *listener, void *data) { m->isoverview = 0; m->sel = NULL; m->is_in_hotarea = 0; + m->m.x = INT32_MAX; + m->m.y = INT32_MAX; float scale = 1; enum wl_output_transform rr = WL_OUTPUT_TRANSFORM_NORMAL; wlr_output_state_set_scale(&state, scale); From ee8a7b5961bfd306b1b592cc15268ce2a5ab31a8 Mon Sep 17 00:00:00 2001 From: Mujk <119647238+Mujk@users.noreply.github.com> Date: Fri, 20 Feb 2026 19:56:26 +0100 Subject: [PATCH 104/170] docs: fix guix installation instructions - Rename GuixSD to Guix System (the distro was renamed in 2019) --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 05eaaae..29c356e 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ Then, install the package: dnf install mangowc ``` -## GuixSD +## Guix System The package definition is described in the source repository. First, add `mangowc` channel to `channels.scm` file: @@ -109,7 +109,8 @@ First, add `mangowc` channel to `channels.scm` file: ;; In $HOME/.config/guix/channels.scm (cons (channel (name 'mangowc) - (url "https://github.com/DreamMaoMao/mangowc.git")) + (url "https://github.com/DreamMaoMao/mangowc.git") + (branch "main")) ... ;; Your other channels %default-channels) ``` @@ -121,8 +122,8 @@ Then, run `guix pull` and after update you can either run (use-modules (mangowc)) ;; Add mangowc module ;; Add mangowc to packages list -(packages (cons - mangowc +(packages (cons* + mangowc-git ... ;; Other packages you specified %base-packages)) ``` From 6b2d694b234cae9876eb81e27991439eb51860f1 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 22 Feb 2026 11:11:07 +0800 Subject: [PATCH 105/170] update readme --- README.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c91192d..45da36c 100644 --- a/README.md +++ b/README.md @@ -27,11 +27,18 @@ This project's development is based on [dwl](https://codeberg.org/dwl/dwl/). https://github.com/user-attachments/assets/bb83004a-0563-4b48-ad89-6461a9b78b1f +# Mango's Vision + +**Mango's primary goal is stability**: After months of testing and development—and aside from a few lingering GPU compatibility issues—it should now be stable enough. I don't plan on making many breaking changes. + +**Mango's preference is practicality**: I tend to add features that genuinely help with daily workflows—things that make our work more convenient. + +**Mango won't cater to every user preference**: For niche feature requests, I'll take a wait-and-see approach. I'll only consider adding them if they get a significant number of upvotes. + # Our discord [mangowc](https://discord.gg/CPjbDxesh5) # Supported layouts - - tile - scroller - monocle @@ -41,23 +48,20 @@ https://github.com/user-attachments/assets/bb83004a-0563-4b48-ad89-6461a9b78b1f - vertical_tile - vertical_grid - vertical_scroller -- tgmix (tile-grid mix) +- tgmix + # Installation [![Packaging status](https://repology.org/badge/vertical-allrepos/mangowc.svg)](https://repology.org/project/mangowc/versions) ## Dependencies -- glibc - wayland - wayland-protocols - libinput - libdrm - libxkbcommon - pixman -- git -- meson -- ninja - libdisplay-info - libliftoff - hwdata From f8fa7a856c3a569974cc89955d640167b2958205 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 23 Feb 2026 08:01:45 +0800 Subject: [PATCH 106/170] opt: optimize frame skip logic --- src/animation/client.h | 4 ++++ src/client/client.h | 28 ------------------------- src/mango.c | 47 ++++++++++++++++++++++++------------------ 3 files changed, 31 insertions(+), 48 deletions(-) diff --git a/src/animation/client.h b/src/animation/client.h index 2588fb1..b6683ec 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -990,6 +990,10 @@ void resize(Client *c, struct wlr_box geo, int32_t interact) { c->configure_serial = client_set_size(c, c->geom.width - 2 * c->bw, c->geom.height - 2 * c->bw); + if (c->configure_serial != 0) { + c->mon->resizing_count_pending++; + } + if (c == grabc) { c->animation.running = false; c->need_output_flush = false; diff --git a/src/client/client.h b/src/client/client.h index fd81a80..4788e44 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -243,34 +243,6 @@ static inline int32_t client_is_rendered_on_mon(Client *c, Monitor *m) { return 0; } -static inline int32_t client_is_stopped(Client *c) { - int32_t pid; - siginfo_t in = {0}; -#ifdef XWAYLAND - if (client_is_x11(c)) - return 0; -#endif - - wl_client_get_credentials(c->surface.xdg->client->client, &pid, NULL, NULL); - if (waitid(P_PID, pid, &in, WNOHANG | WCONTINUED | WSTOPPED | WNOWAIT) < - 0) { - /* This process is not our child process. We cannot determine its - * stopped state; assume it is not stopped to avoid blocking frame skip. - */ - if (errno == ECHILD) - return 0; // if not our child, assume not stopped - /* Other errors, also assume not stopped. */ - return 0; - } else if (in.si_pid) { - if (in.si_code == CLD_STOPPED || in.si_code == CLD_TRAPPED) - return 1; - if (in.si_code == CLD_CONTINUED) - return 0; - } - - return 0; -} - static inline int32_t client_is_unmanaged(Client *c) { #ifdef XWAYLAND if (client_is_x11(c)) diff --git a/src/mango.c b/src/mango.c index 2c4fc43..fc7eeeb 100644 --- a/src/mango.c +++ b/src/mango.c @@ -503,13 +503,15 @@ struct Monitor { struct wl_listener request_state; struct wl_listener destroy_lock_surface; struct wlr_session_lock_surface_v1 *lock_surface; - struct wl_event_source *skip_timeout; + struct wl_event_source *skip_frame_timeout; struct wlr_box m; /* monitor area, layout-relative */ struct wlr_box w; /* window area, layout-relative */ struct wl_list layers[4]; /* LayerSurface::link */ uint32_t seltags; uint32_t tagset[2]; bool skiping_frame; + uint32_t resizing_count_pending; + uint32_t resizing_count_current; struct wl_list dwl_ipc_outputs; int32_t gappih; /* horizontal gap between windows */ @@ -786,7 +788,7 @@ 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); -static void monitor_stop_skip_timer(Monitor *m); +static void monitor_stop_skip_frame_timer(Monitor *m); static int monitor_skip_frame_timeout_callback(void *data); #include "data/static_keymap.h" @@ -2234,10 +2236,10 @@ void cleanupmon(struct wl_listener *listener, void *data) { wlr_scene_node_destroy(&m->blur->node); m->blur = NULL; } - if (m->skip_timeout) { - monitor_stop_skip_timer(m); - wl_event_source_remove(m->skip_timeout); - m->skip_timeout = NULL; + if (m->skip_frame_timeout) { + monitor_stop_skip_frame_timer(m); + wl_event_source_remove(m->skip_frame_timeout); + m->skip_frame_timeout = NULL; } m->wlr_output->data = NULL; free(m->pertag); @@ -2790,9 +2792,11 @@ void createmon(struct wl_listener *listener, void *data) { struct wl_event_loop *loop = wl_display_get_event_loop(dpy); m = wlr_output->data = ecalloc(1, sizeof(*m)); - m->skip_timeout = + m->skip_frame_timeout = wl_event_loop_add_timer(loop, monitor_skip_frame_timeout_callback, m); m->skiping_frame = false; + m->resizing_count_pending = 0; + m->resizing_count_current = 0; m->wlr_output = wlr_output; m->wlr_output->data = m; @@ -4443,10 +4447,12 @@ void client_set_opacity(Client *c, double opacity) { scene_buffer_apply_opacity, &opacity); } -void monitor_stop_skip_timer(Monitor *m) { - if (m->skip_timeout) - wl_event_source_timer_update(m->skip_timeout, 0); +void monitor_stop_skip_frame_timer(Monitor *m) { + if (m->skip_frame_timeout) + wl_event_source_timer_update(m->skip_frame_timeout, 0); m->skiping_frame = false; + m->resizing_count_pending = 0; + m->resizing_count_current = 0; } static int monitor_skip_frame_timeout_callback(void *data) { @@ -4455,20 +4461,22 @@ static int monitor_skip_frame_timeout_callback(void *data) { wl_list_for_each_safe(c, tmp, &clients, link) { c->configure_serial = 0; } - monitor_stop_skip_timer(m); + monitor_stop_skip_frame_timer(m); wlr_output_schedule_frame(m->wlr_output); return 1; } void monitor_check_skip_frame_timeout(Monitor *m) { - if (m->skiping_frame) { + if (m->skiping_frame && + m->resizing_count_pending == m->resizing_count_current) { return; } - if (m->skip_timeout) { - wl_event_source_timer_update(m->skip_timeout, 100); // 100ms + if (m->skip_frame_timeout) { + m->resizing_count_current = m->resizing_count_pending; m->skiping_frame = true; + wl_event_source_timer_update(m->skip_frame_timeout, 100); // 100ms } } @@ -4511,16 +4519,15 @@ void rendermon(struct wl_listener *listener, void *data) { // 绘制客户端 wl_list_for_each(c, &clients, link) { need_more_frames = client_draw_frame(c) || need_more_frames; - if (!animations && !(allow_tearing && frame_allow_tearing) && - c->configure_serial && client_is_rendered_on_mon(c, m) && - !client_is_stopped(c) && !grabc) { + if (!animations && !grabc && c->configure_serial && + client_is_rendered_on_mon(c, m)) { monitor_check_skip_frame_timeout(m); goto skip; } } if (m->skiping_frame) { - monitor_stop_skip_timer(m); + monitor_stop_skip_frame_timer(m); } // 只有在需要帧时才构建和提交状态 @@ -5960,9 +5967,9 @@ void updatemons(struct wl_listener *listener, void *data) { c->mon = selmon; reset_foreign_tolevel(c); } - if(c->tags ==0 && !c->is_in_scratchpad) { + if (c->tags == 0 && !c->is_in_scratchpad) { c->tags = selmon->tagset[selmon->seltags]; - set_size_per(selmon,c); + set_size_per(selmon, c); } } focusclient(focustop(selmon), 1); From 2f630c950e0f7ef4a5bc3c81abf9a6e1d0e88e05 Mon Sep 17 00:00:00 2001 From: Jiatao Liang Date: Thu, 19 Feb 2026 16:20:56 -0500 Subject: [PATCH 107/170] optional-session-manager --- nix/nixos-modules.nix | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/nix/nixos-modules.nix b/nix/nixos-modules.nix index 3381102..7a9f930 100644 --- a/nix/nixos-modules.nix +++ b/nix/nixos-modules.nix @@ -9,6 +9,10 @@ in { options = { programs.mango = { enable = lib.mkEnableOption "mango, a wayland compositor based on dwl"; + addLoginEntry = lib.mkEnableOption { + default = true; + description = "Whether to add a login entry to the display manager for mango"; + }; package = lib.mkOption { type = lib.types.package; default = self.packages.${pkgs.stdenv.hostPlatform.system}.mango; @@ -55,7 +59,7 @@ in { programs.xwayland.enable = lib.mkDefault true; services = { - displayManager.sessionPackages = [cfg.package]; + displayManager.sessionPackages = lib.mkIf cfg.addLoginEntry [ cfg.package ]; graphical-desktop.enable = lib.mkDefault true; }; From a28647585fc75fa492ae9f29ee7b9e542803ddad Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 24 Feb 2026 21:40:26 +0800 Subject: [PATCH 108/170] feat: set dbus env auto --- config.conf => assets/config.conf | 0 assets/mango-portals.conf | 10 ++++ mango.desktop => assets/mango.desktop | 0 meson.build | 6 +- src/common/util.c | 82 +++++++++++++++++++++++++++ src/common/util.h | 6 +- src/config/parse_config.h | 10 ++++ src/mango.c | 44 ++++++++++++++ 8 files changed, 155 insertions(+), 3 deletions(-) rename config.conf => assets/config.conf (100%) create mode 100644 assets/mango-portals.conf rename mango.desktop => assets/mango.desktop (100%) diff --git a/config.conf b/assets/config.conf similarity index 100% rename from config.conf rename to assets/config.conf diff --git a/assets/mango-portals.conf b/assets/mango-portals.conf new file mode 100644 index 0000000..645344a --- /dev/null +++ b/assets/mango-portals.conf @@ -0,0 +1,10 @@ +[preferred] +default=gtk +org.freedesktop.impl.portal.ScreenCast=wlr +org.freedesktop.impl.portal.Screenshot=wlr + +### My addition ### +# ignore inhibit bc gtk portal always returns as success, +# despite sway/the wlr portal not having an implementation, +# stopping firefox from using wayland idle-inhibit +org.freedesktop.impl.portal.Inhibit=none diff --git a/mango.desktop b/assets/mango.desktop similarity index 100% rename from mango.desktop rename to assets/mango.desktop diff --git a/meson.build b/meson.build index 06ccfd7..3f2e5e5 100644 --- a/meson.build +++ b/meson.build @@ -147,5 +147,7 @@ executable('mmsg', ) desktop_install_dir = join_paths(prefix, 'share/wayland-sessions') -install_data('mango.desktop', install_dir : desktop_install_dir) -install_data('config.conf', install_dir : join_paths(sysconfdir, 'mango')) +portal_install_dir = join_paths(prefix, 'share/xdg-desktop-portal') +install_data('assets/mango.desktop', install_dir : desktop_install_dir) +install_data('assets/mango-portals.conf', install_dir : portal_install_dir) +install_data('assets/config.conf', install_dir : join_paths(sysconfdir, 'mango')) diff --git a/src/common/util.c b/src/common/util.c index a15cca7..025aed6 100644 --- a/src/common/util.c +++ b/src/common/util.c @@ -92,3 +92,85 @@ uint32_t get_now_in_ms(void) { uint32_t timespec_to_ms(struct timespec *ts) { return (uint32_t)ts->tv_sec * 1000 + (uint32_t)ts->tv_nsec / 1000000; } + +char *join_strings(char *arr[], const char *sep) { + if (!arr || !arr[0]) { + char *empty = malloc(1); + if (empty) + empty[0] = '\0'; + return empty; + } + + size_t total_len = 0; + int count = 0; + for (int i = 0; arr[i] != NULL; i++) { + total_len += strlen(arr[i]); + count++; + } + if (count > 0) { + total_len += strlen(sep) * (count - 1); + } + + char *result = malloc(total_len + 1); + if (!result) + return NULL; + + result[0] = '\0'; + for (int i = 0; arr[i] != NULL; i++) { + if (i > 0) + strcat(result, sep); + strcat(result, arr[i]); + } + return result; +} + +char *join_strings_with_suffix(char *arr[], const char *suffix, + const char *sep) { + if (!arr || !arr[0]) { + char *empty = malloc(1); + if (empty) + empty[0] = '\0'; + return empty; + } + + size_t total_len = 0; + int count = 0; + for (int i = 0; arr[i] != NULL; i++) { + total_len += strlen(arr[i]) + strlen(suffix); + count++; + } + if (count > 0) { + total_len += strlen(sep) * (count - 1); + } + + char *result = malloc(total_len + 1); + if (!result) + return NULL; + + result[0] = '\0'; + for (int i = 0; arr[i] != NULL; i++) { + if (i > 0) + strcat(result, sep); + strcat(result, arr[i]); + strcat(result, suffix); + } + return result; +} + +char *string_printf(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + int len = vsnprintf(NULL, 0, fmt, args); + va_end(args); + if (len < 0) + return NULL; + + char *str = malloc(len + 1); + if (!str) + return NULL; + + va_start(args, fmt); + vsnprintf(str, len + 1, fmt, args); + va_end(args); + return str; +} diff --git a/src/common/util.h b/src/common/util.h index 8fb6033..cb232ac 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -7,4 +7,8 @@ int32_t fd_set_nonblock(int32_t fd); int32_t regex_match(const char *pattern_mb, const char *str_mb); void wl_list_append(struct wl_list *list, struct wl_list *object); uint32_t get_now_in_ms(void); -uint32_t timespec_to_ms(struct timespec *ts); \ No newline at end of file +uint32_t timespec_to_ms(struct timespec *ts); +char *join_strings(char *arr[], const char *sep); +char *join_strings_with_suffix(char *arr[], const char *suffix, + const char *sep); +char *string_printf(const char *fmt, ...); \ No newline at end of file diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 830d22b..1c33383 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -3647,6 +3647,16 @@ void reapply_cursor_style(void) { cursor_mgr = wlr_xcursor_manager_create(config.cursor_theme, cursor_size); + if(cursor_size > 0){ + char size_str[16]; + snprintf(size_str, sizeof(size_str), "%d", cursor_size); + setenv("XCURSOR_SIZE", size_str, 1); + } + + if(config.cursor_theme){ + setenv("XCURSOR_THEME", config.cursor_theme, 1); + } + Monitor *m = NULL; wl_list_for_each(m, &mons, link) { wlr_xcursor_manager_load(cursor_mgr, m->wlr_output->scale); diff --git a/src/mango.c b/src/mango.c index fc7eeeb..886a8a2 100644 --- a/src/mango.c +++ b/src/mango.c @@ -894,6 +894,12 @@ static KeyMode keymode = { .mode = {'d', 'e', 'f', 'a', 'u', 'l', 't', '\0'}, .isdefault = true, }; + +static char *env_vars[] = {"DISPLAY", + "WAYLAND_DISPLAY", + "XDG_CURRENT_DESKTOP", + "XDG_SESSION_TYPE", + NULL}; static struct { enum wp_cursor_shape_device_v1_shape shape; struct wlr_surface *surface; @@ -4727,6 +4733,41 @@ void exchange_two_client(Client *c1, Client *c2) { } } +void set_activation_env() { + if (!getenv("DBUS_SESSION_BUS_ADDRESS")) { + wlr_log(WLR_INFO, "Not updating dbus execution environment: " + "DBUS_SESSION_BUS_ADDRESS not set"); + return; + } + + wlr_log(WLR_INFO, "Updating dbus execution environment"); + + char *env_keys = join_strings(env_vars, " "); + + // first command: dbus-update-activation-environment + const char *arg1 = env_keys; + char *cmd1 = string_printf("dbus-update-activation-environment %s", arg1); + if (!cmd1) { + wlr_log(WLR_ERROR, "Failed to allocate command string"); + goto cleanup; + } + spawn(&(Arg){.v = cmd1}); + free(cmd1); + + // second command: systemctl --user + const char *action = "import-environment"; + char *cmd2 = string_printf("systemctl --user %s %s", action, env_keys); + if (!cmd2) { + wlr_log(WLR_ERROR, "Failed to allocate command string"); + goto cleanup; + } + spawn(&(Arg){.v = cmd2}); + free(cmd2); + +cleanup: + free(env_keys); +} + void // 17 run(char *startup_cmd) { @@ -4784,6 +4825,8 @@ run(char *startup_cmd) { wlr_cursor_set_xcursor(cursor, cursor_mgr, "left_ptr"); handlecursoractivity(); + set_activation_env(); + run_exec(); run_exec_once(); @@ -5280,6 +5323,7 @@ void setup(void) { setenv("XCURSOR_SIZE", "24", 1); setenv("XDG_CURRENT_DESKTOP", "mango", 1); + parse_config(); init_baked_points(); From 564df864bf3b81c1bcd576a310e5ba297d8880a5 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 25 Feb 2026 10:06:58 +0800 Subject: [PATCH 109/170] fix: popup position constrain not work for some app --- src/config/parse_config.h | 8 +- src/ext-protocol/text-input.h | 11 +-- src/fetch/monitor.h | 9 +++ src/mango.c | 137 ++++++++++++++++++++++++++-------- 4 files changed, 118 insertions(+), 47 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 1c33383..95e54c0 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -3647,13 +3647,13 @@ void reapply_cursor_style(void) { cursor_mgr = wlr_xcursor_manager_create(config.cursor_theme, cursor_size); - if(cursor_size > 0){ - char size_str[16]; - snprintf(size_str, sizeof(size_str), "%d", cursor_size); + if (cursor_size > 0) { + char size_str[16]; + snprintf(size_str, sizeof(size_str), "%d", cursor_size); setenv("XCURSOR_SIZE", size_str, 1); } - if(config.cursor_theme){ + if (config.cursor_theme) { setenv("XCURSOR_THEME", config.cursor_theme, 1); } diff --git a/src/ext-protocol/text-input.h b/src/ext-protocol/text-input.h index dbd97e1..e9f221a 100644 --- a/src/ext-protocol/text-input.h +++ b/src/ext-protocol/text-input.h @@ -77,15 +77,6 @@ Monitor *output_from_wlr_output(struct wlr_output *wlr_output) { return NULL; } -Monitor *output_nearest_to(int32_t lx, int32_t ly) { - double closest_x, closest_y; - wlr_output_layout_closest_point(output_layout, NULL, lx, ly, &closest_x, - &closest_y); - - return output_from_wlr_output( - wlr_output_layout_output_at(output_layout, closest_x, closest_y)); -} - bool output_is_usable(Monitor *m) { return m && m->wlr_output->enabled; } static bool @@ -255,7 +246,7 @@ static void update_popup_position(struct dwl_input_method_popup *popup) { cursor_rect = (struct wlr_box){0}; } - output = output_nearest_to(cursor_rect.x, cursor_rect.y); + output = get_monitor_nearest_to(cursor_rect.x, cursor_rect.y); if (!output_is_usable(output)) { return; } diff --git a/src/fetch/monitor.h b/src/fetch/monitor.h index 7a1ca4d..fd18498 100644 --- a/src/fetch/monitor.h +++ b/src/fetch/monitor.h @@ -96,3 +96,12 @@ Monitor *xytomon(double x, double y) { struct wlr_output *o = wlr_output_layout_output_at(output_layout, x, y); return o ? o->data : NULL; } + +Monitor *get_monitor_nearest_to(int32_t lx, int32_t ly) { + double closest_x, closest_y; + wlr_output_layout_closest_point(output_layout, NULL, lx, ly, &closest_x, + &closest_y); + + return output_from_wlr_output( + wlr_output_layout_output_at(output_layout, closest_x, closest_y)); +} \ No newline at end of file diff --git a/src/mango.c b/src/mango.c index 886a8a2..c8288d0 100644 --- a/src/mango.c +++ b/src/mango.c @@ -486,6 +486,16 @@ typedef struct { bool being_unmapped; } LayerSurface; +typedef struct { + struct wlr_xdg_popup *wlr_popup; + Client *client; + LayerSurface *layer; + uint32_t type; + struct wl_listener destroy; + struct wl_listener commit; + struct wl_listener reposition; +} Popup; + typedef struct { const char *symbol; void (*arrange)(Monitor *); @@ -790,6 +800,7 @@ static Client *get_focused_stack_client(Client *sc); static bool client_is_in_same_stack(Client *sc, Client *tc, Client *fc); static void monitor_stop_skip_frame_timer(Monitor *m); static int monitor_skip_frame_timeout_callback(void *data); +static Monitor *get_monitor_nearest_to(int32_t lx, int32_t ly); #include "data/static_keymap.h" #include "dispatch/bind_declare.h" @@ -895,11 +906,8 @@ static KeyMode keymode = { .isdefault = true, }; -static char *env_vars[] = {"DISPLAY", - "WAYLAND_DISPLAY", - "XDG_CURRENT_DESKTOP", - "XDG_SESSION_TYPE", - NULL}; +static char *env_vars[] = {"DISPLAY", "WAYLAND_DISPLAY", "XDG_CURRENT_DESKTOP", + "XDG_SESSION_TYPE", NULL}; static struct { enum wp_cursor_shape_device_v1_shape shape; struct wlr_surface *surface; @@ -2545,37 +2553,107 @@ void destroydecoration(struct wl_listener *listener, void *data) { wl_list_remove(&c->set_decoration_mode.link); } -void commitpopup(struct wl_listener *listener, void *data) { +static void popup_unconstrain(Popup *popup) { + struct wlr_xdg_popup *wlr_popup = popup->wlr_popup; + + if (!wlr_popup || !wlr_popup->parent) { + return; + } + + struct wlr_scene_node *parent_node = wlr_popup->parent->data; + if (!parent_node) { + wlr_log(WLR_ERROR, "Popup parent has no scene node"); + return; + } + int parent_lx, parent_ly; + wlr_scene_node_coords(parent_node, &parent_lx, &parent_ly); + + struct wlr_box *scheduled = &wlr_popup->scheduled.geometry; + int popup_lx = parent_lx + scheduled->x; + int popup_ly = parent_ly + scheduled->y; + + Monitor *mon = get_monitor_nearest_to(popup_lx, popup_ly); + + struct wlr_box usable = popup->type == LayerShell ? mon->m : mon->w; + + struct wlr_box constraint_box = { + .x = usable.x - parent_lx, + .y = usable.y - parent_ly, + .width = usable.width, + .height = usable.height, + }; + + wlr_xdg_popup_unconstrain_from_box(wlr_popup, &constraint_box); +} + +static void destroypopup(struct wl_listener *listener, void *data) { + Popup *popup = wl_container_of(listener, popup, destroy); + wl_list_remove(&popup->destroy.link); + wl_list_remove(&popup->reposition.link); + free(popup); +} + +static void commitpopup(struct wl_listener *listener, void *data) { + Popup *popup = wl_container_of(listener, popup, commit); + struct wlr_surface *surface = data; - struct wlr_xdg_popup *popup = wlr_xdg_popup_try_from_wlr_surface(surface); - LayerSurface *l = NULL; + struct wlr_xdg_popup *wkr_popup = + wlr_xdg_popup_try_from_wlr_surface(surface); + Client *c = NULL; - struct wlr_box box; + LayerSurface *l = NULL; int32_t type = -1; - if (!popup || !popup->base->initial_commit) + if (!wkr_popup || !wkr_popup->base->initial_commit) goto commitpopup_listen_free; - type = toplevel_from_wlr_surface(popup->base->surface, &c, &l); - if (!popup->parent || !popup->parent->data || type < 0) - goto commitpopup_listen_free; - - wlr_scene_node_raise_to_top(popup->parent->data); - - popup->base->surface->data = - wlr_scene_xdg_surface_create(popup->parent->data, popup->base); - if ((l && !l->mon) || (c && !c->mon)) { - wlr_xdg_popup_destroy(popup); + type = toplevel_from_wlr_surface(wkr_popup->base->surface, &c, &l); + if (!wkr_popup->parent || !wkr_popup->parent->data || type < 0) { + wlr_xdg_popup_destroy(wkr_popup); 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); + + wlr_scene_node_raise_to_top(wkr_popup->parent->data); + + wkr_popup->base->surface->data = + wlr_scene_xdg_surface_create(wkr_popup->parent->data, wkr_popup->base); + if ((l && !l->mon) || (c && !c->mon)) { + wlr_xdg_popup_destroy(wkr_popup); + goto commitpopup_listen_free; + } + + popup->client = c; + popup->layer = l; + popup->type = type; + popup->wlr_popup = wkr_popup; + + popup_unconstrain(popup); commitpopup_listen_free: - wl_list_remove(&listener->link); - free(listener); + wl_list_remove(&popup->commit.link); + popup->commit.notify = NULL; +} + +static void repositionpopup(struct wl_listener *listener, void *data) { + Popup *popup = wl_container_of(listener, popup, reposition); + popup_unconstrain(popup); +} + +static void createpopup(struct wl_listener *listener, void *data) { + struct wlr_xdg_popup *wlr_popup = data; + + Popup *popup = calloc(1, sizeof(Popup)); + if (!popup) + return; + + popup->destroy.notify = destroypopup; + wl_signal_add(&wlr_popup->events.destroy, &popup->destroy); + + popup->commit.notify = commitpopup; + wl_signal_add(&wlr_popup->base->surface->events.commit, &popup->commit); + + popup->reposition.notify = repositionpopup; + wl_signal_add(&wlr_popup->events.reposition, &popup->reposition); } void createdecoration(struct wl_listener *listener, void *data) { @@ -3165,13 +3243,6 @@ void createpointerconstraint(struct wl_listener *listener, void *data) { &pointer_constraint->destroy, destroypointerconstraint); } -void createpopup(struct wl_listener *listener, void *data) { - /* This event is raised when a client (either xdg-shell or layer-shell) - * creates a new popup. */ - struct wlr_xdg_popup *popup = data; - LISTEN_STATIC(&popup->base->surface->events.commit, commitpopup); -} - void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint) { if (active_constraint == constraint) return; From 6894a36019db78395ba10ad01a522233478fbe34 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 25 Feb 2026 11:15:56 +0800 Subject: [PATCH 110/170] opt: remove some portal file comment --- assets/mango-portals.conf | 5 ----- 1 file changed, 5 deletions(-) diff --git a/assets/mango-portals.conf b/assets/mango-portals.conf index 645344a..aebea31 100644 --- a/assets/mango-portals.conf +++ b/assets/mango-portals.conf @@ -2,9 +2,4 @@ default=gtk org.freedesktop.impl.portal.ScreenCast=wlr org.freedesktop.impl.portal.Screenshot=wlr - -### My addition ### -# ignore inhibit bc gtk portal always returns as success, -# despite sway/the wlr portal not having an implementation, -# stopping firefox from using wayland idle-inhibit org.freedesktop.impl.portal.Inhibit=none From e1c038ae087b1b683b583383f47b99479f4df67c Mon Sep 17 00:00:00 2001 From: qaqland Date: Wed, 25 Feb 2026 16:58:18 +0800 Subject: [PATCH 111/170] opt: remove unused variable in function rendermon Signed-off-by: qaqland --- src/mango.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/mango.c b/src/mango.c index c8288d0..1ba319a 100644 --- a/src/mango.c +++ b/src/mango.c @@ -4560,7 +4560,6 @@ void monitor_check_skip_frame_timeout(Monitor *m) { void rendermon(struct wl_listener *listener, void *data) { Monitor *m = wl_container_of(listener, m, frame); Client *c = NULL, *tmp = NULL; - struct wlr_output_state pending = {0}; LayerSurface *l = NULL, *tmpl = NULL; int32_t i; struct wl_list *layer_list; @@ -4621,7 +4620,6 @@ skip: wlr_scene_output_send_frame_done(m->scene_output, &now); } else { wlr_scene_output_send_frame_done(m->scene_output, &now); - wlr_output_state_finish(&pending); } // 如果需要更多帧,确保安排下一帧 From bc5cf2c7d7da4655c8f0139f16a908f93fbb4b2a Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 25 Feb 2026 17:25:05 +0800 Subject: [PATCH 112/170] opt: remove useless code --- src/mango.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/mango.c b/src/mango.c index 1ba319a..87423ba 100644 --- a/src/mango.c +++ b/src/mango.c @@ -4616,11 +4616,7 @@ void rendermon(struct wl_listener *listener, void *data) { skip: // 发送帧完成通知 clock_gettime(CLOCK_MONOTONIC, &now); - if (allow_tearing && frame_allow_tearing) { - wlr_scene_output_send_frame_done(m->scene_output, &now); - } else { - wlr_scene_output_send_frame_done(m->scene_output, &now); - } + wlr_scene_output_send_frame_done(m->scene_output, &now); // 如果需要更多帧,确保安排下一帧 if (need_more_frames && allow_frame_scheduling) { From 43d0f0f54a30dbd75027ef1cc9e361abdb71ce13 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 25 Feb 2026 17:35:19 +0800 Subject: [PATCH 113/170] opt: remove useless code --- src/mango.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/mango.c b/src/mango.c index 87423ba..3344ca7 100644 --- a/src/mango.c +++ b/src/mango.c @@ -488,8 +488,6 @@ typedef struct { typedef struct { struct wlr_xdg_popup *wlr_popup; - Client *client; - LayerSurface *layer; uint32_t type; struct wl_listener destroy; struct wl_listener commit; @@ -2622,8 +2620,6 @@ static void commitpopup(struct wl_listener *listener, void *data) { goto commitpopup_listen_free; } - popup->client = c; - popup->layer = l; popup->type = type; popup->wlr_popup = wkr_popup; From 6667708d9aa069f72687ab3c015738ea2ddf6684 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 25 Feb 2026 19:16:06 +0800 Subject: [PATCH 114/170] feat: monitor arg support multi spec match in disptach --- src/dispatch/bind_define.h | 12 +++---- src/fetch/monitor.h | 69 ++++++++++++++++++++++++++++++++++++++ src/mango.c | 1 + 3 files changed, 76 insertions(+), 6 deletions(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 94d3d4f..03d34cb 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -210,7 +210,7 @@ int32_t focusmon(const Arg *arg) { if (!m->wlr_output->enabled) { continue; } - if (regex_match(arg->v, m->wlr_output->name)) { + if (match_monitor_spec(arg->v, m)) { tm = m; break; } @@ -1102,7 +1102,7 @@ int32_t tagmon(const Arg *arg) { if (!cm->wlr_output->enabled) { continue; } - if (regex_match(arg->v, cm->wlr_output->name)) { + if (match_monitor_spec(arg->v, cm)) { m = cm; break; } @@ -1543,7 +1543,7 @@ int32_t tagcrossmon(const Arg *arg) { if (!selmon || !selmon->sel) return 0; - if (regex_match(selmon->wlr_output->name, arg->v)) { + if (match_monitor_spec(arg->v, selmon)) { tag_client(arg, selmon->sel); return 0; } @@ -1701,7 +1701,7 @@ int32_t disable_monitor(const Arg *arg) { Monitor *m = NULL; struct wlr_output_state state = {0}; wl_list_for_each(m, &mons, link) { - if (regex_match(arg->v, m->wlr_output->name)) { + if (match_monitor_spec(arg->v, m)) { wlr_output_state_set_enabled(&state, false); wlr_output_commit_state(m->wlr_output, &state); m->asleep = 1; @@ -1716,7 +1716,7 @@ int32_t enable_monitor(const Arg *arg) { Monitor *m = NULL; struct wlr_output_state state = {0}; wl_list_for_each(m, &mons, link) { - if (regex_match(arg->v, m->wlr_output->name)) { + if (match_monitor_spec(arg->v, m)) { wlr_output_state_set_enabled(&state, true); wlr_output_commit_state(m->wlr_output, &state); m->asleep = 0; @@ -1731,7 +1731,7 @@ int32_t toggle_monitor(const Arg *arg) { Monitor *m = NULL; struct wlr_output_state state = {0}; wl_list_for_each(m, &mons, link) { - if (regex_match(arg->v, m->wlr_output->name)) { + if (match_monitor_spec(arg->v, m)) { wlr_output_state_set_enabled(&state, !m->wlr_output->enabled); wlr_output_commit_state(m->wlr_output, &state); m->asleep = !m->wlr_output->enabled; diff --git a/src/fetch/monitor.h b/src/fetch/monitor.h index fd18498..d2b4fe6 100644 --- a/src/fetch/monitor.h +++ b/src/fetch/monitor.h @@ -104,4 +104,73 @@ Monitor *get_monitor_nearest_to(int32_t lx, int32_t ly) { return output_from_wlr_output( wlr_output_layout_output_at(output_layout, closest_x, closest_y)); +} + +bool match_monitor_spec(char *spec, Monitor *m) { + if (!spec || !m) + return false; + + // if the spec does not contain a colon, treat it as a match on the monitor + // name + if (strchr(spec, ':') == NULL) { + return regex_match(spec, m->wlr_output->name); + } + + char *spec_copy = strdup(spec); + if (!spec_copy) + return false; + + char *name_rule = NULL; + char *make_rule = NULL; + char *model_rule = NULL; + char *serial_rule = NULL; + + char *token = strtok(spec_copy, "&&"); + while (token) { + char *colon = strchr(token, ':'); + if (colon) { + *colon = '\0'; + char *key = token; + char *value = colon + 1; + + if (strcmp(key, "name") == 0) + name_rule = strdup(value); + else if (strcmp(key, "make") == 0) + make_rule = strdup(value); + else if (strcmp(key, "model") == 0) + model_rule = strdup(value); + else if (strcmp(key, "serial") == 0) + serial_rule = strdup(value); + } + token = strtok(NULL, "&&"); + } + + bool match = true; + + if (name_rule) { + if (!regex_match(name_rule, m->wlr_output->name)) + match = false; + } + if (make_rule) { + if (!m->wlr_output->make || strcmp(make_rule, m->wlr_output->make) != 0) + match = false; + } + if (model_rule) { + if (!m->wlr_output->model || + strcmp(model_rule, m->wlr_output->model) != 0) + match = false; + } + if (serial_rule) { + if (!m->wlr_output->serial || + strcmp(serial_rule, m->wlr_output->serial) != 0) + match = false; + } + + free(spec_copy); + free(name_rule); + free(make_rule); + free(model_rule); + free(serial_rule); + + return match; } \ No newline at end of file diff --git a/src/mango.c b/src/mango.c index 3344ca7..d4cc1fb 100644 --- a/src/mango.c +++ b/src/mango.c @@ -799,6 +799,7 @@ static bool client_is_in_same_stack(Client *sc, Client *tc, Client *fc); static void monitor_stop_skip_frame_timer(Monitor *m); static int monitor_skip_frame_timeout_callback(void *data); static Monitor *get_monitor_nearest_to(int32_t lx, int32_t ly); +static bool match_monitor_spec(char *spec, Monitor *m); #include "data/static_keymap.h" #include "dispatch/bind_declare.h" From 1dac96b426654056d27c3decf999fdfb687f8282 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 25 Feb 2026 21:53:35 +0800 Subject: [PATCH 115/170] bump version to 0.12.4 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 3f2e5e5..32b363b 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('mango', ['c', 'cpp'], - version : '0.12.3', + version : '0.12.4', ) subdir('protocols') From cbc344ab88cc0eef34e8657fbbd7004459a8b3ce Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 26 Feb 2026 08:29:27 +0800 Subject: [PATCH 116/170] fix: avoid opacity exceeds the threshold due to overshot animation curve --- src/mango.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mango.c b/src/mango.c index d4cc1fb..4be08c5 100644 --- a/src/mango.c +++ b/src/mango.c @@ -4517,6 +4517,7 @@ void scene_buffer_apply_opacity(struct wlr_scene_buffer *buffer, int32_t sx, } void client_set_opacity(Client *c, double opacity) { + opacity = CLAMP_FLOAT(opacity, 0.0f, 1.0f); wlr_scene_node_for_each_buffer(&c->scene_surface->node, scene_buffer_apply_opacity, &opacity); } From 4787402b12d0092f5db5a6aa8d8a2108f0b5b62d Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 26 Feb 2026 08:54:15 +0800 Subject: [PATCH 117/170] opt: optimize monitorrule check --- src/config/parse_config.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 95e54c0..b4fb37e 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -1844,6 +1844,13 @@ bool parse_option(Config *config, char *key, char *value) { token = strtok(NULL, ","); } + if (!rule->name && !rule->make && !rule->model && !rule->serial) { + fprintf(stderr, "\033[1m\033[31m[ERROR]:\033[33m Monitor rule " + "must have at least one of the following " + "options: name, make, model, serial\n"); + return false; + } + config->monitor_rules_count++; return !parse_error; } else if (strcmp(key, "tagrule") == 0) { From b33839198412d19621c206e5b8a6ad8584224489 Mon Sep 17 00:00:00 2001 From: Noor Latif Date: Thu, 26 Feb 2026 09:25:25 +0000 Subject: [PATCH 118/170] fix(nix): update deprecated xorg package names to top-level MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit xcbutilwm → libxcb-wm This eliminates the deprecation warnings: - "The xorg package set has been deprecated, 'xorg.xcbutilwm' has been renamed to 'libxcb-wm'" Changes: - nix/default.nix:14: xcbutilwm, → libxcb-wm, - nix/default.nix:60: xcbutilwm → libxcb-wm --- nix/default.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nix/default.nix b/nix/default.nix index 6085565..87d4bfd 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -11,7 +11,7 @@ wayland, wayland-protocols, wayland-scanner, - xcbutilwm, + libxcb-wm, xwayland, meson, ninja, @@ -57,7 +57,7 @@ stdenv.mkDerivation { ] ++ lib.optionals enableXWayland [ libX11 - xcbutilwm + libxcb-wm xwayland ]; From 835269f86bf8d1f1e05b386a6a548e7c7cae1547 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 26 Feb 2026 23:22:51 +0800 Subject: [PATCH 119/170] opt: make spawn and spawn_shell log to debug log --- src/dispatch/bind_define.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 03d34cb..5cf41d6 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -848,7 +848,7 @@ int32_t spawn_shell(const Arg *arg) { execlp("bash", "bash", "-c", arg->v, (char *)NULL); // if execlp fails, we should not reach here - wlr_log(WLR_ERROR, + wlr_log(WLR_DEBUG, "mango: failed to execute command '%s' with shell: %s\n", arg->v, strerror(errno)); _exit(EXIT_FAILURE); @@ -889,7 +889,7 @@ int32_t spawn(const Arg *arg) { execvp(argv[0], argv); // 4. execvp 失败时:打印错误并直接退出(避免 coredump) - wlr_log(WLR_ERROR, "mango: execvp '%s' failed: %s\n", argv[0], + wlr_log(WLR_DEBUG, "mango: execvp '%s' failed: %s\n", argv[0], strerror(errno)); _exit(EXIT_FAILURE); // 使用 _exit 避免缓冲区刷新等操作 } From 43965a1155b33c2174a8a829e6426d0088ef2a7a Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 1 Mar 2026 11:21:24 +0800 Subject: [PATCH 120/170] update readme --- README.md | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 45da36c..b311701 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Mango Wayland Compositor
- MangoWC Logo + MangoWM Logo
This project's development is based on [dwl](https://codeberg.org/dwl/dwl/). @@ -36,7 +36,7 @@ https://github.com/user-attachments/assets/bb83004a-0563-4b48-ad89-6461a9b78b1f **Mango won't cater to every user preference**: For niche feature requests, I'll take a wait-and-see approach. I'll only consider adding them if they get a significant number of upvotes. # Our discord -[mangowc](https://discord.gg/CPjbDxesh5) +[mangowm](https://discord.gg/CPjbDxesh5) # Supported layouts - tile @@ -52,7 +52,7 @@ https://github.com/user-attachments/assets/bb83004a-0563-4b48-ad89-6461a9b78b1f # Installation -[![Packaging status](https://repology.org/badge/vertical-allrepos/mangowc.svg)](https://repology.org/project/mangowc/versions) +[![Packaging status](https://repology.org/badge/vertical-allrepos/mangowm.svg)](https://repology.org/project/mangowm/versions) ## Dependencies @@ -71,9 +71,9 @@ https://github.com/user-attachments/assets/bb83004a-0563-4b48-ad89-6461a9b78b1f - libxcb ## Arch Linux -The package is in the Arch User Repository and is available for manual download [here](https://aur.archlinux.org/packages/mangowc-git) or through a AUR helper like yay: +The package is in the Arch User Repository and is available for manual download [here](https://aur.archlinux.org/packages/mangowm-git) or through a AUR helper like yay: ```bash -yay -S mangowc-git +yay -S mangowm-git ``` @@ -87,12 +87,12 @@ eselect repository enable guru emerge --sync guru ``` -Then, add `gui-libs/scenefx` and `gui-wm/mangowc` to the `package.accept_keywords`. +Then, add `gui-libs/scenefx` and `gui-wm/mangowm` to the `package.accept_keywords`. Finally, install the package: ```bash -emerge --ask --verbose gui-wm/mangowc +emerge --ask --verbose gui-wm/mangowm ``` ## Fedora Linux @@ -102,32 +102,32 @@ First, add the [Terra Repository](https://terra.fyralabs.com/). Then, install the package: ```bash -dnf install mangowc +dnf install mangowm ``` ## Guix System The package definition is described in the source repository. -First, add `mangowc` channel to `channels.scm` file: +First, add `mangowm` channel to `channels.scm` file: ```scheme ;; In $HOME/.config/guix/channels.scm (cons (channel - (name 'mangowc) - (url "https://github.com/DreamMaoMao/mangowc.git") + (name 'mangowm) + (url "https://github.com/mangowm/mango.git") (branch "main")) ... ;; Your other channels %default-channels) ``` Then, run `guix pull` and after update you can either run -`guix install mangowc` or add it to your configuration via: +`guix install mangowm` or add it to your configuration via: ```scheme -(use-modules (mangowc)) ;; Add mangowc module +(use-modules (mangowm)) ;; Add mangowm module -;; Add mangowc to packages list +;; Add mangowm to packages list (packages (cons* - mangowc-git + mangowm-git ... ;; Other packages you specified %base-packages)) ``` @@ -147,8 +147,8 @@ cd scenefx meson build -Dprefix=/usr sudo ninja -C build install -git clone https://github.com/DreamMaoMao/mangowc.git -cd mangowc +git clone https://github.com/mangowm/mango.git +cd mangowm meson build -Dprefix=/usr sudo ninja -C build install ``` @@ -206,9 +206,9 @@ git clone https://github.com/DreamMaoMao/mango-config.git ~/.config/mango ## Config Documentation -Refer to the repo wiki [wiki](https://github.com/DreamMaoMao/mango/wiki/) +Refer to the repo wiki [wiki](https://github.com/mangowm/mango/wiki/) -or the website docs [docs](https://mangowc.vercel.app/docs) +or the website docs [docs](https://mangowm.vercel.app/docs) # NixOS + Home-manager @@ -228,7 +228,7 @@ Here's an example of using the modules in a flake: }; flake-parts.url = "github:hercules-ci/flake-parts"; mango = { - url = "github:DreamMaoMao/mango"; + url = "github:mangowm/mango"; inputs.nixpkgs.follows = "nixpkgs"; }; }; @@ -290,9 +290,9 @@ Here's an example of using the modules in a flake: To package mango for other distributions, you can check the reference setup for: -- [nix](https://github.com/DreamMaoMao/mangowc/blob/main/nix/default.nix) -- [arch](https://aur.archlinux.org/cgit/aur.git/tree/PKGBUILD?h=mangowc-git). -- [gentoo](https://data.gpo.zugaina.org/guru/gui-wm/mangowc) +- [nix](https://github.com/mangowm/mango/blob/main/nix/default.nix) +- [arch](https://aur.archlinux.org/cgit/aur.git/tree/PKGBUILD?h=mangowm-git). +- [gentoo](https://data.gpo.zugaina.org/guru/gui-wm/mangowm) You might need to package `scenefx` for your distribution, check availability [here](https://github.com/wlrfx/scenefx.git). From e935af07c167933e312fcb8c2cfb970991bb4629 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 1 Mar 2026 11:35:51 +0800 Subject: [PATCH 121/170] project: rename guix file --- mangowc.scm => mangowm.scm | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename mangowc.scm => mangowm.scm (100%) diff --git a/mangowc.scm b/mangowm.scm similarity index 100% rename from mangowc.scm rename to mangowm.scm From 811610e481714df988b6602d7b20fedb4c68ac4c Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 1 Mar 2026 11:38:17 +0800 Subject: [PATCH 122/170] rename mangowc to mangowm --- mangowm.scm | 12 ++++++------ mmsg/mmsg.c | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/mangowm.scm b/mangowm.scm index 9c55d43..33d9504 100644 --- a/mangowm.scm +++ b/mangowm.scm @@ -1,4 +1,4 @@ -(define-module (mangowc) +(define-module (mangowm) #:use-module (guix download) #:use-module (guix git-download) #:use-module (guix gexp) @@ -18,11 +18,11 @@ #:use-module (guix licenses)) -(define-public mangowc-git +(define-public mangowm-git (package - (name "mangowc") + (name "mangowm") (version "git") - (source (local-file "." "mangowc-checkout" + (source (local-file "." "mangowm-checkout" #:recursive? #t #:select? (or (git-predicate (current-source-directory)) (const #t)))) @@ -55,10 +55,10 @@ wlroots scenefx)) (native-inputs (list pkg-config wayland-protocols)) - (home-page "https://github.com/DreamMaoMao/mangowc") + (home-page "https://github.com/DreamMaoMao/mangowm") (synopsis "Wayland compositor based on wlroots and scenefx") (description "A Wayland compositor based on wlroots and scenefx, inspired by dwl but aiming to be more feature-rich.") (license gpl3))) -mangowc-git +mangowm-git diff --git a/mmsg/mmsg.c b/mmsg/mmsg.c index fb1d04f..4e0e1d8 100644 --- a/mmsg/mmsg.c +++ b/mmsg/mmsg.c @@ -500,7 +500,7 @@ static const struct wl_registry_listener registry_listener = { static void usage(void) { fprintf(stderr, - "mmsg - MangoWC IPC\n" + "mmsg - MangoWM IPC\n" "\n" "SYNOPSIS:\n" "\tmmsg [-OTLq]\n" @@ -517,7 +517,7 @@ static void usage(void) { "\t-O Get all output (monitor) information\n" "\t-T Get number of tags\n" "\t-L Get all available layouts\n" - "\t-q Quit MangoWC\n" + "\t-q Quit mango\n" "\t-o Select output (monitor)\n" "\n" "GET OPTIONS (used with -g or -w):\n" From 755ffe06af4d8e93f8863c404024bc96ad0c636a Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 1 Mar 2026 11:42:44 +0800 Subject: [PATCH 123/170] update website in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b311701..d5171ac 100644 --- a/README.md +++ b/README.md @@ -208,7 +208,7 @@ git clone https://github.com/DreamMaoMao/mango-config.git ~/.config/mango Refer to the repo wiki [wiki](https://github.com/mangowm/mango/wiki/) -or the website docs [docs](https://mangowm.vercel.app/docs) +or the website docs [docs](https://mangowm.github.io/mango-web/) # NixOS + Home-manager From 243848f43e96e0e6c79210e014219b25c6d35e86 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 1 Mar 2026 12:02:57 +0800 Subject: [PATCH 124/170] bump version to 0.12.5 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 32b363b..85fe15b 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('mango', ['c', 'cpp'], - version : '0.12.4', + version : '0.12.5', ) subdir('protocols') From 008cb9726e36c1062f059a0505e4d6671ea425db Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 1 Mar 2026 18:04:21 +0800 Subject: [PATCH 125/170] Revert "Add nix option to enable/disable adding to session manager" --- nix/nixos-modules.nix | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/nix/nixos-modules.nix b/nix/nixos-modules.nix index 7a9f930..3381102 100644 --- a/nix/nixos-modules.nix +++ b/nix/nixos-modules.nix @@ -9,10 +9,6 @@ in { options = { programs.mango = { enable = lib.mkEnableOption "mango, a wayland compositor based on dwl"; - addLoginEntry = lib.mkEnableOption { - default = true; - description = "Whether to add a login entry to the display manager for mango"; - }; package = lib.mkOption { type = lib.types.package; default = self.packages.${pkgs.stdenv.hostPlatform.system}.mango; @@ -59,7 +55,7 @@ in { programs.xwayland.enable = lib.mkDefault true; services = { - displayManager.sessionPackages = lib.mkIf cfg.addLoginEntry [ cfg.package ]; + displayManager.sessionPackages = [cfg.package]; graphical-desktop.enable = lib.mkDefault true; }; From 9922ed26c7fb1a3456077535c01acec0390bc962 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 1 Mar 2026 18:13:40 +0800 Subject: [PATCH 126/170] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d5171ac..b927b92 100644 --- a/README.md +++ b/README.md @@ -208,7 +208,7 @@ git clone https://github.com/DreamMaoMao/mango-config.git ~/.config/mango Refer to the repo wiki [wiki](https://github.com/mangowm/mango/wiki/) -or the website docs [docs](https://mangowm.github.io/mango-web/) +or the website docs [docs](https://mangowm.github.io/) # NixOS + Home-manager From 6cc7d162817941799c5db4af2295f54a5353f778 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 2 Mar 2026 00:42:07 +0800 Subject: [PATCH 127/170] opt: only set on_demand layer focus when it request in init_commit --- src/mango.c | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/mango.c b/src/mango.c index 0864a98..81792b2 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1693,6 +1693,7 @@ void focuslayer(LayerSurface *l) { void reset_exclusive_layer(Monitor *m) { LayerSurface *l = NULL; int32_t i; + bool neet_change_focus_to_client = false; uint32_t layers_above_shell[] = { ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, ZWLR_LAYER_SHELL_V1_LAYER_TOP, @@ -1706,13 +1707,19 @@ void reset_exclusive_layer(Monitor *m) { wl_list_for_each_reverse(l, &m->layers[layers_above_shell[i]], link) { if (l == exclusive_focus && l->layer_surface->current.keyboard_interactive != - ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE) + ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE) { + exclusive_focus = NULL; + + neet_change_focus_to_client = true; + } + if (l->layer_surface->current.keyboard_interactive == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE && l->layer_surface->surface == - seat->keyboard_state.focused_surface) - focusclient(focustop(selmon), 1); + seat->keyboard_state.focused_surface) { + neet_change_focus_to_client = true; + } if (locked || l->layer_surface->current.keyboard_interactive != @@ -1725,6 +1732,10 @@ void reset_exclusive_layer(Monitor *m) { return; } } + + if (neet_change_focus_to_client) { + focusclient(focustop(selmon), 1); + } } void arrangelayers(Monitor *m) { @@ -2384,13 +2395,6 @@ void maplayersurfacenotify(struct wl_listener *listener, void *data) { } // 刷新布局,让窗口能感应到exclude_zone变化以及设置独占表面 arrangelayers(l->mon); - - // 按需交互layer需要像正常窗口一样抢占非独占layer的焦点 - if (!exclusive_focus && - l->layer_surface->current.keyboard_interactive == - ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND) { - focuslayer(l); - } } void commitlayersurfacenotify(struct wl_listener *listener, void *data) { @@ -2411,6 +2415,12 @@ void commitlayersurfacenotify(struct wl_listener *listener, void *data) { arrangelayers(l->mon); l->layer_surface->current = old_state; + // 按需交互layer只在map之前设置焦点 + if (!exclusive_focus && + l->layer_surface->current.keyboard_interactive == + ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND) { + focuslayer(l); + } return; } @@ -2455,11 +2465,6 @@ void commitlayersurfacenotify(struct wl_listener *listener, void *data) { layer_flush_blur_background(l); - if (layer_surface == exclusive_focus && - layer_surface->current.keyboard_interactive != - ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE) - exclusive_focus = NULL; - if (layer_surface->current.committed == 0 && l->mapped == layer_surface->surface->mapped) return; @@ -3335,7 +3340,6 @@ void destroylocksurface(struct wl_listener *listener, void *data) { if (lock_surface->surface != seat->keyboard_state.focused_surface) { if (exclusive_focus && !locked) { - exclusive_focus = NULL; reset_exclusive_layer(m); } return; @@ -3345,7 +3349,6 @@ void destroylocksurface(struct wl_listener *listener, void *data) { surface = wl_container_of(cur_lock->surfaces.next, surface, link); client_notify_enter(surface->surface, wlr_seat_get_keyboard(seat)); } else if (!locked) { - exclusive_focus = NULL; reset_exclusive_layer(selmon); focusclient(focustop(selmon), 1); } else { From c403a47894a1fa856ea1a913b30d350185a0b34b Mon Sep 17 00:00:00 2001 From: Han Boetes Date: Sun, 1 Mar 2026 21:58:03 +0100 Subject: [PATCH 128/170] Fix use-after-free crash in cursor surface handling ### Problem `setcursor()` stores the client-provided `wlr_surface` pointer in `last_cursor.surface`, but never registers a destroy listener on it. When the client exits (e.g. closing a launcher like fuzzel), the surface is destroyed, but `last_cursor.surface` still holds the stale pointer. If the cursor hide timeout fires while the cursor surface is alive, and the client then exits, the next mouse movement calls `handlecursoractivity()`, which passes the dangling pointer to `wlr_cursor_set_surface()`. This causes a SIGSEGV in `wl_list_insert()` inside libwayland-server, as the `wl_list` embedded in the destroyed surface struct has been freed. A secondary issue exists in `setcursorshape()`: when a client switches from a custom cursor surface to a shape cursor, `last_cursor.surface` is set to NULL but the destroy listener (if registered) is not removed, leaving a dangling listener on the destroyed surface. The crash only manifests when `cursor_hidden` is true at the moment of the mouse movement, which is why it is intermittent and difficult to reproduce. ### Root cause Confirmed via `coredumpctl debug` and `bt full`: ``` #0 wl_list_insert (libwayland-server.so) #1 wlr_cursor_set_surface (libwlroots) #2 handlecursoractivity (mango.c) #3 motionnotify (mango.c) #4 motionrelative (mango.c) #5 wl_signal_emit_mutable #6 handle_libinput_readable ``` ### Fix - Add a `wl_listener` (`last_cursor_surface_destroy_listener`) that clears `last_cursor.surface` and removes itself when the surface is destroyed. - Initialize the listener's link in `setup()` so `wl_list_empty()` checks are reliable from the start. - In `setcursor()`, remove any existing listener before registering a new one on the incoming surface. - In `setcursorshape()`, remove the destroy listener when switching to a shape cursor. - Add a NULL guard in `handlecursoractivity()` as a safety net. --- src/mango.c | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/mango.c b/src/mango.c index 0864a98..cb710d2 100644 --- a/src/mango.c +++ b/src/mango.c @@ -914,6 +914,15 @@ static struct { int32_t hotspot_y; } last_cursor; +static void last_cursor_surface_destroy(struct wl_listener *listener, void *data) { + last_cursor.surface = NULL; + wl_list_remove(&listener->link); + wl_list_init(&listener->link); +} +static struct wl_listener last_cursor_surface_destroy_listener = { + .notify = last_cursor_surface_destroy +}; + #include "client/client.h" #include "config/preset.h" @@ -2141,6 +2150,11 @@ void setcursorshape(struct wl_listener *listener, void *data) { * actually has pointer focus first. If so, we can tell the cursor to * use the provided cursor shape. */ if (event->seat_client == seat->pointer_state.focused_client) { + /* Remove surface destroy listener if active */ + if (!wl_list_empty(&last_cursor_surface_destroy_listener.link)) + wl_list_remove(&last_cursor_surface_destroy_listener.link); + wl_list_init(&last_cursor_surface_destroy_listener.link); + last_cursor.shape = event->shape; last_cursor.surface = NULL; if (!cursor_hidden) @@ -4918,10 +4932,21 @@ void setcursor(struct wl_listener *listener, void *data) { * hardware cursor on the output that it's currently on and continue to * do so as the cursor moves between outputs. */ if (event->seat_client == seat->pointer_state.focused_client) { + /* Clear previous surface destroy listener if any */ + if (!wl_list_empty(&last_cursor_surface_destroy_listener.link)) + wl_list_remove(&last_cursor_surface_destroy_listener.link); + wl_list_init(&last_cursor_surface_destroy_listener.link); + last_cursor.shape = 0; last_cursor.surface = event->surface; last_cursor.hotspot_x = event->hotspot_x; last_cursor.hotspot_y = event->hotspot_y; + + /* Track surface destruction to avoid dangling pointer */ + if (event->surface) + wl_signal_add(&event->surface->events.destroy, + &last_cursor_surface_destroy_listener); + if (!cursor_hidden) wlr_cursor_set_surface(cursor, event->surface, event->hotspot_x, event->hotspot_y); @@ -5384,6 +5409,8 @@ void handle_print_status(struct wl_listener *listener, void *data) { void setup(void) { + wl_list_init(&last_cursor_surface_destroy_listener.link); + setenv("XCURSOR_SIZE", "24", 1); setenv("XDG_CURRENT_DESKTOP", "mango", 1); @@ -5818,7 +5845,7 @@ void handlecursoractivity(void) { if (last_cursor.shape) wlr_cursor_set_xcursor(cursor, cursor_mgr, wlr_cursor_shape_v1_name(last_cursor.shape)); - else + else if (last_cursor.surface) wlr_cursor_set_surface(cursor, last_cursor.surface, last_cursor.hotspot_x, last_cursor.hotspot_y); } From 263b1845bb1d3ecfc3b40fb463a65e36134056dc Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 2 Mar 2026 08:15:52 +0800 Subject: [PATCH 129/170] opt: optimize layer focus change logic --- src/mango.c | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/mango.c b/src/mango.c index 81792b2..641e69a 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1690,7 +1690,7 @@ void focuslayer(LayerSurface *l) { client_notify_enter(l->layer_surface->surface, wlr_seat_get_keyboard(seat)); } -void reset_exclusive_layer(Monitor *m) { +void reset_exclusive_layers_focus(Monitor *m) { LayerSurface *l = NULL; int32_t i; bool neet_change_focus_to_client = false; @@ -1704,7 +1704,7 @@ void reset_exclusive_layer(Monitor *m) { return; for (i = 0; i < (int32_t)LENGTH(layers_above_shell); i++) { - wl_list_for_each_reverse(l, &m->layers[layers_above_shell[i]], link) { + wl_list_for_each(l, &m->layers[layers_above_shell[i]], link) { if (l == exclusive_focus && l->layer_surface->current.keyboard_interactive != ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE) { @@ -1714,6 +1714,12 @@ void reset_exclusive_layer(Monitor *m) { neet_change_focus_to_client = true; } + if (l->layer_surface->surface == + seat->keyboard_state.focused_surface && + l->being_unmapped) { + neet_change_focus_to_client = true; + } + if (l->layer_surface->current.keyboard_interactive == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE && l->layer_surface->surface == @@ -1724,11 +1730,14 @@ void reset_exclusive_layer(Monitor *m) { if (locked || l->layer_surface->current.keyboard_interactive != ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE || - !l->mapped || l == exclusive_focus) + l->being_unmapped) continue; /* Deactivate the focused client. */ exclusive_focus = l; - focuslayer(l); + neet_change_focus_to_client = false; + if (l->layer_surface->surface != + seat->keyboard_state.focused_surface) + focuslayer(l); return; } } @@ -1757,9 +1766,6 @@ void arrangelayers(Monitor *m) { /* Arrange non-exlusive surfaces from top->bottom */ for (i = 3; i >= 0; i--) arrangelayer(m, &m->layers[i], &usable_area, 0); - - /* Find topmost keyboard interactive layer, if such a layer exists */ - reset_exclusive_layer(m); } void // 鼠标滚轮事件 @@ -2395,6 +2401,7 @@ void maplayersurfacenotify(struct wl_listener *listener, void *data) { } // 刷新布局,让窗口能感应到exclude_zone变化以及设置独占表面 arrangelayers(l->mon); + reset_exclusive_layers_focus(l->mon); } void commitlayersurfacenotify(struct wl_listener *listener, void *data) { @@ -2414,7 +2421,6 @@ void commitlayersurfacenotify(struct wl_listener *listener, void *data) { l->layer_surface->current = l->layer_surface->pending; arrangelayers(l->mon); l->layer_surface->current = old_state; - // 按需交互layer只在map之前设置焦点 if (!exclusive_focus && l->layer_surface->current.keyboard_interactive == @@ -2482,6 +2488,7 @@ void commitlayersurfacenotify(struct wl_listener *listener, void *data) { } arrangelayers(l->mon); + reset_exclusive_layers_focus(l->mon); } void commitnotify(struct wl_listener *listener, void *data) { @@ -3340,7 +3347,7 @@ void destroylocksurface(struct wl_listener *listener, void *data) { if (lock_surface->surface != seat->keyboard_state.focused_surface) { if (exclusive_focus && !locked) { - reset_exclusive_layer(m); + reset_exclusive_layers_focus(m); } return; } @@ -3349,8 +3356,7 @@ void destroylocksurface(struct wl_listener *listener, void *data) { surface = wl_container_of(cur_lock->surfaces.next, surface, link); client_notify_enter(surface->surface, wlr_seat_get_keyboard(seat)); } else if (!locked) { - reset_exclusive_layer(selmon); - focusclient(focustop(selmon), 1); + reset_exclusive_layers_focus(selmon); } else { wlr_seat_keyboard_clear_focus(seat); } @@ -5846,17 +5852,20 @@ void unmaplayersurfacenotify(struct wl_listener *listener, void *data) { init_fadeout_layers(l); wlr_scene_node_set_enabled(&l->scene->node, false); + if (l == exclusive_focus) exclusive_focus = NULL; + if (l->layer_surface->output && (l->mon = l->layer_surface->output->data)) arrangelayers(l->mon); - if (l->layer_surface->surface == seat->keyboard_state.focused_surface) - focusclient(focustop(selmon), 1); + + reset_exclusive_layers_focus(l->mon); + motionnotify(0, NULL, 0, 0, 0, 0); - l->being_unmapped = false; layer_flush_blur_background(l); wlr_scene_node_destroy(&l->shadow->node); l->shadow = NULL; + l->being_unmapped = false; } void unmapnotify(struct wl_listener *listener, void *data) { From 20dbffdfafbdc7c4aa5fa464b3422709076667a6 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 2 Mar 2026 08:35:32 +0800 Subject: [PATCH 130/170] opt: avoid unnecessary action when layer surface commit --- src/mango.c | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/mango.c b/src/mango.c index 641e69a..2faf35d 100644 --- a/src/mango.c +++ b/src/mango.c @@ -2476,19 +2476,26 @@ void commitlayersurfacenotify(struct wl_listener *listener, void *data) { return; l->mapped = layer_surface->surface->mapped; - if (scene_layer != l->scene->node.parent) { - wlr_scene_node_reparent(&l->scene->node, scene_layer); - wl_list_remove(&l->link); - wl_list_insert(&l->mon->layers[layer_surface->current.layer], &l->link); - wlr_scene_node_reparent( - &l->popups->node, - (layer_surface->current.layer < ZWLR_LAYER_SHELL_V1_LAYER_TOP - ? layers[LyrTop] - : scene_layer)); + if (layer_surface->current.committed & WLR_LAYER_SURFACE_V1_STATE_LAYER) { + if (scene_layer != l->scene->node.parent) { + wlr_scene_node_reparent(&l->scene->node, scene_layer); + wl_list_remove(&l->link); + wl_list_insert(&l->mon->layers[layer_surface->current.layer], + &l->link); + wlr_scene_node_reparent( + &l->popups->node, + (layer_surface->current.layer < ZWLR_LAYER_SHELL_V1_LAYER_TOP + ? layers[LyrTop] + : scene_layer)); + } + + arrangelayers(l->mon); } - arrangelayers(l->mon); - reset_exclusive_layers_focus(l->mon); + if (layer_surface->current.committed & + WLR_LAYER_SURFACE_V1_STATE_KEYBOARD_INTERACTIVITY) { + reset_exclusive_layers_focus(l->mon); + } } void commitnotify(struct wl_listener *listener, void *data) { From ad754167b7d810dd63bbfd460e80bbd30e531778 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 2 Mar 2026 09:40:50 +0800 Subject: [PATCH 131/170] fix: last_cursor surface destroy detect error --- src/mango.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/mango.c b/src/mango.c index af82733..7c6106f 100644 --- a/src/mango.c +++ b/src/mango.c @@ -800,6 +800,8 @@ static void monitor_stop_skip_frame_timer(Monitor *m); static int monitor_skip_frame_timeout_callback(void *data); static Monitor *get_monitor_nearest_to(int32_t lx, int32_t ly); static bool match_monitor_spec(char *spec, Monitor *m); +static void last_cursor_surface_destroy(struct wl_listener *listener, + void *data); #include "data/static_keymap.h" #include "dispatch/bind_declare.h" @@ -914,15 +916,6 @@ static struct { int32_t hotspot_y; } last_cursor; -static void last_cursor_surface_destroy(struct wl_listener *listener, void *data) { - last_cursor.surface = NULL; - wl_list_remove(&listener->link); - wl_list_init(&listener->link); -} -static struct wl_listener last_cursor_surface_destroy_listener = { - .notify = last_cursor_surface_destroy -}; - #include "client/client.h" #include "config/preset.h" @@ -973,6 +966,8 @@ static struct wl_listener new_session_lock = {.notify = locksession}; static struct wl_listener drm_lease_request = {.notify = requestdrmlease}; static struct wl_listener keyboard_shortcuts_inhibit_new_inhibitor = { .notify = handle_keyboard_shortcuts_inhibit_new_inhibitor}; +static struct wl_listener last_cursor_surface_destroy_listener = { + .notify = last_cursor_surface_destroy}; #ifdef XWAYLAND static void fix_xwayland_unmanaged_coordinate(Client *c); @@ -2159,6 +2154,11 @@ void checkidleinhibitor(struct wlr_surface *exclude) { wlr_idle_notifier_v1_set_inhibited(idle_notifier, inhibited); } +void last_cursor_surface_destroy(struct wl_listener *listener, void *data) { + last_cursor.surface = NULL; + wl_list_remove(&listener->link); +} + void setcursorshape(struct wl_listener *listener, void *data) { struct wlr_cursor_shape_manager_v1_request_set_shape_event *event = data; if (cursor_mode != CurNormal && cursor_mode != CurPressed) @@ -2168,9 +2168,9 @@ void setcursorshape(struct wl_listener *listener, void *data) { * use the provided cursor shape. */ if (event->seat_client == seat->pointer_state.focused_client) { /* Remove surface destroy listener if active */ - if (!wl_list_empty(&last_cursor_surface_destroy_listener.link)) + if (last_cursor.surface && + last_cursor_surface_destroy_listener.link.prev != NULL) wl_list_remove(&last_cursor_surface_destroy_listener.link); - wl_list_init(&last_cursor_surface_destroy_listener.link); last_cursor.shape = event->shape; last_cursor.surface = NULL; @@ -4949,9 +4949,9 @@ void setcursor(struct wl_listener *listener, void *data) { * do so as the cursor moves between outputs. */ if (event->seat_client == seat->pointer_state.focused_client) { /* Clear previous surface destroy listener if any */ - if (!wl_list_empty(&last_cursor_surface_destroy_listener.link)) + if (last_cursor.surface && + last_cursor_surface_destroy_listener.link.prev != NULL) wl_list_remove(&last_cursor_surface_destroy_listener.link); - wl_list_init(&last_cursor_surface_destroy_listener.link); last_cursor.shape = 0; last_cursor.surface = event->surface; @@ -5425,8 +5425,6 @@ void handle_print_status(struct wl_listener *listener, void *data) { void setup(void) { - wl_list_init(&last_cursor_surface_destroy_listener.link); - setenv("XCURSOR_SIZE", "24", 1); setenv("XDG_CURRENT_DESKTOP", "mango", 1); @@ -5662,6 +5660,8 @@ void setup(void) { LISTEN_STATIC(&cursor->events.hold_end, hold_end); seat = wlr_seat_create(dpy, "seat0"); + + wl_list_init(&last_cursor_surface_destroy_listener.link); wl_signal_add(&seat->events.request_set_cursor, &request_cursor); wl_signal_add(&seat->events.request_set_selection, &request_set_sel); wl_signal_add(&seat->events.request_set_primary_selection, From 46e867deb9dfd58abb22ec92b19461d3761b1f3d Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 2 Mar 2026 20:53:52 +0800 Subject: [PATCH 132/170] opt: always arrangelayers if layer commit --- src/mango.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mango.c b/src/mango.c index 7c6106f..3bfdfb7 100644 --- a/src/mango.c +++ b/src/mango.c @@ -2502,10 +2502,10 @@ void commitlayersurfacenotify(struct wl_listener *listener, void *data) { ? layers[LyrTop] : scene_layer)); } - - arrangelayers(l->mon); } + arrangelayers(l->mon); + if (layer_surface->current.committed & WLR_LAYER_SURFACE_V1_STATE_KEYBOARD_INTERACTIVITY) { reset_exclusive_layers_focus(l->mon); From 9aa2d3cd33ce233c3986263e52163112a0c0caec Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 3 Mar 2026 13:03:35 +0800 Subject: [PATCH 133/170] opt: rename hide_source var to hide_cursor_source --- src/config/parse_config.h | 12 ++++++------ src/mango.c | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index b4fb37e..d2946f6 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -3637,10 +3637,10 @@ void reapply_monitor_rules(void) { } void reapply_cursor_style(void) { - if (hide_source) { - wl_event_source_timer_update(hide_source, 0); - wl_event_source_remove(hide_source); - hide_source = NULL; + if (hide_cursor_source) { + wl_event_source_timer_update(hide_cursor_source, 0); + wl_event_source_remove(hide_cursor_source); + hide_cursor_source = NULL; } wlr_cursor_unset_image(cursor); @@ -3671,12 +3671,12 @@ void reapply_cursor_style(void) { wlr_cursor_set_xcursor(cursor, cursor_mgr, "left_ptr"); - hide_source = wl_event_loop_add_timer(wl_display_get_event_loop(dpy), + hide_cursor_source = wl_event_loop_add_timer(wl_display_get_event_loop(dpy), hidecursor, cursor); if (cursor_hidden) { wlr_cursor_unset_image(cursor); } else { - wl_event_source_timer_update(hide_source, cursor_hide_timeout * 1000); + wl_event_source_timer_update(hide_cursor_source, cursor_hide_timeout * 1000); } } diff --git a/src/mango.c b/src/mango.c index 3bfdfb7..d7245de 100644 --- a/src/mango.c +++ b/src/mango.c @@ -898,7 +898,7 @@ struct dvec2 *baked_points_focus; struct dvec2 *baked_points_opafadein; struct dvec2 *baked_points_opafadeout; -static struct wl_event_source *hide_source; +static struct wl_event_source *hide_cursor_source; static bool cursor_hidden = false; static bool tag_combo = false; static const char *cli_config_path = NULL; @@ -5631,7 +5631,7 @@ void setup(void) { cursor_shape_mgr = wlr_cursor_shape_manager_v1_create(dpy, 1); wl_signal_add(&cursor_shape_mgr->events.request_set_shape, &request_set_cursor_shape); - hide_source = wl_event_loop_add_timer(wl_display_get_event_loop(dpy), + hide_cursor_source = wl_event_loop_add_timer(wl_display_get_event_loop(dpy), hidecursor, cursor); /* * Configures a seat, which is a single "seat" at which a user sits and @@ -5851,7 +5851,7 @@ void overview_restore(Client *c, const Arg *arg) { } void handlecursoractivity(void) { - wl_event_source_timer_update(hide_source, cursor_hide_timeout * 1000); + wl_event_source_timer_update(hide_cursor_source, cursor_hide_timeout * 1000); if (!cursor_hidden) return; From 1e1d41e626aa12057c03ec79ed11bcc5619f6748 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 3 Mar 2026 13:23:15 +0800 Subject: [PATCH 134/170] feat: add windowrule option indleinhibit_when_focus --- src/config/parse_config.h | 9 ++++++-- src/mango.c | 47 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index d2946f6..8a96536 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -78,6 +78,7 @@ typedef struct { int32_t ignore_maximize; int32_t ignore_minimize; int32_t isnosizehint; + int32_t indleinhibit_when_focus; const char *monitor; int32_t offsetx; int32_t offsety; @@ -2022,6 +2023,7 @@ bool parse_option(Config *config, char *key, char *value) { rule->ignore_maximize = -1; rule->ignore_minimize = -1; rule->isnosizehint = -1; + rule->indleinhibit_when_focus = -1; rule->isterm = -1; rule->allow_csd = -1; rule->force_maximize = -1; @@ -2132,6 +2134,8 @@ bool parse_option(Config *config, char *key, char *value) { rule->ignore_minimize = atoi(val); } else if (strcmp(key, "isnosizehint") == 0) { rule->isnosizehint = atoi(val); + } else if (strcmp(key, "indleinhibit_when_focus") == 0) { + rule->indleinhibit_when_focus = atoi(val); } else if (strcmp(key, "isterm") == 0) { rule->isterm = atoi(val); } else if (strcmp(key, "allow_csd") == 0) { @@ -3672,11 +3676,12 @@ void reapply_cursor_style(void) { wlr_cursor_set_xcursor(cursor, cursor_mgr, "left_ptr"); hide_cursor_source = wl_event_loop_add_timer(wl_display_get_event_loop(dpy), - hidecursor, cursor); + hidecursor, cursor); if (cursor_hidden) { wlr_cursor_unset_image(cursor); } else { - wl_event_source_timer_update(hide_cursor_source, cursor_hide_timeout * 1000); + wl_event_source_timer_update(hide_cursor_source, + cursor_hide_timeout * 1000); } } diff --git a/src/mango.c b/src/mango.c index d7245de..8a93a79 100644 --- a/src/mango.c +++ b/src/mango.c @@ -347,7 +347,7 @@ struct Client { struct wlr_foreign_toplevel_handle_v1 *foreign_toplevel; int32_t isfloating, isurgent, isfullscreen, isfakefullscreen, need_float_size_reduce, isminimized, isoverlay, isnosizehint, - ignore_maximize, ignore_minimize; + ignore_maximize, ignore_minimize, indleinhibit_when_focus; int32_t ismaximizescreen; int32_t overview_backup_bw; int32_t fullscreen_backup_x, fullscreen_backup_y, fullscreen_backup_w, @@ -802,6 +802,8 @@ static Monitor *get_monitor_nearest_to(int32_t lx, int32_t ly); static bool match_monitor_spec(char *spec, Monitor *m); static void last_cursor_surface_destroy(struct wl_listener *listener, void *data); +static int32_t keep_idle_inhibit(void *data); +static void check_keep_idle_inhibit(Client *c); #include "data/static_keymap.h" #include "dispatch/bind_declare.h" @@ -899,6 +901,7 @@ struct dvec2 *baked_points_opafadein; struct dvec2 *baked_points_opafadeout; static struct wl_event_source *hide_cursor_source; +static struct wl_event_source *keep_idle_inhibit_source; static bool cursor_hidden = false; static bool tag_combo = false; static const char *cli_config_path = NULL; @@ -1335,6 +1338,7 @@ static void apply_rule_properties(Client *c, const ConfigWinRule *r) { APPLY_INT_PROP(c, r, ignore_maximize); APPLY_INT_PROP(c, r, ignore_minimize); APPLY_INT_PROP(c, r, isnosizehint); + APPLY_INT_PROP(c, r, indleinhibit_when_focus); APPLY_INT_PROP(c, r, isunglobal); APPLY_INT_PROP(c, r, noblur); APPLY_INT_PROP(c, r, allow_shortcuts_inhibit); @@ -3482,6 +3486,8 @@ void focusclient(Client *c, int32_t lift) { selmon->sel = c; c->isfocusing = true; + check_keep_idle_inhibit(c); + if (last_focus_client && !last_focus_client->iskilling && last_focus_client != c) { last_focus_client->isfocusing = false; @@ -4029,6 +4035,7 @@ void init_client_properties(Client *c) { c->force_tiled_state = 1; c->force_tearing = 0; c->allow_shortcuts_inhibit = SHORTCUTS_INHIBIT_ENABLE; + c->indleinhibit_when_focus = 0; c->scroller_proportion_single = 0.0f; c->float_geom.width = 0; c->float_geom.height = 0; @@ -5564,6 +5571,9 @@ void setup(void) { idle_inhibit_mgr = wlr_idle_inhibit_v1_create(dpy); wl_signal_add(&idle_inhibit_mgr->events.new_inhibitor, &new_idle_inhibitor); + keep_idle_inhibit_source = wl_event_loop_add_timer( + wl_display_get_event_loop(dpy), keep_idle_inhibit, NULL); + layer_shell = wlr_layer_shell_v1_create(dpy, 4); wl_signal_add(&layer_shell->events.new_surface, &new_layer_surface); @@ -5632,7 +5642,7 @@ void setup(void) { wl_signal_add(&cursor_shape_mgr->events.request_set_shape, &request_set_cursor_shape); hide_cursor_source = wl_event_loop_add_timer(wl_display_get_event_loop(dpy), - hidecursor, cursor); + hidecursor, cursor); /* * Configures a seat, which is a single "seat" at which a user sits and * operates the computer. This conceptually includes up to one keyboard, @@ -5851,7 +5861,8 @@ void overview_restore(Client *c, const Arg *arg) { } void handlecursoractivity(void) { - wl_event_source_timer_update(hide_cursor_source, cursor_hide_timeout * 1000); + wl_event_source_timer_update(hide_cursor_source, + cursor_hide_timeout * 1000); if (!cursor_hidden) return; @@ -5872,6 +5883,36 @@ int32_t hidecursor(void *data) { return 1; } +void check_keep_idle_inhibit(Client *c) { + if (c && c->indleinhibit_when_focus && keep_idle_inhibit_source) { + wl_event_source_timer_update(keep_idle_inhibit_source, 1000); + } +} + +int32_t keep_idle_inhibit(void *data) { + + if (!idle_inhibit_mgr) { + wl_event_source_timer_update(keep_idle_inhibit_source, 0); + return 1; + } + + if (session && !session->active) { + wl_event_source_timer_update(keep_idle_inhibit_source, 0); + return 1; + } + + if (!selmon || !selmon->sel || !selmon->sel->indleinhibit_when_focus) { + wl_event_source_timer_update(keep_idle_inhibit_source, 0); + return 1; + } + + if (seat && idle_notifier) { + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + wl_event_source_timer_update(keep_idle_inhibit_source, 1000); + } + return 1; +} + void unlocksession(struct wl_listener *listener, void *data) { SessionLock *lock = wl_container_of(listener, lock, unlock); destroylock(lock, 1); From 7f99b5ff4870aa44b5237a5eb16edbbf77671cff Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 4 Mar 2026 12:17:44 +0800 Subject: [PATCH 135/170] feat: use monitor spec to match windowrule monitor field --- src/config/parse_config.h | 2 +- src/mango.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 8a96536..ce5db53 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -79,7 +79,7 @@ typedef struct { int32_t ignore_minimize; int32_t isnosizehint; int32_t indleinhibit_when_focus; - const char *monitor; + char *monitor; int32_t offsetx; int32_t offsety; int32_t width; diff --git a/src/mango.c b/src/mango.c index 8a93a79..9a027a3 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1443,7 +1443,7 @@ void applyrules(Client *c) { // set monitor of client wl_list_for_each(m, &mons, link) { - if (regex_match(r->monitor, m->wlr_output->name)) { + if (match_monitor_spec(r->monitor, m)) { mon = m; } } From bf10fabfc2ef43e024165df97056264c09e21fd6 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 5 Mar 2026 08:27:20 +0800 Subject: [PATCH 136/170] fix: popup unconstrain cross monitor --- src/mango.c | 44 ++++++++++++++++++-------------------------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/src/mango.c b/src/mango.c index 9a027a3..9edc25f 100644 --- a/src/mango.c +++ b/src/mango.c @@ -488,7 +488,6 @@ typedef struct { typedef struct { struct wlr_xdg_popup *wlr_popup; - uint32_t type; struct wl_listener destroy; struct wl_listener commit; struct wl_listener reposition; @@ -2591,6 +2590,9 @@ void destroydecoration(struct wl_listener *listener, void *data) { static void popup_unconstrain(Popup *popup) { struct wlr_xdg_popup *wlr_popup = popup->wlr_popup; + Client *c = NULL; + LayerSurface *l = NULL; + int32_t type = -1; if (!wlr_popup || !wlr_popup->parent) { return; @@ -2601,16 +2603,17 @@ static void popup_unconstrain(Popup *popup) { wlr_log(WLR_ERROR, "Popup parent has no scene node"); return; } + + type = toplevel_from_wlr_surface(wlr_popup->base->surface, &c, &l); + if ((l && !l->mon) || (c && !c->mon)) { + wlr_xdg_popup_destroy(wlr_popup); + return; + } + int parent_lx, parent_ly; wlr_scene_node_coords(parent_node, &parent_lx, &parent_ly); - struct wlr_box *scheduled = &wlr_popup->scheduled.geometry; - int popup_lx = parent_lx + scheduled->x; - int popup_ly = parent_ly + scheduled->y; - - Monitor *mon = get_monitor_nearest_to(popup_lx, popup_ly); - - struct wlr_box usable = popup->type == LayerShell ? mon->m : mon->w; + struct wlr_box usable = type == LayerShell ? l->mon->m : c->mon->w; struct wlr_box constraint_box = { .x = usable.x - parent_lx, @@ -2633,33 +2636,22 @@ static void commitpopup(struct wl_listener *listener, void *data) { Popup *popup = wl_container_of(listener, popup, commit); struct wlr_surface *surface = data; - struct wlr_xdg_popup *wkr_popup = + struct wlr_xdg_popup *wlr_popup = wlr_xdg_popup_try_from_wlr_surface(surface); - Client *c = NULL; - LayerSurface *l = NULL; - int32_t type = -1; - - if (!wkr_popup || !wkr_popup->base->initial_commit) + if (!wlr_popup || !wlr_popup->base->initial_commit) goto commitpopup_listen_free; - type = toplevel_from_wlr_surface(wkr_popup->base->surface, &c, &l); - if (!wkr_popup->parent || !wkr_popup->parent->data || type < 0) { - wlr_xdg_popup_destroy(wkr_popup); + if (!wlr_popup->parent || !wlr_popup->parent->data) { goto commitpopup_listen_free; } - wlr_scene_node_raise_to_top(wkr_popup->parent->data); + wlr_scene_node_raise_to_top(wlr_popup->parent->data); - wkr_popup->base->surface->data = - wlr_scene_xdg_surface_create(wkr_popup->parent->data, wkr_popup->base); - if ((l && !l->mon) || (c && !c->mon)) { - wlr_xdg_popup_destroy(wkr_popup); - goto commitpopup_listen_free; - } + wlr_popup->base->surface->data = + wlr_scene_xdg_surface_create(wlr_popup->parent->data, wlr_popup->base); - popup->type = type; - popup->wlr_popup = wkr_popup; + popup->wlr_popup = wlr_popup; popup_unconstrain(popup); From 0f68187cd0a48d18f8f25dcc8f12bfa666524156 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 5 Mar 2026 11:47:17 +0800 Subject: [PATCH 137/170] opt: support restore size per when master change --- src/layout/arrange.h | 78 ++++++++++++++++++++++++++++++++------------ src/mango.c | 16 +++++++++ 2 files changed, 74 insertions(+), 20 deletions(-) diff --git a/src/layout/arrange.h b/src/layout/arrange.h index 1ef89c3..53c6489 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -1,11 +1,31 @@ +void save_old_size_per(Monitor *m) { + Client *c = NULL; + + wl_list_for_each(c, &clients, link) { + if (VISIBLEON(c, m) && ISTILED(c)) { + c->old_master_inner_per = c->master_inner_per; + c->old_stack_inner_per = c->stack_inner_per; + } + } +} + void restore_size_per(Monitor *m, Client *c) { Client *fc = NULL; - double total_master_inner_per = 0; - double total_stack_inner_per = 0; if (!m || !c) return; + wl_list_for_each(fc, &clients, link) { + if (VISIBLEON(fc, m) && ISTILED(fc)) { + fc->old_ismaster = fc->ismaster; + } + } + + c->old_master_inner_per = c->master_inner_per; + c->old_stack_inner_per = c->stack_inner_per; + + pre_caculate_before_arrange(m, false, false, true); + const Layout *current_layout = m->pertag->ltidxs[m->pertag->curtag]; if (current_layout->id == SCROLLER || @@ -15,7 +35,7 @@ void restore_size_per(Monitor *m, Client *c) { return; } - if (current_layout->id == CENTER_TILE || c->ismaster) { + if (current_layout->id == CENTER_TILE) { wl_list_for_each(fc, &clients, link) { if (VISIBLEON(fc, m) && ISTILED(fc) && !c->ismaster) { set_size_per(m, fc); @@ -24,19 +44,28 @@ void restore_size_per(Monitor *m, Client *c) { return; } - wl_list_for_each(fc, &clients, link) { - if (VISIBLEON(fc, m) && ISTILED(fc) && fc != c) { - if (fc->ismaster) { - total_master_inner_per += fc->master_inner_per; - } else { - total_stack_inner_per += fc->stack_inner_per; - } - } + if (!c->ismaster && c->old_stack_inner_per < 1.0 && + c->stack_inner_per < 1.0) { + c->stack_inner_per = (1.0 - c->stack_inner_per) * + c->old_stack_inner_per / + (1.0 - c->old_stack_inner_per); } - if (!c->ismaster && total_stack_inner_per) { - c->stack_inner_per = total_stack_inner_per * c->stack_inner_per / - (1 - c->stack_inner_per); + if (c->ismaster && c->old_master_inner_per < 1.0 && + c->master_inner_per < 1.0) { + c->master_inner_per = (1.0 - c->master_inner_per) * + c->old_master_inner_per / + (1.0 - c->old_master_inner_per); + } + + wl_list_for_each(fc, &clients, link) { + if (VISIBLEON(fc, m) && ISTILED(fc) && fc != c && !fc->ismaster && + fc->old_ismaster && fc->old_stack_inner_per < 1.0 && + fc->stack_inner_per < 1.0) { + fc->stack_inner_per = (1.0 - fc->stack_inner_per) * + fc->old_stack_inner_per / + (1.0 - fc->old_stack_inner_per); + } } } @@ -705,8 +734,8 @@ void reset_size_per_mon(Monitor *m, int32_t tile_cilent_num, } } -void // 17 -arrange(Monitor *m, bool want_animation, bool from_view) { +void pre_caculate_before_arrange(Monitor *m, bool want_animation, + bool from_view, bool only_caculate) { Client *c = NULL; double total_stack_inner_percent = 0; double total_master_inner_percent = 0; @@ -795,14 +824,17 @@ arrange(Monitor *m, bool want_animation, bool from_view) { i++; } - set_arrange_visible(m, c, want_animation); + if (!only_caculate) + set_arrange_visible(m, c, want_animation); } else { - set_arrange_hidden(m, c, want_animation); + if (!only_caculate) + set_arrange_hidden(m, c, want_animation); } } - if (c->mon == m && c->ismaximizescreen && !c->animation.tagouted && - !c->animation.tagouting && VISIBLEON(c, m)) { + if (!only_caculate && c->mon == m && c->ismaximizescreen && + !c->animation.tagouted && !c->animation.tagouting && + VISIBLEON(c, m)) { reset_maximizescreen_size(c); } } @@ -811,6 +843,12 @@ arrange(Monitor *m, bool want_animation, bool from_view) { m, m->visible_tiling_clients, total_left_stack_hight_percent, total_right_stack_hight_percent, total_stack_inner_percent, total_master_inner_percent, master_num, stack_num); +} + +void // 17 +arrange(Monitor *m, bool want_animation, bool from_view) { + + pre_caculate_before_arrange(m, want_animation, from_view, false); if (m->isoverview) { overviewlayout.arrange(m); diff --git a/src/mango.c b/src/mango.c index 9edc25f..3ae5ccc 100644 --- a/src/mango.c +++ b/src/mango.c @@ -412,6 +412,7 @@ struct Client { double old_master_mfact_per, old_master_inner_per, old_stack_inner_per; double old_scroller_pproportion; bool ismaster; + bool old_ismaster; bool cursor_in_upper_half, cursor_in_left_half; bool isleftstack; int32_t tearing_hint; @@ -803,6 +804,8 @@ static void last_cursor_surface_destroy(struct wl_listener *listener, void *data); static int32_t keep_idle_inhibit(void *data); static void check_keep_idle_inhibit(Client *c); +static void pre_caculate_before_arrange(Monitor *m, bool want_animation, + bool from_view, bool only_caculate); #include "data/static_keymap.h" #include "dispatch/bind_declare.h" @@ -3981,6 +3984,7 @@ void init_client_properties(Client *c) { c->swallowing = NULL; c->swallowedby = NULL; c->ismaster = 0; + c->old_ismaster = 0; c->isleftstack = 0; c->ismaximizescreen = 0; c->isfullscreen = 0; @@ -5047,6 +5051,10 @@ setfloating(Client *c, int32_t floating) { restore_size_per(c->mon, c); } + if (c->isfloating && !old_floating_state) { + save_old_size_per(c->mon); + } + if (!c->force_maximize) client_set_maximized(c, false); @@ -5133,6 +5141,10 @@ void setmaximizescreen(Client *c, int32_t maximizescreen) { restore_size_per(c->mon, c); } + if (c->ismaximizescreen && !old_maximizescreen_state) { + save_old_size_per(c->mon); + } + if (!c->force_maximize && !c->ismaximizescreen) { client_set_maximized(c, false); } else if (!c->force_maximize && c->ismaximizescreen) { @@ -5204,6 +5216,10 @@ void setfullscreen(Client *c, int32_t fullscreen) // 用自定义全屏代理自 restore_size_per(c->mon, c); } + if (c->isfullscreen && !old_fullscreen_state) { + save_old_size_per(c->mon); + } + arrange(c->mon, false, false); } From 9df273cdf98cb28057ce20fd9adb9a73215800b1 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 5 Mar 2026 23:03:01 +0800 Subject: [PATCH 138/170] opt: clear some comment --- .github/workflows/stale.yml | 2 +- src/layout/arrange.h | 12 ++++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 43e8bad..d0b19c1 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -16,7 +16,7 @@ jobs: days-before-issue-stale: -1 # 手动标记后,14 天后关闭 days-before-issue-close: 7 - # 使用的标签(必须和你手动添加的标签一致) + # 使用的标签 stale-issue-label: "stale" # 自动关闭时自动加上的标签 close-issue-label: "automatic-closing" diff --git a/src/layout/arrange.h b/src/layout/arrange.h index 53c6489..69d221d 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -119,8 +119,7 @@ void resize_tile_master_horizontal(Client *grabc, bool isdrag, int32_t offsetx, break; } - if (!begin_find_nextnext && VISIBLEON(tc, grabc->mon) && - ISTILED(tc)) { // 根据你的实际字段名调整 + if (!begin_find_nextnext && VISIBLEON(tc, grabc->mon) && ISTILED(tc)) { next = tc; begin_find_nextnext = true; continue; @@ -136,8 +135,7 @@ void resize_tile_master_horizontal(Client *grabc, bool isdrag, int32_t offsetx, break; } - if (!begin_find_prevprev && VISIBLEON(tc, grabc->mon) && - ISTILED(tc)) { // 根据你的实际字段名调整 + if (!begin_find_prevprev && VISIBLEON(tc, grabc->mon) && ISTILED(tc)) { prev = tc; begin_find_prevprev = true; continue; @@ -305,8 +303,7 @@ void resize_tile_master_vertical(Client *grabc, bool isdrag, int32_t offsetx, for (node = grabc->link.next; node != &clients; node = node->next) { tc = wl_container_of(node, tc, link); - if (VISIBLEON(tc, grabc->mon) && - ISTILED(tc)) { // 根据你的实际字段名调整 + if (VISIBLEON(tc, grabc->mon) && ISTILED(tc)) { next = tc; break; } @@ -316,8 +313,7 @@ void resize_tile_master_vertical(Client *grabc, bool isdrag, int32_t offsetx, for (node = grabc->link.prev; node != &clients; node = node->prev) { tc = wl_container_of(node, tc, link); - if (VISIBLEON(tc, grabc->mon) && - ISTILED(tc)) { // 根据你的实际字段名调整 + if (VISIBLEON(tc, grabc->mon) && ISTILED(tc)) { prev = tc; break; } From 6522e18d0839858711974f351a54fa20d3e05b68 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 6 Mar 2026 13:25:24 +0800 Subject: [PATCH 139/170] opt: fix potential issues caused by uninitialization --- src/layout/arrange.h | 20 +++++++++++++++++--- src/mango.c | 3 +++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/layout/arrange.h b/src/layout/arrange.h index 69d221d..d674068 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -5,6 +5,16 @@ void save_old_size_per(Monitor *m) { if (VISIBLEON(c, m) && ISTILED(c)) { c->old_master_inner_per = c->master_inner_per; c->old_stack_inner_per = c->stack_inner_per; + } else { + if (c->old_master_inner_per <= 0.0f || + c->old_master_inner_per > 1.0f) { + c->old_master_inner_per = 1.0f; + } + + if (c->old_stack_inner_per <= 0.0f || + c->old_stack_inner_per > 1.0f) { + c->old_stack_inner_per = 1.0f; + } } } } @@ -45,14 +55,16 @@ void restore_size_per(Monitor *m, Client *c) { } if (!c->ismaster && c->old_stack_inner_per < 1.0 && - c->stack_inner_per < 1.0) { + c->old_stack_inner_per > 0.0f && c->stack_inner_per < 1.0 && + c->stack_inner_per > 0.0f) { c->stack_inner_per = (1.0 - c->stack_inner_per) * c->old_stack_inner_per / (1.0 - c->old_stack_inner_per); } if (c->ismaster && c->old_master_inner_per < 1.0 && - c->master_inner_per < 1.0) { + c->old_master_inner_per > 0.0f && c->master_inner_per < 1.0 && + c->master_inner_per > 0.0f) { c->master_inner_per = (1.0 - c->master_inner_per) * c->old_master_inner_per / (1.0 - c->old_master_inner_per); @@ -61,10 +73,12 @@ void restore_size_per(Monitor *m, Client *c) { wl_list_for_each(fc, &clients, link) { if (VISIBLEON(fc, m) && ISTILED(fc) && fc != c && !fc->ismaster && fc->old_ismaster && fc->old_stack_inner_per < 1.0 && - fc->stack_inner_per < 1.0) { + fc->old_stack_inner_per > 0.0f && fc->stack_inner_per < 1.0 && + fc->stack_inner_per > 0.0f) { fc->stack_inner_per = (1.0 - fc->stack_inner_per) * fc->old_stack_inner_per / (1.0 - fc->old_stack_inner_per); + fc->old_ismaster = false; } } } diff --git a/src/mango.c b/src/mango.c index 3ae5ccc..4c063cf 100644 --- a/src/mango.c +++ b/src/mango.c @@ -4025,6 +4025,9 @@ void init_client_properties(Client *c) { c->master_mfact_per = 0.0f; c->master_inner_per = 0.0f; c->stack_inner_per = 0.0f; + c->old_stack_inner_per = 0.0f; + c->old_master_inner_per = 0.0f; + c->old_master_mfact_per = 0.0f; c->isterm = 0; c->allow_csd = 0; c->force_maximize = 0; From 11b4bb03bfea2e914fc7b208f724d89f7a446060 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 6 Mar 2026 14:17:26 +0800 Subject: [PATCH 140/170] feat: support the repeated exchange of the same two clients --- src/dispatch/bind_define.h | 4 +++- src/mango.c | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 5cf41d6..ac1f602 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -110,7 +110,9 @@ int32_t exchange_client(const Arg *arg) { if ((c->isfullscreen || c->ismaximizescreen) && !is_scroller_layout(c->mon)) return 0; - exchange_two_client(c, direction_select(arg)); + Client *tc = direction_select(arg); + tc = get_focused_stack_client(tc); + exchange_two_client(c, tc); return 0; } diff --git a/src/mango.c b/src/mango.c index 4c063cf..c28844f 100644 --- a/src/mango.c +++ b/src/mango.c @@ -4830,6 +4830,11 @@ void exchange_two_client(Client *c1, Client *c2) { } else { arrange(c1->mon, false, false); } + + // In order to facilitate repeated exchanges for get_focused_stack_client + // set c2 focus order behind c1 + wl_list_remove(&c2->flink); + wl_list_insert(&c1->flink, &c2->flink); } void set_activation_env() { From 9a17a0279c39b330d155d50282d1d2641d519dc3 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 6 Mar 2026 18:21:49 +0800 Subject: [PATCH 141/170] feat: add custom option to monitorrule --- src/config/parse_config.h | 10 ++++++++-- src/mango.c | 5 +++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index ce5db53..59debcf 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -112,6 +112,7 @@ typedef struct { int32_t width, height; // Monitor resolution float refresh; // Refresh rate int32_t vrr; // variable refresh rate + int32_t custom; // enable custom mode } ConfigMonitorRule; // 修改后的宏定义 @@ -1796,6 +1797,7 @@ bool parse_option(Config *config, char *key, char *value) { rule->height = -1; rule->refresh = 0.0f; rule->vrr = 0; + rule->custom = 0; bool parse_error = false; char *token = strtok(value, ","); @@ -1833,6 +1835,8 @@ bool 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 if (strcmp(key, "custom") == 0) { + rule->custom = CLAMP_INT(atoi(val), 0, 1); } else { fprintf(stderr, "\033[1m\033[31m[ERROR]:\033[33m Unknown " @@ -3555,7 +3559,7 @@ void reset_blur_params(void) { void reapply_monitor_rules(void) { ConfigMonitorRule *mr; Monitor *m = NULL; - int32_t ji, vrr; + int32_t ji, vrr, custom; int32_t mx, my; struct wlr_output_state state; struct wlr_output_mode *internal_mode = NULL; @@ -3609,13 +3613,15 @@ void reapply_monitor_rules(void) { 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; + custom = mr->custom >= 0 ? mr->custom : 0; if (mr->width > 0 && mr->height > 0 && mr->refresh > 0) { internal_mode = get_nearest_output_mode( m->wlr_output, mr->width, mr->height, mr->refresh); if (internal_mode) { wlr_output_state_set_mode(&state, internal_mode); - } else if (wlr_output_is_headless(m->wlr_output)) { + } else if (custom || + wlr_output_is_headless(m->wlr_output)) { wlr_output_state_set_custom_mode( &state, mr->width, mr->height, (int32_t)roundf(mr->refresh * 1000)); diff --git a/src/mango.c b/src/mango.c index c28844f..94bddb6 100644 --- a/src/mango.c +++ b/src/mango.c @@ -2884,7 +2884,7 @@ void createmon(struct wl_listener *listener, void *data) { struct wlr_output *wlr_output = data; const ConfigMonitorRule *r; uint32_t i; - int32_t ji, vrr; + int32_t ji, vrr, custom; struct wlr_output_state state; Monitor *m = NULL; struct wlr_output_mode *internal_mode = NULL; @@ -2976,6 +2976,7 @@ void createmon(struct wl_listener *listener, void *data) { 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; + custom = r->custom >= 0 ? r->custom : 0; scale = r->scale; rr = r->rr; @@ -2985,7 +2986,7 @@ void createmon(struct wl_listener *listener, void *data) { if (internal_mode) { custom_monitor_mode = true; wlr_output_state_set_mode(&state, internal_mode); - } else if (wlr_output_is_headless(m->wlr_output)) { + } else if (custom || wlr_output_is_headless(m->wlr_output)) { custom_monitor_mode = true; wlr_output_state_set_custom_mode( &state, r->width, r->height, From 10d7e5c6e3817d36ae8dbe5747ad23fe26944a45 Mon Sep 17 00:00:00 2001 From: kanvolu Date: Fri, 6 Mar 2026 22:23:59 -0500 Subject: [PATCH 142/170] Added cycling both ways for switch_proportion_preset Now switch_proportion_preset requires an argument "prev" or "next" to determine cycle direction --- src/config/parse_config.h | 1 + src/dispatch/bind_declare.h | 2 +- src/dispatch/bind_define.h | 29 ++++++++++++++++++++++------- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 59debcf..47d086a 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -947,6 +947,7 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value, (*arg).f = atof(arg_value); } else if (strcmp(func_name, "switch_proportion_preset") == 0) { func = switch_proportion_preset; + (*arg).i = parse_circle_direction(arg_value); } else if (strcmp(func_name, "viewtoleft") == 0) { func = viewtoleft; (*arg).i = atoi(arg_value); diff --git a/src/dispatch/bind_declare.h b/src/dispatch/bind_declare.h index 22ef612..7dced53 100644 --- a/src/dispatch/bind_declare.h +++ b/src/dispatch/bind_declare.h @@ -69,4 +69,4 @@ 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); -int32_t scroller_stack(const Arg *arg); \ No newline at end of file +int32_t scroller_stack(const Arg *arg); diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index ac1f602..676be51 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1056,13 +1056,28 @@ int32_t switch_proportion_preset(const Arg *arg) { for (int32_t i = 0; i < config.scroller_proportion_preset_count; i++) { if (config.scroller_proportion_preset[i] == tc->scroller_proportion) { - if (i == config.scroller_proportion_preset_count - 1) { - target_proportion = config.scroller_proportion_preset[0]; - break; + + if (arg->i == NEXT) { + if (i == config.scroller_proportion_preset_count - 1) { + target_proportion = + config.scroller_proportion_preset[0]; + break; + } else { + target_proportion = + config.scroller_proportion_preset[i + 1]; + break; + } } else { - target_proportion = - config.scroller_proportion_preset[i + 1]; - break; + if (i == 0) { + target_proportion = + config.scroller_proportion_preset + [config.scroller_proportion_preset_count - 1]; + break; + } else { + target_proportion = + config.scroller_proportion_preset[i - 1]; + break; + } } } } @@ -1847,4 +1862,4 @@ int32_t scroller_stack(const Arg *arg) { arrange(selmon, false, false); return 0; -} \ No newline at end of file +} From 63b9ffb1a422e34b8d2ce4121cf1aee6fd0e5dad Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 7 Mar 2026 12:54:37 +0800 Subject: [PATCH 143/170] opt: opt the old size per init --- src/layout/arrange.h | 10 ---------- src/mango.c | 6 +++--- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/src/layout/arrange.h b/src/layout/arrange.h index d674068..058198b 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -5,16 +5,6 @@ void save_old_size_per(Monitor *m) { if (VISIBLEON(c, m) && ISTILED(c)) { c->old_master_inner_per = c->master_inner_per; c->old_stack_inner_per = c->stack_inner_per; - } else { - if (c->old_master_inner_per <= 0.0f || - c->old_master_inner_per > 1.0f) { - c->old_master_inner_per = 1.0f; - } - - if (c->old_stack_inner_per <= 0.0f || - c->old_stack_inner_per > 1.0f) { - c->old_stack_inner_per = 1.0f; - } } } } diff --git a/src/mango.c b/src/mango.c index 94bddb6..a256d8f 100644 --- a/src/mango.c +++ b/src/mango.c @@ -4026,9 +4026,9 @@ void init_client_properties(Client *c) { c->master_mfact_per = 0.0f; c->master_inner_per = 0.0f; c->stack_inner_per = 0.0f; - c->old_stack_inner_per = 0.0f; - c->old_master_inner_per = 0.0f; - c->old_master_mfact_per = 0.0f; + c->old_stack_inner_per = 1.0f; + c->old_master_inner_per = 1.0f; + c->old_master_mfact_per = 1.0f; c->isterm = 0; c->allow_csd = 0; c->force_maximize = 0; From fd68f188c63e7595f55bbb326bcb7c7ad4ec7c1f Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 7 Mar 2026 13:24:46 +0800 Subject: [PATCH 144/170] opt: add some comment --- src/layout/arrange.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/layout/arrange.h b/src/layout/arrange.h index 058198b..36f6396 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -44,6 +44,10 @@ void restore_size_per(Monitor *m, Client *c) { return; } + // it is possible that the current floating window is moved to another tag, + // but the tag has not executed save_old_size_per + // so it must be judged whether their old size values are initial values + if (!c->ismaster && c->old_stack_inner_per < 1.0 && c->old_stack_inner_per > 0.0f && c->stack_inner_per < 1.0 && c->stack_inner_per > 0.0f) { From 89a4ec83a0d24887d182209feca488f32fe3c071 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 7 Mar 2026 13:46:18 +0800 Subject: [PATCH 145/170] fix: avoid mutual influence of monitor rules --- src/config/parse_config.h | 72 ++++--------------------- src/mango.c | 111 +++++++++++++++++--------------------- 2 files changed, 60 insertions(+), 123 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 47d086a..d10bf0c 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -372,6 +372,9 @@ typedef int32_t (*FuncType)(const Arg *); Config config; bool parse_config_file(Config *config, const char *file_path, bool must_exist); +bool apply_rule_to_state(Monitor *m, const ConfigMonitorRule *rule, + struct wlr_output_state *state, int vrr, int custom); +bool monitor_matches_rule(Monitor *m, const ConfigMonitorRule *rule); // Helper function to trim whitespace from start and end of a string void trim_whitespace(char *str) { @@ -3563,14 +3566,12 @@ void reapply_monitor_rules(void) { int32_t ji, vrr, custom; int32_t mx, my; struct wlr_output_state state; - struct wlr_output_mode *internal_mode = NULL; - wlr_output_state_init(&state); - bool match_rule = false; wl_list_for_each(m, &mons, link) { - if (!m->wlr_output->enabled) { + if (!m->wlr_output->enabled) continue; - } + + wlr_output_state_init(&state); for (ji = 0; ji < config.monitor_rules_count; ji++) { if (config.monitor_rules_count < 1) @@ -3578,73 +3579,22 @@ void reapply_monitor_rules(void) { mr = &config.monitor_rules[ji]; - // 检查是否匹配的变量 - match_rule = true; - - // 检查四个标识字段的匹配 - if (mr->name != NULL) { - if (!regex_match(mr->name, m->wlr_output->name)) { - match_rule = false; - } - } - - if (mr->make != NULL) { - if (m->wlr_output->make == NULL || - strcmp(mr->make, m->wlr_output->make) != 0) { - match_rule = false; - } - } - - if (mr->model != NULL) { - if (m->wlr_output->model == NULL || - strcmp(mr->model, m->wlr_output->model) != 0) { - match_rule = false; - } - } - - if (mr->serial != NULL) { - if (m->wlr_output->serial == NULL || - strcmp(mr->serial, m->wlr_output->serial) != 0) { - match_rule = false; - } - } - - // 只有当所有指定的标识都匹配时才应用规则 - if (match_rule) { + if (monitor_matches_rule(m, mr)) { 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; custom = mr->custom >= 0 ? mr->custom : 0; - if (mr->width > 0 && mr->height > 0 && mr->refresh > 0) { - internal_mode = get_nearest_output_mode( - m->wlr_output, mr->width, mr->height, mr->refresh); - if (internal_mode) { - wlr_output_state_set_mode(&state, internal_mode); - } else if (custom || - wlr_output_is_headless(m->wlr_output)) { - wlr_output_state_set_custom_mode( - &state, mr->width, mr->height, - (int32_t)roundf(mr->refresh * 1000)); - } - } - - 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); - wlr_output_state_set_transform(&state, mr->rr); + (void)apply_rule_to_state(m, mr, &state, vrr, custom); wlr_output_layout_add(output_layout, m->wlr_output, mx, my); + wlr_output_commit_state(m->wlr_output, &state); + break; } } - wlr_output_commit_state(m->wlr_output, &state); wlr_output_state_finish(&state); - updatemons(NULL, NULL); } + updatemons(NULL, NULL); } void reapply_cursor_style(void) { diff --git a/src/mango.c b/src/mango.c index a256d8f..2af2720 100644 --- a/src/mango.c +++ b/src/mango.c @@ -806,7 +806,6 @@ static int32_t keep_idle_inhibit(void *data); static void check_keep_idle_inhibit(Client *c); static void pre_caculate_before_arrange(Monitor *m, bool want_animation, bool from_view, bool only_caculate); - #include "data/static_keymap.h" #include "dispatch/bind_declare.h" #include "layout/layout.h" @@ -2878,6 +2877,49 @@ void enable_adaptive_sync(Monitor *m, struct wlr_output_state *state) { } } +bool monitor_matches_rule(Monitor *m, const ConfigMonitorRule *rule) { + if (rule->name != NULL && !regex_match(rule->name, m->wlr_output->name)) + return false; + if (rule->make != NULL && (m->wlr_output->make == NULL || + strcmp(rule->make, m->wlr_output->make) != 0)) + return false; + if (rule->model != NULL && (m->wlr_output->model == NULL || + strcmp(rule->model, m->wlr_output->model) != 0)) + return false; + if (rule->serial != NULL && + (m->wlr_output->serial == NULL || + strcmp(rule->serial, m->wlr_output->serial) != 0)) + return false; + return true; +} + +/* 将规则中的显示参数应用到 wlr_output_state 中,返回是否设置了自定义模式 */ +bool apply_rule_to_state(Monitor *m, const ConfigMonitorRule *rule, + struct wlr_output_state *state, int vrr, int custom) { + bool mode_set = false; + if (rule->width > 0 && rule->height > 0 && rule->refresh > 0) { + struct wlr_output_mode *internal_mode = get_nearest_output_mode( + m->wlr_output, rule->width, rule->height, rule->refresh); + if (internal_mode) { + wlr_output_state_set_mode(state, internal_mode); + mode_set = true; + } else if (custom || wlr_output_is_headless(m->wlr_output)) { + wlr_output_state_set_custom_mode( + state, rule->width, rule->height, + (int32_t)roundf(rule->refresh * 1000)); + mode_set = true; + } + } + if (vrr) { + enable_adaptive_sync(m, state); + } else { + wlr_output_state_set_adaptive_sync_enabled(state, false); + } + wlr_output_state_set_scale(state, rule->scale); + wlr_output_state_set_transform(state, rule->rr); + return mode_set; +} + void createmon(struct wl_listener *listener, void *data) { /* This event is raised by the backend when a new output (aka a display or * monitor) becomes available. */ @@ -2887,9 +2929,7 @@ void createmon(struct wl_listener *listener, void *data) { int32_t ji, vrr, custom; struct wlr_output_state state; Monitor *m = NULL; - struct wlr_output_mode *internal_mode = NULL; bool custom_monitor_mode = false; - bool match_rule = false; if (!wlr_output_init_render(wlr_output, alloc, drw)) return; @@ -2919,7 +2959,6 @@ void createmon(struct wl_listener *listener, void *data) { for (i = 0; i < LENGTH(m->layers); i++) wl_list_init(&m->layers[i]); - wlr_output_state_init(&state); /* Initialize monitor state using configured rules */ m->gappih = gappih; m->gappiv = gappiv; @@ -2932,6 +2971,8 @@ void createmon(struct wl_listener *listener, void *data) { m->m.y = INT32_MAX; float scale = 1; enum wl_output_transform rr = WL_OUTPUT_TRANSFORM_NORMAL; + + wlr_output_state_init(&state); wlr_output_state_set_scale(&state, scale); wlr_output_state_set_transform(&state, rr); @@ -2941,38 +2982,7 @@ void createmon(struct wl_listener *listener, void *data) { r = &config.monitor_rules[ji]; - // 检查是否匹配的变量 - match_rule = true; - - // 检查四个标识字段的匹配 - if (r->name != NULL) { - if (!regex_match(r->name, m->wlr_output->name)) { - match_rule = false; - } - } - - if (r->make != NULL) { - if (m->wlr_output->make == NULL || - strcmp(r->make, m->wlr_output->make) != 0) { - match_rule = false; - } - } - - if (r->model != NULL) { - if (m->wlr_output->model == NULL || - strcmp(r->model, m->wlr_output->model) != 0) { - match_rule = false; - } - } - - if (r->serial != NULL) { - if (m->wlr_output->serial == NULL || - strcmp(r->serial, m->wlr_output->serial) != 0) { - match_rule = false; - } - } - - if (match_rule) { + if (monitor_matches_rule(m, r)) { 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; @@ -2980,36 +2990,13 @@ void createmon(struct wl_listener *listener, void *data) { scale = r->scale; rr = r->rr; - if (r->width > 0 && r->height > 0 && r->refresh > 0) { - internal_mode = get_nearest_output_mode(m->wlr_output, r->width, - r->height, r->refresh); - if (internal_mode) { - custom_monitor_mode = true; - wlr_output_state_set_mode(&state, internal_mode); - } else if (custom || wlr_output_is_headless(m->wlr_output)) { - custom_monitor_mode = true; - wlr_output_state_set_custom_mode( - &state, r->width, r->height, - (int32_t)roundf(r->refresh * 1000)); - } + if (apply_rule_to_state(m, r, &state, vrr, custom)) { + custom_monitor_mode = true; } - - 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); - wlr_output_state_set_transform(&state, r->rr); - break; + break; // 只应用第一个匹配规则 } } - /* The mode is a tuple of (width, height, refresh rate), and each - * monitor supports only a specific set of modes. We just pick the - * monitor's preferred mode; a more sophisticated compositor would let - * the user configure it. */ if (!custom_monitor_mode) wlr_output_state_set_mode(&state, wlr_output_preferred_mode(wlr_output)); From 75c888bbe4bc7efda789bbe1d8872a36b0b2a964 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 7 Mar 2026 15:31:05 +0800 Subject: [PATCH 146/170] opt: optimize resizewin setp with keyboard --- src/layout/arrange.h | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/layout/arrange.h b/src/layout/arrange.h index 36f6396..853e469 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -276,6 +276,18 @@ void resize_tile_master_horizontal(Client *grabc, bool isdrag, int32_t offsetx, new_master_inner_per = fmaxf(0.1f, fminf(0.9f, new_master_inner_per)); new_stack_inner_per = fmaxf(0.1f, fminf(0.9f, new_stack_inner_per)); + if (!isdrag) { + new_stack_inner_per = + new_stack_inner_per + + (new_stack_inner_per - grabc->old_stack_inner_per) / + ((1 / new_stack_inner_per) - 1); + + new_master_inner_per = + new_master_inner_per + + (new_master_inner_per - grabc->old_master_inner_per) / + ((1 / new_master_inner_per) - 1); + } + // 应用到所有平铺窗口 wl_list_for_each(tc, &clients, link) { if (VISIBLEON(tc, grabc->mon) && ISTILED(tc)) { @@ -431,6 +443,18 @@ void resize_tile_master_vertical(Client *grabc, bool isdrag, int32_t offsetx, new_master_inner_per = fmaxf(0.1f, fminf(0.9f, new_master_inner_per)); new_stack_inner_per = fmaxf(0.1f, fminf(0.9f, new_stack_inner_per)); + if (!isdrag) { + new_stack_inner_per = + new_stack_inner_per + + (new_stack_inner_per - grabc->old_stack_inner_per) / + ((1 / new_stack_inner_per) - 1); + + new_master_inner_per = + new_master_inner_per + + (new_master_inner_per - grabc->old_master_inner_per) / + ((1 / new_master_inner_per) - 1); + } + // 应用到所有平铺窗口 wl_list_for_each(tc, &clients, link) { if (VISIBLEON(tc, grabc->mon) && ISTILED(tc)) { @@ -616,7 +640,14 @@ void resize_tile_scroller(Client *grabc, bool isdrag, int32_t offsetx, // 应用限制,确保比例在合理范围内 new_scroller_proportion = fmaxf(0.1f, fminf(1.0f, new_scroller_proportion)); - new_stack_proportion = fmaxf(0.1f, fminf(1.0f, new_stack_proportion)); + new_stack_proportion = fmaxf(0.1f, fminf(0.9f, new_stack_proportion)); + + if (!isdrag) { + new_stack_proportion = + new_stack_proportion + + (new_stack_proportion - grabc->old_stack_proportion) / + ((1 / new_stack_proportion) - 1); + } grabc->stack_proportion = new_stack_proportion; From b1d744ad1f493cba26fb31a87db5346ac9361c6b Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 7 Mar 2026 17:21:50 +0800 Subject: [PATCH 147/170] feat: export drag interval to able configure --- assets/config.conf | 2 ++ src/config/parse_config.h | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/assets/config.conf b/assets/config.conf index 15b654c..7778664 100644 --- a/assets/config.conf +++ b/assets/config.conf @@ -74,6 +74,8 @@ overviewgappi=5 overviewgappo=30 # Misc +drag_tile_refresh_interval=16.0 +drag_floating_refresh_interval=8.0 no_border_when_single=0 axis_bind_apply_timeout=100 focus_on_activate=1 diff --git a/src/config/parse_config.h b/src/config/parse_config.h index d10bf0c..3220b71 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -356,6 +356,8 @@ typedef struct { int32_t single_scratchpad; int32_t xwayland_persistence; int32_t syncobj_enable; + float drag_tile_refresh_interval; + float drag_floating_refresh_interval; int32_t allow_tearing; int32_t allow_shortcuts_inhibit; int32_t allow_lock_transparent; @@ -1399,6 +1401,10 @@ bool 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, "drag_tile_refresh_interval") == 0) { + config->drag_tile_refresh_interval = atof(value); + } else if (strcmp(key, "drag_floating_refresh_interval") == 0) { + config->drag_floating_refresh_interval = atof(value); } else if (strcmp(key, "allow_tearing") == 0) { config->allow_tearing = atoi(value); } else if (strcmp(key, "allow_shortcuts_inhibit") == 0) { @@ -3149,6 +3155,13 @@ void override_config(void) { // 杂项设置 xwayland_persistence = CLAMP_INT(config.xwayland_persistence, 0, 1); syncobj_enable = CLAMP_INT(config.syncobj_enable, 0, 1); + drag_tile_refresh_interval = + CLAMP_FLOAT(config.drag_tile_refresh_interval, 1.0f, 16.0f); + drag_floating_refresh_interval = + CLAMP_FLOAT(config.drag_floating_refresh_interval, 1.0f, 16.0f); + drag_tile_to_tile = CLAMP_INT(config.drag_tile_to_tile, 0, 1); + drag_floating_refresh_interval = + CLAMP_FLOAT(config.drag_floating_refresh_interval, 0.0f, 1000.0f); 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); @@ -3337,6 +3350,8 @@ void set_value_default() { config.single_scratchpad = single_scratchpad; config.xwayland_persistence = xwayland_persistence; config.syncobj_enable = syncobj_enable; + config.drag_tile_refresh_interval = drag_tile_refresh_interval; + config.drag_floating_refresh_interval = drag_floating_refresh_interval; config.allow_tearing = allow_tearing; config.allow_shortcuts_inhibit = allow_shortcuts_inhibit; config.allow_lock_transparent = allow_lock_transparent; From 636060972dc42c18a7abb1f533fc23be338ceb23 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 7 Mar 2026 17:29:37 +0800 Subject: [PATCH 148/170] opt: change some default config --- assets/config.conf | 2 +- src/config/preset.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/config.conf b/assets/config.conf index 7778664..d258716 100644 --- a/assets/config.conf +++ b/assets/config.conf @@ -74,7 +74,7 @@ overviewgappi=5 overviewgappo=30 # Misc -drag_tile_refresh_interval=16.0 +drag_tile_refresh_interval=8.0 drag_floating_refresh_interval=8.0 no_border_when_single=0 axis_bind_apply_timeout=100 diff --git a/src/config/preset.h b/src/config/preset.h index d982458..f91da11 100644 --- a/src/config/preset.h +++ b/src/config/preset.h @@ -109,7 +109,7 @@ 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_tile_refresh_interval = 16.0; +double drag_tile_refresh_interval = 8.0; double drag_floating_refresh_interval = 8.0; int32_t allow_tearing = TEARING_DISABLED; int32_t allow_shortcuts_inhibit = SHORTCUTS_INHIBIT_ENABLE; From d0eb7d7114705877529045e6315de1c271a8e2a3 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 7 Mar 2026 17:40:22 +0800 Subject: [PATCH 149/170] opt: remove some config in default config file --- assets/config.conf | 2 -- 1 file changed, 2 deletions(-) diff --git a/assets/config.conf b/assets/config.conf index d258716..15b654c 100644 --- a/assets/config.conf +++ b/assets/config.conf @@ -74,8 +74,6 @@ overviewgappi=5 overviewgappo=30 # Misc -drag_tile_refresh_interval=8.0 -drag_floating_refresh_interval=8.0 no_border_when_single=0 axis_bind_apply_timeout=100 focus_on_activate=1 From 09c170793177dcbdb4e0a7881260f4243caf2d59 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 7 Mar 2026 18:51:12 +0800 Subject: [PATCH 150/170] opt: optimize size per caculate when resizewin --- src/layout/arrange.h | 93 ++++++++++++++++++++++++++++---------------- 1 file changed, 60 insertions(+), 33 deletions(-) diff --git a/src/layout/arrange.h b/src/layout/arrange.h index 853e469..bbe735f 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -276,21 +276,24 @@ void resize_tile_master_horizontal(Client *grabc, bool isdrag, int32_t offsetx, new_master_inner_per = fmaxf(0.1f, fminf(0.9f, new_master_inner_per)); new_stack_inner_per = fmaxf(0.1f, fminf(0.9f, new_stack_inner_per)); - if (!isdrag) { - new_stack_inner_per = - new_stack_inner_per + - (new_stack_inner_per - grabc->old_stack_inner_per) / - ((1 / new_stack_inner_per) - 1); - - new_master_inner_per = - new_master_inner_per + - (new_master_inner_per - grabc->old_master_inner_per) / - ((1 / new_master_inner_per) - 1); - } - // 应用到所有平铺窗口 wl_list_for_each(tc, &clients, link) { if (VISIBLEON(tc, grabc->mon) && ISTILED(tc)) { + + if (!isdrag && tc != grabc && type != CENTER_TILE) { + if (!tc->ismaster && new_stack_inner_per != 1.0f && + grabc->old_stack_inner_per != 1.0f) + tc->stack_inner_per = (1 - new_stack_inner_per) / + (1 - grabc->old_stack_inner_per) * + tc->stack_inner_per; + if (tc->ismaster && new_master_inner_per != 1.0f && + grabc->old_master_inner_per != 1.0f) + tc->master_inner_per = + (1.0f - new_master_inner_per) / + (1.0f - grabc->old_master_inner_per) * + tc->master_inner_per; + } + tc->master_mfact_per = new_master_mfact_per; } } @@ -443,21 +446,23 @@ void resize_tile_master_vertical(Client *grabc, bool isdrag, int32_t offsetx, new_master_inner_per = fmaxf(0.1f, fminf(0.9f, new_master_inner_per)); new_stack_inner_per = fmaxf(0.1f, fminf(0.9f, new_stack_inner_per)); - if (!isdrag) { - new_stack_inner_per = - new_stack_inner_per + - (new_stack_inner_per - grabc->old_stack_inner_per) / - ((1 / new_stack_inner_per) - 1); - - new_master_inner_per = - new_master_inner_per + - (new_master_inner_per - grabc->old_master_inner_per) / - ((1 / new_master_inner_per) - 1); - } - // 应用到所有平铺窗口 wl_list_for_each(tc, &clients, link) { if (VISIBLEON(tc, grabc->mon) && ISTILED(tc)) { + if (!isdrag && tc != grabc && type != CENTER_TILE) { + if (!tc->ismaster && new_stack_inner_per != 1.0f && + grabc->old_stack_inner_per != 1.0f) + tc->stack_inner_per = (1 - new_stack_inner_per) / + (1 - grabc->old_stack_inner_per) * + tc->stack_inner_per; + if (tc->ismaster && new_master_inner_per != 1.0f && + grabc->old_master_inner_per != 1.0f) + tc->master_inner_per = + (1.0f - new_master_inner_per) / + (1.0f - grabc->old_master_inner_per) * + tc->master_inner_per; + } + tc->master_mfact_per = new_master_mfact_per; } } @@ -480,6 +485,7 @@ void resize_tile_master_vertical(Client *grabc, bool isdrag, int32_t offsetx, void resize_tile_scroller(Client *grabc, bool isdrag, int32_t offsetx, int32_t offsety, uint32_t time, bool isvertical) { + Client *tc = NULL; float delta_x, delta_y; float new_scroller_proportion; float new_stack_proportion; @@ -642,17 +648,20 @@ void resize_tile_scroller(Client *grabc, bool isdrag, int32_t offsetx, fmaxf(0.1f, fminf(1.0f, new_scroller_proportion)); new_stack_proportion = fmaxf(0.1f, fminf(0.9f, new_stack_proportion)); - if (!isdrag) { - new_stack_proportion = - new_stack_proportion + - (new_stack_proportion - grabc->old_stack_proportion) / - ((1 / new_stack_proportion) - 1); - } - grabc->stack_proportion = new_stack_proportion; stack_head->scroller_proportion = new_scroller_proportion; + wl_list_for_each(tc, &clients, link) { + if (new_stack_proportion != 1.0f && + grabc->old_stack_proportion != 1.0f && tc != grabc && + ISTILED(tc) && get_scroll_stack_head(tc) == stack_head) { + tc->stack_proportion = (1.0f - new_stack_proportion) / + (1.0f - grabc->old_stack_proportion) * + tc->stack_proportion; + } + } + if (!isdrag) { arrange(grabc->mon, false, false); return; @@ -695,6 +704,18 @@ void resize_tile_client(Client *grabc, bool isdrag, int32_t offsetx, } } +/* If there are no calculation omissions, +these two functions will never be triggered. +Just in case to facilitate the final investigation*/ + +void check_size_per_valid(Client *c) { + if (c->ismaster) { + assert(c->master_inner_per > 0.0f && c->master_inner_per <= 1.0f); + } else { + assert(c->stack_inner_per > 0.0f && c->stack_inner_per <= 1.0f); + } +} + void reset_size_per_mon(Monitor *m, int32_t tile_cilent_num, double total_left_stack_hight_percent, double total_right_stack_hight_percent, @@ -710,6 +731,7 @@ void reset_size_per_mon(Monitor *m, int32_t tile_cilent_num, wl_list_for_each(c, &clients, link) { if (VISIBLEON(c, m) && ISTILED(c)) { + if (total_master_inner_percent > 0.0 && i < nmasters) { c->ismaster = true; c->stack_inner_per = stack_num ? 1.0f / stack_num : 1.0f; @@ -725,17 +747,20 @@ void reset_size_per_mon(Monitor *m, int32_t tile_cilent_num, : 1.0f; } i++; + + check_size_per_valid(c); } } } else { wl_list_for_each(c, &clients, link) { if (VISIBLEON(c, m) && ISTILED(c)) { + if (total_master_inner_percent > 0.0 && i < nmasters) { c->ismaster = true; if ((stack_index % 2) ^ (tile_cilent_num % 2 == 0)) { c->stack_inner_per = - stack_num > 1 ? 1.0f / ((stack_num - 1) / 2) : 1.0f; - + stack_num > 1 ? 1.0f / ((stack_num - 1) / 2.0f) + : 1.0f; } else { c->stack_inner_per = stack_num > 1 ? 2.0f / stack_num : 1.0f; @@ -764,6 +789,8 @@ void reset_size_per_mon(Monitor *m, int32_t tile_cilent_num, } } i++; + + check_size_per_valid(c); } } } From 31284b4b5db8785004cdb345c778f31424684a6f Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 7 Mar 2026 22:54:13 +0800 Subject: [PATCH 151/170] opt: optimize center tile layout resizewin --- 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 bbe735f..33db8ec 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -280,9 +280,11 @@ void resize_tile_master_horizontal(Client *grabc, bool isdrag, int32_t offsetx, wl_list_for_each(tc, &clients, link) { if (VISIBLEON(tc, grabc->mon) && ISTILED(tc)) { - if (!isdrag && tc != grabc && type != CENTER_TILE) { + if (!isdrag && tc != grabc) { if (!tc->ismaster && new_stack_inner_per != 1.0f && - grabc->old_stack_inner_per != 1.0f) + grabc->old_stack_inner_per != 1.0f && + (type != CENTER_TILE || + !(grabc->isleftstack ^ tc->isleftstack))) tc->stack_inner_per = (1 - new_stack_inner_per) / (1 - grabc->old_stack_inner_per) * tc->stack_inner_per; From cfe492fbc4af1b9518d1102680c3452e0e52d7b8 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 7 Mar 2026 23:14:17 +0800 Subject: [PATCH 152/170] opt: fix a minor judgment error --- src/layout/arrange.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/layout/arrange.h b/src/layout/arrange.h index 33db8ec..0284f8c 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -89,7 +89,7 @@ void set_size_per(Monitor *m, Client *c) { wl_list_for_each(fc, &clients, link) { if (VISIBLEON(fc, m) && ISTILED(fc) && fc != c) { if (current_layout->id == CENTER_TILE && - !(fc->isleftstack ^ c->isleftstack)) + (fc->isleftstack ^ c->isleftstack)) continue; c->master_mfact_per = fc->master_mfact_per; c->master_inner_per = fc->master_inner_per; From 89a0f7e3155a9b4489a56302153a92e4a5f1f339 Mon Sep 17 00:00:00 2001 From: Nikita Mitasov Date: Sat, 7 Mar 2026 22:18:46 +0300 Subject: [PATCH 153/170] fix(guix): add deprecated package variable with old naming. --- mangowm.scm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mangowm.scm b/mangowm.scm index 33d9504..7d94166 100644 --- a/mangowm.scm +++ b/mangowm.scm @@ -61,4 +61,7 @@ inspired by dwl but aiming to be more feature-rich.") (license gpl3))) +(define-deprecated-package mangowc + mangowm-git) + mangowm-git From a4ad8d0d1945fa37063ac3d112926e061f158c73 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 8 Mar 2026 08:45:56 +0800 Subject: [PATCH 154/170] fix: miss judge isdrag when resize stack in scroller --- src/layout/arrange.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/layout/arrange.h b/src/layout/arrange.h index 0284f8c..e164a0f 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -655,7 +655,7 @@ void resize_tile_scroller(Client *grabc, bool isdrag, int32_t offsetx, stack_head->scroller_proportion = new_scroller_proportion; wl_list_for_each(tc, &clients, link) { - if (new_stack_proportion != 1.0f && + if (!isdrag && new_stack_proportion != 1.0f && grabc->old_stack_proportion != 1.0f && tc != grabc && ISTILED(tc) && get_scroll_stack_head(tc) == stack_head) { tc->stack_proportion = (1.0f - new_stack_proportion) / From a607d63ae7c8965655562ed310991e9dc164d4e0 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 8 Mar 2026 20:00:44 +0800 Subject: [PATCH 155/170] opt: reset size per when toggleview --- src/dispatch/bind_define.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 676be51..228e92e 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1436,6 +1436,7 @@ int32_t toggleview(const Arg *arg) { uint32_t newtagset; uint32_t target; + Client *c = NULL; target = arg->ui == 0 ? ~0 & TAGMASK : arg->ui; @@ -1444,6 +1445,11 @@ int32_t toggleview(const Arg *arg) { if (newtagset) { selmon->tagset[selmon->seltags] = newtagset; focusclient(focustop(selmon), 1); + wl_list_for_each(c, &clients, link) { + if (VISIBLEON(c, selmon) && ISTILED(c)) { + set_size_per(selmon, c); + } + } arrange(selmon, false, false); } printstatus(); From d441ca22f4bd7554f04defdd36f13bedc38610e7 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 8 Mar 2026 20:20:13 +0800 Subject: [PATCH 156/170] opt: set scroller stack to same first tag --- src/dispatch/bind_define.h | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 228e92e..4f808bd 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1774,28 +1774,19 @@ int32_t scroller_stack(const Arg *arg) { 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) || - 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); - } + source_stack_head = get_scroll_stack_head(c); - if (stack_head == source_stack_head) { + if (source_stack_head == stack_head) { return 0; } @@ -1843,6 +1834,10 @@ int32_t scroller_stack(const Arg *arg) { if (!target_client || target_client->mon != c->mon) { return 0; + } else { + c->isglobal = target_client->isglobal = 0; + c->isunglobal = target_client->isglobal = 0; + c->tags = target_client->tags = get_tags_first_tag(target_client->tags); } exit_scroller_stack(c); From db30977196b91cfe2e5db8e9829faafe13417bd9 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 9 Mar 2026 11:15:13 +0800 Subject: [PATCH 157/170] opt: optimize popup unconstrain --- src/mango.c | 55 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/src/mango.c b/src/mango.c index 2af2720..bdafcc5 100644 --- a/src/mango.c +++ b/src/mango.c @@ -2590,41 +2590,49 @@ void destroydecoration(struct wl_listener *listener, void *data) { wl_list_remove(&c->set_decoration_mode.link); } -static void popup_unconstrain(Popup *popup) { +static bool popup_unconstrain(Popup *popup) { struct wlr_xdg_popup *wlr_popup = popup->wlr_popup; Client *c = NULL; LayerSurface *l = NULL; int32_t type = -1; if (!wlr_popup || !wlr_popup->parent) { - return; + return false; } struct wlr_scene_node *parent_node = wlr_popup->parent->data; if (!parent_node) { wlr_log(WLR_ERROR, "Popup parent has no scene node"); - return; + return false; } type = toplevel_from_wlr_surface(wlr_popup->base->surface, &c, &l); if ((l && !l->mon) || (c && !c->mon)) { - wlr_xdg_popup_destroy(wlr_popup); - return; + return true; } - int parent_lx, parent_ly; - wlr_scene_node_coords(parent_node, &parent_lx, &parent_ly); - struct wlr_box usable = type == LayerShell ? l->mon->m : c->mon->w; - struct wlr_box constraint_box = { - .x = usable.x - parent_lx, - .y = usable.y - parent_ly, - .width = usable.width, - .height = usable.height, - }; + int lx, ly; + struct wlr_box constraint_box; + + if (type == LayerShell) { + wlr_scene_node_coords(&l->scene_layer->tree->node, &lx, &ly); + constraint_box.x = usable.x - lx; + constraint_box.y = usable.y - ly; + constraint_box.width = usable.width; + constraint_box.height = usable.height; + } else { + constraint_box.x = + usable.x - (c->geom.x + c->bw - c->surface.xdg->current.geometry.x); + constraint_box.y = + usable.y - (c->geom.y + c->bw - c->surface.xdg->current.geometry.y); + constraint_box.width = usable.width; + constraint_box.height = usable.height; + } wlr_xdg_popup_unconstrain_from_box(wlr_popup, &constraint_box); + return false; } static void destroypopup(struct wl_listener *listener, void *data) { @@ -2638,14 +2646,16 @@ static void commitpopup(struct wl_listener *listener, void *data) { Popup *popup = wl_container_of(listener, popup, commit); struct wlr_surface *surface = data; + bool should_destroy = false; struct wlr_xdg_popup *wlr_popup = wlr_xdg_popup_try_from_wlr_surface(surface); - if (!wlr_popup || !wlr_popup->base->initial_commit) - goto commitpopup_listen_free; + if (!wlr_popup->base->initial_commit) + return; if (!wlr_popup->parent || !wlr_popup->parent->data) { - goto commitpopup_listen_free; + should_destroy = true; + goto cleanup_popup_commit; } wlr_scene_node_raise_to_top(wlr_popup->parent->data); @@ -2655,16 +2665,21 @@ static void commitpopup(struct wl_listener *listener, void *data) { popup->wlr_popup = wlr_popup; - popup_unconstrain(popup); + should_destroy = popup_unconstrain(popup); + +cleanup_popup_commit: -commitpopup_listen_free: wl_list_remove(&popup->commit.link); popup->commit.notify = NULL; + + if (should_destroy) { + wlr_xdg_popup_destroy(wlr_popup); + } } static void repositionpopup(struct wl_listener *listener, void *data) { Popup *popup = wl_container_of(listener, popup, reposition); - popup_unconstrain(popup); + (void)popup_unconstrain(popup); } static void createpopup(struct wl_listener *listener, void *data) { From 1fc89d01eb5aa0f3784fb4006bf9af47826444d2 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 10 Mar 2026 11:18:43 +0800 Subject: [PATCH 158/170] bump versito to 0.12.6 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 85fe15b..c538c92 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('mango', ['c', 'cpp'], - version : '0.12.5', + version : '0.12.6', ) subdir('protocols') From 5906d9621ecae9583848ef29b0feca13ce765b2c Mon Sep 17 00:00:00 2001 From: atheeq-rhxn Date: Thu, 12 Mar 2026 10:31:29 +0530 Subject: [PATCH 159/170] feat: add docs and sync with wiki & website --- .github/scripts/sync-wiki.py | 61 ++++++ .github/workflows/sync-website.yml | 42 ++++ .github/workflows/sync-wiki.yml | 40 ++++ docs/bindings/keys.md | 204 ++++++++++++++++++++ docs/bindings/meta.json | 4 + docs/bindings/mouse-gestures.md | 116 ++++++++++++ docs/configuration/basics.md | 87 +++++++++ docs/configuration/input.md | 150 +++++++++++++++ docs/configuration/meta.json | 4 + docs/configuration/miscellaneous.md | 51 +++++ docs/configuration/monitors.md | 274 +++++++++++++++++++++++++++ docs/configuration/xdg-portals.md | 76 ++++++++ docs/faq.md | 101 ++++++++++ docs/index.md | 42 ++++ docs/installation.md | 231 ++++++++++++++++++++++ docs/ipc.md | 154 +++++++++++++++ docs/meta.json | 14 ++ docs/quick-start.md | 88 +++++++++ docs/visuals/animations.md | 108 +++++++++++ docs/visuals/effects.md | 82 ++++++++ docs/visuals/meta.json | 4 + docs/visuals/status-bar.md | 141 ++++++++++++++ docs/visuals/theming.md | 59 ++++++ docs/window-management/layouts.md | 99 ++++++++++ docs/window-management/meta.json | 4 + docs/window-management/overview.md | 36 ++++ docs/window-management/rules.md | 249 ++++++++++++++++++++++++ docs/window-management/scratchpad.md | 73 +++++++ 28 files changed, 2594 insertions(+) create mode 100644 .github/scripts/sync-wiki.py create mode 100644 .github/workflows/sync-website.yml create mode 100644 .github/workflows/sync-wiki.yml create mode 100644 docs/bindings/keys.md create mode 100644 docs/bindings/meta.json create mode 100644 docs/bindings/mouse-gestures.md create mode 100644 docs/configuration/basics.md create mode 100644 docs/configuration/input.md create mode 100644 docs/configuration/meta.json create mode 100644 docs/configuration/miscellaneous.md create mode 100644 docs/configuration/monitors.md create mode 100644 docs/configuration/xdg-portals.md create mode 100644 docs/faq.md create mode 100644 docs/index.md create mode 100644 docs/installation.md create mode 100644 docs/ipc.md create mode 100644 docs/meta.json create mode 100644 docs/quick-start.md create mode 100644 docs/visuals/animations.md create mode 100644 docs/visuals/effects.md create mode 100644 docs/visuals/meta.json create mode 100644 docs/visuals/status-bar.md create mode 100644 docs/visuals/theming.md create mode 100644 docs/window-management/layouts.md create mode 100644 docs/window-management/meta.json create mode 100644 docs/window-management/overview.md create mode 100644 docs/window-management/rules.md create mode 100644 docs/window-management/scratchpad.md diff --git a/.github/scripts/sync-wiki.py b/.github/scripts/sync-wiki.py new file mode 100644 index 0000000..ebf543c --- /dev/null +++ b/.github/scripts/sync-wiki.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +import json +import re +from pathlib import Path + +DOCS_DIR = Path("docs") +WIKI_DIR = Path("wiki-temp") + +FRONTMATTER_RE = re.compile(r"\A---\s*\n.*?^---\s*\n", re.DOTALL | re.MULTILINE) +DOCS_LINK_RE = re.compile(r"\[([^\]]+)\]\(/docs/(?:[^/)]+/)*([^/)#]+)(#[^)]+)?\)") + + +def collect_all_files() -> list[tuple[Path, str]]: + files = [] + + def from_dir(directory: Path) -> list[Path]: + meta = directory / "meta.json" + if meta.exists(): + data = json.loads(meta.read_text()) + return [directory / f"{p}.md" for p in data.get("pages", []) if (directory / f"{p}.md").exists()] + return sorted(directory.glob("*.md")) + + for src in from_dir(DOCS_DIR): + files.append((src, "Home" if src.stem == "index" else src.stem)) + + for subdir in sorted(DOCS_DIR.iterdir()): + if subdir.is_dir(): + for src in from_dir(subdir): + files.append((src, src.stem)) + + return files + + +def main() -> None: + files = collect_all_files() + + contents = {src: src.read_text() for src, _ in files} + + for src, dest_name in files: + text = FRONTMATTER_RE.sub("", contents[src], count=1).lstrip("\n") + text = DOCS_LINK_RE.sub(lambda m: f"[{m.group(1)}]({m.group(2)}{m.group(3) or ''})", text) + (WIKI_DIR / f"{dest_name}.md").write_text(text) + + lines: list[str] = [] + current_section = None + for src, dest_name in files: + section = "General" if src.parent == DOCS_DIR else src.parent.name.replace("-", " ").title() + if section != current_section: + if current_section is not None: + lines.append("") + lines.append(f"## {section}\n") + current_section = section + if dest_name != "Home": + title = dest_name.replace("-", " ").replace("_", " ").title() + lines.append(f"- [[{dest_name}|{title}]]") + + (WIKI_DIR / "_Sidebar.md").write_text("\n".join(lines)) + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/sync-website.yml b/.github/workflows/sync-website.yml new file mode 100644 index 0000000..f641fb8 --- /dev/null +++ b/.github/workflows/sync-website.yml @@ -0,0 +1,42 @@ +name: Sync website + +on: + push: + branches: [main] + paths: + - docs/** + +concurrency: + group: sync-website + cancel-in-progress: true + +jobs: + sync-website: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + token: ${{ github.token }} + + - name: Checkout website + uses: actions/checkout@v4 + with: + repository: mangowm/mangowm.github.io + path: website + token: ${{ secrets.WEBSITE_SYNC_TOKEN }} + fetch-depth: 1 + + - name: Sync docs + run: | + rm -rf website/apps/web/content/docs + cp -r docs website/apps/web/content/docs + + - name: Commit and push + working-directory: website + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add apps/web/content/docs + git diff --staged --quiet || git commit -m "sync from mango @ ${{ github.sha }}" + git push diff --git a/.github/workflows/sync-wiki.yml b/.github/workflows/sync-wiki.yml new file mode 100644 index 0000000..ef30fe7 --- /dev/null +++ b/.github/workflows/sync-wiki.yml @@ -0,0 +1,40 @@ +name: Sync wiki + +on: + push: + branches: [main] + paths: + - docs/** + +concurrency: + group: sync-wiki + cancel-in-progress: true + +permissions: + contents: write + +jobs: + sync-wiki: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Clone wiki + run: | + git clone --depth 1 \ + https://x-access-token:${{ github.token }}@github.com/${{ github.repository }}.wiki.git \ + wiki-temp + + - name: Sync docs to wiki + run: | + find wiki-temp -not -path 'wiki-temp/.git*' -type f -delete + python3 .github/scripts/sync-wiki.py + + - name: Commit and push + working-directory: wiki-temp + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add -A + git diff --staged --quiet || git commit -m "sync from ${{ github.sha }}" + git push diff --git a/docs/bindings/keys.md b/docs/bindings/keys.md new file mode 100644 index 0000000..64ba64b --- /dev/null +++ b/docs/bindings/keys.md @@ -0,0 +1,204 @@ +--- +title: Key Bindings +description: Define keyboard shortcuts and modes. +--- + +## Syntax + +Key bindings follow this format: + +```ini +bind[flags]=MODIFIERS,KEY,COMMAND,PARAMETERS +``` + +- **Modifiers**: `SUPER`, `CTRL`, `ALT`, `SHIFT`, `NONE` (combine with `+`, e.g. `SUPER+CTRL+ALT`). +- **Key**: Key name (from `xev` or `wev`) or keycode (e.g., `code:24` for `q`). + +> **Info:** `bind` automatically converts keysym to keycode for comparison. This makes it compatible with all keyboard layouts, but the matching may not always be precise. If a key combination doesn't work on your keyboard layout, use a keycode instead (e.g., `code:24` instead of `q`). + +### Flags + +- `l`: Works even when screen is locked. +- `s`: Uses keysym instead of keycode to bind. +- `r`: Triggers on key release instead of press. +- `p`: Pass key event to client. + +**Examples:** + +```ini +bind=SUPER,Q,killclient +bindl=SUPER,L,spawn,swaylock + +# Using keycode instead of key name +bind=ALT,code:24,killclient + +# Combining keycodes for modifiers and keys +bind=code:64,code:24,killclient +bind=code:64+code:133,code:24,killclient + +# Bind with no modifier +bind=NONE,XF86MonBrightnessUp,spawn,brightnessctl set +5% + +# Bind a modifier key itself as the trigger key +bind=alt,shift_l,switch_keyboard_layout +``` + +## Key Modes (Submaps) + +You can divide key bindings into named modes. Rules: + +1. Set `keymode=` before a group of `bind` lines — those binds only apply in that mode. +2. If no `keymode` is set before a bind, it belongs to the `default` mode. +3. The special `common` keymode applies its binds **across all modes**. + +Use `setkeymode` to switch modes, and `mmsg -b` to query the current mode. + +```ini +# Binds in 'common' apply in every mode +keymode=common +bind=SUPER,r,reload_config + +# Default mode bindings +keymode=default +bind=ALT,Return,spawn,foot +bind=SUPER,F,setkeymode,resize + +# 'resize' mode bindings +keymode=resize +bind=NONE,Left,resizewin,-10,0 +bind=NONE,Right,resizewin,+10,0 +bind=NONE,Escape,setkeymode,default +``` + +### Single Modifier Key Binding + +When binding a modifier key itself, use `NONE` for press and the modifier name for release: + +```ini +# Trigger on press of Super key +bind=none,Super_L,spawn,rofi -show run + +# Trigger on release of Super key +bindr=Super,Super_L,spawn,rofi -show run +``` + +## Dispatchers List + +### Window Management + +| Command | Param | Description | +| :--- | :--- | :--- | +| `killclient` | - | Close the focused window. | +| `togglefloating` | - | Toggle floating state. | +| `togglefullscreen` | - | Toggle fullscreen. | +| `togglefakefullscreen` | - | Toggle "fake" fullscreen (remains constrained). | +| `togglemaximizescreen` | - | Maximize window (keep decoration/bar). | +| `toggleglobal` | - | Pin window to all tags. | +| `toggle_render_border` | - | Toggle border rendering. | +| `centerwin` | - | Center the floating window. | +| `minimized` | - | Minimize window to scratchpad. | +| `restore_minimized` | - | Restore window from scratchpad. | +| `toggle_scratchpad` | - | Toggle scratchpad. | +| `toggle_named_scratchpad` | `appid,title,cmd` | Toggle named scratchpad. Launches app if not running, otherwise shows/hides it. | + +### Focus & Movement + +| Command | Param | Description | +| :--- | :--- | :--- | +| `focusdir` | `left/right/up/down` | Focus window in direction. | +| `focusstack` | `next/prev` | Cycle focus within the stack. | +| `focuslast` | - | Focus the previously active window. | +| `exchange_client` | `left/right/up/down` | Swap window with neighbor in direction. | +| `exchange_stack_client` | `next/prev` | Exchange window position in stack. | +| `zoom` | - | Swap focused window with Master. | + +### Tags & Monitors + +| Command | Param | Description | +| :--- | :--- | :--- | +| `view` | `-1/0/1-9` or `mask [,synctag]` | View tag. `-1` = previous tagset, `0` = all tags, `1-9` = specific tag, mask e.g. `1\|3\|5`. Optional `synctag` (0/1) syncs the action to all monitors. | +| `viewtoleft` | `[synctag]` | View previous tag. Optional `synctag` (0/1) syncs to all monitors. | +| `viewtoright` | `[synctag]` | View next tag. Optional `synctag` (0/1) syncs to all monitors. | +| `viewtoleft_have_client` | `[synctag]` | View left tag and focus client if present. Optional `synctag` (0/1). | +| `viewtoright_have_client` | `[synctag]` | View right tag and focus client if present. Optional `synctag` (0/1). | +| `viewcrossmon` | `tag,monitor_spec` | View specified tag on specified monitor. | +| `tag` | `1-9 [,synctag]` | Move window to tag. Optional `synctag` (0/1) syncs to all monitors. | +| `tagsilent` | `1-9` | Move window to tag without focusing it. | +| `tagtoleft` | `[synctag]` | Move window to left tag. Optional `synctag` (0/1). | +| `tagtoright` | `[synctag]` | Move window to right tag. Optional `synctag` (0/1). | +| `tagcrossmon` | `tag,monitor_spec` | Move window to specified tag on specified monitor. | +| `toggletag` | `0-9` | Toggle tag on window (0 means all tags). | +| `toggleview` | `1-9` | Toggle tag view. | +| `comboview` | `1-9` | View multi tags pressed simultaneously. | +| `focusmon` | `left/right/up/down/monitor_spec` | Focus monitor by direction or [monitor spec](/docs/configuration/monitors#monitor-spec-format). | +| `tagmon` | `left/right/up/down/monitor_spec,[keeptag]` | Move window to monitor by direction or [monitor spec](/docs/configuration/monitors#monitor-spec-format). `keeptag` is 0 or 1. | + +### Layouts + +| Command | Param | Description | +| :--- | :--- | :--- | +| `setlayout` | `name` | Switch to layout (e.g., `scroller`, `tile`). | +| `switch_layout` | - | Cycle through available layouts. | +| `incnmaster` | `+1/-1` | Increase/Decrease number of master windows. | +| `setmfact` | `+0.05` | Increase/Decrease master area size. | +| `set_proportion` | `float` | Set scroller window proportion (0.0–1.0). | +| `switch_proportion_preset` | - | Cycle proportion presets of scroller window. | +| `scroller_stack` | `left/right/up/down` | Move window inside/outside scroller stack by direction. | +| `incgaps` | `+/-value` | Adjust gap size. | +| `togglegaps` | - | Toggle gaps. | + +### System + +| Command | Param | Description | +| :--- | :--- | :--- | +| `spawn` | `cmd` | Execute a command. | +| `spawn_shell` | `cmd` | Execute shell command (supports pipes `\|`). | +| `spawn_on_empty` | `cmd,tagnumber` | Open command on empty tag. | +| `reload_config` | - | Hot-reload configuration. | +| `quit` | - | Exit mangowm. | +| `toggleoverview` | - | Toggle overview mode. | +| `create_virtual_output` | - | Create a headless monitor (for VNC/Sunshine). | +| `destroy_all_virtual_output` | - | Destroy all virtual monitors. | +| `toggleoverlay` | - | Toggle overlay state for the focused window. | +| `toggle_trackpad_enable` | - | Toggle trackpad enable. | +| `setkeymode` | `mode` | Set keymode. | +| `switch_keyboard_layout` | `[index]` | Switch keyboard layout. Optional index (0, 1, 2...) to switch to specific layout. | +| `setoption` | `key,value` | Set config option temporarily. | +| `disable_monitor` | `monitor_spec` | Shutdown monitor. Accepts a [monitor spec](/docs/configuration/monitors#monitor-spec-format). | +| `enable_monitor` | `monitor_spec` | Power on monitor. Accepts a [monitor spec](/docs/configuration/monitors#monitor-spec-format). | +| `toggle_monitor` | `monitor_spec` | Toggle monitor power. Accepts a [monitor spec](/docs/configuration/monitors#monitor-spec-format). | + +### Media Controls + +> **Warning:** Some keyboards don't send standard media keys. Run `wev` and press your key to check the exact key name. + +#### Brightness + +Requires: `brightnessctl` + +```ini +bind=NONE,XF86MonBrightnessUp,spawn,brightnessctl s +2% +bind=SHIFT,XF86MonBrightnessUp,spawn,brightnessctl s 100% +bind=NONE,XF86MonBrightnessDown,spawn,brightnessctl s 2%- +bind=SHIFT,XF86MonBrightnessDown,spawn,brightnessctl s 1% +``` + +#### Volume + +Requires: `wpctl` (WirePlumber) + +```ini +bind=NONE,XF86AudioRaiseVolume,spawn,wpctl set-volume @DEFAULT_SINK@ 5%+ +bind=NONE,XF86AudioLowerVolume,spawn,wpctl set-volume @DEFAULT_SINK@ 5%- +bind=NONE,XF86AudioMute,spawn,wpctl set-mute @DEFAULT_SINK@ toggle +bind=SHIFT,XF86AudioMute,spawn,wpctl set-mute @DEFAULT_SOURCE@ toggle +``` + +### Floating Window Movement + +| Command | Param | Description | +| :--- | :--- | :--- | +| `smartmovewin` | `left/right/up/down` | Move floating window by snap distance. | +| `smartresizewin` | `left/right/up/down` | Resize floating window by snap distance. | +| `movewin` | `(x,y)` | Move floating window. | +| `resizewin` | `(width,height)` | Resize window. | \ No newline at end of file diff --git a/docs/bindings/meta.json b/docs/bindings/meta.json new file mode 100644 index 0000000..f1b629b --- /dev/null +++ b/docs/bindings/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Bindings & Input", + "pages": ["keys", "mouse-gestures"] +} diff --git a/docs/bindings/mouse-gestures.md b/docs/bindings/mouse-gestures.md new file mode 100644 index 0000000..c4a3688 --- /dev/null +++ b/docs/bindings/mouse-gestures.md @@ -0,0 +1,116 @@ +--- +title: Mouse & Gestures +description: Configure mouse buttons, scrolling, gestures, and lid switches. +--- + +## Mouse Bindings + +Assign actions to mouse button presses with optional modifier keys. + +### Syntax + +```ini +mousebind=MODIFIERS,BUTTON,COMMAND,PARAMETERS +``` + +- **Modifiers**: `SUPER`, `CTRL`, `ALT`, `SHIFT`, `NONE`. Combine with `+` (e.g., `SUPER+CTRL`) +- **Buttons**: `btn_left`, `btn_right`, `btn_middle`, `btn_side`, `btn_extra`, `btn_forward`, `btn_back`, `btn_task` + +> **Warning:** When modifiers are set to `NONE`, only `btn_middle` works in normal mode. `btn_left` and `btn_right` only work in overview mode. + +### Examples + +```ini +# Window manipulation +mousebind=SUPER,btn_left,moveresize,curmove +mousebind=SUPER,btn_right,moveresize,curresize +mousebind=SUPER+CTRL,btn_right,killclient + +# Overview mode (requires NONE modifier) +mousebind=NONE,btn_left,toggleoverview,-1 +mousebind=NONE,btn_right,killclient,0 +mousebind=NONE,btn_middle,togglemaximizescreen,0 +``` + +--- + +## Axis Bindings + +Map scroll wheel movements to actions for workspace and window navigation. + +### Syntax + +```ini +axisbind=MODIFIERS,DIRECTION,COMMAND,PARAMETERS +``` + +- **Direction**: `UP`, `DOWN`, `LEFT`, `RIGHT` + +### Examples + +```ini +axisbind=SUPER,UP,viewtoleft_have_client +axisbind=SUPER,DOWN,viewtoright_have_client +``` + +--- + +## Gesture Bindings + +Enable touchpad swipe gestures for navigation and window management. + +### Syntax + +```ini +gesturebind=MODIFIERS,DIRECTION,FINGERS,COMMAND,PARAMETERS +``` + +- **Direction**: `up`, `down`, `left`, `right` +- **Fingers**: `3` or `4` + +> **Info:** Gestures require proper touchpad configuration. See [Input Devices](/docs/configuration/input) for touchpad settings like `tap_to_click` and `disable_while_typing`. + +### Examples + +```ini +# 3-finger: Window focus +gesturebind=none,left,3,focusdir,left +gesturebind=none,right,3,focusdir,right +gesturebind=none,up,3,focusdir,up +gesturebind=none,down,3,focusdir,down + +# 4-finger: Workspace navigation +gesturebind=none,left,4,viewtoleft_have_client +gesturebind=none,right,4,viewtoright_have_client +gesturebind=none,up,4,toggleoverview +gesturebind=none,down,4,toggleoverview +``` + +--- + +## Switch Bindings + +Trigger actions on hardware events like laptop lid open/close. + +### Syntax + +```ini +switchbind=FOLD_STATE,COMMAND,PARAMETERS +``` + +- **Fold State**: `fold` (lid closed), `unfold` (lid opened) + +> **Warning:** Disable system lid handling in `/etc/systemd/logind.conf`: +> +> ```ini +> HandleLidSwitch=ignore +> HandleLidSwitchExternalPower=ignore +> HandleLidSwitchDocked=ignore +> ``` + +### Examples + +```ini +switchbind=fold,spawn,swaylock -f -c 000000 +switchbind=unfold,spawn,wlr-dpms on +``` diff --git a/docs/configuration/basics.md b/docs/configuration/basics.md new file mode 100644 index 0000000..dbbe45f --- /dev/null +++ b/docs/configuration/basics.md @@ -0,0 +1,87 @@ +--- +title: Basic Configuration +description: Learn how to configure mangowm files, environment variables, and autostart scripts. +--- + +## Configuration File + +mangowm uses a simple configuration file format. By default, it looks for a configuration file in `~/.config/mango/`. + +1. **Locate Default Config** + + A fallback configuration is provided at `/etc/mango/config.conf`. You can use this as a reference. + +2. **Create User Config** + + Copy the default config to your local config directory to start customizing. + + ```bash + mkdir -p ~/.config/mango + cp /etc/mango/config.conf ~/.config/mango/config.conf + ``` + +3. **Launch with Custom Config (Optional)** + + If you prefer to keep your config elsewhere, you can launch mango with the `-c` flag. + + ```bash + mango -c /path/to/your_config.conf + ``` + +### Sub-Configuration + +To keep your configuration organized, you can split it into multiple files and include them using the `source` keyword. + +```ini +# Import keybindings from a separate file +source=~/.config/mango/bind.conf + +# Relative paths work too +source=./theme.conf + +# Optional: ignore if file doesn't exist (useful for shared configs) +source-optional=~/.config/mango/optional.conf +``` + +### Validate Configuration + +You can check your configuration for errors without starting mangowm: + +```bash +mango -p /path/to/config.conf +``` + +Use with `source-optional` for shared configs across different setups. + +## Environment Variables + +You can define environment variables directly within your config file. These are set before the window manager fully initializes. + +> **Warning:** Environment variables defined here will be **reset** every time you reload the configuration. + +```ini +env=GTK_THEME,Adwaita:dark +env=XCURSOR_SIZE,24 +``` + +## Autostart + +mangowm can automatically run commands or scripts upon startup. There are two modes for execution: + +| Command | Behavior | Usage Case | +| :--- | :--- | :--- | +| `exec-once` | Runs **only once** when mangowm starts. | Status bars, Wallpapers, Notification daemons | +| `exec` | Runs **every time** the config is reloaded. | Scripts that need to refresh settings | + +### Example Setup + +```ini +# Start the status bar once +exec-once=waybar + +# Set wallpaper +exec-once=swaybg -i ~/.config/mango/wallpaper/room.png + +# Reload a custom script on config change +exec=bash ~/.config/mango/reload-settings.sh +``` diff --git a/docs/configuration/input.md b/docs/configuration/input.md new file mode 100644 index 0000000..3ebc0f2 --- /dev/null +++ b/docs/configuration/input.md @@ -0,0 +1,150 @@ +--- +title: Input Devices +description: Configure keyboard layouts, mouse sensitivity, and touchpad gestures. +--- + +## Device Configuration + +mangowm provides granular control over different input devices. + +### Keyboard Settings + +Control key repeat rates and layout rules. + +| Setting | Type | Default | Description | +| :--- | :--- | :--- | :--- | +| `repeat_rate` | `int` | `25` | How many times a key repeats per second. | +| `repeat_delay` | `int` | `600` | Delay (ms) before a held key starts repeating. | +| `numlockon` | `0` or `1` | `0` | Enable NumLock on startup. | +| `xkb_rules_rules` | `string` | - | XKB rules file (e.g., `evdev`, `base`). Usually auto-detected. | +| `xkb_rules_model` | `string` | - | Keyboard model (e.g., `pc104`, `macbook`). | +| `xkb_rules_layout` | `string` | - | Keyboard layout code (e.g., `us`, `de`, `us,de`). | +| `xkb_rules_variant` | `string` | - | Layout variant (e.g., `dvorak`, `colemak`, `intl`). | +| `xkb_rules_options` | `string` | - | XKB options (e.g., `caps:escape`, `ctrl:nocaps`). | + +**Example:** + +```ini +repeat_rate=40 +repeat_delay=300 +numlockon=1 +xkb_rules_layout=us,de +xkb_rules_variant=dvorak +xkb_rules_options=caps:escape,ctrl:nocaps +``` + +--- + +### Trackpad Settings + +Specific settings for laptop touchpads. Some settings may require a relogin to take effect. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `disable_trackpad` | `0` | Set to `1` to disable the trackpad entirely. | +| `tap_to_click` | `1` | Tap to trigger a left click. | +| `tap_and_drag` | `1` | Tap and hold to drag items. | +| `trackpad_natural_scrolling` | `0` | Invert scrolling direction (natural scrolling). | +| `scroll_method` | `1` | `1` (Two-finger), `2` (Edge), `4` (Button). | +| `click_method` | `1` | `1` (Button areas), `2` (Clickfinger). | +| `drag_lock` | `1` | Lock dragging after tapping. | +| `disable_while_typing` | `1` | Disable trackpad while typing. | +| `left_handed` | `0` | Swap left/right buttons. | +| `middle_button_emulation` | `0` | Emulate middle button. | +| `swipe_min_threshold` | `1` | Minimum swipe threshold. | + +--- + +**Detailed descriptions:** + +- `scroll_method` values: + - `0` — Never send scroll events (no scrolling). + - `1` — Two-finger scrolling: send scroll events when two fingers are logically down on the device. + - `2` — Edge scrolling: send scroll events when a finger moves along the bottom or right edge. + - `4` — Button scrolling: send scroll events when a button is held and the device moves along a scroll axis. + +- `click_method` values: + - `0` — No software click emulation. + - `1` — Button areas: use software-defined areas on the touchpad to generate button events. + - `2` — Clickfinger: the number of fingers determines which button is pressed. + +- `accel_profile` values: + - `0` — No acceleration. + - `1` — Flat: no dynamic acceleration. Pointer speed = original input speed × (1 + `accel_speed`). + - `2` — Adaptive: slow movement results in less acceleration, fast movement results in more. + +- `button_map` values: + - `0` — 1/2/3 finger tap maps to left / right / middle. + - `1` — 1/2/3 finger tap maps to left / middle / right. + +- `send_events_mode` values: + - `0` — Send events from this device normally. + - `1` — Do not send events from this device. + - `2` — Disable this device when an external pointer device is plugged in. + +--- + +### Mouse Settings + +Configuration for external mice. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `mouse_natural_scrolling` | `0` | Invert scrolling direction. | +| `accel_profile` | `2` | `0` (None), `1` (Flat), `2` (Adaptive). | +| `accel_speed` | `0.0` | Speed adjustment (-1.0 to 1.0). | +| `left_handed` | `0` | Swap left and right buttons. | +| `middle_button_emulation` | `0` | Emulate middle button. | +| `swipe_min_threshold` | `1` | Minimum swipe threshold. | +| `send_events_mode` | `0` | `0` (Enabled), `1` (Disabled), `2` (Disabled on external mouse). | +| `button_map` | `0` | `0` (Left/right/middle), `1` (Left/middle/right). | + +--- + +--- + +## Keyboard Layout Switching + +To bind multiple layouts and toggle between them, define the layouts in `xkb_rules_layout` and use `xkb_rules_options` to set a toggle key combination. Then bind `switch_keyboard_layout` to trigger a switch. + +```ini +# Define two layouts: US QWERTY and US Dvorak +xkb_rules_layout=us,us +xkb_rules_variant=,dvorak +xkb_rules_options=grp:lalt_lshift_toggle +``` + +Or bind it manually to a key: + +```ini +# Bind Alt+Shift_L to cycle keyboard layout +bind=alt,shift_l,switch_keyboard_layout +``` + +Use `mmsg -g -k` to query the current keyboard layout at any time. + +--- + +## Input Method Editor (IME) + +To use Fcitx5 or IBus, set these environment variables in your config file. + +> **Info:** These settings require a restart of the window manager to take effect. + +**For Fcitx5:** + +```ini +env=GTK_IM_MODULE,fcitx +env=QT_IM_MODULE,fcitx +env=SDL_IM_MODULE,fcitx +env=XMODIFIERS,@im=fcitx +env=GLFW_IM_MODULE,ibus +``` + +**For IBus:** + +```ini +env=GTK_IM_MODULE,ibus +env=QT_IM_MODULE,ibus +env=XMODIFIERS,@im=ibus +``` diff --git a/docs/configuration/meta.json b/docs/configuration/meta.json new file mode 100644 index 0000000..bc209b4 --- /dev/null +++ b/docs/configuration/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Configuration", + "pages": ["basics", "monitors", "input", "xdg-portals", "miscellaneous"] +} diff --git a/docs/configuration/miscellaneous.md b/docs/configuration/miscellaneous.md new file mode 100644 index 0000000..9e06781 --- /dev/null +++ b/docs/configuration/miscellaneous.md @@ -0,0 +1,51 @@ +--- +title: Miscellaneous +description: Advanced settings for XWayland, focus behavior, and system integration. +--- + +## System & Hardware + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `xwayland_persistence` | `1` | Keep XWayland running even when no X11 apps are open (reduces startup lag). | +| `syncobj_enable` | `0` | Enable `drm_syncobj` timeline support (helps with gaming stutter/lag). **Requires restart.** | +| `allow_lock_transparent` | `0` | Allow the lock screen to be transparent. | +| `allow_shortcuts_inhibit` | `1` | Allow shortcuts to be inhibited by clients. | +| `vrr` | - | Set via [monitor rule](/docs/configuration/monitors#monitor-rules). | + +## Focus & Input + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `focus_on_activate` | `1` | Automatically focus windows when they request activation. | +| `sloppyfocus` | `1` | Focus follows the mouse cursor. | +| `warpcursor` | `1` | Warp the cursor to the center of the window when focus changes via keyboard. | +| `cursor_hide_timeout` | `0` | Hide the cursor after `N` seconds of inactivity (`0` to disable). | +| `drag_tile_to_tile` | `0` | Allow dragging a tiled window onto another to swap their positions. | +| `drag_corner` | `3` | Corner for drag-to-tile detection (0: none, 1–3: corners, 4: auto-detect). | +| `drag_warp_cursor` | `1` | Warp cursor when dragging windows to tile. | +| `axis_bind_apply_timeout` | `100` | Timeout (ms) for detecting consecutive scroll events for axis bindings. | +| `axis_scroll_factor` | `1.0` | Scroll factor for axis scroll speed (0.1–10.0). | + +## Multi-Monitor & Tags + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `focus_cross_monitor` | `0` | Allow directional focus to cross monitor boundaries. | +| `exchange_cross_monitor` | `0` | Allow exchanging clients across monitor boundaries. | +| `focus_cross_tag` | `0` | Allow directional focus to cross into other tags. | +| `view_current_to_back` | `0` | Toggling the current tag switches back to the previously viewed tag. | +| `scratchpad_cross_monitor` | `0` | Share the scratchpad pool across all monitors. | +| `single_scratchpad` | `1` | Only allow one scratchpad (named or standard) to be visible at a time. | +| `circle_layout` | - | A comma-separated list of layouts `switch_layout` cycles through. | + +## Window Behavior + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `enable_floating_snap` | `0` | Snap floating windows to edges or other windows. | +| `snap_distance` | `30` | Max distance (pixels) to trigger floating snap. | +| `no_border_when_single` | `0` | Remove window borders when only one window is visible on the tag. | +| `idleinhibit_ignore_visible` | `0` | Allow invisible clients (e.g., background audio players) to inhibit idle. | +| `drag_tile_refresh_interval` | `8.0` | Interval (1.0–16.0) to refresh tiled window resize during drag. Too small may cause application lag. | +| `drag_floating_refresh_interval` | `8.0` | Interval (1.0–16.0) to refresh floating window resize during drag. Too small may cause application lag. | \ No newline at end of file diff --git a/docs/configuration/monitors.md b/docs/configuration/monitors.md new file mode 100644 index 0000000..92c2082 --- /dev/null +++ b/docs/configuration/monitors.md @@ -0,0 +1,274 @@ +--- +title: Monitors +description: Manage display outputs, resolution, scaling, and tearing. +--- + +## Monitor Rules + +You can configure each display output individually using the `monitorrule` keyword. + +**Syntax:** + +```ini +monitorrule=name:Values,Parameter:Values,Parameter:Values +``` + +> **Info:** If any of the matching fields (`name`, `make`, `model`, `serial`) are set, **all** of the set ones must match to be considered a match. Use `wlr-randr` to get your monitor's name, make, model, and serial. + +### Parameters + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `name` | string | Any | Match by monitor name (supports regex) | +| `make` | string | Any | Match by monitor manufacturer | +| `model` | string | Any | Match by monitor model | +| `serial` | string | Any | Match by monitor serial number | +| `width` | integer | 0-9999 | Monitor width | +| `height` | integer | 0-9999 | Monitor height | +| `refresh` | float | 0.001-9999.0 | Monitor refresh rate | +| `x` | integer | 0-99999 | X position | +| `y` | integer | 0-99999 | Y position | +| `scale` | float | 0.01-100.0 | Monitor scale | +| `vrr` | integer | 0, 1 | Enable variable refresh rate | +| `rr` | integer | 0-7 | Monitor transform | +| `custom` | integer | 0, 1 | Enable custom mode (not supported on all displays — may cause black screen) | + +### Transform Values + +| Value | Rotation | +| :--- | :--- | +| `0` | No transform | +| `1` | 90° counter-clockwise | +| `2` | 180° counter-clockwise | +| `3` | 270° counter-clockwise | +| `4` | 180° vertical flip | +| `5` | Flip + 90° counter-clockwise | +| `6` | Flip + 180° counter-clockwise | +| `7` | Flip + 270° counter-clockwise | + +> **Critical:** If you use XWayland applications, **never use negative coordinates** for your monitor positions. This is a known XWayland bug that causes click events to malfunction. Always arrange your monitors starting from `0,0` and extend into positive coordinates. + +### Examples + +```ini +# Laptop display: 1080p, 60Hz, positioned at origin +monitorrule=name:eDP-1,width:1920,height:1080,refresh:60,x:0,y:10 + +# Match by make and model instead of name +monitorrule=make:Chimei Innolux Corporation,model:0x15F5,width:1920,height:1080,refresh:60,x:0,y:0 + +# Virtual monitor with pattern matching +monitorrule=name:HEADLESS-.*,width:1920,height:1080,refresh:60,x:1926,y:0,scale:1,rr:0,vrr:0 +``` + +--- + +## Monitor Spec Format + +Several commands (`focusmon`, `tagmon`, `disable_monitor`, `enable_monitor`, `toggle_monitor`, `viewcrossmon`, `tagcrossmon`) accept a **monitor_spec** string to identify a monitor. + +**Format:** + +```text +name:xxx&&make:xxx&&model:xxx&&serial:xxx +``` + +- Any field can be omitted and there is no order requirement. +- If all fields are omitted, the string is treated as the monitor name directly (e.g., `eDP-1`). +- Use `wlr-randr` to find your monitor's name, make, model, and serial. + +**Examples:** + +```bash +# By name (shorthand) +mmsg -d toggle_monitor,eDP-1 + +# By make and model +mmsg -d toggle_monitor,make:Chimei Innolux Corporation&&model:0x15F5 + +# By serial +mmsg -d toggle_monitor,serial:12345678 +``` + +--- + +## Tearing (Game Mode) + +Tearing allows games to bypass the compositor's VSync for lower latency. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `allow_tearing` | `0` | Global tearing control: `0` (Disable), `1` (Enable), `2` (Fullscreen only). | + +### Configuration + +**Enable Globally:** + +```ini +allow_tearing=1 +``` + +**Enable per Window:** + +Use a window rule to force tearing for specific games. + +```ini +windowrule=force_tearing:1,title:vkcube +``` + +### Tearing Behavior Matrix + +| `force_tearing` \ `allow_tearing` | DISABLED (0) | ENABLED (1) | FULLSCREEN_ONLY (2) | +| :--- | :--- | :--- | :--- | +| **UNSPECIFIED** (0) | Not Allowed | Follows tearing_hint | Only fullscreen follows tearing_hint | +| **ENABLED** (1) | Not Allowed | Allowed | Only fullscreen allowed | +| **DISABLED** (2) | Not Allowed | Not Allowed | Not Allowed | + +### Graphics Card Compatibility + +> **Warning:** Some graphics cards require setting the `WLR_DRM_NO_ATOMIC` environment variable before mango starts to successfully enable tearing. + +Add this to `/etc/environment` and reboot: + +```bash +WLR_DRM_NO_ATOMIC=1 +``` + +Or run mango with the environment variable: + +```bash +WLR_DRM_NO_ATOMIC=1 mango +``` + +--- + +## GPU Compatibility + +If mango cannot display correctly or shows a black screen, try selecting a specific GPU: + +```bash +# Use a single GPU +WLR_DRM_DEVICES=/dev/dri/card1 mango + +# Use multiple GPUs +WLR_DRM_DEVICES=/dev/dri/card0:/dev/dri/card1 mango +``` + +Some GPUs have compatibility issues with `syncobj_enable=1` — it may crash apps like `kitty` that use syncobj. Set `WLR_DRM_NO_ATOMIC=1` in `/etc/environment` and reboot to resolve this. + +--- + +## Power Management + +You can control monitor power using the `mmsg` IPC tool. + +```bash +# Turn off +mmsg -d disable_monitor,eDP-1 + +# Turn on +mmsg -d enable_monitor,eDP-1 + +# Toggle +mmsg -d toggle_monitor,eDP-1 +``` + +You can also use `wlr-randr` for monitor management: + +```bash +# Turn off monitor +wlr-randr --output eDP-1 --off + +# Turn on monitor +wlr-randr --output eDP-1 --on + +# Show all monitors +wlr-randr +``` + +--- + +## Screen Scale + +### Without Global Scale (Recommended) + +- If you do not use XWayland apps, you can use monitor rules or `wlr-randr` to set a global monitor scale. +- If you are using XWayland apps, it is not recommended to set a global monitor scale. + +You can set scale like this, for example with a 1.4 factor. + +**Dependencies:** + +```bash +yay -S xorg-xrdb +yay -S xwayland-satellite +``` + +**In config file:** + +```ini +env=QT_AUTO_SCREEN_SCALE_FACTOR,1 +env=QT_WAYLAND_FORCE_DPI,140 +``` + +**In autostart:** + +```bash +echo "Xft.dpi: 140" | xrdb -merge +gsettings set org.gnome.desktop.interface text-scaling-factor 1.4 +``` + +**Edit autostart for XWayland:** + +```bash +# Start xwayland +/usr/sbin/xwayland-satellite :11 & +# Apply scale 1.4 for xwayland +sleep 0.5s && echo "Xft.dpi: 140" | xrdb -merge +``` + +### Using xwayland-satellite to Prevent Blurry XWayland Apps + +If you use fractional scaling, you can use `xwayland-satellite` to automatically scale XWayland apps to prevent blurriness, for example with a scale of 1.4. + +**Dependencies:** + +```bash +yay -S xwayland-satellite +``` + +**In config file:** + +```ini +env=DISPLAY,:2 +exec=xwayland-satellite :2 +monitorrule=name:eDP-1,width:1920,height:1080,refresh:60,x:0,y:0,scale:1.4,vrr:0,rr:0 +``` + +> **Warning:** Use a `DISPLAY` value other than `:1` to avoid conflicting with mangowm. + +--- + +## Virtual Monitors + +You can create and manage virtual displays through IPC commands: + +```bash +# Create virtual output +mmsg -d create_virtual_output + +# Destroy all virtual outputs +mmsg -d destroy_all_virtual_output +``` + +You can configure virtual monitors using `wlr-randr`: + +```bash +# Show all monitors +wlr-randr + +# Configure virtual monitor +wlr-randr --output HEADLESS-1 --pos 1921,0 --scale 1 --custom-mode 1920x1080@60Hz +``` + +Virtual monitors can be used for screen sharing with tools like [Sunshine](https://github.com/LizardByte/Sunshine) and [Moonlight](https://github.com/moonlight-stream/moonlight-android), allowing other devices to act as extended monitors. \ No newline at end of file diff --git a/docs/configuration/xdg-portals.md b/docs/configuration/xdg-portals.md new file mode 100644 index 0000000..27819ad --- /dev/null +++ b/docs/configuration/xdg-portals.md @@ -0,0 +1,76 @@ +--- +title: XDG Portals +description: Set up screen sharing, clipboard, keyring, and file pickers using XDG portals. +--- + +## Portal Configuration + +You can customize portal settings via the following paths: + +- **User Configuration (Priority):** `~/.config/xdg-desktop-portal/mango-portals.conf` +- **System Fallback:** `/usr/share/xdg-desktop-portal/mango-portals.conf` + +> **Warning:** If you previously added `dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP=wlroots` to your config, remove it. Mango now handles this automatically. + +## Screen Sharing + +To enable screen sharing (OBS, Discord, WebRTC), you need `xdg-desktop-portal-wlr`. + +1. **Install Dependencies** + + `pipewire`, `pipewire-pulse`, `xdg-desktop-portal-wlr` + +2. **Optional: Add to autostart** + + In some situations the portal may not start automatically. You can add this to your autostart script to ensure it launches: + + ```bash + /usr/lib/xdg-desktop-portal-wlr & + ``` + +3. **Restart your computer** to apply changes. + +### Known Issues + +- **Window screen sharing:** Some applications may have issues sharing individual windows. See [#184](https://github.com/mangowm/mango/pull/184) for workarounds. + +- **Screen recording lag:** If you experience stuttering during screen recording, see [xdg-desktop-portal-wlr#351](https://github.com/emersion/xdg-desktop-portal-wlr/issues/351). + +## Clipboard Manager + +Use `cliphist` to manage clipboard history. + +**Dependencies:** `wl-clipboard`, `cliphist`, `wl-clip-persist` + +**Autostart Config:** + +```bash +# Keep clipboard content after app closes +wl-clip-persist --clipboard regular --reconnect-tries 0 & + +# Watch clipboard and store history +wl-paste --type text --watch cliphist store & +``` + +## GNOME Keyring + +If you need to store passwords or secrets (e.g., for VS Code or Minecraft launchers), install `gnome-keyring`. + +**Configuration:** + +Add the following to `~/.config/xdg-desktop-portal/mango-portals.conf`: + +```ini +[preferred] +default=gtk +org.freedesktop.impl.portal.ScreenCast=wlr +org.freedesktop.impl.portal.Screenshot=wlr +org.freedesktop.impl.portal.Secret=gnome-keyring +org.freedesktop.impl.portal.Inhibit=none +``` + +## File Picker (File Selector) + +**Dependencies:** `xdg-desktop-portal`, `xdg-desktop-portal-gtk` + +Reboot your computer once to apply. \ No newline at end of file diff --git a/docs/faq.md b/docs/faq.md new file mode 100644 index 0000000..13c6391 --- /dev/null +++ b/docs/faq.md @@ -0,0 +1,101 @@ +--- +title: FAQ +description: Frequently asked questions and troubleshooting. +--- + +### How do I arrange tiled windows with my mouse? + +You can enable the `drag_tile_to_tile` option in your config. This allows you to drag a tiled window onto another to swap them. + +```ini +drag_tile_to_tile=1 +``` + +--- + +### Why is my background blurry or why does blur look wrong? + +Blur applies to the transparent areas of windows. To disable it entirely, set `blur=0`. + +If you are experiencing **performance issues with blur**, make sure `blur_optimized=1` (the default). This caches the wallpaper as the blur background, which is much cheaper on the GPU: + +```ini +blur_optimized=1 +``` + +--- + +### Blur shows my wallpaper instead of the real background content + +This is expected behavior when `blur_optimized=1` (the default). The optimizer caches the wallpaper to reduce GPU load — windows will blur against the wallpaper rather than the actual content stacked beneath them. + +If you want blur to composite against the true background (i.e., show whatever is actually behind the window), set: + +```ini +blur_optimized=0 +``` + +> **Warning:** Disabling `blur_optimized` significantly increases GPU consumption and may cause rendering lag, especially on lower-end hardware. + +--- + +### My games are lagging or stuttering + +Try enabling **SyncObj** timeline support and **Adaptive Sync** (VRR) if your monitor supports it. + +```ini +syncobj_enable=1 +adaptive_sync=1 +``` + +--- + +### My games have high input latency + +You can enable **Tearing** (similar to VSync off). + +First, enable it globally: + +```ini +allow_tearing=1 +``` + +Then force it for your specific game: + +```ini +windowrule=force_tearing:1,title:Counter-Strike 2 +``` + +> **Warning:** Some graphics cards require setting `WLR_DRM_NO_ATOMIC=1` before mango starts for tearing to work. Add it to `/etc/environment` and reboot, or launch mango with `WLR_DRM_NO_ATOMIC=1 mango`. See [Monitors — Tearing](/docs/configuration/monitors#tearing-game-mode) for details. + +--- + +### How do I use pipes `|` in spawn commands? + +The standard `spawn` command does not support shell pipes directly. You must use `spawn_shell` instead. + +```ini +bind=SUPER,P,spawn_shell,echo "hello" | rofi -dmenu +``` + +--- + +### Certain key combinations do not work on my keyboard layout. + +`bind` automatically converts keysym to keycode, which is compatible with most layouts but can sometimes be imprecise. If a key combination is not triggering, use the **keycode** directly instead of the key name. + +Run `wev` and press the key to find its keycode, then use it in your bind: + +```ini +# Instead of: +bind=ALT,q,killclient + +# Use the keycode (e.g., code:24 = q on most layouts): +bind=ALT,code:24,killclient +``` + +You can also use `binds` (the `s` flag) to match by keysym instead of keycode: + +```ini +binds=ALT,q,killclient +``` diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..f4848cf --- /dev/null +++ b/docs/index.md @@ -0,0 +1,42 @@ +--- +title: Introduction +description: A lightweight and feature-rich Wayland compositor based on dwl. +--- + + +**mango** is a Wayland compositor based on [dwl](https://codeberg.org/dwl/dwl/). It aims to be as lightweight as `dwl` and can be built completely within a few seconds, without compromising on functionality. + +> **Philosophy:** **Lightweight & Fast**: mango is designed to be minimal yet functional. It compiles in seconds and offers a robust set of features out of the box. + +## Feature Highlights + +Beyond basic window management, mangowm provides a rich set of features designed for a modern Wayland experience. + +- **[Animations](/docs/visuals/animations)** — Smooth, customizable animations for opening, moving, closing windows and tag switching. +- **[Layouts](/docs/window-management/layouts)** — Supports Scroller, Master-Stack, Monocle, Grid, Deck, and more, with per-tag layouts. +- **[Visual Effects](/docs/visuals/effects)** — Built-in blur, shadows, corner radius, and opacity effects powered by scenefx. +- **[IPC & Scripting](/docs/ipc)** — Control the compositor externally with robust IPC support for custom scripts and widgets. + +## Additional Features + +- **XWayland Support** — Excellent compatibility for legacy X11 applications. +- **Tag System** — Uses tags instead of workspaces, allowing separate window layouts for each tag. +- **Input Methods** — Great support for text input v2/v3 (Fcitx5, IBus). +- **Window States** — Rich states including swallow, minimize, maximize, fullscreen, and overlay. +- **Hot-Reload Config** — Simple external configuration that supports hot-reloading without restarting. +- **Scratchpads** — Support for both Sway-like and named scratchpads. + +## Community + +- **[Join the mangowm Discord](https://discord.gg/CPjbDxesh5)** — Chat with the community, get support, share your setup, and stay updated with the latest mangowm news. +- **[Join the GitHub Discussions](https://github.com/mangowm/mango/discussions)** — Ask questions, request features, report issues, or share ideas directly with contributors and other users. + +## Acknowledgements + +This project is built upon the hard work of several open-source projects: + +- **[wlroots](https://gitlab.freedesktop.org/wlroots/wlroots)** — Implementation of the Wayland protocol. +- **[owl](https://github.com/dqrk0jeste/owl)** — Basal window animation reference. +- **[dwl](https://codeberg.org/dwl/dwl)** — Basal dwl features. +- **[sway](https://github.com/swaywm/sway)** — Sample implementation of the Wayland protocol. +- **[scenefx](https://github.com/wlrfx/scenefx)** — Library to simplify adding window effects. diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 0000000..1297eaf --- /dev/null +++ b/docs/installation.md @@ -0,0 +1,231 @@ +--- +title: Installation +description: Install mangowm on Arch, Fedora, Gentoo, Guix System, NixOS, PikaOS, or build from source. +--- + +## Package Installation + +mangowm is available as a pre-built package on several distributions. Choose your distribution below. + +--- + +### Arch Linux + +mangowm is available in the **Arch User Repository (AUR)**. + +You can install it using an AUR helper like `yay` or `paru`: + +```bash +yay -S mangowm-git +``` + +> **Tip:** This package pulls the latest git version, ensuring you have the newest features and fixes. + +--- + +### Fedora + +The package is in the third-party **Terra repository**. First, add the Terra Repository. + +> **Warning:** Both commands require root privileges. Use `sudo` if needed. + +```bash +dnf install --nogpgcheck --repofrompath 'terra,https://repos.fyralabs.com/terra$releasever' terra-release +``` + +Then, install the package: + +```bash +dnf install mangowm +``` + +--- + +### Gentoo + +The package is hosted in the community-maintained **GURU** repository. + +1. **Add the GURU repository** + ```bash + emerge --ask --verbose eselect-repository + eselect repository enable guru + emerge --sync guru + ``` + +2. **Unmask packages** + Add the required packages to your `package.accept_keywords` file: + - `gui-libs/scenefx` + - `gui-wm/mangowm` + +3. **Install mango** + ```bash + emerge --ask --verbose gui-wm/mangowm + ``` + +--- + +### Guix System + +The package definition is described in the source repository. + +1. **Add mango channel** + Add to `$HOME/.config/guix/channels.scm`: + ```scheme + (cons (channel + (name 'mangowm) + (url "https://github.com/mangowm/mango.git") + (branch "main")) + %default-channels) + ``` + +2. **Install** + After running `guix pull`, you can install mangowm: + ```bash + guix install mangowm + ``` + + Or add it to your system configuration using the mangowm module: + ```scheme + (use-modules (mangowm)) + + (packages (cons* + mangowm-git + ... ;; Other packages + %base-packages)) + ``` + +> **Tip:** For more information, see the [Guix System documentation](https://guix.gnu.org/manual/devel/en/html_node/Channels.html). + +--- + +### NixOS + +The repository provides a Flake with a NixOS module. + +1. **Add flake input** + ```nix + # flake.nix + { + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + mangowm = { + url = "github:mangowm/mango"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + # other inputs ... + }; + } + ``` + +2. **Import the NixOS module** + + **Option A — Import in `configuration.nix`:** + ```nix + # configuration.nix (or any other file that you import) + {inputs, ...}: { + imports = [ + inputs.mangowm.nixosModules.mango + # .. other imports ... + ]; + + # ... + } + ``` + + **Option B — Import directly in flake:** + ```nix + # flake.nix + { + # ... + + outputs = { self, nixpkgs, mangowm, ...}@inputs: let + inherit (nixpkgs) lib; + # ... + in { + nixosConfigurations.YourHostName = lib.nixosSystem { + modules = [ + mangowm.nixosModules.mango # or inputs.mangowm.nixosModules.mango + # other imports ... + ]; + }; + } + } + ``` + +3. **Enable the module** + ```nix + # configuration.nix (or any other file that you import) + { + programs.mango.enable = true; + } + ``` + +4. **Extra options** + - `programs.mango.package` — the mango package to use, allows usage of custom mango drvs + - `programs.mango.addLoginEntry` (default: `true`) — adds login entry to the display manager + +--- + +### PikaOS + +mangowm is available in the **PikaOS package repository**. + +You can install it using the `pikman` package manager: + +```bash +pikman install mangowc +``` + +--- + +## Building from Source + +If your distribution isn't listed above, or you want the latest unreleased changes, you can build mangowm from source. + +> **Info:** Ensure the following dependencies are installed before proceeding: +> - `glibc` +> - `wayland` +> - `wayland-protocols` +> - `libinput` +> - `libdrm` +> - `libxkbcommon` +> - `pixman` +> - `git` +> - `meson` +> - `ninja` +> - `libdisplay-info` +> - `libliftoff` +> - `hwdata` +> - `seatd` +> - `pcre2` +> - `xorg-xwayland` +> - `libxcb` + +You will need to build `wlroots` and `scenefx` manually as well. + +1. **Build wlroots** + Clone and install the specific version required (check README for latest version). + ```bash + git clone -b 0.19.2 https://gitlab.freedesktop.org/wlroots/wlroots.git + cd wlroots + meson build -Dprefix=/usr + sudo ninja -C build install + ``` + +2. **Build scenefx** + This library handles the visual effects. + ```bash + git clone -b 0.4.1 https://github.com/wlrfx/scenefx.git + cd scenefx + meson build -Dprefix=/usr + sudo ninja -C build install + ``` + +3. **Build mangowm** + Finally, compile the compositor itself. + ```bash + git clone https://github.com/mangowm/mango.git + cd mango + meson build -Dprefix=/usr + sudo ninja -C build install + ``` diff --git a/docs/ipc.md b/docs/ipc.md new file mode 100644 index 0000000..72beefb --- /dev/null +++ b/docs/ipc.md @@ -0,0 +1,154 @@ +--- +title: IPC +description: Control mangowm programmatically using mmsg. +--- + +## Introduction + +mangowm includes a powerful IPC (Inter-Process Communication) tool called `mmsg`. This allows you to query the window manager's state, watch for events, and execute commands from external scripts. + +## Basic Usage + +The general syntax for `mmsg` is: + +```bash +mmsg [-OTLq] +mmsg [-o ] -s [-t ] [-l ] [-c ] [-d ,,,,,] +mmsg [-o ] (-g | -w) [-OotlcvmfxekbA] +``` + +### Options + +| Flag | Description | +| :--- | :--- | +| `-q` | Quit mangowm. | +| `-g` | **Get** values (tags, layout, focused client). | +| `-s` | **Set** values (switch tags, layouts). | +| `-w` | **Watch** mode (streams events). | +| `-O` | Get all output (monitor) information. | +| `-T` | Get number of tags. | +| `-L` | Get all available layouts. | +| `-o` | Select output (monitor). | +| `-t` | Get/set selected tags (set with `[+-^.]`). | +| `-l` | Get/set current layout. | +| `-c` | Get title and appid of focused client. | +| `-v` | Get visibility of statusbar. | +| `-m` | Get fullscreen status. | +| `-f` | Get floating status. | +| `-d` | **Dispatch** an internal command. | +| `-x` | Get focused client geometry. | +| `-e` | Get the name of the last focused layer. | +| `-k` | Get current keyboard layout. | +| `-b` | Get current keybind mode. | +| `-A` | Get scale factor of monitor. | + +## Examples + +### Tag Management + +You can perform arithmetic on tags using the `-t` flag with `-s` (set). + +```bash +# Switch to Tag 1 +mmsg -t 1 + +# Add Tag 2 to current view (Multiview) +mmsg -s -t 2+ + +# Remove Tag 2 from current view +mmsg -s -t 2- + +# Toggle Tag 2 +mmsg -s -t 2^ +``` + +### Layouts + +Switch layouts programmatically. Layout codes: `S` (Scroller), `T` (Tile), `G` (Grid), `M` (Monocle), `K` (Deck), `CT` (Center Tile), `RT` (Right Tile), `VS` (Vertical Scroller), `VT` (Vertical Tile), `VG` (Vertical Grid), `VK` (Vertical Deck), `TG` (TGMix). + +```bash +# Switch to Scroller +mmsg -l "S" + +# Switch to Tile +mmsg -l "T" +``` + +### Dispatching Commands + +Any command available in `config.conf` keybindings can be run via IPC. + +```bash +# Close the focused window +mmsg -d killclient + +# Resize window by +10 width +mmsg -d resizewin,+10,0 + +# Toggle fullscreen +mmsg -d togglefullscreen + +# Disable a monitor power +mmsg -d disable_monitor,eDP-1 +``` + +### Monitoring & Status + +Use `-g` or `-w` to build custom status bars or automation scripts. + +```bash +# Watch for all message changes +mmsg -w + +# Get all messages without watch +mmsg -g + +# Watch focused client appid and title +mmsg -w -c + +# Get all available outputs +mmsg -O + +# Get all tags message +mmsg -g -t + +# Get current focused client message +mmsg -g -c + +# Get current keyboard layout +mmsg -g -k + +# Get current keybind mode +mmsg -g -b + +# Get scale factor of current monitor +mmsg -g -A +``` + +#### Tag Message Format + +- State: 0 → none, 1 → active, 2 → urgent + +Example output: + +| Monitor | Tag Number | Tag State | Clients in Tag | Focused Client | +|---------|------------|-----------|----------------|----------------| +| eDP-1 | tag 2 | 0 | 1 | 0 | + +| Monitor | occupied tags mask | active tags mask | urgent tags mask | +|---------|--------------------|------------------|------------------| +| eDP-1 | 14 | 6 | 0 | + +## Virtual Monitors + +You can create headless outputs for screen mirroring or remote desktop access (e.g., Sunshine/Moonlight). + +```bash +# Create a virtual output +mmsg -d create_virtual_output + +# Configure it (set resolution) +wlr-randr --output HEADLESS-1 --pos 1920,0 --mode 1920x1080@60Hz + +# Destroy all virtual outputs +mmsg -d destroy_all_virtual_output \ No newline at end of file diff --git a/docs/meta.json b/docs/meta.json new file mode 100644 index 0000000..f8aac0c --- /dev/null +++ b/docs/meta.json @@ -0,0 +1,14 @@ +{ + "title": "mangowm", + "pages": [ + "index", + "installation", + "quick-start", + "configuration", + "visuals", + "window-management", + "bindings", + "ipc", + "faq" + ] +} diff --git a/docs/quick-start.md b/docs/quick-start.md new file mode 100644 index 0000000..85f67cc --- /dev/null +++ b/docs/quick-start.md @@ -0,0 +1,88 @@ +--- +title: Quick Start +description: Basic configuration and first steps with mangowm. +--- + +Now that you have mangowm installed, let's get your environment set up. + +## Initial Setup + +1. **Create Configuration Directory** + + mangowm looks for configuration files in `~/.config/mango/`. + + ```bash + mkdir -p ~/.config/mango + ``` + +2. **Copy Default Config** + + A default configuration file is provided at `/etc/mango/config.conf`. Copy it to your local directory to start customizing. + + ```bash + cp /etc/mango/config.conf ~/.config/mango/config.conf + ``` + +3. **Launch mangowm** + + You can now start the compositor from your TTY. + + ```bash + mango + ``` + + Optional: To specify a custom config file path: + + ```bash + mango -c /path/to/your/config.conf + ``` + +## Essential Keybindings + +mangowm uses the following keybinds by default: + +| Key Combination | Action | +| :--- | :--- | +| `Alt` + `Return` | Open Terminal (defaults to `foot`) | +| `Alt` + `Space` | Open Launcher (defaults to `rofi`) | +| `Alt` + `Q` | Close (Kill) the active window | +| `Super` + `M` | Quit mangowm | +| `Super` + `F` | Toggle Fullscreen | +| `Alt` + `Arrow Keys` | Move focus (Left, Right, Up, Down) | +| `Ctrl` + `1-9` | Switch to Tag 1-9 | +| `Alt` + `1-9` | Move window to Tag 1-9 | + +> **Warning:** Some default bindings rely on specific tools like `foot` (terminal) and `rofi` (launcher). Ensure you have them installed or update your `config.conf` to use your preferred alternatives. + +## Recommended Tools + +To get a fully functional desktop experience, we recommend installing the following components: + +| Category | Recommended Tools | +| :--- | :--- | +| Application Launcher | rofi, bemenu, wmenu, fuzzel | +| Terminal Emulator | foot, wezterm, alacritty, kitty, ghostty | +| Status Bar | waybar, eww, quickshell, ags | +| Desktop Shell | Noctalia, DankMaterialShell | +| Wallpaper Setup | swww, swaybg | +| Notification Daemon | swaync, dunst, mako | +| Desktop Portal | xdg-desktop-portal, xdg-desktop-portal-wlr, xdg-desktop-portal-gtk | +| Clipboard | wl-clipboard, wl-clip-persist, cliphist | +| Gamma Control / Night Light | wlsunset, gammastep | +| Miscellaneous | xfce-polkit, wlogout | + +## Example Configuration + +Check out the [example configuration](https://github.com/DreamMaoMao/mango-config) by the creator of mangowm, including complete setups for mangowm, Waybar, Rofi, and more. + +```bash +git clone https://github.com/DreamMaoMao/mango-config.git ~/.config/mango +``` + +## Next Steps + +Now that you are up and running, dive deeper into customizing mangowm: + +- [Configure Monitors](/docs/configuration/monitors) — Set up resolution, scaling, and multi-monitor layouts. +- [Window Rules](/docs/window-management/rules#window-rules) — Define how specific applications should behave. +- [Appearance](/docs/visuals/theming) — Customize colors, borders, gaps, and effects. diff --git a/docs/visuals/animations.md b/docs/visuals/animations.md new file mode 100644 index 0000000..b4b8881 --- /dev/null +++ b/docs/visuals/animations.md @@ -0,0 +1,108 @@ +--- +title: Animations +description: Configure smooth transitions for windows and layers. +--- + +## Enabling Animations + +mangowm supports animations for both standard windows and layer shell surfaces (like bars and notifications). + +```ini +animations=1 +layer_animations=1 +``` + +## Animation Types + +You can define different animation styles for opening and closing windows and layer surfaces. + +Available types: `slide`, `zoom`, `fade`, `none`. + +```ini +animation_type_open=zoom +animation_type_close=slide +layer_animation_type_open=slide +layer_animation_type_close=slide +``` + +## Fade Settings + +Control the fade-in and fade-out effects for animations. + +```ini +animation_fade_in=1 +animation_fade_out=1 +fadein_begin_opacity=0.5 +fadeout_begin_opacity=0.5 +``` + +- `animation_fade_in` — Enable fade-in effect (0: disable, 1: enable) +- `animation_fade_out` — Enable fade-out effect (0: disable, 1: enable) +- `fadein_begin_opacity` — Starting opacity for fade-in animations (0.0–1.0) +- `fadeout_begin_opacity` — Starting opacity for fade-out animations (0.0–1.0) + +## Zoom Settings + +Adjust the zoom ratios for zoom animations. + +```ini +zoom_initial_ratio=0.3 +zoom_end_ratio=0.8 +``` + +- `zoom_initial_ratio` — Initial zoom ratio +- `zoom_end_ratio` — End zoom ratio + +## Durations + +Control the speed of animations (in milliseconds). + +| Setting | Type | Default | Description | +| :--- | :--- | :--- | :--- | +| `animation_duration_move` | integer | `500` | Move animation duration (ms) | +| `animation_duration_open` | integer | `400` | Open animation duration (ms) | +| `animation_duration_tag` | integer | `300` | Tag animation duration (ms) | +| `animation_duration_close` | integer | `300` | Close animation duration (ms) | +| `animation_duration_focus` | integer | `0` | Focus change (opacity transition) animation duration (ms) | + +```ini +animation_duration_move=500 +animation_duration_open=400 +animation_duration_tag=300 +animation_duration_close=300 +animation_duration_focus=0 +``` + +## Custom Bezier Curves + +Bezier curves determine the "feel" of an animation (e.g., linear vs. bouncy). The format is `x1,y1,x2,y2`. + +You can visualize and generate curve values using online tools like [cssportal.com](https://www.cssportal.com/css-cubic-bezier-generator/) or [easings.net](https://easings.net). + +| Setting | Type | Default | Description | +| :--- | :--- | :--- | :--- | +| `animation_curve_open` | string | `0.46,1.0,0.29,0.99` | Open animation bezier curve | +| `animation_curve_move` | string | `0.46,1.0,0.29,0.99` | Move animation bezier curve | +| `animation_curve_tag` | string | `0.46,1.0,0.29,0.99` | Tag animation bezier curve | +| `animation_curve_close` | string | `0.46,1.0,0.29,0.99` | Close animation bezier curve | +| `animation_curve_focus` | string | `0.46,1.0,0.29,0.99` | Focus change (opacity transition) animation bezier curve | +| `animation_curve_opafadein` | string | `0.46,1.0,0.29,0.99` | Open opacity animation bezier curve | +| `animation_curve_opafadeout` | string | `0.5,0.5,0.5,0.5` | Close opacity animation bezier curve | + +```ini +animation_curve_open=0.46,1.0,0.29,0.99 +animation_curve_move=0.46,1.0,0.29,0.99 +animation_curve_tag=0.46,1.0,0.29,0.99 +animation_curve_close=0.46,1.0,0.29,0.99 +animation_curve_focus=0.46,1.0,0.29,0.99 +animation_curve_opafadein=0.46,1.0,0.29,0.99 +animation_curve_opafadeout=0.5,0.5,0.5,0.5 +``` + +## Tag Animation Direction + +Control the direction of tag switch animations. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `tag_animation_direction` | `1` | Tag animation direction (1: horizontal, 0: vertical) | \ No newline at end of file diff --git a/docs/visuals/effects.md b/docs/visuals/effects.md new file mode 100644 index 0000000..23c1f20 --- /dev/null +++ b/docs/visuals/effects.md @@ -0,0 +1,82 @@ +--- +title: Window Effects +description: Add visual polish with blur, shadows, and opacity. +--- + +## Blur + +Blur creates a frosted glass effect for transparent windows. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `blur` | `0` | Enable blur for windows. | +| `blur_layer` | `0` | Enable blur for layer surfaces (like bars/docks). | +| `blur_optimized` | `1` | Caches the wallpaper and blur background, significantly reducing GPU usage. Disabling it will significantly increase GPU consumption and may cause rendering lag. **Highly recommended.** | +| `blur_params_radius` | `5` | The strength (radius) of the blur. | +| `blur_params_num_passes` | `1` | Number of passes. Higher = smoother but more expensive. | +| `blur_params_noise` | `0.02` | Blur noise level. | +| `blur_params_brightness` | `0.9` | Blur brightness adjustment. | +| `blur_params_contrast` | `0.9` | Blur contrast adjustment. | +| `blur_params_saturation` | `1.2` | Blur saturation adjustment. | + +> **Warning:** Blur has a relatively high impact on performance. If your hardware is limited, it is not recommended to enable it. If you experience lag with blur on, ensure `blur_optimized=1` — disabling it will significantly increase GPU consumption and may cause rendering lag. To disable blur entirely, set `blur=0`. + +--- + +## Shadows + +Drop shadows help distinguish floating windows from the background. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `shadows` | `0` | Enable shadows. | +| `layer_shadows` | `0` | Enable shadows for layer surfaces. | +| `shadow_only_floating` | `1` | Only draw shadows for floating windows (saves performance). | +| `shadows_size` | `10` | Size of the shadow. | +| `shadows_blur` | `15` | Shadow blur amount. | +| `shadows_position_x` | `0` | Shadow X offset. | +| `shadows_position_y` | `0` | Shadow Y offset. | +| `shadowscolor` | `0x000000ff` | Color of the shadow. | + +```ini +# Example shadows configuration +shadows=1 +layer_shadows=1 +shadow_only_floating=1 +shadows_size=12 +shadows_blur=15 +shadows_position_x=0 +shadows_position_y=0 +shadowscolor=0x000000ff +``` + +--- + +## Opacity & Corner Radius + +Control the transparency and roundness of your windows. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `border_radius` | `0` | Window corner radius in pixels. | +| `border_radius_location_default` | `0` | Corner radius location: `0` (all), `1` (top-left), `2` (top-right), `3` (bottom-left), `4` (bottom-right), `5` (closest corner). | +| `no_radius_when_single` | `0` | Disable radius if only one window is visible. | +| `focused_opacity` | `1.0` | Opacity for the active window (0.0 - 1.0). | +| `unfocused_opacity` | `1.0` | Opacity for inactive windows (0.0 - 1.0). | + +```ini +# Window corner radius in pixels +border_radius=0 + +# Corner radius location (0=all, 1=top-left, 2=top-right, 3=bottom-left, 4=bottom-right) +border_radius_location_default=0 + +# Disable radius if only one window is visible +no_radius_when_single=0 + +# Opacity for the active window (0.0 - 1.0) +focused_opacity=1.0 + +# Opacity for inactive windows +unfocused_opacity=1.0 +``` diff --git a/docs/visuals/meta.json b/docs/visuals/meta.json new file mode 100644 index 0000000..58723c4 --- /dev/null +++ b/docs/visuals/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Visuals", + "pages": ["theming", "status-bar", "effects", "animations"] +} diff --git a/docs/visuals/status-bar.md b/docs/visuals/status-bar.md new file mode 100644 index 0000000..f2924e8 --- /dev/null +++ b/docs/visuals/status-bar.md @@ -0,0 +1,141 @@ +--- +title: Status Bar +description: Configure Waybar for mangowm. +--- + +## Module Configuration + +mangowm is compatible with Waybar's `ext/workspaces` module (Wayland standard) or the `dwl/tags` module. We recommend `ext/workspaces` for the best experience. + +> **Tip:** You can also use the `dwl/tags` module, but `ext/workspaces` provides better integration with mangowm's features. The `ext/workspaces` module requires **Waybar > 0.14.0**. + +### `config.jsonc` + +Add the following to your Waybar configuration: + +```jsonc +{ + "modules-left": [ + "ext/workspaces", + "dwl/window" + ], + "ext/workspaces": { + "format": "{icon}", + "ignore-hidden": true, + "on-click": "activate", + "on-click-right": "deactivate", + "sort-by-id": true + }, + "dwl/window": { + "format": "[{layout}] {title}" + } +} +``` + +## Styling + +You can style the tags using standard CSS in `style.css`. + +### `style.css` + +```css +#workspaces { + border-radius: 4px; + border-width: 2px; + border-style: solid; + border-color: #c9b890; + margin-left: 4px; + padding-left: 10px; + padding-right: 6px; + background: rgba(40, 40, 40, 0.76); +} + +#workspaces button { + border: none; + background: none; + box-shadow: inherit; + text-shadow: inherit; + color: #ddca9e; + padding: 1px; + padding-left: 1px; + padding-right: 1px; + margin-right: 2px; + margin-left: 2px; +} + +#workspaces button.hidden { + color: #9e906f; + background-color: transparent; +} + +#workspaces button.visible { + color: #ddca9e; +} + +#workspaces button:hover { + color: #d79921; +} + +#workspaces button.active { + background-color: #ddca9e; + color: #282828; + margin-top: 5px; + margin-bottom: 5px; + padding-top: 1px; + padding-bottom: 0px; + border-radius: 3px; +} + +#workspaces button.urgent { + background-color: #ef5e5e; + color: #282828; + margin-top: 5px; + margin-bottom: 5px; + padding-top: 1px; + padding-bottom: 0px; + border-radius: 3px; +} + +#tags { + background-color: transparent; +} + +#tags button { + background-color: #fff; + color: #a585cd; +} + +#tags button:not(.occupied):not(.focused) { + font-size: 0; + min-width: 0; + min-height: 0; + margin: -17px; + padding: 0; + color: transparent; + background-color: transparent; +} + +#tags button.occupied { + background-color: #fff; + color: #cdc885; +} + +#tags button.focused { + background-color: rgb(186, 142, 213); + color: #fff; +} + +#tags button.urgent { + background: rgb(171, 101, 101); + color: #fff; +} + +#window { + background-color: rgb(237, 196, 147); + color: rgb(63, 37, 5); +} +``` + +## Complete Configuration Example + +> **Tip:** You can find a complete Waybar configuration for mangowm at [waybar-config](https://github.com/DreamMaoMao/waybar-config). \ No newline at end of file diff --git a/docs/visuals/theming.md b/docs/visuals/theming.md new file mode 100644 index 0000000..789ce47 --- /dev/null +++ b/docs/visuals/theming.md @@ -0,0 +1,59 @@ +--- +title: Theming +description: Customize the visual appearance of borders, colors, and the cursor. +--- + +## Dimensions + +Control the sizing of window borders and gaps. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `borderpx` | `4` | Border width in pixels. | +| `gappih` | `5` | Horizontal inner gap (between windows). | +| `gappiv` | `5` | Vertical inner gap. | +| `gappoh` | `10` | Horizontal outer gap (between windows and screen edges). | +| `gappov` | `10` | Vertical outer gap. | + +## Colors + +Colors are defined in `0xRRGGBBAA` hex format. + +```ini +# Background color of the root window +rootcolor=0x323232ff + +# Inactive window border +bordercolor=0x444444ff + +# Active window border +focuscolor=0xc66b25ff + +# Urgent window border (alerts) +urgentcolor=0xad401fff +``` + +### State-Specific Colors + +You can also color-code windows based on their state: + +| State | Config Key | Default Color | +| :--- | :--- | :--- | +| Maximized | `maximizescreencolor` | `0x89aa61ff` | +| Scratchpad | `scratchpadcolor` | `0x516c93ff` | +| Global | `globalcolor` | `0xb153a7ff` | +| Overlay | `overlaycolor` | `0x14a57cff` | + +> **Tip:** For scratchpad window sizing, see [Scratchpad](/docs/window-management/scratchpad) configuration. + +## Cursor Theme + +Set the size and theme of your mouse cursor. + +```ini +cursor_size=24 +cursor_theme=Adwaita +``` + +> **Tip:** You may also want to set the `XCURSOR_SIZE` environment variable to match: +> `env=XCURSOR_SIZE,24` diff --git a/docs/window-management/layouts.md b/docs/window-management/layouts.md new file mode 100644 index 0000000..26c05fe --- /dev/null +++ b/docs/window-management/layouts.md @@ -0,0 +1,99 @@ +--- +title: Layouts +description: Configure and switch between different window layouts. +--- + +## Supported Layouts + +mangowm supports a variety of layouts that can be assigned per tag. + +- `tile` +- `scroller` +- `monocle` +- `grid` +- `deck` +- `center_tile` +- `vertical_tile` +- `right_tile` +- `vertical_scroller` +- `vertical_grid` +- `vertical_deck` +- `tgmix` + +--- + +## Scroller Layout + +The Scroller layout positions windows in a scrollable strip, similar to PaperWM. + +### Configuration + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `scroller_structs` | `20` | Width reserved on sides when window ratio is 1. | +| `scroller_default_proportion` | `0.9` | Default width proportion for new windows. | +| `scroller_focus_center` | `0` | Always center the focused window (1 = enable). | +| `scroller_prefer_center` | `0` | Center focused window only if it was outside the view. | +| `scroller_prefer_overspread` | `1` | Allow windows to overspread when there's extra space. | +| `edge_scroller_pointer_focus` | `1` | Focus windows even if partially off-screen. | +| `scroller_proportion_preset` | `0.5,0.8,1.0` | Presets for cycling window widths. | +| `scroller_ignore_proportion_single` | `1` | Ignore proportion adjustments for single windows. | +| `scroller_default_proportion_single` | `1.0` | Default proportion for single windows in scroller. **Requires `scroller_ignore_proportion_single=0` to take effect.** | + +> **Warning:** `scroller_prefer_overspread`, `scroller_focus_center`, and `scroller_prefer_center` interact with each other. Their priority order is: +> +> **scroller_prefer_overspread > scroller_focus_center > scroller_prefer_center** +> +> To ensure a lower-priority setting takes effect, you must set all higher-priority options to `0`. + +```ini +# Example scroller configuration +scroller_structs=20 +scroller_default_proportion=0.9 +scroller_focus_center=0 +scroller_prefer_center=0 +scroller_prefer_overspread=1 +edge_scroller_pointer_focus=1 +scroller_default_proportion_single=1.0 +scroller_proportion_preset=0.5,0.8,1.0 +``` + +--- + +## Master-Stack Layouts + +These settings apply to layouts like `tile` and `center_tile`. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `new_is_master` | `1` | New windows become the master window. | +| `default_mfact` | `0.55` | The split ratio between master and stack areas. | +| `default_nmaster` | `1` | Number of allowed master windows. | +| `smartgaps` | `0` | Disable gaps when only one window is present. | +| `center_master_overspread` | `0` | (Center Tile) Master spreads across screen if no stack exists. | +| `center_when_single_stack` | `1` | (Center Tile) Center master when only one stack window exists. | + +```ini +# Example master-stack configuration +new_is_master=1 +smartgaps=0 +default_mfact=0.55 +default_nmaster=1 +``` + +--- + +## Switching Layouts + +You can switch layouts dynamically or set a default for specific tags using [Tag Rules](/docs/window-management/rules#tag-rules). + +**Keybinding Examples:** + +```ini +# Cycle through layouts +bind=SUPER,n,switch_layout + +# Set specific layout +bind=SUPER,t,setlayout,tile +bind=SUPER,s,setlayout,scroller +``` diff --git a/docs/window-management/meta.json b/docs/window-management/meta.json new file mode 100644 index 0000000..e0937d1 --- /dev/null +++ b/docs/window-management/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Window Management", + "pages": ["layouts", "rules", "overview", "scratchpad"] +} diff --git a/docs/window-management/overview.md b/docs/window-management/overview.md new file mode 100644 index 0000000..290d613 --- /dev/null +++ b/docs/window-management/overview.md @@ -0,0 +1,36 @@ +--- +title: Overview +description: Configure the overview mode for window navigation. +--- + +## Overview Settings + +| Setting | Type | Default | Description | +| :--- | :--- | :--- | :--- | +| `hotarea_size` | integer | `10` | Hot area size in pixels. | +| `enable_hotarea` | integer | `1` | Enable hot areas (0: disable, 1: enable). | +| `hotarea_corner` | integer | `2` | Hot area corner (0: top-left, 1: top-right, 2: bottom-left, 3: bottom-right). | +| `ov_tab_mode` | integer | `0` | Overview tab mode (0: disable, 1: enable). | +| `overviewgappi` | integer | `5` | Inner gap in overview mode. | +| `overviewgappo` | integer | `30` | Outer gap in overview mode. | + +### Setting Descriptions + +- `enable_hotarea` — Toggles overview when the cursor enters the configured corner. +- `hotarea_size` — Size of the hot area trigger zone in pixels. +- `hotarea_corner` — Corner that triggers the hot area (0: top-left, 1: top-right, 2: bottom-left, 3: bottom-right). +- `ov_tab_mode` — Circles focus through windows in overview; releasing the mod key exits overview. + +### Mouse Interaction in Overview + +When in overview mode: + +- **Left mouse button** — Jump to (focus) a window. +- **Right mouse button** — Close a window. + +To enable this behavior, add the following mouse bindings to your config: + +```ini +mousebind=NONE,btn_left,toggleoverview,1 +mousebind=NONE,btn_right,killclient,0 +``` diff --git a/docs/window-management/rules.md b/docs/window-management/rules.md new file mode 100644 index 0000000..996c172 --- /dev/null +++ b/docs/window-management/rules.md @@ -0,0 +1,249 @@ +--- +title: Rules +description: Define behavior for specific windows, tags, and layers. +--- + +## Window Rules + +Window rules allow you to set specific properties (floating, opacity, size, animations, etc.) for applications based on their `appid` or `title`. You can set all parameters in one line, and if you both set appid and title, the window will only follow the rules when appid and title both match. + +**Format:** + +```ini +windowrule=Parameter:Values,title:Values +windowrule=Parameter:Values,Parameter:Values,appid:Values,title:Values +``` + +### State & Behavior Parameters + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `appid` | string | Any | Match by application ID, supports regex | +| `title` | string | Any | Match by window title, supports regex | +| `isfloating` | integer | `0` / `1` | Force floating state | +| `isfullscreen` | integer | `0` / `1` | Force fullscreen state | +| `isfakefullscreen` | integer | `0` / `1` | Force fake-fullscreen state (window stays constrained) | +| `isglobal` | integer | `0` / `1` | Open as global window (sticky across tags) | +| `isoverlay` | integer | `0` / `1` | Make it always in top layer | +| `isopensilent` | integer | `0` / `1` | Open without focus | +| `istagsilent` | integer | `0` / `1` | Don't focus if client is not in current view tag | +| `force_maximize` | integer | `0` / `1` (default 1) | The state of client default to maximized | +| `ignore_maximize` | integer | `0` / `1` (default 1) | Don't handle maximize request from client | +| `ignore_minimize` | integer | `0` / `1` (default 1) | Don't handle minimize request from client | +| `force_tiled_state` | integer | `0` / `1` | Deceive the window into thinking it is tiling, so it better adheres to assigned dimensions | +| `noopenmaximized` | integer | `0` / `1` | Window does not open as maximized mode | +| `single_scratchpad` | integer | `0` / `1` (default 1) | Only show one out of named scratchpads or the normal scratchpad | +| `allow_shortcuts_inhibit` | integer | `0` / `1` (default 1) | Allow shortcuts to be inhibited by clients | +| `indleinhibit_when_focus` | integer | `0` / `1` (default 0) | Automatically keep idle inhibit active when this window is focused | + +### Geometry & Position + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `width` | integer | 0-9999 | Window width when it becomes a floating window | +| `height` | integer | 0-9999 | Window height when it becomes a floating window | +| `offsetx` | integer | -999-999 | X offset from center (%), 100 is the edge of screen with outer gap | +| `offsety` | integer | -999-999 | Y offset from center (%), 100 is the edge of screen with outer gap | +| `monitor` | string | Any | Assign to monitor by [monitor spec](/docs/configuration/monitors#monitor-spec-format) (name, make, model, or serial) | +| `tags` | integer | 1-9 | Assign to specific tag | +| `no_force_center` | integer | `0` / `1` | Window does not force center | +| `isnosizehint` | integer | `0` / `1` | Don't use min size and max size for size hints | + +### Visuals & Decoration + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `noblur` | integer | `0` / `1` | Window does not have blur effect | +| `isnoborder` | integer | `0` / `1` | Remove window border | +| `isnoshadow` | integer | `0` / `1` | Not apply shadow | +| `isnoradius` | integer | `0` / `1` | Not apply corner radius | +| `isnoanimation` | integer | `0` / `1` | Not apply animation | +| `focused_opacity` | integer | `0` / `1` | Window focused opacity | +| `unfocused_opacity` | integer | `0` / `1` | Window unfocused opacity | +| `allow_csd` | integer | `0` / `1` | Allow client side decoration | + +> **Tip:** For detailed visual effects configuration, see the [Window Effects](/docs/visuals/effects) page for blur, shadows, and opacity settings. + +### Layout & Scroller + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `scroller_proportion` | float | 0.1-1.0 | Set scroller proportion | +| `scroller_proportion_single` | float | 0.1-1.0 | Set scroller auto adjust proportion when it is single window | + +> **Tip:** For comprehensive layout configuration, see the [Layouts](/docs/window-management/layouts) page for all layout options and detailed settings. + +### Animation + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `animation_type_open` | string | zoom, slide, fade, none | Set open animation | +| `animation_type_close` | string | zoom, slide, fade, none | Set close animation | +| `nofadein` | integer | `0` / `1` | Window ignores fade-in animation | +| `nofadeout` | integer | `0` / `1` | Window ignores fade-out animation | + +> **Tip:** For detailed animation configuration, see the [Animations](/docs/visuals/animations) page for available types and settings. + +### Terminal & Swallowing + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `isterm` | integer | `0` / `1` | A new GUI window will replace the isterm window when it is opened | +| `noswallow` | integer | `0` / `1` | The window will not replace the isterm window | + +### Global & Special Windows + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `globalkeybinding` | string | `[mod combination][-][key]` | Global keybinding (only works for Wayland apps) | +| `isunglobal` | integer | `0` / `1` | Open as unmanaged global window (for desktop pets or camera windows) | +| `isnamedscratchpad` | integer | `0` / `1` | 0: disable, 1: named scratchpad | + +> **Tip:** For scratchpad usage, see the [Scratchpad](/docs/window-management/scratchpad) page for detailed configuration examples. + +### Performance & Tearing + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `force_tearing` | integer | `0` / `1` | Set window to tearing state, refer to [Tearing](/docs/configuration/monitors#tearing-game-mode) | + +### Examples + +```ini +# Set specific window size and position +windowrule=width:1000,height:900,appid:yesplaymusic,title:Demons + +# Global keybindings for OBS Studio +windowrule=globalkeybinding:ctrl+alt-o,appid:com.obsproject.Studio +windowrule=globalkeybinding:ctrl+alt+n,appid:com.obsproject.Studio +windowrule=isopensilent:1,appid:com.obsproject.Studio + +# Force tearing for games +windowrule=force_tearing:1,title:vkcube +windowrule=force_tearing:1,title:Counter-Strike 2 + +# Named scratchpad for file manager +windowrule=isnamedscratchpad:1,width:1280,height:800,appid:st-yazi + +# Custom opacity for specific apps +windowrule=focused_opacity:0.8,appid:firefox +windowrule=unfocused_opacity:0.6,appid:foot + +# Disable blur for selection tools +windowrule=noblur:1,appid:slurp + +# Position windows relative to screen center +windowrule=offsetx:20,offsety:-30,width:800,height:600,appid:alacritty + +# Send to specific tag and monitor +windowrule=tags:9,monitor:HDMI-A-1,appid:discord + +# Terminal swallowing setup +windowrule=isterm:1,appid:st +windowrule=noswallow:1,appid:foot + +# Disable client-side decorations +windowrule=allow_csd:1,appid:firefox + +# Unmanaged global window (desktop pets, camera) +windowrule=isunglobal:1,appid:cheese + +# Named scratchpad toggle +bind=alt,h,toggle_named_scratchpad,st-yazi,none,st -c st-yazi -e yazi +``` + +--- + +## Tag Rules + +You can set all parameters in one line. If only `id` is set, the rule is followed when the id matches. If any of `monitor_name`, `monitor_make`, `monitor_model`, or `monitor_serial` are set, the rule is followed only if **all** of the set monitor fields match. + +> **Warning:** Layouts set in tag rules have a higher priority than monitor rule layouts. + +**Format:** + +```ini +tagrule=id:Values,Parameter:Values,Parameter:Values +tagrule=id:Values,monitor_name:eDP-1,Parameter:Values,Parameter:Values +tagrule=id:Values,monitor_make:xxx,monitor_model:xxx,Parameter:Values +``` + +> **Tip:** See [Layouts](/docs/window-management/layouts#supported-layouts) for detailed descriptions of each layout type. + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `id` | integer | 0-9 | Match by tag id, 0 means the ~0 tag | +| `monitor_name` | string | monitor name | Match by monitor name | +| `monitor_make` | string | monitor make | Match by monitor manufacturer | +| `monitor_model` | string | monitor model | Match by monitor model | +| `monitor_serial` | string | monitor serial | Match by monitor serial number | +| `layout_name` | string | layout name | Layout name to set | +| `no_render_border` | integer | `0` / `1` | Disable render border | +| `no_hide` | integer | `0` / `1` | Not hide even if the tag is empty | +| `nmaster` | integer | 0, 99 | Number of master windows | +| `mfact` | float | 0.1–0.9 | Master area factor | + +### Examples + +```ini +# Set layout for specific tags +tagrule=id:1,layout_name:scroller +tagrule=id:2,layout_name:scroller + +# Limit to specific monitor +tagrule=id:1,monitor_name:eDP-1,layout_name:scroller +tagrule=id:2,monitor_name:eDP-1,layout_name:scroller + +# Persistent tags (1-4) with layout assignment +tagrule=id:1,no_hide:1,layout_name:scroller +tagrule=id:2,no_hide:1,layout_name:scroller +tagrule=id:3,monitor_name:eDP-1,no_hide:1,layout_name:scroller +tagrule=id:4,monitor_name:eDP-1,no_hide:1,layout_name:scroller + +# Advanced tag configuration with master layout settings +tagrule=id:5,layout_name:tile,nmaster:2,mfact:0.6 +tagrule=id:6,monitor_name:HDMI-A-1,layout_name:monocle,no_render_border:1 +``` + +> **Tip:** For Waybar configuration with persistent tags, see [Status Bar](/docs/visuals/status-bar) documentation. + +--- + +## Layer Rules + +You can set all parameters in one line. Target "layer shell" surfaces like status bars (`waybar`), launchers (`rofi`), or notification daemons. + +**Format:** + +```ini +layerrule=layer_name:Values,Parameter:Values,Parameter:Values +``` + +> **Tip:** You can use `mmsg -e` to get the last open layer name for debugging. + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `layer_name` | string | layer name | Match name of layer, supports regex | +| `animation_type_open` | string | slide, zoom, fade, none | Set open animation | +| `animation_type_close` | string | slide, zoom, fade, none | Set close animation | +| `noblur` | integer | `0` / `1` | Disable blur | +| `noanim` | integer | `0` / `1` | Disable layer animation | +| `noshadow` | integer | `0` / `1` | Disable layer shadow | + +> **Tip:** For animation types, see [Animations](/docs/visuals/animations#animation-types). For visual effects, see [Window Effects](/docs/visuals/effects). + +### Examples + +```ini +# No blur or animation for slurp selection layer (avoids occlusion and ghosting in screenshots) +layerrule=noanim:1,noblur:1,layer_name:selection + +# Zoom animation for Rofi with multiple parameters +layerrule=animation_type_open:zoom,noanim:0,layer_name:rofi + +# Disable animations and shadows for notification daemon +layerrule=noanim:1,noshadow:1,layer_name:swaync + +# Multiple effects for launcher +layerrule=animation_type_open:slide,animation_type_close:fade,noblur:1,layer_name:wofi +``` diff --git a/docs/window-management/scratchpad.md b/docs/window-management/scratchpad.md new file mode 100644 index 0000000..398182f --- /dev/null +++ b/docs/window-management/scratchpad.md @@ -0,0 +1,73 @@ +--- +title: Scratchpad +description: Manage hidden "scratchpad" windows for quick access. +--- + +mangowm supports two types of scratchpads: the standard pool (Sway-like) and named scratchpads. + +## Standard Scratchpad + +Any window can be sent to the "scratchpad" pile, which hides it. You can then cycle through them. + +**Keybindings:** + +```ini +# Send current window to scratchpad +bind=SUPER,i,minimized + +# Toggle (show/hide) the scratchpad +bind=ALT,z,toggle_scratchpad + +# Retrieve window from scratchpad (restore) +bind=SUPER+SHIFT,i,restore_minimized +``` + +--- + +## Named Scratchpad + +Named scratchpads are bound to specific keys and applications. When triggered, mangowm will either launch the app (if not running) or toggle its visibility. + +**1. Define the Window Rule** + +You must identify the app using a unique `appid` or `title` and mark it as a named scratchpad. The application must support setting a custom appid or title at launch. Common examples: + +- `st -c my-appid` — sets the appid +- `kitty -T my-title` — sets the window title +- `foot --app-id my-appid` — sets the appid + +Use `none` as a placeholder when you only want to match by one field. + +```ini +# Match by appid +windowrule=isnamedscratchpad:1,width:1280,height:800,appid:st-yazi + +# Match by title +windowrule=isnamedscratchpad:1,width:1000,height:700,title:kitty-scratch +``` + +**2. Bind the Toggle Key** + +Format: `bind=MOD,KEY,toggle_named_scratchpad,appid,title,command` + +Use `none` for whichever field you are not matching on. + +```ini +# Match by appid: launch 'st' with class 'st-yazi' running 'yazi' +bind=alt,h,toggle_named_scratchpad,st-yazi,none,st -c st-yazi -e yazi + +# Match by title: launch 'kitty' with window title 'kitty-scratch' +bind=alt,k,toggle_named_scratchpad,none,kitty-scratch,kitty -T kitty-scratch +``` + +--- + +## Appearance + +You can customize the size of scratchpad windows relative to the screen. + +```ini +scratchpad_width_ratio=0.8 +scratchpad_height_ratio=0.9 +scratchpadcolor=0x516c93ff +``` From a7461d9d5b36fbd757702714f9737ee9ddc7486b Mon Sep 17 00:00:00 2001 From: tonybanters Date: Thu, 12 Mar 2026 06:49:58 -0700 Subject: [PATCH 160/170] opt: use config.xxx instead of global presets --- src/animation/client.h | 141 ++++--- src/animation/common.h | 16 +- src/animation/layer.h | 66 ++-- src/animation/tag.h | 22 +- src/config/parse_config.h | 727 ++++++++++++++++++------------------- src/config/preset.h | 234 +----------- src/dispatch/bind_define.h | 66 ++-- src/ext-protocol/tearing.h | 7 +- src/fetch/client.h | 24 +- src/layout/arrange.h | 8 +- src/layout/horizontal.h | 122 ++++--- src/layout/vertical.h | 85 +++-- src/mango.c | 305 ++++++++-------- 13 files changed, 826 insertions(+), 997 deletions(-) diff --git a/src/animation/client.h b/src/animation/client.h index b6683ec..8c9c391 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -10,20 +10,21 @@ void set_rect_size(struct wlr_scene_rect *rect, int32_t width, int32_t height) { enum corner_location set_client_corner_location(Client *c) { enum corner_location current_corner_location = CORNER_LOCATION_ALL; - struct wlr_box target_geom = animations ? c->animation.current : c->geom; - if (target_geom.x + border_radius <= c->mon->m.x) { - current_corner_location &= ~CORNER_LOCATION_LEFT; // 清除左标志位 + struct wlr_box target_geom = + config.animations ? c->animation.current : c->geom; + if (target_geom.x + config.border_radius <= c->mon->m.x) { + current_corner_location &= ~CORNER_LOCATION_LEFT; } - if (target_geom.x + target_geom.width - border_radius >= + if (target_geom.x + target_geom.width - config.border_radius >= c->mon->m.x + c->mon->m.width) { - current_corner_location &= ~CORNER_LOCATION_RIGHT; // 清除右标志位 + current_corner_location &= ~CORNER_LOCATION_RIGHT; } - if (target_geom.y + border_radius <= c->mon->m.y) { - current_corner_location &= ~CORNER_LOCATION_TOP; // 清除上标志位 + if (target_geom.y + config.border_radius <= c->mon->m.y) { + current_corner_location &= ~CORNER_LOCATION_TOP; } - if (target_geom.y + target_geom.height - border_radius >= + if (target_geom.y + target_geom.height - config.border_radius >= c->mon->m.y + c->mon->m.height) { - current_corner_location &= ~CORNER_LOCATION_BOTTOM; // 清除下标志位 + current_corner_location &= ~CORNER_LOCATION_BOTTOM; } return current_corner_location; } @@ -54,15 +55,16 @@ int32_t is_special_animation_rule(Client *c) { } else if (c->mon->visible_tiling_clients == 1 && !c->isfloating) { return DOWN; } else if (c->mon->visible_tiling_clients == 2 && !c->isfloating && - !new_is_master && is_horizontal_stack_layout(c->mon)) { + !config.new_is_master && is_horizontal_stack_layout(c->mon)) { return RIGHT; - } else if (!c->isfloating && new_is_master && + } else if (!c->isfloating && config.new_is_master && is_horizontal_stack_layout(c->mon)) { return LEFT; } else if (c->mon->visible_tiling_clients == 2 && !c->isfloating && - !new_is_master && is_horizontal_right_stack_layout(c->mon)) { + !config.new_is_master && + is_horizontal_right_stack_layout(c->mon)) { return LEFT; - } else if (!c->isfloating && new_is_master && + } else if (!c->isfloating && config.new_is_master && is_horizontal_right_stack_layout(c->mon)) { return RIGHT; } else { @@ -77,7 +79,8 @@ void set_client_open_animation(Client *c, struct wlr_box geo) { int32_t special_direction; int32_t center_x, center_y; - if ((!c->animation_type_open && strcmp(animation_type_open, "fade") == 0) || + if ((!c->animation_type_open && + strcmp(config.animation_type_open, "fade") == 0) || (c->animation_type_open && strcmp(c->animation_type_open, "fade") == 0)) { c->animainit_geom.width = geo.width; @@ -86,11 +89,11 @@ void set_client_open_animation(Client *c, struct wlr_box geo) { c->animainit_geom.y = geo.y; return; } else if ((!c->animation_type_open && - strcmp(animation_type_open, "zoom") == 0) || + strcmp(config.animation_type_open, "zoom") == 0) || (c->animation_type_open && strcmp(c->animation_type_open, "zoom") == 0)) { - c->animainit_geom.width = geo.width * zoom_initial_ratio; - c->animainit_geom.height = geo.height * zoom_initial_ratio; + c->animainit_geom.width = geo.width * config.zoom_initial_ratio; + c->animainit_geom.height = geo.height * config.zoom_initial_ratio; c->animainit_geom.x = geo.x + (geo.width - c->animainit_geom.width) / 2; c->animainit_geom.y = geo.y + (geo.height - c->animainit_geom.height) / 2; @@ -223,7 +226,7 @@ void scene_buffer_apply_effect(struct wlr_scene_buffer *buffer, int32_t sx, if (wlr_xdg_popup_try_from_wlr_surface(surface) != NULL) return; - wlr_scene_buffer_set_corner_radius(buffer, border_radius, + wlr_scene_buffer_set_corner_radius(buffer, config.border_radius, buffer_data->corner_location); } @@ -241,7 +244,7 @@ void buffer_set_effect(Client *c, BufferData data) { data.should_scale = false; if (c->isnoradius || c->isfullscreen || - (no_radius_when_single && c->mon && + (config.no_radius_when_single && c->mon && c->mon->visible_tiling_clients == 1)) { data.corner_location = CORNER_LOCATION_NONE; } @@ -255,7 +258,7 @@ void client_draw_shadow(Client *c) { if (c->iskilling || !client_surface(c)->mapped || c->isnoshadow) return; - if (!shadows || (!c->isfloating && shadow_only_floating)) { + if (!config.shadows || (!c->isfloating && config.shadow_only_floating)) { if (c->shadow->node.enabled) wlr_scene_node_set_enabled(&c->shadow->node, false); return; @@ -266,7 +269,7 @@ void client_draw_shadow(Client *c) { bool hit_no_border = check_hit_no_border(c); enum corner_location current_corner_location = - c->isfullscreen || (no_radius_when_single && c->mon && + c->isfullscreen || (config.no_radius_when_single && c->mon && c->mon->visible_tiling_clients == 1) ? CORNER_LOCATION_NONE : CORNER_LOCATION_ALL; @@ -276,9 +279,8 @@ void client_draw_shadow(Client *c) { int32_t width, height; client_actual_size(c, &width, &height); - int32_t delta = shadows_size + (int32_t)c->bw - bwoffset; + int32_t delta = config.shadows_size + (int32_t)c->bw - bwoffset; - /* we calculate where to clip the shadow */ struct wlr_box client_box = { .x = bwoffset, .y = bwoffset, @@ -287,22 +289,20 @@ void client_draw_shadow(Client *c) { }; struct wlr_box shadow_box = { - .x = shadows_position_x + bwoffset, - .y = shadows_position_y + bwoffset, + .x = config.shadows_position_x + bwoffset, + .y = config.shadows_position_y + bwoffset, .width = width + 2 * delta, .height = height + 2 * delta, }; struct wlr_box intersection_box; wlr_box_intersection(&intersection_box, &client_box, &shadow_box); - /* clipped region takes shadow relative coords, so we translate everything - * by its position */ - intersection_box.x -= shadows_position_x + bwoffset; - intersection_box.y -= shadows_position_y + bwoffset; + intersection_box.x -= config.shadows_position_x + bwoffset; + intersection_box.y -= config.shadows_position_y + bwoffset; struct clipped_region clipped_region = { .area = intersection_box, - .corner_radius = border_radius, + .corner_radius = config.border_radius, .corners = current_corner_location, }; @@ -356,23 +356,23 @@ void apply_border(Client *c) { bool hit_no_border = check_hit_no_border(c); enum corner_location current_corner_location; - if (c->isfullscreen || (no_radius_when_single && c->mon && + if (c->isfullscreen || (config.no_radius_when_single && c->mon && c->mon->visible_tiling_clients == 1)) { current_corner_location = CORNER_LOCATION_NONE; } else { current_corner_location = set_client_corner_location(c); } - if (hit_no_border && smartgaps) { + if (hit_no_border && config.smartgaps) { c->bw = 0; c->fake_no_border = true; - } else if (hit_no_border && !smartgaps) { + } else if (hit_no_border && !config.smartgaps) { wlr_scene_rect_set_size(c->border, 0, 0); wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); c->fake_no_border = true; return; } else if (!c->isfullscreen && VISIBLEON(c, c->mon)) { - c->bw = c->isnoborder ? 0 : borderpx; + c->bw = c->isnoborder ? 0 : config.borderpx; c->fake_no_border = false; } @@ -434,14 +434,14 @@ void apply_border(Client *c) { struct clipped_region clipped_region = { .area = {inner_surface_x, inner_surface_y, inner_surface_width, inner_surface_height}, - .corner_radius = border_radius, + .corner_radius = config.border_radius, .corners = current_corner_location, }; wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); wlr_scene_rect_set_size(c->border, rect_width, rect_height); wlr_scene_node_set_position(&c->border->node, rect_x, rect_y); - wlr_scene_rect_set_corner_radius(c->border, border_radius, + wlr_scene_rect_set_corner_radius(c->border, config.border_radius, current_corner_location); wlr_scene_rect_set_clipped_region(c->border, clipped_region); } @@ -523,7 +523,7 @@ void client_apply_clip(Client *c, float factor) { enum corner_location current_corner_location = set_client_corner_location(c); - if (!animations) { + if (!config.animations) { c->animation.running = false; c->need_output_flush = false; c->animainit_geom = c->current = c->pending = c->animation.current = @@ -655,19 +655,19 @@ void fadeout_client_animation_next_tick(Client *c) { double opacity_eased_progress = find_animation_curve_at(animation_passed, OPAFADEOUT); - double percent = fadeout_begin_opacity - - (opacity_eased_progress * fadeout_begin_opacity); + double percent = config.fadeout_begin_opacity - + (opacity_eased_progress * config.fadeout_begin_opacity); double opacity = MAX(percent, 0); - if (animation_fade_out && !c->nofadeout) + if (config.animation_fade_out && !c->nofadeout) wlr_scene_node_for_each_buffer(&c->scene->node, scene_buffer_apply_opacity, &opacity); if ((c->animation_type_close && strcmp(c->animation_type_close, "zoom") == 0) || (!c->animation_type_close && - strcmp(animation_type_close, "zoom") == 0)) { + strcmp(config.animation_type_close, "zoom") == 0)) { buffer_data.width = width; buffer_data.height = height; @@ -768,14 +768,14 @@ void init_fadeout_client(Client *c) { if ((c->animation_type_close && strcmp(c->animation_type_close, "none") == 0) || (!c->animation_type_close && - strcmp(animation_type_close, "none") == 0)) { + strcmp(config.animation_type_close, "none") == 0)) { return; } Client *fadeout_client = ecalloc(1, sizeof(*fadeout_client)); wlr_scene_node_set_enabled(&c->scene->node, true); - client_set_border_color(c, bordercolor); + client_set_border_color(c, config.bordercolor); fadeout_client->scene = wlr_scene_tree_snapshot(&c->scene->node, layers[LyrFadeOut]); wlr_scene_node_set_enabled(&c->scene->node, false); @@ -785,7 +785,7 @@ void init_fadeout_client(Client *c) { return; } - fadeout_client->animation.duration = animation_duration_close; + fadeout_client->animation.duration = config.animation_duration_close; fadeout_client->geom = fadeout_client->current = fadeout_client->animainit_geom = fadeout_client->animation.initial = c->animation.current; @@ -802,7 +802,7 @@ void init_fadeout_client(Client *c) { fadeout_client->animation.initial.y = 0; if ((!c->animation_type_close && - strcmp(animation_type_close, "fade") == 0) || + strcmp(config.animation_type_close, "fade") == 0) || (c->animation_type_close && strcmp(c->animation_type_close, "fade") == 0)) { fadeout_client->current.x = 0; @@ -812,7 +812,7 @@ void init_fadeout_client(Client *c) { } else if ((c->animation_type_close && strcmp(c->animation_type_close, "slide") == 0) || (!c->animation_type_close && - strcmp(animation_type_close, "slide") == 0)) { + strcmp(config.animation_type_close, "slide") == 0)) { fadeout_client->current.y = c->geom.y + c->geom.height / 2 > c->mon->m.y + c->mon->m.height / 2 ? c->mon->m.height - @@ -822,16 +822,16 @@ void init_fadeout_client(Client *c) { } else { fadeout_client->current.y = (fadeout_client->geom.height - - fadeout_client->geom.height * zoom_end_ratio) / + fadeout_client->geom.height * config.zoom_end_ratio) / 2; fadeout_client->current.x = (fadeout_client->geom.width - - fadeout_client->geom.width * zoom_end_ratio) / + fadeout_client->geom.width * config.zoom_end_ratio) / 2; fadeout_client->current.width = - fadeout_client->geom.width * zoom_end_ratio; + fadeout_client->geom.width * config.zoom_end_ratio; fadeout_client->current.height = - fadeout_client->geom.height * zoom_end_ratio; + fadeout_client->geom.height * config.zoom_end_ratio; } fadeout_client->animation.time_started = get_now_in_ms(); @@ -866,12 +866,11 @@ void client_set_pending_state(Client *c) { if (!c || c->iskilling) return; - // 判断是否需要动画 - if (!animations) { + if (!config.animations) { c->animation.should_animate = false; - } else if (animations && c->animation.tagining) { + } else if (config.animations && c->animation.tagining) { c->animation.should_animate = true; - } else if (!animations || c == grabc || + } else if (!config.animations || c == grabc || (!c->is_pending_open_animation && wlr_box_equal(&c->current, &c->pending))) { c->animation.should_animate = false; @@ -882,7 +881,7 @@ void client_set_pending_state(Client *c) { if (((c->animation_type_open && strcmp(c->animation_type_open, "none") == 0) || (!c->animation_type_open && - strcmp(animation_type_open, "none") == 0)) && + strcmp(config.animation_type_open, "none") == 0)) && c->animation.action == OPEN) { c->animation.duration = 0; } @@ -951,16 +950,16 @@ void resize(Client *c, struct wlr_box geo, int32_t interact) { !c->animation.tagouting && wlr_box_equal(&c->geom, &c->current)) { c->animation.action = c->animation.action; } else if (c->animation.tagouting) { - c->animation.duration = animation_duration_tag; + c->animation.duration = config.animation_duration_tag; c->animation.action = TAG; } else if (c->animation.tagining) { - c->animation.duration = animation_duration_tag; + c->animation.duration = config.animation_duration_tag; c->animation.action = TAG; } else if (c->is_pending_open_animation) { - c->animation.duration = animation_duration_open; + c->animation.duration = config.animation_duration_open; c->animation.action = OPEN; } else { - c->animation.duration = animation_duration_move; + c->animation.duration = config.animation_duration_move; c->animation.action = MOVE; } @@ -981,7 +980,7 @@ void resize(Client *c, struct wlr_box geo, int32_t interact) { } bool hit_no_border = check_hit_no_border(c); - if (hit_no_border && smartgaps) { + if (hit_no_border && config.smartgaps) { c->bw = 0; c->fake_no_border = true; } @@ -1048,12 +1047,12 @@ bool client_draw_fadeout_frame(Client *c) { void client_set_focused_opacity_animation(Client *c) { float *border_color = get_border_color(c); - if (!animations) { + if (!config.animations) { setborder_color(c); return; } - c->opacity_animation.duration = animation_duration_focus; + c->opacity_animation.duration = config.animation_duration_focus; memcpy(c->opacity_animation.target_border_color, border_color, sizeof(c->opacity_animation.target_border_color)); c->opacity_animation.target_opacity = c->focused_opacity; @@ -1067,15 +1066,14 @@ void client_set_focused_opacity_animation(Client *c) { } void client_set_unfocused_opacity_animation(Client *c) { - // Start border color animation to unfocused float *border_color = get_border_color(c); - if (!animations) { + if (!config.animations) { setborder_color(c); return; } - c->opacity_animation.duration = animation_duration_focus; + c->opacity_animation.duration = config.animation_duration_focus; memcpy(c->opacity_animation.target_border_color, border_color, sizeof(c->opacity_animation.target_border_color)); // Start opacity animation to unfocused @@ -1110,13 +1108,14 @@ bool client_apply_focus_opacity(Client *c) { double opacity_eased_progress = find_animation_curve_at(linear_progress, OPAFADEIN); - float percent = - animation_fade_in && !c->nofadein ? opacity_eased_progress : 1.0; + float percent = config.animation_fade_in && !c->nofadein + ? opacity_eased_progress + : 1.0; float opacity = c == selmon->sel ? c->focused_opacity : c->unfocused_opacity; - float target_opacity = - percent * (1.0 - fadein_begin_opacity) + fadein_begin_opacity; + float target_opacity = percent * (1.0 - config.fadein_begin_opacity) + + config.fadein_begin_opacity; if (target_opacity > opacity) { target_opacity = opacity; } @@ -1126,7 +1125,7 @@ bool client_apply_focus_opacity(Client *c) { 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) { + } else if (config.animations && c->opacity_animation.running) { struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); @@ -1187,7 +1186,7 @@ bool client_draw_frame(Client *c) { return client_apply_focus_opacity(c); } - if (animations && c->animation.running) { + if (config.animations && c->animation.running) { client_animation_next_tick(c); } else { wlr_scene_node_set_position(&c->scene->node, c->pending.x, diff --git a/src/animation/common.h b/src/animation/common.h index 9f022db..10b66b9 100644 --- a/src/animation/common.h +++ b/src/animation/common.h @@ -2,21 +2,21 @@ struct dvec2 calculate_animation_curve_at(double t, int32_t type) { struct dvec2 point; double *animation_curve; if (type == MOVE) { - animation_curve = animation_curve_move; + animation_curve = config.animation_curve_move; } else if (type == OPEN) { - animation_curve = animation_curve_open; + animation_curve = config.animation_curve_open; } else if (type == TAG) { - animation_curve = animation_curve_tag; + animation_curve = config.animation_curve_tag; } else if (type == CLOSE) { - animation_curve = animation_curve_close; + animation_curve = config.animation_curve_close; } else if (type == FOCUS) { - animation_curve = animation_curve_focus; + animation_curve = config.animation_curve_focus; } else if (type == OPAFADEIN) { - animation_curve = animation_curve_opafadein; + animation_curve = config.animation_curve_opafadein; } else if (type == OPAFADEOUT) { - animation_curve = animation_curve_opafadeout; + animation_curve = config.animation_curve_opafadeout; } else { - animation_curve = animation_curve_move; + animation_curve = config.animation_curve_move; } point.x = 3 * t * (1 - t) * (1 - t) * animation_curve[0] + diff --git a/src/animation/layer.h b/src/animation/layer.h index 568d52b..90f1b4e 100644 --- a/src/animation/layer.h +++ b/src/animation/layer.h @@ -156,7 +156,7 @@ void layer_draw_shadow(LayerSurface *l) { if (!l->mapped || !l->shadow) return; - if (!shadows || !layer_shadows || l->noshadow) { + if (!config.shadows || !config.layer_shadows || l->noshadow) { wlr_scene_shadow_set_size(l->shadow, 0, 0); return; } @@ -164,9 +164,8 @@ void layer_draw_shadow(LayerSurface *l) { int32_t width, height; layer_actual_size(l, &width, &height); - int32_t delta = shadows_size; + int32_t delta = config.shadows_size; - /* we calculate where to clip the shadow */ struct wlr_box layer_box = { .x = 0, .y = 0, @@ -175,23 +174,21 @@ void layer_draw_shadow(LayerSurface *l) { }; struct wlr_box shadow_box = { - .x = shadows_position_x, - .y = shadows_position_y, + .x = config.shadows_position_x, + .y = config.shadows_position_y, .width = width + 2 * delta, .height = height + 2 * delta, }; struct wlr_box intersection_box; wlr_box_intersection(&intersection_box, &layer_box, &shadow_box); - /* clipped region takes shadow relative coords, so we translate everything - * by its position */ - intersection_box.x -= shadows_position_x; - intersection_box.y -= shadows_position_y; + intersection_box.x -= config.shadows_position_x; + intersection_box.y -= config.shadows_position_y; struct clipped_region clipped_region = { .area = intersection_box, - .corner_radius = border_radius, - .corners = border_radius_location_default, + .corner_radius = config.border_radius, + .corners = config.border_radius_location_default, }; wlr_scene_node_set_position(&l->shadow->node, shadow_box.x, shadow_box.y); @@ -261,7 +258,7 @@ void fadeout_layer_animation_next_tick(LayerSurface *l) { buffer_data.height = height; if ((!l->animation_type_close && - strcmp(layer_animation_type_close, "zoom") == 0) || + strcmp(config.layer_animation_type_close, "zoom") == 0) || (l->animation_type_close && strcmp(l->animation_type_close, "zoom") == 0)) { wlr_scene_node_for_each_buffer(&l->scene->node, @@ -279,12 +276,12 @@ void fadeout_layer_animation_next_tick(LayerSurface *l) { double opacity_eased_progress = find_animation_curve_at(animation_passed, OPAFADEOUT); - double percent = fadeout_begin_opacity - - (opacity_eased_progress * fadeout_begin_opacity); + double percent = config.fadeout_begin_opacity - + (opacity_eased_progress * config.fadeout_begin_opacity); double opacity = MAX(percent, 0.0f); - if (animation_fade_out) + if (config.animation_fade_out) wlr_scene_node_for_each_buffer(&l->scene->node, scene_buffer_apply_opacity, &opacity); @@ -327,11 +324,11 @@ void layer_animation_next_tick(LayerSurface *l) { find_animation_curve_at(animation_passed, OPAFADEIN); double opacity = - MIN(fadein_begin_opacity + - opacity_eased_progress * (1.0 - fadein_begin_opacity), + MIN(config.fadein_begin_opacity + + opacity_eased_progress * (1.0 - config.fadein_begin_opacity), 1.0f); - if (animation_fade_in) + if (config.animation_fade_in) wlr_scene_node_for_each_buffer(&l->scene->node, scene_buffer_apply_opacity, &opacity); @@ -347,7 +344,7 @@ void layer_animation_next_tick(LayerSurface *l) { } if ((!l->animation_type_open && - strcmp(layer_animation_type_open, "zoom") == 0) || + strcmp(config.layer_animation_type_open, "zoom") == 0) || (l->animation_type_open && strcmp(l->animation_type_open, "zoom") == 0)) { wlr_scene_node_for_each_buffer( @@ -370,7 +367,7 @@ void layer_animation_next_tick(LayerSurface *l) { void init_fadeout_layers(LayerSurface *l) { - if (!animations || !layer_animations || l->noanim) { + if (!config.animations || !config.layer_animations || l->noanim) { return; } @@ -380,7 +377,7 @@ void init_fadeout_layers(LayerSurface *l) { if ((l->animation_type_close && strcmp(l->animation_type_close, "none") == 0) || (!l->animation_type_close && - strcmp(layer_animation_type_close, "none") == 0)) { + strcmp(config.layer_animation_type_close, "none") == 0)) { return; } @@ -403,7 +400,7 @@ void init_fadeout_layers(LayerSurface *l) { return; } - fadeout_layer->animation.duration = animation_duration_close; + fadeout_layer->animation.duration = config.animation_duration_close; fadeout_layer->geom = fadeout_layer->current = fadeout_layer->animainit_geom = fadeout_layer->animation.initial = l->animation.current; @@ -419,14 +416,14 @@ void init_fadeout_layers(LayerSurface *l) { fadeout_layer->animation.initial.y = 0; if ((!l->animation_type_close && - strcmp(layer_animation_type_close, "zoom") == 0) || + strcmp(config.layer_animation_type_close, "zoom") == 0) || (l->animation_type_close && strcmp(l->animation_type_close, "zoom") == 0)) { // 算出要设置的绝对坐标和大小 fadeout_layer->current.width = - (float)l->animation.current.width * zoom_end_ratio; + (float)l->animation.current.width * config.zoom_end_ratio; fadeout_layer->current.height = - (float)l->animation.current.height * zoom_end_ratio; + (float)l->animation.current.height * config.zoom_end_ratio; fadeout_layer->current.x = usable_area.x + usable_area.width / 2 - fadeout_layer->current.width / 2; fadeout_layer->current.y = usable_area.y + usable_area.height / 2 - @@ -438,7 +435,7 @@ void init_fadeout_layers(LayerSurface *l) { fadeout_layer->current.y - l->animation.current.y; } else if ((!l->animation_type_close && - strcmp(layer_animation_type_close, "slide") == 0) || + strcmp(config.layer_animation_type_close, "slide") == 0) || (l->animation_type_close && strcmp(l->animation_type_close, "slide") == 0)) { // 获取slide动画的结束绝对坐标和大小 @@ -483,17 +480,18 @@ void layer_set_pending_state(LayerSurface *l) { if (l->animation.action == OPEN && !l->animation.running) { if ((!l->animation_type_open && - strcmp(layer_animation_type_open, "zoom") == 0) || + strcmp(config.layer_animation_type_open, "zoom") == 0) || (l->animation_type_open && strcmp(l->animation_type_open, "zoom") == 0)) { - l->animainit_geom.width = l->geom.width * zoom_initial_ratio; - l->animainit_geom.height = l->geom.height * zoom_initial_ratio; + l->animainit_geom.width = l->geom.width * config.zoom_initial_ratio; + l->animainit_geom.height = + l->geom.height * config.zoom_initial_ratio; l->animainit_geom.x = usable_area.x + usable_area.width / 2 - l->animainit_geom.width / 2; l->animainit_geom.y = usable_area.y + usable_area.height / 2 - l->animainit_geom.height / 2; } else if ((!l->animation_type_open && - strcmp(layer_animation_type_open, "slide") == 0) || + strcmp(config.layer_animation_type_open, "slide") == 0) || (l->animation_type_open && strcmp(l->animation_type_open, "slide") == 0)) { @@ -507,8 +505,7 @@ void layer_set_pending_state(LayerSurface *l) { } else { l->animainit_geom = l->animation.current; } - // 判断是否需要动画 - if (!animations || !layer_animations || l->noanim || + if (!config.animations || !config.layer_animations || l->noanim || l->layer_surface->current.layer == ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND || l->layer_surface->current.layer == ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM) { @@ -520,7 +517,7 @@ void layer_set_pending_state(LayerSurface *l) { if (((l->animation_type_open && strcmp(l->animation_type_open, "none") == 0) || (!l->animation_type_open && - strcmp(layer_animation_type_open, "none") == 0)) && + strcmp(config.layer_animation_type_open, "none") == 0)) && l->animation.action == OPEN) { l->animation.should_animate = false; } @@ -567,7 +564,8 @@ bool layer_draw_frame(LayerSurface *l) { return false; } - if (animations && layer_animations && l->animation.running && !l->noanim) { + if (config.animations && config.layer_animations && l->animation.running && + !l->noanim) { layer_animation_next_tick(l); layer_draw_shadow(l); } else { diff --git a/src/animation/tag.h b/src/animation/tag.h index 8e65a93..18eef56 100644 --- a/src/animation/tag.h +++ b/src/animation/tag.h @@ -7,11 +7,11 @@ void set_tagin_animation(Monitor *m, Client *c) { if (m->pertag->curtag > m->pertag->prevtag) { - c->animainit_geom.x = tag_animation_direction == VERTICAL + c->animainit_geom.x = config.tag_animation_direction == VERTICAL ? c->animation.current.x : MAX(c->mon->m.x + c->mon->m.width, c->geom.x + c->mon->m.width); - c->animainit_geom.y = tag_animation_direction == VERTICAL + c->animainit_geom.y = config.tag_animation_direction == VERTICAL ? MAX(c->mon->m.y + c->mon->m.height, c->geom.y + c->mon->m.height) : c->animation.current.y; @@ -19,11 +19,11 @@ void set_tagin_animation(Monitor *m, Client *c) { } else { c->animainit_geom.x = - tag_animation_direction == VERTICAL + config.tag_animation_direction == VERTICAL ? c->animation.current.x : MIN(m->m.x - c->geom.width, c->geom.x - c->mon->m.width); c->animainit_geom.y = - tag_animation_direction == VERTICAL + config.tag_animation_direction == VERTICAL ? MIN(m->m.y - c->geom.height, c->geom.y - c->mon->m.height) : c->animation.current.y; } @@ -39,7 +39,8 @@ void set_arrange_visible(Monitor *m, Client *c, bool want_animation) { client_set_suspended(c, false); if (!c->animation.tag_from_rule && want_animation && - m->pertag->prevtag != 0 && m->pertag->curtag != 0 && animations) { + m->pertag->prevtag != 0 && m->pertag->curtag != 0 && + config.animations) { c->animation.tagining = true; set_tagin_animation(m, c); } else { @@ -57,10 +58,10 @@ void set_tagout_animation(Monitor *m, Client *c) { if (m->pertag->curtag > m->pertag->prevtag) { c->pending = c->geom; c->pending.x = - tag_animation_direction == VERTICAL + config.tag_animation_direction == VERTICAL ? c->animation.current.x : MIN(c->mon->m.x - c->geom.width, c->geom.x - c->mon->m.width); - c->pending.y = tag_animation_direction == VERTICAL + c->pending.y = config.tag_animation_direction == VERTICAL ? MIN(c->mon->m.y - c->geom.height, c->geom.y - c->mon->m.height) : c->animation.current.y; @@ -68,11 +69,11 @@ void set_tagout_animation(Monitor *m, Client *c) { resize(c, c->geom, 0); } else { c->pending = c->geom; - c->pending.x = tag_animation_direction == VERTICAL + c->pending.x = config.tag_animation_direction == VERTICAL ? c->animation.current.x : MAX(c->mon->m.x + c->mon->m.width, c->geom.x + c->mon->m.width); - c->pending.y = tag_animation_direction == VERTICAL + c->pending.y = config.tag_animation_direction == VERTICAL ? MAX(c->mon->m.y + c->mon->m.height, c->geom.y + c->mon->m.height) : c->animation.current.y; @@ -82,7 +83,8 @@ void set_tagout_animation(Monitor *m, Client *c) { void set_arrange_hidden(Monitor *m, Client *c, bool want_animation) { if ((c->tags & (1 << (m->pertag->prevtag - 1))) && - m->pertag->prevtag != 0 && m->pertag->curtag != 0 && animations) { + m->pertag->prevtag != 0 && m->pertag->curtag != 0 && + config.animations) { c->animation.tagouting = true; c->animation.tagining = false; set_tagout_animation(m, c); diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 3220b71..ffb8414 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -285,6 +285,7 @@ typedef struct { int32_t blur_layer; int32_t blur_optimized; int32_t border_radius; + int32_t border_radius_location_default; struct blur_data blur_params; int32_t shadows; int32_t shadow_only_floating; @@ -312,7 +313,8 @@ typedef struct { float globalcolor[4]; float overlaycolor[4]; - char autostart[3][256]; + int32_t log_level; + uint32_t capslock; ConfigTagRule *tag_rules; // 动态数组 int32_t tag_rules_count; // 数量 @@ -363,6 +365,11 @@ typedef struct { int32_t allow_lock_transparent; struct xkb_rule_names xkb_rules; + char xkb_rules_rules[128]; + char xkb_rules_model[128]; + char xkb_rules_layout[128]; + char xkb_rules_variant[128]; + char xkb_rules_options[128]; char keymode[28]; @@ -1428,25 +1435,25 @@ bool parse_option(Config *config, char *key, char *value) { } else if (strcmp(key, "unfocused_opacity") == 0) { config->unfocused_opacity = atof(value); } else if (strcmp(key, "xkb_rules_rules") == 0) { - strncpy(xkb_rules_rules, value, sizeof(xkb_rules_rules) - 1); - xkb_rules_rules[sizeof(xkb_rules_rules) - 1] = - '\0'; // 确保字符串以 null 结尾 + strncpy(config->xkb_rules_rules, value, + sizeof(config->xkb_rules_rules) - 1); + config->xkb_rules_rules[sizeof(config->xkb_rules_rules) - 1] = '\0'; } else if (strcmp(key, "xkb_rules_model") == 0) { - strncpy(xkb_rules_model, value, sizeof(xkb_rules_model) - 1); - xkb_rules_model[sizeof(xkb_rules_model) - 1] = - '\0'; // 确保字符串以 null 结尾 + strncpy(config->xkb_rules_model, value, + sizeof(config->xkb_rules_model) - 1); + config->xkb_rules_model[sizeof(config->xkb_rules_model) - 1] = '\0'; } else if (strcmp(key, "xkb_rules_layout") == 0) { - strncpy(xkb_rules_layout, value, sizeof(xkb_rules_layout) - 1); - xkb_rules_layout[sizeof(xkb_rules_layout) - 1] = - '\0'; // 确保字符串以 null 结尾 + strncpy(config->xkb_rules_layout, value, + sizeof(config->xkb_rules_layout) - 1); + config->xkb_rules_layout[sizeof(config->xkb_rules_layout) - 1] = '\0'; } else if (strcmp(key, "xkb_rules_variant") == 0) { - strncpy(xkb_rules_variant, value, sizeof(xkb_rules_variant) - 1); - xkb_rules_variant[sizeof(xkb_rules_variant) - 1] = - '\0'; // 确保字符串以 null 结尾 + strncpy(config->xkb_rules_variant, value, + sizeof(config->xkb_rules_variant) - 1); + config->xkb_rules_variant[sizeof(config->xkb_rules_variant) - 1] = '\0'; } else if (strcmp(key, "xkb_rules_options") == 0) { - strncpy(xkb_rules_options, value, sizeof(xkb_rules_options) - 1); - xkb_rules_options[sizeof(xkb_rules_options) - 1] = - '\0'; // 确保字符串以 null 结尾 + strncpy(config->xkb_rules_options, value, + sizeof(config->xkb_rules_options) - 1); + config->xkb_rules_options[sizeof(config->xkb_rules_options) - 1] = '\0'; } else if (strcmp(key, "scroller_proportion_preset") == 0) { // 1. 统计 value 中有多少个逗号,确定需要解析的浮点数个数 int32_t count = 0; // 初始化为 0 @@ -3087,363 +3094,347 @@ void free_config(void) { } void override_config(void) { - // 动画启用 - animations = CLAMP_INT(config.animations, 0, 1); - layer_animations = CLAMP_INT(config.layer_animations, 0, 1); - - // 标签动画方向 - tag_animation_direction = CLAMP_INT(config.tag_animation_direction, 0, 1); - - // 动画淡入淡出设置 - animation_fade_in = CLAMP_INT(config.animation_fade_in, 0, 1); - animation_fade_out = CLAMP_INT(config.animation_fade_out, 0, 1); - zoom_initial_ratio = CLAMP_FLOAT(config.zoom_initial_ratio, 0.1f, 1.0f); - zoom_end_ratio = CLAMP_FLOAT(config.zoom_end_ratio, 0.1f, 1.0f); - fadein_begin_opacity = CLAMP_FLOAT(config.fadein_begin_opacity, 0.0f, 1.0f); - fadeout_begin_opacity = + config.animations = CLAMP_INT(config.animations, 0, 1); + config.layer_animations = CLAMP_INT(config.layer_animations, 0, 1); + config.tag_animation_direction = + CLAMP_INT(config.tag_animation_direction, 0, 1); + config.animation_fade_in = CLAMP_INT(config.animation_fade_in, 0, 1); + config.animation_fade_out = CLAMP_INT(config.animation_fade_out, 0, 1); + config.zoom_initial_ratio = + CLAMP_FLOAT(config.zoom_initial_ratio, 0.1f, 1.0f); + config.zoom_end_ratio = CLAMP_FLOAT(config.zoom_end_ratio, 0.1f, 1.0f); + config.fadein_begin_opacity = + CLAMP_FLOAT(config.fadein_begin_opacity, 0.0f, 1.0f); + config.fadeout_begin_opacity = CLAMP_FLOAT(config.fadeout_begin_opacity, 0.0f, 1.0f); - - // 打开关闭动画类型 - animation_type_open = config.animation_type_open; - animation_type_close = config.animation_type_close; - - // layer打开关闭动画类型 - layer_animation_type_open = config.layer_animation_type_open; - layer_animation_type_close = config.layer_animation_type_close; - - // 动画时间限制在合理范围(1-50000ms) - animation_duration_move = + config.animation_duration_move = CLAMP_INT(config.animation_duration_move, 1, 50000); - animation_duration_open = + config.animation_duration_open = CLAMP_INT(config.animation_duration_open, 1, 50000); - animation_duration_tag = CLAMP_INT(config.animation_duration_tag, 1, 50000); - animation_duration_close = + config.animation_duration_tag = + CLAMP_INT(config.animation_duration_tag, 1, 50000); + config.animation_duration_close = CLAMP_INT(config.animation_duration_close, 1, 50000); - animation_duration_focus = + config.animation_duration_focus = CLAMP_INT(config.animation_duration_focus, 1, 50000); - - // 滚动布局设置 - scroller_default_proportion = + config.scroller_default_proportion = CLAMP_FLOAT(config.scroller_default_proportion, 0.1f, 1.0f); - scroller_default_proportion_single = + config.scroller_default_proportion_single = CLAMP_FLOAT(config.scroller_default_proportion_single, 0.1f, 1.0f); - scroller_ignore_proportion_single = + config.scroller_ignore_proportion_single = CLAMP_INT(config.scroller_ignore_proportion_single, 0, 1); - scroller_focus_center = CLAMP_INT(config.scroller_focus_center, 0, 1); - scroller_prefer_center = CLAMP_INT(config.scroller_prefer_center, 0, 1); - scroller_prefer_overspread = + config.scroller_focus_center = + CLAMP_INT(config.scroller_focus_center, 0, 1); + config.scroller_prefer_center = + CLAMP_INT(config.scroller_prefer_center, 0, 1); + config.scroller_prefer_overspread = CLAMP_INT(config.scroller_prefer_overspread, 0, 1); - edge_scroller_pointer_focus = + config.edge_scroller_pointer_focus = CLAMP_INT(config.edge_scroller_pointer_focus, 0, 1); - scroller_structs = CLAMP_INT(config.scroller_structs, 0, 1000); - - // 主从布局设置 - default_mfact = CLAMP_FLOAT(config.default_mfact, 0.1f, 0.9f); - default_nmaster = CLAMP_INT(config.default_nmaster, 1, 1000); - center_master_overspread = CLAMP_INT(config.center_master_overspread, 0, 1); - center_when_single_stack = CLAMP_INT(config.center_when_single_stack, 0, 1); - new_is_master = CLAMP_INT(config.new_is_master, 0, 1); - - // 概述模式设置 - 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); - overviewgappo = CLAMP_INT(config.overviewgappo, 0, 1000); - - // 杂项设置 - xwayland_persistence = CLAMP_INT(config.xwayland_persistence, 0, 1); - syncobj_enable = CLAMP_INT(config.syncobj_enable, 0, 1); - drag_tile_refresh_interval = + config.scroller_structs = CLAMP_INT(config.scroller_structs, 0, 1000); + config.default_mfact = CLAMP_FLOAT(config.default_mfact, 0.1f, 0.9f); + config.default_nmaster = CLAMP_INT(config.default_nmaster, 1, 1000); + config.center_master_overspread = + CLAMP_INT(config.center_master_overspread, 0, 1); + config.center_when_single_stack = + CLAMP_INT(config.center_when_single_stack, 0, 1); + config.new_is_master = CLAMP_INT(config.new_is_master, 0, 1); + config.hotarea_size = CLAMP_INT(config.hotarea_size, 1, 1000); + config.hotarea_corner = CLAMP_INT(config.hotarea_corner, 0, 3); + config.enable_hotarea = CLAMP_INT(config.enable_hotarea, 0, 1); + config.ov_tab_mode = CLAMP_INT(config.ov_tab_mode, 0, 1); + config.overviewgappi = CLAMP_INT(config.overviewgappi, 0, 1000); + config.overviewgappo = CLAMP_INT(config.overviewgappo, 0, 1000); + config.xwayland_persistence = CLAMP_INT(config.xwayland_persistence, 0, 1); + config.syncobj_enable = CLAMP_INT(config.syncobj_enable, 0, 1); + config.drag_tile_refresh_interval = CLAMP_FLOAT(config.drag_tile_refresh_interval, 1.0f, 16.0f); - drag_floating_refresh_interval = - CLAMP_FLOAT(config.drag_floating_refresh_interval, 1.0f, 16.0f); - drag_tile_to_tile = CLAMP_INT(config.drag_tile_to_tile, 0, 1); - drag_floating_refresh_interval = + config.drag_floating_refresh_interval = CLAMP_FLOAT(config.drag_floating_refresh_interval, 0.0f, 1000.0f); - 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); - axis_bind_apply_timeout = + config.drag_tile_to_tile = CLAMP_INT(config.drag_tile_to_tile, 0, 1); + config.allow_tearing = CLAMP_INT(config.allow_tearing, 0, 2); + config.allow_shortcuts_inhibit = + CLAMP_INT(config.allow_shortcuts_inhibit, 0, 1); + config.allow_lock_transparent = + CLAMP_INT(config.allow_lock_transparent, 0, 1); + config.axis_bind_apply_timeout = CLAMP_INT(config.axis_bind_apply_timeout, 0, 1000); - focus_on_activate = CLAMP_INT(config.focus_on_activate, 0, 1); - idleinhibit_ignore_visible = + config.focus_on_activate = CLAMP_INT(config.focus_on_activate, 0, 1); + config.idleinhibit_ignore_visible = 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); - focus_cross_tag = CLAMP_INT(config.focus_cross_tag, 0, 1); - view_current_to_back = CLAMP_INT(config.view_current_to_back, 0, 1); - enable_floating_snap = CLAMP_INT(config.enable_floating_snap, 0, 1); - snap_distance = CLAMP_INT(config.snap_distance, 0, 99999); - cursor_size = CLAMP_INT(config.cursor_size, 4, 512); - no_border_when_single = CLAMP_INT(config.no_border_when_single, 0, 1); - no_radius_when_single = CLAMP_INT(config.no_radius_when_single, 0, 1); - cursor_hide_timeout = - CLAMP_INT(config.cursor_hide_timeout, 0, 36000); // 0-10小时 - drag_tile_to_tile = CLAMP_INT(config.drag_tile_to_tile, 0, 1); - single_scratchpad = CLAMP_INT(config.single_scratchpad, 0, 1); - - // 键盘设置 - repeat_rate = CLAMP_INT(config.repeat_rate, 1, 1000); - repeat_delay = CLAMP_INT(config.repeat_delay, 1, 20000); - numlockon = CLAMP_INT(config.numlockon, 0, 1); - - // 触控板设置 - disable_trackpad = CLAMP_INT(config.disable_trackpad, 0, 1); - tap_to_click = CLAMP_INT(config.tap_to_click, 0, 1); - tap_and_drag = CLAMP_INT(config.tap_and_drag, 0, 1); - drag_lock = CLAMP_INT(config.drag_lock, 0, 1); - trackpad_natural_scrolling = + config.sloppyfocus = CLAMP_INT(config.sloppyfocus, 0, 1); + config.warpcursor = CLAMP_INT(config.warpcursor, 0, 1); + config.drag_corner = CLAMP_INT(config.drag_corner, 0, 4); + config.drag_warp_cursor = CLAMP_INT(config.drag_warp_cursor, 0, 1); + config.focus_cross_monitor = CLAMP_INT(config.focus_cross_monitor, 0, 1); + config.exchange_cross_monitor = + CLAMP_INT(config.exchange_cross_monitor, 0, 1); + config.scratchpad_cross_monitor = + CLAMP_INT(config.scratchpad_cross_monitor, 0, 1); + config.focus_cross_tag = CLAMP_INT(config.focus_cross_tag, 0, 1); + config.view_current_to_back = CLAMP_INT(config.view_current_to_back, 0, 1); + config.enable_floating_snap = CLAMP_INT(config.enable_floating_snap, 0, 1); + config.snap_distance = CLAMP_INT(config.snap_distance, 0, 99999); + config.cursor_size = CLAMP_INT(config.cursor_size, 4, 512); + config.no_border_when_single = + CLAMP_INT(config.no_border_when_single, 0, 1); + config.no_radius_when_single = + CLAMP_INT(config.no_radius_when_single, 0, 1); + config.cursor_hide_timeout = + CLAMP_INT(config.cursor_hide_timeout, 0, 36000); + config.single_scratchpad = CLAMP_INT(config.single_scratchpad, 0, 1); + config.repeat_rate = CLAMP_INT(config.repeat_rate, 1, 1000); + config.repeat_delay = CLAMP_INT(config.repeat_delay, 1, 20000); + config.numlockon = CLAMP_INT(config.numlockon, 0, 1); + config.disable_trackpad = CLAMP_INT(config.disable_trackpad, 0, 1); + config.tap_to_click = CLAMP_INT(config.tap_to_click, 0, 1); + config.tap_and_drag = CLAMP_INT(config.tap_and_drag, 0, 1); + config.drag_lock = CLAMP_INT(config.drag_lock, 0, 1); + config.trackpad_natural_scrolling = CLAMP_INT(config.trackpad_natural_scrolling, 0, 1); - disable_while_typing = CLAMP_INT(config.disable_while_typing, 0, 1); - left_handed = CLAMP_INT(config.left_handed, 0, 1); - middle_button_emulation = CLAMP_INT(config.middle_button_emulation, 0, 1); - swipe_min_threshold = CLAMP_INT(config.swipe_min_threshold, 1, 1000); - - // 鼠标设置 - mouse_natural_scrolling = CLAMP_INT(config.mouse_natural_scrolling, 0, 1); - accel_profile = CLAMP_INT(config.accel_profile, 0, 2); - accel_speed = CLAMP_FLOAT(config.accel_speed, -1.0f, 1.0f); - scroll_method = CLAMP_INT(config.scroll_method, 0, 4); - scroll_button = CLAMP_INT(config.scroll_button, 272, 276); - click_method = CLAMP_INT(config.click_method, 0, 2); - send_events_mode = CLAMP_INT(config.send_events_mode, 0, 2); - button_map = CLAMP_INT(config.button_map, 0, 1); - axis_scroll_factor = CLAMP_FLOAT(config.axis_scroll_factor, 0.1f, 10.0f); - - // 外观设置 - gappih = CLAMP_INT(config.gappih, 0, 1000); - gappiv = CLAMP_INT(config.gappiv, 0, 1000); - gappoh = CLAMP_INT(config.gappoh, 0, 1000); - gappov = CLAMP_INT(config.gappov, 0, 1000); - scratchpad_width_ratio = + config.disable_while_typing = CLAMP_INT(config.disable_while_typing, 0, 1); + config.left_handed = CLAMP_INT(config.left_handed, 0, 1); + config.middle_button_emulation = + CLAMP_INT(config.middle_button_emulation, 0, 1); + config.swipe_min_threshold = CLAMP_INT(config.swipe_min_threshold, 1, 1000); + config.mouse_natural_scrolling = + CLAMP_INT(config.mouse_natural_scrolling, 0, 1); + config.accel_profile = CLAMP_INT(config.accel_profile, 0, 2); + config.accel_speed = CLAMP_FLOAT(config.accel_speed, -1.0f, 1.0f); + config.scroll_method = CLAMP_INT(config.scroll_method, 0, 4); + config.scroll_button = CLAMP_INT(config.scroll_button, 272, 276); + config.click_method = CLAMP_INT(config.click_method, 0, 2); + config.send_events_mode = CLAMP_INT(config.send_events_mode, 0, 2); + config.button_map = CLAMP_INT(config.button_map, 0, 1); + config.axis_scroll_factor = + CLAMP_FLOAT(config.axis_scroll_factor, 0.1f, 10.0f); + config.gappih = CLAMP_INT(config.gappih, 0, 1000); + config.gappiv = CLAMP_INT(config.gappiv, 0, 1000); + config.gappoh = CLAMP_INT(config.gappoh, 0, 1000); + config.gappov = CLAMP_INT(config.gappov, 0, 1000); + config.scratchpad_width_ratio = CLAMP_FLOAT(config.scratchpad_width_ratio, 0.1f, 1.0f); - scratchpad_height_ratio = + config.scratchpad_height_ratio = CLAMP_FLOAT(config.scratchpad_height_ratio, 0.1f, 1.0f); - borderpx = CLAMP_INT(config.borderpx, 0, 200); - smartgaps = CLAMP_INT(config.smartgaps, 0, 1); - - blur = CLAMP_INT(config.blur, 0, 1); - blur_layer = CLAMP_INT(config.blur_layer, 0, 1); - blur_optimized = CLAMP_INT(config.blur_optimized, 0, 1); - border_radius = CLAMP_INT(config.border_radius, 0, 100); - blur_params.num_passes = CLAMP_INT(config.blur_params.num_passes, 0, 10); - blur_params.radius = CLAMP_INT(config.blur_params.radius, 0, 100); - blur_params.noise = CLAMP_FLOAT(config.blur_params.noise, 0, 1); - blur_params.brightness = CLAMP_FLOAT(config.blur_params.brightness, 0, 1); - blur_params.contrast = CLAMP_FLOAT(config.blur_params.contrast, 0, 1); - blur_params.saturation = CLAMP_FLOAT(config.blur_params.saturation, 0, 1); - shadows = CLAMP_INT(config.shadows, 0, 1); - shadow_only_floating = CLAMP_INT(config.shadow_only_floating, 0, 1); - layer_shadows = CLAMP_INT(config.layer_shadows, 0, 1); - shadows_size = CLAMP_INT(config.shadows_size, 0, 100); - shadows_blur = CLAMP_INT(config.shadows_blur, 0, 100); - shadows_position_x = CLAMP_INT(config.shadows_position_x, -1000, 1000); - shadows_position_y = CLAMP_INT(config.shadows_position_y, -1000, 1000); - focused_opacity = CLAMP_FLOAT(config.focused_opacity, 0.0f, 1.0f); - unfocused_opacity = CLAMP_FLOAT(config.unfocused_opacity, 0.0f, 1.0f); - memcpy(shadowscolor, config.shadowscolor, sizeof(shadowscolor)); - - // 复制颜色数组 - memcpy(rootcolor, config.rootcolor, sizeof(rootcolor)); - memcpy(bordercolor, config.bordercolor, sizeof(bordercolor)); - memcpy(focuscolor, config.focuscolor, sizeof(focuscolor)); - memcpy(maximizescreencolor, config.maximizescreencolor, - sizeof(maximizescreencolor)); - memcpy(urgentcolor, config.urgentcolor, sizeof(urgentcolor)); - memcpy(scratchpadcolor, config.scratchpadcolor, sizeof(scratchpadcolor)); - memcpy(globalcolor, config.globalcolor, sizeof(globalcolor)); - memcpy(overlaycolor, config.overlaycolor, sizeof(overlaycolor)); - - // 复制动画曲线 - memcpy(animation_curve_move, config.animation_curve_move, - sizeof(animation_curve_move)); - memcpy(animation_curve_open, config.animation_curve_open, - sizeof(animation_curve_open)); - memcpy(animation_curve_tag, config.animation_curve_tag, - sizeof(animation_curve_tag)); - memcpy(animation_curve_close, config.animation_curve_close, - sizeof(animation_curve_close)); - memcpy(animation_curve_focus, config.animation_curve_focus, - sizeof(animation_curve_focus)); - memcpy(animation_curve_opafadein, config.animation_curve_opafadein, - sizeof(animation_curve_opafadein)); - memcpy(animation_curve_opafadeout, config.animation_curve_opafadeout, - sizeof(animation_curve_opafadeout)); + config.borderpx = CLAMP_INT(config.borderpx, 0, 200); + config.smartgaps = CLAMP_INT(config.smartgaps, 0, 1); + config.blur = CLAMP_INT(config.blur, 0, 1); + config.blur_layer = CLAMP_INT(config.blur_layer, 0, 1); + config.blur_optimized = CLAMP_INT(config.blur_optimized, 0, 1); + config.border_radius = CLAMP_INT(config.border_radius, 0, 100); + config.blur_params.num_passes = + CLAMP_INT(config.blur_params.num_passes, 0, 10); + config.blur_params.radius = CLAMP_INT(config.blur_params.radius, 0, 100); + config.blur_params.noise = CLAMP_FLOAT(config.blur_params.noise, 0, 1); + config.blur_params.brightness = + CLAMP_FLOAT(config.blur_params.brightness, 0, 1); + config.blur_params.contrast = + CLAMP_FLOAT(config.blur_params.contrast, 0, 1); + config.blur_params.saturation = + CLAMP_FLOAT(config.blur_params.saturation, 0, 1); + config.shadows = CLAMP_INT(config.shadows, 0, 1); + config.shadow_only_floating = CLAMP_INT(config.shadow_only_floating, 0, 1); + config.layer_shadows = CLAMP_INT(config.layer_shadows, 0, 1); + config.shadows_size = CLAMP_INT(config.shadows_size, 0, 100); + config.shadows_blur = CLAMP_INT(config.shadows_blur, 0, 100); + config.shadows_position_x = + CLAMP_INT(config.shadows_position_x, -1000, 1000); + config.shadows_position_y = + CLAMP_INT(config.shadows_position_y, -1000, 1000); + config.focused_opacity = CLAMP_FLOAT(config.focused_opacity, 0.0f, 1.0f); + config.unfocused_opacity = + CLAMP_FLOAT(config.unfocused_opacity, 0.0f, 1.0f); } void set_value_default() { - /* animaion */ - config.animations = animations; // 是否启用动画 - config.layer_animations = layer_animations; // 是否启用layer动画 - config.animation_fade_in = animation_fade_in; // Enable animation fade in - config.animation_fade_out = animation_fade_out; // Enable animation fade out - config.tag_animation_direction = tag_animation_direction; // 标签动画方向 - config.zoom_initial_ratio = zoom_initial_ratio; // 动画起始窗口比例 - config.zoom_end_ratio = zoom_end_ratio; // 动画结束窗口比例 - config.fadein_begin_opacity = - fadein_begin_opacity; // Begin opac window ratio for animations - config.fadeout_begin_opacity = fadeout_begin_opacity; - config.animation_duration_move = - animation_duration_move; // Animation move speed - config.animation_duration_open = - animation_duration_open; // Animation open speed - config.animation_duration_tag = - animation_duration_tag; // Animation tag speed - config.animation_duration_close = - animation_duration_close; // Animation tag speed - config.animation_duration_focus = - animation_duration_focus; // Animation focus opacity speed + config.animations = 1; + config.layer_animations = 0; + config.animation_fade_in = 1; + config.animation_fade_out = 1; + config.tag_animation_direction = HORIZONTAL; + config.zoom_initial_ratio = 0.3f; + config.zoom_end_ratio = 0.8f; + config.fadein_begin_opacity = 0.5f; + config.fadeout_begin_opacity = 0.5f; + config.animation_duration_move = 500; + config.animation_duration_open = 400; + config.animation_duration_tag = 300; + config.animation_duration_close = 300; + config.animation_duration_focus = 0; - /* appearance */ - config.axis_bind_apply_timeout = - axis_bind_apply_timeout; // 滚轮绑定动作的触发的时间间隔 - config.focus_on_activate = - focus_on_activate; // 收到窗口激活请求是否自动跳转聚焦 - config.new_is_master = new_is_master; // 新窗口是否插在头部 - config.default_mfact = default_mfact; // master 窗口比例 - config.default_nmaster = default_nmaster; // 默认master数量 - config.center_master_overspread = - center_master_overspread; // 中心master时是否铺满 - config.center_when_single_stack = - center_when_single_stack; // 单个stack时是否居中 + config.axis_bind_apply_timeout = 100; + config.focus_on_activate = 1; + config.new_is_master = 1; + config.default_mfact = 0.55f; + config.default_nmaster = 1; + config.center_master_overspread = 0; + config.center_when_single_stack = 1; - config.numlockon = numlockon; // 是否打开右边小键盘 + config.log_level = WLR_ERROR; + config.numlockon = 0; + config.capslock = 0; - 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 */ - config.sloppyfocus = sloppyfocus; /* focus follows mouse */ - config.gappih = gappih; /* horiz inner gap between windows */ - config.gappiv = gappiv; /* vert inner gap between windows */ - config.gappoh = - gappoh; /* horiz outer gap between windows and screen edge */ - config.gappov = gappov; /* vert outer gap between windows and screen edge */ - config.scratchpad_width_ratio = scratchpad_width_ratio; - config.scratchpad_height_ratio = scratchpad_height_ratio; + config.ov_tab_mode = 0; + config.hotarea_size = 10; + config.hotarea_corner = BOTTOM_LEFT; + config.enable_hotarea = 1; + config.smartgaps = 0; + config.sloppyfocus = 1; + config.gappih = 5; + config.gappiv = 5; + config.gappoh = 10; + config.gappov = 10; + config.scratchpad_width_ratio = 0.8f; + config.scratchpad_height_ratio = 0.9f; - config.scroller_structs = scroller_structs; - config.scroller_default_proportion = scroller_default_proportion; - config.scroller_default_proportion_single = - scroller_default_proportion_single; - config.scroller_ignore_proportion_single = - scroller_ignore_proportion_single; - config.scroller_focus_center = scroller_focus_center; - config.scroller_prefer_center = scroller_prefer_center; - config.scroller_prefer_overspread = scroller_prefer_overspread; - config.edge_scroller_pointer_focus = edge_scroller_pointer_focus; - config.focus_cross_monitor = focus_cross_monitor; - config.exchange_cross_monitor = exchange_cross_monitor; - config.scratchpad_cross_monitor = scratchpad_cross_monitor; - config.focus_cross_tag = focus_cross_tag; - config.axis_scroll_factor = axis_scroll_factor; - config.view_current_to_back = view_current_to_back; - config.single_scratchpad = single_scratchpad; - config.xwayland_persistence = xwayland_persistence; - config.syncobj_enable = syncobj_enable; - config.drag_tile_refresh_interval = drag_tile_refresh_interval; - config.drag_floating_refresh_interval = drag_floating_refresh_interval; - config.allow_tearing = allow_tearing; - config.allow_shortcuts_inhibit = allow_shortcuts_inhibit; - config.allow_lock_transparent = allow_lock_transparent; - config.no_border_when_single = no_border_when_single; - config.no_radius_when_single = no_radius_when_single; - config.snap_distance = snap_distance; - config.drag_tile_to_tile = drag_tile_to_tile; - config.enable_floating_snap = enable_floating_snap; - config.swipe_min_threshold = swipe_min_threshold; + config.scroller_structs = 20; + config.scroller_default_proportion = 0.9f; + config.scroller_default_proportion_single = 1.0f; + config.scroller_ignore_proportion_single = 1; + config.scroller_focus_center = 0; + config.scroller_prefer_center = 0; + config.scroller_prefer_overspread = 1; + config.edge_scroller_pointer_focus = 1; + config.focus_cross_monitor = 0; + config.exchange_cross_monitor = 0; + config.scratchpad_cross_monitor = 0; + config.focus_cross_tag = 0; + config.axis_scroll_factor = 1.0; + config.view_current_to_back = 0; + config.single_scratchpad = 1; + config.xwayland_persistence = 1; + config.syncobj_enable = 0; + config.drag_tile_refresh_interval = 8.0f; + config.drag_floating_refresh_interval = 8.0f; + config.allow_tearing = TEARING_DISABLED; + config.allow_shortcuts_inhibit = SHORTCUTS_INHIBIT_ENABLE; + config.allow_lock_transparent = 0; + config.no_border_when_single = 0; + config.no_radius_when_single = 0; + config.snap_distance = 30; + config.drag_tile_to_tile = 0; + config.enable_floating_snap = 0; + config.swipe_min_threshold = 1; - config.idleinhibit_ignore_visible = - idleinhibit_ignore_visible; /* 1 means idle inhibitors will - disable idle tracking even if it's - surface isn't visible - */ + config.idleinhibit_ignore_visible = 0; - config.borderpx = borderpx; - config.overviewgappi = overviewgappi; /* overview时 窗口与边缘 缝隙大小 */ - config.overviewgappo = overviewgappo; /* overview时 窗口与窗口 缝隙大小 */ - config.cursor_hide_timeout = cursor_hide_timeout; + config.borderpx = 4; + config.overviewgappi = 5; + config.overviewgappo = 30; + config.cursor_hide_timeout = 0; - config.warpcursor = warpcursor; /* Warp cursor to focused client */ - config.drag_corner = drag_corner; - config.drag_warp_cursor = drag_warp_cursor; + config.warpcursor = 1; + config.drag_corner = 3; + config.drag_warp_cursor = 1; - config.repeat_rate = repeat_rate; - config.repeat_delay = repeat_delay; + config.repeat_rate = 25; + config.repeat_delay = 600; - /* Trackpad */ - config.disable_trackpad = disable_trackpad; - config.tap_to_click = tap_to_click; - config.tap_and_drag = tap_and_drag; - config.drag_lock = drag_lock; - config.mouse_natural_scrolling = mouse_natural_scrolling; - config.cursor_size = cursor_size; - config.trackpad_natural_scrolling = trackpad_natural_scrolling; - config.disable_while_typing = disable_while_typing; - config.left_handed = left_handed; - config.middle_button_emulation = middle_button_emulation; - config.accel_profile = accel_profile; - config.accel_speed = accel_speed; - config.scroll_method = scroll_method; - config.scroll_button = scroll_button; - config.click_method = click_method; - config.send_events_mode = send_events_mode; - config.button_map = button_map; + config.disable_trackpad = 0; + config.tap_to_click = 1; + config.tap_and_drag = 1; + config.drag_lock = 1; + config.mouse_natural_scrolling = 0; + config.cursor_size = 24; + config.trackpad_natural_scrolling = 0; + config.disable_while_typing = 1; + config.left_handed = 0; + config.middle_button_emulation = 0; + config.accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; + config.accel_speed = 0.0; + config.scroll_method = LIBINPUT_CONFIG_SCROLL_2FG; + config.scroll_button = 274; + config.click_method = LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS; + config.send_events_mode = LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; + config.button_map = LIBINPUT_CONFIG_TAP_MAP_LRM; - config.blur = blur; - config.blur_layer = blur_layer; - config.blur_optimized = blur_optimized; - config.border_radius = border_radius; - config.blur_params.num_passes = blur_params_num_passes; - config.blur_params.radius = blur_params_radius; - config.blur_params.noise = blur_params_noise; - config.blur_params.brightness = blur_params_brightness; - config.blur_params.contrast = blur_params_contrast; - config.blur_params.saturation = blur_params_saturation; - config.shadows = shadows; - config.shadow_only_floating = shadow_only_floating; - config.layer_shadows = layer_shadows; - config.shadows_size = shadows_size; - config.shadows_blur = shadows_blur; - config.shadows_position_x = shadows_position_x; - config.shadows_position_y = shadows_position_y; - config.focused_opacity = focused_opacity; - config.unfocused_opacity = unfocused_opacity; - memcpy(config.shadowscolor, shadowscolor, sizeof(shadowscolor)); + config.blur = 0; + config.blur_layer = 0; + config.blur_optimized = 1; + config.border_radius = 0; + config.border_radius_location_default = CORNER_LOCATION_ALL; + config.blur_params.num_passes = 1; + config.blur_params.radius = 5; + config.blur_params.noise = 0.02f; + config.blur_params.brightness = 0.9f; + config.blur_params.contrast = 0.9f; + config.blur_params.saturation = 1.2f; + config.shadows = 0; + config.shadow_only_floating = 1; + config.layer_shadows = 0; + config.shadows_size = 10; + config.shadows_blur = 15.0f; + config.shadows_position_x = 0; + config.shadows_position_y = 0; + config.focused_opacity = 1.0f; + config.unfocused_opacity = 1.0f; - memcpy(config.animation_curve_move, animation_curve_move, - sizeof(animation_curve_move)); - memcpy(config.animation_curve_open, animation_curve_open, - sizeof(animation_curve_open)); - memcpy(config.animation_curve_tag, animation_curve_tag, - sizeof(animation_curve_tag)); - memcpy(config.animation_curve_close, animation_curve_close, - sizeof(animation_curve_close)); - memcpy(config.animation_curve_focus, animation_curve_focus, - sizeof(animation_curve_focus)); - memcpy(config.animation_curve_opafadein, animation_curve_opafadein, - sizeof(animation_curve_opafadein)); - memcpy(config.animation_curve_opafadeout, animation_curve_opafadeout, - sizeof(animation_curve_opafadeout)); + config.shadowscolor[0] = 0.0f; + config.shadowscolor[1] = 0.0f; + config.shadowscolor[2] = 0.0f; + config.shadowscolor[3] = 1.0f; - memcpy(config.rootcolor, rootcolor, sizeof(rootcolor)); - memcpy(config.bordercolor, bordercolor, sizeof(bordercolor)); - memcpy(config.focuscolor, focuscolor, sizeof(focuscolor)); - memcpy(config.maximizescreencolor, maximizescreencolor, - sizeof(maximizescreencolor)); - memcpy(config.urgentcolor, urgentcolor, sizeof(urgentcolor)); - memcpy(config.scratchpadcolor, scratchpadcolor, sizeof(scratchpadcolor)); - memcpy(config.globalcolor, globalcolor, sizeof(globalcolor)); - memcpy(config.overlaycolor, overlaycolor, sizeof(overlaycolor)); + config.animation_curve_move[0] = 0.46; + config.animation_curve_move[1] = 1.0; + config.animation_curve_move[2] = 0.29; + config.animation_curve_move[3] = 0.99; + config.animation_curve_open[0] = 0.46; + config.animation_curve_open[1] = 1.0; + config.animation_curve_open[2] = 0.29; + config.animation_curve_open[3] = 0.99; + config.animation_curve_tag[0] = 0.46; + config.animation_curve_tag[1] = 1.0; + config.animation_curve_tag[2] = 0.29; + config.animation_curve_tag[3] = 0.99; + config.animation_curve_close[0] = 0.46; + config.animation_curve_close[1] = 1.0; + config.animation_curve_close[2] = 0.29; + config.animation_curve_close[3] = 0.99; + config.animation_curve_focus[0] = 0.46; + config.animation_curve_focus[1] = 1.0; + config.animation_curve_focus[2] = 0.29; + config.animation_curve_focus[3] = 0.99; + config.animation_curve_opafadein[0] = 0.46; + config.animation_curve_opafadein[1] = 1.0; + config.animation_curve_opafadein[2] = 0.29; + config.animation_curve_opafadein[3] = 0.99; + config.animation_curve_opafadeout[0] = 0.5; + config.animation_curve_opafadeout[1] = 0.5; + config.animation_curve_opafadeout[2] = 0.5; + config.animation_curve_opafadeout[3] = 0.5; + + config.rootcolor[0] = 0x32 / 255.0f; + config.rootcolor[1] = 0x32 / 255.0f; + config.rootcolor[2] = 0x32 / 255.0f; + config.rootcolor[3] = 1.0f; + config.bordercolor[0] = 0x44 / 255.0f; + config.bordercolor[1] = 0x44 / 255.0f; + config.bordercolor[2] = 0x44 / 255.0f; + config.bordercolor[3] = 1.0f; + config.focuscolor[0] = 0xc6 / 255.0f; + config.focuscolor[1] = 0x6b / 255.0f; + config.focuscolor[2] = 0x25 / 255.0f; + config.focuscolor[3] = 1.0f; + config.maximizescreencolor[0] = 0x89 / 255.0f; + config.maximizescreencolor[1] = 0xaa / 255.0f; + config.maximizescreencolor[2] = 0x61 / 255.0f; + config.maximizescreencolor[3] = 1.0f; + config.urgentcolor[0] = 0xad / 255.0f; + config.urgentcolor[1] = 0x40 / 255.0f; + config.urgentcolor[2] = 0x1f / 255.0f; + config.urgentcolor[3] = 1.0f; + config.scratchpadcolor[0] = 0x51 / 255.0f; + config.scratchpadcolor[1] = 0x6c / 255.0f; + config.scratchpadcolor[2] = 0x93 / 255.0f; + config.scratchpadcolor[3] = 1.0f; + config.globalcolor[0] = 0xb1 / 255.0f; + config.globalcolor[1] = 0x53 / 255.0f; + config.globalcolor[2] = 0xa7 / 255.0f; + config.globalcolor[3] = 1.0f; + config.overlaycolor[0] = 0x14 / 255.0f; + config.overlaycolor[1] = 0xa5 / 255.0f; + config.overlaycolor[2] = 0x7c / 255.0f; + config.overlaycolor[3] = 1.0f; } void set_default_key_bindings(Config *config) { @@ -3479,13 +3470,14 @@ bool parse_config(void) { free_config(); - // 重置config结构体,确保所有指针初始化为NULL memset(&config, 0, sizeof(config)); - memset(&xkb_rules_rules, 0, sizeof(xkb_rules_rules)); - memset(&xkb_rules_model, 0, sizeof(xkb_rules_model)); - memset(&xkb_rules_layout, 0, sizeof(xkb_rules_layout)); - memset(&xkb_rules_variant, 0, sizeof(xkb_rules_variant)); - memset(&xkb_rules_options, 0, sizeof(xkb_rules_options)); + + // 重新将xkb_rules指针指向静态数组 + config.xkb_rules.layout = config.xkb_rules_layout; + config.xkb_rules.variant = config.xkb_rules_variant; + config.xkb_rules.options = config.xkb_rules_options; + config.xkb_rules.rules = config.xkb_rules_rules; + config.xkb_rules.model = config.xkb_rules_model; // 初始化动态数组的指针为NULL,避免野指针 config.window_rules = NULL; @@ -3549,7 +3541,7 @@ bool parse_config(void) { } void reset_blur_params(void) { - if (blur) { + if (config.blur) { Monitor *m = NULL; wl_list_for_each(m, &mons, link) { if (m->blur != NULL) { @@ -3559,9 +3551,9 @@ void reset_blur_params(void) { wlr_scene_node_reparent(&m->blur->node, layers[LyrBlur]); wlr_scene_optimized_blur_set_size(m->blur, m->m.width, m->m.height); wlr_scene_set_blur_data( - scene, blur_params.num_passes, blur_params.radius, - blur_params.noise, blur_params.brightness, blur_params.contrast, - blur_params.saturation); + scene, config.blur_params.num_passes, config.blur_params.radius, + config.blur_params.noise, config.blur_params.brightness, + config.blur_params.contrast, config.blur_params.saturation); } } else { Monitor *m = NULL; @@ -3628,11 +3620,12 @@ void reapply_cursor_style(void) { cursor_mgr = NULL; } - cursor_mgr = wlr_xcursor_manager_create(config.cursor_theme, cursor_size); + cursor_mgr = + wlr_xcursor_manager_create(config.cursor_theme, config.cursor_size); - if (cursor_size > 0) { + if (config.cursor_size > 0) { char size_str[16]; - snprintf(size_str, sizeof(size_str), "%d", cursor_size); + snprintf(size_str, sizeof(size_str), "%d", config.cursor_size); setenv("XCURSOR_SIZE", size_str, 1); } @@ -3653,11 +3646,13 @@ void reapply_cursor_style(void) { wlr_cursor_unset_image(cursor); } else { wl_event_source_timer_update(hide_cursor_source, - cursor_hide_timeout * 1000); + config.cursor_hide_timeout * 1000); } } -void reapply_rootbg(void) { wlr_scene_rect_set_color(root_bg, rootcolor); } +void reapply_rootbg(void) { + wlr_scene_rect_set_color(root_bg, config.rootcolor); +} void reapply_border(void) { Client *c = NULL; @@ -3666,7 +3661,7 @@ void reapply_border(void) { wl_list_for_each(c, &clients, link) { if (c && !c->iskilling) { if (!c->isnoborder && !c->isfullscreen) { - c->bw = borderpx; + c->bw = config.borderpx; } } } @@ -3679,7 +3674,7 @@ void reapply_keyboard(void) { continue; } wlr_keyboard_set_repeat_info((struct wlr_keyboard *)id->device_data, - repeat_rate, repeat_delay); + config.repeat_rate, config.repeat_delay); } } @@ -3708,12 +3703,12 @@ void reapply_master(void) { if (!m->wlr_output->enabled) { continue; } - m->pertag->nmasters[i] = default_nmaster; - m->pertag->mfacts[i] = default_mfact; - m->gappih = gappih; - m->gappiv = gappiv; - m->gappoh = gappoh; - m->gappov = gappov; + m->pertag->nmasters[i] = config.default_nmaster; + m->pertag->mfacts[i] = config.default_mfact; + m->gappih = config.gappih; + m->gappiv = config.gappiv; + m->gappoh = config.gappoh; + m->gappov = config.gappov; } } } @@ -3725,8 +3720,8 @@ void parse_tagrule(Monitor *m) { bool match_rule = false; for (i = 0; i <= LENGTH(tags); i++) { - m->pertag->nmasters[i] = default_nmaster; - m->pertag->mfacts[i] = default_mfact; + m->pertag->nmasters[i] = config.default_nmaster; + m->pertag->mfacts[i] = config.default_mfact; } for (i = 0; i < config.tag_rules_count; i++) { diff --git a/src/config/preset.h b/src/config/preset.h index f91da11..6952518 100644 --- a/src/config/preset.h +++ b/src/config/preset.h @@ -1,132 +1,9 @@ -// TODO: remove this file in the future, replace all global variables with -// config.xxx +#define MODKEY WLR_MODIFIER_ALT -/* speedie's mango config */ +static const char *tags[] = { + "1", "2", "3", "4", "5", "6", "7", "8", "9", +}; -#define COLOR(hex) \ - {((hex >> 24) & 0xFF) / 255.0f, ((hex >> 16) & 0xFF) / 255.0f, \ - ((hex >> 8) & 0xFF) / 255.0f, (hex & 0xFF) / 255.0f} - -/* animaion */ -char *animation_type_open = "slide"; // 是否启用动画 //slide,zoom -char *animation_type_close = "slide"; // 是否启用动画 //slide,zoom -char *layer_animation_type_open = "slide"; // 是否启用layer动画 //slide,zoom -char *layer_animation_type_close = "slide"; // 是否启用layer动画 //slide,zoom -int32_t animations = 1; // 是否启用动画 -int32_t layer_animations = 0; // 是否启用layer动画 -int32_t tag_animation_direction = HORIZONTAL; // 标签动画方向 -int32_t animation_fade_in = 1; // Enable animation fade in -int32_t animation_fade_out = 1; // Enable animation fade out -float zoom_initial_ratio = 0.3; // 动画起始窗口比例 -float zoom_end_ratio = 0.8; // 动画结束窗口比例 -float fadein_begin_opacity = 0.5; // Begin opac window ratio for animations -float fadeout_begin_opacity = 0.5; // Begin opac window ratio for animations -uint32_t animation_duration_move = 500; // Animation move speed -uint32_t animation_duration_open = 400; // Animation open speed -uint32_t animation_duration_tag = 300; // Animation tag speed -uint32_t animation_duration_close = 300; // Animation close speed -uint32_t animation_duration_focus = 0; // Animation focus opacity speed -double animation_curve_move[4] = {0.46, 1.0, 0.29, 0.99}; // 动画曲线 -double animation_curve_open[4] = {0.46, 1.0, 0.29, 0.99}; // 动画曲线 -double animation_curve_tag[4] = {0.46, 1.0, 0.29, 0.99}; // 动画曲线 -double animation_curve_close[4] = {0.46, 1.0, 0.29, 0.99}; // 动画曲线 -double animation_curve_focus[4] = {0.46, 1.0, 0.29, 0.99}; // 动画曲线 -double animation_curve_opafadein[4] = {0.46, 1.0, 0.29, 0.99}; // 动画曲线 -double animation_curve_opafadeout[4] = {0.5, 0.5, 0.5, 0.5}; // 动画曲线 - -/* appearance */ -uint32_t axis_bind_apply_timeout = 100; // 滚轮绑定动作的触发的时间间隔 -uint32_t focus_on_activate = 1; // 收到窗口激活请求是否自动跳转聚焦 -uint32_t new_is_master = 1; // 新窗口是否插在头部 -double default_mfact = 0.55f; // master 窗口比例 -uint32_t default_nmaster = 1; // 默认master数量 -int32_t center_master_overspread = 0; // 中心master时是否铺满 -int32_t center_when_single_stack = 1; // 单个stack时是否居中 -/* logging */ -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 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 */ -uint32_t gappih = 5; /* horiz inner gap between windows */ -uint32_t gappiv = 5; /* vert inner gap between windows */ -uint32_t gappoh = 10; /* horiz outer gap between windows and screen edge */ -uint32_t gappov = 10; /* vert outer gap between windows and screen edge */ -float scratchpad_width_ratio = 0.8; -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 = 1; -int32_t scroller_focus_center = 0; -int32_t scroller_prefer_center = 0; -int32_t scroller_prefer_overspread = 1; -int32_t focus_cross_monitor = 0; -int32_t focus_cross_tag = 0; -int32_t exchange_cross_monitor = 0; -int32_t scratchpad_cross_monitor = 0; -int32_t view_current_to_back = 0; -int32_t no_border_when_single = 0; -int32_t no_radius_when_single = 0; -int32_t snap_distance = 30; -int32_t enable_floating_snap = 0; -int32_t drag_tile_to_tile = 0; -uint32_t cursor_size = 24; -uint32_t cursor_hide_timeout = 0; - -uint32_t swipe_min_threshold = 1; - -int32_t idleinhibit_ignore_visible = - 0; /* 1 means idle inhibitors will disable idle tracking even if it's - surface isn't visible */ -uint32_t borderpx = 4; /* border pixel of windows */ -float rootcolor[] = COLOR(0x323232ff); -float bordercolor[] = COLOR(0x444444ff); -float focuscolor[] = COLOR(0xc66b25ff); -float maximizescreencolor[] = COLOR(0x89aa61ff); -float urgentcolor[] = COLOR(0xad401fff); -float scratchpadcolor[] = COLOR(0x516c93ff); -float globalcolor[] = COLOR(0xb153a7ff); -float overlaycolor[] = COLOR(0x14a57cff); -// char *cursor_theme = "Bibata-Modern-Ice"; - -int32_t overviewgappi = 5; /* overview时 窗口与边缘 缝隙大小 */ -int32_t overviewgappo = 30; /* overview时 窗口与窗口 缝隙大小 */ - -/* To conform the xdg-protocol, set the alpha to zero to restore the old - * behavior */ -float fullscreen_bg[] = {0.1, 0.1, 0.1, 1.0}; - -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 allow_lock_transparent = 0; -double drag_tile_refresh_interval = 8.0; -double drag_floating_refresh_interval = 8.0; -int32_t allow_tearing = TEARING_DISABLED; -int32_t allow_shortcuts_inhibit = SHORTCUTS_INHIBIT_ENABLE; - -/* keyboard */ - -/* - only layout can modify after fisrt init - other fields change will be ignored. -*/ -char xkb_rules_rules[256]; -char xkb_rules_model[256]; -char xkb_rules_layout[256]; -char xkb_rules_variant[256]; -char xkb_rules_options[256]; - -/* keyboard */ static const struct xkb_rule_names xkb_fallback_rules = { .layout = "us", .variant = NULL, @@ -134,106 +11,3 @@ static const struct xkb_rule_names xkb_fallback_rules = { .rules = NULL, .options = NULL, }; - -static const struct xkb_rule_names xkb_default_rules = { - .options = NULL, -}; - -struct xkb_rule_names xkb_rules = { - /* can specify fields: rules, model, layout, variant, options */ - /* example: - .options = "ctrl:nocaps", - */ - .rules = xkb_rules_rules, .model = xkb_rules_model, - .layout = xkb_rules_layout, .variant = xkb_rules_variant, - .options = xkb_rules_options, -}; - -int32_t repeat_rate = 25; -int32_t repeat_delay = 600; - -/* Trackpad */ -int32_t disable_trackpad = 0; -int32_t tap_to_click = 1; -int32_t tap_and_drag = 1; -int32_t drag_lock = 1; -int32_t mouse_natural_scrolling = 0; -int32_t trackpad_natural_scrolling = 0; -int32_t disable_while_typing = 1; -int32_t left_handed = 0; -int32_t middle_button_emulation = 0; -int32_t single_scratchpad = 1; -int32_t edge_scroller_pointer_focus = 1; - -/* You can choose between: -LIBINPUT_CONFIG_SCROLL_NO_SCROLL -LIBINPUT_CONFIG_SCROLL_2FG -LIBINPUT_CONFIG_SCROLL_EDGE -LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN -*/ -enum libinput_config_scroll_method scroll_method = LIBINPUT_CONFIG_SCROLL_2FG; -uint32_t scroll_button = 274; - -/* You can choose between: -LIBINPUT_CONFIG_CLICK_METHOD_NONE -LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS -LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER -*/ -enum libinput_config_click_method click_method = - LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS; - -double axis_scroll_factor = 1.0; - -/* You can choose between: -LIBINPUT_CONFIG_SEND_EVENTS_ENABLED -LIBINPUT_CONFIG_SEND_EVENTS_DISABLED -LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE -*/ -uint32_t send_events_mode = LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; - -/* You can choose between: -LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT -LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE -*/ -enum libinput_config_accel_profile accel_profile = - LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; -double accel_speed = 0.0; -/* You can choose between: -LIBINPUT_CONFIG_TAP_MAP_LRM -- 1/2/3 finger tap maps to left/right/middle -LIBINPUT_CONFIG_TAP_MAP_LMR -- 1/2/3 finger tap maps to left/middle/right -*/ -enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TAP_MAP_LRM; - -/* If you want to use the windows key for MODKEY, use WLR_MODIFIER_LOGO */ -#define MODKEY WLR_MODIFIER_ALT - -static const char *tags[] = { - "1", "2", "3", "4", "5", "6", "7", "8", "9", -}; - -float focused_opacity = 1.0; -float unfocused_opacity = 1.0; - -int32_t border_radius = 0; -int32_t border_radius_location_default = CORNER_LOCATION_ALL; -int32_t blur = 0; -int32_t blur_layer = 0; -int32_t blur_optimized = 1; - -struct blur_data blur_params; - -int32_t blur_params_num_passes = 1; -int32_t blur_params_radius = 5; -float blur_params_noise = 0.02; -float blur_params_brightness = 0.9; -float blur_params_contrast = 0.9; -float blur_params_saturation = 1.2; - -int32_t shadows = 0; -int32_t shadow_only_floating = 1; -int32_t layer_shadows = 0; -uint32_t shadows_size = 10; -double shadows_blur = 15; -int32_t shadows_position_x = 0; -int32_t shadows_position_y = 0; -float shadowscolor[] = COLOR(0x000000ff); diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 4f808bd..449adf3 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -3,7 +3,7 @@ int32_t bind_to_view(const Arg *arg) { return 0; uint32_t target = arg->ui; - if (view_current_to_back && selmon->pertag->curtag && + if (config.view_current_to_back && selmon->pertag->curtag && (target & TAGMASK) == (selmon->tagset[selmon->seltags])) { if (selmon->pertag->prevtag) target = 1 << (selmon->pertag->prevtag - 1); @@ -11,13 +11,13 @@ int32_t bind_to_view(const Arg *arg) { target = 0; } - if (!view_current_to_back && + if (!config.view_current_to_back && (target & TAGMASK) == (selmon->tagset[selmon->seltags])) { return 0; } if ((int32_t)target == INT_MIN && selmon->pertag->curtag == 0) { - if (view_current_to_back && selmon->pertag->prevtag) + if (config.view_current_to_back && selmon->pertag->prevtag) target = 1 << (selmon->pertag->prevtag - 1); else target = 0; @@ -96,7 +96,7 @@ int32_t destroy_all_virtual_output(const Arg *arg) { } int32_t defaultgaps(const Arg *arg) { - setgaps(gappoh, gappov, gappih, gappiv); + setgaps(config.gappoh, config.gappov, config.gappih, config.gappiv); return 0; } @@ -140,7 +140,7 @@ int32_t focusdir(const Arg *arg) { c = get_focused_stack_client(c); if (c) { focusclient(c, 1); - if (warpcursor) + if (config.warpcursor) warp_cursor(c); } else { if (config.focus_cross_tag) { @@ -196,7 +196,7 @@ int32_t focuslast(const Arg *arg) { } int32_t toggle_trackpad_enable(const Arg *arg) { - disable_trackpad = !disable_trackpad; + config.disable_trackpad = !config.disable_trackpad; return 0; } @@ -225,7 +225,7 @@ int32_t focusmon(const Arg *arg) { return 0; selmon = tm; - if (warpcursor) { + if (config.warpcursor) { warp_cursor_to_selmon(selmon); } c = focustop(selmon); @@ -258,7 +258,7 @@ int32_t focusstack(const Arg *arg) { return 0; focusclient(tc, 1); - if (warpcursor) + if (config.warpcursor) warp_cursor(tc); return 0; } @@ -390,7 +390,7 @@ 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) { - rzcorner = drag_corner; + rzcorner = config.drag_corner; grabcx = (int)round(cursor->x); grabcy = (int)round(cursor->y); if (rzcorner == 4) @@ -404,7 +404,7 @@ int32_t moveresize(const Arg *arg) { ? 0 : 2); - if (drag_warp_cursor) { + if (config.drag_warp_cursor) { grabcx = rzcorner & 1 ? grabc->geom.x + grabc->geom.width : grabc->geom.x; grabcy = rzcorner & 2 ? grabc->geom.y + grabc->geom.height @@ -476,9 +476,9 @@ int32_t resizewin(const Arg *arg) { if (!c || c->isfullscreen || c->ismaximizescreen) return 0; - int32_t animations_state_backup = animations; + int32_t animations_state_backup = config.animations; if (!c->isfloating) - animations = 0; + config.animations = 0; if (ISTILED(c)) { switch (arg->ui) { @@ -505,7 +505,7 @@ int32_t resizewin(const Arg *arg) { break; } resize_tile_client(c, false, offsetx, offsety, 0); - animations = animations_state_backup; + config.animations = animations_state_backup; return 0; } @@ -536,7 +536,7 @@ int32_t resizewin(const Arg *arg) { c->iscustomsize = 1; c->float_geom = c->geom; resize(c, c->geom, 0); - animations = animations_state_backup; + config.animations = animations_state_backup; return 0; } @@ -608,7 +608,7 @@ int32_t set_proportion(const Arg *arg) { return 0; if (selmon->visible_tiling_clients == 1 && - !scroller_ignore_proportion_single) + !config.scroller_ignore_proportion_single) return 0; Client *tc = selmon->sel; @@ -616,7 +616,7 @@ int32_t set_proportion(const Arg *arg) { if (tc) { tc = get_scroll_stack_head(tc); uint32_t max_client_width = - selmon->w.width - 2 * scroller_structs - gappih; + selmon->w.width - 2 * config.scroller_structs - config.gappih; tc->scroller_proportion = arg->f; tc->geom.width = max_client_width * arg->f; arrange(selmon, false, false); @@ -650,7 +650,7 @@ int32_t smartmovewin(const Arg *arg) { if (c->geom.x + c->geom.width < tc->geom.x || c->geom.x > tc->geom.x + tc->geom.width) continue; - buttom = tc->geom.y + tc->geom.height + gappiv; + buttom = tc->geom.y + tc->geom.height + config.gappiv; if (top > buttom && ny < buttom) { tar = MAX(tar, buttom); }; @@ -670,7 +670,7 @@ int32_t smartmovewin(const Arg *arg) { if (c->geom.x + c->geom.width < tc->geom.x || c->geom.x > tc->geom.x + tc->geom.width) continue; - top = tc->geom.y - gappiv; + top = tc->geom.y - config.gappiv; if (buttom < top && (ny + c->geom.height) > top) { tar = MIN(tar, top - c->geom.height); }; @@ -690,7 +690,7 @@ int32_t smartmovewin(const Arg *arg) { if (c->geom.y + c->geom.height < tc->geom.y || c->geom.y > tc->geom.y + tc->geom.height) continue; - right = tc->geom.x + tc->geom.width + gappih; + right = tc->geom.x + tc->geom.width + config.gappih; if (left > right && nx < right) { tar = MAX(tar, right); }; @@ -709,7 +709,7 @@ int32_t smartmovewin(const Arg *arg) { if (c->geom.y + c->geom.height < tc->geom.y || c->geom.y > tc->geom.y + tc->geom.height) continue; - left = tc->geom.x - gappih; + left = tc->geom.x - config.gappih; if (right < left && (nx + c->geom.width) > left) { tar = MIN(tar, left - c->geom.width); }; @@ -757,14 +757,14 @@ int32_t smartresizewin(const Arg *arg) { if (c->geom.x + c->geom.width < tc->geom.x || c->geom.x > tc->geom.x + tc->geom.width) continue; - top = tc->geom.y - gappiv; + top = tc->geom.y - config.gappiv; if (buttom < top && (nh + c->geom.y) > top) { tar = MAX(tar, top - c->geom.y); }; } nh = tar == -99999 ? nh : tar; - if (c->geom.y + nh + gappov > selmon->w.y + selmon->w.height) - nh = selmon->w.y + selmon->w.height - c->geom.y - gappov; + if (c->geom.y + nh + config.gappov > selmon->w.y + selmon->w.height) + nh = selmon->w.y + selmon->w.height - c->geom.y - config.gappov; break; case LEFT: nw -= selmon->w.width / 16; @@ -780,15 +780,15 @@ int32_t smartresizewin(const Arg *arg) { if (c->geom.y + c->geom.height < tc->geom.y || c->geom.y > tc->geom.y + tc->geom.height) continue; - left = tc->geom.x - gappih; + left = tc->geom.x - config.gappih; if (right < left && (nw + c->geom.x) > left) { tar = MIN(tar, left - c->geom.x); }; } nw = tar == 99999 ? nw : tar; - if (c->geom.x + nw + gappoh > selmon->w.x + selmon->w.width) - nw = selmon->w.x + selmon->w.width - c->geom.x - gappoh; + if (c->geom.x + nw + config.gappoh > selmon->w.x + selmon->w.width) + nw = selmon->w.x + selmon->w.width - c->geom.x - config.gappoh; break; } @@ -1046,7 +1046,7 @@ int32_t switch_proportion_preset(const Arg *arg) { return 0; if (selmon->visible_tiling_clients == 1 && - !scroller_ignore_proportion_single) + !config.scroller_ignore_proportion_single) return 0; Client *tc = selmon->sel; @@ -1087,7 +1087,7 @@ int32_t switch_proportion_preset(const Arg *arg) { } uint32_t max_client_width = - selmon->w.width - 2 * scroller_structs - gappih; + selmon->w.width - 2 * config.scroller_structs - config.gappih; tc->scroller_proportion = target_proportion; tc->geom.width = max_client_width * target_proportion; arrange(selmon, false, false); @@ -1170,7 +1170,7 @@ int32_t tagmon(const Arg *arg) { focusclient(c, 1); arrange(selmon, false, false); } - if (warpcursor) { + if (config.warpcursor) { warp_cursor_to_selmon(c->mon); } return 0; @@ -1257,11 +1257,12 @@ int32_t toggle_scratchpad(const Arg *arg) { return 0; wl_list_for_each_safe(c, tmp, &clients, link) { - if (!scratchpad_cross_monitor && c->mon != selmon) { + if (!config.scratchpad_cross_monitor && c->mon != selmon) { continue; } - if (single_scratchpad && c->isnamedscratchpad && !c->isminimized) { + if (config.single_scratchpad && c->isnamedscratchpad && + !c->isminimized) { set_minimized(c); continue; } @@ -1662,7 +1663,8 @@ int32_t toggleoverview(const Arg *arg) { if (!selmon) return 0; - if (selmon->isoverview && ov_tab_mode && arg->i != 1 && selmon->sel) { + if (selmon->isoverview && config.ov_tab_mode && arg->i != 1 && + selmon->sel) { focusstack(&(Arg){.i = 1}); return 0; } diff --git a/src/ext-protocol/tearing.h b/src/ext-protocol/tearing.h index 8e02656..5a9851e 100644 --- a/src/ext-protocol/tearing.h +++ b/src/ext-protocol/tearing.h @@ -60,7 +60,7 @@ void handle_tearing_new_object(struct wl_listener *listener, void *data) { bool check_tearing_frame_allow(Monitor *m) { /* never allow tearing when disabled */ - if (!allow_tearing) { + if (!config.allow_tearing) { return false; } @@ -72,7 +72,7 @@ bool check_tearing_frame_allow(Monitor *m) { } /* allow tearing for any window when requested or forced */ - if (allow_tearing == TEARING_ENABLED) { + if (config.allow_tearing == TEARING_ENABLED) { if (c->force_tearing == STATE_UNSPECIFIED) { return c->tearing_hint; } else { @@ -87,7 +87,8 @@ bool check_tearing_frame_allow(Monitor *m) { if (c->force_tearing == STATE_UNSPECIFIED) { /* honor the tearing hint or the fullscreen-force preference */ - return c->tearing_hint || allow_tearing == TEARING_FULLSCREEN_ONLY; + return c->tearing_hint || + config.allow_tearing == TEARING_FULLSCREEN_ONLY; } /* honor tearing as requested by action */ diff --git a/src/fetch/client.h b/src/fetch/client.h index 11edb76..0b14284 100644 --- a/src/fetch/client.h +++ b/src/fetch/client.h @@ -12,7 +12,7 @@ bool check_hit_no_border(Client *c) { } } - if (no_border_when_single && c && c->mon && + if (config.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; @@ -39,7 +39,7 @@ Client *get_client_by_id_or_title(const char *arg_id, const char *arg_title) { const char *appid, *title; Client *c = NULL; wl_list_for_each(c, &clients, link) { - if (!scratchpad_cross_monitor && c->mon != selmon) { + if (!config.scratchpad_cross_monitor && c->mon != selmon) { continue; } @@ -167,7 +167,7 @@ Client *find_client_by_direction(Client *tc, const Arg *arg, bool findfloating, // 第一次遍历,计算客户端数量 wl_list_for_each(c, &clients, link) { if (c && (findfloating || !c->isfloating) && !c->isunglobal && - (focus_cross_monitor || c->mon == tc->mon) && + (config.focus_cross_monitor || c->mon == tc->mon) && (c->tags & c->mon->tagset[c->mon->seltags])) { last++; } @@ -188,7 +188,7 @@ Client *find_client_by_direction(Client *tc, const Arg *arg, bool findfloating, last = -1; wl_list_for_each(c, &clients, link) { if (c && (findfloating || !c->isfloating) && !c->isunglobal && - (focus_cross_monitor || c->mon == tc->mon) && + (config.focus_cross_monitor || c->mon == tc->mon) && (c->tags & c->mon->tagset[c->mon->seltags])) { last++; tempClients[last] = c; @@ -511,21 +511,21 @@ Client *get_next_stack_client(Client *c, bool reverse) { float *get_border_color(Client *c) { if (c->mon != selmon) { - return bordercolor; + return config.bordercolor; } else if (c->isurgent) { - return urgentcolor; + return config.urgentcolor; } else if (c->is_in_scratchpad && selmon && c == selmon->sel) { - return scratchpadcolor; + return config.scratchpadcolor; } else if (c->isglobal && selmon && c == selmon->sel) { - return globalcolor; + return config.globalcolor; } else if (c->isoverlay && selmon && c == selmon->sel) { - return overlaycolor; + return config.overlaycolor; } else if (c->ismaximizescreen && selmon && c == selmon->sel) { - return maximizescreencolor; + return config.maximizescreencolor; } else if (selmon && c == selmon->sel) { - return focuscolor; + return config.focuscolor; } else { - return bordercolor; + return config.bordercolor; } } diff --git a/src/layout/arrange.h b/src/layout/arrange.h index e164a0f..87217c7 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -309,7 +309,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_tile_refresh_interval) { + time - last_apply_drap_time > config.drag_tile_refresh_interval) { arrange(grabc->mon, false, false); last_apply_drap_time = time; } @@ -478,7 +478,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_tile_refresh_interval) { + time - last_apply_drap_time > config.drag_tile_refresh_interval) { arrange(grabc->mon, false, false); last_apply_drap_time = time; } @@ -494,7 +494,7 @@ void resize_tile_scroller(Client *grabc, bool isdrag, int32_t offsetx, Client *stack_head = get_scroll_stack_head(grabc); if (grabc && grabc->mon->visible_tiling_clients == 1 && - !scroller_ignore_proportion_single) + !config.scroller_ignore_proportion_single) return; if (!start_drag_window && isdrag) { @@ -670,7 +670,7 @@ void resize_tile_scroller(Client *grabc, bool isdrag, int32_t offsetx, } if (last_apply_drap_time == 0 || - time - last_apply_drap_time > drag_tile_refresh_interval) { + time - last_apply_drap_time > config.drag_tile_refresh_interval) { arrange(grabc->mon, false, false); last_apply_drap_time = time; } diff --git a/src/layout/horizontal.h b/src/layout/horizontal.h index 8140934..eaa7b5c 100644 --- a/src/layout/horizontal.h +++ b/src/layout/horizontal.h @@ -7,9 +7,9 @@ void grid(Monitor *m) { Client *c = NULL; n = 0; int32_t target_gappo = - enablegaps ? m->isoverview ? overviewgappo : gappoh : 0; + enablegaps ? m->isoverview ? config.overviewgappo : config.gappoh : 0; int32_t target_gappi = - enablegaps ? m->isoverview ? overviewgappi : gappih : 0; + enablegaps ? m->isoverview ? config.overviewgappi : config.gappih : 0; float single_width_ratio = m->isoverview ? 0.7 : 0.9; float single_height_ratio = m->isoverview ? 0.8 : 0.9; @@ -123,9 +123,12 @@ void deck(Monitor *m) { int32_t cur_gappoh = enablegaps ? m->gappoh : 0; int32_t cur_gappov = enablegaps ? m->gappov : 0; - cur_gappih = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappih; - cur_gappoh = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; - cur_gappov = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; + cur_gappih = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappih; + cur_gappoh = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; + cur_gappov = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; n = m->visible_tiling_clients; @@ -188,12 +191,15 @@ void horizontal_scroll_adjust_fullandmax(Client *c, int32_t cur_gappoh = enablegaps ? m->gappoh : 0; int32_t cur_gappov = enablegaps ? m->gappov : 0; - cur_gappih = - smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappih; - cur_gappoh = - smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappoh; - cur_gappov = - smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappov; + cur_gappih = config.smartgaps && m->visible_scroll_tiling_clients == 1 + ? 0 + : cur_gappih; + cur_gappoh = config.smartgaps && m->visible_scroll_tiling_clients == 1 + ? 0 + : cur_gappoh; + cur_gappov = config.smartgaps && m->visible_scroll_tiling_clients == 1 + ? 0 + : cur_gappov; if (c->isfullscreen) { target_geom->height = m->m.height; @@ -289,14 +295,18 @@ void scroller(Monitor *m) { 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; - cur_gappoh = - smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappoh; - cur_gappov = - smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappov; + cur_gappih = config.smartgaps && m->visible_scroll_tiling_clients == 1 + ? 0 + : cur_gappih; + cur_gappoh = config.smartgaps && m->visible_scroll_tiling_clients == 1 + ? 0 + : cur_gappoh; + cur_gappov = config.smartgaps && m->visible_scroll_tiling_clients == 1 + ? 0 + : cur_gappov; - int32_t max_client_width = m->w.width - 2 * scroller_structs - cur_gappih; + int32_t max_client_width = + m->w.width - 2 * config.scroller_structs - cur_gappih; n = m->visible_scroll_tiling_clients; @@ -320,13 +330,13 @@ void scroller(Monitor *m) { } } - if (n == 1 && !scroller_ignore_proportion_single && + if (n == 1 && !config.scroller_ignore_proportion_single && !tempClients[0]->isfullscreen && !tempClients[0]->ismaximizescreen) { c = tempClients[0]; single_proportion = c->scroller_proportion_single > 0.0f ? c->scroller_proportion_single - : scroller_default_proportion_single; + : config.scroller_default_proportion_single; target_geom.height = m->w.height - 2 * cur_gappov; target_geom.width = (m->w.width - 2 * cur_gappoh) * single_proportion; @@ -360,9 +370,9 @@ void scroller(Monitor *m) { for (i = 0; i < n; i++) { c = tempClients[i]; if (root_client == c) { - if (c->geom.x >= m->w.x + scroller_structs && + if (c->geom.x >= m->w.x + config.scroller_structs && c->geom.x + c->geom.width <= - m->w.x + m->w.width - scroller_structs) { + m->w.x + m->w.width - config.scroller_structs) { need_scroller = false; } else { need_scroller = true; @@ -373,7 +383,8 @@ void scroller(Monitor *m) { } bool need_apply_overspread = - scroller_prefer_overspread && m->visible_scroll_tiling_clients > 1 && + config.scroller_prefer_overspread && + m->visible_scroll_tiling_clients > 1 && (focus_client_index == 0 || focus_client_index == n - 1) && tempClients[focus_client_index]->scroller_proportion < 1.0f; @@ -403,16 +414,16 @@ void scroller(Monitor *m) { } bool need_apply_center = - scroller_focus_center || m->visible_scroll_tiling_clients == 1 || - (scroller_prefer_center && !need_apply_overspread && + config.scroller_focus_center || m->visible_scroll_tiling_clients == 1 || + (config.scroller_prefer_center && !need_apply_overspread && (!m->prevsel || (ISSCROLLTILED(m->prevsel) && (m->prevsel->scroller_proportion * max_client_width) + (tempClients[focus_client_index]->scroller_proportion * max_client_width) > - m->w.width - 2 * scroller_structs - cur_gappih))); + m->w.width - 2 * config.scroller_structs - cur_gappih))); - if (n == 1 && scroller_ignore_proportion_single) { + if (n == 1 && config.scroller_ignore_proportion_single) { need_scroller = true; } @@ -439,14 +450,14 @@ void scroller(Monitor *m) { target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2; } else if (need_apply_overspread) { if (over_overspread_to_left) { - target_geom.x = m->w.x + scroller_structs; + target_geom.x = m->w.x + config.scroller_structs; } else { target_geom.x = m->w.x + (m->w.width - tempClients[focus_client_index]->scroller_proportion * max_client_width - - scroller_structs); + config.scroller_structs); } } else { @@ -456,8 +467,8 @@ void scroller(Monitor *m) { tempClients[focus_client_index] ->scroller_proportion * max_client_width - - scroller_structs) - : m->w.x + scroller_structs; + config.scroller_structs) + : m->w.x + config.scroller_structs; } horizontal_check_scroller_root_inside_mon( tempClients[focus_client_index], &target_geom); @@ -522,10 +533,14 @@ void center_tile(Monitor *m) { int32_t cur_gappoh = enablegaps ? m->gappoh : 0; // 外部水平间隙 // 智能间隙处理 - cur_gappiv = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappiv; - cur_gappih = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappih; - cur_gappov = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; - cur_gappoh = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; + cur_gappiv = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappiv; + cur_gappih = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappih; + cur_gappov = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; + cur_gappoh = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; int32_t nmasters = m->pertag->nmasters[m->pertag->curtag]; mfact = fc->master_mfact_per > 0.0f ? fc->master_mfact_per @@ -538,7 +553,8 @@ void center_tile(Monitor *m) { tw = mw; // 判断是否需要主区域铺满 - int32_t should_overspread = center_master_overspread && (n <= nmasters); + int32_t should_overspread = + config.center_master_overspread && (n <= nmasters); int32_t master_surplus_height = (m->w.height - 2 * cur_gappov - cur_gappiv * ie * (master_num - 1)); @@ -564,7 +580,7 @@ void center_tile(Monitor *m) { mx = cur_gappoh + tw + cur_gappih * ie; } else if (n - nmasters == 1) { // 单个堆叠窗口的处理 - if (center_when_single_stack) { + if (config.center_when_single_stack) { // stack在右边,master居中,左边空着 tw = (m->w.width - mw) / 2 - cur_gappoh - cur_gappih * ie; mx = cur_gappoh + tw + cur_gappih * ie; // master居中 @@ -641,7 +657,7 @@ void center_tile(Monitor *m) { } int32_t stack_x; - if (center_when_single_stack) { + if (config.center_when_single_stack) { // 放在右侧(master居中时,stack在右边) stack_x = m->w.x + mx + mw + cur_gappih * ie; } else { @@ -745,10 +761,14 @@ void tile(Monitor *m) { int32_t cur_gappov = enablegaps ? m->gappov : 0; int32_t cur_gappoh = enablegaps ? m->gappoh : 0; - cur_gappiv = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappiv; - cur_gappih = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappih; - cur_gappov = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; - cur_gappoh = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; + cur_gappiv = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappiv; + cur_gappih = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappih; + cur_gappov = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; + cur_gappoh = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; wl_list_for_each(fc, &clients, link) { @@ -855,10 +875,14 @@ void right_tile(Monitor *m) { int32_t cur_gappov = enablegaps ? m->gappov : 0; int32_t cur_gappoh = enablegaps ? m->gappoh : 0; - cur_gappiv = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappiv; - cur_gappih = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappih; - cur_gappov = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; - cur_gappoh = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; + cur_gappiv = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappiv; + cur_gappih = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappih; + cur_gappov = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; + cur_gappoh = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; wl_list_for_each(fc, &clients, link) { @@ -953,8 +977,10 @@ monocle(Monitor *m) { int32_t cur_gappov = enablegaps ? m->gappov : 0; int32_t cur_gappoh = enablegaps ? m->gappoh : 0; - cur_gappoh = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; - cur_gappov = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; + cur_gappoh = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; + cur_gappov = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; wl_list_for_each(c, &clients, link) { if (!VISIBLEON(c, m) || !ISTILED(c)) diff --git a/src/layout/vertical.h b/src/layout/vertical.h index 4759e7a..3d8863a 100644 --- a/src/layout/vertical.h +++ b/src/layout/vertical.h @@ -19,10 +19,14 @@ void vertical_tile(Monitor *m) { int32_t cur_gapoh = enablegaps ? m->gappoh : 0; int32_t cur_gapov = enablegaps ? m->gappov : 0; - cur_gapih = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gapih; - cur_gapiv = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gapiv; - cur_gapoh = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gapoh; - cur_gapov = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gapov; + cur_gapih = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gapih; + cur_gapiv = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gapiv; + cur_gapoh = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gapoh; + cur_gapov = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gapov; wl_list_for_each(fc, &clients, link) { if (VISIBLEON(fc, m) && ISTILED(fc)) @@ -116,9 +120,12 @@ void vertical_deck(Monitor *m) { int32_t cur_gappoh = enablegaps ? m->gappoh : 0; int32_t cur_gappov = enablegaps ? m->gappov : 0; - cur_gappiv = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappiv; - cur_gappoh = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; - cur_gappov = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; + cur_gappiv = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappiv; + cur_gappoh = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; + cur_gappov = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; n = m->visible_tiling_clients; @@ -175,12 +182,15 @@ void vertical_scroll_adjust_fullandmax(Client *c, struct wlr_box *target_geom) { int32_t cur_gappov = enablegaps ? m->gappov : 0; int32_t cur_gappoh = enablegaps ? m->gappoh : 0; - cur_gappiv = - smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappiv; - cur_gappov = - smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappov; - cur_gappoh = - smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappoh; + cur_gappiv = config.smartgaps && m->visible_scroll_tiling_clients == 1 + ? 0 + : cur_gappiv; + cur_gappov = config.smartgaps && m->visible_scroll_tiling_clients == 1 + ? 0 + : cur_gappov; + cur_gappoh = config.smartgaps && m->visible_scroll_tiling_clients == 1 + ? 0 + : cur_gappoh; if (c->isfullscreen) { target_geom->width = m->m.width; @@ -276,14 +286,18 @@ void vertical_scroller(Monitor *m) { 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; - cur_gappov = - smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappov; - cur_gappoh = - smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappoh; + cur_gappiv = config.smartgaps && m->visible_scroll_tiling_clients == 1 + ? 0 + : cur_gappiv; + cur_gappov = config.smartgaps && m->visible_scroll_tiling_clients == 1 + ? 0 + : cur_gappov; + cur_gappoh = config.smartgaps && m->visible_scroll_tiling_clients == 1 + ? 0 + : cur_gappoh; - int32_t max_client_height = m->w.height - 2 * scroller_structs - cur_gappiv; + int32_t max_client_height = + m->w.height - 2 * config.scroller_structs - cur_gappiv; n = m->visible_scroll_tiling_clients; @@ -304,13 +318,13 @@ void vertical_scroller(Monitor *m) { } } - if (n == 1 && !scroller_ignore_proportion_single && + if (n == 1 && !config.scroller_ignore_proportion_single && !tempClients[0]->isfullscreen && !tempClients[0]->ismaximizescreen) { c = tempClients[0]; single_proportion = c->scroller_proportion_single > 0.0f ? c->scroller_proportion_single - : scroller_default_proportion_single; + : config.scroller_default_proportion_single; target_geom.width = m->w.width - 2 * cur_gappoh; target_geom.height = (m->w.height - 2 * cur_gappov) * single_proportion; @@ -344,9 +358,9 @@ void vertical_scroller(Monitor *m) { for (i = 0; i < n; i++) { c = tempClients[i]; if (root_client == c) { - if (c->geom.y >= m->w.y + scroller_structs && + if (c->geom.y >= m->w.y + config.scroller_structs && c->geom.y + c->geom.height <= - m->w.y + m->w.height - scroller_structs) { + m->w.y + m->w.height - config.scroller_structs) { need_scroller = false; } else { need_scroller = true; @@ -357,7 +371,8 @@ void vertical_scroller(Monitor *m) { } bool need_apply_overspread = - scroller_prefer_overspread && m->visible_scroll_tiling_clients > 1 && + config.scroller_prefer_overspread && + m->visible_scroll_tiling_clients > 1 && (focus_client_index == 0 || focus_client_index == n - 1) && tempClients[focus_client_index]->scroller_proportion < 1.0f; @@ -387,16 +402,16 @@ void vertical_scroller(Monitor *m) { } bool need_apply_center = - scroller_focus_center || m->visible_scroll_tiling_clients == 1 || - (scroller_prefer_center && !need_apply_overspread && + config.scroller_focus_center || m->visible_scroll_tiling_clients == 1 || + (config.scroller_prefer_center && !need_apply_overspread && (!m->prevsel || (ISSCROLLTILED(m->prevsel) && (m->prevsel->scroller_proportion * max_client_height) + (tempClients[focus_client_index]->scroller_proportion * max_client_height) > - m->w.height - 2 * scroller_structs - cur_gappiv))); + m->w.height - 2 * config.scroller_structs - cur_gappiv))); - if (n == 1 && scroller_ignore_proportion_single) { + if (n == 1 && config.scroller_ignore_proportion_single) { need_scroller = true; } @@ -426,14 +441,14 @@ void vertical_scroller(Monitor *m) { target_geom.y = m->w.y + (m->w.height - target_geom.height) / 2; } else if (need_apply_overspread) { if (over_overspread_to_up) { - target_geom.y = m->w.y + scroller_structs; + target_geom.y = m->w.y + config.scroller_structs; } else { target_geom.y = m->w.y + (m->w.height - tempClients[focus_client_index]->scroller_proportion * max_client_height - - scroller_structs); + config.scroller_structs); } } else { target_geom.y = root_client->geom.y > m->w.y + (m->w.height) / 2 @@ -441,8 +456,8 @@ void vertical_scroller(Monitor *m) { tempClients[focus_client_index] ->scroller_proportion * max_client_height - - scroller_structs) - : m->w.y + scroller_structs; + config.scroller_structs) + : m->w.y + config.scroller_structs; } vertical_check_scroller_root_inside_mon(tempClients[focus_client_index], &target_geom); @@ -486,9 +501,9 @@ void vertical_grid(Monitor *m) { int32_t rows, cols, overrows; Client *c = NULL; int32_t target_gappo = - enablegaps ? m->isoverview ? overviewgappo : gappov : 0; + enablegaps ? m->isoverview ? config.overviewgappo : config.gappov : 0; int32_t target_gappi = - enablegaps ? m->isoverview ? overviewgappi : gappiv : 0; + enablegaps ? m->isoverview ? config.overviewgappi : config.gappiv : 0; float single_width_ratio = m->isoverview ? 0.7 : 0.9; float single_height_ratio = m->isoverview ? 0.8 : 0.9; diff --git a/src/mango.c b/src/mango.c index bdafcc5..aedf1d6 100644 --- a/src/mango.c +++ b/src/mango.c @@ -906,6 +906,7 @@ static struct wl_event_source *keep_idle_inhibit_source; static bool cursor_hidden = false; static bool tag_combo = false; static const char *cli_config_path = NULL; +static bool cli_debug_log = false; static KeyMode keymode = { .mode = {'d', 'e', 'f', 'a', 'u', 'l', 't', '\0'}, .isdefault = true, @@ -933,6 +934,8 @@ struct Pertag { *ltidxs[LENGTH(tags) + 1]; /* matrix of tags and layouts indexes */ }; +#include "config/parse_config.h" + static struct wl_signal mango_print_status; static struct wl_listener print_status_listener = {.notify = @@ -995,7 +998,6 @@ static struct wl_event_source *sync_keymap; #include "animation/common.h" #include "animation/layer.h" #include "animation/tag.h" -#include "config/parse_config.h" #include "dispatch/bind_define.h" #include "ext-protocol/all.h" #include "fetch/fetch.h" @@ -1060,7 +1062,7 @@ void show_scratchpad(Client *c) { if (c->isfullscreen || c->ismaximizescreen) { c->isfullscreen = 0; // 清除窗口全屏标志 c->ismaximizescreen = 0; - c->bw = c->isnoborder ? 0 : borderpx; + c->bw = c->isnoborder ? 0 : config.borderpx; } /* return if fullscreen */ @@ -1068,10 +1070,10 @@ void show_scratchpad(Client *c) { setfloating(c, 1); c->geom.width = c->iscustomsize ? c->float_geom.width - : c->mon->w.width * scratchpad_width_ratio; - c->geom.height = c->iscustomsize - ? c->float_geom.height - : c->mon->w.height * scratchpad_height_ratio; + : c->mon->w.width * config.scratchpad_width_ratio; + c->geom.height = + c->iscustomsize ? c->float_geom.height + : c->mon->w.height * config.scratchpad_height_ratio; // 重新计算居中的坐标 c->float_geom = c->geom = c->animainit_geom = c->animation.current = setclient_coordinate_center(c, c->mon, c->geom, 0, 0); @@ -1139,7 +1141,7 @@ void swallow(Client *c, Client *w) { bool switch_scratchpad_client_state(Client *c) { - if (scratchpad_cross_monitor && selmon && c->mon != selmon && + if (config.scratchpad_cross_monitor && selmon && c->mon != selmon && c->is_in_scratchpad) { // 保存原始monitor用于尺寸计算 Monitor *oldmon = c->mon; @@ -1193,12 +1195,12 @@ void apply_named_scratchpad(Client *target_client) { Client *c = NULL; wl_list_for_each(c, &clients, link) { - if (!scratchpad_cross_monitor && c->mon != selmon) { + if (!config.scratchpad_cross_monitor && c->mon != selmon) { continue; } - if (single_scratchpad && c->is_in_scratchpad && c->is_scratchpad_show && - c != target_client) { + if (config.single_scratchpad && c->is_in_scratchpad && + c->is_scratchpad_show && c != target_client) { set_minimized(c); } } @@ -1259,30 +1261,30 @@ void toggle_hotarea(int32_t x_root, int32_t y_root) { // 根据热角位置计算不同的热区坐标 unsigned hx, hy; - switch (hotarea_corner) { + switch (config.hotarea_corner) { case BOTTOM_RIGHT: // 右下角 - hx = selmon->m.x + selmon->m.width - hotarea_size; - hy = selmon->m.y + selmon->m.height - hotarea_size; + hx = selmon->m.x + selmon->m.width - config.hotarea_size; + hy = selmon->m.y + selmon->m.height - config.hotarea_size; break; case TOP_LEFT: // 左上角 - hx = selmon->m.x + hotarea_size; - hy = selmon->m.y + hotarea_size; + hx = selmon->m.x + config.hotarea_size; + hy = selmon->m.y + config.hotarea_size; break; case TOP_RIGHT: // 右上角 - hx = selmon->m.x + selmon->m.width - hotarea_size; - hy = selmon->m.y + hotarea_size; + hx = selmon->m.x + selmon->m.width - config.hotarea_size; + hy = selmon->m.y + config.hotarea_size; break; case BOTTOM_LEFT: // 左下角(默认) default: - hx = selmon->m.x + hotarea_size; - hy = selmon->m.y + selmon->m.height - hotarea_size; + hx = selmon->m.x + config.hotarea_size; + hy = selmon->m.y + selmon->m.height - config.hotarea_size; break; } // 判断鼠标是否在热区内 int in_hotarea = 0; - switch (hotarea_corner) { + switch (config.hotarea_corner) { case BOTTOM_RIGHT: // 右下角 in_hotarea = (y_root > hy && x_root > hx && x_root <= (selmon->m.x + selmon->m.width) && @@ -1304,10 +1306,11 @@ void toggle_hotarea(int32_t x_root, int32_t y_root) { break; } - if (enable_hotarea == 1 && selmon->is_in_hotarea == 0 && in_hotarea) { + if (config.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 && + } else if (config.enable_hotarea == 1 && selmon->is_in_hotarea == 1 && !in_hotarea) { selmon->is_in_hotarea = 0; } @@ -1600,7 +1603,7 @@ void apply_window_snap(Client *c) { int32_t snap_up_mon = 0, snap_down_mon = 0, snap_left_mon = 0, snap_right_mon = 0; - uint32_t cbw = !render_border || c->fake_no_border ? borderpx : 0; + uint32_t cbw = !render_border || c->fake_no_border ? config.borderpx : 0; uint32_t tcbw; uint32_t cx, cy, cw, ch, tcx, tcy, tcw, tch; cx = c->geom.x + cbw; @@ -1612,14 +1615,14 @@ void apply_window_snap(Client *c) { if (!c || !c->mon || !client_surface(c)->mapped || c->iskilling) return; - if (!c->isfloating || !enable_floating_snap) + if (!c->isfloating || !config.enable_floating_snap) return; wl_list_for_each(tc, &clients, link) { if (tc && tc->isfloating && !tc->iskilling && client_surface(tc)->mapped && VISIBLEON(tc, c->mon)) { - tcbw = !render_border || tc->fake_no_border ? borderpx : 0; + tcbw = !render_border || tc->fake_no_border ? config.borderpx : 0; tcx = tc->geom.x + tcbw; tcy = tc->geom.y + tcbw; tcw = tc->geom.width - 2 * tcbw; @@ -1673,19 +1676,19 @@ void apply_window_snap(Client *c) { if (snap_right_screen >= 0 && snap_right_screen < snap_right) snap_right = snap_right_screen; - if (snap_left < snap_right && snap_left < snap_distance) { + if (snap_left < snap_right && snap_left < config.snap_distance) { c->geom.x = c->geom.x - snap_left; } - if (snap_right <= snap_left && snap_right < snap_distance) { + if (snap_right <= snap_left && snap_right < config.snap_distance) { c->geom.x = c->geom.x + snap_right; } - if (snap_up < snap_down && snap_up < snap_distance) { + if (snap_up < snap_down && snap_up < config.snap_distance) { c->geom.y = c->geom.y - snap_up; } - if (snap_down <= snap_up && snap_down < snap_distance) { + if (snap_down <= snap_up && snap_down < config.snap_distance) { c->geom.y = c->geom.y + snap_down; } @@ -1815,7 +1818,8 @@ axisnotify(struct wl_listener *listener, void *data) { a = &config.axis_bindings[ji]; if (CLEANMASK(mods) == CLEANMASK(a->mod) && // 按键一致 adir == a->dir && a->func) { // 滚轮方向判断一致且处理函数存在 - if (event->time_msec - axis_apply_time > axis_bind_apply_timeout || + if (event->time_msec - axis_apply_time > + config.axis_bind_apply_timeout || axis_apply_dir * event->delta < 0) { a->func(&a->arg); axis_apply_time = event->time_msec; @@ -1835,9 +1839,10 @@ axisnotify(struct wl_listener *listener, void *data) { /* Notify the client with pointer focus of the axis event. */ wlr_seat_pointer_notify_axis( seat, // 滚轮事件发送给客户端也就是窗口 - event->time_msec, event->orientation, event->delta * axis_scroll_factor, - roundf(event->delta_discrete * axis_scroll_factor), event->source, - event->relative_direction); + event->time_msec, event->orientation, + event->delta * config.axis_scroll_factor, + roundf(event->delta_discrete * config.axis_scroll_factor), + event->source, event->relative_direction); } int32_t ongesture(struct wlr_pointer_swipe_end_event *event) { @@ -1855,7 +1860,8 @@ int32_t ongesture(struct wlr_pointer_swipe_end_event *event) { } // Require absolute distance movement beyond a small thresh-hold - if (adx * adx + ady * ady < swipe_min_threshold * swipe_min_threshold) { + if (adx * adx + ady * ady < + config.swipe_min_threshold * config.swipe_min_threshold) { return handled; } @@ -1992,7 +1998,7 @@ void place_drag_tile_client(Client *c) { bool check_trackpad_disabled(struct wlr_pointer *pointer) { struct libinput_device *device; - if (!disable_trackpad) + if (!config.disable_trackpad) return false; if (wlr_input_device_is_libinput(&pointer->base) && @@ -2111,7 +2117,7 @@ buttonpress(struct wl_listener *listener, void *data) { grabc = NULL; start_drag_window = false; last_apply_drap_time = 0; - if (tmpc->drag_to_tile && drag_tile_to_tile) { + if (tmpc->drag_to_tile && config.drag_tile_to_tile) { place_drag_tile_client(tmpc); } else { apply_window_snap(tmpc); @@ -2144,7 +2150,7 @@ void checkidleinhibitor(struct wlr_surface *exclude) { toplevel_from_wlr_surface(inhibitor->surface, &c, NULL); - if (idleinhibit_ignore_visible) { + if (config.idleinhibit_ignore_visible) { inhibited = 1; break; } @@ -2342,7 +2348,7 @@ static void iter_layer_scene_buffers(struct wlr_scene_buffer *buffer, wlr_scene_buffer_set_backdrop_blur(buffer, true); wlr_scene_buffer_set_backdrop_blur_ignore_transparent(buffer, true); - if (blur_optimized) { + if (config.blur_optimized) { wlr_scene_buffer_set_backdrop_blur_optimized(buffer, true); } else { wlr_scene_buffer_set_backdrop_blur_optimized(buffer, false); @@ -2350,7 +2356,7 @@ static void iter_layer_scene_buffers(struct wlr_scene_buffer *buffer, } void layer_flush_blur_background(LayerSurface *l) { - if (!blur) + if (!config.blur) return; // 如果背景层发生变化,标记优化的模糊背景缓存需要更新 @@ -2406,15 +2412,16 @@ void maplayersurfacenotify(struct wl_listener *listener, void *data) { if (layer_surface->current.exclusive_zone == 0 && layer_surface->current.layer != ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM && layer_surface->current.layer != ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND) { - l->shadow = wlr_scene_shadow_create(l->scene, 0, 0, border_radius, - shadows_blur, shadowscolor); + l->shadow = + wlr_scene_shadow_create(l->scene, 0, 0, config.border_radius, + config.shadows_blur, config.shadowscolor); wlr_scene_node_lower_to_bottom(&l->shadow->node); wlr_scene_node_set_enabled(&l->shadow->node, true); } // 初始化动画 - if (animations && layer_animations && !l->noanim) { - l->animation.duration = animation_duration_open; + if (config.animations && config.layer_animations && !l->noanim) { + l->animation.duration = config.animation_duration_open; l->animation.action = OPEN; layer_set_pending_state(l); } @@ -2460,7 +2467,8 @@ void commitlayersurfacenotify(struct wl_listener *listener, void *data) { get_layer_target_geometry(l, &box); - if (animations && layer_animations && !l->noanim && l->mapped && + if (config.animations && config.layer_animations && !l->noanim && + l->mapped && layer_surface->current.layer != ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM && layer_surface->current.layer != ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND && !wlr_box_equal(&box, &l->geom)) { @@ -2470,13 +2478,12 @@ void commitlayersurfacenotify(struct wl_listener *listener, void *data) { l->geom.width = box.width; l->geom.height = box.height; l->animation.action = MOVE; - l->animation.duration = animation_duration_move; + l->animation.duration = config.animation_duration_move; l->need_output_flush = true; layer_set_pending_state(l); } - if (blur && blur_layer) { - // 设置非背景layer模糊 + if (config.blur && config.blur_layer) { if (!l->noblur && layer_surface->current.layer != ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM && @@ -2754,22 +2761,21 @@ KeyboardGroup *createkeyboardgroup(void) { group->wlr_group = wlr_keyboard_group_create(); group->wlr_group->data = group; - /* Prepare an XKB keymap and assign it to the keyboard group. */ context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - if (!(keymap = xkb_keymap_new_from_names(context, &xkb_rules, + if (!(keymap = xkb_keymap_new_from_names(context, &config.xkb_rules, XKB_KEYMAP_COMPILE_NO_FLAGS))) die("failed to compile keymap"); wlr_keyboard_set_keymap(&group->wlr_group->keyboard, keymap); - if (numlockon) { + if (config.numlockon) { xkb_mod_index_t mod_index = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_NUM); if (mod_index != XKB_MOD_INVALID) locked_mods |= (uint32_t)1 << mod_index; } - if (capslock) { + if (config.capslock) { xkb_mod_index_t mod_index = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_CAPS); if (mod_index != XKB_MOD_INVALID) @@ -2783,8 +2789,8 @@ KeyboardGroup *createkeyboardgroup(void) { xkb_keymap_unref(keymap); xkb_context_unref(context); - wlr_keyboard_set_repeat_info(&group->wlr_group->keyboard, repeat_rate, - repeat_delay); + wlr_keyboard_set_repeat_info(&group->wlr_group->keyboard, + config.repeat_rate, config.repeat_delay); /* Set up listeners for keyboard events */ LISTEN(&group->wlr_group->keyboard.events.key, &group->key, keypress); @@ -2974,11 +2980,10 @@ void createmon(struct wl_listener *listener, void *data) { for (i = 0; i < LENGTH(m->layers); i++) wl_list_init(&m->layers[i]); - /* Initialize monitor state using configured rules */ - m->gappih = gappih; - m->gappiv = gappiv; - m->gappoh = gappoh; - m->gappov = gappov; + m->gappih = config.gappih; + m->gappiv = config.gappiv; + m->gappoh = config.gappoh; + m->gappov = config.gappov; m->isoverview = 0; m->sel = NULL; m->is_in_hotarea = 0; @@ -3040,8 +3045,8 @@ void createmon(struct wl_listener *listener, void *data) { } for (i = 0; i <= LENGTH(tags); i++) { - m->pertag->nmasters[i] = default_nmaster; - m->pertag->mfacts[i] = default_mfact; + m->pertag->nmasters[i] = config.default_nmaster; + m->pertag->mfacts[i] = config.default_mfact; m->pertag->ltidxs[i] = &layouts[0]; } @@ -3069,12 +3074,11 @@ void createmon(struct wl_listener *listener, void *data) { else wlr_output_layout_add(output_layout, wlr_output, m->m.x, m->m.y); - if (blur) { + if (config.blur) { m->blur = wlr_scene_optimized_blur_create(&scene->tree, 0, 0); wlr_scene_node_set_position(&m->blur->node, m->m.x, m->m.y); wlr_scene_node_reparent(&m->blur->node, layers[LyrBlur]); wlr_scene_optimized_blur_set_size(m->blur, m->m.width, m->m.height); - // wlr_scene_node_set_enabled(&m->blur->node, 1); } m->ext_group = wlr_ext_workspace_group_handle_v1_create( ext_manager, EXT_WORKSPACE_ENABLE_CAPS); @@ -3100,7 +3104,7 @@ createnotify(struct wl_listener *listener, void *data) { /* Allocate a Client for this surface */ c = toplevel->base->data = ecalloc(1, sizeof(*c)); c->surface.xdg = toplevel->base; - c->bw = borderpx; + c->bw = config.borderpx; LISTEN(&toplevel->base->surface->events.commit, &c->commit, commitnotify); LISTEN(&toplevel->base->surface->events.map, &c->map, mapnotify); @@ -3146,44 +3150,49 @@ void destroyinputdevice(struct wl_listener *listener, void *data) { void configure_pointer(struct libinput_device *device) { if (libinput_device_config_tap_get_finger_count(device)) { - libinput_device_config_tap_set_enabled(device, tap_to_click); - libinput_device_config_tap_set_drag_enabled(device, tap_and_drag); - libinput_device_config_tap_set_drag_lock_enabled(device, drag_lock); - libinput_device_config_tap_set_button_map(device, button_map); + libinput_device_config_tap_set_enabled(device, config.tap_to_click); + libinput_device_config_tap_set_drag_enabled(device, + config.tap_and_drag); + libinput_device_config_tap_set_drag_lock_enabled(device, + config.drag_lock); + libinput_device_config_tap_set_button_map(device, config.button_map); libinput_device_config_scroll_set_natural_scroll_enabled( - device, trackpad_natural_scrolling); + device, config.trackpad_natural_scrolling); } else { libinput_device_config_scroll_set_natural_scroll_enabled( - device, mouse_natural_scrolling); + device, config.mouse_natural_scrolling); } if (libinput_device_config_dwt_is_available(device)) - libinput_device_config_dwt_set_enabled(device, disable_while_typing); + libinput_device_config_dwt_set_enabled(device, + config.disable_while_typing); if (libinput_device_config_left_handed_is_available(device)) - libinput_device_config_left_handed_set(device, left_handed); + libinput_device_config_left_handed_set(device, config.left_handed); if (libinput_device_config_middle_emulation_is_available(device)) libinput_device_config_middle_emulation_set_enabled( - device, middle_button_emulation); + device, config.middle_button_emulation); if (libinput_device_config_scroll_get_methods(device) != LIBINPUT_CONFIG_SCROLL_NO_SCROLL) - libinput_device_config_scroll_set_method(device, scroll_method); + libinput_device_config_scroll_set_method(device, config.scroll_method); if (libinput_device_config_scroll_get_methods(device) == LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN) - libinput_device_config_scroll_set_button(device, scroll_button); + libinput_device_config_scroll_set_button(device, config.scroll_button); if (libinput_device_config_click_get_methods(device) != LIBINPUT_CONFIG_CLICK_METHOD_NONE) - libinput_device_config_click_set_method(device, click_method); + libinput_device_config_click_set_method(device, config.click_method); if (libinput_device_config_send_events_get_modes(device)) - libinput_device_config_send_events_set_mode(device, send_events_mode); + libinput_device_config_send_events_set_mode(device, + config.send_events_mode); - 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); + if (config.accel_profile && + libinput_device_config_accel_is_available(device)) { + libinput_device_config_accel_set_profile(device, config.accel_profile); + libinput_device_config_accel_set_speed(device, config.accel_speed); } else { // profile cannot be directly applied to 0, need to set to 1 first libinput_device_config_accel_set_profile(device, 1); @@ -3835,7 +3844,7 @@ void keypress(struct wl_listener *listener, void *data) { wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); // ov tab mode detect moe key release - if (ov_tab_mode && !locked && group == kb_group && + if (config.ov_tab_mode && !locked && group == kb_group && event->state == WL_KEYBOARD_KEY_STATE_RELEASED && (keycode == 133 || keycode == 37 || keycode == 64 || keycode == 50 || keycode == 134 || keycode == 105 || keycode == 108 || keycode == 62) && @@ -3919,7 +3928,7 @@ void pending_kill_client(Client *c) { void locksession(struct wl_listener *listener, void *data) { struct wlr_session_lock_v1 *session_lock = data; SessionLock *lock; - if (!allow_lock_transparent) { + if (!config.allow_lock_transparent) { wlr_scene_node_set_enabled(&locked_bg->node, true); } if (cur_lock) { @@ -3956,10 +3965,10 @@ static void iter_xdg_scene_buffers(struct wlr_scene_buffer *buffer, int32_t sx, if (wlr_subsurface_try_from_wlr_surface(surface) != NULL) return; - if (blur && c && !c->noblur) { + if (config.blur && c && !c->noblur) { wlr_scene_buffer_set_backdrop_blur(buffer, true); wlr_scene_buffer_set_backdrop_blur_ignore_transparent(buffer, false); - if (blur_optimized) { + if (config.blur_optimized) { wlr_scene_buffer_set_backdrop_blur_optimized(buffer, true); } else { wlr_scene_buffer_set_backdrop_blur_optimized(buffer, false); @@ -4006,13 +4015,13 @@ void init_client_properties(Client *c) { c->is_restoring_from_ov = 0; c->isurgent = 0; c->need_output_flush = 0; - c->scroller_proportion = scroller_default_proportion; + c->scroller_proportion = config.scroller_default_proportion; c->is_pending_open_animation = true; c->drag_to_tile = false; c->scratchpad_switching_mon = false; c->fake_no_border = false; - c->focused_opacity = focused_opacity; - c->unfocused_opacity = unfocused_opacity; + c->focused_opacity = config.focused_opacity; + c->unfocused_opacity = config.unfocused_opacity; c->nofocus = 0; c->nofadein = 0; c->nofadeout = 0; @@ -4047,9 +4056,9 @@ void init_client_properties(Client *c) { c->next_in_stack = NULL; c->prev_in_stack = NULL; memset(c->oldmonname, 0, sizeof(c->oldmonname)); - memcpy(c->opacity_animation.initial_border_color, bordercolor, + memcpy(c->opacity_animation.initial_border_color, config.bordercolor, sizeof(c->opacity_animation.initial_border_color)); - memcpy(c->opacity_animation.current_border_color, bordercolor, + memcpy(c->opacity_animation.current_border_color, config.bordercolor, sizeof(c->opacity_animation.current_border_color)); c->opacity_animation.initial_opacity = c->unfocused_opacity; c->opacity_animation.current_opacity = c->unfocused_opacity; @@ -4079,7 +4088,7 @@ mapnotify(struct wl_listener *listener, void *data) { c->bw = 0; c->isnoborder = 1; } else { - c->bw = borderpx; + c->bw = config.borderpx; } if (client_should_global(c)) { @@ -4111,21 +4120,22 @@ mapnotify(struct wl_listener *listener, void *data) { } // extra node - c->border = wlr_scene_rect_create(c->scene, 0, 0, - c->isurgent ? urgentcolor : bordercolor); + c->border = wlr_scene_rect_create( + c->scene, 0, 0, c->isurgent ? config.urgentcolor : config.bordercolor); wlr_scene_node_lower_to_bottom(&c->border->node); wlr_scene_node_set_position(&c->border->node, 0, 0); - wlr_scene_rect_set_corner_radius(c->border, border_radius, - border_radius_location_default); + wlr_scene_rect_set_corner_radius(c->border, config.border_radius, + config.border_radius_location_default); wlr_scene_node_set_enabled(&c->border->node, true); - c->shadow = wlr_scene_shadow_create(c->scene, 0, 0, border_radius, - shadows_blur, shadowscolor); + c->shadow = + wlr_scene_shadow_create(c->scene, 0, 0, config.border_radius, + config.shadows_blur, config.shadowscolor); wlr_scene_node_lower_to_bottom(&c->shadow->node); wlr_scene_node_set_enabled(&c->shadow->node, true); - if (new_is_master && selmon && !is_scroller_layout(selmon)) + if (config.new_is_master && selmon && !is_scroller_layout(selmon)) // tile at the top wl_list_insert(&clients, &c->link); // 新窗口是master,头部入栈 else if (selmon && is_scroller_layout(selmon) && @@ -4338,7 +4348,7 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); /* Update selmon (even while dragging a window) */ - if (sloppyfocus) + if (config.sloppyfocus) selmon = xytomon(cursor->x, cursor->y); } @@ -4374,7 +4384,8 @@ 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_floating_refresh_interval) { + time - last_apply_drap_time > + config.drag_floating_refresh_interval) { resize_floating_window(grabc); last_apply_drap_time = time; } @@ -4400,7 +4411,7 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, should_lock = true; } - if (!(!edge_scroller_pointer_focus && c && c->mon && + if (!(!config.edge_scroller_pointer_focus && c && c->mon && is_scroller_layout(c->mon) && !INSIDEMON(c))) pointerfocus(c, surface, sx, sy, time); @@ -4506,7 +4517,7 @@ void pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, uint32_t time) { struct timespec now; - if (sloppyfocus && !start_drag_window && c && time && c->scene && + if (config.sloppyfocus && !start_drag_window && c && time && c->scene && c->scene->node.enabled && !c->animation.tagining && (surface != seat->pointer_state.focused_surface) && !client_is_unmanaged(c) && VISIBLEON(c, c->mon)) @@ -4632,7 +4643,7 @@ void rendermon(struct wl_listener *listener, void *data) { // 绘制客户端 wl_list_for_each(c, &clients, link) { need_more_frames = client_draw_frame(c) || need_more_frames; - if (!animations && !grabc && c->configure_serial && + if (!config.animations && !grabc && c->configure_serial && client_is_rendered_on_mon(c, m)) { monitor_check_skip_frame_timeout(m); goto skip; @@ -4644,7 +4655,7 @@ void rendermon(struct wl_listener *listener, void *data) { } // 只有在需要帧时才构建和提交状态 - if (allow_tearing && frame_allow_tearing) { + if (config.allow_tearing && frame_allow_tearing) { apply_tear_state(m); } else { wlr_scene_output_commit(m->scene_output, NULL); @@ -4720,7 +4731,7 @@ void exchange_two_client(Client *c1, Client *c2) { float stack_proportion = 0.0f; if (c1 == NULL || c2 == NULL || - (!exchange_cross_monitor && c1->mon != c2->mon)) { + (!config.exchange_cross_monitor && c1->mon != c2->mon)) { return; } @@ -4822,7 +4833,7 @@ void exchange_two_client(Client *c1, Client *c2) { } // 处理跨监视器交换 - if (exchange_cross_monitor) { + if (config.exchange_cross_monitor) { tmp_mon = c2->mon; tmp_tags = c2->tags; setmon(c2, c1->mon, c1->tags, false); @@ -5002,7 +5013,7 @@ setfloating(Client *c, int32_t floating) { if (c->isfullscreen || c->ismaximizescreen) { c->isfullscreen = 0; // 清除窗口全屏标志 c->ismaximizescreen = 0; - c->bw = c->isnoborder ? 0 : borderpx; + c->bw = c->isnoborder ? 0 : config.borderpx; } exit_scroller_stack(c); @@ -5016,11 +5027,13 @@ setfloating(Client *c, int32_t floating) { // restore to the memeroy geom if (c->float_geom.width > 0 && c->float_geom.height > 0) { - if (c->mon && c->float_geom.width >= c->mon->w.width - gappoh) { + if (c->mon && + c->float_geom.width >= c->mon->w.width - config.gappoh) { c->float_geom.width = c->mon->w.width * 0.9; window_size_outofrange = true; } - if (c->mon && c->float_geom.height >= c->mon->w.height - gappov) { + if (c->mon && + c->float_geom.height >= c->mon->w.height - config.gappov) { c->float_geom.height = c->mon->w.height * 0.9; window_size_outofrange = true; } @@ -5082,10 +5095,10 @@ setfloating(Client *c, int32_t floating) { } void reset_maximizescreen_size(Client *c) { - c->geom.x = c->mon->w.x + gappoh; - c->geom.y = c->mon->w.y + gappov; - c->geom.width = c->mon->w.width - 2 * gappoh; - c->geom.height = c->mon->w.height - 2 * gappov; + c->geom.x = c->mon->w.x + config.gappoh; + c->geom.y = c->mon->w.y + config.gappov; + c->geom.width = c->mon->w.width - 2 * config.gappoh; + c->geom.height = c->mon->w.height - 2 * config.gappov; resize(c, c->geom, 0); } @@ -5131,16 +5144,16 @@ void setmaximizescreen(Client *c, int32_t maximizescreen) { if (c->isfloating) c->float_geom = c->geom; - maximizescreen_box.x = c->mon->w.x + gappoh; - maximizescreen_box.y = c->mon->w.y + gappov; - maximizescreen_box.width = c->mon->w.width - 2 * gappoh; - maximizescreen_box.height = c->mon->w.height - 2 * gappov; - wlr_scene_node_raise_to_top(&c->scene->node); // 将视图提升到顶层 + maximizescreen_box.x = c->mon->w.x + config.gappoh; + maximizescreen_box.y = c->mon->w.y + config.gappov; + maximizescreen_box.width = c->mon->w.width - 2 * config.gappoh; + maximizescreen_box.height = c->mon->w.height - 2 * config.gappov; + wlr_scene_node_raise_to_top(&c->scene->node); if (!is_scroller_layout(c->mon) || c->isfloating) resize(c, maximizescreen_box, 0); c->ismaximizescreen = 1; } else { - c->bw = c->isnoborder ? 0 : borderpx; + c->bw = c->isnoborder ? 0 : config.borderpx; c->ismaximizescreen = 0; if (c->isfloating) setfloating(c, 1); @@ -5207,7 +5220,7 @@ void setfullscreen(Client *c, int32_t fullscreen) // 用自定义全屏代理自 resize(c, c->mon->m, 1); c->isfullscreen = 1; } else { - c->bw = c->isnoborder ? 0 : borderpx; + c->bw = c->isnoborder ? 0 : config.borderpx; c->isfullscreen = 0; if (c->isfloating) setfloating(c, 1); @@ -5270,9 +5283,8 @@ void reset_keyboard_layout(void) { return; } - // 现在安全地创建真正的keymap struct xkb_keymap *new_keymap = xkb_keymap_new_from_names( - context, &xkb_rules, XKB_KEYMAP_COMPILE_NO_FLAGS); + context, &config.xkb_rules, XKB_KEYMAP_COMPILE_NO_FLAGS); if (!new_keymap) { // 理论上这里不应该失败,因为前面已经验证过了 wlr_log(WLR_ERROR, @@ -5455,6 +5467,9 @@ void setup(void) { setenv("XDG_CURRENT_DESKTOP", "mango", 1); parse_config(); + if (cli_debug_log) { + config.log_level = WLR_DEBUG; + } init_baked_points(); int32_t drm_fd, i, sig[] = {SIGCHLD, SIGINT, SIGTERM, SIGPIPE}; @@ -5464,7 +5479,7 @@ void setup(void) { for (i = 0; i < LENGTH(sig); i++) sigaction(sig[i], &sa, NULL); - wlr_log_init(log_level, NULL); + wlr_log_init(config.log_level, NULL); /* The Wayland display is managed by libwayland. It handles accepting * clients from the Unix socket, manging Wayland globals, and so on. */ @@ -5490,7 +5505,7 @@ void setup(void) { /* Initialize the scene graph used to lay out windows */ scene = wlr_scene_create(); - root_bg = wlr_scene_rect_create(&scene->tree, 0, 0, rootcolor); + root_bg = wlr_scene_rect_create(&scene->tree, 0, 0, config.rootcolor); for (i = 0; i < NUM_LAYERS; i++) layers[i] = wlr_scene_tree_create(&scene->tree); drag_icon = wlr_scene_tree_create(&scene->tree); @@ -5515,7 +5530,7 @@ void setup(void) { scene, wlr_linux_dmabuf_v1_create_with_renderer(dpy, 4, drw)); } - if (syncobj_enable && (drm_fd = wlr_renderer_get_drm_fd(drw)) >= 0 && + if (config.syncobj_enable && (drm_fd = wlr_renderer_get_drm_fd(drw)) >= 0 && drw->features.timeline && backend->features.timeline) wlr_linux_drm_syncobj_manager_v1_create(dpy, 1, drm_fd); @@ -5635,7 +5650,8 @@ void setup(void) { * (necessary for HiDPI support). Scaled cursors will be loaded with * each output. */ // cursor_mgr = wlr_xcursor_manager_create(cursor_theme, 24); - cursor_mgr = wlr_xcursor_manager_create(config.cursor_theme, cursor_size); + cursor_mgr = + wlr_xcursor_manager_create(config.cursor_theme, config.cursor_size); /* * wlr_cursor *only* displays an image on screen. It does not move @@ -5709,10 +5725,10 @@ void setup(void) { wl_signal_add(&output_mgr->events.apply, &output_mgr_apply); wl_signal_add(&output_mgr->events.test, &output_mgr_test); - // blur - wlr_scene_set_blur_data(scene, blur_params.num_passes, blur_params.radius, - blur_params.noise, blur_params.brightness, - blur_params.contrast, blur_params.saturation); + wlr_scene_set_blur_data( + scene, config.blur_params.num_passes, config.blur_params.radius, + config.blur_params.noise, config.blur_params.brightness, + config.blur_params.contrast, config.blur_params.saturation); /* create text_input-, and input_method-protocol relevant globals */ input_method_manager = wlr_input_method_manager_v2_create(dpy); @@ -5745,7 +5761,8 @@ void setup(void) { * Initialise the XWayland X server. * It will be started when the first X client is started. */ - xwayland = wlr_xwayland_create(dpy, compositor, !xwayland_persistence); + xwayland = + wlr_xwayland_create(dpy, compositor, !config.xwayland_persistence); if (xwayland) { wl_signal_add(&xwayland->events.ready, &xwayland_ready); wl_signal_add(&xwayland->events.new_surface, &new_xwayland_surface); @@ -5830,7 +5847,7 @@ void overview_backup(Client *c) { c->isfullscreen = 0; // 清除窗口全屏标志 c->ismaximizescreen = 0; } - c->bw = c->isnoborder ? 0 : borderpx; + c->bw = c->isnoborder ? 0 : config.borderpx; client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | WLR_EDGE_RIGHT); @@ -5871,7 +5888,7 @@ void overview_restore(Client *c, const Arg *arg) { if (c->bw == 0 && !c->isfullscreen) { // 如果是在ov模式中创建的窗口,没有bw记录 - c->bw = c->isnoborder ? 0 : borderpx; + c->bw = c->isnoborder ? 0 : config.borderpx; } if (c->isfloating && !c->force_tiled_state) { @@ -5881,7 +5898,7 @@ void overview_restore(Client *c, const Arg *arg) { void handlecursoractivity(void) { wl_event_source_timer_update(hide_cursor_source, - cursor_hide_timeout * 1000); + config.cursor_hide_timeout * 1000); if (!cursor_hidden) return; @@ -5972,7 +5989,7 @@ void unmapnotify(struct wl_listener *listener, void *data) { Client *prev_in_stack = c->prev_in_stack; c->iskilling = 1; - if (animations && !c->is_clip_to_hide && !c->isminimized && + if (config.animations && !c->is_clip_to_hide && !c->isminimized && (!c->mon || VISIBLEON(c, c->mon))) init_fadeout_client(c); @@ -6073,7 +6090,7 @@ void updatemons(struct wl_listener *listener, void *data) { * positions, focus, and the stored configuration in wlroots' * output-manager implementation. */ - struct wlr_output_configuration_v1 *config = + struct wlr_output_configuration_v1 *output_config = wlr_output_configuration_v1_create(); Client *c = NULL; struct wlr_output_configuration_head_v1 *config_head; @@ -6084,8 +6101,8 @@ void updatemons(struct wl_listener *listener, void *data) { wl_list_for_each(m, &mons, link) { if (m->wlr_output->enabled || m->asleep) continue; - config_head = - wlr_output_configuration_head_v1_create(config, m->wlr_output); + config_head = wlr_output_configuration_head_v1_create(output_config, + m->wlr_output); config_head->state.enabled = 0; /* Remove this output from the layout to avoid cursor enter inside * it */ @@ -6113,8 +6130,8 @@ void updatemons(struct wl_listener *listener, void *data) { wl_list_for_each(m, &mons, link) { if (!m->wlr_output->enabled) continue; - config_head = - wlr_output_configuration_head_v1_create(config, m->wlr_output); + config_head = wlr_output_configuration_head_v1_create(output_config, + m->wlr_output); oldx = m->m.x; oldy = m->m.y; @@ -6147,7 +6164,7 @@ void updatemons(struct wl_listener *listener, void *data) { */ wlr_scene_output_set_position(m->scene_output, m->m.x, m->m.y); - if (blur && m->blur) { + if (config.blur && m->blur) { wlr_scene_node_set_position(&m->blur->node, m->m.x, m->m.y); wlr_scene_optimized_blur_set_size(m->blur, m->m.width, m->m.height); } @@ -6200,7 +6217,7 @@ void updatemons(struct wl_listener *listener, void *data) { * it's at the wrong position after all. */ wlr_cursor_move(cursor, NULL, 0, 0); - wlr_output_manager_v1_set_configuration(output_mgr, config); + wlr_output_manager_v1_set_configuration(output_mgr, output_config); } void updatetitle(struct wl_listener *listener, void *data) { @@ -6226,7 +6243,7 @@ urgent(struct wl_listener *listener, void *data) { if (!c || !c->foreign_toplevel) return; - if (focus_on_activate && !c->istagsilent && c != selmon->sel) { + if (config.focus_on_activate && !c->istagsilent && c != selmon->sel) { if (!(c->mon == selmon && c->tags & c->mon->tagset[c->mon->seltags])) view_in_mon(&(Arg){.ui = c->tags}, true, c->mon, true); focusclient(c, 1); @@ -6325,7 +6342,7 @@ void handle_keyboard_shortcuts_inhibit_new_inhibitor( struct wlr_keyboard_shortcuts_inhibitor_v1 *inhibitor = data; - if (allow_shortcuts_inhibit == SHORTCUTS_INHIBIT_DISABLE) { + if (config.allow_shortcuts_inhibit == SHORTCUTS_INHIBIT_DISABLE) { return; } @@ -6445,7 +6462,7 @@ void activatex11(struct wl_listener *listener, void *data) { } } - if (focus_on_activate && !c->istagsilent && c != selmon->sel) { + if (config.focus_on_activate && !c->istagsilent && c != selmon->sel) { if (!(c->mon == selmon && c->tags & c->mon->tagset[c->mon->seltags])) view_in_mon(&(Arg){.ui = c->tags}, true, c->mon, true); wlr_xwayland_surface_activate(c->surface.xwayland, 1); @@ -6591,7 +6608,7 @@ int32_t main(int32_t argc, char *argv[]) { if (c == 's') { startup_cmd = optarg; } else if (c == 'd') { - log_level = WLR_DEBUG; + cli_debug_log = true; } else if (c == 'v') { printf("mango " VERSION "\n"); return EXIT_SUCCESS; From 057e53745be191528c4b0ebe5b4f20ccd13b3286 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 13 Mar 2026 19:41:45 +0800 Subject: [PATCH 161/170] docs: update refer repo --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index f4848cf..d308370 100644 --- a/docs/index.md +++ b/docs/index.md @@ -36,7 +36,7 @@ Beyond basic window management, mangowm provides a rich set of features designed This project is built upon the hard work of several open-source projects: - **[wlroots](https://gitlab.freedesktop.org/wlroots/wlroots)** — Implementation of the Wayland protocol. -- **[owl](https://github.com/dqrk0jeste/owl)** — Basal window animation reference. +- **[mwc](https://github.com/nikoloc/mwc)** — Basal window animation reference. - **[dwl](https://codeberg.org/dwl/dwl)** — Basal dwl features. - **[sway](https://github.com/swaywm/sway)** — Sample implementation of the Wayland protocol. - **[scenefx](https://github.com/wlrfx/scenefx)** — Library to simplify adding window effects. From e4da095d41a32e4e10aaf54d40006d419fbb9008 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 13 Mar 2026 19:53:12 +0800 Subject: [PATCH 162/170] docs: udpate build dependency --- docs/installation.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/installation.md b/docs/installation.md index 1297eaf..5e0e0e8 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -183,16 +183,12 @@ pikman install mangowc If your distribution isn't listed above, or you want the latest unreleased changes, you can build mangowm from source. > **Info:** Ensure the following dependencies are installed before proceeding: -> - `glibc` > - `wayland` > - `wayland-protocols` > - `libinput` > - `libdrm` > - `libxkbcommon` > - `pixman` -> - `git` -> - `meson` -> - `ninja` > - `libdisplay-info` > - `libliftoff` > - `hwdata` From 537e4aa0794a3da4370dc21d02fdf4f16036b3d1 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 13 Mar 2026 20:07:14 +0800 Subject: [PATCH 163/170] docs: update some describe --- docs/configuration/miscellaneous.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration/miscellaneous.md b/docs/configuration/miscellaneous.md index 9e06781..2e5a1e9 100644 --- a/docs/configuration/miscellaneous.md +++ b/docs/configuration/miscellaneous.md @@ -37,7 +37,7 @@ description: Advanced settings for XWayland, focus behavior, and system integrat | `view_current_to_back` | `0` | Toggling the current tag switches back to the previously viewed tag. | | `scratchpad_cross_monitor` | `0` | Share the scratchpad pool across all monitors. | | `single_scratchpad` | `1` | Only allow one scratchpad (named or standard) to be visible at a time. | -| `circle_layout` | - | A comma-separated list of layouts `switch_layout` cycles through. | +| `circle_layout` | - | A comma-separated list of layouts `switch_layout` cycles through,the value sample:`tile,scroller`. | ## Window Behavior From d28c8e43a1b194cf62729ebed8e4b27b06dcc117 Mon Sep 17 00:00:00 2001 From: atheeq-rhxn Date: Fri, 13 Mar 2026 18:02:08 +0530 Subject: [PATCH 164/170] chore(sync-website): update commit message --- .github/workflows/sync-website.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/sync-website.yml b/.github/workflows/sync-website.yml index f641fb8..3577a65 100644 --- a/.github/workflows/sync-website.yml +++ b/.github/workflows/sync-website.yml @@ -38,5 +38,7 @@ jobs: git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" git add apps/web/content/docs - git diff --staged --quiet || git commit -m "sync from mango @ ${{ github.sha }}" + git diff --staged --quiet || git commit \ + -m "docs: content update from mangowm/mango" \ + -m "${{ github.event.head_commit.message }}\nSource: ${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}" git push From 1b2aeeec7ed964712601c942e36fc451eceb9392 Mon Sep 17 00:00:00 2001 From: atheeq-rhxn Date: Fri, 13 Mar 2026 23:40:44 +0530 Subject: [PATCH 165/170] docs: add AerynOS installation --- docs/installation.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/installation.md b/docs/installation.md index 5e0e0e8..10d2bd4 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -1,6 +1,6 @@ --- title: Installation -description: Install mangowm on Arch, Fedora, Gentoo, Guix System, NixOS, PikaOS, or build from source. +description: Install mangowm on AerynOS, Arch, Fedora, Gentoo, Guix System, NixOS, PikaOS, or build from source. --- ## Package Installation @@ -9,6 +9,18 @@ mangowm is available as a pre-built package on several distributions. Choose you --- +### AerynOS + +mangowm is available in the **AerynOS package repository**. + +You can install it using the `moss` package manager: + +```bash +sudo moss install mangowm +``` + +--- + ### Arch Linux mangowm is available in the **Arch User Repository (AUR)**. From 427cf6f89ff4dc9cc774dbe58a9b38d60b5fdbc7 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 14 Mar 2026 08:05:42 +0800 Subject: [PATCH 166/170] docs: update fcitx env suggest --- docs/configuration/input.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/configuration/input.md b/docs/configuration/input.md index 3ebc0f2..6d5eefd 100644 --- a/docs/configuration/input.md +++ b/docs/configuration/input.md @@ -136,6 +136,7 @@ To use Fcitx5 or IBus, set these environment variables in your config file. ```ini env=GTK_IM_MODULE,fcitx env=QT_IM_MODULE,fcitx +env=QT_IM_MODULES,wayland;fcitx env=SDL_IM_MODULE,fcitx env=XMODIFIERS,@im=fcitx env=GLFW_IM_MODULE,ibus From 5c52d578a8958a87d60b11b0c13a8cbb4f2b6730 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 14 Mar 2026 12:24:01 +0800 Subject: [PATCH 167/170] docs: fix some sample --- docs/configuration/monitors.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration/monitors.md b/docs/configuration/monitors.md index 92c2082..5501c77 100644 --- a/docs/configuration/monitors.md +++ b/docs/configuration/monitors.md @@ -241,7 +241,7 @@ yay -S xwayland-satellite ```ini env=DISPLAY,:2 -exec=xwayland-satellite :2 +exec-once=xwayland-satellite :2 monitorrule=name:eDP-1,width:1920,height:1080,refresh:60,x:0,y:0,scale:1.4,vrr:0,rr:0 ``` From b0ec0b4275629e915060bfc46f8586b8ad653ff7 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 14 Mar 2026 12:43:49 +0800 Subject: [PATCH 168/170] docs: remove some old describe --- docs/configuration/basics.md | 2 +- docs/visuals/theming.md | 3 --- docs/window-management/overview.md | 9 +-------- 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/docs/configuration/basics.md b/docs/configuration/basics.md index dbbe45f..22b2679 100644 --- a/docs/configuration/basics.md +++ b/docs/configuration/basics.md @@ -48,7 +48,7 @@ source-optional=~/.config/mango/optional.conf You can check your configuration for errors without starting mangowm: ```bash -mango -p /path/to/config.conf +mango -c /path/to/config.conf -p ``` Use with `source-optional` for shared configs across different setups. diff --git a/docs/visuals/theming.md b/docs/visuals/theming.md index 789ce47..8137eb4 100644 --- a/docs/visuals/theming.md +++ b/docs/visuals/theming.md @@ -54,6 +54,3 @@ Set the size and theme of your mouse cursor. cursor_size=24 cursor_theme=Adwaita ``` - -> **Tip:** You may also want to set the `XCURSOR_SIZE` environment variable to match: -> `env=XCURSOR_SIZE,24` diff --git a/docs/window-management/overview.md b/docs/window-management/overview.md index 290d613..7da6e69 100644 --- a/docs/window-management/overview.md +++ b/docs/window-management/overview.md @@ -26,11 +26,4 @@ description: Configure the overview mode for window navigation. When in overview mode: - **Left mouse button** — Jump to (focus) a window. -- **Right mouse button** — Close a window. - -To enable this behavior, add the following mouse bindings to your config: - -```ini -mousebind=NONE,btn_left,toggleoverview,1 -mousebind=NONE,btn_right,killclient,0 -``` +- **Right mouse button** — Close a window. \ No newline at end of file From 23af3c55957ef16bb61dee6bf7376e810c0c03dc Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 14 Mar 2026 12:50:31 +0800 Subject: [PATCH 169/170] opt: auto sync XCURSOR_SIZE and XCURSOR_THEME env from config --- docs/configuration/basics.md | 4 ++-- src/mango.c | 22 +++++++++++++++++++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/docs/configuration/basics.md b/docs/configuration/basics.md index 22b2679..7afa343 100644 --- a/docs/configuration/basics.md +++ b/docs/configuration/basics.md @@ -60,8 +60,8 @@ You can define environment variables directly within your config file. These are > **Warning:** Environment variables defined here will be **reset** every time you reload the configuration. ```ini -env=GTK_THEME,Adwaita:dark -env=XCURSOR_SIZE,24 +env=QT_IM_MODULES,wayland;fcitx +env=XMODIFIERS,@im=fcitx ``` ## Autostart diff --git a/src/mango.c b/src/mango.c index aedf1d6..915c47b 100644 --- a/src/mango.c +++ b/src/mango.c @@ -912,8 +912,13 @@ static KeyMode keymode = { .isdefault = true, }; -static char *env_vars[] = {"DISPLAY", "WAYLAND_DISPLAY", "XDG_CURRENT_DESKTOP", - "XDG_SESSION_TYPE", NULL}; +static char *env_vars[] = {"DISPLAY", + "WAYLAND_DISPLAY", + "XDG_CURRENT_DESKTOP", + "XDG_SESSION_TYPE", + "XCURSOR_THEME", + "XCURSOR_SIZE", + NULL}; static struct { enum wp_cursor_shape_device_v1_shape shape; struct wlr_surface *surface; @@ -5463,7 +5468,6 @@ void handle_print_status(struct wl_listener *listener, void *data) { void setup(void) { - setenv("XCURSOR_SIZE", "24", 1); setenv("XDG_CURRENT_DESKTOP", "mango", 1); parse_config(); @@ -5653,6 +5657,18 @@ void setup(void) { cursor_mgr = wlr_xcursor_manager_create(config.cursor_theme, config.cursor_size); + if (config.cursor_size > 0) { + char size_str[16]; + snprintf(size_str, sizeof(size_str), "%d", config.cursor_size); + setenv("XCURSOR_SIZE", size_str, 1); + } else { + setenv("XCURSOR_SIZE", "24", 1); + } + + if (config.cursor_theme) { + setenv("XCURSOR_THEME", config.cursor_theme, 1); + } + /* * wlr_cursor *only* displays an image on screen. It does not move * around when the pointer moves. However, we can attach input devices From 32c36ba48548f1b30e929c8a739f838bac72db2f Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 14 Mar 2026 13:09:41 +0800 Subject: [PATCH 170/170] opt: optimzie code struct --- src/config/parse_config.h | 26 ++++++++++++++++---------- src/mango.c | 17 +++-------------- 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index ffb8414..6ae9740 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -3604,6 +3604,20 @@ void reapply_monitor_rules(void) { updatemons(NULL, NULL); } +void set_xcursor_env() { + if (config.cursor_size > 0) { + char size_str[16]; + snprintf(size_str, sizeof(size_str), "%d", config.cursor_size); + setenv("XCURSOR_SIZE", size_str, 1); + } else { + setenv("XCURSOR_SIZE", "24", 1); + } + + if (config.cursor_theme) { + setenv("XCURSOR_THEME", config.cursor_theme, 1); + } +} + void reapply_cursor_style(void) { if (hide_cursor_source) { wl_event_source_timer_update(hide_cursor_source, 0); @@ -3620,19 +3634,11 @@ void reapply_cursor_style(void) { cursor_mgr = NULL; } + set_xcursor_env(); + cursor_mgr = wlr_xcursor_manager_create(config.cursor_theme, config.cursor_size); - if (config.cursor_size > 0) { - char size_str[16]; - snprintf(size_str, sizeof(size_str), "%d", config.cursor_size); - setenv("XCURSOR_SIZE", size_str, 1); - } - - if (config.cursor_theme) { - setenv("XCURSOR_THEME", config.cursor_theme, 1); - } - Monitor *m = NULL; wl_list_for_each(m, &mons, link) { wlr_xcursor_manager_load(cursor_mgr, m->wlr_output->scale); diff --git a/src/mango.c b/src/mango.c index 915c47b..3d36dd8 100644 --- a/src/mango.c +++ b/src/mango.c @@ -5653,22 +5653,11 @@ void setup(void) { * cursor images are available at all scale factors on the screen * (necessary for HiDPI support). Scaled cursors will be loaded with * each output. */ - // cursor_mgr = wlr_xcursor_manager_create(cursor_theme, 24); + + set_xcursor_env(); + cursor_mgr = wlr_xcursor_manager_create(config.cursor_theme, config.cursor_size); - - if (config.cursor_size > 0) { - char size_str[16]; - snprintf(size_str, sizeof(size_str), "%d", config.cursor_size); - setenv("XCURSOR_SIZE", size_str, 1); - } else { - setenv("XCURSOR_SIZE", "24", 1); - } - - if (config.cursor_theme) { - setenv("XCURSOR_THEME", config.cursor_theme, 1); - } - /* * wlr_cursor *only* displays an image on screen. It does not move * around when the pointer moves. However, we can attach input devices