From 47b5c9aa2e0135247a87cb01de9e32dacc674524 Mon Sep 17 00:00:00 2001 From: beeb5k Date: Mon, 16 Mar 2026 16:46:01 +0530 Subject: [PATCH 01/34] meta: update homepage repository links --- nix/default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nix/default.nix b/nix/default.nix index 87d4bfd0..9ca7ded5 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -68,7 +68,7 @@ stdenv.mkDerivation { meta = { mainProgram = "mango"; description = "A streamlined but feature-rich Wayland compositor"; - homepage = "https://github.com/DreamMaoMao/mango"; + homepage = "https://github.com/mangowm/mango"; license = lib.licenses.gpl3Plus; maintainers = []; platforms = lib.platforms.unix; From 6c88999adac27306ce034670adc5c7584bbbcda2 Mon Sep 17 00:00:00 2001 From: beeb5k Date: Mon, 16 Mar 2026 19:02:36 +0530 Subject: [PATCH 02/34] meta(nix): update description --- nix/default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nix/default.nix b/nix/default.nix index 9ca7ded5..cb6497b9 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -67,7 +67,7 @@ stdenv.mkDerivation { meta = { mainProgram = "mango"; - description = "A streamlined but feature-rich Wayland compositor"; + description = "Practical and Powerful wayland compositor (dwm but wayland)"; homepage = "https://github.com/mangowm/mango"; license = lib.licenses.gpl3Plus; maintainers = []; From 17434d62624f6a6b74e00a5f9fefab10419d34b4 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 17 Mar 2026 19:44:25 +0800 Subject: [PATCH 03/34] opt: allowe space on both sides of the plus sign when parse mod key --- src/config/parse_config.h | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 6ae97406..7816631e 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -634,9 +634,14 @@ uint32_t parse_mod(const char *mod_str) { // 分割处理每个部分 token = strtok_r(input_copy, "+", &saveptr); while (token != NULL) { - // 去除空白 - while (*token == ' ' || *token == '\t') - token++; + // 去除前后空白 + trim_whitespace(token); + + // 如果 token 变成空字符串则跳过 + if (*token == '\0') { + token = strtok_r(NULL, "+", &saveptr); + continue; + } if (strncmp(token, "code:", 5) == 0) { // 处理 code: 形式 From 6c81384c53bdf78ab764457a5ffe2d536f1c1937 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 18 Mar 2026 23:10:15 +0800 Subject: [PATCH 04/34] feat: add tag rule option open_as_floating --- src/config/parse_config.h | 6 ++++++ src/fetch/client.h | 10 ++++------ src/mango.c | 39 +++++++++++++++++++++++++++------------ 3 files changed, 37 insertions(+), 18 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 7816631e..f103c4f0 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -168,6 +168,7 @@ typedef struct { float mfact; int32_t nmaster; int32_t no_render_border; + int32_t open_as_floating; int32_t no_hide; } ConfigTagRule; @@ -1904,6 +1905,7 @@ bool parse_option(Config *config, char *key, char *value) { rule->nmaster = 0; rule->mfact = 0.0f; rule->no_render_border = 0; + rule->open_as_floating = 0; rule->no_hide = 0; bool parse_error = false; @@ -1932,6 +1934,8 @@ bool parse_option(Config *config, char *key, char *value) { rule->monitor_serial = strdup(val); } else if (strcmp(key, "no_render_border") == 0) { rule->no_render_border = CLAMP_INT(atoi(val), 0, 1); + } else if (strcmp(key, "open_as_floating") == 0) { + rule->open_as_floating = CLAMP_INT(atoi(val), 0, 1); } else if (strcmp(key, "no_hide") == 0) { rule->no_hide = CLAMP_INT(atoi(val), 0, 1); } else if (strcmp(key, "nmaster") == 0) { @@ -3785,6 +3789,8 @@ void parse_tagrule(Monitor *m) { m->pertag->mfacts[tr.id] = tr.mfact; if (tr.no_render_border >= 0) m->pertag->no_render_border[tr.id] = tr.no_render_border; + if (tr.open_as_floating >= 0) + m->pertag->open_as_floating[tr.id] = tr.open_as_floating; } } diff --git a/src/fetch/client.h b/src/fetch/client.h index 0b142847..abf684c1 100644 --- a/src/fetch/client.h +++ b/src/fetch/client.h @@ -1,15 +1,12 @@ bool check_hit_no_border(Client *c) { - int32_t i; bool hit_no_border = false; if (!render_border) { hit_no_border = true; } - for (i = 0; i < config.tag_rules_count; i++) { - if (c->tags & (1 << (config.tag_rules[i].id - 1)) && - config.tag_rules[i].no_render_border) { - hit_no_border = true; - } + if (c->mon && !c->mon->isoverview && + c->mon->pertag->no_render_border[get_tags_first_tag_num(c->tags) + 1]) { + hit_no_border = true; } if (config.no_border_when_single && c && c->mon && @@ -19,6 +16,7 @@ bool check_hit_no_border(Client *c) { } return hit_no_border; } + Client *termforwin(Client *w) { Client *c = NULL; diff --git a/src/mango.c b/src/mango.c index df1315a1..01de9545 100644 --- a/src/mango.c +++ b/src/mango.c @@ -933,8 +933,9 @@ struct Pertag { uint32_t curtag, prevtag; /* current and previous tag */ int32_t nmasters[LENGTH(tags) + 1]; /* number of windows in master area */ float mfacts[LENGTH(tags) + 1]; /* mfacts per tag */ - bool no_hide[LENGTH(tags) + 1]; /* no_hide per tag */ - bool no_render_border[LENGTH(tags) + 1]; /* no_render_border per tag */ + int32_t no_hide[LENGTH(tags) + 1]; /* no_hide per tag */ + int32_t no_render_border[LENGTH(tags) + 1]; /* no_render_border per tag */ + int32_t open_as_floating[LENGTH(tags) + 1]; /* open_as_floating per tag */ const Layout *ltidxs[LENGTH(tags) + 1]; /* matrix of tags and layouts indexes */ }; @@ -1402,6 +1403,25 @@ void set_float_malposition(Client *tc) { tc->float_geom.y = tc->geom.y = y; } +void client_reset_mon_tags(Client *c, Monitor *mon, uint32_t newtags) { + if (!newtags && mon && !mon->isoverview) { + c->tags = mon->tagset[mon->seltags]; + } else if (!newtags && mon && mon->isoverview) { + c->tags = mon->ovbk_current_tagset; + } else if (newtags) { + c->tags = newtags; + } else { + c->tags = mon->tagset[mon->seltags]; + } +} + +void check_match_tag_floating_rule(Client *c, Monitor *mon) { + if (c->tags && !c->isfloating && mon && !c->swallowedby && + mon->pertag->open_as_floating[get_tags_first_tag_num(c->tags) + 1]) { + c->isfloating = 1; + } +} + void applyrules(Client *c) { /* rule matching */ const char *appid, *title; @@ -1526,6 +1546,7 @@ void applyrules(Client *c) { int32_t fullscreen_state_backup = c->isfullscreen || client_wants_fullscreen(c); + setmon(c, mon, newtags, !c->isopensilent && !(client_is_x11_popup(c) && client_should_ignore_focus(c)) && @@ -5062,7 +5083,8 @@ setfloating(Client *c, int32_t floating) { // 让当前tag中的全屏窗口退出全屏参与平铺 wl_list_for_each(fc, &clients, link) if (fc && fc != c && VISIBLEON(fc, c->mon) && - c->tags & fc->tags && ISFULLSCREEN(fc)) { + c->tags & fc->tags && ISFULLSCREEN(fc) && + old_floating_state) { clear_fullscreen_flag(fc); } } @@ -5375,15 +5397,8 @@ 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); - 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]; - } + client_reset_mon_tags(c, m, newtags); + check_match_tag_floating_rule(c, m); setfloating(c, c->isfloating); setfullscreen(c, c->isfullscreen); /* This will call arrange(c->mon) */ } From 17c037171a5502e65ed934260694dc0facfaf607 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 18 Mar 2026 23:51:05 +0800 Subject: [PATCH 05/34] docs: update docs --- docs/window-management/rules.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/window-management/rules.md b/docs/window-management/rules.md index 996c172f..d37120de 100644 --- a/docs/window-management/rules.md +++ b/docs/window-management/rules.md @@ -179,6 +179,7 @@ tagrule=id:Values,monitor_make:xxx,monitor_model:xxx,Parameter:Values | `monitor_serial` | string | monitor serial | Match by monitor serial number | | `layout_name` | string | layout name | Layout name to set | | `no_render_border` | integer | `0` / `1` | Disable render border | +| `open_as_floating` | integer | `0` / `1` | New open window will be floating| | `no_hide` | integer | `0` / `1` | Not hide even if the tag is empty | | `nmaster` | integer | 0, 99 | Number of master windows | | `mfact` | float | 0.1–0.9 | Master area factor | From 949063804a7393b487225787b29d9ca3211d52f6 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 19 Mar 2026 07:36:50 +0800 Subject: [PATCH 06/34] fix: open_as_floating not match tag correctly --- src/fetch/client.h | 2 +- src/mango.c | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/fetch/client.h b/src/fetch/client.h index abf684c1..8fe831be 100644 --- a/src/fetch/client.h +++ b/src/fetch/client.h @@ -5,7 +5,7 @@ bool check_hit_no_border(Client *c) { } if (c->mon && !c->mon->isoverview && - c->mon->pertag->no_render_border[get_tags_first_tag_num(c->tags) + 1]) { + c->mon->pertag->no_render_border[get_tags_first_tag_num(c->tags)]) { hit_no_border = true; } diff --git a/src/mango.c b/src/mango.c index 01de9545..dae16898 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1417,7 +1417,7 @@ void client_reset_mon_tags(Client *c, Monitor *mon, uint32_t newtags) { void check_match_tag_floating_rule(Client *c, Monitor *mon) { if (c->tags && !c->isfloating && mon && !c->swallowedby && - mon->pertag->open_as_floating[get_tags_first_tag_num(c->tags) + 1]) { + mon->pertag->open_as_floating[get_tags_first_tag_num(c->tags)]) { c->isfloating = 1; } } @@ -4063,9 +4063,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 = 1.0f; - c->old_master_inner_per = 1.0f; - c->old_master_mfact_per = 1.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; @@ -5098,7 +5098,7 @@ setfloating(Client *c, int32_t floating) { layers[c->isfloating ? LyrTop : LyrTile]); } - if (!c->isfloating && old_floating_state) { + if (!c->isfloating && old_floating_state && (c->old_stack_inner_per > 0.0f || c->old_master_inner_per > 0.0f)) { restore_size_per(c->mon, c); } From c2559f6c7cd18bfc1808183af8738deacef23862 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 19 Mar 2026 07:56:29 +0800 Subject: [PATCH 07/34] feat: add dispatch toggle_all_floating --- docs/bindings/keys.md | 1 + src/config/parse_config.h | 2 ++ src/dispatch/bind_declare.h | 1 + src/dispatch/bind_define.h | 16 ++++++++++++++++ src/mango.c | 3 ++- 5 files changed, 22 insertions(+), 1 deletion(-) diff --git a/docs/bindings/keys.md b/docs/bindings/keys.md index 64ba64b7..4c318fb3 100644 --- a/docs/bindings/keys.md +++ b/docs/bindings/keys.md @@ -90,6 +90,7 @@ bindr=Super,Super_L,spawn,rofi -show run | :--- | :--- | :--- | | `killclient` | - | Close the focused window. | | `togglefloating` | - | Toggle floating state. | +| `toggle_all_floating` | - | Toggle all visible clients floating state. | | `togglefullscreen` | - | Toggle fullscreen. | | `togglefakefullscreen` | - | Toggle "fake" fullscreen (remains constrained). | | `togglemaximizescreen` | - | Maximize window (keep decoration/bar). | diff --git a/src/config/parse_config.h b/src/config/parse_config.h index f103c4f0..fda401d9 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -1202,6 +1202,8 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value, } else if (strcmp(func_name, "scroller_stack") == 0) { func = scroller_stack; (*arg).i = parse_direction(arg_value); + } else if (strcmp(func_name, "toggle_all_floating") == 0) { + func = toggle_all_floating; } else { return NULL; } diff --git a/src/dispatch/bind_declare.h b/src/dispatch/bind_declare.h index 7dced532..dbeebd33 100644 --- a/src/dispatch/bind_declare.h +++ b/src/dispatch/bind_declare.h @@ -70,3 +70,4 @@ 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); +int32_t toggle_all_floating(const Arg *arg); \ No newline at end of file diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 449adf3b..d6854a0c 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1866,3 +1866,19 @@ int32_t scroller_stack(const Arg *arg) { arrange(selmon, false, false); return 0; } + +int32_t toggle_all_floating(const Arg *arg) { + if (!selmon || !selmon->sel) + return 0; + + Client *c = NULL; + bool should_floating = !selmon->sel->isfloating; + wl_list_for_each(c, &clients, link) { + if (VISIBLEON(c, selmon)) { + if (c->isfloating != should_floating) { + setfloating(c, should_floating); + } + } + } + return 0; +} diff --git a/src/mango.c b/src/mango.c index dae16898..9dac7bb4 100644 --- a/src/mango.c +++ b/src/mango.c @@ -5098,7 +5098,8 @@ setfloating(Client *c, int32_t floating) { layers[c->isfloating ? LyrTop : LyrTile]); } - if (!c->isfloating && old_floating_state && (c->old_stack_inner_per > 0.0f || c->old_master_inner_per > 0.0f)) { + if (!c->isfloating && old_floating_state && + (c->old_stack_inner_per > 0.0f || c->old_master_inner_per > 0.0f)) { restore_size_per(c->mon, c); } From 6eb3378c0c12ec0dadace80c70f45b24d66e1979 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 19 Mar 2026 08:44:42 +0800 Subject: [PATCH 08/34] opt: dont restore size per whe toggle_all_floating --- src/dispatch/bind_define.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index d6854a0c..68309356 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1873,8 +1873,16 @@ int32_t toggle_all_floating(const Arg *arg) { Client *c = NULL; bool should_floating = !selmon->sel->isfloating; + wl_list_for_each(c, &clients, link) { if (VISIBLEON(c, selmon)) { + + if (c->isfloating && !should_floating) { + c->old_master_inner_per = 0.0f; + c->old_stack_inner_per = 0.0f; + set_size_per(selmon, c); + } + if (c->isfloating != should_floating) { setfloating(c, should_floating); } From c776356efe165dca09742f78f2a018e065b825be Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 19 Mar 2026 10:12:36 +0800 Subject: [PATCH 09/34] fix: size per not restroe when togglefloating --- src/mango.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/mango.c b/src/mango.c index 9dac7bb4..dcfd57ec 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1554,6 +1554,11 @@ void applyrules(Client *c) { (!c->istagsilent || !newtags || newtags & mon->tagset[mon->seltags])); + if (!c->isfloating) { + c->old_stack_inner_per = c->stack_inner_per; + c->old_master_inner_per = c->master_inner_per; + } + if (c->mon && !(c->mon == selmon && c->tags & c->mon->tagset[c->mon->seltags]) && !c->isopensilent && !c->istagsilent) { @@ -5118,6 +5123,12 @@ setfloating(Client *c, int32_t floating) { } arrange(c->mon, false, false); + + if (!c->isfloating) { + c->old_master_inner_per = c->master_inner_per; + c->old_stack_inner_per = c->stack_inner_per; + } + setborder_color(c); printstatus(); } From ca665cc6f8f51b6c7cfcf49ff352327085b25337 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 19 Mar 2026 15:27:43 +0800 Subject: [PATCH 10/34] opt: optimize state change between flating maximizescrenn and fullscreen --- src/mango.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/mango.c b/src/mango.c index dcfd57ec..826294bd 100644 --- a/src/mango.c +++ b/src/mango.c @@ -5041,12 +5041,12 @@ setfloating(Client *c, int32_t floating) { if (floating == 1 && c != grabc) { - if (c->isfullscreen || c->ismaximizescreen) { - c->isfullscreen = 0; // 清除窗口全屏标志 - c->ismaximizescreen = 0; - c->bw = c->isnoborder ? 0 : config.borderpx; + if (c->isfullscreen) { + c->isfullscreen = 0; + client_set_fullscreen(c, 0); } + c->ismaximizescreen = 0; exit_scroller_stack(c); // 重新计算居中的坐标 @@ -5175,8 +5175,10 @@ void setmaximizescreen(Client *c, int32_t maximizescreen) { if (maximizescreen) { - if (c->isfullscreen) - setfullscreen(c, 0); + if (c->isfullscreen) { + c->isfullscreen = 0; + client_set_fullscreen(c, 0); + } exit_scroller_stack(c); @@ -5243,9 +5245,8 @@ void setfullscreen(Client *c, int32_t fullscreen) // 用自定义全屏代理自 client_set_fullscreen(c, fullscreen); if (fullscreen) { - if (c->ismaximizescreen) - setmaximizescreen(c, 0); + c->ismaximizescreen = 0; exit_scroller_stack(c); if (c->isfloating) From deb47e8ab957550e376c3fb0dfb2c6aeab1ce9fd Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 19 Mar 2026 17:02:48 +0800 Subject: [PATCH 11/34] opt: not need to reset float_geom in setfullscreen and setmaximizescreen --- src/mango.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/mango.c b/src/mango.c index 826294bd..93c049f4 100644 --- a/src/mango.c +++ b/src/mango.c @@ -5182,9 +5182,6 @@ void setmaximizescreen(Client *c, int32_t maximizescreen) { exit_scroller_stack(c); - if (c->isfloating) - c->float_geom = c->geom; - maximizescreen_box.x = c->mon->w.x + config.gappoh; maximizescreen_box.y = c->mon->w.y + config.gappov; maximizescreen_box.width = c->mon->w.width - 2 * config.gappoh; @@ -5249,9 +5246,6 @@ void setfullscreen(Client *c, int32_t fullscreen) // 用自定义全屏代理自 c->ismaximizescreen = 0; exit_scroller_stack(c); - if (c->isfloating) - c->float_geom = c->geom; - c->isfakefullscreen = 0; c->bw = 0; From ab6e8a6545e37b0ff78434896b7d84a5b2e6ec32 Mon Sep 17 00:00:00 2001 From: Kiki <138850119+kiikii-dev@users.noreply.github.com> Date: Thu, 19 Mar 2026 16:29:15 +0100 Subject: [PATCH 12/34] Add playback section to keys.md Under the "Media Controls" section on "keys.md", add playback keybindings through the Playerctl command-line utility. --- docs/bindings/keys.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/bindings/keys.md b/docs/bindings/keys.md index 4c318fb3..b3a4ab64 100644 --- a/docs/bindings/keys.md +++ b/docs/bindings/keys.md @@ -195,6 +195,16 @@ bind=NONE,XF86AudioMute,spawn,wpctl set-mute @DEFAULT_SINK@ toggle bind=SHIFT,XF86AudioMute,spawn,wpctl set-mute @DEFAULT_SOURCE@ toggle ``` +#### Playback + +Requires: `playerctl` + +```ini +bind=NONE,XF86AudioNext,spawn,playerctl next +bind=NONE,XF86AudioPrev,spawn,playerctl previous +bind=NONE,XF86AudioPlay,spawn,playerctl play-pause +``` + ### Floating Window Movement | Command | Param | Description | @@ -202,4 +212,4 @@ bind=SHIFT,XF86AudioMute,spawn,wpctl set-mute @DEFAULT_SOURCE@ toggle | `smartmovewin` | `left/right/up/down` | Move floating window by snap distance. | | `smartresizewin` | `left/right/up/down` | Resize floating window by snap distance. | | `movewin` | `(x,y)` | Move floating window. | -| `resizewin` | `(width,height)` | Resize window. | \ No newline at end of file +| `resizewin` | `(width,height)` | Resize window. | From c55e0693645503ae270fe12a51e498d0471ced20 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 20 Mar 2026 09:15:47 +0800 Subject: [PATCH 13/34] docs: update wlroots build message --- README.md | 2 +- docs/installation.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b927b920..843780ba 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,7 @@ And then rebuild your system. ## Other ```bash -git clone -b 0.19.2 https://gitlab.freedesktop.org/wlroots/wlroots.git +git clone -b 0.19.3 https://gitlab.freedesktop.org/wlroots/wlroots.git cd wlroots meson build -Dprefix=/usr sudo ninja -C build install diff --git a/docs/installation.md b/docs/installation.md index 10d2bd47..d3b9afaa 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -214,7 +214,7 @@ You will need to build `wlroots` and `scenefx` manually as well. 1. **Build wlroots** Clone and install the specific version required (check README for latest version). ```bash - git clone -b 0.19.2 https://gitlab.freedesktop.org/wlroots/wlroots.git + git clone -b 0.19.3 https://gitlab.freedesktop.org/wlroots/wlroots.git cd wlroots meson build -Dprefix=/usr sudo ninja -C build install From ccefa572e1cd221bb3d8b4edc44bf6c91f5be6f6 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 20 Mar 2026 22:43:09 +0800 Subject: [PATCH 14/34] fix: make sure run the last frame even if the animation time is unreasonable --- src/animation/client.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/animation/client.h b/src/animation/client.h index 8c9c3915..e94f872a 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -723,6 +723,8 @@ void client_animation_next_tick(Client *c) { c->is_pending_open_animation = false; + client_apply_clip(c, factor); + if (animation_passed >= 1.0) { // clear the open action state @@ -752,8 +754,6 @@ void client_animation_next_tick(Client *c) { // end flush in next frame, not the current frame c->need_output_flush = false; } - - client_apply_clip(c, factor); } void init_fadeout_client(Client *c) { From 0232dcda823ac672f308598dfbfaf0dcc4595843 Mon Sep 17 00:00:00 2001 From: quadratic Date: Sun, 22 Mar 2026 19:50:30 +0100 Subject: [PATCH 15/34] mmsg: fix -o flag causing subsequent flags to be ignored when used with -g --- mmsg/mmsg.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mmsg/mmsg.c b/mmsg/mmsg.c index 4e0e1d8c..83b116da 100644 --- a/mmsg/mmsg.c +++ b/mmsg/mmsg.c @@ -569,12 +569,12 @@ int32_t main(int32_t argc, char *argv[]) { mode = WATCH; break; case 'o': - if (mode == SET) + if (mode == GET || mode == WATCH) + oflag = 1; + else if (mode == SET) output_name = EARGF(usage()); else output_name = ARGF(); - if (!output_name) - oflag = 1; break; case 't': tflag = 1; From 064bcad6f73d320c700afb58d8b8e909dec2b170 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 23 Mar 2026 10:01:34 +0800 Subject: [PATCH 16/34] opt: optimize fullscreen state change --- src/mango.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/mango.c b/src/mango.c index 93c049f4..f8f323d0 100644 --- a/src/mango.c +++ b/src/mango.c @@ -5243,9 +5243,12 @@ void setfullscreen(Client *c, int32_t fullscreen) // 用自定义全屏代理自 if (fullscreen) { - c->ismaximizescreen = 0; - exit_scroller_stack(c); + if (c->ismaximizescreen) { + client_set_maximized(c, false); + c->ismaximizescreen = 0; + } + exit_scroller_stack(c); c->isfakefullscreen = 0; c->bw = 0; From e6429f873329597df4804a8c3db7ac4a2e9aaae9 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 23 Mar 2026 22:15:28 +0800 Subject: [PATCH 17/34] opt: not unset maximize state if enable force_maximize --- mmsg/mmsg.c | 4 ++-- src/mango.c | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/mmsg/mmsg.c b/mmsg/mmsg.c index 83b116da..0191a635 100644 --- a/mmsg/mmsg.c +++ b/mmsg/mmsg.c @@ -569,8 +569,8 @@ int32_t main(int32_t argc, char *argv[]) { mode = WATCH; break; case 'o': - if (mode == GET || mode == WATCH) - oflag = 1; + if (mode == GET || mode == WATCH) + oflag = 1; else if (mode == SET) output_name = EARGF(usage()); else diff --git a/src/mango.c b/src/mango.c index f8f323d0..7a419cdd 100644 --- a/src/mango.c +++ b/src/mango.c @@ -5243,11 +5243,12 @@ void setfullscreen(Client *c, int32_t fullscreen) // 用自定义全屏代理自 if (fullscreen) { - if (c->ismaximizescreen) { + if (c->ismaximizescreen && !c->force_maximize) { client_set_maximized(c, false); - c->ismaximizescreen = 0; } + c->ismaximizescreen = 0; + exit_scroller_stack(c); c->isfakefullscreen = 0; From d69682aef95e5e494c1ba02bd9c471e3698ea424 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 25 Mar 2026 11:57:04 +0800 Subject: [PATCH 18/34] opt: limit button range to 272-279 --- src/config/parse_config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index fda401d9..6e46f138 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -3213,7 +3213,7 @@ void override_config(void) { config.accel_profile = CLAMP_INT(config.accel_profile, 0, 2); config.accel_speed = CLAMP_FLOAT(config.accel_speed, -1.0f, 1.0f); config.scroll_method = CLAMP_INT(config.scroll_method, 0, 4); - config.scroll_button = CLAMP_INT(config.scroll_button, 272, 276); + config.scroll_button = CLAMP_INT(config.scroll_button, 272, 279); config.click_method = CLAMP_INT(config.click_method, 0, 2); config.send_events_mode = CLAMP_INT(config.send_events_mode, 0, 2); config.button_map = CLAMP_INT(config.button_map, 0, 1); From 585e9ae4b62d3f1aa85baa5bdc58de04df2c5b62 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 25 Mar 2026 16:20:07 +0800 Subject: [PATCH 19/34] opt: change zoom_initial_ratio default to 0.4 --- assets/config.conf | 2 +- docs/visuals/animations.md | 2 +- src/config/parse_config.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/config.conf b/assets/config.conf index 15b654c1..eb326d16 100644 --- a/assets/config.conf +++ b/assets/config.conf @@ -34,7 +34,7 @@ animation_type_close=slide animation_fade_in=1 animation_fade_out=1 tag_animation_direction=1 -zoom_initial_ratio=0.3 +zoom_initial_ratio=0.4 zoom_end_ratio=0.8 fadein_begin_opacity=0.5 fadeout_begin_opacity=0.8 diff --git a/docs/visuals/animations.md b/docs/visuals/animations.md index b4b88816..76477e05 100644 --- a/docs/visuals/animations.md +++ b/docs/visuals/animations.md @@ -46,7 +46,7 @@ fadeout_begin_opacity=0.5 Adjust the zoom ratios for zoom animations. ```ini -zoom_initial_ratio=0.3 +zoom_initial_ratio=0.4 zoom_end_ratio=0.8 ``` diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 6e46f138..8226c771 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -3263,7 +3263,7 @@ void set_value_default() { config.animation_fade_in = 1; config.animation_fade_out = 1; config.tag_animation_direction = HORIZONTAL; - config.zoom_initial_ratio = 0.3f; + config.zoom_initial_ratio = 0.4f; config.zoom_end_ratio = 0.8f; config.fadein_begin_opacity = 0.5f; config.fadeout_begin_opacity = 0.5f; From 4ccd56ad0128cb0560147d103fea1a66d8ebafcd Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 25 Mar 2026 19:44:29 +0800 Subject: [PATCH 20/34] fix: miss make grabc exit scroll stack --- src/dispatch/bind_define.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 68309356..9c6cde9d 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -376,6 +376,7 @@ int32_t moveresize(const Arg *arg) { /* Float the window and tell motionnotify to grab it */ if (grabc->isfloating == 0 && arg->ui == CurMove) { grabc->drag_to_tile = true; + exit_scroller_stack(grabc); setfloating(grabc, 1); } From 958a6d87684b7e6817ff12739d5277e7729b266b Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 25 Mar 2026 19:50:42 +0800 Subject: [PATCH 21/34] opt: clear size per record when drag a window to floating --- src/dispatch/bind_define.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 9c6cde9d..07eafde5 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -378,6 +378,9 @@ int32_t moveresize(const Arg *arg) { grabc->drag_to_tile = true; exit_scroller_stack(grabc); setfloating(grabc, 1); + grabc->old_stack_inner_per = 0.0f; + grabc->old_master_inner_per = 0.0f; + set_size_per(grabc->mon, grabc); } switch (cursor_mode = arg->ui) { From 91e10b239f65d9e0d0d04c85829fb85ded15a231 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 25 Mar 2026 20:11:57 +0800 Subject: [PATCH 22/34] docs: add scroll_button desc --- docs/configuration/input.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/configuration/input.md b/docs/configuration/input.md index 6d5eefdb..ac30f179 100644 --- a/docs/configuration/input.md +++ b/docs/configuration/input.md @@ -45,6 +45,7 @@ Specific settings for laptop touchpads. Some settings may require a relogin to t | `tap_to_click` | `1` | Tap to trigger a left click. | | `tap_and_drag` | `1` | Tap and hold to drag items. | | `trackpad_natural_scrolling` | `0` | Invert scrolling direction (natural scrolling). | +| `scroll_button` | `274` | The mouse button that use for scrolling(272 to 279). | `scroll_method` | `1` | `1` (Two-finger), `2` (Edge), `4` (Button). | | `click_method` | `1` | `1` (Button areas), `2` (Clickfinger). | | `drag_lock` | `1` | Lock dragging after tapping. | @@ -57,6 +58,16 @@ Specific settings for laptop touchpads. Some settings may require a relogin to t **Detailed descriptions:** +- `scroll_button` values: + - `272` — Left button. + - `273` — Right button. + - `274` — Middle button. + - `275` — Side button. + - `276` — Extra button. + - `277` — Forward button. + - `278` — Back button. + - `279` — Task button. + - `scroll_method` values: - `0` — Never send scroll events (no scrolling). - `1` — Two-finger scrolling: send scroll events when two fingers are logically down on the device. From 63256ea31b25383187ae1887f2b01a2df28f083c Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 26 Mar 2026 18:05:25 +0800 Subject: [PATCH 23/34] break: rename force_maximize to force_fakemaximize --- docs/window-management/rules.md | 2 +- src/client/client.h | 4 ++-- src/config/parse_config.h | 8 ++++---- src/mango.c | 14 +++++++------- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/window-management/rules.md b/docs/window-management/rules.md index d37120de..93e81eba 100644 --- a/docs/window-management/rules.md +++ b/docs/window-management/rules.md @@ -27,7 +27,7 @@ windowrule=Parameter:Values,Parameter:Values,appid:Values,title:Values | `isoverlay` | integer | `0` / `1` | Make it always in top layer | | `isopensilent` | integer | `0` / `1` | Open without focus | | `istagsilent` | integer | `0` / `1` | Don't focus if client is not in current view tag | -| `force_maximize` | integer | `0` / `1` (default 1) | The state of client default to maximized | +| `force_fakemaximize` | integer | `0` / `1` (default 1) | The state of client set to fake maximized | | `ignore_maximize` | integer | `0` / `1` (default 1) | Don't handle maximize request from client | | `ignore_minimize` | integer | `0` / `1` (default 1) | Don't handle minimize request from client | | `force_tiled_state` | integer | `0` / `1` | Deceive the window into thinking it is tiling, so it better adheres to assigned dimensions | diff --git a/src/client/client.h b/src/client/client.h index 4788e448..648d6600 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -350,7 +350,7 @@ static inline void client_set_maximized(Client *c, bool maximized) { static inline void client_set_tiled(Client *c, uint32_t edges) { struct wlr_xdg_toplevel *toplevel; #ifdef XWAYLAND - if (client_is_x11(c) && c->force_maximize) { + if (client_is_x11(c) && c->force_fakemaximize) { wlr_xwayland_surface_set_maximized(c->surface.xwayland, edges != WLR_EDGE_NONE, edges != WLR_EDGE_NONE); @@ -365,7 +365,7 @@ static inline void client_set_tiled(Client *c, uint32_t edges) { wlr_xdg_toplevel_set_tiled(c->surface.xdg->toplevel, edges); } - if (c->force_maximize) { + if (c->force_fakemaximize) { wlr_xdg_toplevel_set_maximized(toplevel, edges != WLR_EDGE_NONE); } } diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 8226c771..e02b5017 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -90,7 +90,7 @@ typedef struct { int32_t no_force_center; int32_t isterm; int32_t allow_csd; - int32_t force_maximize; + int32_t force_fakemaximize; int32_t force_tiled_state; int32_t force_tearing; int32_t noswallow; @@ -2058,7 +2058,7 @@ bool parse_option(Config *config, char *key, char *value) { rule->indleinhibit_when_focus = -1; rule->isterm = -1; rule->allow_csd = -1; - rule->force_maximize = -1; + rule->force_fakemaximize = -1; rule->force_tiled_state = -1; rule->force_tearing = -1; rule->noswallow = -1; @@ -2172,8 +2172,8 @@ bool parse_option(Config *config, char *key, char *value) { rule->isterm = atoi(val); } else if (strcmp(key, "allow_csd") == 0) { rule->allow_csd = atoi(val); - } else if (strcmp(key, "force_maximize") == 0) { - rule->force_maximize = atoi(val); + } else if (strcmp(key, "force_fakemaximize") == 0) { + rule->force_fakemaximize = atoi(val); } else if (strcmp(key, "force_tiled_state") == 0) { rule->force_tiled_state = atoi(val); } else if (strcmp(key, "force_tearing") == 0) { diff --git a/src/mango.c b/src/mango.c index 7a419cdd..fad86b20 100644 --- a/src/mango.c +++ b/src/mango.c @@ -391,7 +391,7 @@ struct Client { struct dwl_opacity_animation opacity_animation; int32_t isterm, noswallow; int32_t allow_csd; - int32_t force_maximize; + int32_t force_fakemaximize; int32_t force_tiled_state; pid_t pid; Client *swallowing, *swallowedby; @@ -1325,7 +1325,7 @@ void toggle_hotarea(int32_t x_root, int32_t y_root) { static void apply_rule_properties(Client *c, const ConfigWinRule *r) { APPLY_INT_PROP(c, r, isterm); APPLY_INT_PROP(c, r, allow_csd); - APPLY_INT_PROP(c, r, force_maximize); + APPLY_INT_PROP(c, r, force_fakemaximize); APPLY_INT_PROP(c, r, force_tiled_state); APPLY_INT_PROP(c, r, force_tearing); APPLY_INT_PROP(c, r, noswallow); @@ -4073,7 +4073,7 @@ void init_client_properties(Client *c) { c->old_master_mfact_per = 0.0f; c->isterm = 0; c->allow_csd = 0; - c->force_maximize = 0; + c->force_fakemaximize = 0; c->force_tiled_state = 1; c->force_tearing = 0; c->allow_shortcuts_inhibit = SHORTCUTS_INHIBIT_ENABLE; @@ -5112,7 +5112,7 @@ setfloating(Client *c, int32_t floating) { save_old_size_per(c->mon); } - if (!c->force_maximize) + if (!c->force_fakemaximize) client_set_maximized(c, false); if (!c->isfloating || c->force_tiled_state) { @@ -5207,9 +5207,9 @@ void setmaximizescreen(Client *c, int32_t maximizescreen) { save_old_size_per(c->mon); } - if (!c->force_maximize && !c->ismaximizescreen) { + if (!c->force_fakemaximize && !c->ismaximizescreen) { client_set_maximized(c, false); - } else if (!c->force_maximize && c->ismaximizescreen) { + } else if (!c->force_fakemaximize && c->ismaximizescreen) { client_set_maximized(c, true); } @@ -5243,7 +5243,7 @@ void setfullscreen(Client *c, int32_t fullscreen) // 用自定义全屏代理自 if (fullscreen) { - if (c->ismaximizescreen && !c->force_maximize) { + if (c->ismaximizescreen && !c->force_fakemaximize) { client_set_maximized(c, false); } From b55de2874929b146c37b866f9a3e23c35c94c8d8 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 26 Mar 2026 18:57:02 +0800 Subject: [PATCH 24/34] opt: dont force request resize when the x11 app reject resize --- src/client/client.h | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/client/client.h b/src/client/client.h index 648d6600..e7e44748 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -294,9 +294,8 @@ 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_xwayland_surface *surface = c->surface.xwayland; + struct wlr_surface_state *state = &surface->surface->current; if ((int32_t)c->geom.width - 2 * (int32_t)c->bw == (int32_t)state->width && @@ -309,6 +308,30 @@ static inline uint32_t client_set_size(Client *c, uint32_t width, return 0; } + 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 && + c->mon && !INSIDEMON(c)) { + return 0; + } + + xcb_size_hints_t *size_hints = surface->size_hints; + int32_t width = c->geom.width - 2 * c->bw; + int32_t height = c->geom.height - 2 * c->bw; + + if (c->mon && c->mon->isoverview && size_hints && + c->geom.width - 2 * (int32_t)c->bw < size_hints->min_width && + c->geom.height - 2 * (int32_t)c->bw < size_hints->min_height) + return 0; + + if (size_hints && + c->geom.width - 2 * (int32_t)c->bw < size_hints->min_width) + width = size_hints->min_width; + if (size_hints && + c->geom.height - 2 * (int32_t)c->bw < size_hints->min_height) + height = size_hints->min_height; + wlr_xwayland_surface_configure(c->surface.xwayland, c->geom.x + c->bw, c->geom.y + c->bw, width, height); return 1; From f94ddc671e1d01ad85ffd6f1cf2f09811c2629c8 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 27 Mar 2026 07:14:56 +0800 Subject: [PATCH 25/34] opt: always notify the position change to x11 client --- src/client/client.h | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/client/client.h b/src/client/client.h index e7e44748..965b4106 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -308,14 +308,6 @@ static inline uint32_t client_set_size(Client *c, uint32_t width, return 0; } - 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 && - c->mon && !INSIDEMON(c)) { - return 0; - } - xcb_size_hints_t *size_hints = surface->size_hints; int32_t width = c->geom.width - 2 * c->bw; int32_t height = c->geom.height - 2 * c->bw; From bccdb651bd9a4191c3435a7dbb6322b74dcbdf08 Mon Sep 17 00:00:00 2001 From: atheeq-rhxn Date: Sat, 28 Mar 2026 20:50:26 +0530 Subject: [PATCH 26/34] docs: update pikaos installation --- docs/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation.md b/docs/installation.md index d3b9afaa..6f3927a0 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -185,7 +185,7 @@ mangowm is available in the **PikaOS package repository**. You can install it using the `pikman` package manager: ```bash -pikman install mangowc +pikman install mangowm ``` --- From c89f8147f97ab07aab573a09f0f4b13bf9f2855d Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 29 Mar 2026 08:40:40 +0800 Subject: [PATCH 27/34] bump version to 0.12.8 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 5d09d536..64394cd9 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('mango', ['c', 'cpp'], - version : '0.12.7', + version : '0.12.8', ) subdir('protocols') From 52676492fe22356b45f9cbda9d24d887f4039f02 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 30 Mar 2026 18:11:10 +0800 Subject: [PATCH 28/34] opt: optimize foreign toplevel state sync --- src/dispatch/bind_define.h | 2 +- src/mango.c | 77 +++++++++++++++++++++++--------------- 2 files changed, 47 insertions(+), 32 deletions(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 07eafde5..ec06ce5a 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -552,7 +552,7 @@ int32_t restore_minimized(const Arg *arg) { if (selmon && selmon->sel && selmon->sel->is_in_scratchpad && selmon->sel->is_scratchpad_show) { - selmon->sel->isminimized = 0; + client_pending_minimized_state(selmon->sel, 0); selmon->sel->is_scratchpad_show = 0; selmon->sel->is_in_scratchpad = 0; selmon->sel->isnamedscratchpad = 0; diff --git a/src/mango.c b/src/mango.c index fad86b20..8fdff709 100644 --- a/src/mango.c +++ b/src/mango.c @@ -806,6 +806,10 @@ 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); +static void client_pending_fullscreen_state(Client *c, int32_t isfullscreen); +static void client_pending_maximized_state(Client *c, int32_t ismaximized); +static void client_pending_minimized_state(Client *c, int32_t isminimized); + #include "data/static_keymap.h" #include "dispatch/bind_declare.h" #include "layout/layout.h" @@ -1063,11 +1067,33 @@ void clear_fullscreen_flag(Client *c) { } } +void client_pending_fullscreen_state(Client *c, int32_t isfullscreen) { + c->isfullscreen = isfullscreen; + + if (c->foreign_toplevel && !c->iskilling) + wlr_foreign_toplevel_handle_v1_set_fullscreen(c->foreign_toplevel, + isfullscreen); +} + +void client_pending_maximized_state(Client *c, int32_t ismaximized) { + c->ismaximizescreen = ismaximized; + if (c->foreign_toplevel && !c->iskilling) + wlr_foreign_toplevel_handle_v1_set_maximized(c->foreign_toplevel, + ismaximized); +} + +void client_pending_minimized_state(Client *c, int32_t isminimized) { + c->isminimized = isminimized; + if (c->foreign_toplevel && !c->iskilling) + wlr_foreign_toplevel_handle_v1_set_minimized(c->foreign_toplevel, + isminimized); +} + void show_scratchpad(Client *c) { c->is_scratchpad_show = 1; if (c->isfullscreen || c->ismaximizescreen) { - c->isfullscreen = 0; // 清除窗口全屏标志 - c->ismaximizescreen = 0; + client_pending_fullscreen_state(c, 0); + client_pending_maximized_state(c, 0); c->bw = c->isnoborder ? 0 : config.borderpx; } @@ -1106,9 +1132,6 @@ void swallow(Client *c, Client *w) { c->bw = w->bw; c->isfloating = w->isfloating; c->isurgent = w->isurgent; - c->isfullscreen = w->isfullscreen; - c->ismaximizescreen = w->ismaximizescreen; - c->isminimized = w->isminimized; c->is_in_scratchpad = w->is_in_scratchpad; c->is_scratchpad_show = w->is_scratchpad_show; c->tags = w->tags; @@ -1120,6 +1143,7 @@ void swallow(Client *c, Client *w) { c->scroller_proportion = w->scroller_proportion; c->next_in_stack = w->next_in_stack; c->prev_in_stack = w->prev_in_stack; + if (w->next_in_stack) w->next_in_stack->prev_in_stack = c; if (w->prev_in_stack) @@ -1138,11 +1162,9 @@ void swallow(Client *c, Client *w) { if (!c->foreign_toplevel && c->mon) add_foreign_toplevel(c); - if (c->isminimized && c->foreign_toplevel) { - wlr_foreign_toplevel_handle_v1_set_activated(c->foreign_toplevel, - false); - wlr_foreign_toplevel_handle_v1_set_minimized(c->foreign_toplevel, true); - } + client_pending_fullscreen_state(c, w->isfullscreen); + client_pending_maximized_state(c, w->ismaximizescreen); + client_pending_minimized_state(c, w->isminimized); } bool switch_scratchpad_client_state(Client *c) { @@ -4231,7 +4253,7 @@ void maximizenotify(struct wl_listener *listener, void *data) { void unminimize(Client *c) { if (c && c->is_in_scratchpad && c->is_scratchpad_show) { - c->isminimized = 0; + client_pending_minimized_state(c, 0); c->is_scratchpad_show = 0; c->is_in_scratchpad = 0; c->isnamedscratchpad = 0; @@ -4259,13 +4281,12 @@ void set_minimized(Client *c) { c->oldtags = c->mon->tagset[c->mon->seltags]; c->mini_restore_tag = c->tags; c->tags = 0; - c->isminimized = 1; + client_pending_minimized_state(c, 1); c->is_in_scratchpad = 1; c->is_scratchpad_show = 0; focusclient(focustop(selmon), 1); arrange(c->mon, false, false); wlr_foreign_toplevel_handle_v1_set_activated(c->foreign_toplevel, false); - wlr_foreign_toplevel_handle_v1_set_minimized(c->foreign_toplevel, true); wl_list_remove(&c->link); // 从原来位置移除 wl_list_insert(clients.prev, &c->link); // 插入尾部 } @@ -5042,11 +5063,11 @@ setfloating(Client *c, int32_t floating) { if (floating == 1 && c != grabc) { if (c->isfullscreen) { - c->isfullscreen = 0; + client_pending_fullscreen_state(c, 0); client_set_fullscreen(c, 0); } - c->ismaximizescreen = 0; + client_pending_maximized_state(c, 0); exit_scroller_stack(c); // 重新计算居中的坐标 @@ -5171,12 +5192,12 @@ void setmaximizescreen(Client *c, int32_t maximizescreen) { return; int32_t old_maximizescreen_state = c->ismaximizescreen; - c->ismaximizescreen = maximizescreen; + client_pending_maximized_state(c, maximizescreen); if (maximizescreen) { if (c->isfullscreen) { - c->isfullscreen = 0; + client_pending_fullscreen_state(c, 0); client_set_fullscreen(c, 0); } @@ -5189,10 +5210,8 @@ void setmaximizescreen(Client *c, int32_t maximizescreen) { wlr_scene_node_raise_to_top(&c->scene->node); if (!is_scroller_layout(c->mon) || c->isfloating) resize(c, maximizescreen_box, 0); - c->ismaximizescreen = 1; } else { c->bw = c->isnoborder ? 0 : config.borderpx; - c->ismaximizescreen = 0; if (c->isfloating) setfloating(c, 1); } @@ -5240,6 +5259,7 @@ void setfullscreen(Client *c, int32_t fullscreen) // 用自定义全屏代理自 c->isfullscreen = fullscreen; client_set_fullscreen(c, fullscreen); + client_pending_fullscreen_state(c, fullscreen); if (fullscreen) { @@ -5247,7 +5267,7 @@ void setfullscreen(Client *c, int32_t fullscreen) // 用自定义全屏代理自 client_set_maximized(c, false); } - c->ismaximizescreen = 0; + client_pending_maximized_state(c, 0); exit_scroller_stack(c); c->isfakefullscreen = 0; @@ -5256,10 +5276,8 @@ void setfullscreen(Client *c, int32_t fullscreen) // 用自定义全屏代理自 wlr_scene_node_raise_to_top(&c->scene->node); // 将视图提升到顶层 if (!is_scroller_layout(c->mon) || c->isfloating) resize(c, c->mon->m, 1); - c->isfullscreen = 1; } else { c->bw = c->isnoborder ? 0 : config.borderpx; - c->isfullscreen = 0; if (c->isfloating) setfloating(c, 1); } @@ -5451,8 +5469,7 @@ void show_hide_client(Client *c) { c->tags = c->oldtags; arrange(c->mon, false, false); } - c->isminimized = 0; - wlr_foreign_toplevel_handle_v1_set_minimized(c->foreign_toplevel, false); + client_pending_minimized_state(c, 0); focusclient(c, 1); wlr_foreign_toplevel_handle_v1_set_activated(c->foreign_toplevel, true); } @@ -5876,8 +5893,8 @@ void overview_backup(Client *c) { c->isfloating = 0; } if (c->isfullscreen || c->ismaximizescreen) { - c->isfullscreen = 0; // 清除窗口全屏标志 - c->ismaximizescreen = 0; + client_pending_fullscreen_state(c, 0); // 清除窗口全屏标志 + client_pending_maximized_state(c, 0); } c->bw = c->isnoborder ? 0 : config.borderpx; @@ -5907,8 +5924,8 @@ void overview_restore(Client *c, const Arg *arg) { } else if (want_restore_fullscreen(c) && c->isfullscreen) { setfullscreen(c, 1); } else { - c->isfullscreen = 0; - c->ismaximizescreen = 0; + client_pending_fullscreen_state(c, 0); + client_pending_maximized_state(c, 0); setfullscreen(c, false); } } else { @@ -6481,13 +6498,11 @@ void activatex11(struct wl_listener *listener, void *data) { return; if (c->isminimized) { - c->isminimized = 0; + client_pending_minimized_state(c, 0); c->tags = c->mini_restore_tag; c->is_scratchpad_show = 0; c->is_in_scratchpad = 0; c->isnamedscratchpad = 0; - wlr_foreign_toplevel_handle_v1_set_minimized(c->foreign_toplevel, - false); setborder_color(c); if (VISIBLEON(c, c->mon)) { need_arrange = true; From c7c41c31034d861b558156fe4abb112225d2e67d Mon Sep 17 00:00:00 2001 From: Ananya Timalsina <84459091+ananyatimalsina@users.noreply.github.com> Date: Sun, 15 Feb 2026 12:35:34 +0100 Subject: [PATCH 29/34] refactor(nix): add structured config support to home-manager module Convert settings from raw text to structured Nix attrs, following Hyprland's module pattern. Implementation based 1:1 on Hyprland's design - all credit to the Hyprland project. - Add nix/lib.nix with toMango conversion function - Support nested attrs, lists for duplicate keys - Add extraConfig, topPrefixes, bottomPrefixes options - Auto-add exec-once for autostart.sh Adapted for mangowc syntax (underscore separators vs colons). --- nix/hm-modules.nix | 161 +++++++++++++++++++++++----- nix/lib.nix | 257 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 393 insertions(+), 25 deletions(-) create mode 100644 nix/lib.nix diff --git a/nix/hm-modules.nix b/nix/hm-modules.nix index 85d57908..2c450f61 100644 --- a/nix/hm-modules.nix +++ b/nix/hm-modules.nix @@ -1,18 +1,22 @@ -self: { +self: +{ lib, config, pkgs, ... -}: let +}: +let cfg = config.wayland.windowManager.mango; + selflib = import ./lib.nix lib; variables = lib.concatStringsSep " " cfg.systemd.variables; extraCommands = lib.concatStringsSep " && " cfg.systemd.extraCommands; - systemdActivation = ''${pkgs.dbus}/bin/dbus-update-activation-environment --systemd ${variables}; ${extraCommands}''; + systemdActivation = "${pkgs.dbus}/bin/dbus-update-activation-environment --systemd ${variables}; ${extraCommands}"; autostart_sh = pkgs.writeShellScript "autostart.sh" '' ${lib.optionalString cfg.systemd.enable systemdActivation} ${cfg.autostart_sh} ''; -in { +in +{ options = { wayland.windowManager.mango = with lib; { enable = mkOption { @@ -54,7 +58,7 @@ in { "XCURSOR_THEME" "XCURSOR_SIZE" ]; - example = ["--all"]; + example = [ "--all" ]; description = '' Environment variables imported into the systemd and D-Bus user environment. ''; @@ -75,32 +79,140 @@ in { ''; }; settings = mkOption { - description = "mango config content"; - type = types.lines; - default = ""; - example = '' - # menu and terminal - bind=Alt,space,spawn,rofi -show drun - bind=Alt,Return,spawn,foot + type = + with lib.types; + let + valueType = + nullOr (oneOf [ + bool + int + float + str + path + (attrsOf valueType) + (listOf valueType) + ]) + // { + description = "Mango configuration value"; + }; + in + valueType; + default = { }; + description = '' + Mango configuration written in Nix. Entries with the same key + should be written as lists. Variables and colors names should be + quoted. See for more examples. + + ::: {.note} + This option uses a structured format that is converted to Mango's + configuration syntax. Nested attributes are flattened with underscore separators. + For example: `animation.duration_open = 400` becomes `animation_duration_open = 400` + ::: + ''; + example = lib.literalExpression '' + { + # Window effects + blur = 1; + blur_optimized = 1; + blur_params = { + radius = 5; + num_passes = 2; + }; + border_radius = 6; + focused_opacity = 1.0; + + # Animations - use underscores for multi-part keys + animations = 1; + animation_type_open = "slide"; + animation_type_close = "slide"; + animation_duration_open = 400; + animation_duration_close = 800; + + # Or use nested attrs (will be flattened with underscores) + animation_curve = { + open = "0.46,1.0,0.29,1"; + close = "0.08,0.92,0,1"; + }; + + # Use lists for duplicate keys like bind and tagrule + bind = [ + "SUPER,r,reload_config" + "Alt,space,spawn,rofi -show drun" + "Alt,Return,spawn,foot" + ]; + + tagrule = [ + "id:1,layout_name:tile" + "id:2,layout_name:scroller" + ]; + } ''; }; + extraConfig = mkOption { + type = types.lines; + default = ""; + description = '' + Extra configuration lines to add to `~/.config/mango/config.conf`. + This is useful for advanced configurations that don't fit the structured + settings format, or for options that aren't yet supported by the module. + ''; + example = '' + # Advanced config that doesn't fit structured format + special_option = 1 + ''; + }; + topPrefixes = mkOption { + type = with lib.types; listOf str; + default = [ ]; + description = '' + List of prefixes for attributes that should appear at the top of the config file. + Attributes starting with these prefixes will be sorted to the beginning. + ''; + example = [ "source" ]; + }; + bottomPrefixes = mkOption { + type = with lib.types; listOf str; + default = [ ]; + description = '' + List of prefixes for attributes that should appear at the bottom of the config file. + Attributes starting with these prefixes will be sorted to the end. + ''; + example = [ "source" ]; + }; autostart_sh = mkOption { - description = "WARRNING: This is a shell script, but no need to add shebang"; + description = '' + Shell script to run on mango startup. No shebang needed. + + When this option is set, the script will be written to + `~/.config/mango/autostart.sh` and an `exec-once` line + will be automatically added to the config to execute it. + ''; type = types.lines; default = ""; example = '' waybar & + dunst & ''; }; }; }; config = lib.mkIf cfg.enable { - home.packages = [cfg.package]; + home.packages = [ cfg.package ]; xdg.configFile = { - "mango/config.conf" = lib.mkIf (cfg.settings != "") { - text = cfg.settings; - }; + "mango/config.conf" = + lib.mkIf (cfg.settings != { } || cfg.extraConfig != "" || cfg.autostart_sh != "") + { + text = + lib.optionalString (cfg.settings != { }) ( + selflib.toMango { + topCommandsPrefixes = cfg.topPrefixes; + bottomCommandsPrefixes = cfg.bottomPrefixes; + } cfg.settings + ) + + lib.optionalString (cfg.extraConfig != "") cfg.extraConfig + + lib.optionalString (cfg.autostart_sh != "") "\nexec-once=~/.config/mango/autostart.sh\n"; + }; "mango/autostart.sh" = lib.mkIf (cfg.autostart_sh != "") { source = autostart_sh; executable = true; @@ -109,14 +221,13 @@ in { systemd.user.targets.mango-session = lib.mkIf cfg.systemd.enable { Unit = { Description = "mango compositor session"; - Documentation = ["man:systemd.special(7)"]; - BindsTo = ["graphical-session.target"]; - Wants = - [ - "graphical-session-pre.target" - ] - ++ lib.optional cfg.systemd.xdgAutostart "xdg-desktop-autostart.target"; - After = ["graphical-session-pre.target"]; + Documentation = [ "man:systemd.special(7)" ]; + BindsTo = [ "graphical-session.target" ]; + Wants = [ + "graphical-session-pre.target" + ] + ++ lib.optional cfg.systemd.xdgAutostart "xdg-desktop-autostart.target"; + After = [ "graphical-session-pre.target" ]; Before = lib.optional cfg.systemd.xdgAutostart "xdg-desktop-autostart.target"; }; }; diff --git a/nix/lib.nix b/nix/lib.nix new file mode 100644 index 00000000..406d94ae --- /dev/null +++ b/nix/lib.nix @@ -0,0 +1,257 @@ +lib: +let + inherit (lib) + attrNames + filterAttrs + foldl + generators + partition + ; + + inherit (lib.strings) + concatMapStrings + hasPrefix + ; + + /** + Convert a structured Nix attribute set into Mango's configuration format. + + This function takes a nested attribute set and converts it into Mango-compatible + configuration syntax, supporting top, bottom, and regular command sections. + + Commands are flattened using the `flattenAttrs` function, and attributes are formatted as + `key = value` pairs. Lists are expanded as duplicate keys to match Mango's expected format. + + Configuration: + + * `topCommandsPrefixes` - A list of prefixes to define **top** commands (default: `[]`). + * `bottomCommandsPrefixes` - A list of prefixes to define **bottom** commands (default: `[]`). + + Attention: + + - The function ensures top commands appear **first** and bottom commands **last**. + - The generated configuration is a **single string**, suitable for writing to a config file. + - Lists are converted into multiple entries, ensuring compatibility with Mango. + + # Inputs + + Structured function argument: + + : topCommandsPrefixes (optional, default: `[]`) + : A list of prefixes that define **top** commands. Any key starting with one of these + prefixes will be placed at the beginning of the configuration. + : bottomCommandsPrefixes (optional, default: `[]`) + : A list of prefixes that define **bottom** commands. Any key starting with one of these + prefixes will be placed at the end of the configuration. + + Value: + + : The attribute set to be converted to Hyprland configuration format. + + # Type + + ``` + toMango :: AttrSet -> AttrSet -> String + ``` + + # Examples + :::{.example} + + ## Basic mangowc configuration + + ```nix + let + config = { + blur = 1; + blur_params_radius = 5; + border_radius = 6; + animations = 1; + animation_duration_open = 400; + }; + in lib.toMango {} config + ``` + + **Output:** + ``` + animations = 1 + animation_duration_open = 400 + blur = 1 + blur_params_radius = 5 + border_radius = 6 + ``` + + ## Using nested attributes + + ```nix + let + config = { + blur = 1; + blur_params = { + radius = 5; + num_passes = 2; + noise = 0.02; + }; + animation_curve = { + open = "0.46,1.0,0.29,1"; + close = "0.08,0.92,0,1"; + }; + }; + in lib.toMango {} config + ``` + + **Output:** + ``` + animation_curve_close = 0.08,0.92,0,1 + animation_curve_open = 0.46,1.0,0.29,1 + blur = 1 + blur_params_noise = 0.02 + blur_params_num_passes = 2 + blur_params_radius = 5 + ``` + + ## Using lists for duplicate keys + + ```nix + let + config = { + bind = [ + "SUPER,r,reload_config" + "Alt,space,spawn,rofi -show drun" + "Alt,Return,spawn,foot" + ]; + tagrule = [ + "id:1,layout_name:tile" + "id:2,layout_name:scroller" + ]; + }; + in lib.toMango {} config + ``` + + **Output:** + ``` + bind = SUPER,r,reload_config + bind = Alt,space,spawn,rofi -show drun + bind = Alt,Return,spawn,foot + tagrule = id:1,layout_name:tile + tagrule = id:2,layout_name:scroller + ``` + + ::: + */ + toMango = + { + topCommandsPrefixes ? [ ], + bottomCommandsPrefixes ? [ ], + }: + attrs: + let + toMango' = + attrs: + let + # Specially configured `toKeyValue` generator with support for duplicate keys + # and a legible key-value separator. + mkCommands = generators.toKeyValue { + mkKeyValue = generators.mkKeyValueDefault { } " = "; + listsAsDuplicateKeys = true; + indent = ""; # No indent, since we don't have nesting + }; + + # Flatten the attrset, combining keys in a "path" like `"a_b_c" = "x"`. + # Uses `flattenAttrs` with an underscore separator. + commands = flattenAttrs (p: k: "${p}_${k}") attrs; + + # General filtering function to check if a key starts with any prefix in a given list. + filterCommands = list: n: foldl (acc: prefix: acc || hasPrefix prefix n) false list; + + # Partition keys into top commands and the rest + result = partition (filterCommands topCommandsPrefixes) (attrNames commands); + topCommands = filterAttrs (n: _: builtins.elem n result.right) commands; + remainingCommands = removeAttrs commands result.right; + + # Partition remaining commands into bottom commands and regular commands + result2 = partition (filterCommands bottomCommandsPrefixes) result.wrong; + bottomCommands = filterAttrs (n: _: builtins.elem n result2.right) remainingCommands; + regularCommands = removeAttrs remainingCommands result2.right; + in + # Concatenate strings from mapping `mkCommands` over top, regular, and bottom commands. + concatMapStrings mkCommands [ + topCommands + regularCommands + bottomCommands + ]; + in + toMango' attrs; + + /** + Flatten a nested attribute set into a flat attribute set, using a custom key separator function. + + This function recursively traverses a nested attribute set and produces a flat attribute set + where keys are joined using a user-defined function (`pred`). It allows transforming deeply + nested structures into a single-level attribute set while preserving key-value relationships. + + Configuration: + + * `pred` - A function `(string -> string -> string)` defining how keys should be concatenated. + + # Inputs + + Structured function argument: + + : pred (required) + : A function that determines how parent and child keys should be combined into a single key. + It takes a `prefix` (parent key) and `key` (current key) and returns the joined key. + + Value: + + : The nested attribute set to be flattened. + + # Type + + ``` + flattenAttrs :: (String -> String -> String) -> AttrSet -> AttrSet + ``` + + # Examples + :::{.example} + + ```nix + let + nested = { + a = "3"; + b = { c = "4"; d = "5"; }; + }; + + separator = (prefix: key: "${prefix}.${key}"); # Use dot notation + in lib.flattenAttrs separator nested + ``` + + **Output:** + ```nix + { + "a" = "3"; + "b.c" = "4"; + "b.d" = "5"; + } + ``` + + ::: + */ + flattenAttrs = + pred: attrs: + let + flattenAttrs' = + prefix: attrs: + builtins.foldl' ( + acc: key: + let + value = attrs.${key}; + newKey = if prefix == "" then key else pred prefix key; + in + acc // (if builtins.isAttrs value then flattenAttrs' newKey value else { "${newKey}" = value; }) + ) { } (builtins.attrNames attrs); + in + flattenAttrs' "" attrs; +in +{ + inherit flattenAttrs toMango; +} From b63e93fc0945007f409c622d417a40ffb26188d3 Mon Sep 17 00:00:00 2001 From: Ananya Timalsina <84459091+ananyatimalsina@users.noreply.github.com> Date: Sun, 15 Feb 2026 14:04:56 +0100 Subject: [PATCH 30/34] feat(nix): add keymode support for modal keybindings --- nix/hm-modules.nix | 14 +++++++++++ nix/lib.nix | 59 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/nix/hm-modules.nix b/nix/hm-modules.nix index 2c450f61..81cec1e8 100644 --- a/nix/hm-modules.nix +++ b/nix/hm-modules.nix @@ -107,6 +107,9 @@ in This option uses a structured format that is converted to Mango's configuration syntax. Nested attributes are flattened with underscore separators. For example: `animation.duration_open = 400` becomes `animation_duration_open = 400` + + Keymodes (submaps) are supported via the special `keymode` attribute. Each keymode + is a nested attribute set under `keymode` that contains its own bindings. ::: ''; example = lib.literalExpression '' @@ -139,12 +142,23 @@ in "SUPER,r,reload_config" "Alt,space,spawn,rofi -show drun" "Alt,Return,spawn,foot" + "ALT,R,setkeymode,resize" # Enter resize mode ]; tagrule = [ "id:1,layout_name:tile" "id:2,layout_name:scroller" ]; + + # Keymodes (submaps) for modal keybindings + keymode = { + resize = { + bind = [ + "NONE,Left,resizewin,-10,0" + "NONE,Escape,setkeymode,default" + ]; + }; + }; } ''; }; diff --git a/nix/lib.nix b/nix/lib.nix index 406d94ae..9dfd2ff6 100644 --- a/nix/lib.nix +++ b/nix/lib.nix @@ -6,6 +6,7 @@ let foldl generators partition + removeAttrs ; inherit (lib.strings) @@ -136,6 +137,39 @@ let tagrule = id:2,layout_name:scroller ``` + ## Using keymodes (submaps) + + ```nix + let + config = { + bind = [ + "SUPER,Q,killclient" + "ALT,R,setkeymode,resize" + ]; + keymode = { + resize = { + bind = [ + "NONE,Left,resizewin,-10,0" + "NONE,Right,resizewin,10,0" + "NONE,Escape,setkeymode,default" + ]; + }; + }; + }; + in lib.toMango {} config + ``` + + **Output:** + ``` + bind = SUPER,Q,killclient + bind = ALT,R,setkeymode,resize + + keymode = resize + bind = NONE,Left,resizewin,-10,0 + bind = NONE,Right,resizewin,10,0 + bind = NONE,Escape,setkeymode,default + ``` + ::: */ toMango = @@ -156,9 +190,28 @@ let indent = ""; # No indent, since we don't have nesting }; + # Extract keymode definitions if they exist + keymodes = attrs.keymode or { }; + attrsWithoutKeymodes = removeAttrs attrs [ "keymode" ]; + + # Generate keymode blocks + # Format: keymode=name\nbind=...\nbind=...\n + mkKeymodeBlock = + name: modeAttrs: + let + modeCommands = flattenAttrs (p: k: "${p}_${k}") modeAttrs; + in + "keymode = ${name}\n${mkCommands modeCommands}"; + + keymodeBlocks = + if keymodes == { } then + "" + else + "\n" + concatMapStrings (name: mkKeymodeBlock name keymodes.${name} + "\n") (attrNames keymodes); + # Flatten the attrset, combining keys in a "path" like `"a_b_c" = "x"`. # Uses `flattenAttrs` with an underscore separator. - commands = flattenAttrs (p: k: "${p}_${k}") attrs; + commands = flattenAttrs (p: k: "${p}_${k}") attrsWithoutKeymodes; # General filtering function to check if a key starts with any prefix in a given list. filterCommands = list: n: foldl (acc: prefix: acc || hasPrefix prefix n) false list; @@ -174,11 +227,13 @@ let regularCommands = removeAttrs remainingCommands result2.right; in # Concatenate strings from mapping `mkCommands` over top, regular, and bottom commands. + # Keymodes are appended at the end. concatMapStrings mkCommands [ topCommands regularCommands bottomCommands - ]; + ] + + keymodeBlocks; in toMango' attrs; From a8afc0dc4d76b9fe1ae9eab93e51c4cb09d3c5c6 Mon Sep 17 00:00:00 2001 From: Ananya Timalsina <84459091+ananyatimalsina@users.noreply.github.com> Date: Tue, 17 Feb 2026 10:12:11 +0100 Subject: [PATCH 31/34] feat(nix): support old config format with deprecation warning --- nix/hm-modules.nix | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/nix/hm-modules.nix b/nix/hm-modules.nix index 81cec1e8..4d48ddf6 100644 --- a/nix/hm-modules.nix +++ b/nix/hm-modules.nix @@ -212,17 +212,31 @@ in }; config = lib.mkIf cfg.enable { + # Backwards compatibility warning for old string-based config + warnings = lib.optional (builtins.isString cfg.settings) '' + wayland.windowManager.mango.settings: Using a string for settings is deprecated. + Please migrate to the new structured attribute set format. + See the module documentation for examples, or use the 'extraConfig' option for raw config strings. + The old string format will be removed in a future release. + ''; + home.packages = [ cfg.package ]; xdg.configFile = { "mango/config.conf" = lib.mkIf (cfg.settings != { } || cfg.extraConfig != "" || cfg.autostart_sh != "") { text = - lib.optionalString (cfg.settings != { }) ( - selflib.toMango { - topCommandsPrefixes = cfg.topPrefixes; - bottomCommandsPrefixes = cfg.bottomPrefixes; - } cfg.settings + # Support old string-based config during transition period + ( + if builtins.isString cfg.settings then + cfg.settings + else + lib.optionalString (cfg.settings != { }) ( + selflib.toMango { + topCommandsPrefixes = cfg.topPrefixes; + bottomCommandsPrefixes = cfg.bottomPrefixes; + } cfg.settings + ) ) + lib.optionalString (cfg.extraConfig != "") cfg.extraConfig + lib.optionalString (cfg.autostart_sh != "") "\nexec-once=~/.config/mango/autostart.sh\n"; From bb0160d7cf1187f1d3292adbed51d834c6a31471 Mon Sep 17 00:00:00 2001 From: Ananya Timalsina <84459091+ananyatimalsina@users.noreply.github.com> Date: Sun, 1 Mar 2026 15:40:25 +0100 Subject: [PATCH 32/34] feat(nix): add build-time configuration validation Uses `pkgs.runCommand` in the home-manager module to parse and validate the generated config file prior to deployment, preventing broken setups. --- nix/hm-modules.nix | 102 +++++++++++++++++++++++++-------------------- 1 file changed, 56 insertions(+), 46 deletions(-) diff --git a/nix/hm-modules.nix b/nix/hm-modules.nix index 4d48ddf6..f00d9c68 100644 --- a/nix/hm-modules.nix +++ b/nix/hm-modules.nix @@ -211,53 +211,63 @@ in }; }; - config = lib.mkIf cfg.enable { - # Backwards compatibility warning for old string-based config - warnings = lib.optional (builtins.isString cfg.settings) '' - wayland.windowManager.mango.settings: Using a string for settings is deprecated. - Please migrate to the new structured attribute set format. - See the module documentation for examples, or use the 'extraConfig' option for raw config strings. - The old string format will be removed in a future release. - ''; + config = lib.mkIf cfg.enable ( + let + finalConfigText = + # Support old string-based config during transition period + ( + if builtins.isString cfg.settings then + cfg.settings + else + lib.optionalString (cfg.settings != { }) ( + selflib.toMango { + topCommandsPrefixes = cfg.topPrefixes; + bottomCommandsPrefixes = cfg.bottomPrefixes; + } cfg.settings + ) + ) + + lib.optionalString (cfg.extraConfig != "") cfg.extraConfig + + lib.optionalString (cfg.autostart_sh != "") "\nexec-once=~/.config/mango/autostart.sh\n"; - home.packages = [ cfg.package ]; - xdg.configFile = { - "mango/config.conf" = - lib.mkIf (cfg.settings != { } || cfg.extraConfig != "" || cfg.autostart_sh != "") - { - text = - # Support old string-based config during transition period - ( - if builtins.isString cfg.settings then - cfg.settings - else - lib.optionalString (cfg.settings != { }) ( - selflib.toMango { - topCommandsPrefixes = cfg.topPrefixes; - bottomCommandsPrefixes = cfg.bottomPrefixes; - } cfg.settings - ) - ) - + lib.optionalString (cfg.extraConfig != "") cfg.extraConfig - + lib.optionalString (cfg.autostart_sh != "") "\nexec-once=~/.config/mango/autostart.sh\n"; - }; - "mango/autostart.sh" = lib.mkIf (cfg.autostart_sh != "") { - source = autostart_sh; - executable = true; + validatedConfig = pkgs.runCommand "mango-config.conf" { } '' + cp ${pkgs.writeText "mango-config.conf" finalConfigText} "$out" + ${cfg.package}/bin/mango -c "$out" -p || exit 1 + ''; + in + { + # Backwards compatibility warning for old string-based config + warnings = lib.optional (builtins.isString cfg.settings) '' + wayland.windowManager.mango.settings: Using a string for settings is deprecated. + Please migrate to the new structured attribute set format. + See the module documentation for examples, or use the 'extraConfig' option for raw config strings. + The old string format will be removed in a future release. + ''; + + home.packages = [ cfg.package ]; + xdg.configFile = { + "mango/config.conf" = + lib.mkIf (cfg.settings != { } || cfg.extraConfig != "" || cfg.autostart_sh != "") + { + source = validatedConfig; + }; + "mango/autostart.sh" = lib.mkIf (cfg.autostart_sh != "") { + source = autostart_sh; + executable = true; + }; }; - }; - systemd.user.targets.mango-session = lib.mkIf cfg.systemd.enable { - Unit = { - Description = "mango compositor session"; - Documentation = [ "man:systemd.special(7)" ]; - BindsTo = [ "graphical-session.target" ]; - Wants = [ - "graphical-session-pre.target" - ] - ++ lib.optional cfg.systemd.xdgAutostart "xdg-desktop-autostart.target"; - After = [ "graphical-session-pre.target" ]; - Before = lib.optional cfg.systemd.xdgAutostart "xdg-desktop-autostart.target"; + systemd.user.targets.mango-session = lib.mkIf cfg.systemd.enable { + Unit = { + Description = "mango compositor session"; + Documentation = [ "man:systemd.special(7)" ]; + BindsTo = [ "graphical-session.target" ]; + Wants = [ + "graphical-session-pre.target" + ] + ++ lib.optional cfg.systemd.xdgAutostart "xdg-desktop-autostart.target"; + After = [ "graphical-session-pre.target" ]; + Before = lib.optional cfg.systemd.xdgAutostart "xdg-desktop-autostart.target"; + }; }; - }; - }; + } + ); } From c44d12314c543bbe229bba5654341fedb3f51dca Mon Sep 17 00:00:00 2001 From: Nikita Mitasov Date: Sat, 4 Apr 2026 19:35:38 +0300 Subject: [PATCH 33/34] fix(guix): pin wlroots version meta(guix): update home-page meta(guix): more appropriate licenses field refactor(guix): use more easier meson patch phase meta(guix): update description --- mangowm.scm | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/mangowm.scm b/mangowm.scm index 7d94166d..44031ed0 100644 --- a/mangowm.scm +++ b/mangowm.scm @@ -15,7 +15,7 @@ #:use-module (gnu packages ninja) #:use-module (gnu packages pkg-config) #:use-module (guix build-system meson) - #:use-module (guix licenses)) + #:use-module ((guix licenses) #:prefix license:)) (define-public mangowm-git @@ -36,10 +36,13 @@ (add-before 'configure 'patch-meson (lambda _ (substitute* "meson.build" + ;; MangoWM ignores sysconfdir handling for NixOS. + ;; We also need to skip that sysconfdir edits. + (("is_nixos = false") + "is_nixos = true") + ;; Unhardcode path. Fixes loading default config. (("'-DSYSCONFDIR=\\\"@0@\\\"'.format\\('/etc'\\)") - "'-DSYSCONFDIR=\"@0@\"'.format(sysconfdir)") - (("sysconfdir = sysconfdir.substring\\(prefix.length\\(\\)\\)") - ""))))))) + "'-DSYSCONFDIR=\"@0@\"'.format(sysconfdir)"))))))) (inputs (list wayland libinput libdrm @@ -52,14 +55,17 @@ pcre2 libxcb xcb-util-wm - wlroots + wlroots-0.19 scenefx)) (native-inputs (list pkg-config wayland-protocols)) - (home-page "https://github.com/DreamMaoMao/mangowm") + (home-page "https://github.com/mangowm/mango") (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))) + (description + "MangoWM is a modern, lightweight, high-performance Wayland compositor +built on dwl — crafted for speed, flexibility, and a customizable desktop experience.") + (license (list license:gpl3 ;mangowm itself, dwl + license:expat ;dwm, sway, wlroots + license:cc0)))) ;tinywl (define-deprecated-package mangowc mangowm-git) From e83a9dff820ce2215ecbbbea7d617d24d0825174 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 6 Apr 2026 15:39:39 +0800 Subject: [PATCH 34/34] fix: fix potential memory leaks --- src/dispatch/bind_define.h | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index ec06ce5a..f5992e29 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -863,7 +863,6 @@ int32_t spawn_shell(const Arg *arg) { } int32_t spawn(const Arg *arg) { - if (!arg->v) return 0; @@ -876,28 +875,21 @@ int32_t spawn(const Arg *arg) { dup2(STDERR_FILENO, STDOUT_FILENO); setsid(); - // 2. 解析参数 - char *argv[64]; - int32_t argc = 0; - char *token = strtok((char *)arg->v, " "); - while (token != NULL && argc < 63) { - wordexp_t p; - if (wordexp(token, &p, 0) == 0) { - argv[argc++] = p.we_wordv[0]; - } else { - argv[argc++] = token; - } - token = strtok(NULL, " "); + // 2. 对整个参数字符串进行单词展开 + wordexp_t p; + if (wordexp(arg->v, &p, 0) != 0) { + wlr_log(WLR_DEBUG, "mango: wordexp failed for '%s'\n", arg->v); + _exit(EXIT_FAILURE); } - argv[argc] = NULL; - // 3. 执行命令 - execvp(argv[0], argv); + // 3. 执行命令(p.we_wordv 已经是 argv 数组) + execvp(p.we_wordv[0], p.we_wordv); - // 4. execvp 失败时:打印错误并直接退出(避免 coredump) - wlr_log(WLR_DEBUG, "mango: execvp '%s' failed: %s\n", argv[0], + // 4. execvp 失败时:打印错误,释放 wordexp 资源,然后退出 + wlr_log(WLR_DEBUG, "mango: execvp '%s' failed: %s\n", p.we_wordv[0], strerror(errno)); - _exit(EXIT_FAILURE); // 使用 _exit 避免缓冲区刷新等操作 + wordfree(&p); // 释放 wordexp 分配的内存 + _exit(EXIT_FAILURE); } return 0; }