From 1029936b18f9e40be424ad1d542573f4c79398b4 Mon Sep 17 00:00:00 2001 From: eater <=@eater.me> Date: Fri, 7 Nov 2025 15:44:47 +0100 Subject: [PATCH 01/34] check is drm_release_manager is set before cleaning up to avoid segfault --- src/mango.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mango.c b/src/mango.c index 36cbbbf..0962520 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1964,7 +1964,9 @@ void cleanuplisteners(void) { wl_list_remove(&request_start_drag.link); wl_list_remove(&start_drag.link); wl_list_remove(&new_session_lock.link); - wl_list_remove(&drm_lease_request.link); + if (drm_lease_manager) { + wl_list_remove(&drm_lease_request.link); + } wl_list_remove(&tearing_new_object.link); #ifdef XWAYLAND wl_list_remove(&new_xwayland_surface.link); From e0bc7fb5e47c545ce15e2be012a6c18b76894a55 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 7 Nov 2025 21:56:25 +0800 Subject: [PATCH 02/34] fix: crash when click waybar overview button --- src/ext-protocol/ext-workspace.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/ext-protocol/ext-workspace.h b/src/ext-protocol/ext-workspace.h index 61e36da..930e6c9 100644 --- a/src/ext-protocol/ext-workspace.h +++ b/src/ext-protocol/ext-workspace.h @@ -47,6 +47,11 @@ static void handle_ext_workspace_activate(struct wl_listener *listener, void *data) { struct workspace *workspace = wl_container_of(listener, workspace, activate); + + if (workspace->m->isoverview) { + return; + } + goto_workspace(workspace); wlr_log(WLR_INFO, "ext activating workspace %d", workspace->tag); } @@ -55,6 +60,11 @@ static void handle_ext_workspace_deactivate(struct wl_listener *listener, void *data) { struct workspace *workspace = wl_container_of(listener, workspace, deactivate); + + if (workspace->m->isoverview) { + return; + } + toggle_workspace(workspace); wlr_log(WLR_INFO, "ext deactivating workspace %d", workspace->tag); } From 9d6436cf42e89835f40664d5aa93c44b016ed679 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 12 Nov 2025 12:55:06 +0800 Subject: [PATCH 03/34] feat: support keyboard shortcut inhibitor --- src/config/parse_config.h | 9 ++++ src/config/preset.h | 1 + src/mango.c | 88 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 97 insertions(+), 1 deletion(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 0e9e15c..a71d4f1 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -69,6 +69,7 @@ typedef struct { int isunglobal; int isglobal; int isoverlay; + int allow_shortcuts_inhibit; int ignore_maximize; int ignore_minimize; int isnosizehint; @@ -336,6 +337,7 @@ typedef struct { int syncobj_enable; int adaptive_sync; int allow_tearing; + int allow_shortcuts_inhibit; struct xkb_rule_names xkb_rules; @@ -1271,6 +1273,8 @@ void parse_option(Config *config, char *key, char *value) { config->adaptive_sync = atoi(value); } else if (strcmp(key, "allow_tearing") == 0) { config->allow_tearing = atoi(value); + } else if (strcmp(key, "allow_shortcuts_inhibit") == 0) { + config->allow_shortcuts_inhibit = atoi(value); } else if (strcmp(key, "no_border_when_single") == 0) { config->no_border_when_single = atoi(value); } else if (strcmp(key, "no_radius_when_single") == 0) { @@ -1708,6 +1712,7 @@ void parse_option(Config *config, char *key, char *value) { rule->isunglobal = -1; rule->isglobal = -1; rule->isoverlay = -1; + rule->allow_shortcuts_inhibit = -1; rule->ignore_maximize = -1; rule->ignore_minimize = -1; rule->isnosizehint = -1; @@ -1806,6 +1811,8 @@ void parse_option(Config *config, char *key, char *value) { rule->focused_opacity = atof(val); } else if (strcmp(key, "isoverlay") == 0) { rule->isoverlay = atoi(val); + } else if (strcmp(key, "allow_shortcuts_inhibit") == 0) { + rule->allow_shortcuts_inhibit = atoi(val); } else if (strcmp(key, "ignore_maximize") == 0) { rule->ignore_maximize = atoi(val); } else if (strcmp(key, "ignore_minimize") == 0) { @@ -2696,6 +2703,7 @@ void override_config(void) { syncobj_enable = CLAMP_INT(config.syncobj_enable, 0, 1); adaptive_sync = CLAMP_INT(config.adaptive_sync, 0, 1); allow_tearing = CLAMP_INT(config.allow_tearing, 0, 2); + allow_shortcuts_inhibit = CLAMP_INT(config.allow_shortcuts_inhibit, 0, 1); axis_bind_apply_timeout = CLAMP_INT(config.axis_bind_apply_timeout, 0, 1000); focus_on_activate = CLAMP_INT(config.focus_on_activate, 0, 1); @@ -2873,6 +2881,7 @@ void set_value_default() { config.syncobj_enable = syncobj_enable; config.adaptive_sync = adaptive_sync; config.allow_tearing = allow_tearing; + config.allow_shortcuts_inhibit = allow_shortcuts_inhibit; config.no_border_when_single = no_border_when_single; config.no_radius_when_single = no_radius_when_single; config.snap_distance = snap_distance; diff --git a/src/config/preset.h b/src/config/preset.h index be1c1b0..eaa7be2 100644 --- a/src/config/preset.h +++ b/src/config/preset.h @@ -105,6 +105,7 @@ int syncobj_enable = 0; int adaptive_sync = 0; double drag_refresh_interval = 30.0; int allow_tearing = TEARING_DISABLED; +int allow_shortcuts_inhibit = SHORTCUTS_INHIBIT_ENABLE; /* keyboard */ diff --git a/src/mango.c b/src/mango.c index 7ad9aa6..6a66d0a 100644 --- a/src/mango.c +++ b/src/mango.c @@ -48,6 +48,7 @@ #include #include #include +#include #include #include #include @@ -172,6 +173,11 @@ enum tearing_mode { TEARING_FULLSCREEN_ONLY, }; +enum seat_config_shortcuts_inhibit { + SHORTCUTS_INHIBIT_DISABLE, + SHORTCUTS_INHIBIT_ENABLE, +}; + typedef struct Pertag Pertag; typedef struct Monitor Monitor; typedef struct Client Client; @@ -369,6 +375,7 @@ struct Client { bool isleftstack; int tearing_hint; int force_tearing; + int allow_shortcuts_inhibit; }; typedef struct { @@ -398,6 +405,12 @@ typedef struct { struct wl_listener destroy; } KeyboardGroup; +typedef struct { + struct wlr_keyboard_shortcuts_inhibitor_v1 *inhibitor; + struct wl_listener destroy; + struct wl_list link; +} KeyboardShortcutsInhibitor; + typedef struct { /* Must keep these three elements in this order */ unsigned int type; /* LayerShell */ @@ -622,7 +635,9 @@ static void urgent(struct wl_listener *listener, void *data); static void view(const Arg *arg, bool want_animation); static void handlesig(int signo); - +static void +handle_keyboard_shortcuts_inhibit_new_inhibitor(struct wl_listener *listener, + void *data); static void virtualkeyboard(struct wl_listener *listener, void *data); static void virtualpointer(struct wl_listener *listener, void *data); static void warp_cursor(const Client *c); @@ -750,6 +765,8 @@ static struct wlr_idle_inhibit_manager_v1 *idle_inhibit_mgr; static struct wlr_layer_shell_v1 *layer_shell; static struct wlr_output_manager_v1 *output_mgr; static struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard_mgr; +static struct wlr_keyboard_shortcuts_inhibit_manager_v1 + *keyboard_shortcuts_inhibit; static struct wlr_virtual_pointer_manager_v1 *virtual_pointer_mgr; static struct wlr_output_power_manager_v1 *power_mgr; static struct wlr_pointer_gestures_v1 *pointer_gestures; @@ -773,6 +790,7 @@ static struct wlr_pointer_constraint_v1 *active_constraint; static struct wlr_seat *seat; static KeyboardGroup *kb_group; static struct wl_list inputdevices; +static struct wl_list keyboard_shortcut_inhibitors; static unsigned int cursor_mode; static Client *grabc; static int grabcx, grabcy; /* client-relative */ @@ -859,6 +877,8 @@ static struct wl_listener request_start_drag = {.notify = requeststartdrag}; static struct wl_listener start_drag = {.notify = startdrag}; 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}; #ifdef XWAYLAND static void activatex11(struct wl_listener *listener, void *data); @@ -1150,6 +1170,7 @@ static void apply_rule_properties(Client *c, const ConfigWinRule *r) { APPLY_INT_PROP(c, r, isnosizehint); APPLY_INT_PROP(c, r, isunglobal); APPLY_INT_PROP(c, r, noblur); + APPLY_INT_PROP(c, r, allow_shortcuts_inhibit); APPLY_FLOAT_PROP(c, r, scroller_proportion); APPLY_FLOAT_PROP(c, r, focused_opacity); @@ -1977,6 +1998,7 @@ void cleanuplisteners(void) { wl_list_remove(&start_drag.link); wl_list_remove(&new_session_lock.link); wl_list_remove(&tearing_new_object.link); + wl_list_remove(&keyboard_shortcuts_inhibit_new_inhibitor.link); if (drm_lease_manager) { wl_list_remove(&drm_lease_request.link); } @@ -3266,6 +3288,17 @@ int keyrepeat(void *data) { return 0; } +bool is_keyboard_shortcut_inhibitor(struct wlr_surface *surface) { + KeyboardShortcutsInhibitor *kbsinhibitor; + + wl_list_for_each(kbsinhibitor, &keyboard_shortcut_inhibitors, link) { + if (kbsinhibitor->inhibitor->surface == surface) { + return true; + } + } + return false; +} + int // 17 keybinding(unsigned int state, bool locked, unsigned int mods, xkb_keysym_t sym, unsigned int keycode) { @@ -3284,6 +3317,10 @@ keybinding(unsigned int state, bool locked, unsigned int mods, xkb_keysym_t sym, keycode == 62 || keycode == 108 || keycode == 105 || keycode == 134) return false; + if (is_keyboard_shortcut_inhibitor(seat->keyboard_state.focused_surface)) { + return false; + } + for (ji = 0; ji < config.key_bindings_count; ji++) { if (config.key_bindings_count < 1) break; @@ -3591,6 +3628,7 @@ void init_client_properties(Client *c) { c->allow_csd = 0; c->force_maximize = 0; c->force_tearing = 0; + c->allow_shortcuts_inhibit = SHORTCUTS_INHIBIT_ENABLE; } void // old fix to 0.5 @@ -5003,6 +5041,7 @@ void setup(void) { * to let us know when new input devices are available on the backend. */ wl_list_init(&inputdevices); + wl_list_init(&keyboard_shortcut_inhibitors); wl_signal_add(&backend->events.new_input, &new_input_device); virtual_keyboard_mgr = wlr_virtual_keyboard_manager_v1_create(dpy); wl_signal_add(&virtual_keyboard_mgr->events.new_virtual_keyboard, @@ -5032,6 +5071,10 @@ void setup(void) { kb_group = createkeyboardgroup(); wl_list_init(&kb_group->destroy.link); + keyboard_shortcuts_inhibit = wlr_keyboard_shortcuts_inhibit_v1_create(dpy); + wl_signal_add(&keyboard_shortcuts_inhibit->events.new_inhibitor, + &keyboard_shortcuts_inhibit_new_inhibitor); + output_mgr = wlr_output_manager_v1_create(dpy); wl_signal_add(&output_mgr->events.apply, &output_mgr_apply); wl_signal_add(&output_mgr->events.test, &output_mgr_test); @@ -5559,6 +5602,49 @@ void view(const Arg *arg, bool want_animation) { } } +static void +handle_keyboard_shortcuts_inhibitor_destroy(struct wl_listener *listener, + void *data) { + KeyboardShortcutsInhibitor *inhibitor = + wl_container_of(listener, inhibitor, destroy); + + wlr_log(WLR_DEBUG, "Removing keyboard shortcuts inhibitor"); + + wl_list_remove(&inhibitor->link); + wl_list_remove(&inhibitor->destroy.link); + free(inhibitor); +} + +void handle_keyboard_shortcuts_inhibit_new_inhibitor( + struct wl_listener *listener, void *data) { + + struct wlr_keyboard_shortcuts_inhibitor_v1 *inhibitor = data; + + if (allow_shortcuts_inhibit == SHORTCUTS_INHIBIT_DISABLE) { + return; + } + + // per-view, seat-agnostic config via criteria + Client *c = get_client_from_surface(inhibitor->surface); + if (c && !c->allow_shortcuts_inhibit) { + return; + } + + wlr_log(WLR_DEBUG, "Adding keyboard shortcuts inhibitor"); + + KeyboardShortcutsInhibitor *kbsinhibitor = + calloc(1, sizeof(KeyboardShortcutsInhibitor)); + + kbsinhibitor->inhibitor = inhibitor; + + kbsinhibitor->destroy.notify = handle_keyboard_shortcuts_inhibitor_destroy; + wl_signal_add(&inhibitor->events.destroy, &kbsinhibitor->destroy); + + wl_list_insert(&keyboard_shortcut_inhibitors, &kbsinhibitor->link); + + wlr_keyboard_shortcuts_inhibitor_v1_activate(inhibitor); +} + void virtualkeyboard(struct wl_listener *listener, void *data) { struct wlr_virtual_keyboard_v1 *kb = data; /* virtual keyboards shouldn't share keyboard group */ From 18ad32384b1ab75c2e92d42b4d8775ff11c31d2e Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 8 Nov 2025 10:35:16 +0800 Subject: [PATCH 04/34] feat: add global option scroller_ignore_proportion_single --- src/config/parse_config.h | 7 +++++++ src/config/preset.h | 1 + src/layout/horizontal.h | 6 +++++- src/layout/vertical.h | 9 ++++++++- 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 19e64b7..30e118e 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -193,6 +193,7 @@ typedef struct { int scroller_structs; float scroller_default_proportion; float scroller_default_proportion_single; + int scroller_ignore_proportion_single; int scroller_focus_center; int scroller_prefer_center; int edge_scroller_pointer_focus; @@ -1210,6 +1211,8 @@ void parse_option(Config *config, char *key, char *value) { config->scroller_default_proportion = atof(value); } else if (strcmp(key, "scroller_default_proportion_single") == 0) { config->scroller_default_proportion_single = atof(value); + } else if (strcmp(key, "scroller_ignore_proportion_single") == 0) { + config->scroller_ignore_proportion_single = atoi(value); } else if (strcmp(key, "scroller_focus_center") == 0) { config->scroller_focus_center = atoi(value); } else if (strcmp(key, "scroller_prefer_center") == 0) { @@ -2665,6 +2668,8 @@ void override_config(void) { CLAMP_FLOAT(config.scroller_default_proportion, 0.1f, 1.0f); scroller_default_proportion_single = CLAMP_FLOAT(config.scroller_default_proportion_single, 0.1f, 1.0f); + scroller_ignore_proportion_single = + CLAMP_INT(config.scroller_ignore_proportion_single, 0, 1); scroller_focus_center = CLAMP_INT(config.scroller_focus_center, 0, 1); scroller_prefer_center = CLAMP_INT(config.scroller_prefer_center, 0, 1); edge_scroller_pointer_focus = @@ -2852,6 +2857,8 @@ void set_value_default() { config.scroller_default_proportion = scroller_default_proportion; config.scroller_default_proportion_single = scroller_default_proportion_single; + config.scroller_ignore_proportion_single = + scroller_ignore_proportion_single; config.scroller_focus_center = scroller_focus_center; config.scroller_prefer_center = scroller_prefer_center; config.edge_scroller_pointer_focus = edge_scroller_pointer_focus; diff --git a/src/config/preset.h b/src/config/preset.h index 2f994ec..be1c1b0 100644 --- a/src/config/preset.h +++ b/src/config/preset.h @@ -60,6 +60,7 @@ float scratchpad_height_ratio = 0.9; int scroller_structs = 20; float scroller_default_proportion = 0.9; float scroller_default_proportion_single = 1.0; +int scroller_ignore_proportion_single = 0; int scroller_focus_center = 0; int scroller_prefer_center = 0; int focus_cross_monitor = 0; diff --git a/src/layout/horizontal.h b/src/layout/horizontal.h index b2d76bb..178ae9e 100644 --- a/src/layout/horizontal.h +++ b/src/layout/horizontal.h @@ -231,7 +231,7 @@ void scroller(Monitor *m) { } } - if (n == 1) { + if (n == 1 && !scroller_ignore_proportion_single) { c = tempClients[0]; target_geom.height = m->w.height - 2 * cur_gappov; target_geom.width = @@ -274,6 +274,10 @@ void scroller(Monitor *m) { } } + if (n == 1 && scroller_ignore_proportion_single) { + need_scroller = true; + } + if (start_drag_window) need_scroller = false; diff --git a/src/layout/vertical.h b/src/layout/vertical.h index 3c74463..6ae21a6 100644 --- a/src/layout/vertical.h +++ b/src/layout/vertical.h @@ -192,7 +192,7 @@ void vertical_scroller(Monitor *m) { } } - if (n == 1) { + if (n == 1 && !scroller_ignore_proportion_single) { c = tempClients[0]; target_geom.width = m->w.width - 2 * cur_gappoh; target_geom.height = @@ -235,6 +235,13 @@ void vertical_scroller(Monitor *m) { } } + if (n == 1 && scroller_ignore_proportion_single) { + need_scroller = true; + } + + if (start_drag_window) + need_scroller = false; + target_geom.width = m->w.width - 2 * cur_gappoh; target_geom.height = max_client_height * c->scroller_proportion; target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2; From d32fecfd2375ec9f89662b224230e23c13447be3 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 12 Nov 2025 23:10:09 +0800 Subject: [PATCH 05/34] fix: crash in some crossmon dispatch --- src/dispatch/bind_define.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 65c2b03..168852f 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -971,7 +971,7 @@ int tag(const Arg *arg) { } int tagmon(const Arg *arg) { - Monitor *m = NULL; + Monitor *m = NULL,*cm = NULL; Client *c = focustop(selmon); if (!c) @@ -980,11 +980,12 @@ int tagmon(const Arg *arg) { if (arg->i != UNDIR) { m = dirtomon(arg->i); } else if (arg->v) { - wl_list_for_each(m, &mons, link) { - if (!m->wlr_output->enabled) { + wl_list_for_each(cm, &mons, link) { + if (!cm->wlr_output->enabled) { continue; } - if (regex_match(arg->v, m->wlr_output->name)) { + if (regex_match(arg->v, cm->wlr_output->name)) { + m = cm; break; } } From 5774df00e09d43ffe6148e6c84541a31793786bf Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 8 Nov 2025 12:39:41 +0800 Subject: [PATCH 06/34] opt:optimize code struct --- src/mango.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mango.c b/src/mango.c index 0962520..96970aa 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1964,10 +1964,10 @@ void cleanuplisteners(void) { wl_list_remove(&request_start_drag.link); wl_list_remove(&start_drag.link); wl_list_remove(&new_session_lock.link); + wl_list_remove(&tearing_new_object.link); if (drm_lease_manager) { wl_list_remove(&drm_lease_request.link); } - wl_list_remove(&tearing_new_object.link); #ifdef XWAYLAND wl_list_remove(&new_xwayland_surface.link); wl_list_remove(&xwayland_ready.link); From e09748764d7f6501eeef71a4fff5b65cace0b88e Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 13 Nov 2025 10:42:35 +0800 Subject: [PATCH 07/34] opt: remove useless normalize keysym convert --- src/config/parse_config.h | 55 -------------------------------------- src/dispatch/bind_define.h | 2 +- src/mango.c | 4 +-- 3 files changed, 3 insertions(+), 58 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index a71d4f1..a6cfb64 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -517,61 +517,6 @@ long int parse_color(const char *hex_str) { return hex_num; } -xkb_keysym_t normalize_keysym(xkb_keysym_t sym) { - // 首先转换为小写(主要影响字母键) - sym = xkb_keysym_to_lower(sym); - - // 将数字小键盘键转换为普通数字键 - switch (sym) { - // 小键盘数字转换 - case XKB_KEY_KP_0: - return XKB_KEY_0; - case XKB_KEY_KP_1: - return XKB_KEY_1; - case XKB_KEY_KP_2: - return XKB_KEY_2; - case XKB_KEY_KP_3: - return XKB_KEY_3; - case XKB_KEY_KP_4: - return XKB_KEY_4; - case XKB_KEY_KP_5: - return XKB_KEY_5; - case XKB_KEY_KP_6: - return XKB_KEY_6; - case XKB_KEY_KP_7: - return XKB_KEY_7; - case XKB_KEY_KP_8: - return XKB_KEY_8; - case XKB_KEY_KP_9: - return XKB_KEY_9; - - // 将 Shift+数字 的符号转换回基础数字 - case XKB_KEY_exclam: - return XKB_KEY_1; // ! - case XKB_KEY_at: - return XKB_KEY_2; // @ - case XKB_KEY_numbersign: - return XKB_KEY_3; // # - case XKB_KEY_dollar: - return XKB_KEY_4; // $ - case XKB_KEY_percent: - return XKB_KEY_5; // % - case XKB_KEY_asciicircum: - return XKB_KEY_6; // ^ - case XKB_KEY_ampersand: - return XKB_KEY_7; // & - case XKB_KEY_asterisk: - return XKB_KEY_8; // * - case XKB_KEY_parenleft: - return XKB_KEY_9; // ( - case XKB_KEY_parenright: - return XKB_KEY_0; // ) - - default: - return sym; - } -} - // 辅助函数:检查字符串是否以指定的前缀开头(忽略大小写) static bool starts_with_ignore_case(const char *str, const char *prefix) { while (*prefix) { diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 168852f..d3ab629 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -971,7 +971,7 @@ int tag(const Arg *arg) { } int tagmon(const Arg *arg) { - Monitor *m = NULL,*cm = NULL; + Monitor *m = NULL, *cm = NULL; Client *c = focustop(selmon); if (!c) diff --git a/src/mango.c b/src/mango.c index 6a66d0a..eff7dde 100644 --- a/src/mango.c +++ b/src/mango.c @@ -3345,8 +3345,8 @@ keybinding(unsigned int state, bool locked, unsigned int mods, xkb_keysym_t sym, (strcmp(keymode.mode, k->mode) == 0)) && CLEANMASK(mods) == CLEANMASK(k->mod) && ((k->keysymcode.type == KEY_TYPE_SYM && - normalize_keysym(sym) == - normalize_keysym(k->keysymcode.keysym)) || + xkb_keysym_to_lower(sym) == + xkb_keysym_to_lower(k->keysymcode.keysym)) || (k->keysymcode.type == KEY_TYPE_CODE && (keycode == k->keysymcode.keycode.keycode1 || keycode == k->keysymcode.keycode.keycode2 || From 6010cea8051b82004392b092958b960d280271ce Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 8 Nov 2025 20:10:11 +0800 Subject: [PATCH 08/34] opt: optimize shadow node and blur node enable --- src/animation/client.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/animation/client.h b/src/animation/client.h index 90b664b..bf8864b 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -255,8 +255,12 @@ void client_draw_shadow(Client *c) { return; if (!shadows || (!c->isfloating && shadow_only_floating)) { - wlr_scene_shadow_set_size(c->shadow, 0, 0); + if (c->shadow->node.enabled) + wlr_scene_node_set_enabled(&c->shadow->node, false); return; + } else { + if (c->scene_surface->node.enabled && !c->shadow->node.enabled) + wlr_scene_node_set_enabled(&c->shadow->node, true); } bool hit_no_border = check_hit_no_border(c); From 050171960420c438d7345395f4752fa5232cda59 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 13 Nov 2025 10:49:44 +0800 Subject: [PATCH 09/34] opt: spawn_on_empty and toggle_named_scratchapd use spawn_shell --- 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 d3ab629..27f7977 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -805,7 +805,7 @@ int spawn_on_empty(const Arg *arg) { return 0; } else { view(arg, true); - spawn(arg); + spawn_shell(arg); } return 0; } @@ -1088,7 +1088,7 @@ int toggle_named_scratchpad(const Arg *arg) { if (!target_client && arg->v3) { Arg arg_spawn = {.v = arg->v3}; - spawn(&arg_spawn); + spawn_shell(&arg_spawn); return 0; } From 64dc30dc31d585e6e5d2c3547488282b124130f6 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 8 Nov 2025 20:17:16 +0800 Subject: [PATCH 10/34] opt: disable resize scroller window when it force to default single size --- src/layout/arrange.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/layout/arrange.h b/src/layout/arrange.h index 4df9786..b53592f 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -374,6 +374,10 @@ void resize_tile_scroller(Client *grabc, bool isdrag, int offsetx, int offsety, float delta_x, delta_y; float new_scroller_proportion; + if (grabc && grabc->mon->visible_tiling_clients == 1 && + !scroller_ignore_proportion_single) + return; + if (!start_drag_window && isdrag) { drag_begin_cursorx = cursor->x; drag_begin_cursory = cursor->y; From 17f1ae2463146d2525a389121df5129316a538fa Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 13 Nov 2025 10:55:44 +0800 Subject: [PATCH 11/34] opt: optmize restore_minimized size and not restore namedscratchpad --- src/dispatch/bind_define.h | 2 +- src/mango.c | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 27f7977..d653bb1 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -476,7 +476,7 @@ int restore_minimized(const Arg *arg) { } wl_list_for_each(c, &clients, link) { - if (c->isminied) { + if (c->isminied && !c->isnamedscratchpad) { c->is_scratchpad_show = 0; c->is_in_scratchpad = 0; c->isnamedscratchpad = 0; diff --git a/src/mango.c b/src/mango.c index eff7dde..aa22de3 100644 --- a/src/mango.c +++ b/src/mango.c @@ -4793,8 +4793,11 @@ void setsel(struct wl_listener *listener, void *data) { } void show_hide_client(Client *c) { + unsigned int target = 1; + + set_size_per(c->mon, c); + target = get_tags_first_tag(c->oldtags); - unsigned int target = get_tags_first_tag(c->oldtags); if (!c->is_in_scratchpad) { tag_client(&(Arg){.ui = target}, c); } else { From 5ba7da0570b0f66e69604e1a06b24b45114d455c Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 8 Nov 2025 22:47:18 +0800 Subject: [PATCH 12/34] opt: remove increase_proportion dispatch should use resizewin to replace it --- src/config/parse_config.h | 3 --- src/dispatch/bind_declare.h | 1 - src/dispatch/bind_define.h | 12 ------------ 3 files changed, 16 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 30e118e..321de20 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -887,9 +887,6 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value, } else if (strcmp(func_name, "set_proportion") == 0) { func = set_proportion; (*arg).f = atof(arg_value); - } else if (strcmp(func_name, "increase_proportion") == 0) { - func = increase_proportion; - (*arg).f = atof(arg_value); } else if (strcmp(func_name, "switch_proportion_preset") == 0) { func = switch_proportion_preset; } else if (strcmp(func_name, "viewtoleft") == 0) { diff --git a/src/dispatch/bind_declare.h b/src/dispatch/bind_declare.h index b38e2da..5bc215a 100644 --- a/src/dispatch/bind_declare.h +++ b/src/dispatch/bind_declare.h @@ -4,7 +4,6 @@ int toggle_scratchpad(const Arg *arg); int focusdir(const Arg *arg); int toggleoverview(const Arg *arg); int set_proportion(const Arg *arg); -int increase_proportion(const Arg *arg); int switch_proportion_preset(const Arg *arg); int zoom(const Arg *arg); int tagsilent(const Arg *arg); diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 57288b4..464e2a6 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -281,18 +281,6 @@ int incovgaps(const Arg *arg) { return 0; } -int increase_proportion(const Arg *arg) { - if (selmon->sel) { - unsigned int max_client_width = - selmon->w.width - 2 * scroller_structs - gappih; - selmon->sel->scroller_proportion = - MIN(MAX(arg->f + selmon->sel->scroller_proportion, 0.1), 1.0); - selmon->sel->geom.width = max_client_width * arg->f; - arrange(selmon, false); - } - return 0; -} - int setmfact(const Arg *arg) { float f; Client *c = NULL; From 16296898ce694ced439b1bd2aa2080b45859c51f Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 14 Nov 2025 11:53:41 +0800 Subject: [PATCH 13/34] fix: tagrule not apply correctly --- src/config/parse_config.h | 46 +++++++++++++++++++++------------------ src/mango.c | 13 +---------- 2 files changed, 26 insertions(+), 33 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index a6cfb64..59cb37b 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -3165,33 +3165,37 @@ void reapply_master(void) { } } +void parse_tagrule(Monitor *m) { + int i, jk; + + for (i = 0; i < config.tag_rules_count; i++) { + + if (config.tag_rules_count > 0 && + (!config.tag_rules[i].monitor_name || + regex_match(config.tag_rules[i].monitor_name, + m->wlr_output->name))) { + + for (jk = 0; jk < LENGTH(layouts); jk++) { + if (config.tag_rules[i].layout_name && + strcmp(layouts[jk].name, config.tag_rules[i].layout_name) == + 0) { + m->pertag->ltidxs[config.tag_rules[i].id] = &layouts[jk]; + } + } + + m->pertag->no_hide[config.tag_rules[i].id] = + config.tag_rules[i].no_hide; + } + } +} + void reapply_tagrule(void) { Monitor *m = NULL; - int i, jk; - char *rule_monitor_name = NULL; wl_list_for_each(m, &mons, link) { if (!m->wlr_output->enabled) { continue; } - - // apply tag rule - for (i = 1; i <= config.tag_rules_count; i++) { - rule_monitor_name = config.tag_rules[i - 1].monitor_name; - if (regex_match(rule_monitor_name, m->wlr_output->name) || - !rule_monitor_name) { - for (jk = 0; jk < LENGTH(layouts); jk++) { - if (config.tag_rules_count > 0 && - config.tag_rules[i - 1].layout_name && - strcmp(layouts[jk].name, - config.tag_rules[i - 1].layout_name) == 0) { - m->pertag->ltidxs[config.tag_rules[i - 1].id] = - &layouts[jk]; - m->pertag->no_hide[config.tag_rules[i - 1].id] = - config.tag_rules[i - 1].no_hide; - } - } - } - } + parse_tagrule(m); } } diff --git a/src/mango.c b/src/mango.c index aa22de3..2ab3eab 100644 --- a/src/mango.c +++ b/src/mango.c @@ -2694,18 +2694,7 @@ void createmon(struct wl_listener *listener, void *data) { } // apply tag rule - for (i = 1; i <= config.tag_rules_count; i++) { - for (jk = 0; jk < LENGTH(layouts); jk++) { - if (config.tag_rules_count > 0 && - config.tag_rules[i - 1].layout_name && - strcmp(layouts[jk].name, config.tag_rules[i - 1].layout_name) == - 0) { - m->pertag->ltidxs[config.tag_rules[i - 1].id] = &layouts[jk]; - m->pertag->no_hide[config.tag_rules[i - 1].id] = - config.tag_rules[i - 1].no_hide; - } - } - } + parse_tagrule(m); /* The xdg-protocol specifies: * From 33a0fe24850785b57c97308d3c15553a41920f8a Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 8 Nov 2025 22:48:53 +0800 Subject: [PATCH 14/34] opt: not resizewin in overview --- src/layout/arrange.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/layout/arrange.h b/src/layout/arrange.h index b53592f..aafb79e 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -479,6 +479,9 @@ void resize_tile_client(Client *grabc, bool isdrag, int offsetx, int offsety, if (!grabc || grabc->isfullscreen || grabc->ismaximizescreen) return; + if (grabc->mon->isoverview) + return; + const Layout *current_layout = grabc->mon->pertag->ltidxs[grabc->mon->pertag->curtag]; if (current_layout->id == TILE || current_layout->id == DECK || From d85f4375c8fee672e376e81df6e42f2043176988 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 14 Nov 2025 12:15:34 +0800 Subject: [PATCH 15/34] fix: fix border color change when swithc mon focus --- src/animation/client.h | 2 +- src/dispatch/bind_define.h | 2 +- src/fetch/client.h | 5 ++++- src/mango.c | 9 +++++++-- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/animation/client.h b/src/animation/client.h index bf8864b..e3673a2 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -1060,7 +1060,7 @@ void client_set_focused_opacity_animation(Client *c) { c->opacity_animation.running = true; } -void cleint_set_unfocused_opacity_animation(Client *c) { +void client_set_unfocused_opacity_animation(Client *c) { // Start border color animation to unfocused float *border_color = get_border_color(c); diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index d653bb1..c812bf1 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -202,7 +202,7 @@ int focusmon(const Arg *arg) { focusclient(c, 1); if (old_selmon_sel) { - setborder_color(old_selmon_sel); + client_set_unfocused_opacity_animation(old_selmon_sel); } return 0; } diff --git a/src/fetch/client.h b/src/fetch/client.h index 6e67522..5f62d23 100644 --- a/src/fetch/client.h +++ b/src/fetch/client.h @@ -374,7 +374,10 @@ Client *get_next_stack_client(Client *c, bool reverse) { } float *get_border_color(Client *c) { - if (c->isurgent) { + + if (c->mon != selmon) { + return bordercolor; + } else if (c->isurgent) { return urgentcolor; } else if (c->is_in_scratchpad && selmon && c == selmon->sel) { return scratchpadcolor; diff --git a/src/mango.c b/src/mango.c index 2ab3eab..7bb3bb2 100644 --- a/src/mango.c +++ b/src/mango.c @@ -3092,6 +3092,9 @@ void destroykeyboardgroup(struct wl_listener *listener, void *data) { } void focusclient(Client *c, int lift) { + + Client *last_focus_client = NULL; + struct wlr_surface *old_keyboard_focus_surface = seat->keyboard_state.focused_surface; @@ -3126,12 +3129,14 @@ void focusclient(Client *c, int lift) { if (c && !c->iskilling && !client_is_unmanaged(c) && c->mon) { + last_focus_client = selmon->sel; selmon = c->mon; selmon->prevsel = selmon->sel; selmon->sel = c; - if (selmon->prevsel && !selmon->prevsel->iskilling) { - cleint_set_unfocused_opacity_animation(selmon->prevsel); + if (last_focus_client && !last_focus_client->iskilling && + last_focus_client != c) { + client_set_unfocused_opacity_animation(last_focus_client); } client_set_focused_opacity_animation(c); From f542d5d5e6d9999b627ffd5f55bf4a2085a36041 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 8 Nov 2025 23:26:52 +0800 Subject: [PATCH 16/34] opt: disable switch proportion action in some case --- src/dispatch/bind_define.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 464e2a6..65c2b03 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -518,6 +518,14 @@ int setkeymode(const Arg *arg) { } int set_proportion(const Arg *arg) { + + if (selmon->isoverview || !is_scroller_layout(selmon)) + return 0; + + if (selmon->visible_tiling_clients == 1 && + !scroller_ignore_proportion_single) + return 0; + if (selmon->sel) { unsigned int max_client_width = selmon->w.width - 2 * scroller_structs - gappih; @@ -919,6 +927,13 @@ int switch_proportion_preset(const Arg *arg) { return 0; } + if (selmon->isoverview || !is_scroller_layout(selmon)) + return 0; + + if (selmon->visible_tiling_clients == 1 && + !scroller_ignore_proportion_single) + return 0; + if (selmon->sel) { for (int i = 0; i < config.scroller_proportion_preset_count; i++) { From 514c9166785274ed525ecc3ef4697d71c47b2ab4 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 14 Nov 2025 12:32:07 +0800 Subject: [PATCH 17/34] opt: optimize code struct --- src/config/parse_config.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 59cb37b..6358083 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -3167,24 +3167,24 @@ void reapply_master(void) { void parse_tagrule(Monitor *m) { int i, jk; + ConfigTagRule tr; for (i = 0; i < config.tag_rules_count; i++) { + tr = config.tag_rules[i]; + if (config.tag_rules_count > 0 && - (!config.tag_rules[i].monitor_name || - regex_match(config.tag_rules[i].monitor_name, - m->wlr_output->name))) { + (!tr.monitor_name || + regex_match(tr.monitor_name, m->wlr_output->name))) { for (jk = 0; jk < LENGTH(layouts); jk++) { - if (config.tag_rules[i].layout_name && - strcmp(layouts[jk].name, config.tag_rules[i].layout_name) == - 0) { - m->pertag->ltidxs[config.tag_rules[i].id] = &layouts[jk]; + if (tr.layout_name && + strcmp(layouts[jk].name, tr.layout_name) == 0) { + m->pertag->ltidxs[tr.id] = &layouts[jk]; } } - m->pertag->no_hide[config.tag_rules[i].id] = - config.tag_rules[i].no_hide; + m->pertag->no_hide[tr.id] = tr.no_hide; } } } From 8875156760b48d82c209cff895b61aa5b3817f4a Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 9 Nov 2025 15:40:51 +0800 Subject: [PATCH 18/34] feat: support nofucs rule for some special window --- src/config/parse_config.h | 4 ++++ src/mango.c | 42 +++++++++++++++++++++++---------------- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 321de20..0e9e15c 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -77,6 +77,7 @@ typedef struct { int offsety; int width; int height; + int nofocus; int nofadein; int nofadeout; int no_force_center; @@ -1716,6 +1717,7 @@ void parse_option(Config *config, char *key, char *value) { rule->force_tearing = -1; rule->noswallow = -1; rule->noblur = -1; + rule->nofocus = -1; rule->nofadein = -1; rule->nofadeout = -1; rule->no_force_center = -1; @@ -1770,6 +1772,8 @@ void parse_option(Config *config, char *key, char *value) { rule->offsetx = atoi(val); } else if (strcmp(key, "offsety") == 0) { rule->offsety = atoi(val); + } else if (strcmp(key, "nofocus") == 0) { + rule->nofocus = atoi(val); } else if (strcmp(key, "nofadein") == 0) { rule->nofadein = atoi(val); } else if (strcmp(key, "nofadeout") == 0) { diff --git a/src/mango.c b/src/mango.c index 96970aa..f7440f4 100644 --- a/src/mango.c +++ b/src/mango.c @@ -352,6 +352,7 @@ struct Client { bool drag_to_tile; bool scratchpad_switching_mon; bool fake_no_border; + int nofocus; int nofadein; int nofadeout; int no_force_center; @@ -1130,6 +1131,7 @@ static void apply_rule_properties(Client *c, const ConfigWinRule *r) { APPLY_INT_PROP(c, r, force_maximize); APPLY_INT_PROP(c, r, force_tearing); APPLY_INT_PROP(c, r, noswallow); + APPLY_INT_PROP(c, r, nofocus); APPLY_INT_PROP(c, r, nofadein); APPLY_INT_PROP(c, r, nofadeout); APPLY_INT_PROP(c, r, no_force_center); @@ -1243,11 +1245,16 @@ void applyrules(Client *c) { const char *appid, *title; unsigned int i, newtags = 0; const ConfigWinRule *r; - Monitor *mon = selmon, *m = NULL; + Monitor *m = NULL; Client *fc = NULL; bool hit_rule_pos = false; + Client *parent = NULL; - c->isfloating = client_is_float_type(c); + parent = client_get_parent(c); + + Monitor *mon = parent && parent->mon ? parent->mon : selmon; + + c->isfloating = client_is_float_type(c) || parent; if (!(appid = client_get_appid(c))) appid = broken; if (!(title = client_get_title(c))) @@ -1264,8 +1271,14 @@ void applyrules(Client *c) { // set general properties apply_rule_properties(c, r); - // set tags - newtags |= (r->tags > 0) ? r->tags : 0; + // // set tags + if (r->tags > 0) { + newtags |= r->tags; + } else if (parent) { + newtags = parent->tags; + } else { + newtags |= 0; + } // set monitor of client wl_list_for_each(m, &mons, link) { @@ -1334,8 +1347,9 @@ void applyrules(Client *c) { int fullscreen_state_backup = c->isfullscreen || client_wants_fullscreen(c); setmon(c, mon, newtags, - !c->isopensilent && (!c->istagsilent || !newtags || - newtags & mon->tagset[mon->seltags])); + !c->isopensilent && !client_should_ignore_focus(c) && + (!c->istagsilent || !newtags || + newtags & mon->tagset[mon->seltags])); if (c->mon && !(c->mon == selmon && c->tags & c->mon->tagset[c->mon->seltags]) && @@ -3084,6 +3098,9 @@ void focusclient(Client *c, int lift) { if (c && client_should_ignore_focus(c) && client_is_x11_popup(c)) return; + if (c && c->nofocus) + return; + /* Raise client in stacking order if requested */ if (c && lift) wlr_scene_node_raise_to_top(&c->scene->node); // 将视图提升到顶层 @@ -3560,6 +3577,7 @@ void init_client_properties(Client *c) { c->fake_no_border = false; c->focused_opacity = focused_opacity; c->unfocused_opacity = unfocused_opacity; + c->nofocus = 0; c->nofadein = 0; c->nofadeout = 0; c->no_force_center = 0; @@ -3580,7 +3598,6 @@ void init_client_properties(Client *c) { void // old fix to 0.5 mapnotify(struct wl_listener *listener, void *data) { /* Called when the surface is mapped, or ready to display on-screen. */ - Client *p = NULL; Client *at_client = NULL; Client *c = wl_container_of(listener, c, map); /* Create scene tree for this client and its border */ @@ -3672,16 +3689,7 @@ mapnotify(struct wl_listener *listener, void *data) { wl_list_insert(clients.prev, &c->link); // 尾部入栈 wl_list_insert(&fstack, &c->flink); - /* Set initial monitor, tags, floating status, and focus: - * we always consider floating, clients that have parent and thus - * we set the same tags and monitor than its parent, if not - * try to apply rules for them */ - if ((p = client_get_parent(c))) { - c->isfloating = 1; - setmon(c, p->mon, p->tags, true); - } else { - applyrules(c); - } + applyrules(c); client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | WLR_EDGE_RIGHT); From 407c9d74a42763f56817af2ee4821c92e9eba4f7 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 14 Nov 2025 15:39:04 +0800 Subject: [PATCH 19/34] feat: add windowrule option scroller_proportion_single --- src/config/parse_config.h | 4 ++++ src/layout/horizontal.h | 9 +++++++-- src/layout/vertical.h | 10 ++++++++-- src/mango.c | 3 +++ 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 6358083..074660b 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -90,6 +90,7 @@ typedef struct { int noblur; float focused_opacity; float unfocused_opacity; + float scroller_proportion_single; uint32_t passmod; xkb_keysym_t keysym; KeyBinding globalkeybinding; @@ -1679,6 +1680,7 @@ void parse_option(Config *config, char *key, char *value) { // float rule value, relay to a client property rule->focused_opacity = 0; rule->unfocused_opacity = 0; + rule->scroller_proportion_single = 0.0f; rule->scroller_proportion = 0; // special rule value,not directly set to client property @@ -1750,6 +1752,8 @@ void parse_option(Config *config, char *key, char *value) { rule->isunglobal = atoi(val); } else if (strcmp(key, "isglobal") == 0) { rule->isglobal = atoi(val); + } else if (strcmp(key, "scroller_proportion_single") == 0) { + rule->scroller_proportion_single = atof(val); } else if (strcmp(key, "unfocused_opacity") == 0) { rule->unfocused_opacity = atof(val); } else if (strcmp(key, "focused_opacity") == 0) { diff --git a/src/layout/horizontal.h b/src/layout/horizontal.h index 178ae9e..934dc13 100644 --- a/src/layout/horizontal.h +++ b/src/layout/horizontal.h @@ -192,6 +192,7 @@ void deck(Monitor *m) { // 滚动布局 void scroller(Monitor *m) { unsigned int i, n, j; + float single_proportion = 1.0; Client *c = NULL, *root_client = NULL; Client **tempClients = NULL; // 初始化为 NULL @@ -233,9 +234,13 @@ void scroller(Monitor *m) { if (n == 1 && !scroller_ignore_proportion_single) { c = tempClients[0]; + + single_proportion = c->scroller_proportion_single > 0.0f + ? c->scroller_proportion_single + : scroller_default_proportion_single; + target_geom.height = m->w.height - 2 * cur_gappov; - target_geom.width = - (m->w.width - 2 * cur_gappoh) * scroller_default_proportion_single; + target_geom.width = (m->w.width - 2 * cur_gappoh) * single_proportion; target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2; target_geom.y = m->w.y + (m->w.height - target_geom.height) / 2; resize(c, target_geom, 0); diff --git a/src/layout/vertical.h b/src/layout/vertical.h index 6ae21a6..46ca3b6 100644 --- a/src/layout/vertical.h +++ b/src/layout/vertical.h @@ -157,6 +157,8 @@ void vertical_deck(Monitor *m) { void vertical_scroller(Monitor *m) { unsigned int i, n, j; + float single_proportion = 1.0; + Client *c = NULL, *root_client = NULL; Client **tempClients = NULL; struct wlr_box target_geom; @@ -194,9 +196,13 @@ void vertical_scroller(Monitor *m) { if (n == 1 && !scroller_ignore_proportion_single) { c = tempClients[0]; + + single_proportion = c->scroller_proportion_single > 0.0f + ? c->scroller_proportion_single + : scroller_default_proportion_single; + target_geom.width = m->w.width - 2 * cur_gappoh; - target_geom.height = - (m->w.height - 2 * cur_gappov) * scroller_default_proportion_single; + target_geom.height = (m->w.height - 2 * cur_gappov) * single_proportion; target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2; target_geom.y = m->w.y + (m->w.height - target_geom.height) / 2; resize(c, target_geom, 0); diff --git a/src/mango.c b/src/mango.c index 7bb3bb2..601c36f 100644 --- a/src/mango.c +++ b/src/mango.c @@ -376,6 +376,7 @@ struct Client { int tearing_hint; int force_tearing; int allow_shortcuts_inhibit; + float scroller_proportion_single; }; typedef struct { @@ -1173,6 +1174,7 @@ static void apply_rule_properties(Client *c, const ConfigWinRule *r) { APPLY_INT_PROP(c, r, allow_shortcuts_inhibit); APPLY_FLOAT_PROP(c, r, scroller_proportion); + APPLY_FLOAT_PROP(c, r, scroller_proportion_single); APPLY_FLOAT_PROP(c, r, focused_opacity); APPLY_FLOAT_PROP(c, r, unfocused_opacity); @@ -3623,6 +3625,7 @@ void init_client_properties(Client *c) { c->force_maximize = 0; c->force_tearing = 0; c->allow_shortcuts_inhibit = SHORTCUTS_INHIBIT_ENABLE; + c->scroller_proportion_single = 0.0f; } void // old fix to 0.5 From f2b98352431838fdadd60e4fb5b8475027ffe2fe Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 9 Nov 2025 23:19:49 +0800 Subject: [PATCH 20/34] opt: remove useless code --- src/mango.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/mango.c b/src/mango.c index f7440f4..7ad9aa6 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1276,8 +1276,6 @@ void applyrules(Client *c) { newtags |= r->tags; } else if (parent) { newtags = parent->tags; - } else { - newtags |= 0; } // set monitor of client From fce47b37d941808260c757fc68658d1b54fd1f01 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 14 Nov 2025 17:13:26 +0800 Subject: [PATCH 21/34] opt: optimize init focus for x11 window --- src/mango.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mango.c b/src/mango.c index 601c36f..06e1d61 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1368,7 +1368,8 @@ void applyrules(Client *c) { int fullscreen_state_backup = c->isfullscreen || client_wants_fullscreen(c); setmon(c, mon, newtags, - !c->isopensilent && !client_should_ignore_focus(c) && + !c->isopensilent && + !(client_is_x11_popup(c) && client_should_ignore_focus(c)) && (!c->istagsilent || !newtags || newtags & mon->tagset[mon->seltags])); From ecab47fe929519349735ce489b69b8e8744387fb Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 10 Nov 2025 14:30:20 +0800 Subject: [PATCH 22/34] update readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c90cee1..8e982b6 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ emerge --ask --verbose gui-wm/mangowc ## Other ```bash -git clone -b 0.19.1 https://gitlab.freedesktop.org/wlroots/wlroots.git +git clone -b 0.19.2 https://gitlab.freedesktop.org/wlroots/wlroots.git cd wlroots meson build -Dprefix=/usr sudo ninja -C build install @@ -119,7 +119,7 @@ sudo ninja -C build install ## Suggested Tools -### integrated component +### Hybrid component - [dms-shell](https://github.com/AvengeMedia/DankMaterialShell) ### Independent component From cdcc64ab5fc656e1d314f719f5a6e416bbdcb04c Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 15 Nov 2025 00:08:57 +0800 Subject: [PATCH 23/34] feat: support scroll maximize and fullscreen window --- src/dispatch/bind_define.h | 11 +++- src/ext-protocol/dwl-ipc.h | 1 + src/fetch/client.h | 113 ++++++++++++++++++++----------------- src/layout/arrange.h | 5 ++ src/layout/horizontal.h | 71 ++++++++++++++++++----- src/layout/vertical.h | 72 ++++++++++++++++++----- src/mango.c | 45 ++++++++++++--- 7 files changed, 228 insertions(+), 90 deletions(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index c812bf1..d79a7c6 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -80,8 +80,12 @@ int defaultgaps(const Arg *arg) { int exchange_client(const Arg *arg) { Client *c = selmon->sel; - if (!c || c->isfloating || c->isfullscreen || c->ismaximizescreen) + if (!c || c->isfloating) return 0; + + if ((c->isfullscreen || c->ismaximizescreen) && !is_scroller_layout(c->mon)) + return 0; + exchange_two_client(c, direction_select(arg)); return 0; } @@ -497,7 +501,7 @@ int setlayout(const Arg *arg) { for (jk = 0; jk < LENGTH(layouts); jk++) { if (strcmp(layouts[jk].name, arg->v) == 0) { selmon->pertag->ltidxs[selmon->pertag->curtag] = &layouts[jk]; - + clear_fullscreen_and_maximized_state(selmon); arrange(selmon, false); printstatus(); return 0; @@ -901,7 +905,7 @@ int switch_layout(const Arg *arg) { break; } } - + clear_fullscreen_and_maximized_state(selmon); arrange(selmon, false); printstatus(); return 0; @@ -912,6 +916,7 @@ int switch_layout(const Arg *arg) { selmon->pertag->ltidxs[selmon->pertag->curtag]->name) == 0) { selmon->pertag->ltidxs[selmon->pertag->curtag] = jk == LENGTH(layouts) - 1 ? &layouts[0] : &layouts[jk + 1]; + clear_fullscreen_and_maximized_state(selmon); arrange(selmon, false); printstatus(); return 0; diff --git a/src/ext-protocol/dwl-ipc.h b/src/ext-protocol/dwl-ipc.h index 6b0c4c5..15b2376 100644 --- a/src/ext-protocol/dwl-ipc.h +++ b/src/ext-protocol/dwl-ipc.h @@ -258,6 +258,7 @@ void dwl_ipc_output_set_layout(struct wl_client *client, index = 0; monitor->pertag->ltidxs[monitor->pertag->curtag] = &layouts[index]; + clear_fullscreen_and_maximized_state(monitor); arrange(monitor, false); printstatus(); } diff --git a/src/fetch/client.h b/src/fetch/client.h index 5f62d23..c2b0abd 100644 --- a/src/fetch/client.h +++ b/src/fetch/client.h @@ -142,7 +142,7 @@ Client *center_tiled_select(Monitor *m) { return target_c; } Client *find_client_by_direction(Client *tc, const Arg *arg, bool findfloating, - bool align) { + bool ignore_align) { Client *c = NULL; Client **tempClients = NULL; // 初始化为 NULL int last = -1; @@ -185,21 +185,23 @@ Client *find_client_by_direction(Client *tc, const Arg *arg, bool findfloating, switch (arg->i) { case UP: - for (int _i = 0; _i <= last; _i++) { - if (tempClients[_i]->geom.y < sel_y && - tempClients[_i]->geom.x == sel_x && - tempClients[_i]->mon == tc->mon) { - int dis_x = tempClients[_i]->geom.x - sel_x; - int dis_y = tempClients[_i]->geom.y - sel_y; - long long int tmp_distance = - dis_x * dis_x + dis_y * dis_y; // 计算距离 - if (tmp_distance < distance) { - distance = tmp_distance; - tempFocusClients = tempClients[_i]; + if (!ignore_align) { + for (int _i = 0; _i <= last; _i++) { + if (tempClients[_i]->geom.y < sel_y && + tempClients[_i]->geom.x == sel_x && + tempClients[_i]->mon == tc->mon) { + int dis_x = tempClients[_i]->geom.x - sel_x; + int dis_y = tempClients[_i]->geom.y - sel_y; + long long int tmp_distance = + dis_x * dis_x + dis_y * dis_y; // 计算距离 + if (tmp_distance < distance) { + distance = tmp_distance; + tempFocusClients = tempClients[_i]; + } } } } - if (!tempFocusClients && !align) { + if (!tempFocusClients) { for (int _i = 0; _i <= last; _i++) { if (tempClients[_i]->geom.y < sel_y) { int dis_x = tempClients[_i]->geom.x - sel_x; @@ -215,21 +217,23 @@ Client *find_client_by_direction(Client *tc, const Arg *arg, bool findfloating, } break; case DOWN: - for (int _i = 0; _i <= last; _i++) { - if (tempClients[_i]->geom.y > sel_y && - tempClients[_i]->geom.x == sel_x && - tempClients[_i]->mon == tc->mon) { - int dis_x = tempClients[_i]->geom.x - sel_x; - int dis_y = tempClients[_i]->geom.y - sel_y; - long long int tmp_distance = - dis_x * dis_x + dis_y * dis_y; // 计算距离 - if (tmp_distance < distance) { - distance = tmp_distance; - tempFocusClients = tempClients[_i]; + if (!ignore_align) { + for (int _i = 0; _i <= last; _i++) { + if (tempClients[_i]->geom.y > sel_y && + tempClients[_i]->geom.x == sel_x && + tempClients[_i]->mon == tc->mon) { + int dis_x = tempClients[_i]->geom.x - sel_x; + int dis_y = tempClients[_i]->geom.y - sel_y; + long long int tmp_distance = + dis_x * dis_x + dis_y * dis_y; // 计算距离 + if (tmp_distance < distance) { + distance = tmp_distance; + tempFocusClients = tempClients[_i]; + } } } } - if (!tempFocusClients && !align) { + if (!tempFocusClients) { for (int _i = 0; _i <= last; _i++) { if (tempClients[_i]->geom.y > sel_y) { int dis_x = tempClients[_i]->geom.x - sel_x; @@ -245,21 +249,23 @@ Client *find_client_by_direction(Client *tc, const Arg *arg, bool findfloating, } break; case LEFT: - for (int _i = 0; _i <= last; _i++) { - if (tempClients[_i]->geom.x < sel_x && - tempClients[_i]->geom.y == sel_y && - tempClients[_i]->mon == tc->mon) { - int dis_x = tempClients[_i]->geom.x - sel_x; - int dis_y = tempClients[_i]->geom.y - sel_y; - long long int tmp_distance = - dis_x * dis_x + dis_y * dis_y; // 计算距离 - if (tmp_distance < distance) { - distance = tmp_distance; - tempFocusClients = tempClients[_i]; + if (!ignore_align) { + for (int _i = 0; _i <= last; _i++) { + if (tempClients[_i]->geom.x < sel_x && + tempClients[_i]->geom.y == sel_y && + tempClients[_i]->mon == tc->mon) { + int dis_x = tempClients[_i]->geom.x - sel_x; + int dis_y = tempClients[_i]->geom.y - sel_y; + long long int tmp_distance = + dis_x * dis_x + dis_y * dis_y; // 计算距离 + if (tmp_distance < distance) { + distance = tmp_distance; + tempFocusClients = tempClients[_i]; + } } } } - if (!tempFocusClients && !align) { + if (!tempFocusClients) { for (int _i = 0; _i <= last; _i++) { if (tempClients[_i]->geom.x < sel_x) { int dis_x = tempClients[_i]->geom.x - sel_x; @@ -275,21 +281,23 @@ Client *find_client_by_direction(Client *tc, const Arg *arg, bool findfloating, } break; case RIGHT: - for (int _i = 0; _i <= last; _i++) { - if (tempClients[_i]->geom.x > sel_x && - tempClients[_i]->geom.y == sel_y && - tempClients[_i]->mon == tc->mon) { - int dis_x = tempClients[_i]->geom.x - sel_x; - int dis_y = tempClients[_i]->geom.y - sel_y; - long long int tmp_distance = - dis_x * dis_x + dis_y * dis_y; // 计算距离 - if (tmp_distance < distance) { - distance = tmp_distance; - tempFocusClients = tempClients[_i]; + if (!ignore_align) { + for (int _i = 0; _i <= last; _i++) { + if (tempClients[_i]->geom.x > sel_x && + tempClients[_i]->geom.y == sel_y && + tempClients[_i]->mon == tc->mon) { + int dis_x = tempClients[_i]->geom.x - sel_x; + int dis_y = tempClients[_i]->geom.y - sel_y; + long long int tmp_distance = + dis_x * dis_x + dis_y * dis_y; // 计算距离 + if (tmp_distance < distance) { + distance = tmp_distance; + tempFocusClients = tempClients[_i]; + } } } } - if (!tempFocusClients && !align) { + if (!tempFocusClients) { for (int _i = 0; _i <= last; _i++) { if (tempClients[_i]->geom.x > sel_x) { int dis_x = tempClients[_i]->geom.x - sel_x; @@ -317,12 +325,13 @@ Client *direction_select(const Arg *arg) { if (!tc) return NULL; - if (tc && (tc->isfullscreen || tc->ismaximizescreen)) { - // 不支持全屏窗口的焦点切换 + if (tc && (tc->isfullscreen || tc->ismaximizescreen) && + (!is_scroller_layout(selmon) || tc->isfloating)) { return NULL; } - return find_client_by_direction(tc, arg, true, false); + return find_client_by_direction( + tc, arg, true, is_scroller_layout(selmon) && !selmon->isoverview); } /* We probably should change the name of this, it sounds like diff --git a/src/layout/arrange.h b/src/layout/arrange.h index aafb79e..ba1391e 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -599,6 +599,7 @@ arrange(Monitor *m, bool want_animation) { return; m->visible_clients = 0; m->visible_tiling_clients = 0; + m->visible_scroll_tiling_clients = 0; m->has_visible_fullscreen_client = false; wl_list_for_each(c, &clients, link) { @@ -619,6 +620,10 @@ arrange(Monitor *m, bool want_animation) { if (ISTILED(c)) { m->visible_tiling_clients++; } + + if (ISSCROLLTILED(c)) { + m->visible_scroll_tiling_clients++; + } } } diff --git a/src/layout/horizontal.h b/src/layout/horizontal.h index 934dc13..ff517cc 100644 --- a/src/layout/horizontal.h +++ b/src/layout/horizontal.h @@ -189,6 +189,38 @@ void deck(Monitor *m) { } } +void horizontal_scroll_adjust_fullandmax(Client *c, + struct wlr_box *target_geom) { + Monitor *m = c->mon; + unsigned int cur_gappih = enablegaps ? m->gappih : 0; + unsigned int cur_gappoh = enablegaps ? m->gappoh : 0; + unsigned int cur_gappov = enablegaps ? m->gappov : 0; + + cur_gappih = + smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappih; + cur_gappoh = + smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappoh; + cur_gappov = + smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappov; + + if (c->isfullscreen) { + target_geom->height = m->m.height; + target_geom->width = m->m.width; + target_geom->y = m->m.y; + return; + } + + if (c->ismaximizescreen) { + target_geom->height = m->w.height - 2 * cur_gappov; + target_geom->width = m->w.width - 2 * cur_gappoh; + target_geom->y = m->w.y + cur_gappov; + return; + } + + target_geom->height = m->w.height - 2 * cur_gappov; + target_geom->y = m->w.y + (m->w.height - target_geom->height) / 2; +} + // 滚动布局 void scroller(Monitor *m) { unsigned int i, n, j; @@ -203,14 +235,17 @@ void scroller(Monitor *m) { unsigned int cur_gappoh = enablegaps ? m->gappoh : 0; unsigned int cur_gappov = enablegaps ? m->gappov : 0; - cur_gappih = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappih; - cur_gappoh = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; - cur_gappov = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; + cur_gappih = + smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappih; + cur_gappoh = + smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappoh; + cur_gappov = + smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappov; unsigned int max_client_width = m->w.width - 2 * scroller_structs - cur_gappih; - n = m->visible_tiling_clients; + n = m->visible_scroll_tiling_clients; if (n == 0) { return; // 没有需要处理的客户端,直接返回 @@ -226,13 +261,14 @@ void scroller(Monitor *m) { // 第二次遍历,填充 tempClients j = 0; wl_list_for_each(c, &clients, link) { - if (VISIBLEON(c, m) && ISTILED(c)) { + if (VISIBLEON(c, m) && ISSCROLLTILED(c)) { tempClients[j] = c; j++; } } - if (n == 1 && !scroller_ignore_proportion_single) { + if (n == 1 && !scroller_ignore_proportion_single && + !tempClients[0]->isfullscreen && !tempClients[0]->ismaximizescreen) { c = tempClients[0]; single_proportion = c->scroller_proportion_single > 0.0f @@ -248,11 +284,10 @@ void scroller(Monitor *m) { return; } - if (m->sel && !client_is_unmanaged(m->sel) && !m->sel->isfloating && - !m->sel->ismaximizescreen && !m->sel->isfullscreen) { + if (m->sel && !client_is_unmanaged(m->sel) && !m->sel->isfloating) { root_client = m->sel; - } else if (m->prevsel && ISTILED(m->prevsel) && VISIBLEON(m->prevsel, m) && - !client_is_unmanaged(m->prevsel)) { + } else if (m->prevsel && ISSCROLLTILED(m->prevsel) && + VISIBLEON(m->prevsel, m) && !client_is_unmanaged(m->prevsel)) { root_client = m->prevsel; } else { root_client = center_tiled_select(m); @@ -289,11 +324,18 @@ void scroller(Monitor *m) { target_geom.height = m->w.height - 2 * cur_gappov; target_geom.width = max_client_width * c->scroller_proportion; target_geom.y = m->w.y + (m->w.height - target_geom.height) / 2; - - if (need_scroller) { + horizontal_scroll_adjust_fullandmax(tempClients[focus_client_index], + &target_geom); + if (tempClients[focus_client_index]->isfullscreen) { + target_geom.x = m->m.x; + resize(tempClients[focus_client_index], target_geom, 0); + } else if (tempClients[focus_client_index]->ismaximizescreen) { + target_geom.x = m->w.x + cur_gappoh; + resize(tempClients[focus_client_index], target_geom, 0); + } else if (need_scroller) { if (scroller_focus_center || ((!m->prevsel || - (ISTILED(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)) && @@ -316,14 +358,17 @@ void scroller(Monitor *m) { for (i = 1; i <= focus_client_index; i++) { c = tempClients[focus_client_index - i]; target_geom.width = max_client_width * c->scroller_proportion; + horizontal_scroll_adjust_fullandmax(c, &target_geom); target_geom.x = tempClients[focus_client_index - i + 1]->geom.x - cur_gappih - target_geom.width; + resize(c, target_geom, 0); } for (i = 1; i < n - focus_client_index; i++) { c = tempClients[focus_client_index + i]; target_geom.width = max_client_width * c->scroller_proportion; + horizontal_scroll_adjust_fullandmax(c, &target_geom); target_geom.x = tempClients[focus_client_index + i - 1]->geom.x + cur_gappih + tempClients[focus_client_index + i - 1]->geom.width; diff --git a/src/layout/vertical.h b/src/layout/vertical.h index 46ca3b6..cef0e3b 100644 --- a/src/layout/vertical.h +++ b/src/layout/vertical.h @@ -155,6 +155,38 @@ void vertical_deck(Monitor *m) { } } +void vertical_scroll_adjust_fullandmax(Client *c, struct wlr_box *target_geom) { + Monitor *m = c->mon; + unsigned int cur_gappiv = enablegaps ? m->gappiv : 0; + unsigned int cur_gappov = enablegaps ? m->gappov : 0; + unsigned int cur_gappoh = enablegaps ? m->gappoh : 0; + + cur_gappiv = + smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappiv; + cur_gappov = + smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappov; + cur_gappoh = + smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappoh; + + if (c->isfullscreen) { + target_geom->width = m->m.width; + target_geom->height = m->m.height; + target_geom->x = m->m.x; + return; + } + + if (c->ismaximizescreen) { + target_geom->width = m->w.width - 2 * cur_gappoh; + target_geom->height = m->w.height - 2 * cur_gappov; + target_geom->x = m->w.x + cur_gappoh; + return; + } + + target_geom->width = m->w.width - 2 * cur_gappoh; + target_geom->x = m->w.x + (m->w.width - target_geom->width) / 2; +} + +// 竖屏滚动布局 void vertical_scroller(Monitor *m) { unsigned int i, n, j; float single_proportion = 1.0; @@ -168,14 +200,17 @@ void vertical_scroller(Monitor *m) { unsigned int cur_gappov = enablegaps ? m->gappov : 0; unsigned int cur_gappoh = enablegaps ? m->gappoh : 0; - cur_gappiv = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappiv; - cur_gappov = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; - cur_gappoh = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; + cur_gappiv = + smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappiv; + cur_gappov = + smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappov; + cur_gappoh = + smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappoh; unsigned int max_client_height = m->w.height - 2 * scroller_structs - cur_gappiv; - n = m->visible_tiling_clients; + n = m->visible_scroll_tiling_clients; if (n == 0) { return; @@ -188,13 +223,14 @@ void vertical_scroller(Monitor *m) { j = 0; wl_list_for_each(c, &clients, link) { - if (VISIBLEON(c, m) && ISTILED(c)) { + if (VISIBLEON(c, m) && ISSCROLLTILED(c)) { tempClients[j] = c; j++; } } - if (n == 1 && !scroller_ignore_proportion_single) { + if (n == 1 && !scroller_ignore_proportion_single && + !tempClients[0]->isfullscreen && !tempClients[0]->ismaximizescreen) { c = tempClients[0]; single_proportion = c->scroller_proportion_single > 0.0f @@ -203,18 +239,17 @@ void vertical_scroller(Monitor *m) { target_geom.width = m->w.width - 2 * cur_gappoh; target_geom.height = (m->w.height - 2 * cur_gappov) * single_proportion; - target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2; target_geom.y = m->w.y + (m->w.height - target_geom.height) / 2; + target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2; resize(c, target_geom, 0); free(tempClients); return; } - if (m->sel && !client_is_unmanaged(m->sel) && !m->sel->isfloating && - !m->sel->ismaximizescreen && !m->sel->isfullscreen) { + if (m->sel && !client_is_unmanaged(m->sel) && !m->sel->isfloating) { root_client = m->sel; - } else if (m->prevsel && ISTILED(m->prevsel) && VISIBLEON(m->prevsel, m) && - !client_is_unmanaged(m->prevsel)) { + } else if (m->prevsel && ISSCROLLTILED(m->prevsel) && + VISIBLEON(m->prevsel, m) && !client_is_unmanaged(m->prevsel)) { root_client = m->prevsel; } else { root_client = center_tiled_select(m); @@ -251,11 +286,19 @@ void vertical_scroller(Monitor *m) { target_geom.width = m->w.width - 2 * cur_gappoh; target_geom.height = max_client_height * c->scroller_proportion; target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2; + vertical_scroll_adjust_fullandmax(tempClients[focus_client_index], + &target_geom); - if (need_scroller) { + if (tempClients[focus_client_index]->isfullscreen) { + target_geom.y = m->m.y; + resize(tempClients[focus_client_index], target_geom, 0); + } else if (tempClients[focus_client_index]->ismaximizescreen) { + target_geom.y = m->w.y + cur_gappov; + resize(tempClients[focus_client_index], target_geom, 0); + } else if (need_scroller) { if (scroller_focus_center || ((!m->prevsel || - (ISTILED(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)) && @@ -278,14 +321,17 @@ void vertical_scroller(Monitor *m) { for (i = 1; i <= focus_client_index; i++) { c = tempClients[focus_client_index - i]; target_geom.height = max_client_height * c->scroller_proportion; + vertical_scroll_adjust_fullandmax(c, &target_geom); target_geom.y = tempClients[focus_client_index - i + 1]->geom.y - cur_gappiv - target_geom.height; + resize(c, target_geom, 0); } for (i = 1; i < n - focus_client_index; i++) { c = tempClients[focus_client_index + i]; target_geom.height = max_client_height * c->scroller_proportion; + vertical_scroll_adjust_fullandmax(c, &target_geom); target_geom.y = tempClients[focus_client_index + i - 1]->geom.y + cur_gappiv + tempClients[focus_client_index + i - 1]->geom.height; diff --git a/src/mango.c b/src/mango.c index 06e1d61..540a025 100644 --- a/src/mango.c +++ b/src/mango.c @@ -104,6 +104,9 @@ #define ISTILED(A) \ (A && !(A)->isfloating && !(A)->isminied && !(A)->iskilling && \ !(A)->ismaximizescreen && !(A)->isfullscreen && !(A)->isunglobal) +#define ISSCROLLTILED(A) \ + (A && !(A)->isfloating && !(A)->isminied && !(A)->iskilling && \ + !(A)->isunglobal) #define VISIBLEON(C, M) \ ((C) && (M) && (C)->mon == (M) && ((C)->tags & (M)->tagset[(M)->seltags])) #define LENGTH(X) (sizeof X / sizeof X[0]) @@ -479,6 +482,7 @@ struct Monitor { int asleep; unsigned int visible_clients; unsigned int visible_tiling_clients; + unsigned int visible_scroll_tiling_clients; bool has_visible_fullscreen_client; struct wlr_scene_optimized_blur *blur; char last_surface_ws_name[256]; @@ -733,6 +737,7 @@ static void refresh_monitors_workspaces_status(Monitor *m); static void init_client_properties(Client *c); static float *get_border_color(Client *c); static void request_fresh_all_monitors(void); +static void clear_fullscreen_and_maximized_state(Monitor *m); #include "data/static_keymap.h" #include "dispatch/bind_declare.h" @@ -930,8 +935,26 @@ void applybounds(Client *c, struct wlr_box *bbox) { c->geom.y = bbox->y; } +void clear_fullscreen_and_maximized_state(Monitor *m) { + Client *fc = NULL; + wl_list_for_each(fc, &clients, link) { + if (fc && VISIBLEON(fc, m) && ISFULLSCREEN(fc)) { + clear_fullscreen_flag(fc); + } + } +} + /*清除全屏标志,还原全屏时清0的border*/ void clear_fullscreen_flag(Client *c) { + + if ((c->mon->pertag->ltidxs[get_tags_first_tag_num(c->tags)]->id == + SCROLLER || + c->mon->pertag->ltidxs[get_tags_first_tag_num(c->tags)]->id == + VERTICAL_SCROLLER) && + !c->isfloating) { + return; + } + if (c->isfullscreen) { setfullscreen(c, false); } @@ -3149,7 +3172,6 @@ void focusclient(Client *c, int lift) { if (c && selmon->prevsel && (selmon->prevsel->tags & selmon->tagset[selmon->seltags]) && (c->tags & selmon->tagset[selmon->seltags]) && !c->isfloating && - !c->isfullscreen && !c->ismaximizescreen && is_scroller_layout(selmon)) { arrange(selmon, false); } @@ -3702,9 +3724,9 @@ mapnotify(struct wl_listener *listener, void *data) { // tile at the top wl_list_insert(&clients, &c->link); // 新窗口是master,头部入栈 else if (selmon && is_scroller_layout(selmon) && - selmon->visible_tiling_clients > 0) { + selmon->visible_scroll_tiling_clients > 0) { - if (selmon->sel && ISTILED(selmon->sel) && + if (selmon->sel && ISSCROLLTILED(selmon->sel) && VISIBLEON(selmon->sel, selmon)) { at_client = selmon->sel; } else { @@ -4536,7 +4558,8 @@ void setmaximizescreen(Client *c, int maximizescreen) { maximizescreen_box.width = c->mon->w.width - 2 * gappoh; maximizescreen_box.height = c->mon->w.height - 2 * gappov; wlr_scene_node_raise_to_top(&c->scene->node); // 将视图提升到顶层 - resize(c, maximizescreen_box, 0); + if (!is_scroller_layout(c->mon) || c->isfloating) + resize(c, maximizescreen_box, 0); c->ismaximizescreen = 1; } else { c->bw = c->isnoborder ? 0 : borderpx; @@ -4545,9 +4568,8 @@ void setmaximizescreen(Client *c, int maximizescreen) { setfloating(c, 1); } - wlr_scene_node_reparent(&c->scene->node, layers[maximizescreen ? LyrTile - : c->isfloating ? LyrTop - : LyrTile]); + wlr_scene_node_reparent(&c->scene->node, + layers[c->isfloating ? LyrTop : LyrTile]); if (!c->ismaximizescreen) { set_size_per(c->mon, c); } @@ -4593,7 +4615,8 @@ void setfullscreen(Client *c, int fullscreen) // 用自定义全屏代理自带 c->bw = 0; wlr_scene_node_raise_to_top(&c->scene->node); // 将视图提升到顶层 - resize(c, c->mon->m, 1); + if (!is_scroller_layout(c->mon) || c->isfloating) + resize(c, c->mon->m, 1); c->isfullscreen = 1; } else { c->bw = c->isnoborder ? 0 : borderpx; @@ -5168,7 +5191,11 @@ unsigned int want_restore_fullscreen(Client *target_client) { Client *c = NULL; wl_list_for_each(c, &clients, link) { if (c && c != target_client && c->tags == target_client->tags && - c == selmon->sel) { + c == selmon->sel && + c->mon->pertag->ltidxs[get_tags_first_tag_num(c->tags)]->id != + SCROLLER && + c->mon->pertag->ltidxs[get_tags_first_tag_num(c->tags)]->id != + VERTICAL_SCROLLER) { return 0; } } From 9c8f9e3b38f64b5aeee09ebc60d1468f3975dee9 Mon Sep 17 00:00:00 2001 From: Jorrit van der Heide Date: Tue, 11 Nov 2025 17:03:43 +0100 Subject: [PATCH 24/34] fix: nixos example --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8e982b6..967e66a 100644 --- a/README.md +++ b/README.md @@ -189,7 +189,10 @@ Here's an example of using the modules in a flake: inputs.nixpkgs.follows = "nixpkgs"; }; flake-parts.url = "github:hercules-ci/flake-parts"; - mango.url = "github:DreamMaoMao/mango"; + mango = { + url = "github:DreamMaoMao/mango"; + inputs.nixpkgs.follows = "nixpkgs"; + }; }; outputs = inputs@{ self, flake-parts, ... }: From d941e0078eb7773a27f72890425eb62f085e417d Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 16 Nov 2025 07:19:29 +0800 Subject: [PATCH 25/34] fix: fullscreen and maximize window overflow screen --- src/animation/client.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/animation/client.h b/src/animation/client.h index e3673a2..d26265b 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -446,7 +446,7 @@ struct ivec2 clip_to_hide(Client *c, struct wlr_box *clip_box) { int offsetx = 0, offsety = 0, offsetw = 0, offseth = 0; struct ivec2 offset = {0, 0, 0, 0}; - if (!ISTILED(c) && !c->animation.tagining && !c->animation.tagouted && + if (!ISSCROLLTILED(c) && !c->animation.tagining && !c->animation.tagouted && !c->animation.tagouting) return offset; @@ -467,7 +467,7 @@ struct ivec2 clip_to_hide(Client *c, struct wlr_box *clip_box) { 需要主要border超出屏幕的时候不计算如偏差之内而是 要等窗口表面超出才开始计算偏差 */ - if (ISTILED(c) || c->animation.tagining || c->animation.tagouted || + if (ISSCROLLTILED(c) || c->animation.tagining || c->animation.tagouted || c->animation.tagouting) { if (left_out_offset > 0) { offsetx = GEZERO(left_out_offset - bw); @@ -495,7 +495,7 @@ struct ivec2 clip_to_hide(Client *c, struct wlr_box *clip_box) { offset.height = offseth; if ((clip_box->width + bw <= 0 || clip_box->height + bw <= 0) && - (ISTILED(c) || c->animation.tagouting || c->animation.tagining)) { + (ISSCROLLTILED(c) || c->animation.tagouting || c->animation.tagining)) { c->is_clip_to_hide = true; wlr_scene_node_set_enabled(&c->scene->node, false); } else if (c->is_clip_to_hide && VISIBLEON(c, c->mon)) { From 58d5938e147bb2b1173ffd69ef00dc18d0e87c54 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 18 Nov 2025 19:21:19 +0800 Subject: [PATCH 26/34] opt: support hot reload cursor config --- 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 074660b..d7b3223 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -3110,6 +3110,12 @@ void reapply_monitor_rules(void) { } } +void reapply_cursor_style(void) { + if (cursor_mgr) + wlr_xcursor_manager_destroy(cursor_mgr); + cursor_mgr = wlr_xcursor_manager_create(config.cursor_theme, cursor_size); +} + void reapply_border(void) { Client *c = NULL; @@ -3211,6 +3217,7 @@ void reset_option(void) { set_env(); run_exec(); + reapply_cursor_style(); reapply_border(); reapply_keyboard(); reapply_pointer(); From 8e4d3b7aac483c31514fe93c9898037889620475 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 19 Nov 2025 12:45:04 +0800 Subject: [PATCH 27/34] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 967e66a..b321be5 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,7 @@ cd wlroots meson build -Dprefix=/usr sudo ninja -C build install -git clone https://github.com/wlrfx/scenefx.git +git clone -b 0.4.1 https://github.com/wlrfx/scenefx.git cd scenefx meson build -Dprefix=/usr sudo ninja -C build install From e22cf99e742bfc93f73c862b351ee2557f20b1ab Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 20 Nov 2025 10:34:32 +0800 Subject: [PATCH 28/34] feat: support relative path for source keyword --- src/config/parse_config.h | 48 +++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index d7b3223..a58a106 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -2274,35 +2274,53 @@ void parse_config_line(Config *config, const char *line) { void parse_config_file(Config *config, const char *file_path) { FILE *file; - // 检查路径是否以 ~/ 开头 - if (file_path[0] == '~' && (file_path[1] == '/' || file_path[1] == '\0')) { + char full_path[1024]; + + if (file_path[0] == '.' && file_path[1] == '/') { + // Relative path + + const char *mangoconfig = getenv("MANGOCONFIG"); + if (mangoconfig && mangoconfig[0] != '\0') { + snprintf(full_path, sizeof(full_path), "%s/%s", mangoconfig, + file_path + 1); + } else { + const char *home = getenv("HOME"); + if (!home) { + fprintf(stderr, "Error: HOME environment variable not set.\n"); + return; + } + snprintf(full_path, sizeof(full_path), "%s/.config/mango/%s", home, + file_path + 1); + } + file = fopen(full_path, "r"); + + } else if (file_path[0] == '~' && + (file_path[1] == '/' || file_path[1] == '\0')) { + // Home directory + const char *home = getenv("HOME"); if (!home) { fprintf(stderr, "Error: HOME environment variable not set.\n"); return; } - - // 构建完整路径(家目录 + / + 原路径去掉 ~) - char full_path[1024]; snprintf(full_path, sizeof(full_path), "%s%s", home, file_path + 1); - file = fopen(full_path, "r"); - if (!file) { - perror("Error opening file"); - return; - } + } else { + // Absolute path file = fopen(file_path, "r"); - if (!file) { - perror("Error opening file"); - return; - } + } + + if (!file) { + perror("Error opening file"); + return; } char line[512]; while (fgets(line, sizeof(line), file)) { - if (line[0] == '#' || line[0] == '\n') + if (line[0] == '#' || line[0] == '\n') { continue; + } parse_config_line(config, line); } From e7cb4f77f37c3ebb15bcd803adcadd9d64f92039 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 20 Nov 2025 21:21:29 +0800 Subject: [PATCH 29/34] fix: wrong scroll judge when disable animaitons --- src/layout/horizontal.h | 2 +- src/layout/vertical.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/layout/horizontal.h b/src/layout/horizontal.h index ff517cc..99bb092 100644 --- a/src/layout/horizontal.h +++ b/src/layout/horizontal.h @@ -301,7 +301,7 @@ void scroller(Monitor *m) { for (i = 0; i < n; i++) { c = tempClients[i]; if (root_client == c) { - if (!c->is_pending_open_animation && + if ((!c->is_pending_open_animation || !animations) && c->geom.x >= m->w.x + scroller_structs && c->geom.x + c->geom.width <= m->w.x + m->w.width - scroller_structs) { diff --git a/src/layout/vertical.h b/src/layout/vertical.h index cef0e3b..5cc8ceb 100644 --- a/src/layout/vertical.h +++ b/src/layout/vertical.h @@ -263,7 +263,7 @@ void vertical_scroller(Monitor *m) { for (i = 0; i < n; i++) { c = tempClients[i]; if (root_client == c) { - if (!c->is_pending_open_animation && + if ((!c->is_pending_open_animation || !animations) && c->geom.y >= m->w.y + scroller_structs && c->geom.y + c->geom.height <= m->w.y + m->w.height - scroller_structs) { From a0824c05df5482fde37898b9a5d81f779167b60e Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 20 Nov 2025 22:47:12 +0800 Subject: [PATCH 30/34] opt: optimize scroll judge when open new client --- src/layout/horizontal.h | 3 +-- src/layout/vertical.h | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/layout/horizontal.h b/src/layout/horizontal.h index 99bb092..2385c84 100644 --- a/src/layout/horizontal.h +++ b/src/layout/horizontal.h @@ -301,8 +301,7 @@ void scroller(Monitor *m) { for (i = 0; i < n; i++) { c = tempClients[i]; if (root_client == c) { - if ((!c->is_pending_open_animation || !animations) && - c->geom.x >= m->w.x + scroller_structs && + if (c->geom.x >= m->w.x + scroller_structs && c->geom.x + c->geom.width <= m->w.x + m->w.width - scroller_structs) { need_scroller = false; diff --git a/src/layout/vertical.h b/src/layout/vertical.h index 5cc8ceb..7eb9529 100644 --- a/src/layout/vertical.h +++ b/src/layout/vertical.h @@ -263,8 +263,7 @@ void vertical_scroller(Monitor *m) { for (i = 0; i < n; i++) { c = tempClients[i]; if (root_client == c) { - if ((!c->is_pending_open_animation || !animations) && - c->geom.y >= m->w.y + scroller_structs && + if (c->geom.y >= m->w.y + scroller_structs && c->geom.y + c->geom.height <= m->w.y + m->w.height - scroller_structs) { need_scroller = false; From 03ee277ef604639e623a2c7a5cbafa7ecf3c76d4 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 21 Nov 2025 14:50:27 +0800 Subject: [PATCH 31/34] opt: allow init focus to on-demand-focus layer --- src/mango.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/mango.c b/src/mango.c index 540a025..8a27b75 100644 --- a/src/mango.c +++ b/src/mango.c @@ -2210,6 +2210,15 @@ 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) { + focusclient(NULL, 0); + client_notify_enter(l->layer_surface->surface, + wlr_seat_get_keyboard(seat)); + } } void commitlayersurfacenotify(struct wl_listener *listener, void *data) { From c004f7460c48a5c317df1ec2b4cb4ed6c3ca57a6 Mon Sep 17 00:00:00 2001 From: David Delarosa Date: Thu, 20 Nov 2025 14:40:34 +0200 Subject: [PATCH 32/34] nix: add portals.conf file --- nix/nixos-modules.nix | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/nix/nixos-modules.nix b/nix/nixos-modules.nix index 5d0aa61..3381102 100644 --- a/nix/nixos-modules.nix +++ b/nix/nixos-modules.nix @@ -26,6 +26,25 @@ in { xdg.portal = { enable = lib.mkDefault true; + config = { + mango = { + default = [ + "gtk" + ]; + # except those + "org.freedesktop.impl.portal.Secret" = ["gnome-keyring"]; + "org.freedesktop.impl.portal.ScreenCast" = ["wlr"]; + "org.freedesktop.impl.portal.ScreenShot" = ["wlr"]; + + # wlr does not have this interface + "org.freedesktop.impl.portal.Inhibit" = []; + }; + }; + extraPortals = with pkgs; [ + xdg-desktop-portal-wlr + xdg-desktop-portal-gtk + ]; + wlr.enable = lib.mkDefault true; configPackages = [cfg.package]; From e2ee985b8fa72aa4755718187de4e520cdbf63e4 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 26 Nov 2025 10:09:06 +0800 Subject: [PATCH 33/34] bump version to 0.10.6 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 041a1fd..b02e0fd 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('mango', ['c', 'cpp'], - version : '0.10.5', + version : '0.10.6', ) subdir('protocols') From 2f9cabe4b23cb91ebda707b27df12fe30ddeb86e Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 27 Nov 2025 22:40:41 +0800 Subject: [PATCH 34/34] fix: sloppyfocus not work when move cursor slowly --- src/fetch/common.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/fetch/common.h b/src/fetch/common.h index 33d7272..41dc994 100644 --- a/src/fetch/common.h +++ b/src/fetch/common.h @@ -115,6 +115,10 @@ 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,