From c2a7146168dfe4d122beec7e20f36d7742d81e1f Mon Sep 17 00:00:00 2001 From: werapi Date: Wed, 7 Jan 2026 15:46:25 +0100 Subject: [PATCH 01/83] 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 454145f6e03a462b743c64776d2b17121c2637ea Mon Sep 17 00:00:00 2001 From: Daniel Jampen Date: Sun, 8 Feb 2026 17:56:26 +0100 Subject: [PATCH 02/83] 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 5ae8975b11395dd73594fa7ea187fd5798510e30 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 13 Feb 2026 10:44:17 +0800 Subject: [PATCH 03/83] 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 04/83] 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 05/83] 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 06/83] 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 07/83] 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 08/83] 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 09/83] 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 10/83] 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 11/83] 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 12/83] 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 13/83] 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 14/83] 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 15/83] 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 16/83] 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 17/83] 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 18/83] 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 19/83] 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 20/83] 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 21/83] 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 22/83] 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 23/83] 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 24/83] 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 25/83] 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 26/83] 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 27/83] 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 28/83] 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 29/83] 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 30/83] 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 31/83] 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 32/83] 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 33/83] 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 34/83] 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 35/83] 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 36/83] 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 37/83] 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 38/83] 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 39/83] 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 40/83] 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 41/83] 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 42/83] 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 43/83] 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 44/83] 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 45/83] 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 46/83] 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 47/83] 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 48/83] 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 49/83] 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 50/83] 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 51/83] 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 52/83] 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 53/83] 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 54/83] 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 55/83] 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 56/83] 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 57/83] 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 58/83] 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 59/83] 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 60/83] 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 61/83] 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 62/83] 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 63/83] 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 64/83] 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 65/83] 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 66/83] 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 67/83] 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 68/83] 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 69/83] 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 70/83] 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 71/83] 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 72/83] 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 73/83] 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 74/83] 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 75/83] 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 76/83] 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 77/83] 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 78/83] 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 79/83] 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 80/83] 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 81/83] 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 82/83] 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 83/83] 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) /