From c2a7146168dfe4d122beec7e20f36d7742d81e1f Mon Sep 17 00:00:00 2001 From: werapi Date: Wed, 7 Jan 2026 15:46:25 +0100 Subject: [PATCH 001/328] fix: pointer events being one event behind --- src/mango.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/mango.c b/src/mango.c index 89ee23e2..f4f75170 100644 --- a/src/mango.c +++ b/src/mango.c @@ -3966,19 +3966,6 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, struct wlr_pointer_constraint_v1 *constraint; bool should_lock = false; - /* Find the client under the pointer and send the event along. */ - xytonode(cursor->x, cursor->y, &surface, &c, NULL, &sx, &sy); - - if (cursor_mode == CurPressed && !seat->drag && - surface != seat->pointer_state.focused_surface && - toplevel_from_wlr_surface(seat->pointer_state.focused_surface, &w, - &l) >= 0) { - c = w; - surface = seat->pointer_state.focused_surface; - sx = cursor->x - (l ? l->scene->node.x : w->geom.x); - sy = cursor->y - (l ? l->scene->node.y : w->geom.y); - } - /* time is 0 in internal calls meant to restore pointer focus. */ if (time) { wlr_relative_pointer_manager_v1_send_relative_motion( @@ -4016,6 +4003,19 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, selmon = xytomon(cursor->x, cursor->y); } + /* Find the client under the pointer and send the event along. */ + xytonode(cursor->x, cursor->y, &surface, &c, NULL, &sx, &sy); + + if (cursor_mode == CurPressed && !seat->drag && + surface != seat->pointer_state.focused_surface && + toplevel_from_wlr_surface(seat->pointer_state.focused_surface, &w, + &l) >= 0) { + c = w; + surface = seat->pointer_state.focused_surface; + sx = cursor->x - (l ? l->scene->node.x : w->geom.x); + sy = cursor->y - (l ? l->scene->node.y : w->geom.y); + } + /* Update drag icon's position */ wlr_scene_node_set_position(&drag_icon->node, (int32_t)round(cursor->x), (int32_t)round(cursor->y)); From 454145f6e03a462b743c64776d2b17121c2637ea Mon Sep 17 00:00:00 2001 From: Daniel Jampen Date: Sun, 8 Feb 2026 17:56:26 +0100 Subject: [PATCH 002/328] support isfakefullscreen as windowrule property --- src/config/parse_config.h | 4 ++++ src/mango.c | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 7e24d43f..49936de3 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -58,6 +58,7 @@ typedef struct { uint32_t tags; int32_t isfloating; int32_t isfullscreen; + int32_t isfakefullscreen; float scroller_proportion; const char *animation_type_open; const char *animation_type_close; @@ -1972,6 +1973,7 @@ bool parse_option(Config *config, char *key, char *value) { // int32_t rule value, relay to a client property rule->isfloating = -1; rule->isfullscreen = -1; + rule->isfakefullscreen = -1; rule->isnoborder = -1; rule->isnoshadow = -1; rule->isnoradius = -1; @@ -2111,6 +2113,8 @@ bool parse_option(Config *config, char *key, char *value) { rule->scroller_proportion = atof(val); } else if (strcmp(key, "isfullscreen") == 0) { rule->isfullscreen = atoi(val); + } else if (strcmp(key, "isfakefullscreen") == 0) { + rule->isfakefullscreen = atoi(val); } else if (strcmp(key, "globalkeybinding") == 0) { char mod_str[256], keysym_str[256]; sscanf(val, "%255[^-]-%255[a-zA-Z]", mod_str, keysym_str); diff --git a/src/mango.c b/src/mango.c index 591d4f65..8ec1b5b0 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1289,6 +1289,7 @@ static void apply_rule_properties(Client *c, const ConfigWinRule *r) { APPLY_INT_PROP(c, r, no_force_center); APPLY_INT_PROP(c, r, isfloating); APPLY_INT_PROP(c, r, isfullscreen); + APPLY_INT_PROP(c, r, isfakefullscreen); APPLY_INT_PROP(c, r, isnoborder); APPLY_INT_PROP(c, r, isnoshadow); APPLY_INT_PROP(c, r, isnoradius); @@ -1482,6 +1483,10 @@ void applyrules(Client *c) { setfullscreen(c, fullscreen_state_backup); + if (c->isfakefullscreen) { + setfakefullscreen(c, 1); + } + /* if there is a new non-floating window in the current tag, the fullscreen window in the current tag will exit fullscreen and participate in tiling From 5ae8975b11395dd73594fa7ea187fd5798510e30 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 13 Feb 2026 10:44:17 +0800 Subject: [PATCH 003/328] bump version to 0.12.2 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 478ef0e6..e7979bdf 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('mango', ['c', 'cpp'], - version : '0.12.1', + version : '0.12.2', ) subdir('protocols') From f25161552455d076f0f15cadf522a8b57b829e2f Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 13 Feb 2026 11:06:12 +0800 Subject: [PATCH 004/328] opt: flush the blur background cache when unmap a background layer --- src/mango.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/mango.c b/src/mango.c index ca25dbca..e8fd2e68 100644 --- a/src/mango.c +++ b/src/mango.c @@ -2275,6 +2275,19 @@ static void iter_layer_scene_buffers(struct wlr_scene_buffer *buffer, } } +void layer_flush_blur_background(LayerSurface *l) { + if (!blur) + return; + + // 如果背景层发生变化,标记优化的模糊背景缓存需要更新 + if (l->layer_surface->current.layer == + ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND) { + if (l->mon) { + wlr_scene_optimized_blur_mark_dirty(l->mon->blur); + } + } +} + void maplayersurfacenotify(struct wl_listener *listener, void *data) { LayerSurface *l = wl_container_of(listener, l, map); struct wlr_layer_surface_v1 *layer_surface = l->layer_surface; @@ -2402,15 +2415,7 @@ void commitlayersurfacenotify(struct wl_listener *listener, void *data) { } } - if (blur) { - // 如果背景层发生变化,标记优化的模糊背景缓存需要更新 - if (layer_surface->current.layer == - ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND) { - if (l->mon) { - wlr_scene_optimized_blur_mark_dirty(l->mon->blur); - } - } - } + layer_flush_blur_background(l); if (layer_surface == exclusive_focus && layer_surface->current.keyboard_interactive != @@ -5643,6 +5648,7 @@ void unmaplayersurfacenotify(struct wl_listener *listener, void *data) { focusclient(focustop(selmon), 1); motionnotify(0, NULL, 0, 0, 0, 0); l->being_unmapped = false; + layer_flush_blur_background(l); wlr_scene_node_destroy(&l->shadow->node); l->shadow = NULL; } From 711498490bdf3119ac5cb4debb60f6379e5d5df1 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 13 Feb 2026 18:11:18 +0800 Subject: [PATCH 005/328] opt: not back to ov tag when view prev tag --- src/dispatch/bind_define.h | 15 ++++++++++++++- src/mango.c | 2 ++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 1d29bc97..ea213e4f 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1521,6 +1521,16 @@ int32_t minimized(const Arg *arg) { return 0; } +void fix_mon_tagset_from_overview(Monitor *m) { + if (m->tagset[m->seltags] == (m->ovbk_prev_tagset & TAGMASK)) { + m->tagset[m->seltags ^ 1] = m->ovbk_current_tagset; + m->pertag->prevtag = get_tags_first_tag_num(m->ovbk_current_tagset); + } else { + m->tagset[m->seltags ^ 1] = m->ovbk_prev_tagset; + m->pertag->prevtag = get_tags_first_tag_num(m->ovbk_prev_tagset); + } +} + int32_t toggleoverview(const Arg *arg) { Client *c = NULL; @@ -1542,6 +1552,8 @@ int32_t toggleoverview(const Arg *arg) { visible_client_number++; } if (visible_client_number > 0) { + selmon->ovbk_current_tagset = selmon->tagset[selmon->seltags]; + selmon->ovbk_prev_tagset = selmon->tagset[selmon->seltags ^ 1]; target = ~0 & TAGMASK; } else { selmon->isoverview ^= 1; @@ -1552,6 +1564,7 @@ int32_t toggleoverview(const Arg *arg) { } else if (!selmon->isoverview && !selmon->sel) { target = (1 << (selmon->pertag->prevtag - 1)); view(&(Arg){.ui = target}, false); + fix_mon_tagset_from_overview(selmon); refresh_monitors_workspaces_status(selmon); return 0; } @@ -1574,7 +1587,7 @@ int32_t toggleoverview(const Arg *arg) { } view(&(Arg){.ui = target}, false); - + fix_mon_tagset_from_overview(selmon); refresh_monitors_workspaces_status(selmon); return 0; } diff --git a/src/mango.c b/src/mango.c index e8fd2e68..5c0a610c 100644 --- a/src/mango.c +++ b/src/mango.c @@ -514,6 +514,8 @@ struct Monitor { int32_t gappoh; /* horizontal outer gaps */ int32_t gappov; /* vertical outer gaps */ Pertag *pertag; + uint32_t ovbk_current_tagset; + uint32_t ovbk_prev_tagset; Client *sel, *prevsel; int32_t isoverview; int32_t is_in_hotarea; From 8a924494c6693529eb83b043bf56cbff696e78e1 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 13 Feb 2026 18:16:06 +0800 Subject: [PATCH 006/328] opt: the tagset is current tagset when open window in ov mode --- src/mango.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/mango.c b/src/mango.c index 5c0a610c..b0fde12f 100644 --- a/src/mango.c +++ b/src/mango.c @@ -5097,9 +5097,15 @@ void setmon(Client *c, Monitor *m, uint32_t newtags, bool focus) { /* Make sure window actually overlaps with the monitor */ reset_foreign_tolevel(c); resize(c, c->geom, 0); - c->tags = - newtags ? newtags - : m->tagset[m->seltags]; /* assign tags of target monitor */ + if (!newtags && !m->isoverview) { + c->tags = m->tagset[m->seltags]; + } else if (!newtags && m->isoverview) { + c->tags = m->ovbk_current_tagset; + } else if (newtags) { + c->tags = newtags; + } else { + c->tags = m->tagset[m->seltags]; + } setfloating(c, c->isfloating); setfullscreen(c, c->isfullscreen); /* This will call arrange(c->mon) */ } From c05eec7f53a8f1af25826cdc5bb3104b791e8cbb Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 13 Feb 2026 20:02:23 +0800 Subject: [PATCH 007/328] feat: support restore stack from non-tile state --- src/dispatch/bind_define.h | 8 +++++--- src/layout/arrange.h | 39 ++++++++++++++++++++++++++++++++++++++ src/mango.c | 6 +++--- 3 files changed, 47 insertions(+), 6 deletions(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index ea213e4f..0bfab158 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1217,13 +1217,15 @@ int32_t togglefloating(const Arg *arg) { if (!sel) return 0; + bool isfloating = sel->isfloating; + if ((sel->isfullscreen || sel->ismaximizescreen)) { - sel->isfloating = 1; + isfloating = 1; } else { - sel->isfloating = !sel->isfloating; + isfloating = !sel->isfloating; } - setfloating(sel, sel->isfloating); + setfloating(sel, isfloating); return 0; } diff --git a/src/layout/arrange.h b/src/layout/arrange.h index 37213640..cc4bc076 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -1,3 +1,42 @@ +void restore_size_per(Monitor *m, Client *c) { + Client *fc = NULL; + double total_master_inner_per = 0; + double total_stack_inner_per = 0; + + if (!m || !c) + return; + + const Layout *current_layout = m->pertag->ltidxs[m->pertag->curtag]; + + if (current_layout->id == SCROLLER || + current_layout->id == VERTICAL_SCROLLER || current_layout->id == GRID || + current_layout->id == VERTICAL_GRID || current_layout->id == DECK || + current_layout->id == VERTICAL_DECK || + current_layout->id == CENTER_TILE || current_layout->id == MONOCLE) { + return; + } + + if (current_layout->id == CENTER_TILE || c->ismaster) { + set_size_per(m, c); + return; + } + + wl_list_for_each(fc, &clients, link) { + if (VISIBLEON(fc, m) && ISTILED(fc) && fc != c) { + if (fc->ismaster) { + total_master_inner_per += fc->master_inner_per; + } else { + total_stack_inner_per += fc->stack_inner_per; + } + } + } + + if (!c->ismaster && total_stack_inner_per) { + c->stack_inner_per = total_stack_inner_per * c->stack_inner_per / + (1 - c->stack_inner_per); + } +} + void set_size_per(Monitor *m, Client *c) { Client *fc = NULL; bool found = false; diff --git a/src/mango.c b/src/mango.c index b0fde12f..45e0289c 100644 --- a/src/mango.c +++ b/src/mango.c @@ -4810,7 +4810,7 @@ setfloating(Client *c, int32_t floating) { } if (!c->isfloating && old_floating_state) { - set_size_per(c->mon, c); + restore_size_per(c->mon, c); } if (!c->force_maximize) @@ -4896,7 +4896,7 @@ void setmaximizescreen(Client *c, int32_t maximizescreen) { wlr_scene_node_reparent(&c->scene->node, layers[c->isfloating ? LyrTop : LyrTile]); if (!c->ismaximizescreen && old_maximizescreen_state) { - set_size_per(c->mon, c); + restore_size_per(c->mon, c); } if (!c->force_maximize && !c->ismaximizescreen) { @@ -4967,7 +4967,7 @@ void setfullscreen(Client *c, int32_t fullscreen) // 用自定义全屏代理自 } if (!c->isfullscreen && old_fullscreen_state) { - set_size_per(c->mon, c); + restore_size_per(c->mon, c); } arrange(c->mon, false, false); From 0fe87e6286f2a6b899fa8d7683a982f831c983af Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 13 Feb 2026 20:23:03 +0800 Subject: [PATCH 008/328] fix: fix multi master focus record error --- src/fetch/client.h | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/fetch/client.h b/src/fetch/client.h index bf30e175..8fccb261 100644 --- a/src/fetch/client.h +++ b/src/fetch/client.h @@ -574,23 +574,29 @@ bool client_is_in_same_stack(Client *sc, Client *tc, Client *fc) { if (id == TILE || id == VERTICAL_TILE || id == DECK || id == VERTICAL_DECK || id == RIGHT_TILE) { - if (fc && !fc->ismaster) + if (tc->ismaster ^ sc->ismaster) return false; - else if (!sc->ismaster) + if (fc && !(fc->ismaster ^ sc->ismaster)) + return false; + else return true; } if (id == TGMIX) { - if (fc && !fc->ismaster) + if (tc->ismaster ^ sc->ismaster) + return false; + if (fc && !(fc->ismaster ^ sc->ismaster)) return false; if (!sc->ismaster && sc->mon->visible_tiling_clients <= 3) return true; } if (id == CENTER_TILE) { - if (fc && !fc->ismaster) + if (tc->ismaster ^ sc->ismaster) return false; - if (!sc->ismaster && sc->geom.x == tc->geom.x) + if (fc && !(fc->ismaster ^ sc->ismaster)) + return false; + if (sc->geom.x == tc->geom.x) return true; else return false; From 89413aacf53e11340d0558e7d18576720a067158 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 14 Feb 2026 08:35:30 +0800 Subject: [PATCH 009/328] fix: fix center tile size per reset --- src/layout/arrange.h | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/layout/arrange.h b/src/layout/arrange.h index cc4bc076..1ef89c3a 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -11,13 +11,16 @@ void restore_size_per(Monitor *m, Client *c) { if (current_layout->id == SCROLLER || current_layout->id == VERTICAL_SCROLLER || current_layout->id == GRID || current_layout->id == VERTICAL_GRID || current_layout->id == DECK || - current_layout->id == VERTICAL_DECK || - current_layout->id == CENTER_TILE || current_layout->id == MONOCLE) { + current_layout->id == VERTICAL_DECK || current_layout->id == MONOCLE) { return; } if (current_layout->id == CENTER_TILE || c->ismaster) { - set_size_per(m, c); + wl_list_for_each(fc, &clients, link) { + if (VISIBLEON(fc, m) && ISTILED(fc) && !c->ismaster) { + set_size_per(m, fc); + } + } return; } @@ -44,8 +47,13 @@ void set_size_per(Monitor *m, Client *c) { if (!m || !c) return; + const Layout *current_layout = m->pertag->ltidxs[m->pertag->curtag]; + wl_list_for_each(fc, &clients, link) { if (VISIBLEON(fc, m) && ISTILED(fc) && fc != c) { + if (current_layout->id == CENTER_TILE && + !(fc->isleftstack ^ c->isleftstack)) + continue; c->master_mfact_per = fc->master_mfact_per; c->master_inner_per = fc->master_inner_per; c->stack_inner_per = fc->stack_inner_per; From fdd54afb7e49d0dfdf3976b1824a660a83e09713 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 15 Feb 2026 08:00:44 +0800 Subject: [PATCH 010/328] fix: some app frame skip fail when disable animaitons --- src/mango.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/mango.c b/src/mango.c index 2bc21c8b..87d2f7d5 100644 --- a/src/mango.c +++ b/src/mango.c @@ -3835,6 +3835,7 @@ static void iter_xdg_scene_buffers(struct wlr_scene_buffer *buffer, int32_t sx, void init_client_properties(Client *c) { c->isfocusing = false; + c->isfloating = 0; c->ismaximizescreen = 0; c->isfullscreen = 0; c->need_float_size_reduce = 0; @@ -4441,8 +4442,8 @@ void rendermon(struct wl_listener *listener, void *data) { wl_list_for_each(c, &clients, link) { need_more_frames = client_draw_frame(c) || need_more_frames; if (!animations && !(allow_tearing && frame_allow_tearing) && - c->configure_serial && !c->isfloating && - client_is_rendered_on_mon(c, m) && !client_is_stopped(c)) { + c->configure_serial && client_is_rendered_on_mon(c, m) && + !client_is_stopped(c)) { goto skip; } } From 02067e3b1e00b568b63dabb4c1c57b188911c349 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 15 Feb 2026 08:07:00 +0800 Subject: [PATCH 011/328] fix: some client property missing init --- src/mango.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/mango.c b/src/mango.c index 87d2f7d5..6cdd5549 100644 --- a/src/mango.c +++ b/src/mango.c @@ -3836,6 +3836,22 @@ static void iter_xdg_scene_buffers(struct wlr_scene_buffer *buffer, int32_t sx, void init_client_properties(Client *c) { c->isfocusing = false; c->isfloating = 0; + c->isfakefullscreen = 0; + c->isnoanimation = 0; + c->isopensilent = 0; + c->istagsilent = 0; + c->noswallow = 0; + c->isterm = 0; + c->noblur = 0; + c->tearing_hint = 0; + c->overview_isfullscreenbak = 0; + c->overview_ismaximizescreenbak = 0; + c->overview_isfloatingbak = 0; + c->pid = 0; + c->swallowing = NULL; + c->swallowedby = NULL; + c->ismaster = 0; + c->isleftstack = 0; c->ismaximizescreen = 0; c->isfullscreen = 0; c->need_float_size_reduce = 0; @@ -3889,6 +3905,7 @@ void init_client_properties(Client *c) { c->stack_proportion = 0.0f; c->next_in_stack = NULL; c->prev_in_stack = NULL; + memset(c->oldmonname, 0, sizeof(c->oldmonname)); memcpy(c->opacity_animation.initial_border_color, bordercolor, sizeof(c->opacity_animation.initial_border_color)); memcpy(c->opacity_animation.current_border_color, bordercolor, From 842b45b584bd4b9409e474757e79ab7f08e05b21 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 15 Feb 2026 08:32:49 +0800 Subject: [PATCH 012/328] feat: add skip timer to avoid rermanently block render --- src/mango.c | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/mango.c b/src/mango.c index 6cdd5549..ded59587 100644 --- a/src/mango.c +++ b/src/mango.c @@ -502,6 +502,7 @@ struct Monitor { struct wl_listener request_state; struct wl_listener destroy_lock_surface; struct wlr_session_lock_surface_v1 *lock_surface; + struct wl_event_source *skip_timeout; struct wlr_box m; /* monitor area, layout-relative */ struct wlr_box w; /* window area, layout-relative */ struct wl_list layers[4]; /* LayerSurface::link */ @@ -783,6 +784,7 @@ static Client *get_scroll_stack_head(Client *c); static bool client_only_in_one_tag(Client *c); static Client *get_focused_stack_client(Client *sc); static bool client_is_in_same_stack(Client *sc, Client *tc, Client *fc); +static void destroy_monitor_skip_timer(Monitor *m); #include "data/static_keymap.h" #include "dispatch/bind_declare.h" @@ -2223,6 +2225,9 @@ void cleanupmon(struct wl_listener *listener, void *data) { wlr_scene_node_destroy(&m->blur->node); m->blur = NULL; } + if (m->skip_timeout) { + destroy_monitor_skip_timer(m); + } m->wlr_output->data = NULL; free(m->pertag); free(m); @@ -2774,6 +2779,7 @@ void createmon(struct wl_listener *listener, void *data) { m = wlr_output->data = ecalloc(1, sizeof(*m)); m->wlr_output = wlr_output; m->wlr_output->data = m; + m->skip_timeout = NULL; wl_list_init(&m->dwl_ipc_outputs); @@ -4419,6 +4425,37 @@ void client_set_opacity(Client *c, double opacity) { scene_buffer_apply_opacity, &opacity); } +void destroy_monitor_skip_timer(Monitor *m) { + if (m->skip_timeout) { + wl_event_source_timer_update(m->skip_timeout, 0); + wl_event_source_remove(m->skip_timeout); + m->skip_timeout = NULL; + } +} + +static int skip_timeout_callback(void *data) { + Monitor *m = data; + Client *c, *tmp; + + wl_list_for_each_safe(c, tmp, &clients, link) { c->configure_serial = 0; } + + if (m->skip_timeout) { + destroy_monitor_skip_timer(m); + } + return 0; +} + +void check_skip_timeout(Monitor *m) { + if (m->skip_timeout) { + return; + } + struct wl_event_loop *loop = wl_display_get_event_loop(dpy); + m->skip_timeout = wl_event_loop_add_timer(loop, skip_timeout_callback, m); + if (m->skip_timeout) { + wl_event_source_timer_update(m->skip_timeout, 100); // 100ms + } +} + void rendermon(struct wl_listener *listener, void *data) { Monitor *m = wl_container_of(listener, m, frame); Client *c = NULL, *tmp = NULL; @@ -4461,10 +4498,15 @@ void rendermon(struct wl_listener *listener, void *data) { if (!animations && !(allow_tearing && frame_allow_tearing) && c->configure_serial && client_is_rendered_on_mon(c, m) && !client_is_stopped(c)) { + check_skip_timeout(m); goto skip; } } + if (m->skip_timeout) { + destroy_monitor_skip_timer(m); + } + // 只有在需要帧时才构建和提交状态 if (allow_tearing && frame_allow_tearing) { apply_tear_state(m); From 0696fe964de6b820e172ecbed6d3af942996b6bd Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 15 Feb 2026 10:31:23 +0800 Subject: [PATCH 013/328] opt: optimize frame skip logic --- src/mango.c | 47 +++++++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/src/mango.c b/src/mango.c index ded59587..8ae26031 100644 --- a/src/mango.c +++ b/src/mango.c @@ -508,6 +508,7 @@ struct Monitor { struct wl_list layers[4]; /* LayerSurface::link */ uint32_t seltags; uint32_t tagset[2]; + bool skiping_frame; struct wl_list dwl_ipc_outputs; int32_t gappih; /* horizontal gap between windows */ @@ -784,7 +785,8 @@ static Client *get_scroll_stack_head(Client *c); static bool client_only_in_one_tag(Client *c); static Client *get_focused_stack_client(Client *sc); static bool client_is_in_same_stack(Client *sc, Client *tc, Client *fc); -static void destroy_monitor_skip_timer(Monitor *m); +static void monitor_stop_skip_timer(Monitor *m); +static int monitor_skip_frame_timeout_callback(void *data); #include "data/static_keymap.h" #include "dispatch/bind_declare.h" @@ -2226,7 +2228,9 @@ void cleanupmon(struct wl_listener *listener, void *data) { m->blur = NULL; } if (m->skip_timeout) { - destroy_monitor_skip_timer(m); + monitor_stop_skip_timer(m); + wl_event_source_remove(m->skip_timeout); + m->skip_timeout = NULL; } m->wlr_output->data = NULL; free(m->pertag); @@ -2776,10 +2780,15 @@ void createmon(struct wl_listener *listener, void *data) { return; } + struct wl_event_loop *loop = wl_display_get_event_loop(dpy); m = wlr_output->data = ecalloc(1, sizeof(*m)); + + m->skip_timeout = + wl_event_loop_add_timer(loop, monitor_skip_frame_timeout_callback, m); + m->skiping_frame = false; + m->wlr_output = wlr_output; m->wlr_output->data = m; - m->skip_timeout = NULL; wl_list_init(&m->dwl_ipc_outputs); @@ -4425,34 +4434,32 @@ void client_set_opacity(Client *c, double opacity) { scene_buffer_apply_opacity, &opacity); } -void destroy_monitor_skip_timer(Monitor *m) { - if (m->skip_timeout) { +void monitor_stop_skip_timer(Monitor *m) { + if (m->skip_timeout) wl_event_source_timer_update(m->skip_timeout, 0); - wl_event_source_remove(m->skip_timeout); - m->skip_timeout = NULL; - } + m->skiping_frame = false; } -static int skip_timeout_callback(void *data) { +static int monitor_skip_frame_timeout_callback(void *data) { Monitor *m = data; Client *c, *tmp; wl_list_for_each_safe(c, tmp, &clients, link) { c->configure_serial = 0; } - if (m->skip_timeout) { - destroy_monitor_skip_timer(m); - } - return 0; + monitor_stop_skip_timer(m); + wlr_output_schedule_frame(m->wlr_output); + + return 1; } -void check_skip_timeout(Monitor *m) { - if (m->skip_timeout) { +void monitor_check_skip_frame_timeout(Monitor *m) { + if (m->skiping_frame) { return; } - struct wl_event_loop *loop = wl_display_get_event_loop(dpy); - m->skip_timeout = wl_event_loop_add_timer(loop, skip_timeout_callback, m); + if (m->skip_timeout) { wl_event_source_timer_update(m->skip_timeout, 100); // 100ms + m->skiping_frame = true; } } @@ -4498,13 +4505,13 @@ void rendermon(struct wl_listener *listener, void *data) { if (!animations && !(allow_tearing && frame_allow_tearing) && c->configure_serial && client_is_rendered_on_mon(c, m) && !client_is_stopped(c)) { - check_skip_timeout(m); + monitor_check_skip_frame_timeout(m); goto skip; } } - if (m->skip_timeout) { - destroy_monitor_skip_timer(m); + if (m->skiping_frame) { + monitor_stop_skip_timer(m); } // 只有在需要帧时才构建和提交状态 From 49cb5a9d7efb03e726e4eea1579242e3549f6d8b Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 15 Feb 2026 12:29:12 +0800 Subject: [PATCH 014/328] feat: support frame skip for x11 app resize --- src/client/client.h | 16 +++++++++++++++- src/mango.c | 18 ++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/client/client.h b/src/client/client.h index 49ab3988..fbb7a242 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -319,9 +319,23 @@ static inline uint32_t client_set_size(Client *c, uint32_t width, uint32_t height) { #ifdef XWAYLAND if (client_is_x11(c)) { + + struct wlr_surface_state *state = + &c->surface.xwayland->surface->current; + struct wlr_box new_geo = {0}; + new_geo.width = state->width; + new_geo.height = state->height; + if (c->geom.width - 2 * c->bw == new_geo.width && + c->geom.height - 2 * c->bw == new_geo.height && + c->surface.xwayland->x == c->geom.x + c->bw && + c->surface.xwayland->y == c->geom.y + c->bw) { + c->configure_serial = 0; + return 0; + } + wlr_xwayland_surface_configure(c->surface.xwayland, c->geom.x + c->bw, c->geom.y + c->bw, width, height); - return 0; + return 1; } #endif if ((int32_t)width == c->surface.xdg->toplevel->current.width && diff --git a/src/mango.c b/src/mango.c index 8ae26031..f0aefec3 100644 --- a/src/mango.c +++ b/src/mango.c @@ -338,6 +338,7 @@ struct Client { struct wl_listener configure; struct wl_listener set_hints; struct wl_listener set_geometry; + struct wl_listener commmitx11; #endif uint32_t bw; uint32_t tags, oldtags, mini_restore_tag; @@ -956,6 +957,7 @@ static void activatex11(struct wl_listener *listener, void *data); static void configurex11(struct wl_listener *listener, void *data); static void createnotifyx11(struct wl_listener *listener, void *data); static void dissociatex11(struct wl_listener *listener, void *data); +static void commitx11(struct wl_listener *listener, void *data); static void associatex11(struct wl_listener *listener, void *data); static void sethints(struct wl_listener *listener, void *data); static void xwaylandready(struct wl_listener *listener, void *data); @@ -6280,17 +6282,33 @@ void createnotifyx11(struct wl_listener *listener, void *data) { LISTEN(&xsurface->events.request_minimize, &c->minimize, minimizenotify); } +void commitx11(struct wl_listener *listener, void *data) { + Client *c = wl_container_of(listener, c, commmitx11); + struct wlr_surface_state *state = &c->surface.xwayland->surface->current; + struct wlr_box new_geo = {0}; + new_geo.width = state->width; + new_geo.height = state->height; + if (c->geom.width - 2 * c->bw == new_geo.width && + c->geom.height - 2 * c->bw == new_geo.height && + c->surface.xwayland->x == c->geom.x + c->bw && + c->surface.xwayland->y == c->geom.y + c->bw) { + c->configure_serial = 0; + } +} + void associatex11(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, associate); LISTEN(&client_surface(c)->events.map, &c->map, mapnotify); LISTEN(&client_surface(c)->events.unmap, &c->unmap, unmapnotify); + LISTEN(&client_surface(c)->events.commit, &c->commmitx11, commitx11); } void dissociatex11(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, dissociate); wl_list_remove(&c->map.link); wl_list_remove(&c->unmap.link); + wl_list_remove(&c->commmitx11.link); } void sethints(struct wl_listener *listener, void *data) { From 7ccbeae8b8bcf0c9b506579758c6ac499f3d5dab Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 15 Feb 2026 14:08:12 +0800 Subject: [PATCH 015/328] fix: if the progress not the child of main, not assume it is stop --- src/client/client.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/client/client.h b/src/client/client.h index fbb7a242..e9519106 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -254,10 +254,13 @@ static inline int32_t client_is_stopped(Client *c) { wl_client_get_credentials(c->surface.xdg->client->client, &pid, NULL, NULL); if (waitid(P_PID, pid, &in, WNOHANG | WCONTINUED | WSTOPPED | WNOWAIT) < 0) { - /* This process is not our child process, while is very unlikely that - * it is stopped, in order to do not skip frames assume that it is. */ + /* This process is not our child process. We cannot determine its + * stopped state; assume it is not stopped to avoid blocking frame skip. + */ if (errno == ECHILD) - return 1; + return 0; // if not our child, assume not stopped + /* Other errors, also assume not stopped. */ + return 0; } else if (in.si_pid) { if (in.si_code == CLD_STOPPED || in.si_code == CLD_TRAPPED) return 1; From 62ab00a7a3656fcdc39008d048b13369ffe1ad11 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 15 Feb 2026 16:43:02 +0800 Subject: [PATCH 016/328] update readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b78ccfa9..9d14d06b 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ This project's development is based on [dwl](https://codeberg.org/dwl/dwl/). - Ipc support(get/send message from/to compositor by external program) - Hycov-like overview - Window effects from scenefx (blur, shadow, corner radius, opacity) + - Zero flickering - every frame is perfect. https://github.com/user-attachments/assets/bb83004a-0563-4b48-ad89-6461a9b78b1f From f75efa13124e6017fbb935d7e3b8d86077660669 Mon Sep 17 00:00:00 2001 From: Ricardo Squassina Lee <8495707+squassina@users.noreply.github.com> Date: Sun, 15 Feb 2026 07:08:35 -0300 Subject: [PATCH 017/328] Fix wayland protocol directory variable retrieval --- protocols/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocols/meson.build b/protocols/meson.build index cafab64a..922a76ed 100644 --- a/protocols/meson.build +++ b/protocols/meson.build @@ -1,6 +1,6 @@ wayland_scanner = find_program('wayland-scanner') wayland_protos_dep = dependency('wayland-protocols') -wl_protocol_dir = wayland_protos_dep.get_pkgconfig_variable('pkgdatadir') +wl_protocol_dir = wayland_protos_dep.get_variable(pkgconfig:'pkgdatadir') wayland_scanner_code = generator( wayland_scanner, output: '@BASENAME@-protocol.c', From 26a616e3d5a99811708b8b0afa62eafd0ca490be Mon Sep 17 00:00:00 2001 From: Yappaholic Date: Sun, 15 Feb 2026 16:50:07 +0300 Subject: [PATCH 018/328] docs: add guix installation instructions --- README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/README.md b/README.md index 9d14d06b..9b558fd1 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,34 @@ Then, install the package: dnf install mangowc ``` +## GuixSD +The package definition is described in the source repository. +First, add `mangowc` channel to `channels.scm` file: + +```scheme +;; In $HOME/.config/guix/channels.scm +(cons (channel + (name 'mangowc) + (url "https://github.com/DreamMaoMao/mangowc.git")) + ... ;; Your other channels + %default-channels) +``` + +Then, run `guix pull` and after update you can either run +`guix install mangowc` or add it to your configuration via: + +```scheme +(use-modules (mangowc)) ;; Add mangowc module + +;; Add mangowc to packages list +(packages (cons + mangowc + ... ;; Other packages you specified + %base-packages)) +``` + +And then rebuild your system. + ## Other ```bash From 1158fb2e3c09424e1d0f3aaf9b77968743cc235e Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 16 Feb 2026 07:46:42 +0800 Subject: [PATCH 019/328] opt: don't skip frame when grab client --- src/mango.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mango.c b/src/mango.c index f0aefec3..cbdc53a6 100644 --- a/src/mango.c +++ b/src/mango.c @@ -4506,7 +4506,7 @@ void rendermon(struct wl_listener *listener, void *data) { need_more_frames = client_draw_frame(c) || need_more_frames; if (!animations && !(allow_tearing && frame_allow_tearing) && c->configure_serial && client_is_rendered_on_mon(c, m) && - !client_is_stopped(c)) { + !client_is_stopped(c) && !grabc) { monitor_check_skip_frame_timeout(m); goto skip; } From 112fb5c0074f6f2110e70ae4765fe0a46ff04adb Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 16 Feb 2026 10:06:16 +0800 Subject: [PATCH 020/328] opt: optimize code struct --- src/client/client.h | 16 +++++++++------- src/mango.c | 15 ++++++++------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/client/client.h b/src/client/client.h index e9519106..2174c6fa 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -325,13 +325,15 @@ static inline uint32_t client_set_size(Client *c, uint32_t width, struct wlr_surface_state *state = &c->surface.xwayland->surface->current; - struct wlr_box new_geo = {0}; - new_geo.width = state->width; - new_geo.height = state->height; - if (c->geom.width - 2 * c->bw == new_geo.width && - c->geom.height - 2 * c->bw == new_geo.height && - c->surface.xwayland->x == c->geom.x + c->bw && - c->surface.xwayland->y == c->geom.y + c->bw) { + + if ((int32_t)c->geom.width - 2 * (int32_t)c->bw == + (int32_t)state->width && + (int32_t)c->geom.height - 2 * (int32_t)c->bw == + (int32_t)state->height && + (int32_t)c->surface.xwayland->x == + (int32_t)c->geom.x + (int32_t)c->bw && + (int32_t)c->surface.xwayland->y == + (int32_t)c->geom.y + (int32_t)c->bw) { c->configure_serial = 0; return 0; } diff --git a/src/mango.c b/src/mango.c index cbdc53a6..540395ae 100644 --- a/src/mango.c +++ b/src/mango.c @@ -6285,13 +6285,14 @@ void createnotifyx11(struct wl_listener *listener, void *data) { void commitx11(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, commmitx11); struct wlr_surface_state *state = &c->surface.xwayland->surface->current; - struct wlr_box new_geo = {0}; - new_geo.width = state->width; - new_geo.height = state->height; - if (c->geom.width - 2 * c->bw == new_geo.width && - c->geom.height - 2 * c->bw == new_geo.height && - c->surface.xwayland->x == c->geom.x + c->bw && - c->surface.xwayland->y == c->geom.y + c->bw) { + + if ((int32_t)c->geom.width - 2 * (int32_t)c->bw == (int32_t)state->width && + (int32_t)c->geom.height - 2 * (int32_t)c->bw == + (int32_t)state->height && + (int32_t)c->surface.xwayland->x == + (int32_t)c->geom.x + (int32_t)c->bw && + (int32_t)c->surface.xwayland->y == + (int32_t)c->geom.y + (int32_t)c->bw) { c->configure_serial = 0; } } From fa88ebace0cb352cb25fe5f989388df06d709e9c Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 16 Feb 2026 11:56:42 +0800 Subject: [PATCH 021/328] project: version not use latest tag --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index e7979bdf..5b19f9fc 100644 --- a/meson.build +++ b/meson.build @@ -56,7 +56,7 @@ endif if is_git_repo # 如果是 Git 目录,获取 Commit Hash 和最新的 tag commit_hash = run_command(git, 'rev-parse', '--short', 'HEAD', check : false).stdout().strip() - latest_tag = run_command(git, 'describe', '--tags', '--abbrev=0', check : false).stdout().strip() + latest_tag = meson.project_version() version_with_hash = '@0@(@1@)'.format(latest_tag, commit_hash) else # 如果不是 Git 目录,使用项目版本号和 "release" 字符串 From c3dcee2c8e3ae4e2839fda699a25e2d0a836b341 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 17 Feb 2026 08:33:45 +0800 Subject: [PATCH 022/328] opt: remove useless code --- src/client/client.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/client/client.h b/src/client/client.h index 2174c6fa..fd81a800 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -334,7 +334,6 @@ static inline uint32_t client_set_size(Client *c, uint32_t width, (int32_t)c->geom.x + (int32_t)c->bw && (int32_t)c->surface.xwayland->y == (int32_t)c->geom.y + (int32_t)c->bw) { - c->configure_serial = 0; return 0; } From 259fdb3a875e4c5842f75579c3e0dc85d897a4f9 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 18 Feb 2026 13:26:24 +0800 Subject: [PATCH 023/328] bump versiont to 0.12.3 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 5b19f9fc..06ccfd75 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('mango', ['c', 'cpp'], - version : '0.12.2', + version : '0.12.3', ) subdir('protocols') From 6924ca8512090c37d64386bb5e6fabb4a6410ecb Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 18 Feb 2026 23:07:23 +0800 Subject: [PATCH 024/328] feat: add global option prefer_scroller_overspread --- src/config/parse_config.h | 6 ++++ src/config/preset.h | 1 + src/layout/horizontal.h | 66 +++++++++++++++++++++++++++++++++------ src/layout/vertical.h | 62 +++++++++++++++++++++++++++++++----- 4 files changed, 118 insertions(+), 17 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index b7f89d59..64afd889 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -211,6 +211,7 @@ typedef struct { int32_t scroller_ignore_proportion_single; int32_t scroller_focus_center; int32_t scroller_prefer_center; + int32_t scroller_prefer_overspread; int32_t edge_scroller_pointer_focus; int32_t focus_cross_monitor; int32_t exchange_cross_monitor; @@ -1337,6 +1338,8 @@ bool parse_option(Config *config, char *key, char *value) { config->scroller_focus_center = atoi(value); } else if (strcmp(key, "scroller_prefer_center") == 0) { config->scroller_prefer_center = atoi(value); + } else if (strcmp(key, "scroller_prefer_overspread") == 0) { + config->scroller_prefer_overspread = atoi(value); } else if (strcmp(key, "edge_scroller_pointer_focus") == 0) { config->edge_scroller_pointer_focus = atoi(value); } else if (strcmp(key, "focus_cross_monitor") == 0) { @@ -3102,6 +3105,8 @@ void override_config(void) { CLAMP_INT(config.scroller_ignore_proportion_single, 0, 1); scroller_focus_center = CLAMP_INT(config.scroller_focus_center, 0, 1); scroller_prefer_center = CLAMP_INT(config.scroller_prefer_center, 0, 1); + scroller_prefer_overspread = + CLAMP_INT(config.scroller_prefer_overspread, 0, 1); edge_scroller_pointer_focus = CLAMP_INT(config.edge_scroller_pointer_focus, 0, 1); scroller_structs = CLAMP_INT(config.scroller_structs, 0, 1000); @@ -3301,6 +3306,7 @@ void set_value_default() { scroller_ignore_proportion_single; config.scroller_focus_center = scroller_focus_center; config.scroller_prefer_center = scroller_prefer_center; + config.scroller_prefer_overspread = scroller_prefer_overspread; config.edge_scroller_pointer_focus = edge_scroller_pointer_focus; config.focus_cross_monitor = focus_cross_monitor; config.exchange_cross_monitor = exchange_cross_monitor; diff --git a/src/config/preset.h b/src/config/preset.h index ae4424f9..d9824588 100644 --- a/src/config/preset.h +++ b/src/config/preset.h @@ -66,6 +66,7 @@ float scroller_default_proportion_single = 1.0; int32_t scroller_ignore_proportion_single = 1; int32_t scroller_focus_center = 0; int32_t scroller_prefer_center = 0; +int32_t scroller_prefer_overspread = 1; int32_t focus_cross_monitor = 0; int32_t focus_cross_tag = 0; int32_t exchange_cross_monitor = 0; diff --git a/src/layout/horizontal.h b/src/layout/horizontal.h index e1a335d1..b8016d50 100644 --- a/src/layout/horizontal.h +++ b/src/layout/horizontal.h @@ -283,6 +283,7 @@ void scroller(Monitor *m) { struct wlr_box target_geom; int32_t focus_client_index = 0; bool need_scroller = false; + bool over_overspread_to_left = false; int32_t cur_gappih = enablegaps ? m->gappih : 0; int32_t cur_gappoh = enablegaps ? m->gappoh : 0; int32_t cur_gappov = enablegaps ? m->gappov : 0; @@ -371,6 +372,45 @@ void scroller(Monitor *m) { } } + bool need_apply_overspread = + scroller_prefer_overspread && m->visible_scroll_tiling_clients > 1 && + tempClients[focus_client_index]->scroller_proportion < 1.0f; + + if (need_apply_overspread) { + + if (focus_client_index == 0) { + over_overspread_to_left = true; + } else { + over_overspread_to_left = false; + } + + if (over_overspread_to_left && + (!INSIDEMON(tempClients[1]) || + (tempClients[1]->scroller_proportion + + tempClients[focus_client_index]->scroller_proportion >= + 1.0f))) { + need_scroller = true; + } else if (!over_overspread_to_left && + (!INSIDEMON(tempClients[focus_client_index - 1]) || + (tempClients[focus_client_index - 1]->scroller_proportion + + tempClients[focus_client_index]->scroller_proportion >= + 1.0f))) { + need_scroller = true; + } else { + need_apply_overspread = false; + } + } + + bool need_apply_center = + scroller_focus_center || m->visible_scroll_tiling_clients == 1 || + (scroller_prefer_center && !need_apply_overspread && + (!m->prevsel || + (ISSCROLLTILED(m->prevsel) && + (m->prevsel->scroller_proportion * max_client_width) + + (tempClients[focus_client_index]->scroller_proportion * + max_client_width) > + m->w.width - 2 * scroller_structs - cur_gappih))); + if (n == 1 && scroller_ignore_proportion_single) { need_scroller = true; } @@ -394,18 +434,26 @@ void scroller(Monitor *m) { tempClients[focus_client_index], &target_geom); arrange_stack(tempClients[focus_client_index], target_geom, cur_gappiv); } else if (need_scroller) { - if (scroller_focus_center || - ((!m->prevsel || - (ISSCROLLTILED(m->prevsel) && - (m->prevsel->scroller_proportion * max_client_width) + - (root_client->scroller_proportion * max_client_width) > - m->w.width - 2 * scroller_structs - cur_gappih)) && - scroller_prefer_center)) { + if (need_apply_center) { target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2; + } else if (need_apply_overspread) { + if (over_overspread_to_left) { + target_geom.x = m->w.x + scroller_structs; + } else { + target_geom.x = + m->w.x + + (m->w.width - + tempClients[focus_client_index]->scroller_proportion * + max_client_width - + scroller_structs); + } + } else { - target_geom.x = root_client->geom.x > m->w.x + (m->w.width) / 2 + target_geom.x = tempClients[focus_client_index]->geom.x > + m->w.x + (m->w.width) / 2 ? m->w.x + (m->w.width - - root_client->scroller_proportion * + tempClients[focus_client_index] + ->scroller_proportion * max_client_width - scroller_structs) : m->w.x + scroller_structs; diff --git a/src/layout/vertical.h b/src/layout/vertical.h index f7bd442c..f036ca46 100644 --- a/src/layout/vertical.h +++ b/src/layout/vertical.h @@ -270,6 +270,7 @@ void vertical_scroller(Monitor *m) { struct wlr_box target_geom; int32_t focus_client_index = 0; bool need_scroller = false; + bool over_overspread_to_up = false; int32_t cur_gappiv = enablegaps ? m->gappiv : 0; int32_t cur_gappov = enablegaps ? m->gappov : 0; int32_t cur_gappoh = enablegaps ? m->gappoh : 0; @@ -355,6 +356,45 @@ void vertical_scroller(Monitor *m) { } } + bool need_apply_overspread = + scroller_prefer_overspread && m->visible_scroll_tiling_clients > 1 && + tempClients[focus_client_index]->scroller_proportion < 1.0f; + + if (need_apply_overspread) { + + if (focus_client_index == 0) { + over_overspread_to_up = true; + } else { + over_overspread_to_up = false; + } + + if (over_overspread_to_up && + (!INSIDEMON(tempClients[1]) || + (tempClients[1]->scroller_proportion + + tempClients[focus_client_index]->scroller_proportion >= + 1.0f))) { + need_scroller = true; + } else if (!over_overspread_to_up && + (!INSIDEMON(tempClients[focus_client_index - 1]) || + (tempClients[focus_client_index - 1]->scroller_proportion + + tempClients[focus_client_index]->scroller_proportion >= + 1.0f))) { + need_scroller = true; + } else { + need_apply_overspread = false; + } + } + + bool need_apply_center = + scroller_focus_center || m->visible_scroll_tiling_clients == 1 || + (scroller_prefer_center && !need_apply_overspread && + (!m->prevsel || + (ISSCROLLTILED(m->prevsel) && + (m->prevsel->scroller_proportion * max_client_height) + + (tempClients[focus_client_index]->scroller_proportion * + max_client_height) > + m->w.height - 2 * scroller_structs - cur_gappiv))); + if (n == 1 && scroller_ignore_proportion_single) { need_scroller = true; } @@ -381,18 +421,24 @@ void vertical_scroller(Monitor *m) { arrange_stack_vertical(tempClients[focus_client_index], target_geom, cur_gappih); } else if (need_scroller) { - if (scroller_focus_center || - ((!m->prevsel || - (ISSCROLLTILED(m->prevsel) && - (m->prevsel->scroller_proportion * max_client_height) + - (root_client->scroller_proportion * max_client_height) > - m->w.height - 2 * scroller_structs - cur_gappiv)) && - scroller_prefer_center)) { + if (need_apply_center) { target_geom.y = m->w.y + (m->w.height - target_geom.height) / 2; + } else if (need_apply_overspread) { + if (over_overspread_to_up) { + target_geom.y = m->w.y + scroller_structs; + } else { + target_geom.y = + m->w.y + + (m->w.height - + tempClients[focus_client_index]->scroller_proportion * + max_client_height - + scroller_structs); + } } else { target_geom.y = root_client->geom.y > m->w.y + (m->w.height) / 2 ? m->w.y + (m->w.height - - root_client->scroller_proportion * + tempClients[focus_client_index] + ->scroller_proportion * max_client_height - scroller_structs) : m->w.y + scroller_structs; From 23d7b11e27a1078dba9492943662a1fa444a82d7 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 19 Feb 2026 08:41:42 +0800 Subject: [PATCH 025/328] fix: only apply scroller overspread to head and tail client --- src/layout/horizontal.h | 1 + src/layout/vertical.h | 1 + 2 files changed, 2 insertions(+) diff --git a/src/layout/horizontal.h b/src/layout/horizontal.h index b8016d50..8140934a 100644 --- a/src/layout/horizontal.h +++ b/src/layout/horizontal.h @@ -374,6 +374,7 @@ void scroller(Monitor *m) { bool need_apply_overspread = scroller_prefer_overspread && m->visible_scroll_tiling_clients > 1 && + (focus_client_index == 0 || focus_client_index == n - 1) && tempClients[focus_client_index]->scroller_proportion < 1.0f; if (need_apply_overspread) { diff --git a/src/layout/vertical.h b/src/layout/vertical.h index f036ca46..4759e7a5 100644 --- a/src/layout/vertical.h +++ b/src/layout/vertical.h @@ -358,6 +358,7 @@ void vertical_scroller(Monitor *m) { bool need_apply_overspread = scroller_prefer_overspread && m->visible_scroll_tiling_clients > 1 && + (focus_client_index == 0 || focus_client_index == n - 1) && tempClients[focus_client_index]->scroller_proportion < 1.0f; if (need_apply_overspread) { From 68075c00445e23028ec49fa1008bec8c9bb1cdb7 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 19 Feb 2026 09:59:58 +0800 Subject: [PATCH 026/328] feat: support index arg in switch_keyboard_layout --- src/config/parse_config.h | 1 + src/dispatch/bind_define.h | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 64afd889..830d22ba 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -1017,6 +1017,7 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value, (*arg).v = strdup(arg_value); } else if (strcmp(func_name, "switch_keyboard_layout") == 0) { func = switch_keyboard_layout; + (*arg).i = CLAMP_INT(atoi(arg_value), 0, 100); } else if (strcmp(func_name, "setlayout") == 0) { func = setlayout; (*arg).v = strdup(arg_value); diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 0bfab158..bd065141 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -900,7 +900,13 @@ int32_t switch_keyboard_layout(const Arg *arg) { wlr_log(WLR_INFO, "Only one layout available"); return 0; } - xkb_layout_index_t next = (current + 1) % num_layouts; + + xkb_layout_index_t next = 0; + if (arg->i > 0 && arg->i <= num_layouts) { + next = arg->i - 1; + } else { + next = (current + 1) % num_layouts; + } // 6. 应用新 keymap uint32_t depressed = keyboard->modifiers.depressed; From 2f12f46919a3e224156f6c83e07aeff1f48b72e2 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 19 Feb 2026 19:07:36 +0800 Subject: [PATCH 027/328] opt: use base surface of client when xytonode in rect node --- src/fetch/common.h | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/fetch/common.h b/src/fetch/common.h index 58e69dc1..57a1a8e6 100644 --- a/src/fetch/common.h +++ b/src/fetch/common.h @@ -100,10 +100,6 @@ void xytonode(double x, double y, struct wlr_surface **psurface, Client **pc, surface = wlr_scene_surface_try_from_buffer( wlr_scene_buffer_from_node(node)) ->surface; - else if (node->type == WLR_SCENE_NODE_RECT) { - surface = NULL; - break; - } /* start from the topmost layer, find a sureface that can be focused by pointer, @@ -119,6 +115,13 @@ void xytonode(double x, double y, struct wlr_surface **psurface, Client **pc, l = pnode->data; } } + + if (node->type == WLR_SCENE_NODE_RECT) { + if (c) { + surface = client_surface(c); + } + break; + } } if (psurface) From f0259c6285a8a1f1ca5759414fb1bcbe54799956 Mon Sep 17 00:00:00 2001 From: Emil Miler Date: Thu, 19 Feb 2026 15:02:03 +0100 Subject: [PATCH 028/328] Include packaging status in README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 9b558fd1..05eaaaec 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,8 @@ https://github.com/user-attachments/assets/bb83004a-0563-4b48-ad89-6461a9b78b1f # Installation +[![Packaging status](https://repology.org/badge/vertical-allrepos/mangowc.svg)](https://repology.org/project/mangowc/versions) + ## Dependencies - glibc From 595f9e34327a65828984aa8df1f4f9251d8504e9 Mon Sep 17 00:00:00 2001 From: Baba Date: Thu, 19 Feb 2026 14:24:56 -0600 Subject: [PATCH 029/328] include tgmix in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9b558fd1..d468e7f7 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ https://github.com/user-attachments/assets/bb83004a-0563-4b48-ad89-6461a9b78b1f - vertical_tile - vertical_grid - vertical_scroller - +- tgmix (tile-grid mix) # Installation ## Dependencies From 07aed60245440fe2b4c6add0d50f7dfbffa550a2 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 21 Feb 2026 16:37:37 +0800 Subject: [PATCH 030/328] opt: improve some risk judgments --- src/dispatch/bind_define.h | 105 +++++++++++++++++++++++++++++++++++-- src/fetch/client.h | 3 ++ src/mango.c | 11 ++-- 3 files changed, 113 insertions(+), 6 deletions(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index bd065141..94d3d4ff 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1,5 +1,6 @@ int32_t bind_to_view(const Arg *arg) { - + if (!selmon) + return 0; uint32_t target = arg->ui; if (view_current_to_back && selmon->pertag->curtag && @@ -100,6 +101,8 @@ int32_t defaultgaps(const Arg *arg) { } int32_t exchange_client(const Arg *arg) { + if (!selmon) + return 0; Client *c = selmon->sel; if (!c || c->isfloating) return 0; @@ -112,6 +115,9 @@ int32_t exchange_client(const Arg *arg) { } int32_t exchange_stack_client(const Arg *arg) { + if (!selmon) + return 0; + Client *c = selmon->sel; Client *tc = NULL; if (!c || c->isfloating || c->isfullscreen || c->ismaximizescreen) @@ -265,42 +271,56 @@ int32_t incnmaster(const Arg *arg) { } int32_t incgaps(const Arg *arg) { + if (!selmon) + return 0; setgaps(selmon->gappoh + arg->i, selmon->gappov + arg->i, selmon->gappih + arg->i, selmon->gappiv + arg->i); return 0; } int32_t incigaps(const Arg *arg) { + if (!selmon) + return 0; setgaps(selmon->gappoh, selmon->gappov, selmon->gappih + arg->i, selmon->gappiv + arg->i); return 0; } int32_t incogaps(const Arg *arg) { + if (!selmon) + return 0; setgaps(selmon->gappoh + arg->i, selmon->gappov + arg->i, selmon->gappih, selmon->gappiv); return 0; } int32_t incihgaps(const Arg *arg) { + if (!selmon) + return 0; setgaps(selmon->gappoh, selmon->gappov, selmon->gappih + arg->i, selmon->gappiv); return 0; } int32_t incivgaps(const Arg *arg) { + if (!selmon) + return 0; setgaps(selmon->gappoh, selmon->gappov, selmon->gappih, selmon->gappiv + arg->i); return 0; } int32_t incohgaps(const Arg *arg) { + if (!selmon) + return 0; setgaps(selmon->gappoh + arg->i, selmon->gappov, selmon->gappih, selmon->gappiv); return 0; } int32_t incovgaps(const Arg *arg) { + if (!selmon) + return 0; setgaps(selmon->gappoh, selmon->gappov + arg->i, selmon->gappih, selmon->gappiv); return 0; @@ -330,6 +350,8 @@ int32_t setmfact(const Arg *arg) { int32_t killclient(const Arg *arg) { Client *c = NULL; + if (!selmon) + return 0; c = selmon->sel; if (c) { pending_kill_client(c); @@ -399,6 +421,8 @@ int32_t moveresize(const Arg *arg) { int32_t movewin(const Arg *arg) { Client *c = NULL; + if (!selmon) + return 0; c = selmon->sel; if (!c || c->isfullscreen) return 0; @@ -442,6 +466,8 @@ int32_t quit(const Arg *arg) { int32_t resizewin(const Arg *arg) { Client *c = NULL; + if (!selmon) + return 0; c = selmon->sel; int32_t offsetx = 0, offsety = 0; @@ -546,6 +572,8 @@ int32_t restore_minimized(const Arg *arg) { int32_t setlayout(const Arg *arg) { int32_t jk; + if (!selmon) + return 0; for (jk = 0; jk < LENGTH(layouts); jk++) { if (strcmp(layouts[jk].name, arg->v) == 0) { @@ -571,6 +599,8 @@ int32_t setkeymode(const Arg *arg) { } int32_t set_proportion(const Arg *arg) { + if (!selmon) + return 0; if (selmon->isoverview || !is_scroller_layout(selmon)) return 0; @@ -596,6 +626,8 @@ int32_t smartmovewin(const Arg *arg) { Client *c = NULL, *tc = NULL; int32_t nx, ny; int32_t buttom, top, left, right, tar; + if (!selmon) + return 0; c = selmon->sel; if (!c || c->isfullscreen) return 0; @@ -697,6 +729,8 @@ int32_t smartresizewin(const Arg *arg) { Client *c = NULL, *tc = NULL; int32_t nw, nh; int32_t buttom, top, left, right, tar; + if (!selmon) + return 0; c = selmon->sel; if (!c || c->isfullscreen) return 0; @@ -765,6 +799,8 @@ int32_t smartresizewin(const Arg *arg) { int32_t centerwin(const Arg *arg) { Client *c = NULL; + if (!selmon) + return 0; c = selmon->sel; if (!c || c->isfullscreen || c->ismaximizescreen) @@ -943,6 +979,9 @@ int32_t switch_layout(const Arg *arg) { char *target_layout_name = NULL; uint32_t len; + if (!selmon) + return 0; + if (config.circle_layout_count != 0) { for (jk = 0; jk < config.circle_layout_count; jk++) { @@ -994,6 +1033,8 @@ int32_t switch_layout(const Arg *arg) { int32_t switch_proportion_preset(const Arg *arg) { float target_proportion = 0; + if (!selmon) + return 0; if (config.scroller_proportion_preset_count == 0) { return 0; @@ -1038,6 +1079,8 @@ int32_t switch_proportion_preset(const Arg *arg) { } int32_t tag(const Arg *arg) { + if (!selmon) + return 0; Client *target_client = selmon->sel; tag_client(arg, target_client); return 0; @@ -1045,6 +1088,8 @@ int32_t tag(const Arg *arg) { int32_t tagmon(const Arg *arg) { Monitor *m = NULL, *cm = NULL; + if (!selmon) + return 0; Client *c = focustop(selmon); if (!c) @@ -1136,6 +1181,9 @@ int32_t tagsilent(const Arg *arg) { } int32_t tagtoleft(const Arg *arg) { + if (!selmon) + return 0; + if (selmon->sel != NULL && __builtin_popcount(selmon->tagset[selmon->seltags] & TAGMASK) == 1 && selmon->tagset[selmon->seltags] > 1) { @@ -1145,6 +1193,9 @@ int32_t tagtoleft(const Arg *arg) { } int32_t tagtoright(const Arg *arg) { + if (!selmon) + return 0; + if (selmon->sel != NULL && __builtin_popcount(selmon->tagset[selmon->seltags] & TAGMASK) == 1 && selmon->tagset[selmon->seltags] & (TAGMASK >> 1)) { @@ -1173,6 +1224,8 @@ int32_t toggle_named_scratchpad(const Arg *arg) { } int32_t toggle_render_border(const Arg *arg) { + if (!selmon) + return 0; render_border = !render_border; arrange(selmon, false, false); return 0; @@ -1208,6 +1261,8 @@ int32_t toggle_scratchpad(const Arg *arg) { } int32_t togglefakefullscreen(const Arg *arg) { + if (!selmon) + return 0; Client *sel = focustop(selmon); if (sel) setfakefullscreen(sel, !sel->isfakefullscreen); @@ -1215,6 +1270,9 @@ int32_t togglefakefullscreen(const Arg *arg) { } int32_t togglefloating(const Arg *arg) { + if (!selmon) + return 0; + Client *sel = focustop(selmon); if (selmon && selmon->isoverview) @@ -1236,6 +1294,9 @@ int32_t togglefloating(const Arg *arg) { } int32_t togglefullscreen(const Arg *arg) { + if (!selmon) + return 0; + Client *sel = focustop(selmon); if (!sel) return 0; @@ -1252,6 +1313,9 @@ int32_t togglefullscreen(const Arg *arg) { } int32_t toggleglobal(const Arg *arg) { + if (!selmon) + return 0; + if (!selmon->sel) return 0; if (selmon->sel->is_in_scratchpad) { @@ -1270,12 +1334,18 @@ int32_t toggleglobal(const Arg *arg) { } int32_t togglegaps(const Arg *arg) { + if (!selmon) + return 0; + enablegaps ^= 1; arrange(selmon, false, false); return 0; } int32_t togglemaximizescreen(const Arg *arg) { + if (!selmon) + return 0; + Client *sel = focustop(selmon); if (!sel) return 0; @@ -1294,6 +1364,9 @@ int32_t togglemaximizescreen(const Arg *arg) { } int32_t toggleoverlay(const Arg *arg) { + if (!selmon) + return 0; + if (!selmon->sel || !selmon->sel->mon || selmon->sel->isfullscreen) { return 0; } @@ -1315,6 +1388,9 @@ int32_t toggleoverlay(const Arg *arg) { } int32_t toggletag(const Arg *arg) { + if (!selmon) + return 0; + uint32_t newtags; Client *sel = focustop(selmon); if (!sel) @@ -1338,13 +1414,15 @@ int32_t toggletag(const Arg *arg) { } int32_t toggleview(const Arg *arg) { + if (!selmon) + return 0; + uint32_t newtagset; uint32_t target; target = arg->ui == 0 ? ~0 & TAGMASK : arg->ui; - newtagset = - selmon ? selmon->tagset[selmon->seltags] ^ (target & TAGMASK) : 0; + newtagset = selmon->tagset[selmon->seltags] ^ (target & TAGMASK); if (newtagset) { selmon->tagset[selmon->seltags] = newtagset; @@ -1356,6 +1434,9 @@ int32_t toggleview(const Arg *arg) { } int32_t viewtoleft(const Arg *arg) { + if (!selmon) + return 0; + uint32_t target = selmon->tagset[selmon->seltags]; if (selmon->isoverview || selmon->pertag->curtag == 0) { @@ -1376,6 +1457,9 @@ int32_t viewtoleft(const Arg *arg) { } int32_t viewtoright(const Arg *arg) { + if (!selmon) + return 0; + if (selmon->isoverview || selmon->pertag->curtag == 0) { return 0; } @@ -1393,6 +1477,9 @@ int32_t viewtoright(const Arg *arg) { } int32_t viewtoleft_have_client(const Arg *arg) { + if (!selmon) + return 0; + uint32_t n; uint32_t current = get_tags_first_tag_num(selmon->tagset[selmon->seltags]); bool found = false; @@ -1417,6 +1504,9 @@ int32_t viewtoleft_have_client(const Arg *arg) { } int32_t viewtoright_have_client(const Arg *arg) { + if (!selmon) + return 0; + uint32_t n; uint32_t current = get_tags_first_tag_num(selmon->tagset[selmon->seltags]); bool found = false; @@ -1441,6 +1531,9 @@ int32_t viewtoright_have_client(const Arg *arg) { } int32_t viewcrossmon(const Arg *arg) { + if (!selmon) + return 0; + focusmon(&(Arg){.v = arg->v, .i = UNDIR}); view_in_mon(arg, true, selmon, true); return 0; @@ -1519,6 +1612,8 @@ int32_t setoption(const Arg *arg) { } int32_t minimized(const Arg *arg) { + if (!selmon) + return 0; if (selmon && selmon->isoverview) return 0; @@ -1541,6 +1636,8 @@ void fix_mon_tagset_from_overview(Monitor *m) { int32_t toggleoverview(const Arg *arg) { Client *c = NULL; + if (!selmon) + return 0; if (selmon->isoverview && ov_tab_mode && arg->i != 1 && selmon->sel) { focusstack(&(Arg){.i = 1}); @@ -1646,6 +1743,8 @@ int32_t toggle_monitor(const Arg *arg) { } int32_t scroller_stack(const Arg *arg) { + if (!selmon) + return 0; Client *c = selmon->sel; Client *stack_head = NULL; Client *source_stack_head = NULL; diff --git a/src/fetch/client.h b/src/fetch/client.h index 8fccb261..11edb76b 100644 --- a/src/fetch/client.h +++ b/src/fetch/client.h @@ -83,6 +83,9 @@ setclient_coordinate_center(Client *c, Monitor *tm, struct wlr_box geom, int32_t len = 0; Monitor *m = tm ? tm : selmon; + if (!m) + return geom; + uint32_t cbw = check_hit_no_border(c) ? c->bw : 0; if (!c->no_force_center && m) { diff --git a/src/mango.c b/src/mango.c index 540395ae..45d72885 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1379,6 +1379,9 @@ void applyrules(Client *c) { Client *fc = NULL; Client *parent = NULL; + if (!c) + return; + parent = client_get_parent(c); Monitor *mon = parent && parent->mon ? parent->mon : selmon; @@ -1467,7 +1470,8 @@ void applyrules(Client *c) { /*-----------------------apply rule action-------------------------*/ // rule action only apply after map not apply in the init commit - if (!client_surface(c)->mapped) + struct wlr_surface *surface = client_surface(c); + if (!surface || !surface->mapped) return; // apply swallow rule @@ -1493,6 +1497,7 @@ void applyrules(Client *c) { setmon(c, mon, newtags, !c->isopensilent && !(client_is_x11_popup(c) && client_should_ignore_focus(c)) && + mon && (!c->istagsilent || !newtags || newtags & mon->tagset[mon->seltags])); @@ -1514,7 +1519,7 @@ void applyrules(Client *c) { window in the current tag will exit fullscreen and participate in tiling */ wl_list_for_each(fc, &clients, - link) if (fc && fc != c && c->tags & fc->tags && + link) if (fc && fc != c && c->tags & fc->tags && c->mon && VISIBLEON(fc, c->mon) && ISFULLSCREEN(fc) && !c->isfloating) { clear_fullscreen_flag(fc); @@ -1533,7 +1538,7 @@ void applyrules(Client *c) { } // apply overlay rule - if (c->isoverlay) { + if (c->isoverlay && c->scene) { wlr_scene_node_reparent(&c->scene->node, layers[LyrOverlay]); wlr_scene_node_raise_to_top(&c->scene->node); } From 3d680523d6d1d9d2d9ccbb95794bd7affee97121 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 21 Feb 2026 16:53:21 +0800 Subject: [PATCH 031/328] opt: if app open when no monitor, init tags and size in updatemons --- src/mango.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/mango.c b/src/mango.c index 45d72885..93f59800 100644 --- a/src/mango.c +++ b/src/mango.c @@ -5958,6 +5958,10 @@ void updatemons(struct wl_listener *listener, void *data) { c->mon = selmon; reset_foreign_tolevel(c); } + if(c->tags ==0 && !c->is_in_scratchpad) { + c->tags = selmon->tagset[selmon->seltags]; + set_size_per(selmon,c); + } } focusclient(focustop(selmon), 1); if (selmon->lock_surface) { From d1fd12898153853e2e2e77b709f4ea1ea7aa0d7f Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 21 Feb 2026 18:52:54 +0800 Subject: [PATCH 032/328] fix: auto set monitor coordinate when no match monitor rule --- src/mango.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mango.c b/src/mango.c index 93f59800..2c4fc436 100644 --- a/src/mango.c +++ b/src/mango.c @@ -2811,6 +2811,8 @@ void createmon(struct wl_listener *listener, void *data) { m->isoverview = 0; m->sel = NULL; m->is_in_hotarea = 0; + m->m.x = INT32_MAX; + m->m.y = INT32_MAX; float scale = 1; enum wl_output_transform rr = WL_OUTPUT_TRANSFORM_NORMAL; wlr_output_state_set_scale(&state, scale); From ee8a7b5961bfd306b1b592cc15268ce2a5ab31a8 Mon Sep 17 00:00:00 2001 From: Mujk <119647238+Mujk@users.noreply.github.com> Date: Fri, 20 Feb 2026 19:56:26 +0100 Subject: [PATCH 033/328] docs: fix guix installation instructions - Rename GuixSD to Guix System (the distro was renamed in 2019) --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 05eaaaec..29c356ea 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ Then, install the package: dnf install mangowc ``` -## GuixSD +## Guix System The package definition is described in the source repository. First, add `mangowc` channel to `channels.scm` file: @@ -109,7 +109,8 @@ First, add `mangowc` channel to `channels.scm` file: ;; In $HOME/.config/guix/channels.scm (cons (channel (name 'mangowc) - (url "https://github.com/DreamMaoMao/mangowc.git")) + (url "https://github.com/DreamMaoMao/mangowc.git") + (branch "main")) ... ;; Your other channels %default-channels) ``` @@ -121,8 +122,8 @@ Then, run `guix pull` and after update you can either run (use-modules (mangowc)) ;; Add mangowc module ;; Add mangowc to packages list -(packages (cons - mangowc +(packages (cons* + mangowc-git ... ;; Other packages you specified %base-packages)) ``` From 6b2d694b234cae9876eb81e27991439eb51860f1 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 22 Feb 2026 11:11:07 +0800 Subject: [PATCH 034/328] update readme --- README.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c91192d9..45da36cd 100644 --- a/README.md +++ b/README.md @@ -27,11 +27,18 @@ This project's development is based on [dwl](https://codeberg.org/dwl/dwl/). https://github.com/user-attachments/assets/bb83004a-0563-4b48-ad89-6461a9b78b1f +# Mango's Vision + +**Mango's primary goal is stability**: After months of testing and development—and aside from a few lingering GPU compatibility issues—it should now be stable enough. I don't plan on making many breaking changes. + +**Mango's preference is practicality**: I tend to add features that genuinely help with daily workflows—things that make our work more convenient. + +**Mango won't cater to every user preference**: For niche feature requests, I'll take a wait-and-see approach. I'll only consider adding them if they get a significant number of upvotes. + # Our discord [mangowc](https://discord.gg/CPjbDxesh5) # Supported layouts - - tile - scroller - monocle @@ -41,23 +48,20 @@ https://github.com/user-attachments/assets/bb83004a-0563-4b48-ad89-6461a9b78b1f - vertical_tile - vertical_grid - vertical_scroller -- tgmix (tile-grid mix) +- tgmix + # Installation [![Packaging status](https://repology.org/badge/vertical-allrepos/mangowc.svg)](https://repology.org/project/mangowc/versions) ## Dependencies -- glibc - wayland - wayland-protocols - libinput - libdrm - libxkbcommon - pixman -- git -- meson -- ninja - libdisplay-info - libliftoff - hwdata From f8fa7a856c3a569974cc89955d640167b2958205 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 23 Feb 2026 08:01:45 +0800 Subject: [PATCH 035/328] opt: optimize frame skip logic --- src/animation/client.h | 4 ++++ src/client/client.h | 28 ------------------------- src/mango.c | 47 ++++++++++++++++++++++++------------------ 3 files changed, 31 insertions(+), 48 deletions(-) diff --git a/src/animation/client.h b/src/animation/client.h index 2588fb1c..b6683ecc 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -990,6 +990,10 @@ void resize(Client *c, struct wlr_box geo, int32_t interact) { c->configure_serial = client_set_size(c, c->geom.width - 2 * c->bw, c->geom.height - 2 * c->bw); + if (c->configure_serial != 0) { + c->mon->resizing_count_pending++; + } + if (c == grabc) { c->animation.running = false; c->need_output_flush = false; diff --git a/src/client/client.h b/src/client/client.h index fd81a800..4788e448 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -243,34 +243,6 @@ static inline int32_t client_is_rendered_on_mon(Client *c, Monitor *m) { return 0; } -static inline int32_t client_is_stopped(Client *c) { - int32_t pid; - siginfo_t in = {0}; -#ifdef XWAYLAND - if (client_is_x11(c)) - return 0; -#endif - - wl_client_get_credentials(c->surface.xdg->client->client, &pid, NULL, NULL); - if (waitid(P_PID, pid, &in, WNOHANG | WCONTINUED | WSTOPPED | WNOWAIT) < - 0) { - /* This process is not our child process. We cannot determine its - * stopped state; assume it is not stopped to avoid blocking frame skip. - */ - if (errno == ECHILD) - return 0; // if not our child, assume not stopped - /* Other errors, also assume not stopped. */ - return 0; - } else if (in.si_pid) { - if (in.si_code == CLD_STOPPED || in.si_code == CLD_TRAPPED) - return 1; - if (in.si_code == CLD_CONTINUED) - return 0; - } - - return 0; -} - static inline int32_t client_is_unmanaged(Client *c) { #ifdef XWAYLAND if (client_is_x11(c)) diff --git a/src/mango.c b/src/mango.c index 2c4fc436..fc7eeebc 100644 --- a/src/mango.c +++ b/src/mango.c @@ -503,13 +503,15 @@ struct Monitor { struct wl_listener request_state; struct wl_listener destroy_lock_surface; struct wlr_session_lock_surface_v1 *lock_surface; - struct wl_event_source *skip_timeout; + struct wl_event_source *skip_frame_timeout; struct wlr_box m; /* monitor area, layout-relative */ struct wlr_box w; /* window area, layout-relative */ struct wl_list layers[4]; /* LayerSurface::link */ uint32_t seltags; uint32_t tagset[2]; bool skiping_frame; + uint32_t resizing_count_pending; + uint32_t resizing_count_current; struct wl_list dwl_ipc_outputs; int32_t gappih; /* horizontal gap between windows */ @@ -786,7 +788,7 @@ static Client *get_scroll_stack_head(Client *c); static bool client_only_in_one_tag(Client *c); static Client *get_focused_stack_client(Client *sc); static bool client_is_in_same_stack(Client *sc, Client *tc, Client *fc); -static void monitor_stop_skip_timer(Monitor *m); +static void monitor_stop_skip_frame_timer(Monitor *m); static int monitor_skip_frame_timeout_callback(void *data); #include "data/static_keymap.h" @@ -2234,10 +2236,10 @@ void cleanupmon(struct wl_listener *listener, void *data) { wlr_scene_node_destroy(&m->blur->node); m->blur = NULL; } - if (m->skip_timeout) { - monitor_stop_skip_timer(m); - wl_event_source_remove(m->skip_timeout); - m->skip_timeout = NULL; + if (m->skip_frame_timeout) { + monitor_stop_skip_frame_timer(m); + wl_event_source_remove(m->skip_frame_timeout); + m->skip_frame_timeout = NULL; } m->wlr_output->data = NULL; free(m->pertag); @@ -2790,9 +2792,11 @@ void createmon(struct wl_listener *listener, void *data) { struct wl_event_loop *loop = wl_display_get_event_loop(dpy); m = wlr_output->data = ecalloc(1, sizeof(*m)); - m->skip_timeout = + m->skip_frame_timeout = wl_event_loop_add_timer(loop, monitor_skip_frame_timeout_callback, m); m->skiping_frame = false; + m->resizing_count_pending = 0; + m->resizing_count_current = 0; m->wlr_output = wlr_output; m->wlr_output->data = m; @@ -4443,10 +4447,12 @@ void client_set_opacity(Client *c, double opacity) { scene_buffer_apply_opacity, &opacity); } -void monitor_stop_skip_timer(Monitor *m) { - if (m->skip_timeout) - wl_event_source_timer_update(m->skip_timeout, 0); +void monitor_stop_skip_frame_timer(Monitor *m) { + if (m->skip_frame_timeout) + wl_event_source_timer_update(m->skip_frame_timeout, 0); m->skiping_frame = false; + m->resizing_count_pending = 0; + m->resizing_count_current = 0; } static int monitor_skip_frame_timeout_callback(void *data) { @@ -4455,20 +4461,22 @@ static int monitor_skip_frame_timeout_callback(void *data) { wl_list_for_each_safe(c, tmp, &clients, link) { c->configure_serial = 0; } - monitor_stop_skip_timer(m); + monitor_stop_skip_frame_timer(m); wlr_output_schedule_frame(m->wlr_output); return 1; } void monitor_check_skip_frame_timeout(Monitor *m) { - if (m->skiping_frame) { + if (m->skiping_frame && + m->resizing_count_pending == m->resizing_count_current) { return; } - if (m->skip_timeout) { - wl_event_source_timer_update(m->skip_timeout, 100); // 100ms + if (m->skip_frame_timeout) { + m->resizing_count_current = m->resizing_count_pending; m->skiping_frame = true; + wl_event_source_timer_update(m->skip_frame_timeout, 100); // 100ms } } @@ -4511,16 +4519,15 @@ void rendermon(struct wl_listener *listener, void *data) { // 绘制客户端 wl_list_for_each(c, &clients, link) { need_more_frames = client_draw_frame(c) || need_more_frames; - if (!animations && !(allow_tearing && frame_allow_tearing) && - c->configure_serial && client_is_rendered_on_mon(c, m) && - !client_is_stopped(c) && !grabc) { + if (!animations && !grabc && c->configure_serial && + client_is_rendered_on_mon(c, m)) { monitor_check_skip_frame_timeout(m); goto skip; } } if (m->skiping_frame) { - monitor_stop_skip_timer(m); + monitor_stop_skip_frame_timer(m); } // 只有在需要帧时才构建和提交状态 @@ -5960,9 +5967,9 @@ void updatemons(struct wl_listener *listener, void *data) { c->mon = selmon; reset_foreign_tolevel(c); } - if(c->tags ==0 && !c->is_in_scratchpad) { + if (c->tags == 0 && !c->is_in_scratchpad) { c->tags = selmon->tagset[selmon->seltags]; - set_size_per(selmon,c); + set_size_per(selmon, c); } } focusclient(focustop(selmon), 1); From 2f630c950e0f7ef4a5bc3c81abf9a6e1d0e88e05 Mon Sep 17 00:00:00 2001 From: Jiatao Liang Date: Thu, 19 Feb 2026 16:20:56 -0500 Subject: [PATCH 036/328] optional-session-manager --- nix/nixos-modules.nix | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/nix/nixos-modules.nix b/nix/nixos-modules.nix index 33811022..7a9f930a 100644 --- a/nix/nixos-modules.nix +++ b/nix/nixos-modules.nix @@ -9,6 +9,10 @@ in { options = { programs.mango = { enable = lib.mkEnableOption "mango, a wayland compositor based on dwl"; + addLoginEntry = lib.mkEnableOption { + default = true; + description = "Whether to add a login entry to the display manager for mango"; + }; package = lib.mkOption { type = lib.types.package; default = self.packages.${pkgs.stdenv.hostPlatform.system}.mango; @@ -55,7 +59,7 @@ in { programs.xwayland.enable = lib.mkDefault true; services = { - displayManager.sessionPackages = [cfg.package]; + displayManager.sessionPackages = lib.mkIf cfg.addLoginEntry [ cfg.package ]; graphical-desktop.enable = lib.mkDefault true; }; From a28647585fc75fa492ae9f29ee7b9e542803ddad Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 24 Feb 2026 21:40:26 +0800 Subject: [PATCH 037/328] feat: set dbus env auto --- config.conf => assets/config.conf | 0 assets/mango-portals.conf | 10 ++++ mango.desktop => assets/mango.desktop | 0 meson.build | 6 +- src/common/util.c | 82 +++++++++++++++++++++++++++ src/common/util.h | 6 +- src/config/parse_config.h | 10 ++++ src/mango.c | 44 ++++++++++++++ 8 files changed, 155 insertions(+), 3 deletions(-) rename config.conf => assets/config.conf (100%) create mode 100644 assets/mango-portals.conf rename mango.desktop => assets/mango.desktop (100%) diff --git a/config.conf b/assets/config.conf similarity index 100% rename from config.conf rename to assets/config.conf diff --git a/assets/mango-portals.conf b/assets/mango-portals.conf new file mode 100644 index 00000000..645344af --- /dev/null +++ b/assets/mango-portals.conf @@ -0,0 +1,10 @@ +[preferred] +default=gtk +org.freedesktop.impl.portal.ScreenCast=wlr +org.freedesktop.impl.portal.Screenshot=wlr + +### My addition ### +# ignore inhibit bc gtk portal always returns as success, +# despite sway/the wlr portal not having an implementation, +# stopping firefox from using wayland idle-inhibit +org.freedesktop.impl.portal.Inhibit=none diff --git a/mango.desktop b/assets/mango.desktop similarity index 100% rename from mango.desktop rename to assets/mango.desktop diff --git a/meson.build b/meson.build index 06ccfd75..3f2e5e50 100644 --- a/meson.build +++ b/meson.build @@ -147,5 +147,7 @@ executable('mmsg', ) desktop_install_dir = join_paths(prefix, 'share/wayland-sessions') -install_data('mango.desktop', install_dir : desktop_install_dir) -install_data('config.conf', install_dir : join_paths(sysconfdir, 'mango')) +portal_install_dir = join_paths(prefix, 'share/xdg-desktop-portal') +install_data('assets/mango.desktop', install_dir : desktop_install_dir) +install_data('assets/mango-portals.conf', install_dir : portal_install_dir) +install_data('assets/config.conf', install_dir : join_paths(sysconfdir, 'mango')) diff --git a/src/common/util.c b/src/common/util.c index a15cca7c..025aed6d 100644 --- a/src/common/util.c +++ b/src/common/util.c @@ -92,3 +92,85 @@ uint32_t get_now_in_ms(void) { uint32_t timespec_to_ms(struct timespec *ts) { return (uint32_t)ts->tv_sec * 1000 + (uint32_t)ts->tv_nsec / 1000000; } + +char *join_strings(char *arr[], const char *sep) { + if (!arr || !arr[0]) { + char *empty = malloc(1); + if (empty) + empty[0] = '\0'; + return empty; + } + + size_t total_len = 0; + int count = 0; + for (int i = 0; arr[i] != NULL; i++) { + total_len += strlen(arr[i]); + count++; + } + if (count > 0) { + total_len += strlen(sep) * (count - 1); + } + + char *result = malloc(total_len + 1); + if (!result) + return NULL; + + result[0] = '\0'; + for (int i = 0; arr[i] != NULL; i++) { + if (i > 0) + strcat(result, sep); + strcat(result, arr[i]); + } + return result; +} + +char *join_strings_with_suffix(char *arr[], const char *suffix, + const char *sep) { + if (!arr || !arr[0]) { + char *empty = malloc(1); + if (empty) + empty[0] = '\0'; + return empty; + } + + size_t total_len = 0; + int count = 0; + for (int i = 0; arr[i] != NULL; i++) { + total_len += strlen(arr[i]) + strlen(suffix); + count++; + } + if (count > 0) { + total_len += strlen(sep) * (count - 1); + } + + char *result = malloc(total_len + 1); + if (!result) + return NULL; + + result[0] = '\0'; + for (int i = 0; arr[i] != NULL; i++) { + if (i > 0) + strcat(result, sep); + strcat(result, arr[i]); + strcat(result, suffix); + } + return result; +} + +char *string_printf(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + int len = vsnprintf(NULL, 0, fmt, args); + va_end(args); + if (len < 0) + return NULL; + + char *str = malloc(len + 1); + if (!str) + return NULL; + + va_start(args, fmt); + vsnprintf(str, len + 1, fmt, args); + va_end(args); + return str; +} diff --git a/src/common/util.h b/src/common/util.h index 8fb60338..cb232ac5 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -7,4 +7,8 @@ int32_t fd_set_nonblock(int32_t fd); int32_t regex_match(const char *pattern_mb, const char *str_mb); void wl_list_append(struct wl_list *list, struct wl_list *object); uint32_t get_now_in_ms(void); -uint32_t timespec_to_ms(struct timespec *ts); \ No newline at end of file +uint32_t timespec_to_ms(struct timespec *ts); +char *join_strings(char *arr[], const char *sep); +char *join_strings_with_suffix(char *arr[], const char *suffix, + const char *sep); +char *string_printf(const char *fmt, ...); \ No newline at end of file diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 830d22ba..1c333834 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -3647,6 +3647,16 @@ void reapply_cursor_style(void) { cursor_mgr = wlr_xcursor_manager_create(config.cursor_theme, cursor_size); + if(cursor_size > 0){ + char size_str[16]; + snprintf(size_str, sizeof(size_str), "%d", cursor_size); + setenv("XCURSOR_SIZE", size_str, 1); + } + + if(config.cursor_theme){ + setenv("XCURSOR_THEME", config.cursor_theme, 1); + } + Monitor *m = NULL; wl_list_for_each(m, &mons, link) { wlr_xcursor_manager_load(cursor_mgr, m->wlr_output->scale); diff --git a/src/mango.c b/src/mango.c index fc7eeebc..886a8a29 100644 --- a/src/mango.c +++ b/src/mango.c @@ -894,6 +894,12 @@ static KeyMode keymode = { .mode = {'d', 'e', 'f', 'a', 'u', 'l', 't', '\0'}, .isdefault = true, }; + +static char *env_vars[] = {"DISPLAY", + "WAYLAND_DISPLAY", + "XDG_CURRENT_DESKTOP", + "XDG_SESSION_TYPE", + NULL}; static struct { enum wp_cursor_shape_device_v1_shape shape; struct wlr_surface *surface; @@ -4727,6 +4733,41 @@ void exchange_two_client(Client *c1, Client *c2) { } } +void set_activation_env() { + if (!getenv("DBUS_SESSION_BUS_ADDRESS")) { + wlr_log(WLR_INFO, "Not updating dbus execution environment: " + "DBUS_SESSION_BUS_ADDRESS not set"); + return; + } + + wlr_log(WLR_INFO, "Updating dbus execution environment"); + + char *env_keys = join_strings(env_vars, " "); + + // first command: dbus-update-activation-environment + const char *arg1 = env_keys; + char *cmd1 = string_printf("dbus-update-activation-environment %s", arg1); + if (!cmd1) { + wlr_log(WLR_ERROR, "Failed to allocate command string"); + goto cleanup; + } + spawn(&(Arg){.v = cmd1}); + free(cmd1); + + // second command: systemctl --user + const char *action = "import-environment"; + char *cmd2 = string_printf("systemctl --user %s %s", action, env_keys); + if (!cmd2) { + wlr_log(WLR_ERROR, "Failed to allocate command string"); + goto cleanup; + } + spawn(&(Arg){.v = cmd2}); + free(cmd2); + +cleanup: + free(env_keys); +} + void // 17 run(char *startup_cmd) { @@ -4784,6 +4825,8 @@ run(char *startup_cmd) { wlr_cursor_set_xcursor(cursor, cursor_mgr, "left_ptr"); handlecursoractivity(); + set_activation_env(); + run_exec(); run_exec_once(); @@ -5280,6 +5323,7 @@ void setup(void) { setenv("XCURSOR_SIZE", "24", 1); setenv("XDG_CURRENT_DESKTOP", "mango", 1); + parse_config(); init_baked_points(); From 564df864bf3b81c1bcd576a310e5ba297d8880a5 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 25 Feb 2026 10:06:58 +0800 Subject: [PATCH 038/328] fix: popup position constrain not work for some app --- src/config/parse_config.h | 8 +- src/ext-protocol/text-input.h | 11 +-- src/fetch/monitor.h | 9 +++ src/mango.c | 137 ++++++++++++++++++++++++++-------- 4 files changed, 118 insertions(+), 47 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 1c333834..95e54c05 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -3647,13 +3647,13 @@ void reapply_cursor_style(void) { cursor_mgr = wlr_xcursor_manager_create(config.cursor_theme, cursor_size); - if(cursor_size > 0){ - char size_str[16]; - snprintf(size_str, sizeof(size_str), "%d", cursor_size); + if (cursor_size > 0) { + char size_str[16]; + snprintf(size_str, sizeof(size_str), "%d", cursor_size); setenv("XCURSOR_SIZE", size_str, 1); } - if(config.cursor_theme){ + if (config.cursor_theme) { setenv("XCURSOR_THEME", config.cursor_theme, 1); } diff --git a/src/ext-protocol/text-input.h b/src/ext-protocol/text-input.h index dbd97e11..e9f221a4 100644 --- a/src/ext-protocol/text-input.h +++ b/src/ext-protocol/text-input.h @@ -77,15 +77,6 @@ Monitor *output_from_wlr_output(struct wlr_output *wlr_output) { return NULL; } -Monitor *output_nearest_to(int32_t lx, int32_t ly) { - double closest_x, closest_y; - wlr_output_layout_closest_point(output_layout, NULL, lx, ly, &closest_x, - &closest_y); - - return output_from_wlr_output( - wlr_output_layout_output_at(output_layout, closest_x, closest_y)); -} - bool output_is_usable(Monitor *m) { return m && m->wlr_output->enabled; } static bool @@ -255,7 +246,7 @@ static void update_popup_position(struct dwl_input_method_popup *popup) { cursor_rect = (struct wlr_box){0}; } - output = output_nearest_to(cursor_rect.x, cursor_rect.y); + output = get_monitor_nearest_to(cursor_rect.x, cursor_rect.y); if (!output_is_usable(output)) { return; } diff --git a/src/fetch/monitor.h b/src/fetch/monitor.h index 7a1ca4dc..fd18498e 100644 --- a/src/fetch/monitor.h +++ b/src/fetch/monitor.h @@ -96,3 +96,12 @@ Monitor *xytomon(double x, double y) { struct wlr_output *o = wlr_output_layout_output_at(output_layout, x, y); return o ? o->data : NULL; } + +Monitor *get_monitor_nearest_to(int32_t lx, int32_t ly) { + double closest_x, closest_y; + wlr_output_layout_closest_point(output_layout, NULL, lx, ly, &closest_x, + &closest_y); + + return output_from_wlr_output( + wlr_output_layout_output_at(output_layout, closest_x, closest_y)); +} \ No newline at end of file diff --git a/src/mango.c b/src/mango.c index 886a8a29..c8288d05 100644 --- a/src/mango.c +++ b/src/mango.c @@ -486,6 +486,16 @@ typedef struct { bool being_unmapped; } LayerSurface; +typedef struct { + struct wlr_xdg_popup *wlr_popup; + Client *client; + LayerSurface *layer; + uint32_t type; + struct wl_listener destroy; + struct wl_listener commit; + struct wl_listener reposition; +} Popup; + typedef struct { const char *symbol; void (*arrange)(Monitor *); @@ -790,6 +800,7 @@ static Client *get_focused_stack_client(Client *sc); static bool client_is_in_same_stack(Client *sc, Client *tc, Client *fc); static void monitor_stop_skip_frame_timer(Monitor *m); static int monitor_skip_frame_timeout_callback(void *data); +static Monitor *get_monitor_nearest_to(int32_t lx, int32_t ly); #include "data/static_keymap.h" #include "dispatch/bind_declare.h" @@ -895,11 +906,8 @@ static KeyMode keymode = { .isdefault = true, }; -static char *env_vars[] = {"DISPLAY", - "WAYLAND_DISPLAY", - "XDG_CURRENT_DESKTOP", - "XDG_SESSION_TYPE", - NULL}; +static char *env_vars[] = {"DISPLAY", "WAYLAND_DISPLAY", "XDG_CURRENT_DESKTOP", + "XDG_SESSION_TYPE", NULL}; static struct { enum wp_cursor_shape_device_v1_shape shape; struct wlr_surface *surface; @@ -2545,37 +2553,107 @@ void destroydecoration(struct wl_listener *listener, void *data) { wl_list_remove(&c->set_decoration_mode.link); } -void commitpopup(struct wl_listener *listener, void *data) { +static void popup_unconstrain(Popup *popup) { + struct wlr_xdg_popup *wlr_popup = popup->wlr_popup; + + if (!wlr_popup || !wlr_popup->parent) { + return; + } + + struct wlr_scene_node *parent_node = wlr_popup->parent->data; + if (!parent_node) { + wlr_log(WLR_ERROR, "Popup parent has no scene node"); + return; + } + int parent_lx, parent_ly; + wlr_scene_node_coords(parent_node, &parent_lx, &parent_ly); + + struct wlr_box *scheduled = &wlr_popup->scheduled.geometry; + int popup_lx = parent_lx + scheduled->x; + int popup_ly = parent_ly + scheduled->y; + + Monitor *mon = get_monitor_nearest_to(popup_lx, popup_ly); + + struct wlr_box usable = popup->type == LayerShell ? mon->m : mon->w; + + struct wlr_box constraint_box = { + .x = usable.x - parent_lx, + .y = usable.y - parent_ly, + .width = usable.width, + .height = usable.height, + }; + + wlr_xdg_popup_unconstrain_from_box(wlr_popup, &constraint_box); +} + +static void destroypopup(struct wl_listener *listener, void *data) { + Popup *popup = wl_container_of(listener, popup, destroy); + wl_list_remove(&popup->destroy.link); + wl_list_remove(&popup->reposition.link); + free(popup); +} + +static void commitpopup(struct wl_listener *listener, void *data) { + Popup *popup = wl_container_of(listener, popup, commit); + struct wlr_surface *surface = data; - struct wlr_xdg_popup *popup = wlr_xdg_popup_try_from_wlr_surface(surface); - LayerSurface *l = NULL; + struct wlr_xdg_popup *wkr_popup = + wlr_xdg_popup_try_from_wlr_surface(surface); + Client *c = NULL; - struct wlr_box box; + LayerSurface *l = NULL; int32_t type = -1; - if (!popup || !popup->base->initial_commit) + if (!wkr_popup || !wkr_popup->base->initial_commit) goto commitpopup_listen_free; - type = toplevel_from_wlr_surface(popup->base->surface, &c, &l); - if (!popup->parent || !popup->parent->data || type < 0) - goto commitpopup_listen_free; - - wlr_scene_node_raise_to_top(popup->parent->data); - - popup->base->surface->data = - wlr_scene_xdg_surface_create(popup->parent->data, popup->base); - if ((l && !l->mon) || (c && !c->mon)) { - wlr_xdg_popup_destroy(popup); + type = toplevel_from_wlr_surface(wkr_popup->base->surface, &c, &l); + if (!wkr_popup->parent || !wkr_popup->parent->data || type < 0) { + wlr_xdg_popup_destroy(wkr_popup); goto commitpopup_listen_free; } - box = type == LayerShell ? l->mon->m : c->mon->w; - box.x -= (type == LayerShell ? l->scene->node.x : c->geom.x); - box.y -= (type == LayerShell ? l->scene->node.y : c->geom.y); - wlr_xdg_popup_unconstrain_from_box(popup, &box); + + wlr_scene_node_raise_to_top(wkr_popup->parent->data); + + wkr_popup->base->surface->data = + wlr_scene_xdg_surface_create(wkr_popup->parent->data, wkr_popup->base); + if ((l && !l->mon) || (c && !c->mon)) { + wlr_xdg_popup_destroy(wkr_popup); + goto commitpopup_listen_free; + } + + popup->client = c; + popup->layer = l; + popup->type = type; + popup->wlr_popup = wkr_popup; + + popup_unconstrain(popup); commitpopup_listen_free: - wl_list_remove(&listener->link); - free(listener); + wl_list_remove(&popup->commit.link); + popup->commit.notify = NULL; +} + +static void repositionpopup(struct wl_listener *listener, void *data) { + Popup *popup = wl_container_of(listener, popup, reposition); + popup_unconstrain(popup); +} + +static void createpopup(struct wl_listener *listener, void *data) { + struct wlr_xdg_popup *wlr_popup = data; + + Popup *popup = calloc(1, sizeof(Popup)); + if (!popup) + return; + + popup->destroy.notify = destroypopup; + wl_signal_add(&wlr_popup->events.destroy, &popup->destroy); + + popup->commit.notify = commitpopup; + wl_signal_add(&wlr_popup->base->surface->events.commit, &popup->commit); + + popup->reposition.notify = repositionpopup; + wl_signal_add(&wlr_popup->events.reposition, &popup->reposition); } void createdecoration(struct wl_listener *listener, void *data) { @@ -3165,13 +3243,6 @@ void createpointerconstraint(struct wl_listener *listener, void *data) { &pointer_constraint->destroy, destroypointerconstraint); } -void createpopup(struct wl_listener *listener, void *data) { - /* This event is raised when a client (either xdg-shell or layer-shell) - * creates a new popup. */ - struct wlr_xdg_popup *popup = data; - LISTEN_STATIC(&popup->base->surface->events.commit, commitpopup); -} - void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint) { if (active_constraint == constraint) return; From 6894a36019db78395ba10ad01a522233478fbe34 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 25 Feb 2026 11:15:56 +0800 Subject: [PATCH 039/328] opt: remove some portal file comment --- assets/mango-portals.conf | 5 ----- 1 file changed, 5 deletions(-) diff --git a/assets/mango-portals.conf b/assets/mango-portals.conf index 645344af..aebea31a 100644 --- a/assets/mango-portals.conf +++ b/assets/mango-portals.conf @@ -2,9 +2,4 @@ default=gtk org.freedesktop.impl.portal.ScreenCast=wlr org.freedesktop.impl.portal.Screenshot=wlr - -### My addition ### -# ignore inhibit bc gtk portal always returns as success, -# despite sway/the wlr portal not having an implementation, -# stopping firefox from using wayland idle-inhibit org.freedesktop.impl.portal.Inhibit=none From e1c038ae087b1b683b583383f47b99479f4df67c Mon Sep 17 00:00:00 2001 From: qaqland Date: Wed, 25 Feb 2026 16:58:18 +0800 Subject: [PATCH 040/328] opt: remove unused variable in function rendermon Signed-off-by: qaqland --- src/mango.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/mango.c b/src/mango.c index c8288d05..1ba319a0 100644 --- a/src/mango.c +++ b/src/mango.c @@ -4560,7 +4560,6 @@ void monitor_check_skip_frame_timeout(Monitor *m) { void rendermon(struct wl_listener *listener, void *data) { Monitor *m = wl_container_of(listener, m, frame); Client *c = NULL, *tmp = NULL; - struct wlr_output_state pending = {0}; LayerSurface *l = NULL, *tmpl = NULL; int32_t i; struct wl_list *layer_list; @@ -4621,7 +4620,6 @@ skip: wlr_scene_output_send_frame_done(m->scene_output, &now); } else { wlr_scene_output_send_frame_done(m->scene_output, &now); - wlr_output_state_finish(&pending); } // 如果需要更多帧,确保安排下一帧 From bc5cf2c7d7da4655c8f0139f16a908f93fbb4b2a Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 25 Feb 2026 17:25:05 +0800 Subject: [PATCH 041/328] opt: remove useless code --- src/mango.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/mango.c b/src/mango.c index 1ba319a0..87423baa 100644 --- a/src/mango.c +++ b/src/mango.c @@ -4616,11 +4616,7 @@ void rendermon(struct wl_listener *listener, void *data) { skip: // 发送帧完成通知 clock_gettime(CLOCK_MONOTONIC, &now); - if (allow_tearing && frame_allow_tearing) { - wlr_scene_output_send_frame_done(m->scene_output, &now); - } else { - wlr_scene_output_send_frame_done(m->scene_output, &now); - } + wlr_scene_output_send_frame_done(m->scene_output, &now); // 如果需要更多帧,确保安排下一帧 if (need_more_frames && allow_frame_scheduling) { From 43d0f0f54a30dbd75027ef1cc9e361abdb71ce13 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 25 Feb 2026 17:35:19 +0800 Subject: [PATCH 042/328] opt: remove useless code --- src/mango.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/mango.c b/src/mango.c index 87423baa..3344ca7f 100644 --- a/src/mango.c +++ b/src/mango.c @@ -488,8 +488,6 @@ typedef struct { typedef struct { struct wlr_xdg_popup *wlr_popup; - Client *client; - LayerSurface *layer; uint32_t type; struct wl_listener destroy; struct wl_listener commit; @@ -2622,8 +2620,6 @@ static void commitpopup(struct wl_listener *listener, void *data) { goto commitpopup_listen_free; } - popup->client = c; - popup->layer = l; popup->type = type; popup->wlr_popup = wkr_popup; From 6667708d9aa069f72687ab3c015738ea2ddf6684 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 25 Feb 2026 19:16:06 +0800 Subject: [PATCH 043/328] feat: monitor arg support multi spec match in disptach --- src/dispatch/bind_define.h | 12 +++---- src/fetch/monitor.h | 69 ++++++++++++++++++++++++++++++++++++++ src/mango.c | 1 + 3 files changed, 76 insertions(+), 6 deletions(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 94d3d4ff..03d34cbb 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -210,7 +210,7 @@ int32_t focusmon(const Arg *arg) { if (!m->wlr_output->enabled) { continue; } - if (regex_match(arg->v, m->wlr_output->name)) { + if (match_monitor_spec(arg->v, m)) { tm = m; break; } @@ -1102,7 +1102,7 @@ int32_t tagmon(const Arg *arg) { if (!cm->wlr_output->enabled) { continue; } - if (regex_match(arg->v, cm->wlr_output->name)) { + if (match_monitor_spec(arg->v, cm)) { m = cm; break; } @@ -1543,7 +1543,7 @@ int32_t tagcrossmon(const Arg *arg) { if (!selmon || !selmon->sel) return 0; - if (regex_match(selmon->wlr_output->name, arg->v)) { + if (match_monitor_spec(arg->v, selmon)) { tag_client(arg, selmon->sel); return 0; } @@ -1701,7 +1701,7 @@ int32_t disable_monitor(const Arg *arg) { Monitor *m = NULL; struct wlr_output_state state = {0}; wl_list_for_each(m, &mons, link) { - if (regex_match(arg->v, m->wlr_output->name)) { + if (match_monitor_spec(arg->v, m)) { wlr_output_state_set_enabled(&state, false); wlr_output_commit_state(m->wlr_output, &state); m->asleep = 1; @@ -1716,7 +1716,7 @@ int32_t enable_monitor(const Arg *arg) { Monitor *m = NULL; struct wlr_output_state state = {0}; wl_list_for_each(m, &mons, link) { - if (regex_match(arg->v, m->wlr_output->name)) { + if (match_monitor_spec(arg->v, m)) { wlr_output_state_set_enabled(&state, true); wlr_output_commit_state(m->wlr_output, &state); m->asleep = 0; @@ -1731,7 +1731,7 @@ int32_t toggle_monitor(const Arg *arg) { Monitor *m = NULL; struct wlr_output_state state = {0}; wl_list_for_each(m, &mons, link) { - if (regex_match(arg->v, m->wlr_output->name)) { + if (match_monitor_spec(arg->v, m)) { wlr_output_state_set_enabled(&state, !m->wlr_output->enabled); wlr_output_commit_state(m->wlr_output, &state); m->asleep = !m->wlr_output->enabled; diff --git a/src/fetch/monitor.h b/src/fetch/monitor.h index fd18498e..d2b4fe62 100644 --- a/src/fetch/monitor.h +++ b/src/fetch/monitor.h @@ -104,4 +104,73 @@ Monitor *get_monitor_nearest_to(int32_t lx, int32_t ly) { return output_from_wlr_output( wlr_output_layout_output_at(output_layout, closest_x, closest_y)); +} + +bool match_monitor_spec(char *spec, Monitor *m) { + if (!spec || !m) + return false; + + // if the spec does not contain a colon, treat it as a match on the monitor + // name + if (strchr(spec, ':') == NULL) { + return regex_match(spec, m->wlr_output->name); + } + + char *spec_copy = strdup(spec); + if (!spec_copy) + return false; + + char *name_rule = NULL; + char *make_rule = NULL; + char *model_rule = NULL; + char *serial_rule = NULL; + + char *token = strtok(spec_copy, "&&"); + while (token) { + char *colon = strchr(token, ':'); + if (colon) { + *colon = '\0'; + char *key = token; + char *value = colon + 1; + + if (strcmp(key, "name") == 0) + name_rule = strdup(value); + else if (strcmp(key, "make") == 0) + make_rule = strdup(value); + else if (strcmp(key, "model") == 0) + model_rule = strdup(value); + else if (strcmp(key, "serial") == 0) + serial_rule = strdup(value); + } + token = strtok(NULL, "&&"); + } + + bool match = true; + + if (name_rule) { + if (!regex_match(name_rule, m->wlr_output->name)) + match = false; + } + if (make_rule) { + if (!m->wlr_output->make || strcmp(make_rule, m->wlr_output->make) != 0) + match = false; + } + if (model_rule) { + if (!m->wlr_output->model || + strcmp(model_rule, m->wlr_output->model) != 0) + match = false; + } + if (serial_rule) { + if (!m->wlr_output->serial || + strcmp(serial_rule, m->wlr_output->serial) != 0) + match = false; + } + + free(spec_copy); + free(name_rule); + free(make_rule); + free(model_rule); + free(serial_rule); + + return match; } \ No newline at end of file diff --git a/src/mango.c b/src/mango.c index 3344ca7f..d4cc1fba 100644 --- a/src/mango.c +++ b/src/mango.c @@ -799,6 +799,7 @@ static bool client_is_in_same_stack(Client *sc, Client *tc, Client *fc); static void monitor_stop_skip_frame_timer(Monitor *m); static int monitor_skip_frame_timeout_callback(void *data); static Monitor *get_monitor_nearest_to(int32_t lx, int32_t ly); +static bool match_monitor_spec(char *spec, Monitor *m); #include "data/static_keymap.h" #include "dispatch/bind_declare.h" From 1dac96b426654056d27c3decf999fdfb687f8282 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 25 Feb 2026 21:53:35 +0800 Subject: [PATCH 044/328] bump version to 0.12.4 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 3f2e5e50..32b363be 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('mango', ['c', 'cpp'], - version : '0.12.3', + version : '0.12.4', ) subdir('protocols') From cbc344ab88cc0eef34e8657fbbd7004459a8b3ce Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 26 Feb 2026 08:29:27 +0800 Subject: [PATCH 045/328] fix: avoid opacity exceeds the threshold due to overshot animation curve --- src/mango.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mango.c b/src/mango.c index d4cc1fba..4be08c58 100644 --- a/src/mango.c +++ b/src/mango.c @@ -4517,6 +4517,7 @@ void scene_buffer_apply_opacity(struct wlr_scene_buffer *buffer, int32_t sx, } void client_set_opacity(Client *c, double opacity) { + opacity = CLAMP_FLOAT(opacity, 0.0f, 1.0f); wlr_scene_node_for_each_buffer(&c->scene_surface->node, scene_buffer_apply_opacity, &opacity); } From 4787402b12d0092f5db5a6aa8d8a2108f0b5b62d Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 26 Feb 2026 08:54:15 +0800 Subject: [PATCH 046/328] opt: optimize monitorrule check --- src/config/parse_config.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 95e54c05..b4fb37e9 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -1844,6 +1844,13 @@ bool parse_option(Config *config, char *key, char *value) { token = strtok(NULL, ","); } + if (!rule->name && !rule->make && !rule->model && !rule->serial) { + fprintf(stderr, "\033[1m\033[31m[ERROR]:\033[33m Monitor rule " + "must have at least one of the following " + "options: name, make, model, serial\n"); + return false; + } + config->monitor_rules_count++; return !parse_error; } else if (strcmp(key, "tagrule") == 0) { From b33839198412d19621c206e5b8a6ad8584224489 Mon Sep 17 00:00:00 2001 From: Noor Latif Date: Thu, 26 Feb 2026 09:25:25 +0000 Subject: [PATCH 047/328] fix(nix): update deprecated xorg package names to top-level MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit xcbutilwm → libxcb-wm This eliminates the deprecation warnings: - "The xorg package set has been deprecated, 'xorg.xcbutilwm' has been renamed to 'libxcb-wm'" Changes: - nix/default.nix:14: xcbutilwm, → libxcb-wm, - nix/default.nix:60: xcbutilwm → libxcb-wm --- nix/default.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nix/default.nix b/nix/default.nix index 6085565e..87d4bfd0 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -11,7 +11,7 @@ wayland, wayland-protocols, wayland-scanner, - xcbutilwm, + libxcb-wm, xwayland, meson, ninja, @@ -57,7 +57,7 @@ stdenv.mkDerivation { ] ++ lib.optionals enableXWayland [ libX11 - xcbutilwm + libxcb-wm xwayland ]; From 835269f86bf8d1f1e05b386a6a548e7c7cae1547 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 26 Feb 2026 23:22:51 +0800 Subject: [PATCH 048/328] opt: make spawn and spawn_shell log to debug log --- src/dispatch/bind_define.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 03d34cbb..5cf41d6c 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -848,7 +848,7 @@ int32_t spawn_shell(const Arg *arg) { execlp("bash", "bash", "-c", arg->v, (char *)NULL); // if execlp fails, we should not reach here - wlr_log(WLR_ERROR, + wlr_log(WLR_DEBUG, "mango: failed to execute command '%s' with shell: %s\n", arg->v, strerror(errno)); _exit(EXIT_FAILURE); @@ -889,7 +889,7 @@ int32_t spawn(const Arg *arg) { execvp(argv[0], argv); // 4. execvp 失败时:打印错误并直接退出(避免 coredump) - wlr_log(WLR_ERROR, "mango: execvp '%s' failed: %s\n", argv[0], + wlr_log(WLR_DEBUG, "mango: execvp '%s' failed: %s\n", argv[0], strerror(errno)); _exit(EXIT_FAILURE); // 使用 _exit 避免缓冲区刷新等操作 } From 43965a1155b33c2174a8a829e6426d0088ef2a7a Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 1 Mar 2026 11:21:24 +0800 Subject: [PATCH 049/328] update readme --- README.md | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 45da36cd..b3117018 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Mango Wayland Compositor
- MangoWC Logo + MangoWM Logo
This project's development is based on [dwl](https://codeberg.org/dwl/dwl/). @@ -36,7 +36,7 @@ https://github.com/user-attachments/assets/bb83004a-0563-4b48-ad89-6461a9b78b1f **Mango won't cater to every user preference**: For niche feature requests, I'll take a wait-and-see approach. I'll only consider adding them if they get a significant number of upvotes. # Our discord -[mangowc](https://discord.gg/CPjbDxesh5) +[mangowm](https://discord.gg/CPjbDxesh5) # Supported layouts - tile @@ -52,7 +52,7 @@ https://github.com/user-attachments/assets/bb83004a-0563-4b48-ad89-6461a9b78b1f # Installation -[![Packaging status](https://repology.org/badge/vertical-allrepos/mangowc.svg)](https://repology.org/project/mangowc/versions) +[![Packaging status](https://repology.org/badge/vertical-allrepos/mangowm.svg)](https://repology.org/project/mangowm/versions) ## Dependencies @@ -71,9 +71,9 @@ https://github.com/user-attachments/assets/bb83004a-0563-4b48-ad89-6461a9b78b1f - libxcb ## Arch Linux -The package is in the Arch User Repository and is available for manual download [here](https://aur.archlinux.org/packages/mangowc-git) or through a AUR helper like yay: +The package is in the Arch User Repository and is available for manual download [here](https://aur.archlinux.org/packages/mangowm-git) or through a AUR helper like yay: ```bash -yay -S mangowc-git +yay -S mangowm-git ``` @@ -87,12 +87,12 @@ eselect repository enable guru emerge --sync guru ``` -Then, add `gui-libs/scenefx` and `gui-wm/mangowc` to the `package.accept_keywords`. +Then, add `gui-libs/scenefx` and `gui-wm/mangowm` to the `package.accept_keywords`. Finally, install the package: ```bash -emerge --ask --verbose gui-wm/mangowc +emerge --ask --verbose gui-wm/mangowm ``` ## Fedora Linux @@ -102,32 +102,32 @@ First, add the [Terra Repository](https://terra.fyralabs.com/). Then, install the package: ```bash -dnf install mangowc +dnf install mangowm ``` ## Guix System The package definition is described in the source repository. -First, add `mangowc` channel to `channels.scm` file: +First, add `mangowm` channel to `channels.scm` file: ```scheme ;; In $HOME/.config/guix/channels.scm (cons (channel - (name 'mangowc) - (url "https://github.com/DreamMaoMao/mangowc.git") + (name 'mangowm) + (url "https://github.com/mangowm/mango.git") (branch "main")) ... ;; Your other channels %default-channels) ``` Then, run `guix pull` and after update you can either run -`guix install mangowc` or add it to your configuration via: +`guix install mangowm` or add it to your configuration via: ```scheme -(use-modules (mangowc)) ;; Add mangowc module +(use-modules (mangowm)) ;; Add mangowm module -;; Add mangowc to packages list +;; Add mangowm to packages list (packages (cons* - mangowc-git + mangowm-git ... ;; Other packages you specified %base-packages)) ``` @@ -147,8 +147,8 @@ cd scenefx meson build -Dprefix=/usr sudo ninja -C build install -git clone https://github.com/DreamMaoMao/mangowc.git -cd mangowc +git clone https://github.com/mangowm/mango.git +cd mangowm meson build -Dprefix=/usr sudo ninja -C build install ``` @@ -206,9 +206,9 @@ git clone https://github.com/DreamMaoMao/mango-config.git ~/.config/mango ## Config Documentation -Refer to the repo wiki [wiki](https://github.com/DreamMaoMao/mango/wiki/) +Refer to the repo wiki [wiki](https://github.com/mangowm/mango/wiki/) -or the website docs [docs](https://mangowc.vercel.app/docs) +or the website docs [docs](https://mangowm.vercel.app/docs) # NixOS + Home-manager @@ -228,7 +228,7 @@ Here's an example of using the modules in a flake: }; flake-parts.url = "github:hercules-ci/flake-parts"; mango = { - url = "github:DreamMaoMao/mango"; + url = "github:mangowm/mango"; inputs.nixpkgs.follows = "nixpkgs"; }; }; @@ -290,9 +290,9 @@ Here's an example of using the modules in a flake: To package mango for other distributions, you can check the reference setup for: -- [nix](https://github.com/DreamMaoMao/mangowc/blob/main/nix/default.nix) -- [arch](https://aur.archlinux.org/cgit/aur.git/tree/PKGBUILD?h=mangowc-git). -- [gentoo](https://data.gpo.zugaina.org/guru/gui-wm/mangowc) +- [nix](https://github.com/mangowm/mango/blob/main/nix/default.nix) +- [arch](https://aur.archlinux.org/cgit/aur.git/tree/PKGBUILD?h=mangowm-git). +- [gentoo](https://data.gpo.zugaina.org/guru/gui-wm/mangowm) You might need to package `scenefx` for your distribution, check availability [here](https://github.com/wlrfx/scenefx.git). From e935af07c167933e312fcb8c2cfb970991bb4629 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 1 Mar 2026 11:35:51 +0800 Subject: [PATCH 050/328] project: rename guix file --- mangowc.scm => mangowm.scm | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename mangowc.scm => mangowm.scm (100%) diff --git a/mangowc.scm b/mangowm.scm similarity index 100% rename from mangowc.scm rename to mangowm.scm From 811610e481714df988b6602d7b20fedb4c68ac4c Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 1 Mar 2026 11:38:17 +0800 Subject: [PATCH 051/328] rename mangowc to mangowm --- mangowm.scm | 12 ++++++------ mmsg/mmsg.c | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/mangowm.scm b/mangowm.scm index 9c55d43e..33d95045 100644 --- a/mangowm.scm +++ b/mangowm.scm @@ -1,4 +1,4 @@ -(define-module (mangowc) +(define-module (mangowm) #:use-module (guix download) #:use-module (guix git-download) #:use-module (guix gexp) @@ -18,11 +18,11 @@ #:use-module (guix licenses)) -(define-public mangowc-git +(define-public mangowm-git (package - (name "mangowc") + (name "mangowm") (version "git") - (source (local-file "." "mangowc-checkout" + (source (local-file "." "mangowm-checkout" #:recursive? #t #:select? (or (git-predicate (current-source-directory)) (const #t)))) @@ -55,10 +55,10 @@ wlroots scenefx)) (native-inputs (list pkg-config wayland-protocols)) - (home-page "https://github.com/DreamMaoMao/mangowc") + (home-page "https://github.com/DreamMaoMao/mangowm") (synopsis "Wayland compositor based on wlroots and scenefx") (description "A Wayland compositor based on wlroots and scenefx, inspired by dwl but aiming to be more feature-rich.") (license gpl3))) -mangowc-git +mangowm-git diff --git a/mmsg/mmsg.c b/mmsg/mmsg.c index fb1d04fd..4e0e1d8c 100644 --- a/mmsg/mmsg.c +++ b/mmsg/mmsg.c @@ -500,7 +500,7 @@ static const struct wl_registry_listener registry_listener = { static void usage(void) { fprintf(stderr, - "mmsg - MangoWC IPC\n" + "mmsg - MangoWM IPC\n" "\n" "SYNOPSIS:\n" "\tmmsg [-OTLq]\n" @@ -517,7 +517,7 @@ static void usage(void) { "\t-O Get all output (monitor) information\n" "\t-T Get number of tags\n" "\t-L Get all available layouts\n" - "\t-q Quit MangoWC\n" + "\t-q Quit mango\n" "\t-o Select output (monitor)\n" "\n" "GET OPTIONS (used with -g or -w):\n" From 755ffe06af4d8e93f8863c404024bc96ad0c636a Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 1 Mar 2026 11:42:44 +0800 Subject: [PATCH 052/328] update website in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b3117018..d5171acc 100644 --- a/README.md +++ b/README.md @@ -208,7 +208,7 @@ git clone https://github.com/DreamMaoMao/mango-config.git ~/.config/mango Refer to the repo wiki [wiki](https://github.com/mangowm/mango/wiki/) -or the website docs [docs](https://mangowm.vercel.app/docs) +or the website docs [docs](https://mangowm.github.io/mango-web/) # NixOS + Home-manager From 243848f43e96e0e6c79210e014219b25c6d35e86 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 1 Mar 2026 12:02:57 +0800 Subject: [PATCH 053/328] bump version to 0.12.5 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 32b363be..85fe15b6 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('mango', ['c', 'cpp'], - version : '0.12.4', + version : '0.12.5', ) subdir('protocols') From 008cb9726e36c1062f059a0505e4d6671ea425db Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 1 Mar 2026 18:04:21 +0800 Subject: [PATCH 054/328] Revert "Add nix option to enable/disable adding to session manager" --- nix/nixos-modules.nix | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/nix/nixos-modules.nix b/nix/nixos-modules.nix index 7a9f930a..33811022 100644 --- a/nix/nixos-modules.nix +++ b/nix/nixos-modules.nix @@ -9,10 +9,6 @@ in { options = { programs.mango = { enable = lib.mkEnableOption "mango, a wayland compositor based on dwl"; - addLoginEntry = lib.mkEnableOption { - default = true; - description = "Whether to add a login entry to the display manager for mango"; - }; package = lib.mkOption { type = lib.types.package; default = self.packages.${pkgs.stdenv.hostPlatform.system}.mango; @@ -59,7 +55,7 @@ in { programs.xwayland.enable = lib.mkDefault true; services = { - displayManager.sessionPackages = lib.mkIf cfg.addLoginEntry [ cfg.package ]; + displayManager.sessionPackages = [cfg.package]; graphical-desktop.enable = lib.mkDefault true; }; From 9922ed26c7fb1a3456077535c01acec0390bc962 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 1 Mar 2026 18:13:40 +0800 Subject: [PATCH 055/328] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d5171acc..b927b920 100644 --- a/README.md +++ b/README.md @@ -208,7 +208,7 @@ git clone https://github.com/DreamMaoMao/mango-config.git ~/.config/mango Refer to the repo wiki [wiki](https://github.com/mangowm/mango/wiki/) -or the website docs [docs](https://mangowm.github.io/mango-web/) +or the website docs [docs](https://mangowm.github.io/) # NixOS + Home-manager From d2b91594944b5f785e4119ec1ebe92296e135407 Mon Sep 17 00:00:00 2001 From: Jiatao Liang Date: Sun, 1 Mar 2026 10:21:59 -0500 Subject: [PATCH 056/328] Sets default for addLoginEntry correctly --- nix/nixos-modules.nix | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/nix/nixos-modules.nix b/nix/nixos-modules.nix index 33811022..7295fffa 100644 --- a/nix/nixos-modules.nix +++ b/nix/nixos-modules.nix @@ -9,6 +9,11 @@ in { options = { programs.mango = { enable = lib.mkEnableOption "mango, a wayland compositor based on dwl"; + addLoginEntry = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Whether to add a login entry to the display manager for mango"; + }; package = lib.mkOption { type = lib.types.package; default = self.packages.${pkgs.stdenv.hostPlatform.system}.mango; @@ -55,7 +60,7 @@ in { programs.xwayland.enable = lib.mkDefault true; services = { - displayManager.sessionPackages = [cfg.package]; + displayManager.sessionPackages = lib.mkIf cfg.addLoginEntry [ cfg.package ]; graphical-desktop.enable = lib.mkDefault true; }; From 6cc7d162817941799c5db4af2295f54a5353f778 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 2 Mar 2026 00:42:07 +0800 Subject: [PATCH 057/328] opt: only set on_demand layer focus when it request in init_commit --- src/mango.c | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/mango.c b/src/mango.c index 0864a982..81792b20 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1693,6 +1693,7 @@ void focuslayer(LayerSurface *l) { void reset_exclusive_layer(Monitor *m) { LayerSurface *l = NULL; int32_t i; + bool neet_change_focus_to_client = false; uint32_t layers_above_shell[] = { ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, ZWLR_LAYER_SHELL_V1_LAYER_TOP, @@ -1706,13 +1707,19 @@ void reset_exclusive_layer(Monitor *m) { wl_list_for_each_reverse(l, &m->layers[layers_above_shell[i]], link) { if (l == exclusive_focus && l->layer_surface->current.keyboard_interactive != - ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE) + ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE) { + exclusive_focus = NULL; + + neet_change_focus_to_client = true; + } + if (l->layer_surface->current.keyboard_interactive == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE && l->layer_surface->surface == - seat->keyboard_state.focused_surface) - focusclient(focustop(selmon), 1); + seat->keyboard_state.focused_surface) { + neet_change_focus_to_client = true; + } if (locked || l->layer_surface->current.keyboard_interactive != @@ -1725,6 +1732,10 @@ void reset_exclusive_layer(Monitor *m) { return; } } + + if (neet_change_focus_to_client) { + focusclient(focustop(selmon), 1); + } } void arrangelayers(Monitor *m) { @@ -2384,13 +2395,6 @@ void maplayersurfacenotify(struct wl_listener *listener, void *data) { } // 刷新布局,让窗口能感应到exclude_zone变化以及设置独占表面 arrangelayers(l->mon); - - // 按需交互layer需要像正常窗口一样抢占非独占layer的焦点 - if (!exclusive_focus && - l->layer_surface->current.keyboard_interactive == - ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND) { - focuslayer(l); - } } void commitlayersurfacenotify(struct wl_listener *listener, void *data) { @@ -2411,6 +2415,12 @@ void commitlayersurfacenotify(struct wl_listener *listener, void *data) { arrangelayers(l->mon); l->layer_surface->current = old_state; + // 按需交互layer只在map之前设置焦点 + if (!exclusive_focus && + l->layer_surface->current.keyboard_interactive == + ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND) { + focuslayer(l); + } return; } @@ -2455,11 +2465,6 @@ void commitlayersurfacenotify(struct wl_listener *listener, void *data) { layer_flush_blur_background(l); - if (layer_surface == exclusive_focus && - layer_surface->current.keyboard_interactive != - ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE) - exclusive_focus = NULL; - if (layer_surface->current.committed == 0 && l->mapped == layer_surface->surface->mapped) return; @@ -3335,7 +3340,6 @@ void destroylocksurface(struct wl_listener *listener, void *data) { if (lock_surface->surface != seat->keyboard_state.focused_surface) { if (exclusive_focus && !locked) { - exclusive_focus = NULL; reset_exclusive_layer(m); } return; @@ -3345,7 +3349,6 @@ void destroylocksurface(struct wl_listener *listener, void *data) { surface = wl_container_of(cur_lock->surfaces.next, surface, link); client_notify_enter(surface->surface, wlr_seat_get_keyboard(seat)); } else if (!locked) { - exclusive_focus = NULL; reset_exclusive_layer(selmon); focusclient(focustop(selmon), 1); } else { From c403a47894a1fa856ea1a913b30d350185a0b34b Mon Sep 17 00:00:00 2001 From: Han Boetes Date: Sun, 1 Mar 2026 21:58:03 +0100 Subject: [PATCH 058/328] Fix use-after-free crash in cursor surface handling ### Problem `setcursor()` stores the client-provided `wlr_surface` pointer in `last_cursor.surface`, but never registers a destroy listener on it. When the client exits (e.g. closing a launcher like fuzzel), the surface is destroyed, but `last_cursor.surface` still holds the stale pointer. If the cursor hide timeout fires while the cursor surface is alive, and the client then exits, the next mouse movement calls `handlecursoractivity()`, which passes the dangling pointer to `wlr_cursor_set_surface()`. This causes a SIGSEGV in `wl_list_insert()` inside libwayland-server, as the `wl_list` embedded in the destroyed surface struct has been freed. A secondary issue exists in `setcursorshape()`: when a client switches from a custom cursor surface to a shape cursor, `last_cursor.surface` is set to NULL but the destroy listener (if registered) is not removed, leaving a dangling listener on the destroyed surface. The crash only manifests when `cursor_hidden` is true at the moment of the mouse movement, which is why it is intermittent and difficult to reproduce. ### Root cause Confirmed via `coredumpctl debug` and `bt full`: ``` #0 wl_list_insert (libwayland-server.so) #1 wlr_cursor_set_surface (libwlroots) #2 handlecursoractivity (mango.c) #3 motionnotify (mango.c) #4 motionrelative (mango.c) #5 wl_signal_emit_mutable #6 handle_libinput_readable ``` ### Fix - Add a `wl_listener` (`last_cursor_surface_destroy_listener`) that clears `last_cursor.surface` and removes itself when the surface is destroyed. - Initialize the listener's link in `setup()` so `wl_list_empty()` checks are reliable from the start. - In `setcursor()`, remove any existing listener before registering a new one on the incoming surface. - In `setcursorshape()`, remove the destroy listener when switching to a shape cursor. - Add a NULL guard in `handlecursoractivity()` as a safety net. --- src/mango.c | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/mango.c b/src/mango.c index 0864a982..cb710d2e 100644 --- a/src/mango.c +++ b/src/mango.c @@ -914,6 +914,15 @@ static struct { int32_t hotspot_y; } last_cursor; +static void last_cursor_surface_destroy(struct wl_listener *listener, void *data) { + last_cursor.surface = NULL; + wl_list_remove(&listener->link); + wl_list_init(&listener->link); +} +static struct wl_listener last_cursor_surface_destroy_listener = { + .notify = last_cursor_surface_destroy +}; + #include "client/client.h" #include "config/preset.h" @@ -2141,6 +2150,11 @@ void setcursorshape(struct wl_listener *listener, void *data) { * actually has pointer focus first. If so, we can tell the cursor to * use the provided cursor shape. */ if (event->seat_client == seat->pointer_state.focused_client) { + /* Remove surface destroy listener if active */ + if (!wl_list_empty(&last_cursor_surface_destroy_listener.link)) + wl_list_remove(&last_cursor_surface_destroy_listener.link); + wl_list_init(&last_cursor_surface_destroy_listener.link); + last_cursor.shape = event->shape; last_cursor.surface = NULL; if (!cursor_hidden) @@ -4918,10 +4932,21 @@ void setcursor(struct wl_listener *listener, void *data) { * hardware cursor on the output that it's currently on and continue to * do so as the cursor moves between outputs. */ if (event->seat_client == seat->pointer_state.focused_client) { + /* Clear previous surface destroy listener if any */ + if (!wl_list_empty(&last_cursor_surface_destroy_listener.link)) + wl_list_remove(&last_cursor_surface_destroy_listener.link); + wl_list_init(&last_cursor_surface_destroy_listener.link); + last_cursor.shape = 0; last_cursor.surface = event->surface; last_cursor.hotspot_x = event->hotspot_x; last_cursor.hotspot_y = event->hotspot_y; + + /* Track surface destruction to avoid dangling pointer */ + if (event->surface) + wl_signal_add(&event->surface->events.destroy, + &last_cursor_surface_destroy_listener); + if (!cursor_hidden) wlr_cursor_set_surface(cursor, event->surface, event->hotspot_x, event->hotspot_y); @@ -5384,6 +5409,8 @@ void handle_print_status(struct wl_listener *listener, void *data) { void setup(void) { + wl_list_init(&last_cursor_surface_destroy_listener.link); + setenv("XCURSOR_SIZE", "24", 1); setenv("XDG_CURRENT_DESKTOP", "mango", 1); @@ -5818,7 +5845,7 @@ void handlecursoractivity(void) { if (last_cursor.shape) wlr_cursor_set_xcursor(cursor, cursor_mgr, wlr_cursor_shape_v1_name(last_cursor.shape)); - else + else if (last_cursor.surface) wlr_cursor_set_surface(cursor, last_cursor.surface, last_cursor.hotspot_x, last_cursor.hotspot_y); } From 263b1845bb1d3ecfc3b40fb463a65e36134056dc Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 2 Mar 2026 08:15:52 +0800 Subject: [PATCH 059/328] opt: optimize layer focus change logic --- src/mango.c | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/mango.c b/src/mango.c index 81792b20..641e69ab 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1690,7 +1690,7 @@ void focuslayer(LayerSurface *l) { client_notify_enter(l->layer_surface->surface, wlr_seat_get_keyboard(seat)); } -void reset_exclusive_layer(Monitor *m) { +void reset_exclusive_layers_focus(Monitor *m) { LayerSurface *l = NULL; int32_t i; bool neet_change_focus_to_client = false; @@ -1704,7 +1704,7 @@ void reset_exclusive_layer(Monitor *m) { return; for (i = 0; i < (int32_t)LENGTH(layers_above_shell); i++) { - wl_list_for_each_reverse(l, &m->layers[layers_above_shell[i]], link) { + wl_list_for_each(l, &m->layers[layers_above_shell[i]], link) { if (l == exclusive_focus && l->layer_surface->current.keyboard_interactive != ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE) { @@ -1714,6 +1714,12 @@ void reset_exclusive_layer(Monitor *m) { neet_change_focus_to_client = true; } + if (l->layer_surface->surface == + seat->keyboard_state.focused_surface && + l->being_unmapped) { + neet_change_focus_to_client = true; + } + if (l->layer_surface->current.keyboard_interactive == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE && l->layer_surface->surface == @@ -1724,11 +1730,14 @@ void reset_exclusive_layer(Monitor *m) { if (locked || l->layer_surface->current.keyboard_interactive != ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE || - !l->mapped || l == exclusive_focus) + l->being_unmapped) continue; /* Deactivate the focused client. */ exclusive_focus = l; - focuslayer(l); + neet_change_focus_to_client = false; + if (l->layer_surface->surface != + seat->keyboard_state.focused_surface) + focuslayer(l); return; } } @@ -1757,9 +1766,6 @@ void arrangelayers(Monitor *m) { /* Arrange non-exlusive surfaces from top->bottom */ for (i = 3; i >= 0; i--) arrangelayer(m, &m->layers[i], &usable_area, 0); - - /* Find topmost keyboard interactive layer, if such a layer exists */ - reset_exclusive_layer(m); } void // 鼠标滚轮事件 @@ -2395,6 +2401,7 @@ void maplayersurfacenotify(struct wl_listener *listener, void *data) { } // 刷新布局,让窗口能感应到exclude_zone变化以及设置独占表面 arrangelayers(l->mon); + reset_exclusive_layers_focus(l->mon); } void commitlayersurfacenotify(struct wl_listener *listener, void *data) { @@ -2414,7 +2421,6 @@ void commitlayersurfacenotify(struct wl_listener *listener, void *data) { l->layer_surface->current = l->layer_surface->pending; arrangelayers(l->mon); l->layer_surface->current = old_state; - // 按需交互layer只在map之前设置焦点 if (!exclusive_focus && l->layer_surface->current.keyboard_interactive == @@ -2482,6 +2488,7 @@ void commitlayersurfacenotify(struct wl_listener *listener, void *data) { } arrangelayers(l->mon); + reset_exclusive_layers_focus(l->mon); } void commitnotify(struct wl_listener *listener, void *data) { @@ -3340,7 +3347,7 @@ void destroylocksurface(struct wl_listener *listener, void *data) { if (lock_surface->surface != seat->keyboard_state.focused_surface) { if (exclusive_focus && !locked) { - reset_exclusive_layer(m); + reset_exclusive_layers_focus(m); } return; } @@ -3349,8 +3356,7 @@ void destroylocksurface(struct wl_listener *listener, void *data) { surface = wl_container_of(cur_lock->surfaces.next, surface, link); client_notify_enter(surface->surface, wlr_seat_get_keyboard(seat)); } else if (!locked) { - reset_exclusive_layer(selmon); - focusclient(focustop(selmon), 1); + reset_exclusive_layers_focus(selmon); } else { wlr_seat_keyboard_clear_focus(seat); } @@ -5846,17 +5852,20 @@ void unmaplayersurfacenotify(struct wl_listener *listener, void *data) { init_fadeout_layers(l); wlr_scene_node_set_enabled(&l->scene->node, false); + if (l == exclusive_focus) exclusive_focus = NULL; + if (l->layer_surface->output && (l->mon = l->layer_surface->output->data)) arrangelayers(l->mon); - if (l->layer_surface->surface == seat->keyboard_state.focused_surface) - focusclient(focustop(selmon), 1); + + reset_exclusive_layers_focus(l->mon); + motionnotify(0, NULL, 0, 0, 0, 0); - l->being_unmapped = false; layer_flush_blur_background(l); wlr_scene_node_destroy(&l->shadow->node); l->shadow = NULL; + l->being_unmapped = false; } void unmapnotify(struct wl_listener *listener, void *data) { From 20dbffdfafbdc7c4aa5fa464b3422709076667a6 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 2 Mar 2026 08:35:32 +0800 Subject: [PATCH 060/328] opt: avoid unnecessary action when layer surface commit --- src/mango.c | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/mango.c b/src/mango.c index 641e69ab..2faf35da 100644 --- a/src/mango.c +++ b/src/mango.c @@ -2476,19 +2476,26 @@ void commitlayersurfacenotify(struct wl_listener *listener, void *data) { return; l->mapped = layer_surface->surface->mapped; - if (scene_layer != l->scene->node.parent) { - wlr_scene_node_reparent(&l->scene->node, scene_layer); - wl_list_remove(&l->link); - wl_list_insert(&l->mon->layers[layer_surface->current.layer], &l->link); - wlr_scene_node_reparent( - &l->popups->node, - (layer_surface->current.layer < ZWLR_LAYER_SHELL_V1_LAYER_TOP - ? layers[LyrTop] - : scene_layer)); + if (layer_surface->current.committed & WLR_LAYER_SURFACE_V1_STATE_LAYER) { + if (scene_layer != l->scene->node.parent) { + wlr_scene_node_reparent(&l->scene->node, scene_layer); + wl_list_remove(&l->link); + wl_list_insert(&l->mon->layers[layer_surface->current.layer], + &l->link); + wlr_scene_node_reparent( + &l->popups->node, + (layer_surface->current.layer < ZWLR_LAYER_SHELL_V1_LAYER_TOP + ? layers[LyrTop] + : scene_layer)); + } + + arrangelayers(l->mon); } - arrangelayers(l->mon); - reset_exclusive_layers_focus(l->mon); + if (layer_surface->current.committed & + WLR_LAYER_SURFACE_V1_STATE_KEYBOARD_INTERACTIVITY) { + reset_exclusive_layers_focus(l->mon); + } } void commitnotify(struct wl_listener *listener, void *data) { From ad754167b7d810dd63bbfd460e80bbd30e531778 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 2 Mar 2026 09:40:50 +0800 Subject: [PATCH 061/328] fix: last_cursor surface destroy detect error --- src/mango.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/mango.c b/src/mango.c index af82733d..7c6106f9 100644 --- a/src/mango.c +++ b/src/mango.c @@ -800,6 +800,8 @@ static void monitor_stop_skip_frame_timer(Monitor *m); static int monitor_skip_frame_timeout_callback(void *data); static Monitor *get_monitor_nearest_to(int32_t lx, int32_t ly); static bool match_monitor_spec(char *spec, Monitor *m); +static void last_cursor_surface_destroy(struct wl_listener *listener, + void *data); #include "data/static_keymap.h" #include "dispatch/bind_declare.h" @@ -914,15 +916,6 @@ static struct { int32_t hotspot_y; } last_cursor; -static void last_cursor_surface_destroy(struct wl_listener *listener, void *data) { - last_cursor.surface = NULL; - wl_list_remove(&listener->link); - wl_list_init(&listener->link); -} -static struct wl_listener last_cursor_surface_destroy_listener = { - .notify = last_cursor_surface_destroy -}; - #include "client/client.h" #include "config/preset.h" @@ -973,6 +966,8 @@ static struct wl_listener new_session_lock = {.notify = locksession}; static struct wl_listener drm_lease_request = {.notify = requestdrmlease}; static struct wl_listener keyboard_shortcuts_inhibit_new_inhibitor = { .notify = handle_keyboard_shortcuts_inhibit_new_inhibitor}; +static struct wl_listener last_cursor_surface_destroy_listener = { + .notify = last_cursor_surface_destroy}; #ifdef XWAYLAND static void fix_xwayland_unmanaged_coordinate(Client *c); @@ -2159,6 +2154,11 @@ void checkidleinhibitor(struct wlr_surface *exclude) { wlr_idle_notifier_v1_set_inhibited(idle_notifier, inhibited); } +void last_cursor_surface_destroy(struct wl_listener *listener, void *data) { + last_cursor.surface = NULL; + wl_list_remove(&listener->link); +} + void setcursorshape(struct wl_listener *listener, void *data) { struct wlr_cursor_shape_manager_v1_request_set_shape_event *event = data; if (cursor_mode != CurNormal && cursor_mode != CurPressed) @@ -2168,9 +2168,9 @@ void setcursorshape(struct wl_listener *listener, void *data) { * use the provided cursor shape. */ if (event->seat_client == seat->pointer_state.focused_client) { /* Remove surface destroy listener if active */ - if (!wl_list_empty(&last_cursor_surface_destroy_listener.link)) + if (last_cursor.surface && + last_cursor_surface_destroy_listener.link.prev != NULL) wl_list_remove(&last_cursor_surface_destroy_listener.link); - wl_list_init(&last_cursor_surface_destroy_listener.link); last_cursor.shape = event->shape; last_cursor.surface = NULL; @@ -4949,9 +4949,9 @@ void setcursor(struct wl_listener *listener, void *data) { * do so as the cursor moves between outputs. */ if (event->seat_client == seat->pointer_state.focused_client) { /* Clear previous surface destroy listener if any */ - if (!wl_list_empty(&last_cursor_surface_destroy_listener.link)) + if (last_cursor.surface && + last_cursor_surface_destroy_listener.link.prev != NULL) wl_list_remove(&last_cursor_surface_destroy_listener.link); - wl_list_init(&last_cursor_surface_destroy_listener.link); last_cursor.shape = 0; last_cursor.surface = event->surface; @@ -5425,8 +5425,6 @@ void handle_print_status(struct wl_listener *listener, void *data) { void setup(void) { - wl_list_init(&last_cursor_surface_destroy_listener.link); - setenv("XCURSOR_SIZE", "24", 1); setenv("XDG_CURRENT_DESKTOP", "mango", 1); @@ -5662,6 +5660,8 @@ void setup(void) { LISTEN_STATIC(&cursor->events.hold_end, hold_end); seat = wlr_seat_create(dpy, "seat0"); + + wl_list_init(&last_cursor_surface_destroy_listener.link); wl_signal_add(&seat->events.request_set_cursor, &request_cursor); wl_signal_add(&seat->events.request_set_selection, &request_set_sel); wl_signal_add(&seat->events.request_set_primary_selection, From 46e867deb9dfd58abb22ec92b19461d3761b1f3d Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 2 Mar 2026 20:53:52 +0800 Subject: [PATCH 062/328] opt: always arrangelayers if layer commit --- src/mango.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mango.c b/src/mango.c index 7c6106f9..3bfdfb77 100644 --- a/src/mango.c +++ b/src/mango.c @@ -2502,10 +2502,10 @@ void commitlayersurfacenotify(struct wl_listener *listener, void *data) { ? layers[LyrTop] : scene_layer)); } - - arrangelayers(l->mon); } + arrangelayers(l->mon); + if (layer_surface->current.committed & WLR_LAYER_SURFACE_V1_STATE_KEYBOARD_INTERACTIVITY) { reset_exclusive_layers_focus(l->mon); From 9aa2d3cd33ce233c3986263e52163112a0c0caec Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 3 Mar 2026 13:03:35 +0800 Subject: [PATCH 063/328] opt: rename hide_source var to hide_cursor_source --- src/config/parse_config.h | 12 ++++++------ src/mango.c | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index b4fb37e9..d2946f60 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -3637,10 +3637,10 @@ void reapply_monitor_rules(void) { } void reapply_cursor_style(void) { - if (hide_source) { - wl_event_source_timer_update(hide_source, 0); - wl_event_source_remove(hide_source); - hide_source = NULL; + if (hide_cursor_source) { + wl_event_source_timer_update(hide_cursor_source, 0); + wl_event_source_remove(hide_cursor_source); + hide_cursor_source = NULL; } wlr_cursor_unset_image(cursor); @@ -3671,12 +3671,12 @@ void reapply_cursor_style(void) { wlr_cursor_set_xcursor(cursor, cursor_mgr, "left_ptr"); - hide_source = wl_event_loop_add_timer(wl_display_get_event_loop(dpy), + hide_cursor_source = wl_event_loop_add_timer(wl_display_get_event_loop(dpy), hidecursor, cursor); if (cursor_hidden) { wlr_cursor_unset_image(cursor); } else { - wl_event_source_timer_update(hide_source, cursor_hide_timeout * 1000); + wl_event_source_timer_update(hide_cursor_source, cursor_hide_timeout * 1000); } } diff --git a/src/mango.c b/src/mango.c index 3bfdfb77..d7245ded 100644 --- a/src/mango.c +++ b/src/mango.c @@ -898,7 +898,7 @@ struct dvec2 *baked_points_focus; struct dvec2 *baked_points_opafadein; struct dvec2 *baked_points_opafadeout; -static struct wl_event_source *hide_source; +static struct wl_event_source *hide_cursor_source; static bool cursor_hidden = false; static bool tag_combo = false; static const char *cli_config_path = NULL; @@ -5631,7 +5631,7 @@ void setup(void) { cursor_shape_mgr = wlr_cursor_shape_manager_v1_create(dpy, 1); wl_signal_add(&cursor_shape_mgr->events.request_set_shape, &request_set_cursor_shape); - hide_source = wl_event_loop_add_timer(wl_display_get_event_loop(dpy), + hide_cursor_source = wl_event_loop_add_timer(wl_display_get_event_loop(dpy), hidecursor, cursor); /* * Configures a seat, which is a single "seat" at which a user sits and @@ -5851,7 +5851,7 @@ void overview_restore(Client *c, const Arg *arg) { } void handlecursoractivity(void) { - wl_event_source_timer_update(hide_source, cursor_hide_timeout * 1000); + wl_event_source_timer_update(hide_cursor_source, cursor_hide_timeout * 1000); if (!cursor_hidden) return; From 1e1d41e626aa12057c03ec79ed11bcc5619f6748 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 3 Mar 2026 13:23:15 +0800 Subject: [PATCH 064/328] feat: add windowrule option indleinhibit_when_focus --- src/config/parse_config.h | 9 ++++++-- src/mango.c | 47 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index d2946f60..8a965360 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -78,6 +78,7 @@ typedef struct { int32_t ignore_maximize; int32_t ignore_minimize; int32_t isnosizehint; + int32_t indleinhibit_when_focus; const char *monitor; int32_t offsetx; int32_t offsety; @@ -2022,6 +2023,7 @@ bool parse_option(Config *config, char *key, char *value) { rule->ignore_maximize = -1; rule->ignore_minimize = -1; rule->isnosizehint = -1; + rule->indleinhibit_when_focus = -1; rule->isterm = -1; rule->allow_csd = -1; rule->force_maximize = -1; @@ -2132,6 +2134,8 @@ bool parse_option(Config *config, char *key, char *value) { rule->ignore_minimize = atoi(val); } else if (strcmp(key, "isnosizehint") == 0) { rule->isnosizehint = atoi(val); + } else if (strcmp(key, "indleinhibit_when_focus") == 0) { + rule->indleinhibit_when_focus = atoi(val); } else if (strcmp(key, "isterm") == 0) { rule->isterm = atoi(val); } else if (strcmp(key, "allow_csd") == 0) { @@ -3672,11 +3676,12 @@ void reapply_cursor_style(void) { wlr_cursor_set_xcursor(cursor, cursor_mgr, "left_ptr"); hide_cursor_source = wl_event_loop_add_timer(wl_display_get_event_loop(dpy), - hidecursor, cursor); + hidecursor, cursor); if (cursor_hidden) { wlr_cursor_unset_image(cursor); } else { - wl_event_source_timer_update(hide_cursor_source, cursor_hide_timeout * 1000); + wl_event_source_timer_update(hide_cursor_source, + cursor_hide_timeout * 1000); } } diff --git a/src/mango.c b/src/mango.c index d7245ded..8a93a792 100644 --- a/src/mango.c +++ b/src/mango.c @@ -347,7 +347,7 @@ struct Client { struct wlr_foreign_toplevel_handle_v1 *foreign_toplevel; int32_t isfloating, isurgent, isfullscreen, isfakefullscreen, need_float_size_reduce, isminimized, isoverlay, isnosizehint, - ignore_maximize, ignore_minimize; + ignore_maximize, ignore_minimize, indleinhibit_when_focus; int32_t ismaximizescreen; int32_t overview_backup_bw; int32_t fullscreen_backup_x, fullscreen_backup_y, fullscreen_backup_w, @@ -802,6 +802,8 @@ static Monitor *get_monitor_nearest_to(int32_t lx, int32_t ly); static bool match_monitor_spec(char *spec, Monitor *m); static void last_cursor_surface_destroy(struct wl_listener *listener, void *data); +static int32_t keep_idle_inhibit(void *data); +static void check_keep_idle_inhibit(Client *c); #include "data/static_keymap.h" #include "dispatch/bind_declare.h" @@ -899,6 +901,7 @@ struct dvec2 *baked_points_opafadein; struct dvec2 *baked_points_opafadeout; static struct wl_event_source *hide_cursor_source; +static struct wl_event_source *keep_idle_inhibit_source; static bool cursor_hidden = false; static bool tag_combo = false; static const char *cli_config_path = NULL; @@ -1335,6 +1338,7 @@ static void apply_rule_properties(Client *c, const ConfigWinRule *r) { APPLY_INT_PROP(c, r, ignore_maximize); APPLY_INT_PROP(c, r, ignore_minimize); APPLY_INT_PROP(c, r, isnosizehint); + APPLY_INT_PROP(c, r, indleinhibit_when_focus); APPLY_INT_PROP(c, r, isunglobal); APPLY_INT_PROP(c, r, noblur); APPLY_INT_PROP(c, r, allow_shortcuts_inhibit); @@ -3482,6 +3486,8 @@ void focusclient(Client *c, int32_t lift) { selmon->sel = c; c->isfocusing = true; + check_keep_idle_inhibit(c); + if (last_focus_client && !last_focus_client->iskilling && last_focus_client != c) { last_focus_client->isfocusing = false; @@ -4029,6 +4035,7 @@ void init_client_properties(Client *c) { c->force_tiled_state = 1; c->force_tearing = 0; c->allow_shortcuts_inhibit = SHORTCUTS_INHIBIT_ENABLE; + c->indleinhibit_when_focus = 0; c->scroller_proportion_single = 0.0f; c->float_geom.width = 0; c->float_geom.height = 0; @@ -5564,6 +5571,9 @@ void setup(void) { idle_inhibit_mgr = wlr_idle_inhibit_v1_create(dpy); wl_signal_add(&idle_inhibit_mgr->events.new_inhibitor, &new_idle_inhibitor); + keep_idle_inhibit_source = wl_event_loop_add_timer( + wl_display_get_event_loop(dpy), keep_idle_inhibit, NULL); + layer_shell = wlr_layer_shell_v1_create(dpy, 4); wl_signal_add(&layer_shell->events.new_surface, &new_layer_surface); @@ -5632,7 +5642,7 @@ void setup(void) { wl_signal_add(&cursor_shape_mgr->events.request_set_shape, &request_set_cursor_shape); hide_cursor_source = wl_event_loop_add_timer(wl_display_get_event_loop(dpy), - hidecursor, cursor); + hidecursor, cursor); /* * Configures a seat, which is a single "seat" at which a user sits and * operates the computer. This conceptually includes up to one keyboard, @@ -5851,7 +5861,8 @@ void overview_restore(Client *c, const Arg *arg) { } void handlecursoractivity(void) { - wl_event_source_timer_update(hide_cursor_source, cursor_hide_timeout * 1000); + wl_event_source_timer_update(hide_cursor_source, + cursor_hide_timeout * 1000); if (!cursor_hidden) return; @@ -5872,6 +5883,36 @@ int32_t hidecursor(void *data) { return 1; } +void check_keep_idle_inhibit(Client *c) { + if (c && c->indleinhibit_when_focus && keep_idle_inhibit_source) { + wl_event_source_timer_update(keep_idle_inhibit_source, 1000); + } +} + +int32_t keep_idle_inhibit(void *data) { + + if (!idle_inhibit_mgr) { + wl_event_source_timer_update(keep_idle_inhibit_source, 0); + return 1; + } + + if (session && !session->active) { + wl_event_source_timer_update(keep_idle_inhibit_source, 0); + return 1; + } + + if (!selmon || !selmon->sel || !selmon->sel->indleinhibit_when_focus) { + wl_event_source_timer_update(keep_idle_inhibit_source, 0); + return 1; + } + + if (seat && idle_notifier) { + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + wl_event_source_timer_update(keep_idle_inhibit_source, 1000); + } + return 1; +} + void unlocksession(struct wl_listener *listener, void *data) { SessionLock *lock = wl_container_of(listener, lock, unlock); destroylock(lock, 1); From 7f99b5ff4870aa44b5237a5eb16edbbf77671cff Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 4 Mar 2026 12:17:44 +0800 Subject: [PATCH 065/328] feat: use monitor spec to match windowrule monitor field --- src/config/parse_config.h | 2 +- src/mango.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 8a965360..ce5db532 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -79,7 +79,7 @@ typedef struct { int32_t ignore_minimize; int32_t isnosizehint; int32_t indleinhibit_when_focus; - const char *monitor; + char *monitor; int32_t offsetx; int32_t offsety; int32_t width; diff --git a/src/mango.c b/src/mango.c index 8a93a792..9a027a33 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1443,7 +1443,7 @@ void applyrules(Client *c) { // set monitor of client wl_list_for_each(m, &mons, link) { - if (regex_match(r->monitor, m->wlr_output->name)) { + if (match_monitor_spec(r->monitor, m)) { mon = m; } } From bf10fabfc2ef43e024165df97056264c09e21fd6 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 5 Mar 2026 08:27:20 +0800 Subject: [PATCH 066/328] fix: popup unconstrain cross monitor --- src/mango.c | 44 ++++++++++++++++++-------------------------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/src/mango.c b/src/mango.c index 9a027a33..9edc25fb 100644 --- a/src/mango.c +++ b/src/mango.c @@ -488,7 +488,6 @@ typedef struct { typedef struct { struct wlr_xdg_popup *wlr_popup; - uint32_t type; struct wl_listener destroy; struct wl_listener commit; struct wl_listener reposition; @@ -2591,6 +2590,9 @@ void destroydecoration(struct wl_listener *listener, void *data) { static void popup_unconstrain(Popup *popup) { struct wlr_xdg_popup *wlr_popup = popup->wlr_popup; + Client *c = NULL; + LayerSurface *l = NULL; + int32_t type = -1; if (!wlr_popup || !wlr_popup->parent) { return; @@ -2601,16 +2603,17 @@ static void popup_unconstrain(Popup *popup) { wlr_log(WLR_ERROR, "Popup parent has no scene node"); return; } + + type = toplevel_from_wlr_surface(wlr_popup->base->surface, &c, &l); + if ((l && !l->mon) || (c && !c->mon)) { + wlr_xdg_popup_destroy(wlr_popup); + return; + } + int parent_lx, parent_ly; wlr_scene_node_coords(parent_node, &parent_lx, &parent_ly); - struct wlr_box *scheduled = &wlr_popup->scheduled.geometry; - int popup_lx = parent_lx + scheduled->x; - int popup_ly = parent_ly + scheduled->y; - - Monitor *mon = get_monitor_nearest_to(popup_lx, popup_ly); - - struct wlr_box usable = popup->type == LayerShell ? mon->m : mon->w; + struct wlr_box usable = type == LayerShell ? l->mon->m : c->mon->w; struct wlr_box constraint_box = { .x = usable.x - parent_lx, @@ -2633,33 +2636,22 @@ static void commitpopup(struct wl_listener *listener, void *data) { Popup *popup = wl_container_of(listener, popup, commit); struct wlr_surface *surface = data; - struct wlr_xdg_popup *wkr_popup = + struct wlr_xdg_popup *wlr_popup = wlr_xdg_popup_try_from_wlr_surface(surface); - Client *c = NULL; - LayerSurface *l = NULL; - int32_t type = -1; - - if (!wkr_popup || !wkr_popup->base->initial_commit) + if (!wlr_popup || !wlr_popup->base->initial_commit) goto commitpopup_listen_free; - type = toplevel_from_wlr_surface(wkr_popup->base->surface, &c, &l); - if (!wkr_popup->parent || !wkr_popup->parent->data || type < 0) { - wlr_xdg_popup_destroy(wkr_popup); + if (!wlr_popup->parent || !wlr_popup->parent->data) { goto commitpopup_listen_free; } - wlr_scene_node_raise_to_top(wkr_popup->parent->data); + wlr_scene_node_raise_to_top(wlr_popup->parent->data); - wkr_popup->base->surface->data = - wlr_scene_xdg_surface_create(wkr_popup->parent->data, wkr_popup->base); - if ((l && !l->mon) || (c && !c->mon)) { - wlr_xdg_popup_destroy(wkr_popup); - goto commitpopup_listen_free; - } + wlr_popup->base->surface->data = + wlr_scene_xdg_surface_create(wlr_popup->parent->data, wlr_popup->base); - popup->type = type; - popup->wlr_popup = wkr_popup; + popup->wlr_popup = wlr_popup; popup_unconstrain(popup); From 0f68187cd0a48d18f8f25dcc8f12bfa666524156 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 5 Mar 2026 11:47:17 +0800 Subject: [PATCH 067/328] opt: support restore size per when master change --- src/layout/arrange.h | 78 ++++++++++++++++++++++++++++++++------------ src/mango.c | 16 +++++++++ 2 files changed, 74 insertions(+), 20 deletions(-) diff --git a/src/layout/arrange.h b/src/layout/arrange.h index 1ef89c3a..53c64893 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -1,11 +1,31 @@ +void save_old_size_per(Monitor *m) { + Client *c = NULL; + + wl_list_for_each(c, &clients, link) { + if (VISIBLEON(c, m) && ISTILED(c)) { + c->old_master_inner_per = c->master_inner_per; + c->old_stack_inner_per = c->stack_inner_per; + } + } +} + void restore_size_per(Monitor *m, Client *c) { Client *fc = NULL; - double total_master_inner_per = 0; - double total_stack_inner_per = 0; if (!m || !c) return; + wl_list_for_each(fc, &clients, link) { + if (VISIBLEON(fc, m) && ISTILED(fc)) { + fc->old_ismaster = fc->ismaster; + } + } + + c->old_master_inner_per = c->master_inner_per; + c->old_stack_inner_per = c->stack_inner_per; + + pre_caculate_before_arrange(m, false, false, true); + const Layout *current_layout = m->pertag->ltidxs[m->pertag->curtag]; if (current_layout->id == SCROLLER || @@ -15,7 +35,7 @@ void restore_size_per(Monitor *m, Client *c) { return; } - if (current_layout->id == CENTER_TILE || c->ismaster) { + if (current_layout->id == CENTER_TILE) { wl_list_for_each(fc, &clients, link) { if (VISIBLEON(fc, m) && ISTILED(fc) && !c->ismaster) { set_size_per(m, fc); @@ -24,19 +44,28 @@ void restore_size_per(Monitor *m, Client *c) { return; } - wl_list_for_each(fc, &clients, link) { - if (VISIBLEON(fc, m) && ISTILED(fc) && fc != c) { - if (fc->ismaster) { - total_master_inner_per += fc->master_inner_per; - } else { - total_stack_inner_per += fc->stack_inner_per; - } - } + if (!c->ismaster && c->old_stack_inner_per < 1.0 && + c->stack_inner_per < 1.0) { + c->stack_inner_per = (1.0 - c->stack_inner_per) * + c->old_stack_inner_per / + (1.0 - c->old_stack_inner_per); } - if (!c->ismaster && total_stack_inner_per) { - c->stack_inner_per = total_stack_inner_per * c->stack_inner_per / - (1 - c->stack_inner_per); + if (c->ismaster && c->old_master_inner_per < 1.0 && + c->master_inner_per < 1.0) { + c->master_inner_per = (1.0 - c->master_inner_per) * + c->old_master_inner_per / + (1.0 - c->old_master_inner_per); + } + + wl_list_for_each(fc, &clients, link) { + if (VISIBLEON(fc, m) && ISTILED(fc) && fc != c && !fc->ismaster && + fc->old_ismaster && fc->old_stack_inner_per < 1.0 && + fc->stack_inner_per < 1.0) { + fc->stack_inner_per = (1.0 - fc->stack_inner_per) * + fc->old_stack_inner_per / + (1.0 - fc->old_stack_inner_per); + } } } @@ -705,8 +734,8 @@ void reset_size_per_mon(Monitor *m, int32_t tile_cilent_num, } } -void // 17 -arrange(Monitor *m, bool want_animation, bool from_view) { +void pre_caculate_before_arrange(Monitor *m, bool want_animation, + bool from_view, bool only_caculate) { Client *c = NULL; double total_stack_inner_percent = 0; double total_master_inner_percent = 0; @@ -795,14 +824,17 @@ arrange(Monitor *m, bool want_animation, bool from_view) { i++; } - set_arrange_visible(m, c, want_animation); + if (!only_caculate) + set_arrange_visible(m, c, want_animation); } else { - set_arrange_hidden(m, c, want_animation); + if (!only_caculate) + set_arrange_hidden(m, c, want_animation); } } - if (c->mon == m && c->ismaximizescreen && !c->animation.tagouted && - !c->animation.tagouting && VISIBLEON(c, m)) { + if (!only_caculate && c->mon == m && c->ismaximizescreen && + !c->animation.tagouted && !c->animation.tagouting && + VISIBLEON(c, m)) { reset_maximizescreen_size(c); } } @@ -811,6 +843,12 @@ arrange(Monitor *m, bool want_animation, bool from_view) { m, m->visible_tiling_clients, total_left_stack_hight_percent, total_right_stack_hight_percent, total_stack_inner_percent, total_master_inner_percent, master_num, stack_num); +} + +void // 17 +arrange(Monitor *m, bool want_animation, bool from_view) { + + pre_caculate_before_arrange(m, want_animation, from_view, false); if (m->isoverview) { overviewlayout.arrange(m); diff --git a/src/mango.c b/src/mango.c index 9edc25fb..3ae5ccc9 100644 --- a/src/mango.c +++ b/src/mango.c @@ -412,6 +412,7 @@ struct Client { double old_master_mfact_per, old_master_inner_per, old_stack_inner_per; double old_scroller_pproportion; bool ismaster; + bool old_ismaster; bool cursor_in_upper_half, cursor_in_left_half; bool isleftstack; int32_t tearing_hint; @@ -803,6 +804,8 @@ static void last_cursor_surface_destroy(struct wl_listener *listener, void *data); static int32_t keep_idle_inhibit(void *data); static void check_keep_idle_inhibit(Client *c); +static void pre_caculate_before_arrange(Monitor *m, bool want_animation, + bool from_view, bool only_caculate); #include "data/static_keymap.h" #include "dispatch/bind_declare.h" @@ -3981,6 +3984,7 @@ void init_client_properties(Client *c) { c->swallowing = NULL; c->swallowedby = NULL; c->ismaster = 0; + c->old_ismaster = 0; c->isleftstack = 0; c->ismaximizescreen = 0; c->isfullscreen = 0; @@ -5047,6 +5051,10 @@ setfloating(Client *c, int32_t floating) { restore_size_per(c->mon, c); } + if (c->isfloating && !old_floating_state) { + save_old_size_per(c->mon); + } + if (!c->force_maximize) client_set_maximized(c, false); @@ -5133,6 +5141,10 @@ void setmaximizescreen(Client *c, int32_t maximizescreen) { restore_size_per(c->mon, c); } + if (c->ismaximizescreen && !old_maximizescreen_state) { + save_old_size_per(c->mon); + } + if (!c->force_maximize && !c->ismaximizescreen) { client_set_maximized(c, false); } else if (!c->force_maximize && c->ismaximizescreen) { @@ -5204,6 +5216,10 @@ void setfullscreen(Client *c, int32_t fullscreen) // 用自定义全屏代理自 restore_size_per(c->mon, c); } + if (c->isfullscreen && !old_fullscreen_state) { + save_old_size_per(c->mon); + } + arrange(c->mon, false, false); } From 9df273cdf98cb28057ce20fd9adb9a73215800b1 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 5 Mar 2026 23:03:01 +0800 Subject: [PATCH 068/328] opt: clear some comment --- .github/workflows/stale.yml | 2 +- src/layout/arrange.h | 12 ++++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 43e8badc..d0b19c1e 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -16,7 +16,7 @@ jobs: days-before-issue-stale: -1 # 手动标记后,14 天后关闭 days-before-issue-close: 7 - # 使用的标签(必须和你手动添加的标签一致) + # 使用的标签 stale-issue-label: "stale" # 自动关闭时自动加上的标签 close-issue-label: "automatic-closing" diff --git a/src/layout/arrange.h b/src/layout/arrange.h index 53c64893..69d221d1 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -119,8 +119,7 @@ void resize_tile_master_horizontal(Client *grabc, bool isdrag, int32_t offsetx, break; } - if (!begin_find_nextnext && VISIBLEON(tc, grabc->mon) && - ISTILED(tc)) { // 根据你的实际字段名调整 + if (!begin_find_nextnext && VISIBLEON(tc, grabc->mon) && ISTILED(tc)) { next = tc; begin_find_nextnext = true; continue; @@ -136,8 +135,7 @@ void resize_tile_master_horizontal(Client *grabc, bool isdrag, int32_t offsetx, break; } - if (!begin_find_prevprev && VISIBLEON(tc, grabc->mon) && - ISTILED(tc)) { // 根据你的实际字段名调整 + if (!begin_find_prevprev && VISIBLEON(tc, grabc->mon) && ISTILED(tc)) { prev = tc; begin_find_prevprev = true; continue; @@ -305,8 +303,7 @@ void resize_tile_master_vertical(Client *grabc, bool isdrag, int32_t offsetx, for (node = grabc->link.next; node != &clients; node = node->next) { tc = wl_container_of(node, tc, link); - if (VISIBLEON(tc, grabc->mon) && - ISTILED(tc)) { // 根据你的实际字段名调整 + if (VISIBLEON(tc, grabc->mon) && ISTILED(tc)) { next = tc; break; } @@ -316,8 +313,7 @@ void resize_tile_master_vertical(Client *grabc, bool isdrag, int32_t offsetx, for (node = grabc->link.prev; node != &clients; node = node->prev) { tc = wl_container_of(node, tc, link); - if (VISIBLEON(tc, grabc->mon) && - ISTILED(tc)) { // 根据你的实际字段名调整 + if (VISIBLEON(tc, grabc->mon) && ISTILED(tc)) { prev = tc; break; } From 6522e18d0839858711974f351a54fa20d3e05b68 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 6 Mar 2026 13:25:24 +0800 Subject: [PATCH 069/328] opt: fix potential issues caused by uninitialization --- src/layout/arrange.h | 20 +++++++++++++++++--- src/mango.c | 3 +++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/layout/arrange.h b/src/layout/arrange.h index 69d221d1..d674068e 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -5,6 +5,16 @@ void save_old_size_per(Monitor *m) { if (VISIBLEON(c, m) && ISTILED(c)) { c->old_master_inner_per = c->master_inner_per; c->old_stack_inner_per = c->stack_inner_per; + } else { + if (c->old_master_inner_per <= 0.0f || + c->old_master_inner_per > 1.0f) { + c->old_master_inner_per = 1.0f; + } + + if (c->old_stack_inner_per <= 0.0f || + c->old_stack_inner_per > 1.0f) { + c->old_stack_inner_per = 1.0f; + } } } } @@ -45,14 +55,16 @@ void restore_size_per(Monitor *m, Client *c) { } if (!c->ismaster && c->old_stack_inner_per < 1.0 && - c->stack_inner_per < 1.0) { + c->old_stack_inner_per > 0.0f && c->stack_inner_per < 1.0 && + c->stack_inner_per > 0.0f) { c->stack_inner_per = (1.0 - c->stack_inner_per) * c->old_stack_inner_per / (1.0 - c->old_stack_inner_per); } if (c->ismaster && c->old_master_inner_per < 1.0 && - c->master_inner_per < 1.0) { + c->old_master_inner_per > 0.0f && c->master_inner_per < 1.0 && + c->master_inner_per > 0.0f) { c->master_inner_per = (1.0 - c->master_inner_per) * c->old_master_inner_per / (1.0 - c->old_master_inner_per); @@ -61,10 +73,12 @@ void restore_size_per(Monitor *m, Client *c) { wl_list_for_each(fc, &clients, link) { if (VISIBLEON(fc, m) && ISTILED(fc) && fc != c && !fc->ismaster && fc->old_ismaster && fc->old_stack_inner_per < 1.0 && - fc->stack_inner_per < 1.0) { + fc->old_stack_inner_per > 0.0f && fc->stack_inner_per < 1.0 && + fc->stack_inner_per > 0.0f) { fc->stack_inner_per = (1.0 - fc->stack_inner_per) * fc->old_stack_inner_per / (1.0 - fc->old_stack_inner_per); + fc->old_ismaster = false; } } } diff --git a/src/mango.c b/src/mango.c index 3ae5ccc9..4c063cf4 100644 --- a/src/mango.c +++ b/src/mango.c @@ -4025,6 +4025,9 @@ void init_client_properties(Client *c) { c->master_mfact_per = 0.0f; c->master_inner_per = 0.0f; c->stack_inner_per = 0.0f; + c->old_stack_inner_per = 0.0f; + c->old_master_inner_per = 0.0f; + c->old_master_mfact_per = 0.0f; c->isterm = 0; c->allow_csd = 0; c->force_maximize = 0; From 11b4bb03bfea2e914fc7b208f724d89f7a446060 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 6 Mar 2026 14:17:26 +0800 Subject: [PATCH 070/328] feat: support the repeated exchange of the same two clients --- src/dispatch/bind_define.h | 4 +++- src/mango.c | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 5cf41d6c..ac1f6022 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -110,7 +110,9 @@ int32_t exchange_client(const Arg *arg) { if ((c->isfullscreen || c->ismaximizescreen) && !is_scroller_layout(c->mon)) return 0; - exchange_two_client(c, direction_select(arg)); + Client *tc = direction_select(arg); + tc = get_focused_stack_client(tc); + exchange_two_client(c, tc); return 0; } diff --git a/src/mango.c b/src/mango.c index 4c063cf4..c28844fe 100644 --- a/src/mango.c +++ b/src/mango.c @@ -4830,6 +4830,11 @@ void exchange_two_client(Client *c1, Client *c2) { } else { arrange(c1->mon, false, false); } + + // In order to facilitate repeated exchanges for get_focused_stack_client + // set c2 focus order behind c1 + wl_list_remove(&c2->flink); + wl_list_insert(&c1->flink, &c2->flink); } void set_activation_env() { From 9a17a0279c39b330d155d50282d1d2641d519dc3 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 6 Mar 2026 18:21:49 +0800 Subject: [PATCH 071/328] feat: add custom option to monitorrule --- src/config/parse_config.h | 10 ++++++++-- src/mango.c | 5 +++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index ce5db532..59debcf2 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -112,6 +112,7 @@ typedef struct { int32_t width, height; // Monitor resolution float refresh; // Refresh rate int32_t vrr; // variable refresh rate + int32_t custom; // enable custom mode } ConfigMonitorRule; // 修改后的宏定义 @@ -1796,6 +1797,7 @@ bool parse_option(Config *config, char *key, char *value) { rule->height = -1; rule->refresh = 0.0f; rule->vrr = 0; + rule->custom = 0; bool parse_error = false; char *token = strtok(value, ","); @@ -1833,6 +1835,8 @@ bool parse_option(Config *config, char *key, char *value) { rule->refresh = CLAMP_FLOAT(atof(val), 0.001f, 1000.0f); } else if (strcmp(key, "vrr") == 0) { rule->vrr = CLAMP_INT(atoi(val), 0, 1); + } else if (strcmp(key, "custom") == 0) { + rule->custom = CLAMP_INT(atoi(val), 0, 1); } else { fprintf(stderr, "\033[1m\033[31m[ERROR]:\033[33m Unknown " @@ -3555,7 +3559,7 @@ void reset_blur_params(void) { void reapply_monitor_rules(void) { ConfigMonitorRule *mr; Monitor *m = NULL; - int32_t ji, vrr; + int32_t ji, vrr, custom; int32_t mx, my; struct wlr_output_state state; struct wlr_output_mode *internal_mode = NULL; @@ -3609,13 +3613,15 @@ void reapply_monitor_rules(void) { mx = mr->x == INT32_MAX ? m->m.x : mr->x; my = mr->y == INT32_MAX ? m->m.y : mr->y; vrr = mr->vrr >= 0 ? mr->vrr : 0; + custom = mr->custom >= 0 ? mr->custom : 0; if (mr->width > 0 && mr->height > 0 && mr->refresh > 0) { internal_mode = get_nearest_output_mode( m->wlr_output, mr->width, mr->height, mr->refresh); if (internal_mode) { wlr_output_state_set_mode(&state, internal_mode); - } else if (wlr_output_is_headless(m->wlr_output)) { + } else if (custom || + wlr_output_is_headless(m->wlr_output)) { wlr_output_state_set_custom_mode( &state, mr->width, mr->height, (int32_t)roundf(mr->refresh * 1000)); diff --git a/src/mango.c b/src/mango.c index c28844fe..94bddb6b 100644 --- a/src/mango.c +++ b/src/mango.c @@ -2884,7 +2884,7 @@ void createmon(struct wl_listener *listener, void *data) { struct wlr_output *wlr_output = data; const ConfigMonitorRule *r; uint32_t i; - int32_t ji, vrr; + int32_t ji, vrr, custom; struct wlr_output_state state; Monitor *m = NULL; struct wlr_output_mode *internal_mode = NULL; @@ -2976,6 +2976,7 @@ void createmon(struct wl_listener *listener, void *data) { m->m.x = r->x == INT32_MAX ? INT32_MAX : r->x; m->m.y = r->y == INT32_MAX ? INT32_MAX : r->y; vrr = r->vrr >= 0 ? r->vrr : 0; + custom = r->custom >= 0 ? r->custom : 0; scale = r->scale; rr = r->rr; @@ -2985,7 +2986,7 @@ void createmon(struct wl_listener *listener, void *data) { if (internal_mode) { custom_monitor_mode = true; wlr_output_state_set_mode(&state, internal_mode); - } else if (wlr_output_is_headless(m->wlr_output)) { + } else if (custom || wlr_output_is_headless(m->wlr_output)) { custom_monitor_mode = true; wlr_output_state_set_custom_mode( &state, r->width, r->height, From 10d7e5c6e3817d36ae8dbe5747ad23fe26944a45 Mon Sep 17 00:00:00 2001 From: kanvolu Date: Fri, 6 Mar 2026 22:23:59 -0500 Subject: [PATCH 072/328] Added cycling both ways for switch_proportion_preset Now switch_proportion_preset requires an argument "prev" or "next" to determine cycle direction --- src/config/parse_config.h | 1 + src/dispatch/bind_declare.h | 2 +- src/dispatch/bind_define.h | 29 ++++++++++++++++++++++------- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 59debcf2..47d086a7 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -947,6 +947,7 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value, (*arg).f = atof(arg_value); } else if (strcmp(func_name, "switch_proportion_preset") == 0) { func = switch_proportion_preset; + (*arg).i = parse_circle_direction(arg_value); } else if (strcmp(func_name, "viewtoleft") == 0) { func = viewtoleft; (*arg).i = atoi(arg_value); diff --git a/src/dispatch/bind_declare.h b/src/dispatch/bind_declare.h index 22ef6123..7dced532 100644 --- a/src/dispatch/bind_declare.h +++ b/src/dispatch/bind_declare.h @@ -69,4 +69,4 @@ int32_t setoption(const Arg *arg); int32_t disable_monitor(const Arg *arg); int32_t enable_monitor(const Arg *arg); int32_t toggle_monitor(const Arg *arg); -int32_t scroller_stack(const Arg *arg); \ No newline at end of file +int32_t scroller_stack(const Arg *arg); diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index ac1f6022..676be515 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1056,13 +1056,28 @@ int32_t switch_proportion_preset(const Arg *arg) { for (int32_t i = 0; i < config.scroller_proportion_preset_count; i++) { if (config.scroller_proportion_preset[i] == tc->scroller_proportion) { - if (i == config.scroller_proportion_preset_count - 1) { - target_proportion = config.scroller_proportion_preset[0]; - break; + + if (arg->i == NEXT) { + if (i == config.scroller_proportion_preset_count - 1) { + target_proportion = + config.scroller_proportion_preset[0]; + break; + } else { + target_proportion = + config.scroller_proportion_preset[i + 1]; + break; + } } else { - target_proportion = - config.scroller_proportion_preset[i + 1]; - break; + if (i == 0) { + target_proportion = + config.scroller_proportion_preset + [config.scroller_proportion_preset_count - 1]; + break; + } else { + target_proportion = + config.scroller_proportion_preset[i - 1]; + break; + } } } } @@ -1847,4 +1862,4 @@ int32_t scroller_stack(const Arg *arg) { arrange(selmon, false, false); return 0; -} \ No newline at end of file +} From 63b9ffb1a422e34b8d2ce4121cf1aee6fd0e5dad Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 7 Mar 2026 12:54:37 +0800 Subject: [PATCH 073/328] opt: opt the old size per init --- src/layout/arrange.h | 10 ---------- src/mango.c | 6 +++--- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/src/layout/arrange.h b/src/layout/arrange.h index d674068e..058198bf 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -5,16 +5,6 @@ void save_old_size_per(Monitor *m) { if (VISIBLEON(c, m) && ISTILED(c)) { c->old_master_inner_per = c->master_inner_per; c->old_stack_inner_per = c->stack_inner_per; - } else { - if (c->old_master_inner_per <= 0.0f || - c->old_master_inner_per > 1.0f) { - c->old_master_inner_per = 1.0f; - } - - if (c->old_stack_inner_per <= 0.0f || - c->old_stack_inner_per > 1.0f) { - c->old_stack_inner_per = 1.0f; - } } } } diff --git a/src/mango.c b/src/mango.c index 94bddb6b..a256d8f0 100644 --- a/src/mango.c +++ b/src/mango.c @@ -4026,9 +4026,9 @@ void init_client_properties(Client *c) { c->master_mfact_per = 0.0f; c->master_inner_per = 0.0f; c->stack_inner_per = 0.0f; - c->old_stack_inner_per = 0.0f; - c->old_master_inner_per = 0.0f; - c->old_master_mfact_per = 0.0f; + c->old_stack_inner_per = 1.0f; + c->old_master_inner_per = 1.0f; + c->old_master_mfact_per = 1.0f; c->isterm = 0; c->allow_csd = 0; c->force_maximize = 0; From fd68f188c63e7595f55bbb326bcb7c7ad4ec7c1f Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 7 Mar 2026 13:24:46 +0800 Subject: [PATCH 074/328] opt: add some comment --- src/layout/arrange.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/layout/arrange.h b/src/layout/arrange.h index 058198bf..36f6396a 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -44,6 +44,10 @@ void restore_size_per(Monitor *m, Client *c) { return; } + // it is possible that the current floating window is moved to another tag, + // but the tag has not executed save_old_size_per + // so it must be judged whether their old size values are initial values + if (!c->ismaster && c->old_stack_inner_per < 1.0 && c->old_stack_inner_per > 0.0f && c->stack_inner_per < 1.0 && c->stack_inner_per > 0.0f) { From 89a4ec83a0d24887d182209feca488f32fe3c071 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 7 Mar 2026 13:46:18 +0800 Subject: [PATCH 075/328] fix: avoid mutual influence of monitor rules --- src/config/parse_config.h | 72 ++++--------------------- src/mango.c | 111 +++++++++++++++++--------------------- 2 files changed, 60 insertions(+), 123 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 47d086a7..d10bf0c2 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -372,6 +372,9 @@ typedef int32_t (*FuncType)(const Arg *); Config config; bool parse_config_file(Config *config, const char *file_path, bool must_exist); +bool apply_rule_to_state(Monitor *m, const ConfigMonitorRule *rule, + struct wlr_output_state *state, int vrr, int custom); +bool monitor_matches_rule(Monitor *m, const ConfigMonitorRule *rule); // Helper function to trim whitespace from start and end of a string void trim_whitespace(char *str) { @@ -3563,14 +3566,12 @@ void reapply_monitor_rules(void) { int32_t ji, vrr, custom; int32_t mx, my; struct wlr_output_state state; - struct wlr_output_mode *internal_mode = NULL; - wlr_output_state_init(&state); - bool match_rule = false; wl_list_for_each(m, &mons, link) { - if (!m->wlr_output->enabled) { + if (!m->wlr_output->enabled) continue; - } + + wlr_output_state_init(&state); for (ji = 0; ji < config.monitor_rules_count; ji++) { if (config.monitor_rules_count < 1) @@ -3578,73 +3579,22 @@ void reapply_monitor_rules(void) { mr = &config.monitor_rules[ji]; - // 检查是否匹配的变量 - match_rule = true; - - // 检查四个标识字段的匹配 - if (mr->name != NULL) { - if (!regex_match(mr->name, m->wlr_output->name)) { - match_rule = false; - } - } - - if (mr->make != NULL) { - if (m->wlr_output->make == NULL || - strcmp(mr->make, m->wlr_output->make) != 0) { - match_rule = false; - } - } - - if (mr->model != NULL) { - if (m->wlr_output->model == NULL || - strcmp(mr->model, m->wlr_output->model) != 0) { - match_rule = false; - } - } - - if (mr->serial != NULL) { - if (m->wlr_output->serial == NULL || - strcmp(mr->serial, m->wlr_output->serial) != 0) { - match_rule = false; - } - } - - // 只有当所有指定的标识都匹配时才应用规则 - if (match_rule) { + if (monitor_matches_rule(m, mr)) { mx = mr->x == INT32_MAX ? m->m.x : mr->x; my = mr->y == INT32_MAX ? m->m.y : mr->y; vrr = mr->vrr >= 0 ? mr->vrr : 0; custom = mr->custom >= 0 ? mr->custom : 0; - if (mr->width > 0 && mr->height > 0 && mr->refresh > 0) { - internal_mode = get_nearest_output_mode( - m->wlr_output, mr->width, mr->height, mr->refresh); - if (internal_mode) { - wlr_output_state_set_mode(&state, internal_mode); - } else if (custom || - wlr_output_is_headless(m->wlr_output)) { - wlr_output_state_set_custom_mode( - &state, mr->width, mr->height, - (int32_t)roundf(mr->refresh * 1000)); - } - } - - if (vrr) { - enable_adaptive_sync(m, &state); - } else { - wlr_output_state_set_adaptive_sync_enabled(&state, false); - } - - wlr_output_state_set_scale(&state, mr->scale); - wlr_output_state_set_transform(&state, mr->rr); + (void)apply_rule_to_state(m, mr, &state, vrr, custom); wlr_output_layout_add(output_layout, m->wlr_output, mx, my); + wlr_output_commit_state(m->wlr_output, &state); + break; } } - wlr_output_commit_state(m->wlr_output, &state); wlr_output_state_finish(&state); - updatemons(NULL, NULL); } + updatemons(NULL, NULL); } void reapply_cursor_style(void) { diff --git a/src/mango.c b/src/mango.c index a256d8f0..2af2720f 100644 --- a/src/mango.c +++ b/src/mango.c @@ -806,7 +806,6 @@ static int32_t keep_idle_inhibit(void *data); static void check_keep_idle_inhibit(Client *c); static void pre_caculate_before_arrange(Monitor *m, bool want_animation, bool from_view, bool only_caculate); - #include "data/static_keymap.h" #include "dispatch/bind_declare.h" #include "layout/layout.h" @@ -2878,6 +2877,49 @@ void enable_adaptive_sync(Monitor *m, struct wlr_output_state *state) { } } +bool monitor_matches_rule(Monitor *m, const ConfigMonitorRule *rule) { + if (rule->name != NULL && !regex_match(rule->name, m->wlr_output->name)) + return false; + if (rule->make != NULL && (m->wlr_output->make == NULL || + strcmp(rule->make, m->wlr_output->make) != 0)) + return false; + if (rule->model != NULL && (m->wlr_output->model == NULL || + strcmp(rule->model, m->wlr_output->model) != 0)) + return false; + if (rule->serial != NULL && + (m->wlr_output->serial == NULL || + strcmp(rule->serial, m->wlr_output->serial) != 0)) + return false; + return true; +} + +/* 将规则中的显示参数应用到 wlr_output_state 中,返回是否设置了自定义模式 */ +bool apply_rule_to_state(Monitor *m, const ConfigMonitorRule *rule, + struct wlr_output_state *state, int vrr, int custom) { + bool mode_set = false; + if (rule->width > 0 && rule->height > 0 && rule->refresh > 0) { + struct wlr_output_mode *internal_mode = get_nearest_output_mode( + m->wlr_output, rule->width, rule->height, rule->refresh); + if (internal_mode) { + wlr_output_state_set_mode(state, internal_mode); + mode_set = true; + } else if (custom || wlr_output_is_headless(m->wlr_output)) { + wlr_output_state_set_custom_mode( + state, rule->width, rule->height, + (int32_t)roundf(rule->refresh * 1000)); + mode_set = true; + } + } + if (vrr) { + enable_adaptive_sync(m, state); + } else { + wlr_output_state_set_adaptive_sync_enabled(state, false); + } + wlr_output_state_set_scale(state, rule->scale); + wlr_output_state_set_transform(state, rule->rr); + return mode_set; +} + void createmon(struct wl_listener *listener, void *data) { /* This event is raised by the backend when a new output (aka a display or * monitor) becomes available. */ @@ -2887,9 +2929,7 @@ void createmon(struct wl_listener *listener, void *data) { int32_t ji, vrr, custom; struct wlr_output_state state; Monitor *m = NULL; - struct wlr_output_mode *internal_mode = NULL; bool custom_monitor_mode = false; - bool match_rule = false; if (!wlr_output_init_render(wlr_output, alloc, drw)) return; @@ -2919,7 +2959,6 @@ void createmon(struct wl_listener *listener, void *data) { for (i = 0; i < LENGTH(m->layers); i++) wl_list_init(&m->layers[i]); - wlr_output_state_init(&state); /* Initialize monitor state using configured rules */ m->gappih = gappih; m->gappiv = gappiv; @@ -2932,6 +2971,8 @@ void createmon(struct wl_listener *listener, void *data) { m->m.y = INT32_MAX; float scale = 1; enum wl_output_transform rr = WL_OUTPUT_TRANSFORM_NORMAL; + + wlr_output_state_init(&state); wlr_output_state_set_scale(&state, scale); wlr_output_state_set_transform(&state, rr); @@ -2941,38 +2982,7 @@ void createmon(struct wl_listener *listener, void *data) { r = &config.monitor_rules[ji]; - // 检查是否匹配的变量 - match_rule = true; - - // 检查四个标识字段的匹配 - if (r->name != NULL) { - if (!regex_match(r->name, m->wlr_output->name)) { - match_rule = false; - } - } - - if (r->make != NULL) { - if (m->wlr_output->make == NULL || - strcmp(r->make, m->wlr_output->make) != 0) { - match_rule = false; - } - } - - if (r->model != NULL) { - if (m->wlr_output->model == NULL || - strcmp(r->model, m->wlr_output->model) != 0) { - match_rule = false; - } - } - - if (r->serial != NULL) { - if (m->wlr_output->serial == NULL || - strcmp(r->serial, m->wlr_output->serial) != 0) { - match_rule = false; - } - } - - if (match_rule) { + if (monitor_matches_rule(m, r)) { m->m.x = r->x == INT32_MAX ? INT32_MAX : r->x; m->m.y = r->y == INT32_MAX ? INT32_MAX : r->y; vrr = r->vrr >= 0 ? r->vrr : 0; @@ -2980,36 +2990,13 @@ void createmon(struct wl_listener *listener, void *data) { scale = r->scale; rr = r->rr; - if (r->width > 0 && r->height > 0 && r->refresh > 0) { - internal_mode = get_nearest_output_mode(m->wlr_output, r->width, - r->height, r->refresh); - if (internal_mode) { - custom_monitor_mode = true; - wlr_output_state_set_mode(&state, internal_mode); - } else if (custom || wlr_output_is_headless(m->wlr_output)) { - custom_monitor_mode = true; - wlr_output_state_set_custom_mode( - &state, r->width, r->height, - (int32_t)roundf(r->refresh * 1000)); - } + if (apply_rule_to_state(m, r, &state, vrr, custom)) { + custom_monitor_mode = true; } - - if (vrr) { - enable_adaptive_sync(m, &state); - } else { - wlr_output_state_set_adaptive_sync_enabled(&state, false); - } - - wlr_output_state_set_scale(&state, r->scale); - wlr_output_state_set_transform(&state, r->rr); - break; + break; // 只应用第一个匹配规则 } } - /* The mode is a tuple of (width, height, refresh rate), and each - * monitor supports only a specific set of modes. We just pick the - * monitor's preferred mode; a more sophisticated compositor would let - * the user configure it. */ if (!custom_monitor_mode) wlr_output_state_set_mode(&state, wlr_output_preferred_mode(wlr_output)); From 75c888bbe4bc7efda789bbe1d8872a36b0b2a964 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 7 Mar 2026 15:31:05 +0800 Subject: [PATCH 076/328] opt: optimize resizewin setp with keyboard --- src/layout/arrange.h | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/layout/arrange.h b/src/layout/arrange.h index 36f6396a..853e4692 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -276,6 +276,18 @@ void resize_tile_master_horizontal(Client *grabc, bool isdrag, int32_t offsetx, new_master_inner_per = fmaxf(0.1f, fminf(0.9f, new_master_inner_per)); new_stack_inner_per = fmaxf(0.1f, fminf(0.9f, new_stack_inner_per)); + if (!isdrag) { + new_stack_inner_per = + new_stack_inner_per + + (new_stack_inner_per - grabc->old_stack_inner_per) / + ((1 / new_stack_inner_per) - 1); + + new_master_inner_per = + new_master_inner_per + + (new_master_inner_per - grabc->old_master_inner_per) / + ((1 / new_master_inner_per) - 1); + } + // 应用到所有平铺窗口 wl_list_for_each(tc, &clients, link) { if (VISIBLEON(tc, grabc->mon) && ISTILED(tc)) { @@ -431,6 +443,18 @@ void resize_tile_master_vertical(Client *grabc, bool isdrag, int32_t offsetx, new_master_inner_per = fmaxf(0.1f, fminf(0.9f, new_master_inner_per)); new_stack_inner_per = fmaxf(0.1f, fminf(0.9f, new_stack_inner_per)); + if (!isdrag) { + new_stack_inner_per = + new_stack_inner_per + + (new_stack_inner_per - grabc->old_stack_inner_per) / + ((1 / new_stack_inner_per) - 1); + + new_master_inner_per = + new_master_inner_per + + (new_master_inner_per - grabc->old_master_inner_per) / + ((1 / new_master_inner_per) - 1); + } + // 应用到所有平铺窗口 wl_list_for_each(tc, &clients, link) { if (VISIBLEON(tc, grabc->mon) && ISTILED(tc)) { @@ -616,7 +640,14 @@ void resize_tile_scroller(Client *grabc, bool isdrag, int32_t offsetx, // 应用限制,确保比例在合理范围内 new_scroller_proportion = fmaxf(0.1f, fminf(1.0f, new_scroller_proportion)); - new_stack_proportion = fmaxf(0.1f, fminf(1.0f, new_stack_proportion)); + new_stack_proportion = fmaxf(0.1f, fminf(0.9f, new_stack_proportion)); + + if (!isdrag) { + new_stack_proportion = + new_stack_proportion + + (new_stack_proportion - grabc->old_stack_proportion) / + ((1 / new_stack_proportion) - 1); + } grabc->stack_proportion = new_stack_proportion; From b1d744ad1f493cba26fb31a87db5346ac9361c6b Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 7 Mar 2026 17:21:50 +0800 Subject: [PATCH 077/328] feat: export drag interval to able configure --- assets/config.conf | 2 ++ src/config/parse_config.h | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/assets/config.conf b/assets/config.conf index 15b654c1..77786649 100644 --- a/assets/config.conf +++ b/assets/config.conf @@ -74,6 +74,8 @@ overviewgappi=5 overviewgappo=30 # Misc +drag_tile_refresh_interval=16.0 +drag_floating_refresh_interval=8.0 no_border_when_single=0 axis_bind_apply_timeout=100 focus_on_activate=1 diff --git a/src/config/parse_config.h b/src/config/parse_config.h index d10bf0c2..3220b713 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -356,6 +356,8 @@ typedef struct { int32_t single_scratchpad; int32_t xwayland_persistence; int32_t syncobj_enable; + float drag_tile_refresh_interval; + float drag_floating_refresh_interval; int32_t allow_tearing; int32_t allow_shortcuts_inhibit; int32_t allow_lock_transparent; @@ -1399,6 +1401,10 @@ bool parse_option(Config *config, char *key, char *value) { config->xwayland_persistence = atoi(value); } else if (strcmp(key, "syncobj_enable") == 0) { config->syncobj_enable = atoi(value); + } else if (strcmp(key, "drag_tile_refresh_interval") == 0) { + config->drag_tile_refresh_interval = atof(value); + } else if (strcmp(key, "drag_floating_refresh_interval") == 0) { + config->drag_floating_refresh_interval = atof(value); } else if (strcmp(key, "allow_tearing") == 0) { config->allow_tearing = atoi(value); } else if (strcmp(key, "allow_shortcuts_inhibit") == 0) { @@ -3149,6 +3155,13 @@ void override_config(void) { // 杂项设置 xwayland_persistence = CLAMP_INT(config.xwayland_persistence, 0, 1); syncobj_enable = CLAMP_INT(config.syncobj_enable, 0, 1); + drag_tile_refresh_interval = + CLAMP_FLOAT(config.drag_tile_refresh_interval, 1.0f, 16.0f); + drag_floating_refresh_interval = + CLAMP_FLOAT(config.drag_floating_refresh_interval, 1.0f, 16.0f); + drag_tile_to_tile = CLAMP_INT(config.drag_tile_to_tile, 0, 1); + drag_floating_refresh_interval = + CLAMP_FLOAT(config.drag_floating_refresh_interval, 0.0f, 1000.0f); allow_tearing = CLAMP_INT(config.allow_tearing, 0, 2); allow_shortcuts_inhibit = CLAMP_INT(config.allow_shortcuts_inhibit, 0, 1); allow_lock_transparent = CLAMP_INT(config.allow_lock_transparent, 0, 1); @@ -3337,6 +3350,8 @@ void set_value_default() { config.single_scratchpad = single_scratchpad; config.xwayland_persistence = xwayland_persistence; config.syncobj_enable = syncobj_enable; + config.drag_tile_refresh_interval = drag_tile_refresh_interval; + config.drag_floating_refresh_interval = drag_floating_refresh_interval; config.allow_tearing = allow_tearing; config.allow_shortcuts_inhibit = allow_shortcuts_inhibit; config.allow_lock_transparent = allow_lock_transparent; From 636060972dc42c18a7abb1f533fc23be338ceb23 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 7 Mar 2026 17:29:37 +0800 Subject: [PATCH 078/328] opt: change some default config --- assets/config.conf | 2 +- src/config/preset.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/config.conf b/assets/config.conf index 77786649..d2587165 100644 --- a/assets/config.conf +++ b/assets/config.conf @@ -74,7 +74,7 @@ overviewgappi=5 overviewgappo=30 # Misc -drag_tile_refresh_interval=16.0 +drag_tile_refresh_interval=8.0 drag_floating_refresh_interval=8.0 no_border_when_single=0 axis_bind_apply_timeout=100 diff --git a/src/config/preset.h b/src/config/preset.h index d9824588..f91da11a 100644 --- a/src/config/preset.h +++ b/src/config/preset.h @@ -109,7 +109,7 @@ int32_t drag_warp_cursor = 1; int32_t xwayland_persistence = 1; /* xwayland persistence */ int32_t syncobj_enable = 0; int32_t allow_lock_transparent = 0; -double drag_tile_refresh_interval = 16.0; +double drag_tile_refresh_interval = 8.0; double drag_floating_refresh_interval = 8.0; int32_t allow_tearing = TEARING_DISABLED; int32_t allow_shortcuts_inhibit = SHORTCUTS_INHIBIT_ENABLE; From d0eb7d7114705877529045e6315de1c271a8e2a3 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 7 Mar 2026 17:40:22 +0800 Subject: [PATCH 079/328] opt: remove some config in default config file --- assets/config.conf | 2 -- 1 file changed, 2 deletions(-) diff --git a/assets/config.conf b/assets/config.conf index d2587165..15b654c1 100644 --- a/assets/config.conf +++ b/assets/config.conf @@ -74,8 +74,6 @@ overviewgappi=5 overviewgappo=30 # Misc -drag_tile_refresh_interval=8.0 -drag_floating_refresh_interval=8.0 no_border_when_single=0 axis_bind_apply_timeout=100 focus_on_activate=1 From 09c170793177dcbdb4e0a7881260f4243caf2d59 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 7 Mar 2026 18:51:12 +0800 Subject: [PATCH 080/328] opt: optimize size per caculate when resizewin --- src/layout/arrange.h | 93 ++++++++++++++++++++++++++++---------------- 1 file changed, 60 insertions(+), 33 deletions(-) diff --git a/src/layout/arrange.h b/src/layout/arrange.h index 853e4692..bbe735f7 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -276,21 +276,24 @@ void resize_tile_master_horizontal(Client *grabc, bool isdrag, int32_t offsetx, new_master_inner_per = fmaxf(0.1f, fminf(0.9f, new_master_inner_per)); new_stack_inner_per = fmaxf(0.1f, fminf(0.9f, new_stack_inner_per)); - if (!isdrag) { - new_stack_inner_per = - new_stack_inner_per + - (new_stack_inner_per - grabc->old_stack_inner_per) / - ((1 / new_stack_inner_per) - 1); - - new_master_inner_per = - new_master_inner_per + - (new_master_inner_per - grabc->old_master_inner_per) / - ((1 / new_master_inner_per) - 1); - } - // 应用到所有平铺窗口 wl_list_for_each(tc, &clients, link) { if (VISIBLEON(tc, grabc->mon) && ISTILED(tc)) { + + if (!isdrag && tc != grabc && type != CENTER_TILE) { + if (!tc->ismaster && new_stack_inner_per != 1.0f && + grabc->old_stack_inner_per != 1.0f) + tc->stack_inner_per = (1 - new_stack_inner_per) / + (1 - grabc->old_stack_inner_per) * + tc->stack_inner_per; + if (tc->ismaster && new_master_inner_per != 1.0f && + grabc->old_master_inner_per != 1.0f) + tc->master_inner_per = + (1.0f - new_master_inner_per) / + (1.0f - grabc->old_master_inner_per) * + tc->master_inner_per; + } + tc->master_mfact_per = new_master_mfact_per; } } @@ -443,21 +446,23 @@ void resize_tile_master_vertical(Client *grabc, bool isdrag, int32_t offsetx, new_master_inner_per = fmaxf(0.1f, fminf(0.9f, new_master_inner_per)); new_stack_inner_per = fmaxf(0.1f, fminf(0.9f, new_stack_inner_per)); - if (!isdrag) { - new_stack_inner_per = - new_stack_inner_per + - (new_stack_inner_per - grabc->old_stack_inner_per) / - ((1 / new_stack_inner_per) - 1); - - new_master_inner_per = - new_master_inner_per + - (new_master_inner_per - grabc->old_master_inner_per) / - ((1 / new_master_inner_per) - 1); - } - // 应用到所有平铺窗口 wl_list_for_each(tc, &clients, link) { if (VISIBLEON(tc, grabc->mon) && ISTILED(tc)) { + if (!isdrag && tc != grabc && type != CENTER_TILE) { + if (!tc->ismaster && new_stack_inner_per != 1.0f && + grabc->old_stack_inner_per != 1.0f) + tc->stack_inner_per = (1 - new_stack_inner_per) / + (1 - grabc->old_stack_inner_per) * + tc->stack_inner_per; + if (tc->ismaster && new_master_inner_per != 1.0f && + grabc->old_master_inner_per != 1.0f) + tc->master_inner_per = + (1.0f - new_master_inner_per) / + (1.0f - grabc->old_master_inner_per) * + tc->master_inner_per; + } + tc->master_mfact_per = new_master_mfact_per; } } @@ -480,6 +485,7 @@ void resize_tile_master_vertical(Client *grabc, bool isdrag, int32_t offsetx, void resize_tile_scroller(Client *grabc, bool isdrag, int32_t offsetx, int32_t offsety, uint32_t time, bool isvertical) { + Client *tc = NULL; float delta_x, delta_y; float new_scroller_proportion; float new_stack_proportion; @@ -642,17 +648,20 @@ void resize_tile_scroller(Client *grabc, bool isdrag, int32_t offsetx, fmaxf(0.1f, fminf(1.0f, new_scroller_proportion)); new_stack_proportion = fmaxf(0.1f, fminf(0.9f, new_stack_proportion)); - if (!isdrag) { - new_stack_proportion = - new_stack_proportion + - (new_stack_proportion - grabc->old_stack_proportion) / - ((1 / new_stack_proportion) - 1); - } - grabc->stack_proportion = new_stack_proportion; stack_head->scroller_proportion = new_scroller_proportion; + wl_list_for_each(tc, &clients, link) { + if (new_stack_proportion != 1.0f && + grabc->old_stack_proportion != 1.0f && tc != grabc && + ISTILED(tc) && get_scroll_stack_head(tc) == stack_head) { + tc->stack_proportion = (1.0f - new_stack_proportion) / + (1.0f - grabc->old_stack_proportion) * + tc->stack_proportion; + } + } + if (!isdrag) { arrange(grabc->mon, false, false); return; @@ -695,6 +704,18 @@ void resize_tile_client(Client *grabc, bool isdrag, int32_t offsetx, } } +/* If there are no calculation omissions, +these two functions will never be triggered. +Just in case to facilitate the final investigation*/ + +void check_size_per_valid(Client *c) { + if (c->ismaster) { + assert(c->master_inner_per > 0.0f && c->master_inner_per <= 1.0f); + } else { + assert(c->stack_inner_per > 0.0f && c->stack_inner_per <= 1.0f); + } +} + void reset_size_per_mon(Monitor *m, int32_t tile_cilent_num, double total_left_stack_hight_percent, double total_right_stack_hight_percent, @@ -710,6 +731,7 @@ void reset_size_per_mon(Monitor *m, int32_t tile_cilent_num, wl_list_for_each(c, &clients, link) { if (VISIBLEON(c, m) && ISTILED(c)) { + if (total_master_inner_percent > 0.0 && i < nmasters) { c->ismaster = true; c->stack_inner_per = stack_num ? 1.0f / stack_num : 1.0f; @@ -725,17 +747,20 @@ void reset_size_per_mon(Monitor *m, int32_t tile_cilent_num, : 1.0f; } i++; + + check_size_per_valid(c); } } } else { wl_list_for_each(c, &clients, link) { if (VISIBLEON(c, m) && ISTILED(c)) { + if (total_master_inner_percent > 0.0 && i < nmasters) { c->ismaster = true; if ((stack_index % 2) ^ (tile_cilent_num % 2 == 0)) { c->stack_inner_per = - stack_num > 1 ? 1.0f / ((stack_num - 1) / 2) : 1.0f; - + stack_num > 1 ? 1.0f / ((stack_num - 1) / 2.0f) + : 1.0f; } else { c->stack_inner_per = stack_num > 1 ? 2.0f / stack_num : 1.0f; @@ -764,6 +789,8 @@ void reset_size_per_mon(Monitor *m, int32_t tile_cilent_num, } } i++; + + check_size_per_valid(c); } } } From 31284b4b5db8785004cdb345c778f31424684a6f Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 7 Mar 2026 22:54:13 +0800 Subject: [PATCH 081/328] opt: optimize center tile layout resizewin --- src/layout/arrange.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/layout/arrange.h b/src/layout/arrange.h index bbe735f7..33db8ec0 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -280,9 +280,11 @@ void resize_tile_master_horizontal(Client *grabc, bool isdrag, int32_t offsetx, wl_list_for_each(tc, &clients, link) { if (VISIBLEON(tc, grabc->mon) && ISTILED(tc)) { - if (!isdrag && tc != grabc && type != CENTER_TILE) { + if (!isdrag && tc != grabc) { if (!tc->ismaster && new_stack_inner_per != 1.0f && - grabc->old_stack_inner_per != 1.0f) + grabc->old_stack_inner_per != 1.0f && + (type != CENTER_TILE || + !(grabc->isleftstack ^ tc->isleftstack))) tc->stack_inner_per = (1 - new_stack_inner_per) / (1 - grabc->old_stack_inner_per) * tc->stack_inner_per; From cfe492fbc4af1b9518d1102680c3452e0e52d7b8 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 7 Mar 2026 23:14:17 +0800 Subject: [PATCH 082/328] opt: fix a minor judgment error --- src/layout/arrange.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/layout/arrange.h b/src/layout/arrange.h index 33db8ec0..0284f8ca 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -89,7 +89,7 @@ void set_size_per(Monitor *m, Client *c) { wl_list_for_each(fc, &clients, link) { if (VISIBLEON(fc, m) && ISTILED(fc) && fc != c) { if (current_layout->id == CENTER_TILE && - !(fc->isleftstack ^ c->isleftstack)) + (fc->isleftstack ^ c->isleftstack)) continue; c->master_mfact_per = fc->master_mfact_per; c->master_inner_per = fc->master_inner_per; From 89a0f7e3155a9b4489a56302153a92e4a5f1f339 Mon Sep 17 00:00:00 2001 From: Nikita Mitasov Date: Sat, 7 Mar 2026 22:18:46 +0300 Subject: [PATCH 083/328] fix(guix): add deprecated package variable with old naming. --- mangowm.scm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mangowm.scm b/mangowm.scm index 33d95045..7d94166d 100644 --- a/mangowm.scm +++ b/mangowm.scm @@ -61,4 +61,7 @@ inspired by dwl but aiming to be more feature-rich.") (license gpl3))) +(define-deprecated-package mangowc + mangowm-git) + mangowm-git From a4ad8d0d1945fa37063ac3d112926e061f158c73 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 8 Mar 2026 08:45:56 +0800 Subject: [PATCH 084/328] fix: miss judge isdrag when resize stack in scroller --- src/layout/arrange.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/layout/arrange.h b/src/layout/arrange.h index 0284f8ca..e164a0f6 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -655,7 +655,7 @@ void resize_tile_scroller(Client *grabc, bool isdrag, int32_t offsetx, stack_head->scroller_proportion = new_scroller_proportion; wl_list_for_each(tc, &clients, link) { - if (new_stack_proportion != 1.0f && + if (!isdrag && new_stack_proportion != 1.0f && grabc->old_stack_proportion != 1.0f && tc != grabc && ISTILED(tc) && get_scroll_stack_head(tc) == stack_head) { tc->stack_proportion = (1.0f - new_stack_proportion) / From a607d63ae7c8965655562ed310991e9dc164d4e0 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 8 Mar 2026 20:00:44 +0800 Subject: [PATCH 085/328] opt: reset size per when toggleview --- src/dispatch/bind_define.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 676be515..228e92e3 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1436,6 +1436,7 @@ int32_t toggleview(const Arg *arg) { uint32_t newtagset; uint32_t target; + Client *c = NULL; target = arg->ui == 0 ? ~0 & TAGMASK : arg->ui; @@ -1444,6 +1445,11 @@ int32_t toggleview(const Arg *arg) { if (newtagset) { selmon->tagset[selmon->seltags] = newtagset; focusclient(focustop(selmon), 1); + wl_list_for_each(c, &clients, link) { + if (VISIBLEON(c, selmon) && ISTILED(c)) { + set_size_per(selmon, c); + } + } arrange(selmon, false, false); } printstatus(); From d441ca22f4bd7554f04defdd36f13bedc38610e7 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 8 Mar 2026 20:20:13 +0800 Subject: [PATCH 086/328] opt: set scroller stack to same first tag --- src/dispatch/bind_define.h | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 228e92e3..4f808bdf 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1774,28 +1774,19 @@ int32_t scroller_stack(const Arg *arg) { if (!c || !c->mon || c->isfloating || !is_scroller_layout(selmon)) return 0; - if (c && (!client_only_in_one_tag(c) || c->isglobal || c->isunglobal)) - return 0; - bool is_horizontal_layout = c->mon->pertag->ltidxs[c->mon->pertag->curtag]->id == SCROLLER ? true : false; Client *target_client = find_client_by_direction(c, arg, false, true); - if (target_client && (!client_only_in_one_tag(target_client) || - target_client->isglobal || target_client->isunglobal)) - return 0; - if (target_client) { stack_head = get_scroll_stack_head(target_client); } - if (c) { - source_stack_head = get_scroll_stack_head(c); - } + source_stack_head = get_scroll_stack_head(c); - if (stack_head == source_stack_head) { + if (source_stack_head == stack_head) { return 0; } @@ -1843,6 +1834,10 @@ int32_t scroller_stack(const Arg *arg) { if (!target_client || target_client->mon != c->mon) { return 0; + } else { + c->isglobal = target_client->isglobal = 0; + c->isunglobal = target_client->isglobal = 0; + c->tags = target_client->tags = get_tags_first_tag(target_client->tags); } exit_scroller_stack(c); From db30977196b91cfe2e5db8e9829faafe13417bd9 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 9 Mar 2026 11:15:13 +0800 Subject: [PATCH 087/328] opt: optimize popup unconstrain --- src/mango.c | 55 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/src/mango.c b/src/mango.c index 2af2720f..bdafcc58 100644 --- a/src/mango.c +++ b/src/mango.c @@ -2590,41 +2590,49 @@ void destroydecoration(struct wl_listener *listener, void *data) { wl_list_remove(&c->set_decoration_mode.link); } -static void popup_unconstrain(Popup *popup) { +static bool popup_unconstrain(Popup *popup) { struct wlr_xdg_popup *wlr_popup = popup->wlr_popup; Client *c = NULL; LayerSurface *l = NULL; int32_t type = -1; if (!wlr_popup || !wlr_popup->parent) { - return; + return false; } struct wlr_scene_node *parent_node = wlr_popup->parent->data; if (!parent_node) { wlr_log(WLR_ERROR, "Popup parent has no scene node"); - return; + return false; } type = toplevel_from_wlr_surface(wlr_popup->base->surface, &c, &l); if ((l && !l->mon) || (c && !c->mon)) { - wlr_xdg_popup_destroy(wlr_popup); - return; + return true; } - int parent_lx, parent_ly; - wlr_scene_node_coords(parent_node, &parent_lx, &parent_ly); - struct wlr_box usable = type == LayerShell ? l->mon->m : c->mon->w; - struct wlr_box constraint_box = { - .x = usable.x - parent_lx, - .y = usable.y - parent_ly, - .width = usable.width, - .height = usable.height, - }; + int lx, ly; + struct wlr_box constraint_box; + + if (type == LayerShell) { + wlr_scene_node_coords(&l->scene_layer->tree->node, &lx, &ly); + constraint_box.x = usable.x - lx; + constraint_box.y = usable.y - ly; + constraint_box.width = usable.width; + constraint_box.height = usable.height; + } else { + constraint_box.x = + usable.x - (c->geom.x + c->bw - c->surface.xdg->current.geometry.x); + constraint_box.y = + usable.y - (c->geom.y + c->bw - c->surface.xdg->current.geometry.y); + constraint_box.width = usable.width; + constraint_box.height = usable.height; + } wlr_xdg_popup_unconstrain_from_box(wlr_popup, &constraint_box); + return false; } static void destroypopup(struct wl_listener *listener, void *data) { @@ -2638,14 +2646,16 @@ static void commitpopup(struct wl_listener *listener, void *data) { Popup *popup = wl_container_of(listener, popup, commit); struct wlr_surface *surface = data; + bool should_destroy = false; struct wlr_xdg_popup *wlr_popup = wlr_xdg_popup_try_from_wlr_surface(surface); - if (!wlr_popup || !wlr_popup->base->initial_commit) - goto commitpopup_listen_free; + if (!wlr_popup->base->initial_commit) + return; if (!wlr_popup->parent || !wlr_popup->parent->data) { - goto commitpopup_listen_free; + should_destroy = true; + goto cleanup_popup_commit; } wlr_scene_node_raise_to_top(wlr_popup->parent->data); @@ -2655,16 +2665,21 @@ static void commitpopup(struct wl_listener *listener, void *data) { popup->wlr_popup = wlr_popup; - popup_unconstrain(popup); + should_destroy = popup_unconstrain(popup); + +cleanup_popup_commit: -commitpopup_listen_free: wl_list_remove(&popup->commit.link); popup->commit.notify = NULL; + + if (should_destroy) { + wlr_xdg_popup_destroy(wlr_popup); + } } static void repositionpopup(struct wl_listener *listener, void *data) { Popup *popup = wl_container_of(listener, popup, reposition); - popup_unconstrain(popup); + (void)popup_unconstrain(popup); } static void createpopup(struct wl_listener *listener, void *data) { From 1fc89d01eb5aa0f3784fb4006bf9af47826444d2 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 10 Mar 2026 11:18:43 +0800 Subject: [PATCH 088/328] bump versito to 0.12.6 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 85fe15b6..c538c927 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('mango', ['c', 'cpp'], - version : '0.12.5', + version : '0.12.6', ) subdir('protocols') From 5906d9621ecae9583848ef29b0feca13ce765b2c Mon Sep 17 00:00:00 2001 From: atheeq-rhxn Date: Thu, 12 Mar 2026 10:31:29 +0530 Subject: [PATCH 089/328] feat: add docs and sync with wiki & website --- .github/scripts/sync-wiki.py | 61 ++++++ .github/workflows/sync-website.yml | 42 ++++ .github/workflows/sync-wiki.yml | 40 ++++ docs/bindings/keys.md | 204 ++++++++++++++++++++ docs/bindings/meta.json | 4 + docs/bindings/mouse-gestures.md | 116 ++++++++++++ docs/configuration/basics.md | 87 +++++++++ docs/configuration/input.md | 150 +++++++++++++++ docs/configuration/meta.json | 4 + docs/configuration/miscellaneous.md | 51 +++++ docs/configuration/monitors.md | 274 +++++++++++++++++++++++++++ docs/configuration/xdg-portals.md | 76 ++++++++ docs/faq.md | 101 ++++++++++ docs/index.md | 42 ++++ docs/installation.md | 231 ++++++++++++++++++++++ docs/ipc.md | 154 +++++++++++++++ docs/meta.json | 14 ++ docs/quick-start.md | 88 +++++++++ docs/visuals/animations.md | 108 +++++++++++ docs/visuals/effects.md | 82 ++++++++ docs/visuals/meta.json | 4 + docs/visuals/status-bar.md | 141 ++++++++++++++ docs/visuals/theming.md | 59 ++++++ docs/window-management/layouts.md | 99 ++++++++++ docs/window-management/meta.json | 4 + docs/window-management/overview.md | 36 ++++ docs/window-management/rules.md | 249 ++++++++++++++++++++++++ docs/window-management/scratchpad.md | 73 +++++++ 28 files changed, 2594 insertions(+) create mode 100644 .github/scripts/sync-wiki.py create mode 100644 .github/workflows/sync-website.yml create mode 100644 .github/workflows/sync-wiki.yml create mode 100644 docs/bindings/keys.md create mode 100644 docs/bindings/meta.json create mode 100644 docs/bindings/mouse-gestures.md create mode 100644 docs/configuration/basics.md create mode 100644 docs/configuration/input.md create mode 100644 docs/configuration/meta.json create mode 100644 docs/configuration/miscellaneous.md create mode 100644 docs/configuration/monitors.md create mode 100644 docs/configuration/xdg-portals.md create mode 100644 docs/faq.md create mode 100644 docs/index.md create mode 100644 docs/installation.md create mode 100644 docs/ipc.md create mode 100644 docs/meta.json create mode 100644 docs/quick-start.md create mode 100644 docs/visuals/animations.md create mode 100644 docs/visuals/effects.md create mode 100644 docs/visuals/meta.json create mode 100644 docs/visuals/status-bar.md create mode 100644 docs/visuals/theming.md create mode 100644 docs/window-management/layouts.md create mode 100644 docs/window-management/meta.json create mode 100644 docs/window-management/overview.md create mode 100644 docs/window-management/rules.md create mode 100644 docs/window-management/scratchpad.md diff --git a/.github/scripts/sync-wiki.py b/.github/scripts/sync-wiki.py new file mode 100644 index 00000000..ebf543c7 --- /dev/null +++ b/.github/scripts/sync-wiki.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +import json +import re +from pathlib import Path + +DOCS_DIR = Path("docs") +WIKI_DIR = Path("wiki-temp") + +FRONTMATTER_RE = re.compile(r"\A---\s*\n.*?^---\s*\n", re.DOTALL | re.MULTILINE) +DOCS_LINK_RE = re.compile(r"\[([^\]]+)\]\(/docs/(?:[^/)]+/)*([^/)#]+)(#[^)]+)?\)") + + +def collect_all_files() -> list[tuple[Path, str]]: + files = [] + + def from_dir(directory: Path) -> list[Path]: + meta = directory / "meta.json" + if meta.exists(): + data = json.loads(meta.read_text()) + return [directory / f"{p}.md" for p in data.get("pages", []) if (directory / f"{p}.md").exists()] + return sorted(directory.glob("*.md")) + + for src in from_dir(DOCS_DIR): + files.append((src, "Home" if src.stem == "index" else src.stem)) + + for subdir in sorted(DOCS_DIR.iterdir()): + if subdir.is_dir(): + for src in from_dir(subdir): + files.append((src, src.stem)) + + return files + + +def main() -> None: + files = collect_all_files() + + contents = {src: src.read_text() for src, _ in files} + + for src, dest_name in files: + text = FRONTMATTER_RE.sub("", contents[src], count=1).lstrip("\n") + text = DOCS_LINK_RE.sub(lambda m: f"[{m.group(1)}]({m.group(2)}{m.group(3) or ''})", text) + (WIKI_DIR / f"{dest_name}.md").write_text(text) + + lines: list[str] = [] + current_section = None + for src, dest_name in files: + section = "General" if src.parent == DOCS_DIR else src.parent.name.replace("-", " ").title() + if section != current_section: + if current_section is not None: + lines.append("") + lines.append(f"## {section}\n") + current_section = section + if dest_name != "Home": + title = dest_name.replace("-", " ").replace("_", " ").title() + lines.append(f"- [[{dest_name}|{title}]]") + + (WIKI_DIR / "_Sidebar.md").write_text("\n".join(lines)) + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/sync-website.yml b/.github/workflows/sync-website.yml new file mode 100644 index 00000000..f641fb81 --- /dev/null +++ b/.github/workflows/sync-website.yml @@ -0,0 +1,42 @@ +name: Sync website + +on: + push: + branches: [main] + paths: + - docs/** + +concurrency: + group: sync-website + cancel-in-progress: true + +jobs: + sync-website: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + token: ${{ github.token }} + + - name: Checkout website + uses: actions/checkout@v4 + with: + repository: mangowm/mangowm.github.io + path: website + token: ${{ secrets.WEBSITE_SYNC_TOKEN }} + fetch-depth: 1 + + - name: Sync docs + run: | + rm -rf website/apps/web/content/docs + cp -r docs website/apps/web/content/docs + + - name: Commit and push + working-directory: website + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add apps/web/content/docs + git diff --staged --quiet || git commit -m "sync from mango @ ${{ github.sha }}" + git push diff --git a/.github/workflows/sync-wiki.yml b/.github/workflows/sync-wiki.yml new file mode 100644 index 00000000..ef30fe77 --- /dev/null +++ b/.github/workflows/sync-wiki.yml @@ -0,0 +1,40 @@ +name: Sync wiki + +on: + push: + branches: [main] + paths: + - docs/** + +concurrency: + group: sync-wiki + cancel-in-progress: true + +permissions: + contents: write + +jobs: + sync-wiki: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Clone wiki + run: | + git clone --depth 1 \ + https://x-access-token:${{ github.token }}@github.com/${{ github.repository }}.wiki.git \ + wiki-temp + + - name: Sync docs to wiki + run: | + find wiki-temp -not -path 'wiki-temp/.git*' -type f -delete + python3 .github/scripts/sync-wiki.py + + - name: Commit and push + working-directory: wiki-temp + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add -A + git diff --staged --quiet || git commit -m "sync from ${{ github.sha }}" + git push diff --git a/docs/bindings/keys.md b/docs/bindings/keys.md new file mode 100644 index 00000000..64ba64b7 --- /dev/null +++ b/docs/bindings/keys.md @@ -0,0 +1,204 @@ +--- +title: Key Bindings +description: Define keyboard shortcuts and modes. +--- + +## Syntax + +Key bindings follow this format: + +```ini +bind[flags]=MODIFIERS,KEY,COMMAND,PARAMETERS +``` + +- **Modifiers**: `SUPER`, `CTRL`, `ALT`, `SHIFT`, `NONE` (combine with `+`, e.g. `SUPER+CTRL+ALT`). +- **Key**: Key name (from `xev` or `wev`) or keycode (e.g., `code:24` for `q`). + +> **Info:** `bind` automatically converts keysym to keycode for comparison. This makes it compatible with all keyboard layouts, but the matching may not always be precise. If a key combination doesn't work on your keyboard layout, use a keycode instead (e.g., `code:24` instead of `q`). + +### Flags + +- `l`: Works even when screen is locked. +- `s`: Uses keysym instead of keycode to bind. +- `r`: Triggers on key release instead of press. +- `p`: Pass key event to client. + +**Examples:** + +```ini +bind=SUPER,Q,killclient +bindl=SUPER,L,spawn,swaylock + +# Using keycode instead of key name +bind=ALT,code:24,killclient + +# Combining keycodes for modifiers and keys +bind=code:64,code:24,killclient +bind=code:64+code:133,code:24,killclient + +# Bind with no modifier +bind=NONE,XF86MonBrightnessUp,spawn,brightnessctl set +5% + +# Bind a modifier key itself as the trigger key +bind=alt,shift_l,switch_keyboard_layout +``` + +## Key Modes (Submaps) + +You can divide key bindings into named modes. Rules: + +1. Set `keymode=` before a group of `bind` lines — those binds only apply in that mode. +2. If no `keymode` is set before a bind, it belongs to the `default` mode. +3. The special `common` keymode applies its binds **across all modes**. + +Use `setkeymode` to switch modes, and `mmsg -b` to query the current mode. + +```ini +# Binds in 'common' apply in every mode +keymode=common +bind=SUPER,r,reload_config + +# Default mode bindings +keymode=default +bind=ALT,Return,spawn,foot +bind=SUPER,F,setkeymode,resize + +# 'resize' mode bindings +keymode=resize +bind=NONE,Left,resizewin,-10,0 +bind=NONE,Right,resizewin,+10,0 +bind=NONE,Escape,setkeymode,default +``` + +### Single Modifier Key Binding + +When binding a modifier key itself, use `NONE` for press and the modifier name for release: + +```ini +# Trigger on press of Super key +bind=none,Super_L,spawn,rofi -show run + +# Trigger on release of Super key +bindr=Super,Super_L,spawn,rofi -show run +``` + +## Dispatchers List + +### Window Management + +| Command | Param | Description | +| :--- | :--- | :--- | +| `killclient` | - | Close the focused window. | +| `togglefloating` | - | Toggle floating state. | +| `togglefullscreen` | - | Toggle fullscreen. | +| `togglefakefullscreen` | - | Toggle "fake" fullscreen (remains constrained). | +| `togglemaximizescreen` | - | Maximize window (keep decoration/bar). | +| `toggleglobal` | - | Pin window to all tags. | +| `toggle_render_border` | - | Toggle border rendering. | +| `centerwin` | - | Center the floating window. | +| `minimized` | - | Minimize window to scratchpad. | +| `restore_minimized` | - | Restore window from scratchpad. | +| `toggle_scratchpad` | - | Toggle scratchpad. | +| `toggle_named_scratchpad` | `appid,title,cmd` | Toggle named scratchpad. Launches app if not running, otherwise shows/hides it. | + +### Focus & Movement + +| Command | Param | Description | +| :--- | :--- | :--- | +| `focusdir` | `left/right/up/down` | Focus window in direction. | +| `focusstack` | `next/prev` | Cycle focus within the stack. | +| `focuslast` | - | Focus the previously active window. | +| `exchange_client` | `left/right/up/down` | Swap window with neighbor in direction. | +| `exchange_stack_client` | `next/prev` | Exchange window position in stack. | +| `zoom` | - | Swap focused window with Master. | + +### Tags & Monitors + +| Command | Param | Description | +| :--- | :--- | :--- | +| `view` | `-1/0/1-9` or `mask [,synctag]` | View tag. `-1` = previous tagset, `0` = all tags, `1-9` = specific tag, mask e.g. `1\|3\|5`. Optional `synctag` (0/1) syncs the action to all monitors. | +| `viewtoleft` | `[synctag]` | View previous tag. Optional `synctag` (0/1) syncs to all monitors. | +| `viewtoright` | `[synctag]` | View next tag. Optional `synctag` (0/1) syncs to all monitors. | +| `viewtoleft_have_client` | `[synctag]` | View left tag and focus client if present. Optional `synctag` (0/1). | +| `viewtoright_have_client` | `[synctag]` | View right tag and focus client if present. Optional `synctag` (0/1). | +| `viewcrossmon` | `tag,monitor_spec` | View specified tag on specified monitor. | +| `tag` | `1-9 [,synctag]` | Move window to tag. Optional `synctag` (0/1) syncs to all monitors. | +| `tagsilent` | `1-9` | Move window to tag without focusing it. | +| `tagtoleft` | `[synctag]` | Move window to left tag. Optional `synctag` (0/1). | +| `tagtoright` | `[synctag]` | Move window to right tag. Optional `synctag` (0/1). | +| `tagcrossmon` | `tag,monitor_spec` | Move window to specified tag on specified monitor. | +| `toggletag` | `0-9` | Toggle tag on window (0 means all tags). | +| `toggleview` | `1-9` | Toggle tag view. | +| `comboview` | `1-9` | View multi tags pressed simultaneously. | +| `focusmon` | `left/right/up/down/monitor_spec` | Focus monitor by direction or [monitor spec](/docs/configuration/monitors#monitor-spec-format). | +| `tagmon` | `left/right/up/down/monitor_spec,[keeptag]` | Move window to monitor by direction or [monitor spec](/docs/configuration/monitors#monitor-spec-format). `keeptag` is 0 or 1. | + +### Layouts + +| Command | Param | Description | +| :--- | :--- | :--- | +| `setlayout` | `name` | Switch to layout (e.g., `scroller`, `tile`). | +| `switch_layout` | - | Cycle through available layouts. | +| `incnmaster` | `+1/-1` | Increase/Decrease number of master windows. | +| `setmfact` | `+0.05` | Increase/Decrease master area size. | +| `set_proportion` | `float` | Set scroller window proportion (0.0–1.0). | +| `switch_proportion_preset` | - | Cycle proportion presets of scroller window. | +| `scroller_stack` | `left/right/up/down` | Move window inside/outside scroller stack by direction. | +| `incgaps` | `+/-value` | Adjust gap size. | +| `togglegaps` | - | Toggle gaps. | + +### System + +| Command | Param | Description | +| :--- | :--- | :--- | +| `spawn` | `cmd` | Execute a command. | +| `spawn_shell` | `cmd` | Execute shell command (supports pipes `\|`). | +| `spawn_on_empty` | `cmd,tagnumber` | Open command on empty tag. | +| `reload_config` | - | Hot-reload configuration. | +| `quit` | - | Exit mangowm. | +| `toggleoverview` | - | Toggle overview mode. | +| `create_virtual_output` | - | Create a headless monitor (for VNC/Sunshine). | +| `destroy_all_virtual_output` | - | Destroy all virtual monitors. | +| `toggleoverlay` | - | Toggle overlay state for the focused window. | +| `toggle_trackpad_enable` | - | Toggle trackpad enable. | +| `setkeymode` | `mode` | Set keymode. | +| `switch_keyboard_layout` | `[index]` | Switch keyboard layout. Optional index (0, 1, 2...) to switch to specific layout. | +| `setoption` | `key,value` | Set config option temporarily. | +| `disable_monitor` | `monitor_spec` | Shutdown monitor. Accepts a [monitor spec](/docs/configuration/monitors#monitor-spec-format). | +| `enable_monitor` | `monitor_spec` | Power on monitor. Accepts a [monitor spec](/docs/configuration/monitors#monitor-spec-format). | +| `toggle_monitor` | `monitor_spec` | Toggle monitor power. Accepts a [monitor spec](/docs/configuration/monitors#monitor-spec-format). | + +### Media Controls + +> **Warning:** Some keyboards don't send standard media keys. Run `wev` and press your key to check the exact key name. + +#### Brightness + +Requires: `brightnessctl` + +```ini +bind=NONE,XF86MonBrightnessUp,spawn,brightnessctl s +2% +bind=SHIFT,XF86MonBrightnessUp,spawn,brightnessctl s 100% +bind=NONE,XF86MonBrightnessDown,spawn,brightnessctl s 2%- +bind=SHIFT,XF86MonBrightnessDown,spawn,brightnessctl s 1% +``` + +#### Volume + +Requires: `wpctl` (WirePlumber) + +```ini +bind=NONE,XF86AudioRaiseVolume,spawn,wpctl set-volume @DEFAULT_SINK@ 5%+ +bind=NONE,XF86AudioLowerVolume,spawn,wpctl set-volume @DEFAULT_SINK@ 5%- +bind=NONE,XF86AudioMute,spawn,wpctl set-mute @DEFAULT_SINK@ toggle +bind=SHIFT,XF86AudioMute,spawn,wpctl set-mute @DEFAULT_SOURCE@ toggle +``` + +### Floating Window Movement + +| Command | Param | Description | +| :--- | :--- | :--- | +| `smartmovewin` | `left/right/up/down` | Move floating window by snap distance. | +| `smartresizewin` | `left/right/up/down` | Resize floating window by snap distance. | +| `movewin` | `(x,y)` | Move floating window. | +| `resizewin` | `(width,height)` | Resize window. | \ No newline at end of file diff --git a/docs/bindings/meta.json b/docs/bindings/meta.json new file mode 100644 index 00000000..f1b629b6 --- /dev/null +++ b/docs/bindings/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Bindings & Input", + "pages": ["keys", "mouse-gestures"] +} diff --git a/docs/bindings/mouse-gestures.md b/docs/bindings/mouse-gestures.md new file mode 100644 index 00000000..c4a36889 --- /dev/null +++ b/docs/bindings/mouse-gestures.md @@ -0,0 +1,116 @@ +--- +title: Mouse & Gestures +description: Configure mouse buttons, scrolling, gestures, and lid switches. +--- + +## Mouse Bindings + +Assign actions to mouse button presses with optional modifier keys. + +### Syntax + +```ini +mousebind=MODIFIERS,BUTTON,COMMAND,PARAMETERS +``` + +- **Modifiers**: `SUPER`, `CTRL`, `ALT`, `SHIFT`, `NONE`. Combine with `+` (e.g., `SUPER+CTRL`) +- **Buttons**: `btn_left`, `btn_right`, `btn_middle`, `btn_side`, `btn_extra`, `btn_forward`, `btn_back`, `btn_task` + +> **Warning:** When modifiers are set to `NONE`, only `btn_middle` works in normal mode. `btn_left` and `btn_right` only work in overview mode. + +### Examples + +```ini +# Window manipulation +mousebind=SUPER,btn_left,moveresize,curmove +mousebind=SUPER,btn_right,moveresize,curresize +mousebind=SUPER+CTRL,btn_right,killclient + +# Overview mode (requires NONE modifier) +mousebind=NONE,btn_left,toggleoverview,-1 +mousebind=NONE,btn_right,killclient,0 +mousebind=NONE,btn_middle,togglemaximizescreen,0 +``` + +--- + +## Axis Bindings + +Map scroll wheel movements to actions for workspace and window navigation. + +### Syntax + +```ini +axisbind=MODIFIERS,DIRECTION,COMMAND,PARAMETERS +``` + +- **Direction**: `UP`, `DOWN`, `LEFT`, `RIGHT` + +### Examples + +```ini +axisbind=SUPER,UP,viewtoleft_have_client +axisbind=SUPER,DOWN,viewtoright_have_client +``` + +--- + +## Gesture Bindings + +Enable touchpad swipe gestures for navigation and window management. + +### Syntax + +```ini +gesturebind=MODIFIERS,DIRECTION,FINGERS,COMMAND,PARAMETERS +``` + +- **Direction**: `up`, `down`, `left`, `right` +- **Fingers**: `3` or `4` + +> **Info:** Gestures require proper touchpad configuration. See [Input Devices](/docs/configuration/input) for touchpad settings like `tap_to_click` and `disable_while_typing`. + +### Examples + +```ini +# 3-finger: Window focus +gesturebind=none,left,3,focusdir,left +gesturebind=none,right,3,focusdir,right +gesturebind=none,up,3,focusdir,up +gesturebind=none,down,3,focusdir,down + +# 4-finger: Workspace navigation +gesturebind=none,left,4,viewtoleft_have_client +gesturebind=none,right,4,viewtoright_have_client +gesturebind=none,up,4,toggleoverview +gesturebind=none,down,4,toggleoverview +``` + +--- + +## Switch Bindings + +Trigger actions on hardware events like laptop lid open/close. + +### Syntax + +```ini +switchbind=FOLD_STATE,COMMAND,PARAMETERS +``` + +- **Fold State**: `fold` (lid closed), `unfold` (lid opened) + +> **Warning:** Disable system lid handling in `/etc/systemd/logind.conf`: +> +> ```ini +> HandleLidSwitch=ignore +> HandleLidSwitchExternalPower=ignore +> HandleLidSwitchDocked=ignore +> ``` + +### Examples + +```ini +switchbind=fold,spawn,swaylock -f -c 000000 +switchbind=unfold,spawn,wlr-dpms on +``` diff --git a/docs/configuration/basics.md b/docs/configuration/basics.md new file mode 100644 index 00000000..dbbe45f3 --- /dev/null +++ b/docs/configuration/basics.md @@ -0,0 +1,87 @@ +--- +title: Basic Configuration +description: Learn how to configure mangowm files, environment variables, and autostart scripts. +--- + +## Configuration File + +mangowm uses a simple configuration file format. By default, it looks for a configuration file in `~/.config/mango/`. + +1. **Locate Default Config** + + A fallback configuration is provided at `/etc/mango/config.conf`. You can use this as a reference. + +2. **Create User Config** + + Copy the default config to your local config directory to start customizing. + + ```bash + mkdir -p ~/.config/mango + cp /etc/mango/config.conf ~/.config/mango/config.conf + ``` + +3. **Launch with Custom Config (Optional)** + + If you prefer to keep your config elsewhere, you can launch mango with the `-c` flag. + + ```bash + mango -c /path/to/your_config.conf + ``` + +### Sub-Configuration + +To keep your configuration organized, you can split it into multiple files and include them using the `source` keyword. + +```ini +# Import keybindings from a separate file +source=~/.config/mango/bind.conf + +# Relative paths work too +source=./theme.conf + +# Optional: ignore if file doesn't exist (useful for shared configs) +source-optional=~/.config/mango/optional.conf +``` + +### Validate Configuration + +You can check your configuration for errors without starting mangowm: + +```bash +mango -p /path/to/config.conf +``` + +Use with `source-optional` for shared configs across different setups. + +## Environment Variables + +You can define environment variables directly within your config file. These are set before the window manager fully initializes. + +> **Warning:** Environment variables defined here will be **reset** every time you reload the configuration. + +```ini +env=GTK_THEME,Adwaita:dark +env=XCURSOR_SIZE,24 +``` + +## Autostart + +mangowm can automatically run commands or scripts upon startup. There are two modes for execution: + +| Command | Behavior | Usage Case | +| :--- | :--- | :--- | +| `exec-once` | Runs **only once** when mangowm starts. | Status bars, Wallpapers, Notification daemons | +| `exec` | Runs **every time** the config is reloaded. | Scripts that need to refresh settings | + +### Example Setup + +```ini +# Start the status bar once +exec-once=waybar + +# Set wallpaper +exec-once=swaybg -i ~/.config/mango/wallpaper/room.png + +# Reload a custom script on config change +exec=bash ~/.config/mango/reload-settings.sh +``` diff --git a/docs/configuration/input.md b/docs/configuration/input.md new file mode 100644 index 00000000..3ebc0f23 --- /dev/null +++ b/docs/configuration/input.md @@ -0,0 +1,150 @@ +--- +title: Input Devices +description: Configure keyboard layouts, mouse sensitivity, and touchpad gestures. +--- + +## Device Configuration + +mangowm provides granular control over different input devices. + +### Keyboard Settings + +Control key repeat rates and layout rules. + +| Setting | Type | Default | Description | +| :--- | :--- | :--- | :--- | +| `repeat_rate` | `int` | `25` | How many times a key repeats per second. | +| `repeat_delay` | `int` | `600` | Delay (ms) before a held key starts repeating. | +| `numlockon` | `0` or `1` | `0` | Enable NumLock on startup. | +| `xkb_rules_rules` | `string` | - | XKB rules file (e.g., `evdev`, `base`). Usually auto-detected. | +| `xkb_rules_model` | `string` | - | Keyboard model (e.g., `pc104`, `macbook`). | +| `xkb_rules_layout` | `string` | - | Keyboard layout code (e.g., `us`, `de`, `us,de`). | +| `xkb_rules_variant` | `string` | - | Layout variant (e.g., `dvorak`, `colemak`, `intl`). | +| `xkb_rules_options` | `string` | - | XKB options (e.g., `caps:escape`, `ctrl:nocaps`). | + +**Example:** + +```ini +repeat_rate=40 +repeat_delay=300 +numlockon=1 +xkb_rules_layout=us,de +xkb_rules_variant=dvorak +xkb_rules_options=caps:escape,ctrl:nocaps +``` + +--- + +### Trackpad Settings + +Specific settings for laptop touchpads. Some settings may require a relogin to take effect. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `disable_trackpad` | `0` | Set to `1` to disable the trackpad entirely. | +| `tap_to_click` | `1` | Tap to trigger a left click. | +| `tap_and_drag` | `1` | Tap and hold to drag items. | +| `trackpad_natural_scrolling` | `0` | Invert scrolling direction (natural scrolling). | +| `scroll_method` | `1` | `1` (Two-finger), `2` (Edge), `4` (Button). | +| `click_method` | `1` | `1` (Button areas), `2` (Clickfinger). | +| `drag_lock` | `1` | Lock dragging after tapping. | +| `disable_while_typing` | `1` | Disable trackpad while typing. | +| `left_handed` | `0` | Swap left/right buttons. | +| `middle_button_emulation` | `0` | Emulate middle button. | +| `swipe_min_threshold` | `1` | Minimum swipe threshold. | + +--- + +**Detailed descriptions:** + +- `scroll_method` values: + - `0` — Never send scroll events (no scrolling). + - `1` — Two-finger scrolling: send scroll events when two fingers are logically down on the device. + - `2` — Edge scrolling: send scroll events when a finger moves along the bottom or right edge. + - `4` — Button scrolling: send scroll events when a button is held and the device moves along a scroll axis. + +- `click_method` values: + - `0` — No software click emulation. + - `1` — Button areas: use software-defined areas on the touchpad to generate button events. + - `2` — Clickfinger: the number of fingers determines which button is pressed. + +- `accel_profile` values: + - `0` — No acceleration. + - `1` — Flat: no dynamic acceleration. Pointer speed = original input speed × (1 + `accel_speed`). + - `2` — Adaptive: slow movement results in less acceleration, fast movement results in more. + +- `button_map` values: + - `0` — 1/2/3 finger tap maps to left / right / middle. + - `1` — 1/2/3 finger tap maps to left / middle / right. + +- `send_events_mode` values: + - `0` — Send events from this device normally. + - `1` — Do not send events from this device. + - `2` — Disable this device when an external pointer device is plugged in. + +--- + +### Mouse Settings + +Configuration for external mice. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `mouse_natural_scrolling` | `0` | Invert scrolling direction. | +| `accel_profile` | `2` | `0` (None), `1` (Flat), `2` (Adaptive). | +| `accel_speed` | `0.0` | Speed adjustment (-1.0 to 1.0). | +| `left_handed` | `0` | Swap left and right buttons. | +| `middle_button_emulation` | `0` | Emulate middle button. | +| `swipe_min_threshold` | `1` | Minimum swipe threshold. | +| `send_events_mode` | `0` | `0` (Enabled), `1` (Disabled), `2` (Disabled on external mouse). | +| `button_map` | `0` | `0` (Left/right/middle), `1` (Left/middle/right). | + +--- + +--- + +## Keyboard Layout Switching + +To bind multiple layouts and toggle between them, define the layouts in `xkb_rules_layout` and use `xkb_rules_options` to set a toggle key combination. Then bind `switch_keyboard_layout` to trigger a switch. + +```ini +# Define two layouts: US QWERTY and US Dvorak +xkb_rules_layout=us,us +xkb_rules_variant=,dvorak +xkb_rules_options=grp:lalt_lshift_toggle +``` + +Or bind it manually to a key: + +```ini +# Bind Alt+Shift_L to cycle keyboard layout +bind=alt,shift_l,switch_keyboard_layout +``` + +Use `mmsg -g -k` to query the current keyboard layout at any time. + +--- + +## Input Method Editor (IME) + +To use Fcitx5 or IBus, set these environment variables in your config file. + +> **Info:** These settings require a restart of the window manager to take effect. + +**For Fcitx5:** + +```ini +env=GTK_IM_MODULE,fcitx +env=QT_IM_MODULE,fcitx +env=SDL_IM_MODULE,fcitx +env=XMODIFIERS,@im=fcitx +env=GLFW_IM_MODULE,ibus +``` + +**For IBus:** + +```ini +env=GTK_IM_MODULE,ibus +env=QT_IM_MODULE,ibus +env=XMODIFIERS,@im=ibus +``` diff --git a/docs/configuration/meta.json b/docs/configuration/meta.json new file mode 100644 index 00000000..bc209b4e --- /dev/null +++ b/docs/configuration/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Configuration", + "pages": ["basics", "monitors", "input", "xdg-portals", "miscellaneous"] +} diff --git a/docs/configuration/miscellaneous.md b/docs/configuration/miscellaneous.md new file mode 100644 index 00000000..9e06781d --- /dev/null +++ b/docs/configuration/miscellaneous.md @@ -0,0 +1,51 @@ +--- +title: Miscellaneous +description: Advanced settings for XWayland, focus behavior, and system integration. +--- + +## System & Hardware + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `xwayland_persistence` | `1` | Keep XWayland running even when no X11 apps are open (reduces startup lag). | +| `syncobj_enable` | `0` | Enable `drm_syncobj` timeline support (helps with gaming stutter/lag). **Requires restart.** | +| `allow_lock_transparent` | `0` | Allow the lock screen to be transparent. | +| `allow_shortcuts_inhibit` | `1` | Allow shortcuts to be inhibited by clients. | +| `vrr` | - | Set via [monitor rule](/docs/configuration/monitors#monitor-rules). | + +## Focus & Input + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `focus_on_activate` | `1` | Automatically focus windows when they request activation. | +| `sloppyfocus` | `1` | Focus follows the mouse cursor. | +| `warpcursor` | `1` | Warp the cursor to the center of the window when focus changes via keyboard. | +| `cursor_hide_timeout` | `0` | Hide the cursor after `N` seconds of inactivity (`0` to disable). | +| `drag_tile_to_tile` | `0` | Allow dragging a tiled window onto another to swap their positions. | +| `drag_corner` | `3` | Corner for drag-to-tile detection (0: none, 1–3: corners, 4: auto-detect). | +| `drag_warp_cursor` | `1` | Warp cursor when dragging windows to tile. | +| `axis_bind_apply_timeout` | `100` | Timeout (ms) for detecting consecutive scroll events for axis bindings. | +| `axis_scroll_factor` | `1.0` | Scroll factor for axis scroll speed (0.1–10.0). | + +## Multi-Monitor & Tags + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `focus_cross_monitor` | `0` | Allow directional focus to cross monitor boundaries. | +| `exchange_cross_monitor` | `0` | Allow exchanging clients across monitor boundaries. | +| `focus_cross_tag` | `0` | Allow directional focus to cross into other tags. | +| `view_current_to_back` | `0` | Toggling the current tag switches back to the previously viewed tag. | +| `scratchpad_cross_monitor` | `0` | Share the scratchpad pool across all monitors. | +| `single_scratchpad` | `1` | Only allow one scratchpad (named or standard) to be visible at a time. | +| `circle_layout` | - | A comma-separated list of layouts `switch_layout` cycles through. | + +## Window Behavior + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `enable_floating_snap` | `0` | Snap floating windows to edges or other windows. | +| `snap_distance` | `30` | Max distance (pixels) to trigger floating snap. | +| `no_border_when_single` | `0` | Remove window borders when only one window is visible on the tag. | +| `idleinhibit_ignore_visible` | `0` | Allow invisible clients (e.g., background audio players) to inhibit idle. | +| `drag_tile_refresh_interval` | `8.0` | Interval (1.0–16.0) to refresh tiled window resize during drag. Too small may cause application lag. | +| `drag_floating_refresh_interval` | `8.0` | Interval (1.0–16.0) to refresh floating window resize during drag. Too small may cause application lag. | \ No newline at end of file diff --git a/docs/configuration/monitors.md b/docs/configuration/monitors.md new file mode 100644 index 00000000..92c2082c --- /dev/null +++ b/docs/configuration/monitors.md @@ -0,0 +1,274 @@ +--- +title: Monitors +description: Manage display outputs, resolution, scaling, and tearing. +--- + +## Monitor Rules + +You can configure each display output individually using the `monitorrule` keyword. + +**Syntax:** + +```ini +monitorrule=name:Values,Parameter:Values,Parameter:Values +``` + +> **Info:** If any of the matching fields (`name`, `make`, `model`, `serial`) are set, **all** of the set ones must match to be considered a match. Use `wlr-randr` to get your monitor's name, make, model, and serial. + +### Parameters + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `name` | string | Any | Match by monitor name (supports regex) | +| `make` | string | Any | Match by monitor manufacturer | +| `model` | string | Any | Match by monitor model | +| `serial` | string | Any | Match by monitor serial number | +| `width` | integer | 0-9999 | Monitor width | +| `height` | integer | 0-9999 | Monitor height | +| `refresh` | float | 0.001-9999.0 | Monitor refresh rate | +| `x` | integer | 0-99999 | X position | +| `y` | integer | 0-99999 | Y position | +| `scale` | float | 0.01-100.0 | Monitor scale | +| `vrr` | integer | 0, 1 | Enable variable refresh rate | +| `rr` | integer | 0-7 | Monitor transform | +| `custom` | integer | 0, 1 | Enable custom mode (not supported on all displays — may cause black screen) | + +### Transform Values + +| Value | Rotation | +| :--- | :--- | +| `0` | No transform | +| `1` | 90° counter-clockwise | +| `2` | 180° counter-clockwise | +| `3` | 270° counter-clockwise | +| `4` | 180° vertical flip | +| `5` | Flip + 90° counter-clockwise | +| `6` | Flip + 180° counter-clockwise | +| `7` | Flip + 270° counter-clockwise | + +> **Critical:** If you use XWayland applications, **never use negative coordinates** for your monitor positions. This is a known XWayland bug that causes click events to malfunction. Always arrange your monitors starting from `0,0` and extend into positive coordinates. + +### Examples + +```ini +# Laptop display: 1080p, 60Hz, positioned at origin +monitorrule=name:eDP-1,width:1920,height:1080,refresh:60,x:0,y:10 + +# Match by make and model instead of name +monitorrule=make:Chimei Innolux Corporation,model:0x15F5,width:1920,height:1080,refresh:60,x:0,y:0 + +# Virtual monitor with pattern matching +monitorrule=name:HEADLESS-.*,width:1920,height:1080,refresh:60,x:1926,y:0,scale:1,rr:0,vrr:0 +``` + +--- + +## Monitor Spec Format + +Several commands (`focusmon`, `tagmon`, `disable_monitor`, `enable_monitor`, `toggle_monitor`, `viewcrossmon`, `tagcrossmon`) accept a **monitor_spec** string to identify a monitor. + +**Format:** + +```text +name:xxx&&make:xxx&&model:xxx&&serial:xxx +``` + +- Any field can be omitted and there is no order requirement. +- If all fields are omitted, the string is treated as the monitor name directly (e.g., `eDP-1`). +- Use `wlr-randr` to find your monitor's name, make, model, and serial. + +**Examples:** + +```bash +# By name (shorthand) +mmsg -d toggle_monitor,eDP-1 + +# By make and model +mmsg -d toggle_monitor,make:Chimei Innolux Corporation&&model:0x15F5 + +# By serial +mmsg -d toggle_monitor,serial:12345678 +``` + +--- + +## Tearing (Game Mode) + +Tearing allows games to bypass the compositor's VSync for lower latency. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `allow_tearing` | `0` | Global tearing control: `0` (Disable), `1` (Enable), `2` (Fullscreen only). | + +### Configuration + +**Enable Globally:** + +```ini +allow_tearing=1 +``` + +**Enable per Window:** + +Use a window rule to force tearing for specific games. + +```ini +windowrule=force_tearing:1,title:vkcube +``` + +### Tearing Behavior Matrix + +| `force_tearing` \ `allow_tearing` | DISABLED (0) | ENABLED (1) | FULLSCREEN_ONLY (2) | +| :--- | :--- | :--- | :--- | +| **UNSPECIFIED** (0) | Not Allowed | Follows tearing_hint | Only fullscreen follows tearing_hint | +| **ENABLED** (1) | Not Allowed | Allowed | Only fullscreen allowed | +| **DISABLED** (2) | Not Allowed | Not Allowed | Not Allowed | + +### Graphics Card Compatibility + +> **Warning:** Some graphics cards require setting the `WLR_DRM_NO_ATOMIC` environment variable before mango starts to successfully enable tearing. + +Add this to `/etc/environment` and reboot: + +```bash +WLR_DRM_NO_ATOMIC=1 +``` + +Or run mango with the environment variable: + +```bash +WLR_DRM_NO_ATOMIC=1 mango +``` + +--- + +## GPU Compatibility + +If mango cannot display correctly or shows a black screen, try selecting a specific GPU: + +```bash +# Use a single GPU +WLR_DRM_DEVICES=/dev/dri/card1 mango + +# Use multiple GPUs +WLR_DRM_DEVICES=/dev/dri/card0:/dev/dri/card1 mango +``` + +Some GPUs have compatibility issues with `syncobj_enable=1` — it may crash apps like `kitty` that use syncobj. Set `WLR_DRM_NO_ATOMIC=1` in `/etc/environment` and reboot to resolve this. + +--- + +## Power Management + +You can control monitor power using the `mmsg` IPC tool. + +```bash +# Turn off +mmsg -d disable_monitor,eDP-1 + +# Turn on +mmsg -d enable_monitor,eDP-1 + +# Toggle +mmsg -d toggle_monitor,eDP-1 +``` + +You can also use `wlr-randr` for monitor management: + +```bash +# Turn off monitor +wlr-randr --output eDP-1 --off + +# Turn on monitor +wlr-randr --output eDP-1 --on + +# Show all monitors +wlr-randr +``` + +--- + +## Screen Scale + +### Without Global Scale (Recommended) + +- If you do not use XWayland apps, you can use monitor rules or `wlr-randr` to set a global monitor scale. +- If you are using XWayland apps, it is not recommended to set a global monitor scale. + +You can set scale like this, for example with a 1.4 factor. + +**Dependencies:** + +```bash +yay -S xorg-xrdb +yay -S xwayland-satellite +``` + +**In config file:** + +```ini +env=QT_AUTO_SCREEN_SCALE_FACTOR,1 +env=QT_WAYLAND_FORCE_DPI,140 +``` + +**In autostart:** + +```bash +echo "Xft.dpi: 140" | xrdb -merge +gsettings set org.gnome.desktop.interface text-scaling-factor 1.4 +``` + +**Edit autostart for XWayland:** + +```bash +# Start xwayland +/usr/sbin/xwayland-satellite :11 & +# Apply scale 1.4 for xwayland +sleep 0.5s && echo "Xft.dpi: 140" | xrdb -merge +``` + +### Using xwayland-satellite to Prevent Blurry XWayland Apps + +If you use fractional scaling, you can use `xwayland-satellite` to automatically scale XWayland apps to prevent blurriness, for example with a scale of 1.4. + +**Dependencies:** + +```bash +yay -S xwayland-satellite +``` + +**In config file:** + +```ini +env=DISPLAY,:2 +exec=xwayland-satellite :2 +monitorrule=name:eDP-1,width:1920,height:1080,refresh:60,x:0,y:0,scale:1.4,vrr:0,rr:0 +``` + +> **Warning:** Use a `DISPLAY` value other than `:1` to avoid conflicting with mangowm. + +--- + +## Virtual Monitors + +You can create and manage virtual displays through IPC commands: + +```bash +# Create virtual output +mmsg -d create_virtual_output + +# Destroy all virtual outputs +mmsg -d destroy_all_virtual_output +``` + +You can configure virtual monitors using `wlr-randr`: + +```bash +# Show all monitors +wlr-randr + +# Configure virtual monitor +wlr-randr --output HEADLESS-1 --pos 1921,0 --scale 1 --custom-mode 1920x1080@60Hz +``` + +Virtual monitors can be used for screen sharing with tools like [Sunshine](https://github.com/LizardByte/Sunshine) and [Moonlight](https://github.com/moonlight-stream/moonlight-android), allowing other devices to act as extended monitors. \ No newline at end of file diff --git a/docs/configuration/xdg-portals.md b/docs/configuration/xdg-portals.md new file mode 100644 index 00000000..27819ad8 --- /dev/null +++ b/docs/configuration/xdg-portals.md @@ -0,0 +1,76 @@ +--- +title: XDG Portals +description: Set up screen sharing, clipboard, keyring, and file pickers using XDG portals. +--- + +## Portal Configuration + +You can customize portal settings via the following paths: + +- **User Configuration (Priority):** `~/.config/xdg-desktop-portal/mango-portals.conf` +- **System Fallback:** `/usr/share/xdg-desktop-portal/mango-portals.conf` + +> **Warning:** If you previously added `dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP=wlroots` to your config, remove it. Mango now handles this automatically. + +## Screen Sharing + +To enable screen sharing (OBS, Discord, WebRTC), you need `xdg-desktop-portal-wlr`. + +1. **Install Dependencies** + + `pipewire`, `pipewire-pulse`, `xdg-desktop-portal-wlr` + +2. **Optional: Add to autostart** + + In some situations the portal may not start automatically. You can add this to your autostart script to ensure it launches: + + ```bash + /usr/lib/xdg-desktop-portal-wlr & + ``` + +3. **Restart your computer** to apply changes. + +### Known Issues + +- **Window screen sharing:** Some applications may have issues sharing individual windows. See [#184](https://github.com/mangowm/mango/pull/184) for workarounds. + +- **Screen recording lag:** If you experience stuttering during screen recording, see [xdg-desktop-portal-wlr#351](https://github.com/emersion/xdg-desktop-portal-wlr/issues/351). + +## Clipboard Manager + +Use `cliphist` to manage clipboard history. + +**Dependencies:** `wl-clipboard`, `cliphist`, `wl-clip-persist` + +**Autostart Config:** + +```bash +# Keep clipboard content after app closes +wl-clip-persist --clipboard regular --reconnect-tries 0 & + +# Watch clipboard and store history +wl-paste --type text --watch cliphist store & +``` + +## GNOME Keyring + +If you need to store passwords or secrets (e.g., for VS Code or Minecraft launchers), install `gnome-keyring`. + +**Configuration:** + +Add the following to `~/.config/xdg-desktop-portal/mango-portals.conf`: + +```ini +[preferred] +default=gtk +org.freedesktop.impl.portal.ScreenCast=wlr +org.freedesktop.impl.portal.Screenshot=wlr +org.freedesktop.impl.portal.Secret=gnome-keyring +org.freedesktop.impl.portal.Inhibit=none +``` + +## File Picker (File Selector) + +**Dependencies:** `xdg-desktop-portal`, `xdg-desktop-portal-gtk` + +Reboot your computer once to apply. \ No newline at end of file diff --git a/docs/faq.md b/docs/faq.md new file mode 100644 index 00000000..13c6391b --- /dev/null +++ b/docs/faq.md @@ -0,0 +1,101 @@ +--- +title: FAQ +description: Frequently asked questions and troubleshooting. +--- + +### How do I arrange tiled windows with my mouse? + +You can enable the `drag_tile_to_tile` option in your config. This allows you to drag a tiled window onto another to swap them. + +```ini +drag_tile_to_tile=1 +``` + +--- + +### Why is my background blurry or why does blur look wrong? + +Blur applies to the transparent areas of windows. To disable it entirely, set `blur=0`. + +If you are experiencing **performance issues with blur**, make sure `blur_optimized=1` (the default). This caches the wallpaper as the blur background, which is much cheaper on the GPU: + +```ini +blur_optimized=1 +``` + +--- + +### Blur shows my wallpaper instead of the real background content + +This is expected behavior when `blur_optimized=1` (the default). The optimizer caches the wallpaper to reduce GPU load — windows will blur against the wallpaper rather than the actual content stacked beneath them. + +If you want blur to composite against the true background (i.e., show whatever is actually behind the window), set: + +```ini +blur_optimized=0 +``` + +> **Warning:** Disabling `blur_optimized` significantly increases GPU consumption and may cause rendering lag, especially on lower-end hardware. + +--- + +### My games are lagging or stuttering + +Try enabling **SyncObj** timeline support and **Adaptive Sync** (VRR) if your monitor supports it. + +```ini +syncobj_enable=1 +adaptive_sync=1 +``` + +--- + +### My games have high input latency + +You can enable **Tearing** (similar to VSync off). + +First, enable it globally: + +```ini +allow_tearing=1 +``` + +Then force it for your specific game: + +```ini +windowrule=force_tearing:1,title:Counter-Strike 2 +``` + +> **Warning:** Some graphics cards require setting `WLR_DRM_NO_ATOMIC=1` before mango starts for tearing to work. Add it to `/etc/environment` and reboot, or launch mango with `WLR_DRM_NO_ATOMIC=1 mango`. See [Monitors — Tearing](/docs/configuration/monitors#tearing-game-mode) for details. + +--- + +### How do I use pipes `|` in spawn commands? + +The standard `spawn` command does not support shell pipes directly. You must use `spawn_shell` instead. + +```ini +bind=SUPER,P,spawn_shell,echo "hello" | rofi -dmenu +``` + +--- + +### Certain key combinations do not work on my keyboard layout. + +`bind` automatically converts keysym to keycode, which is compatible with most layouts but can sometimes be imprecise. If a key combination is not triggering, use the **keycode** directly instead of the key name. + +Run `wev` and press the key to find its keycode, then use it in your bind: + +```ini +# Instead of: +bind=ALT,q,killclient + +# Use the keycode (e.g., code:24 = q on most layouts): +bind=ALT,code:24,killclient +``` + +You can also use `binds` (the `s` flag) to match by keysym instead of keycode: + +```ini +binds=ALT,q,killclient +``` diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..f4848cf2 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,42 @@ +--- +title: Introduction +description: A lightweight and feature-rich Wayland compositor based on dwl. +--- + + +**mango** is a Wayland compositor based on [dwl](https://codeberg.org/dwl/dwl/). It aims to be as lightweight as `dwl` and can be built completely within a few seconds, without compromising on functionality. + +> **Philosophy:** **Lightweight & Fast**: mango is designed to be minimal yet functional. It compiles in seconds and offers a robust set of features out of the box. + +## Feature Highlights + +Beyond basic window management, mangowm provides a rich set of features designed for a modern Wayland experience. + +- **[Animations](/docs/visuals/animations)** — Smooth, customizable animations for opening, moving, closing windows and tag switching. +- **[Layouts](/docs/window-management/layouts)** — Supports Scroller, Master-Stack, Monocle, Grid, Deck, and more, with per-tag layouts. +- **[Visual Effects](/docs/visuals/effects)** — Built-in blur, shadows, corner radius, and opacity effects powered by scenefx. +- **[IPC & Scripting](/docs/ipc)** — Control the compositor externally with robust IPC support for custom scripts and widgets. + +## Additional Features + +- **XWayland Support** — Excellent compatibility for legacy X11 applications. +- **Tag System** — Uses tags instead of workspaces, allowing separate window layouts for each tag. +- **Input Methods** — Great support for text input v2/v3 (Fcitx5, IBus). +- **Window States** — Rich states including swallow, minimize, maximize, fullscreen, and overlay. +- **Hot-Reload Config** — Simple external configuration that supports hot-reloading without restarting. +- **Scratchpads** — Support for both Sway-like and named scratchpads. + +## Community + +- **[Join the mangowm Discord](https://discord.gg/CPjbDxesh5)** — Chat with the community, get support, share your setup, and stay updated with the latest mangowm news. +- **[Join the GitHub Discussions](https://github.com/mangowm/mango/discussions)** — Ask questions, request features, report issues, or share ideas directly with contributors and other users. + +## Acknowledgements + +This project is built upon the hard work of several open-source projects: + +- **[wlroots](https://gitlab.freedesktop.org/wlroots/wlroots)** — Implementation of the Wayland protocol. +- **[owl](https://github.com/dqrk0jeste/owl)** — Basal window animation reference. +- **[dwl](https://codeberg.org/dwl/dwl)** — Basal dwl features. +- **[sway](https://github.com/swaywm/sway)** — Sample implementation of the Wayland protocol. +- **[scenefx](https://github.com/wlrfx/scenefx)** — Library to simplify adding window effects. diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 00000000..1297eafe --- /dev/null +++ b/docs/installation.md @@ -0,0 +1,231 @@ +--- +title: Installation +description: Install mangowm on Arch, Fedora, Gentoo, Guix System, NixOS, PikaOS, or build from source. +--- + +## Package Installation + +mangowm is available as a pre-built package on several distributions. Choose your distribution below. + +--- + +### Arch Linux + +mangowm is available in the **Arch User Repository (AUR)**. + +You can install it using an AUR helper like `yay` or `paru`: + +```bash +yay -S mangowm-git +``` + +> **Tip:** This package pulls the latest git version, ensuring you have the newest features and fixes. + +--- + +### Fedora + +The package is in the third-party **Terra repository**. First, add the Terra Repository. + +> **Warning:** Both commands require root privileges. Use `sudo` if needed. + +```bash +dnf install --nogpgcheck --repofrompath 'terra,https://repos.fyralabs.com/terra$releasever' terra-release +``` + +Then, install the package: + +```bash +dnf install mangowm +``` + +--- + +### Gentoo + +The package is hosted in the community-maintained **GURU** repository. + +1. **Add the GURU repository** + ```bash + emerge --ask --verbose eselect-repository + eselect repository enable guru + emerge --sync guru + ``` + +2. **Unmask packages** + Add the required packages to your `package.accept_keywords` file: + - `gui-libs/scenefx` + - `gui-wm/mangowm` + +3. **Install mango** + ```bash + emerge --ask --verbose gui-wm/mangowm + ``` + +--- + +### Guix System + +The package definition is described in the source repository. + +1. **Add mango channel** + Add to `$HOME/.config/guix/channels.scm`: + ```scheme + (cons (channel + (name 'mangowm) + (url "https://github.com/mangowm/mango.git") + (branch "main")) + %default-channels) + ``` + +2. **Install** + After running `guix pull`, you can install mangowm: + ```bash + guix install mangowm + ``` + + Or add it to your system configuration using the mangowm module: + ```scheme + (use-modules (mangowm)) + + (packages (cons* + mangowm-git + ... ;; Other packages + %base-packages)) + ``` + +> **Tip:** For more information, see the [Guix System documentation](https://guix.gnu.org/manual/devel/en/html_node/Channels.html). + +--- + +### NixOS + +The repository provides a Flake with a NixOS module. + +1. **Add flake input** + ```nix + # flake.nix + { + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + mangowm = { + url = "github:mangowm/mango"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + # other inputs ... + }; + } + ``` + +2. **Import the NixOS module** + + **Option A — Import in `configuration.nix`:** + ```nix + # configuration.nix (or any other file that you import) + {inputs, ...}: { + imports = [ + inputs.mangowm.nixosModules.mango + # .. other imports ... + ]; + + # ... + } + ``` + + **Option B — Import directly in flake:** + ```nix + # flake.nix + { + # ... + + outputs = { self, nixpkgs, mangowm, ...}@inputs: let + inherit (nixpkgs) lib; + # ... + in { + nixosConfigurations.YourHostName = lib.nixosSystem { + modules = [ + mangowm.nixosModules.mango # or inputs.mangowm.nixosModules.mango + # other imports ... + ]; + }; + } + } + ``` + +3. **Enable the module** + ```nix + # configuration.nix (or any other file that you import) + { + programs.mango.enable = true; + } + ``` + +4. **Extra options** + - `programs.mango.package` — the mango package to use, allows usage of custom mango drvs + - `programs.mango.addLoginEntry` (default: `true`) — adds login entry to the display manager + +--- + +### PikaOS + +mangowm is available in the **PikaOS package repository**. + +You can install it using the `pikman` package manager: + +```bash +pikman install mangowc +``` + +--- + +## Building from Source + +If your distribution isn't listed above, or you want the latest unreleased changes, you can build mangowm from source. + +> **Info:** Ensure the following dependencies are installed before proceeding: +> - `glibc` +> - `wayland` +> - `wayland-protocols` +> - `libinput` +> - `libdrm` +> - `libxkbcommon` +> - `pixman` +> - `git` +> - `meson` +> - `ninja` +> - `libdisplay-info` +> - `libliftoff` +> - `hwdata` +> - `seatd` +> - `pcre2` +> - `xorg-xwayland` +> - `libxcb` + +You will need to build `wlroots` and `scenefx` manually as well. + +1. **Build wlroots** + Clone and install the specific version required (check README for latest version). + ```bash + git clone -b 0.19.2 https://gitlab.freedesktop.org/wlroots/wlroots.git + cd wlroots + meson build -Dprefix=/usr + sudo ninja -C build install + ``` + +2. **Build scenefx** + This library handles the visual effects. + ```bash + git clone -b 0.4.1 https://github.com/wlrfx/scenefx.git + cd scenefx + meson build -Dprefix=/usr + sudo ninja -C build install + ``` + +3. **Build mangowm** + Finally, compile the compositor itself. + ```bash + git clone https://github.com/mangowm/mango.git + cd mango + meson build -Dprefix=/usr + sudo ninja -C build install + ``` diff --git a/docs/ipc.md b/docs/ipc.md new file mode 100644 index 00000000..72beefb5 --- /dev/null +++ b/docs/ipc.md @@ -0,0 +1,154 @@ +--- +title: IPC +description: Control mangowm programmatically using mmsg. +--- + +## Introduction + +mangowm includes a powerful IPC (Inter-Process Communication) tool called `mmsg`. This allows you to query the window manager's state, watch for events, and execute commands from external scripts. + +## Basic Usage + +The general syntax for `mmsg` is: + +```bash +mmsg [-OTLq] +mmsg [-o ] -s [-t ] [-l ] [-c ] [-d ,,,,,] +mmsg [-o ] (-g | -w) [-OotlcvmfxekbA] +``` + +### Options + +| Flag | Description | +| :--- | :--- | +| `-q` | Quit mangowm. | +| `-g` | **Get** values (tags, layout, focused client). | +| `-s` | **Set** values (switch tags, layouts). | +| `-w` | **Watch** mode (streams events). | +| `-O` | Get all output (monitor) information. | +| `-T` | Get number of tags. | +| `-L` | Get all available layouts. | +| `-o` | Select output (monitor). | +| `-t` | Get/set selected tags (set with `[+-^.]`). | +| `-l` | Get/set current layout. | +| `-c` | Get title and appid of focused client. | +| `-v` | Get visibility of statusbar. | +| `-m` | Get fullscreen status. | +| `-f` | Get floating status. | +| `-d` | **Dispatch** an internal command. | +| `-x` | Get focused client geometry. | +| `-e` | Get the name of the last focused layer. | +| `-k` | Get current keyboard layout. | +| `-b` | Get current keybind mode. | +| `-A` | Get scale factor of monitor. | + +## Examples + +### Tag Management + +You can perform arithmetic on tags using the `-t` flag with `-s` (set). + +```bash +# Switch to Tag 1 +mmsg -t 1 + +# Add Tag 2 to current view (Multiview) +mmsg -s -t 2+ + +# Remove Tag 2 from current view +mmsg -s -t 2- + +# Toggle Tag 2 +mmsg -s -t 2^ +``` + +### Layouts + +Switch layouts programmatically. Layout codes: `S` (Scroller), `T` (Tile), `G` (Grid), `M` (Monocle), `K` (Deck), `CT` (Center Tile), `RT` (Right Tile), `VS` (Vertical Scroller), `VT` (Vertical Tile), `VG` (Vertical Grid), `VK` (Vertical Deck), `TG` (TGMix). + +```bash +# Switch to Scroller +mmsg -l "S" + +# Switch to Tile +mmsg -l "T" +``` + +### Dispatching Commands + +Any command available in `config.conf` keybindings can be run via IPC. + +```bash +# Close the focused window +mmsg -d killclient + +# Resize window by +10 width +mmsg -d resizewin,+10,0 + +# Toggle fullscreen +mmsg -d togglefullscreen + +# Disable a monitor power +mmsg -d disable_monitor,eDP-1 +``` + +### Monitoring & Status + +Use `-g` or `-w` to build custom status bars or automation scripts. + +```bash +# Watch for all message changes +mmsg -w + +# Get all messages without watch +mmsg -g + +# Watch focused client appid and title +mmsg -w -c + +# Get all available outputs +mmsg -O + +# Get all tags message +mmsg -g -t + +# Get current focused client message +mmsg -g -c + +# Get current keyboard layout +mmsg -g -k + +# Get current keybind mode +mmsg -g -b + +# Get scale factor of current monitor +mmsg -g -A +``` + +#### Tag Message Format + +- State: 0 → none, 1 → active, 2 → urgent + +Example output: + +| Monitor | Tag Number | Tag State | Clients in Tag | Focused Client | +|---------|------------|-----------|----------------|----------------| +| eDP-1 | tag 2 | 0 | 1 | 0 | + +| Monitor | occupied tags mask | active tags mask | urgent tags mask | +|---------|--------------------|------------------|------------------| +| eDP-1 | 14 | 6 | 0 | + +## Virtual Monitors + +You can create headless outputs for screen mirroring or remote desktop access (e.g., Sunshine/Moonlight). + +```bash +# Create a virtual output +mmsg -d create_virtual_output + +# Configure it (set resolution) +wlr-randr --output HEADLESS-1 --pos 1920,0 --mode 1920x1080@60Hz + +# Destroy all virtual outputs +mmsg -d destroy_all_virtual_output \ No newline at end of file diff --git a/docs/meta.json b/docs/meta.json new file mode 100644 index 00000000..f8aac0ca --- /dev/null +++ b/docs/meta.json @@ -0,0 +1,14 @@ +{ + "title": "mangowm", + "pages": [ + "index", + "installation", + "quick-start", + "configuration", + "visuals", + "window-management", + "bindings", + "ipc", + "faq" + ] +} diff --git a/docs/quick-start.md b/docs/quick-start.md new file mode 100644 index 00000000..85f67cc2 --- /dev/null +++ b/docs/quick-start.md @@ -0,0 +1,88 @@ +--- +title: Quick Start +description: Basic configuration and first steps with mangowm. +--- + +Now that you have mangowm installed, let's get your environment set up. + +## Initial Setup + +1. **Create Configuration Directory** + + mangowm looks for configuration files in `~/.config/mango/`. + + ```bash + mkdir -p ~/.config/mango + ``` + +2. **Copy Default Config** + + A default configuration file is provided at `/etc/mango/config.conf`. Copy it to your local directory to start customizing. + + ```bash + cp /etc/mango/config.conf ~/.config/mango/config.conf + ``` + +3. **Launch mangowm** + + You can now start the compositor from your TTY. + + ```bash + mango + ``` + + Optional: To specify a custom config file path: + + ```bash + mango -c /path/to/your/config.conf + ``` + +## Essential Keybindings + +mangowm uses the following keybinds by default: + +| Key Combination | Action | +| :--- | :--- | +| `Alt` + `Return` | Open Terminal (defaults to `foot`) | +| `Alt` + `Space` | Open Launcher (defaults to `rofi`) | +| `Alt` + `Q` | Close (Kill) the active window | +| `Super` + `M` | Quit mangowm | +| `Super` + `F` | Toggle Fullscreen | +| `Alt` + `Arrow Keys` | Move focus (Left, Right, Up, Down) | +| `Ctrl` + `1-9` | Switch to Tag 1-9 | +| `Alt` + `1-9` | Move window to Tag 1-9 | + +> **Warning:** Some default bindings rely on specific tools like `foot` (terminal) and `rofi` (launcher). Ensure you have them installed or update your `config.conf` to use your preferred alternatives. + +## Recommended Tools + +To get a fully functional desktop experience, we recommend installing the following components: + +| Category | Recommended Tools | +| :--- | :--- | +| Application Launcher | rofi, bemenu, wmenu, fuzzel | +| Terminal Emulator | foot, wezterm, alacritty, kitty, ghostty | +| Status Bar | waybar, eww, quickshell, ags | +| Desktop Shell | Noctalia, DankMaterialShell | +| Wallpaper Setup | swww, swaybg | +| Notification Daemon | swaync, dunst, mako | +| Desktop Portal | xdg-desktop-portal, xdg-desktop-portal-wlr, xdg-desktop-portal-gtk | +| Clipboard | wl-clipboard, wl-clip-persist, cliphist | +| Gamma Control / Night Light | wlsunset, gammastep | +| Miscellaneous | xfce-polkit, wlogout | + +## Example Configuration + +Check out the [example configuration](https://github.com/DreamMaoMao/mango-config) by the creator of mangowm, including complete setups for mangowm, Waybar, Rofi, and more. + +```bash +git clone https://github.com/DreamMaoMao/mango-config.git ~/.config/mango +``` + +## Next Steps + +Now that you are up and running, dive deeper into customizing mangowm: + +- [Configure Monitors](/docs/configuration/monitors) — Set up resolution, scaling, and multi-monitor layouts. +- [Window Rules](/docs/window-management/rules#window-rules) — Define how specific applications should behave. +- [Appearance](/docs/visuals/theming) — Customize colors, borders, gaps, and effects. diff --git a/docs/visuals/animations.md b/docs/visuals/animations.md new file mode 100644 index 00000000..b4b88816 --- /dev/null +++ b/docs/visuals/animations.md @@ -0,0 +1,108 @@ +--- +title: Animations +description: Configure smooth transitions for windows and layers. +--- + +## Enabling Animations + +mangowm supports animations for both standard windows and layer shell surfaces (like bars and notifications). + +```ini +animations=1 +layer_animations=1 +``` + +## Animation Types + +You can define different animation styles for opening and closing windows and layer surfaces. + +Available types: `slide`, `zoom`, `fade`, `none`. + +```ini +animation_type_open=zoom +animation_type_close=slide +layer_animation_type_open=slide +layer_animation_type_close=slide +``` + +## Fade Settings + +Control the fade-in and fade-out effects for animations. + +```ini +animation_fade_in=1 +animation_fade_out=1 +fadein_begin_opacity=0.5 +fadeout_begin_opacity=0.5 +``` + +- `animation_fade_in` — Enable fade-in effect (0: disable, 1: enable) +- `animation_fade_out` — Enable fade-out effect (0: disable, 1: enable) +- `fadein_begin_opacity` — Starting opacity for fade-in animations (0.0–1.0) +- `fadeout_begin_opacity` — Starting opacity for fade-out animations (0.0–1.0) + +## Zoom Settings + +Adjust the zoom ratios for zoom animations. + +```ini +zoom_initial_ratio=0.3 +zoom_end_ratio=0.8 +``` + +- `zoom_initial_ratio` — Initial zoom ratio +- `zoom_end_ratio` — End zoom ratio + +## Durations + +Control the speed of animations (in milliseconds). + +| Setting | Type | Default | Description | +| :--- | :--- | :--- | :--- | +| `animation_duration_move` | integer | `500` | Move animation duration (ms) | +| `animation_duration_open` | integer | `400` | Open animation duration (ms) | +| `animation_duration_tag` | integer | `300` | Tag animation duration (ms) | +| `animation_duration_close` | integer | `300` | Close animation duration (ms) | +| `animation_duration_focus` | integer | `0` | Focus change (opacity transition) animation duration (ms) | + +```ini +animation_duration_move=500 +animation_duration_open=400 +animation_duration_tag=300 +animation_duration_close=300 +animation_duration_focus=0 +``` + +## Custom Bezier Curves + +Bezier curves determine the "feel" of an animation (e.g., linear vs. bouncy). The format is `x1,y1,x2,y2`. + +You can visualize and generate curve values using online tools like [cssportal.com](https://www.cssportal.com/css-cubic-bezier-generator/) or [easings.net](https://easings.net). + +| Setting | Type | Default | Description | +| :--- | :--- | :--- | :--- | +| `animation_curve_open` | string | `0.46,1.0,0.29,0.99` | Open animation bezier curve | +| `animation_curve_move` | string | `0.46,1.0,0.29,0.99` | Move animation bezier curve | +| `animation_curve_tag` | string | `0.46,1.0,0.29,0.99` | Tag animation bezier curve | +| `animation_curve_close` | string | `0.46,1.0,0.29,0.99` | Close animation bezier curve | +| `animation_curve_focus` | string | `0.46,1.0,0.29,0.99` | Focus change (opacity transition) animation bezier curve | +| `animation_curve_opafadein` | string | `0.46,1.0,0.29,0.99` | Open opacity animation bezier curve | +| `animation_curve_opafadeout` | string | `0.5,0.5,0.5,0.5` | Close opacity animation bezier curve | + +```ini +animation_curve_open=0.46,1.0,0.29,0.99 +animation_curve_move=0.46,1.0,0.29,0.99 +animation_curve_tag=0.46,1.0,0.29,0.99 +animation_curve_close=0.46,1.0,0.29,0.99 +animation_curve_focus=0.46,1.0,0.29,0.99 +animation_curve_opafadein=0.46,1.0,0.29,0.99 +animation_curve_opafadeout=0.5,0.5,0.5,0.5 +``` + +## Tag Animation Direction + +Control the direction of tag switch animations. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `tag_animation_direction` | `1` | Tag animation direction (1: horizontal, 0: vertical) | \ No newline at end of file diff --git a/docs/visuals/effects.md b/docs/visuals/effects.md new file mode 100644 index 00000000..23c1f206 --- /dev/null +++ b/docs/visuals/effects.md @@ -0,0 +1,82 @@ +--- +title: Window Effects +description: Add visual polish with blur, shadows, and opacity. +--- + +## Blur + +Blur creates a frosted glass effect for transparent windows. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `blur` | `0` | Enable blur for windows. | +| `blur_layer` | `0` | Enable blur for layer surfaces (like bars/docks). | +| `blur_optimized` | `1` | Caches the wallpaper and blur background, significantly reducing GPU usage. Disabling it will significantly increase GPU consumption and may cause rendering lag. **Highly recommended.** | +| `blur_params_radius` | `5` | The strength (radius) of the blur. | +| `blur_params_num_passes` | `1` | Number of passes. Higher = smoother but more expensive. | +| `blur_params_noise` | `0.02` | Blur noise level. | +| `blur_params_brightness` | `0.9` | Blur brightness adjustment. | +| `blur_params_contrast` | `0.9` | Blur contrast adjustment. | +| `blur_params_saturation` | `1.2` | Blur saturation adjustment. | + +> **Warning:** Blur has a relatively high impact on performance. If your hardware is limited, it is not recommended to enable it. If you experience lag with blur on, ensure `blur_optimized=1` — disabling it will significantly increase GPU consumption and may cause rendering lag. To disable blur entirely, set `blur=0`. + +--- + +## Shadows + +Drop shadows help distinguish floating windows from the background. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `shadows` | `0` | Enable shadows. | +| `layer_shadows` | `0` | Enable shadows for layer surfaces. | +| `shadow_only_floating` | `1` | Only draw shadows for floating windows (saves performance). | +| `shadows_size` | `10` | Size of the shadow. | +| `shadows_blur` | `15` | Shadow blur amount. | +| `shadows_position_x` | `0` | Shadow X offset. | +| `shadows_position_y` | `0` | Shadow Y offset. | +| `shadowscolor` | `0x000000ff` | Color of the shadow. | + +```ini +# Example shadows configuration +shadows=1 +layer_shadows=1 +shadow_only_floating=1 +shadows_size=12 +shadows_blur=15 +shadows_position_x=0 +shadows_position_y=0 +shadowscolor=0x000000ff +``` + +--- + +## Opacity & Corner Radius + +Control the transparency and roundness of your windows. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `border_radius` | `0` | Window corner radius in pixels. | +| `border_radius_location_default` | `0` | Corner radius location: `0` (all), `1` (top-left), `2` (top-right), `3` (bottom-left), `4` (bottom-right), `5` (closest corner). | +| `no_radius_when_single` | `0` | Disable radius if only one window is visible. | +| `focused_opacity` | `1.0` | Opacity for the active window (0.0 - 1.0). | +| `unfocused_opacity` | `1.0` | Opacity for inactive windows (0.0 - 1.0). | + +```ini +# Window corner radius in pixels +border_radius=0 + +# Corner radius location (0=all, 1=top-left, 2=top-right, 3=bottom-left, 4=bottom-right) +border_radius_location_default=0 + +# Disable radius if only one window is visible +no_radius_when_single=0 + +# Opacity for the active window (0.0 - 1.0) +focused_opacity=1.0 + +# Opacity for inactive windows +unfocused_opacity=1.0 +``` diff --git a/docs/visuals/meta.json b/docs/visuals/meta.json new file mode 100644 index 00000000..58723c4e --- /dev/null +++ b/docs/visuals/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Visuals", + "pages": ["theming", "status-bar", "effects", "animations"] +} diff --git a/docs/visuals/status-bar.md b/docs/visuals/status-bar.md new file mode 100644 index 00000000..f2924e83 --- /dev/null +++ b/docs/visuals/status-bar.md @@ -0,0 +1,141 @@ +--- +title: Status Bar +description: Configure Waybar for mangowm. +--- + +## Module Configuration + +mangowm is compatible with Waybar's `ext/workspaces` module (Wayland standard) or the `dwl/tags` module. We recommend `ext/workspaces` for the best experience. + +> **Tip:** You can also use the `dwl/tags` module, but `ext/workspaces` provides better integration with mangowm's features. The `ext/workspaces` module requires **Waybar > 0.14.0**. + +### `config.jsonc` + +Add the following to your Waybar configuration: + +```jsonc +{ + "modules-left": [ + "ext/workspaces", + "dwl/window" + ], + "ext/workspaces": { + "format": "{icon}", + "ignore-hidden": true, + "on-click": "activate", + "on-click-right": "deactivate", + "sort-by-id": true + }, + "dwl/window": { + "format": "[{layout}] {title}" + } +} +``` + +## Styling + +You can style the tags using standard CSS in `style.css`. + +### `style.css` + +```css +#workspaces { + border-radius: 4px; + border-width: 2px; + border-style: solid; + border-color: #c9b890; + margin-left: 4px; + padding-left: 10px; + padding-right: 6px; + background: rgba(40, 40, 40, 0.76); +} + +#workspaces button { + border: none; + background: none; + box-shadow: inherit; + text-shadow: inherit; + color: #ddca9e; + padding: 1px; + padding-left: 1px; + padding-right: 1px; + margin-right: 2px; + margin-left: 2px; +} + +#workspaces button.hidden { + color: #9e906f; + background-color: transparent; +} + +#workspaces button.visible { + color: #ddca9e; +} + +#workspaces button:hover { + color: #d79921; +} + +#workspaces button.active { + background-color: #ddca9e; + color: #282828; + margin-top: 5px; + margin-bottom: 5px; + padding-top: 1px; + padding-bottom: 0px; + border-radius: 3px; +} + +#workspaces button.urgent { + background-color: #ef5e5e; + color: #282828; + margin-top: 5px; + margin-bottom: 5px; + padding-top: 1px; + padding-bottom: 0px; + border-radius: 3px; +} + +#tags { + background-color: transparent; +} + +#tags button { + background-color: #fff; + color: #a585cd; +} + +#tags button:not(.occupied):not(.focused) { + font-size: 0; + min-width: 0; + min-height: 0; + margin: -17px; + padding: 0; + color: transparent; + background-color: transparent; +} + +#tags button.occupied { + background-color: #fff; + color: #cdc885; +} + +#tags button.focused { + background-color: rgb(186, 142, 213); + color: #fff; +} + +#tags button.urgent { + background: rgb(171, 101, 101); + color: #fff; +} + +#window { + background-color: rgb(237, 196, 147); + color: rgb(63, 37, 5); +} +``` + +## Complete Configuration Example + +> **Tip:** You can find a complete Waybar configuration for mangowm at [waybar-config](https://github.com/DreamMaoMao/waybar-config). \ No newline at end of file diff --git a/docs/visuals/theming.md b/docs/visuals/theming.md new file mode 100644 index 00000000..789ce478 --- /dev/null +++ b/docs/visuals/theming.md @@ -0,0 +1,59 @@ +--- +title: Theming +description: Customize the visual appearance of borders, colors, and the cursor. +--- + +## Dimensions + +Control the sizing of window borders and gaps. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `borderpx` | `4` | Border width in pixels. | +| `gappih` | `5` | Horizontal inner gap (between windows). | +| `gappiv` | `5` | Vertical inner gap. | +| `gappoh` | `10` | Horizontal outer gap (between windows and screen edges). | +| `gappov` | `10` | Vertical outer gap. | + +## Colors + +Colors are defined in `0xRRGGBBAA` hex format. + +```ini +# Background color of the root window +rootcolor=0x323232ff + +# Inactive window border +bordercolor=0x444444ff + +# Active window border +focuscolor=0xc66b25ff + +# Urgent window border (alerts) +urgentcolor=0xad401fff +``` + +### State-Specific Colors + +You can also color-code windows based on their state: + +| State | Config Key | Default Color | +| :--- | :--- | :--- | +| Maximized | `maximizescreencolor` | `0x89aa61ff` | +| Scratchpad | `scratchpadcolor` | `0x516c93ff` | +| Global | `globalcolor` | `0xb153a7ff` | +| Overlay | `overlaycolor` | `0x14a57cff` | + +> **Tip:** For scratchpad window sizing, see [Scratchpad](/docs/window-management/scratchpad) configuration. + +## Cursor Theme + +Set the size and theme of your mouse cursor. + +```ini +cursor_size=24 +cursor_theme=Adwaita +``` + +> **Tip:** You may also want to set the `XCURSOR_SIZE` environment variable to match: +> `env=XCURSOR_SIZE,24` diff --git a/docs/window-management/layouts.md b/docs/window-management/layouts.md new file mode 100644 index 00000000..26c05fe4 --- /dev/null +++ b/docs/window-management/layouts.md @@ -0,0 +1,99 @@ +--- +title: Layouts +description: Configure and switch between different window layouts. +--- + +## Supported Layouts + +mangowm supports a variety of layouts that can be assigned per tag. + +- `tile` +- `scroller` +- `monocle` +- `grid` +- `deck` +- `center_tile` +- `vertical_tile` +- `right_tile` +- `vertical_scroller` +- `vertical_grid` +- `vertical_deck` +- `tgmix` + +--- + +## Scroller Layout + +The Scroller layout positions windows in a scrollable strip, similar to PaperWM. + +### Configuration + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `scroller_structs` | `20` | Width reserved on sides when window ratio is 1. | +| `scroller_default_proportion` | `0.9` | Default width proportion for new windows. | +| `scroller_focus_center` | `0` | Always center the focused window (1 = enable). | +| `scroller_prefer_center` | `0` | Center focused window only if it was outside the view. | +| `scroller_prefer_overspread` | `1` | Allow windows to overspread when there's extra space. | +| `edge_scroller_pointer_focus` | `1` | Focus windows even if partially off-screen. | +| `scroller_proportion_preset` | `0.5,0.8,1.0` | Presets for cycling window widths. | +| `scroller_ignore_proportion_single` | `1` | Ignore proportion adjustments for single windows. | +| `scroller_default_proportion_single` | `1.0` | Default proportion for single windows in scroller. **Requires `scroller_ignore_proportion_single=0` to take effect.** | + +> **Warning:** `scroller_prefer_overspread`, `scroller_focus_center`, and `scroller_prefer_center` interact with each other. Their priority order is: +> +> **scroller_prefer_overspread > scroller_focus_center > scroller_prefer_center** +> +> To ensure a lower-priority setting takes effect, you must set all higher-priority options to `0`. + +```ini +# Example scroller configuration +scroller_structs=20 +scroller_default_proportion=0.9 +scroller_focus_center=0 +scroller_prefer_center=0 +scroller_prefer_overspread=1 +edge_scroller_pointer_focus=1 +scroller_default_proportion_single=1.0 +scroller_proportion_preset=0.5,0.8,1.0 +``` + +--- + +## Master-Stack Layouts + +These settings apply to layouts like `tile` and `center_tile`. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `new_is_master` | `1` | New windows become the master window. | +| `default_mfact` | `0.55` | The split ratio between master and stack areas. | +| `default_nmaster` | `1` | Number of allowed master windows. | +| `smartgaps` | `0` | Disable gaps when only one window is present. | +| `center_master_overspread` | `0` | (Center Tile) Master spreads across screen if no stack exists. | +| `center_when_single_stack` | `1` | (Center Tile) Center master when only one stack window exists. | + +```ini +# Example master-stack configuration +new_is_master=1 +smartgaps=0 +default_mfact=0.55 +default_nmaster=1 +``` + +--- + +## Switching Layouts + +You can switch layouts dynamically or set a default for specific tags using [Tag Rules](/docs/window-management/rules#tag-rules). + +**Keybinding Examples:** + +```ini +# Cycle through layouts +bind=SUPER,n,switch_layout + +# Set specific layout +bind=SUPER,t,setlayout,tile +bind=SUPER,s,setlayout,scroller +``` diff --git a/docs/window-management/meta.json b/docs/window-management/meta.json new file mode 100644 index 00000000..e0937d14 --- /dev/null +++ b/docs/window-management/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Window Management", + "pages": ["layouts", "rules", "overview", "scratchpad"] +} diff --git a/docs/window-management/overview.md b/docs/window-management/overview.md new file mode 100644 index 00000000..290d6139 --- /dev/null +++ b/docs/window-management/overview.md @@ -0,0 +1,36 @@ +--- +title: Overview +description: Configure the overview mode for window navigation. +--- + +## Overview Settings + +| Setting | Type | Default | Description | +| :--- | :--- | :--- | :--- | +| `hotarea_size` | integer | `10` | Hot area size in pixels. | +| `enable_hotarea` | integer | `1` | Enable hot areas (0: disable, 1: enable). | +| `hotarea_corner` | integer | `2` | Hot area corner (0: top-left, 1: top-right, 2: bottom-left, 3: bottom-right). | +| `ov_tab_mode` | integer | `0` | Overview tab mode (0: disable, 1: enable). | +| `overviewgappi` | integer | `5` | Inner gap in overview mode. | +| `overviewgappo` | integer | `30` | Outer gap in overview mode. | + +### Setting Descriptions + +- `enable_hotarea` — Toggles overview when the cursor enters the configured corner. +- `hotarea_size` — Size of the hot area trigger zone in pixels. +- `hotarea_corner` — Corner that triggers the hot area (0: top-left, 1: top-right, 2: bottom-left, 3: bottom-right). +- `ov_tab_mode` — Circles focus through windows in overview; releasing the mod key exits overview. + +### Mouse Interaction in Overview + +When in overview mode: + +- **Left mouse button** — Jump to (focus) a window. +- **Right mouse button** — Close a window. + +To enable this behavior, add the following mouse bindings to your config: + +```ini +mousebind=NONE,btn_left,toggleoverview,1 +mousebind=NONE,btn_right,killclient,0 +``` diff --git a/docs/window-management/rules.md b/docs/window-management/rules.md new file mode 100644 index 00000000..996c172f --- /dev/null +++ b/docs/window-management/rules.md @@ -0,0 +1,249 @@ +--- +title: Rules +description: Define behavior for specific windows, tags, and layers. +--- + +## Window Rules + +Window rules allow you to set specific properties (floating, opacity, size, animations, etc.) for applications based on their `appid` or `title`. You can set all parameters in one line, and if you both set appid and title, the window will only follow the rules when appid and title both match. + +**Format:** + +```ini +windowrule=Parameter:Values,title:Values +windowrule=Parameter:Values,Parameter:Values,appid:Values,title:Values +``` + +### State & Behavior Parameters + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `appid` | string | Any | Match by application ID, supports regex | +| `title` | string | Any | Match by window title, supports regex | +| `isfloating` | integer | `0` / `1` | Force floating state | +| `isfullscreen` | integer | `0` / `1` | Force fullscreen state | +| `isfakefullscreen` | integer | `0` / `1` | Force fake-fullscreen state (window stays constrained) | +| `isglobal` | integer | `0` / `1` | Open as global window (sticky across tags) | +| `isoverlay` | integer | `0` / `1` | Make it always in top layer | +| `isopensilent` | integer | `0` / `1` | Open without focus | +| `istagsilent` | integer | `0` / `1` | Don't focus if client is not in current view tag | +| `force_maximize` | integer | `0` / `1` (default 1) | The state of client default to maximized | +| `ignore_maximize` | integer | `0` / `1` (default 1) | Don't handle maximize request from client | +| `ignore_minimize` | integer | `0` / `1` (default 1) | Don't handle minimize request from client | +| `force_tiled_state` | integer | `0` / `1` | Deceive the window into thinking it is tiling, so it better adheres to assigned dimensions | +| `noopenmaximized` | integer | `0` / `1` | Window does not open as maximized mode | +| `single_scratchpad` | integer | `0` / `1` (default 1) | Only show one out of named scratchpads or the normal scratchpad | +| `allow_shortcuts_inhibit` | integer | `0` / `1` (default 1) | Allow shortcuts to be inhibited by clients | +| `indleinhibit_when_focus` | integer | `0` / `1` (default 0) | Automatically keep idle inhibit active when this window is focused | + +### Geometry & Position + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `width` | integer | 0-9999 | Window width when it becomes a floating window | +| `height` | integer | 0-9999 | Window height when it becomes a floating window | +| `offsetx` | integer | -999-999 | X offset from center (%), 100 is the edge of screen with outer gap | +| `offsety` | integer | -999-999 | Y offset from center (%), 100 is the edge of screen with outer gap | +| `monitor` | string | Any | Assign to monitor by [monitor spec](/docs/configuration/monitors#monitor-spec-format) (name, make, model, or serial) | +| `tags` | integer | 1-9 | Assign to specific tag | +| `no_force_center` | integer | `0` / `1` | Window does not force center | +| `isnosizehint` | integer | `0` / `1` | Don't use min size and max size for size hints | + +### Visuals & Decoration + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `noblur` | integer | `0` / `1` | Window does not have blur effect | +| `isnoborder` | integer | `0` / `1` | Remove window border | +| `isnoshadow` | integer | `0` / `1` | Not apply shadow | +| `isnoradius` | integer | `0` / `1` | Not apply corner radius | +| `isnoanimation` | integer | `0` / `1` | Not apply animation | +| `focused_opacity` | integer | `0` / `1` | Window focused opacity | +| `unfocused_opacity` | integer | `0` / `1` | Window unfocused opacity | +| `allow_csd` | integer | `0` / `1` | Allow client side decoration | + +> **Tip:** For detailed visual effects configuration, see the [Window Effects](/docs/visuals/effects) page for blur, shadows, and opacity settings. + +### Layout & Scroller + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `scroller_proportion` | float | 0.1-1.0 | Set scroller proportion | +| `scroller_proportion_single` | float | 0.1-1.0 | Set scroller auto adjust proportion when it is single window | + +> **Tip:** For comprehensive layout configuration, see the [Layouts](/docs/window-management/layouts) page for all layout options and detailed settings. + +### Animation + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `animation_type_open` | string | zoom, slide, fade, none | Set open animation | +| `animation_type_close` | string | zoom, slide, fade, none | Set close animation | +| `nofadein` | integer | `0` / `1` | Window ignores fade-in animation | +| `nofadeout` | integer | `0` / `1` | Window ignores fade-out animation | + +> **Tip:** For detailed animation configuration, see the [Animations](/docs/visuals/animations) page for available types and settings. + +### Terminal & Swallowing + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `isterm` | integer | `0` / `1` | A new GUI window will replace the isterm window when it is opened | +| `noswallow` | integer | `0` / `1` | The window will not replace the isterm window | + +### Global & Special Windows + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `globalkeybinding` | string | `[mod combination][-][key]` | Global keybinding (only works for Wayland apps) | +| `isunglobal` | integer | `0` / `1` | Open as unmanaged global window (for desktop pets or camera windows) | +| `isnamedscratchpad` | integer | `0` / `1` | 0: disable, 1: named scratchpad | + +> **Tip:** For scratchpad usage, see the [Scratchpad](/docs/window-management/scratchpad) page for detailed configuration examples. + +### Performance & Tearing + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `force_tearing` | integer | `0` / `1` | Set window to tearing state, refer to [Tearing](/docs/configuration/monitors#tearing-game-mode) | + +### Examples + +```ini +# Set specific window size and position +windowrule=width:1000,height:900,appid:yesplaymusic,title:Demons + +# Global keybindings for OBS Studio +windowrule=globalkeybinding:ctrl+alt-o,appid:com.obsproject.Studio +windowrule=globalkeybinding:ctrl+alt+n,appid:com.obsproject.Studio +windowrule=isopensilent:1,appid:com.obsproject.Studio + +# Force tearing for games +windowrule=force_tearing:1,title:vkcube +windowrule=force_tearing:1,title:Counter-Strike 2 + +# Named scratchpad for file manager +windowrule=isnamedscratchpad:1,width:1280,height:800,appid:st-yazi + +# Custom opacity for specific apps +windowrule=focused_opacity:0.8,appid:firefox +windowrule=unfocused_opacity:0.6,appid:foot + +# Disable blur for selection tools +windowrule=noblur:1,appid:slurp + +# Position windows relative to screen center +windowrule=offsetx:20,offsety:-30,width:800,height:600,appid:alacritty + +# Send to specific tag and monitor +windowrule=tags:9,monitor:HDMI-A-1,appid:discord + +# Terminal swallowing setup +windowrule=isterm:1,appid:st +windowrule=noswallow:1,appid:foot + +# Disable client-side decorations +windowrule=allow_csd:1,appid:firefox + +# Unmanaged global window (desktop pets, camera) +windowrule=isunglobal:1,appid:cheese + +# Named scratchpad toggle +bind=alt,h,toggle_named_scratchpad,st-yazi,none,st -c st-yazi -e yazi +``` + +--- + +## Tag Rules + +You can set all parameters in one line. If only `id` is set, the rule is followed when the id matches. If any of `monitor_name`, `monitor_make`, `monitor_model`, or `monitor_serial` are set, the rule is followed only if **all** of the set monitor fields match. + +> **Warning:** Layouts set in tag rules have a higher priority than monitor rule layouts. + +**Format:** + +```ini +tagrule=id:Values,Parameter:Values,Parameter:Values +tagrule=id:Values,monitor_name:eDP-1,Parameter:Values,Parameter:Values +tagrule=id:Values,monitor_make:xxx,monitor_model:xxx,Parameter:Values +``` + +> **Tip:** See [Layouts](/docs/window-management/layouts#supported-layouts) for detailed descriptions of each layout type. + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `id` | integer | 0-9 | Match by tag id, 0 means the ~0 tag | +| `monitor_name` | string | monitor name | Match by monitor name | +| `monitor_make` | string | monitor make | Match by monitor manufacturer | +| `monitor_model` | string | monitor model | Match by monitor model | +| `monitor_serial` | string | monitor serial | Match by monitor serial number | +| `layout_name` | string | layout name | Layout name to set | +| `no_render_border` | integer | `0` / `1` | Disable render border | +| `no_hide` | integer | `0` / `1` | Not hide even if the tag is empty | +| `nmaster` | integer | 0, 99 | Number of master windows | +| `mfact` | float | 0.1–0.9 | Master area factor | + +### Examples + +```ini +# Set layout for specific tags +tagrule=id:1,layout_name:scroller +tagrule=id:2,layout_name:scroller + +# Limit to specific monitor +tagrule=id:1,monitor_name:eDP-1,layout_name:scroller +tagrule=id:2,monitor_name:eDP-1,layout_name:scroller + +# Persistent tags (1-4) with layout assignment +tagrule=id:1,no_hide:1,layout_name:scroller +tagrule=id:2,no_hide:1,layout_name:scroller +tagrule=id:3,monitor_name:eDP-1,no_hide:1,layout_name:scroller +tagrule=id:4,monitor_name:eDP-1,no_hide:1,layout_name:scroller + +# Advanced tag configuration with master layout settings +tagrule=id:5,layout_name:tile,nmaster:2,mfact:0.6 +tagrule=id:6,monitor_name:HDMI-A-1,layout_name:monocle,no_render_border:1 +``` + +> **Tip:** For Waybar configuration with persistent tags, see [Status Bar](/docs/visuals/status-bar) documentation. + +--- + +## Layer Rules + +You can set all parameters in one line. Target "layer shell" surfaces like status bars (`waybar`), launchers (`rofi`), or notification daemons. + +**Format:** + +```ini +layerrule=layer_name:Values,Parameter:Values,Parameter:Values +``` + +> **Tip:** You can use `mmsg -e` to get the last open layer name for debugging. + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `layer_name` | string | layer name | Match name of layer, supports regex | +| `animation_type_open` | string | slide, zoom, fade, none | Set open animation | +| `animation_type_close` | string | slide, zoom, fade, none | Set close animation | +| `noblur` | integer | `0` / `1` | Disable blur | +| `noanim` | integer | `0` / `1` | Disable layer animation | +| `noshadow` | integer | `0` / `1` | Disable layer shadow | + +> **Tip:** For animation types, see [Animations](/docs/visuals/animations#animation-types). For visual effects, see [Window Effects](/docs/visuals/effects). + +### Examples + +```ini +# No blur or animation for slurp selection layer (avoids occlusion and ghosting in screenshots) +layerrule=noanim:1,noblur:1,layer_name:selection + +# Zoom animation for Rofi with multiple parameters +layerrule=animation_type_open:zoom,noanim:0,layer_name:rofi + +# Disable animations and shadows for notification daemon +layerrule=noanim:1,noshadow:1,layer_name:swaync + +# Multiple effects for launcher +layerrule=animation_type_open:slide,animation_type_close:fade,noblur:1,layer_name:wofi +``` diff --git a/docs/window-management/scratchpad.md b/docs/window-management/scratchpad.md new file mode 100644 index 00000000..398182f9 --- /dev/null +++ b/docs/window-management/scratchpad.md @@ -0,0 +1,73 @@ +--- +title: Scratchpad +description: Manage hidden "scratchpad" windows for quick access. +--- + +mangowm supports two types of scratchpads: the standard pool (Sway-like) and named scratchpads. + +## Standard Scratchpad + +Any window can be sent to the "scratchpad" pile, which hides it. You can then cycle through them. + +**Keybindings:** + +```ini +# Send current window to scratchpad +bind=SUPER,i,minimized + +# Toggle (show/hide) the scratchpad +bind=ALT,z,toggle_scratchpad + +# Retrieve window from scratchpad (restore) +bind=SUPER+SHIFT,i,restore_minimized +``` + +--- + +## Named Scratchpad + +Named scratchpads are bound to specific keys and applications. When triggered, mangowm will either launch the app (if not running) or toggle its visibility. + +**1. Define the Window Rule** + +You must identify the app using a unique `appid` or `title` and mark it as a named scratchpad. The application must support setting a custom appid or title at launch. Common examples: + +- `st -c my-appid` — sets the appid +- `kitty -T my-title` — sets the window title +- `foot --app-id my-appid` — sets the appid + +Use `none` as a placeholder when you only want to match by one field. + +```ini +# Match by appid +windowrule=isnamedscratchpad:1,width:1280,height:800,appid:st-yazi + +# Match by title +windowrule=isnamedscratchpad:1,width:1000,height:700,title:kitty-scratch +``` + +**2. Bind the Toggle Key** + +Format: `bind=MOD,KEY,toggle_named_scratchpad,appid,title,command` + +Use `none` for whichever field you are not matching on. + +```ini +# Match by appid: launch 'st' with class 'st-yazi' running 'yazi' +bind=alt,h,toggle_named_scratchpad,st-yazi,none,st -c st-yazi -e yazi + +# Match by title: launch 'kitty' with window title 'kitty-scratch' +bind=alt,k,toggle_named_scratchpad,none,kitty-scratch,kitty -T kitty-scratch +``` + +--- + +## Appearance + +You can customize the size of scratchpad windows relative to the screen. + +```ini +scratchpad_width_ratio=0.8 +scratchpad_height_ratio=0.9 +scratchpadcolor=0x516c93ff +``` From a7461d9d5b36fbd757702714f9737ee9ddc7486b Mon Sep 17 00:00:00 2001 From: tonybanters Date: Thu, 12 Mar 2026 06:49:58 -0700 Subject: [PATCH 090/328] opt: use config.xxx instead of global presets --- src/animation/client.h | 141 ++++--- src/animation/common.h | 16 +- src/animation/layer.h | 66 ++-- src/animation/tag.h | 22 +- src/config/parse_config.h | 727 ++++++++++++++++++------------------- src/config/preset.h | 234 +----------- src/dispatch/bind_define.h | 66 ++-- src/ext-protocol/tearing.h | 7 +- src/fetch/client.h | 24 +- src/layout/arrange.h | 8 +- src/layout/horizontal.h | 122 ++++--- src/layout/vertical.h | 85 +++-- src/mango.c | 305 ++++++++-------- 13 files changed, 826 insertions(+), 997 deletions(-) diff --git a/src/animation/client.h b/src/animation/client.h index b6683ecc..8c9c3915 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -10,20 +10,21 @@ void set_rect_size(struct wlr_scene_rect *rect, int32_t width, int32_t height) { enum corner_location set_client_corner_location(Client *c) { enum corner_location current_corner_location = CORNER_LOCATION_ALL; - struct wlr_box target_geom = animations ? c->animation.current : c->geom; - if (target_geom.x + border_radius <= c->mon->m.x) { - current_corner_location &= ~CORNER_LOCATION_LEFT; // 清除左标志位 + struct wlr_box target_geom = + config.animations ? c->animation.current : c->geom; + if (target_geom.x + config.border_radius <= c->mon->m.x) { + current_corner_location &= ~CORNER_LOCATION_LEFT; } - if (target_geom.x + target_geom.width - border_radius >= + if (target_geom.x + target_geom.width - config.border_radius >= c->mon->m.x + c->mon->m.width) { - current_corner_location &= ~CORNER_LOCATION_RIGHT; // 清除右标志位 + current_corner_location &= ~CORNER_LOCATION_RIGHT; } - if (target_geom.y + border_radius <= c->mon->m.y) { - current_corner_location &= ~CORNER_LOCATION_TOP; // 清除上标志位 + if (target_geom.y + config.border_radius <= c->mon->m.y) { + current_corner_location &= ~CORNER_LOCATION_TOP; } - if (target_geom.y + target_geom.height - border_radius >= + if (target_geom.y + target_geom.height - config.border_radius >= c->mon->m.y + c->mon->m.height) { - current_corner_location &= ~CORNER_LOCATION_BOTTOM; // 清除下标志位 + current_corner_location &= ~CORNER_LOCATION_BOTTOM; } return current_corner_location; } @@ -54,15 +55,16 @@ int32_t is_special_animation_rule(Client *c) { } else if (c->mon->visible_tiling_clients == 1 && !c->isfloating) { return DOWN; } else if (c->mon->visible_tiling_clients == 2 && !c->isfloating && - !new_is_master && is_horizontal_stack_layout(c->mon)) { + !config.new_is_master && is_horizontal_stack_layout(c->mon)) { return RIGHT; - } else if (!c->isfloating && new_is_master && + } else if (!c->isfloating && config.new_is_master && is_horizontal_stack_layout(c->mon)) { return LEFT; } else if (c->mon->visible_tiling_clients == 2 && !c->isfloating && - !new_is_master && is_horizontal_right_stack_layout(c->mon)) { + !config.new_is_master && + is_horizontal_right_stack_layout(c->mon)) { return LEFT; - } else if (!c->isfloating && new_is_master && + } else if (!c->isfloating && config.new_is_master && is_horizontal_right_stack_layout(c->mon)) { return RIGHT; } else { @@ -77,7 +79,8 @@ void set_client_open_animation(Client *c, struct wlr_box geo) { int32_t special_direction; int32_t center_x, center_y; - if ((!c->animation_type_open && strcmp(animation_type_open, "fade") == 0) || + if ((!c->animation_type_open && + strcmp(config.animation_type_open, "fade") == 0) || (c->animation_type_open && strcmp(c->animation_type_open, "fade") == 0)) { c->animainit_geom.width = geo.width; @@ -86,11 +89,11 @@ void set_client_open_animation(Client *c, struct wlr_box geo) { c->animainit_geom.y = geo.y; return; } else if ((!c->animation_type_open && - strcmp(animation_type_open, "zoom") == 0) || + strcmp(config.animation_type_open, "zoom") == 0) || (c->animation_type_open && strcmp(c->animation_type_open, "zoom") == 0)) { - c->animainit_geom.width = geo.width * zoom_initial_ratio; - c->animainit_geom.height = geo.height * zoom_initial_ratio; + c->animainit_geom.width = geo.width * config.zoom_initial_ratio; + c->animainit_geom.height = geo.height * config.zoom_initial_ratio; c->animainit_geom.x = geo.x + (geo.width - c->animainit_geom.width) / 2; c->animainit_geom.y = geo.y + (geo.height - c->animainit_geom.height) / 2; @@ -223,7 +226,7 @@ void scene_buffer_apply_effect(struct wlr_scene_buffer *buffer, int32_t sx, if (wlr_xdg_popup_try_from_wlr_surface(surface) != NULL) return; - wlr_scene_buffer_set_corner_radius(buffer, border_radius, + wlr_scene_buffer_set_corner_radius(buffer, config.border_radius, buffer_data->corner_location); } @@ -241,7 +244,7 @@ void buffer_set_effect(Client *c, BufferData data) { data.should_scale = false; if (c->isnoradius || c->isfullscreen || - (no_radius_when_single && c->mon && + (config.no_radius_when_single && c->mon && c->mon->visible_tiling_clients == 1)) { data.corner_location = CORNER_LOCATION_NONE; } @@ -255,7 +258,7 @@ void client_draw_shadow(Client *c) { if (c->iskilling || !client_surface(c)->mapped || c->isnoshadow) return; - if (!shadows || (!c->isfloating && shadow_only_floating)) { + if (!config.shadows || (!c->isfloating && config.shadow_only_floating)) { if (c->shadow->node.enabled) wlr_scene_node_set_enabled(&c->shadow->node, false); return; @@ -266,7 +269,7 @@ void client_draw_shadow(Client *c) { bool hit_no_border = check_hit_no_border(c); enum corner_location current_corner_location = - c->isfullscreen || (no_radius_when_single && c->mon && + c->isfullscreen || (config.no_radius_when_single && c->mon && c->mon->visible_tiling_clients == 1) ? CORNER_LOCATION_NONE : CORNER_LOCATION_ALL; @@ -276,9 +279,8 @@ void client_draw_shadow(Client *c) { int32_t width, height; client_actual_size(c, &width, &height); - int32_t delta = shadows_size + (int32_t)c->bw - bwoffset; + int32_t delta = config.shadows_size + (int32_t)c->bw - bwoffset; - /* we calculate where to clip the shadow */ struct wlr_box client_box = { .x = bwoffset, .y = bwoffset, @@ -287,22 +289,20 @@ void client_draw_shadow(Client *c) { }; struct wlr_box shadow_box = { - .x = shadows_position_x + bwoffset, - .y = shadows_position_y + bwoffset, + .x = config.shadows_position_x + bwoffset, + .y = config.shadows_position_y + bwoffset, .width = width + 2 * delta, .height = height + 2 * delta, }; struct wlr_box intersection_box; wlr_box_intersection(&intersection_box, &client_box, &shadow_box); - /* clipped region takes shadow relative coords, so we translate everything - * by its position */ - intersection_box.x -= shadows_position_x + bwoffset; - intersection_box.y -= shadows_position_y + bwoffset; + intersection_box.x -= config.shadows_position_x + bwoffset; + intersection_box.y -= config.shadows_position_y + bwoffset; struct clipped_region clipped_region = { .area = intersection_box, - .corner_radius = border_radius, + .corner_radius = config.border_radius, .corners = current_corner_location, }; @@ -356,23 +356,23 @@ void apply_border(Client *c) { bool hit_no_border = check_hit_no_border(c); enum corner_location current_corner_location; - if (c->isfullscreen || (no_radius_when_single && c->mon && + if (c->isfullscreen || (config.no_radius_when_single && c->mon && c->mon->visible_tiling_clients == 1)) { current_corner_location = CORNER_LOCATION_NONE; } else { current_corner_location = set_client_corner_location(c); } - if (hit_no_border && smartgaps) { + if (hit_no_border && config.smartgaps) { c->bw = 0; c->fake_no_border = true; - } else if (hit_no_border && !smartgaps) { + } else if (hit_no_border && !config.smartgaps) { wlr_scene_rect_set_size(c->border, 0, 0); wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); c->fake_no_border = true; return; } else if (!c->isfullscreen && VISIBLEON(c, c->mon)) { - c->bw = c->isnoborder ? 0 : borderpx; + c->bw = c->isnoborder ? 0 : config.borderpx; c->fake_no_border = false; } @@ -434,14 +434,14 @@ void apply_border(Client *c) { struct clipped_region clipped_region = { .area = {inner_surface_x, inner_surface_y, inner_surface_width, inner_surface_height}, - .corner_radius = border_radius, + .corner_radius = config.border_radius, .corners = current_corner_location, }; wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); wlr_scene_rect_set_size(c->border, rect_width, rect_height); wlr_scene_node_set_position(&c->border->node, rect_x, rect_y); - wlr_scene_rect_set_corner_radius(c->border, border_radius, + wlr_scene_rect_set_corner_radius(c->border, config.border_radius, current_corner_location); wlr_scene_rect_set_clipped_region(c->border, clipped_region); } @@ -523,7 +523,7 @@ void client_apply_clip(Client *c, float factor) { enum corner_location current_corner_location = set_client_corner_location(c); - if (!animations) { + if (!config.animations) { c->animation.running = false; c->need_output_flush = false; c->animainit_geom = c->current = c->pending = c->animation.current = @@ -655,19 +655,19 @@ void fadeout_client_animation_next_tick(Client *c) { double opacity_eased_progress = find_animation_curve_at(animation_passed, OPAFADEOUT); - double percent = fadeout_begin_opacity - - (opacity_eased_progress * fadeout_begin_opacity); + double percent = config.fadeout_begin_opacity - + (opacity_eased_progress * config.fadeout_begin_opacity); double opacity = MAX(percent, 0); - if (animation_fade_out && !c->nofadeout) + if (config.animation_fade_out && !c->nofadeout) wlr_scene_node_for_each_buffer(&c->scene->node, scene_buffer_apply_opacity, &opacity); if ((c->animation_type_close && strcmp(c->animation_type_close, "zoom") == 0) || (!c->animation_type_close && - strcmp(animation_type_close, "zoom") == 0)) { + strcmp(config.animation_type_close, "zoom") == 0)) { buffer_data.width = width; buffer_data.height = height; @@ -768,14 +768,14 @@ void init_fadeout_client(Client *c) { if ((c->animation_type_close && strcmp(c->animation_type_close, "none") == 0) || (!c->animation_type_close && - strcmp(animation_type_close, "none") == 0)) { + strcmp(config.animation_type_close, "none") == 0)) { return; } Client *fadeout_client = ecalloc(1, sizeof(*fadeout_client)); wlr_scene_node_set_enabled(&c->scene->node, true); - client_set_border_color(c, bordercolor); + client_set_border_color(c, config.bordercolor); fadeout_client->scene = wlr_scene_tree_snapshot(&c->scene->node, layers[LyrFadeOut]); wlr_scene_node_set_enabled(&c->scene->node, false); @@ -785,7 +785,7 @@ void init_fadeout_client(Client *c) { return; } - fadeout_client->animation.duration = animation_duration_close; + fadeout_client->animation.duration = config.animation_duration_close; fadeout_client->geom = fadeout_client->current = fadeout_client->animainit_geom = fadeout_client->animation.initial = c->animation.current; @@ -802,7 +802,7 @@ void init_fadeout_client(Client *c) { fadeout_client->animation.initial.y = 0; if ((!c->animation_type_close && - strcmp(animation_type_close, "fade") == 0) || + strcmp(config.animation_type_close, "fade") == 0) || (c->animation_type_close && strcmp(c->animation_type_close, "fade") == 0)) { fadeout_client->current.x = 0; @@ -812,7 +812,7 @@ void init_fadeout_client(Client *c) { } else if ((c->animation_type_close && strcmp(c->animation_type_close, "slide") == 0) || (!c->animation_type_close && - strcmp(animation_type_close, "slide") == 0)) { + strcmp(config.animation_type_close, "slide") == 0)) { fadeout_client->current.y = c->geom.y + c->geom.height / 2 > c->mon->m.y + c->mon->m.height / 2 ? c->mon->m.height - @@ -822,16 +822,16 @@ void init_fadeout_client(Client *c) { } else { fadeout_client->current.y = (fadeout_client->geom.height - - fadeout_client->geom.height * zoom_end_ratio) / + fadeout_client->geom.height * config.zoom_end_ratio) / 2; fadeout_client->current.x = (fadeout_client->geom.width - - fadeout_client->geom.width * zoom_end_ratio) / + fadeout_client->geom.width * config.zoom_end_ratio) / 2; fadeout_client->current.width = - fadeout_client->geom.width * zoom_end_ratio; + fadeout_client->geom.width * config.zoom_end_ratio; fadeout_client->current.height = - fadeout_client->geom.height * zoom_end_ratio; + fadeout_client->geom.height * config.zoom_end_ratio; } fadeout_client->animation.time_started = get_now_in_ms(); @@ -866,12 +866,11 @@ void client_set_pending_state(Client *c) { if (!c || c->iskilling) return; - // 判断是否需要动画 - if (!animations) { + if (!config.animations) { c->animation.should_animate = false; - } else if (animations && c->animation.tagining) { + } else if (config.animations && c->animation.tagining) { c->animation.should_animate = true; - } else if (!animations || c == grabc || + } else if (!config.animations || c == grabc || (!c->is_pending_open_animation && wlr_box_equal(&c->current, &c->pending))) { c->animation.should_animate = false; @@ -882,7 +881,7 @@ void client_set_pending_state(Client *c) { if (((c->animation_type_open && strcmp(c->animation_type_open, "none") == 0) || (!c->animation_type_open && - strcmp(animation_type_open, "none") == 0)) && + strcmp(config.animation_type_open, "none") == 0)) && c->animation.action == OPEN) { c->animation.duration = 0; } @@ -951,16 +950,16 @@ void resize(Client *c, struct wlr_box geo, int32_t interact) { !c->animation.tagouting && wlr_box_equal(&c->geom, &c->current)) { c->animation.action = c->animation.action; } else if (c->animation.tagouting) { - c->animation.duration = animation_duration_tag; + c->animation.duration = config.animation_duration_tag; c->animation.action = TAG; } else if (c->animation.tagining) { - c->animation.duration = animation_duration_tag; + c->animation.duration = config.animation_duration_tag; c->animation.action = TAG; } else if (c->is_pending_open_animation) { - c->animation.duration = animation_duration_open; + c->animation.duration = config.animation_duration_open; c->animation.action = OPEN; } else { - c->animation.duration = animation_duration_move; + c->animation.duration = config.animation_duration_move; c->animation.action = MOVE; } @@ -981,7 +980,7 @@ void resize(Client *c, struct wlr_box geo, int32_t interact) { } bool hit_no_border = check_hit_no_border(c); - if (hit_no_border && smartgaps) { + if (hit_no_border && config.smartgaps) { c->bw = 0; c->fake_no_border = true; } @@ -1048,12 +1047,12 @@ bool client_draw_fadeout_frame(Client *c) { void client_set_focused_opacity_animation(Client *c) { float *border_color = get_border_color(c); - if (!animations) { + if (!config.animations) { setborder_color(c); return; } - c->opacity_animation.duration = animation_duration_focus; + c->opacity_animation.duration = config.animation_duration_focus; memcpy(c->opacity_animation.target_border_color, border_color, sizeof(c->opacity_animation.target_border_color)); c->opacity_animation.target_opacity = c->focused_opacity; @@ -1067,15 +1066,14 @@ void client_set_focused_opacity_animation(Client *c) { } void client_set_unfocused_opacity_animation(Client *c) { - // Start border color animation to unfocused float *border_color = get_border_color(c); - if (!animations) { + if (!config.animations) { setborder_color(c); return; } - c->opacity_animation.duration = animation_duration_focus; + c->opacity_animation.duration = config.animation_duration_focus; memcpy(c->opacity_animation.target_border_color, border_color, sizeof(c->opacity_animation.target_border_color)); // Start opacity animation to unfocused @@ -1110,13 +1108,14 @@ bool client_apply_focus_opacity(Client *c) { double opacity_eased_progress = find_animation_curve_at(linear_progress, OPAFADEIN); - float percent = - animation_fade_in && !c->nofadein ? opacity_eased_progress : 1.0; + float percent = config.animation_fade_in && !c->nofadein + ? opacity_eased_progress + : 1.0; float opacity = c == selmon->sel ? c->focused_opacity : c->unfocused_opacity; - float target_opacity = - percent * (1.0 - fadein_begin_opacity) + fadein_begin_opacity; + float target_opacity = percent * (1.0 - config.fadein_begin_opacity) + + config.fadein_begin_opacity; if (target_opacity > opacity) { target_opacity = opacity; } @@ -1126,7 +1125,7 @@ bool client_apply_focus_opacity(Client *c) { c->opacity_animation.current_opacity = target_opacity; client_set_opacity(c, target_opacity); client_set_border_color(c, c->opacity_animation.target_border_color); - } else if (animations && c->opacity_animation.running) { + } else if (config.animations && c->opacity_animation.running) { struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); @@ -1187,7 +1186,7 @@ bool client_draw_frame(Client *c) { return client_apply_focus_opacity(c); } - if (animations && c->animation.running) { + if (config.animations && c->animation.running) { client_animation_next_tick(c); } else { wlr_scene_node_set_position(&c->scene->node, c->pending.x, diff --git a/src/animation/common.h b/src/animation/common.h index 9f022db2..10b66b91 100644 --- a/src/animation/common.h +++ b/src/animation/common.h @@ -2,21 +2,21 @@ struct dvec2 calculate_animation_curve_at(double t, int32_t type) { struct dvec2 point; double *animation_curve; if (type == MOVE) { - animation_curve = animation_curve_move; + animation_curve = config.animation_curve_move; } else if (type == OPEN) { - animation_curve = animation_curve_open; + animation_curve = config.animation_curve_open; } else if (type == TAG) { - animation_curve = animation_curve_tag; + animation_curve = config.animation_curve_tag; } else if (type == CLOSE) { - animation_curve = animation_curve_close; + animation_curve = config.animation_curve_close; } else if (type == FOCUS) { - animation_curve = animation_curve_focus; + animation_curve = config.animation_curve_focus; } else if (type == OPAFADEIN) { - animation_curve = animation_curve_opafadein; + animation_curve = config.animation_curve_opafadein; } else if (type == OPAFADEOUT) { - animation_curve = animation_curve_opafadeout; + animation_curve = config.animation_curve_opafadeout; } else { - animation_curve = animation_curve_move; + animation_curve = config.animation_curve_move; } point.x = 3 * t * (1 - t) * (1 - t) * animation_curve[0] + diff --git a/src/animation/layer.h b/src/animation/layer.h index 568d52b3..90f1b4ee 100644 --- a/src/animation/layer.h +++ b/src/animation/layer.h @@ -156,7 +156,7 @@ void layer_draw_shadow(LayerSurface *l) { if (!l->mapped || !l->shadow) return; - if (!shadows || !layer_shadows || l->noshadow) { + if (!config.shadows || !config.layer_shadows || l->noshadow) { wlr_scene_shadow_set_size(l->shadow, 0, 0); return; } @@ -164,9 +164,8 @@ void layer_draw_shadow(LayerSurface *l) { int32_t width, height; layer_actual_size(l, &width, &height); - int32_t delta = shadows_size; + int32_t delta = config.shadows_size; - /* we calculate where to clip the shadow */ struct wlr_box layer_box = { .x = 0, .y = 0, @@ -175,23 +174,21 @@ void layer_draw_shadow(LayerSurface *l) { }; struct wlr_box shadow_box = { - .x = shadows_position_x, - .y = shadows_position_y, + .x = config.shadows_position_x, + .y = config.shadows_position_y, .width = width + 2 * delta, .height = height + 2 * delta, }; struct wlr_box intersection_box; wlr_box_intersection(&intersection_box, &layer_box, &shadow_box); - /* clipped region takes shadow relative coords, so we translate everything - * by its position */ - intersection_box.x -= shadows_position_x; - intersection_box.y -= shadows_position_y; + intersection_box.x -= config.shadows_position_x; + intersection_box.y -= config.shadows_position_y; struct clipped_region clipped_region = { .area = intersection_box, - .corner_radius = border_radius, - .corners = border_radius_location_default, + .corner_radius = config.border_radius, + .corners = config.border_radius_location_default, }; wlr_scene_node_set_position(&l->shadow->node, shadow_box.x, shadow_box.y); @@ -261,7 +258,7 @@ void fadeout_layer_animation_next_tick(LayerSurface *l) { buffer_data.height = height; if ((!l->animation_type_close && - strcmp(layer_animation_type_close, "zoom") == 0) || + strcmp(config.layer_animation_type_close, "zoom") == 0) || (l->animation_type_close && strcmp(l->animation_type_close, "zoom") == 0)) { wlr_scene_node_for_each_buffer(&l->scene->node, @@ -279,12 +276,12 @@ void fadeout_layer_animation_next_tick(LayerSurface *l) { double opacity_eased_progress = find_animation_curve_at(animation_passed, OPAFADEOUT); - double percent = fadeout_begin_opacity - - (opacity_eased_progress * fadeout_begin_opacity); + double percent = config.fadeout_begin_opacity - + (opacity_eased_progress * config.fadeout_begin_opacity); double opacity = MAX(percent, 0.0f); - if (animation_fade_out) + if (config.animation_fade_out) wlr_scene_node_for_each_buffer(&l->scene->node, scene_buffer_apply_opacity, &opacity); @@ -327,11 +324,11 @@ void layer_animation_next_tick(LayerSurface *l) { find_animation_curve_at(animation_passed, OPAFADEIN); double opacity = - MIN(fadein_begin_opacity + - opacity_eased_progress * (1.0 - fadein_begin_opacity), + MIN(config.fadein_begin_opacity + + opacity_eased_progress * (1.0 - config.fadein_begin_opacity), 1.0f); - if (animation_fade_in) + if (config.animation_fade_in) wlr_scene_node_for_each_buffer(&l->scene->node, scene_buffer_apply_opacity, &opacity); @@ -347,7 +344,7 @@ void layer_animation_next_tick(LayerSurface *l) { } if ((!l->animation_type_open && - strcmp(layer_animation_type_open, "zoom") == 0) || + strcmp(config.layer_animation_type_open, "zoom") == 0) || (l->animation_type_open && strcmp(l->animation_type_open, "zoom") == 0)) { wlr_scene_node_for_each_buffer( @@ -370,7 +367,7 @@ void layer_animation_next_tick(LayerSurface *l) { void init_fadeout_layers(LayerSurface *l) { - if (!animations || !layer_animations || l->noanim) { + if (!config.animations || !config.layer_animations || l->noanim) { return; } @@ -380,7 +377,7 @@ void init_fadeout_layers(LayerSurface *l) { if ((l->animation_type_close && strcmp(l->animation_type_close, "none") == 0) || (!l->animation_type_close && - strcmp(layer_animation_type_close, "none") == 0)) { + strcmp(config.layer_animation_type_close, "none") == 0)) { return; } @@ -403,7 +400,7 @@ void init_fadeout_layers(LayerSurface *l) { return; } - fadeout_layer->animation.duration = animation_duration_close; + fadeout_layer->animation.duration = config.animation_duration_close; fadeout_layer->geom = fadeout_layer->current = fadeout_layer->animainit_geom = fadeout_layer->animation.initial = l->animation.current; @@ -419,14 +416,14 @@ void init_fadeout_layers(LayerSurface *l) { fadeout_layer->animation.initial.y = 0; if ((!l->animation_type_close && - strcmp(layer_animation_type_close, "zoom") == 0) || + strcmp(config.layer_animation_type_close, "zoom") == 0) || (l->animation_type_close && strcmp(l->animation_type_close, "zoom") == 0)) { // 算出要设置的绝对坐标和大小 fadeout_layer->current.width = - (float)l->animation.current.width * zoom_end_ratio; + (float)l->animation.current.width * config.zoom_end_ratio; fadeout_layer->current.height = - (float)l->animation.current.height * zoom_end_ratio; + (float)l->animation.current.height * config.zoom_end_ratio; fadeout_layer->current.x = usable_area.x + usable_area.width / 2 - fadeout_layer->current.width / 2; fadeout_layer->current.y = usable_area.y + usable_area.height / 2 - @@ -438,7 +435,7 @@ void init_fadeout_layers(LayerSurface *l) { fadeout_layer->current.y - l->animation.current.y; } else if ((!l->animation_type_close && - strcmp(layer_animation_type_close, "slide") == 0) || + strcmp(config.layer_animation_type_close, "slide") == 0) || (l->animation_type_close && strcmp(l->animation_type_close, "slide") == 0)) { // 获取slide动画的结束绝对坐标和大小 @@ -483,17 +480,18 @@ void layer_set_pending_state(LayerSurface *l) { if (l->animation.action == OPEN && !l->animation.running) { if ((!l->animation_type_open && - strcmp(layer_animation_type_open, "zoom") == 0) || + strcmp(config.layer_animation_type_open, "zoom") == 0) || (l->animation_type_open && strcmp(l->animation_type_open, "zoom") == 0)) { - l->animainit_geom.width = l->geom.width * zoom_initial_ratio; - l->animainit_geom.height = l->geom.height * zoom_initial_ratio; + l->animainit_geom.width = l->geom.width * config.zoom_initial_ratio; + l->animainit_geom.height = + l->geom.height * config.zoom_initial_ratio; l->animainit_geom.x = usable_area.x + usable_area.width / 2 - l->animainit_geom.width / 2; l->animainit_geom.y = usable_area.y + usable_area.height / 2 - l->animainit_geom.height / 2; } else if ((!l->animation_type_open && - strcmp(layer_animation_type_open, "slide") == 0) || + strcmp(config.layer_animation_type_open, "slide") == 0) || (l->animation_type_open && strcmp(l->animation_type_open, "slide") == 0)) { @@ -507,8 +505,7 @@ void layer_set_pending_state(LayerSurface *l) { } else { l->animainit_geom = l->animation.current; } - // 判断是否需要动画 - if (!animations || !layer_animations || l->noanim || + if (!config.animations || !config.layer_animations || l->noanim || l->layer_surface->current.layer == ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND || l->layer_surface->current.layer == ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM) { @@ -520,7 +517,7 @@ void layer_set_pending_state(LayerSurface *l) { if (((l->animation_type_open && strcmp(l->animation_type_open, "none") == 0) || (!l->animation_type_open && - strcmp(layer_animation_type_open, "none") == 0)) && + strcmp(config.layer_animation_type_open, "none") == 0)) && l->animation.action == OPEN) { l->animation.should_animate = false; } @@ -567,7 +564,8 @@ bool layer_draw_frame(LayerSurface *l) { return false; } - if (animations && layer_animations && l->animation.running && !l->noanim) { + if (config.animations && config.layer_animations && l->animation.running && + !l->noanim) { layer_animation_next_tick(l); layer_draw_shadow(l); } else { diff --git a/src/animation/tag.h b/src/animation/tag.h index 8e65a93a..18eef56a 100644 --- a/src/animation/tag.h +++ b/src/animation/tag.h @@ -7,11 +7,11 @@ void set_tagin_animation(Monitor *m, Client *c) { if (m->pertag->curtag > m->pertag->prevtag) { - c->animainit_geom.x = tag_animation_direction == VERTICAL + c->animainit_geom.x = config.tag_animation_direction == VERTICAL ? c->animation.current.x : MAX(c->mon->m.x + c->mon->m.width, c->geom.x + c->mon->m.width); - c->animainit_geom.y = tag_animation_direction == VERTICAL + c->animainit_geom.y = config.tag_animation_direction == VERTICAL ? MAX(c->mon->m.y + c->mon->m.height, c->geom.y + c->mon->m.height) : c->animation.current.y; @@ -19,11 +19,11 @@ void set_tagin_animation(Monitor *m, Client *c) { } else { c->animainit_geom.x = - tag_animation_direction == VERTICAL + config.tag_animation_direction == VERTICAL ? c->animation.current.x : MIN(m->m.x - c->geom.width, c->geom.x - c->mon->m.width); c->animainit_geom.y = - tag_animation_direction == VERTICAL + config.tag_animation_direction == VERTICAL ? MIN(m->m.y - c->geom.height, c->geom.y - c->mon->m.height) : c->animation.current.y; } @@ -39,7 +39,8 @@ void set_arrange_visible(Monitor *m, Client *c, bool want_animation) { client_set_suspended(c, false); if (!c->animation.tag_from_rule && want_animation && - m->pertag->prevtag != 0 && m->pertag->curtag != 0 && animations) { + m->pertag->prevtag != 0 && m->pertag->curtag != 0 && + config.animations) { c->animation.tagining = true; set_tagin_animation(m, c); } else { @@ -57,10 +58,10 @@ void set_tagout_animation(Monitor *m, Client *c) { if (m->pertag->curtag > m->pertag->prevtag) { c->pending = c->geom; c->pending.x = - tag_animation_direction == VERTICAL + config.tag_animation_direction == VERTICAL ? c->animation.current.x : MIN(c->mon->m.x - c->geom.width, c->geom.x - c->mon->m.width); - c->pending.y = tag_animation_direction == VERTICAL + c->pending.y = config.tag_animation_direction == VERTICAL ? MIN(c->mon->m.y - c->geom.height, c->geom.y - c->mon->m.height) : c->animation.current.y; @@ -68,11 +69,11 @@ void set_tagout_animation(Monitor *m, Client *c) { resize(c, c->geom, 0); } else { c->pending = c->geom; - c->pending.x = tag_animation_direction == VERTICAL + c->pending.x = config.tag_animation_direction == VERTICAL ? c->animation.current.x : MAX(c->mon->m.x + c->mon->m.width, c->geom.x + c->mon->m.width); - c->pending.y = tag_animation_direction == VERTICAL + c->pending.y = config.tag_animation_direction == VERTICAL ? MAX(c->mon->m.y + c->mon->m.height, c->geom.y + c->mon->m.height) : c->animation.current.y; @@ -82,7 +83,8 @@ void set_tagout_animation(Monitor *m, Client *c) { void set_arrange_hidden(Monitor *m, Client *c, bool want_animation) { if ((c->tags & (1 << (m->pertag->prevtag - 1))) && - m->pertag->prevtag != 0 && m->pertag->curtag != 0 && animations) { + m->pertag->prevtag != 0 && m->pertag->curtag != 0 && + config.animations) { c->animation.tagouting = true; c->animation.tagining = false; set_tagout_animation(m, c); diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 3220b713..ffb8414b 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -285,6 +285,7 @@ typedef struct { int32_t blur_layer; int32_t blur_optimized; int32_t border_radius; + int32_t border_radius_location_default; struct blur_data blur_params; int32_t shadows; int32_t shadow_only_floating; @@ -312,7 +313,8 @@ typedef struct { float globalcolor[4]; float overlaycolor[4]; - char autostart[3][256]; + int32_t log_level; + uint32_t capslock; ConfigTagRule *tag_rules; // 动态数组 int32_t tag_rules_count; // 数量 @@ -363,6 +365,11 @@ typedef struct { int32_t allow_lock_transparent; struct xkb_rule_names xkb_rules; + char xkb_rules_rules[128]; + char xkb_rules_model[128]; + char xkb_rules_layout[128]; + char xkb_rules_variant[128]; + char xkb_rules_options[128]; char keymode[28]; @@ -1428,25 +1435,25 @@ bool parse_option(Config *config, char *key, char *value) { } else if (strcmp(key, "unfocused_opacity") == 0) { config->unfocused_opacity = atof(value); } else if (strcmp(key, "xkb_rules_rules") == 0) { - strncpy(xkb_rules_rules, value, sizeof(xkb_rules_rules) - 1); - xkb_rules_rules[sizeof(xkb_rules_rules) - 1] = - '\0'; // 确保字符串以 null 结尾 + strncpy(config->xkb_rules_rules, value, + sizeof(config->xkb_rules_rules) - 1); + config->xkb_rules_rules[sizeof(config->xkb_rules_rules) - 1] = '\0'; } else if (strcmp(key, "xkb_rules_model") == 0) { - strncpy(xkb_rules_model, value, sizeof(xkb_rules_model) - 1); - xkb_rules_model[sizeof(xkb_rules_model) - 1] = - '\0'; // 确保字符串以 null 结尾 + strncpy(config->xkb_rules_model, value, + sizeof(config->xkb_rules_model) - 1); + config->xkb_rules_model[sizeof(config->xkb_rules_model) - 1] = '\0'; } else if (strcmp(key, "xkb_rules_layout") == 0) { - strncpy(xkb_rules_layout, value, sizeof(xkb_rules_layout) - 1); - xkb_rules_layout[sizeof(xkb_rules_layout) - 1] = - '\0'; // 确保字符串以 null 结尾 + strncpy(config->xkb_rules_layout, value, + sizeof(config->xkb_rules_layout) - 1); + config->xkb_rules_layout[sizeof(config->xkb_rules_layout) - 1] = '\0'; } else if (strcmp(key, "xkb_rules_variant") == 0) { - strncpy(xkb_rules_variant, value, sizeof(xkb_rules_variant) - 1); - xkb_rules_variant[sizeof(xkb_rules_variant) - 1] = - '\0'; // 确保字符串以 null 结尾 + strncpy(config->xkb_rules_variant, value, + sizeof(config->xkb_rules_variant) - 1); + config->xkb_rules_variant[sizeof(config->xkb_rules_variant) - 1] = '\0'; } else if (strcmp(key, "xkb_rules_options") == 0) { - strncpy(xkb_rules_options, value, sizeof(xkb_rules_options) - 1); - xkb_rules_options[sizeof(xkb_rules_options) - 1] = - '\0'; // 确保字符串以 null 结尾 + strncpy(config->xkb_rules_options, value, + sizeof(config->xkb_rules_options) - 1); + config->xkb_rules_options[sizeof(config->xkb_rules_options) - 1] = '\0'; } else if (strcmp(key, "scroller_proportion_preset") == 0) { // 1. 统计 value 中有多少个逗号,确定需要解析的浮点数个数 int32_t count = 0; // 初始化为 0 @@ -3087,363 +3094,347 @@ void free_config(void) { } void override_config(void) { - // 动画启用 - animations = CLAMP_INT(config.animations, 0, 1); - layer_animations = CLAMP_INT(config.layer_animations, 0, 1); - - // 标签动画方向 - tag_animation_direction = CLAMP_INT(config.tag_animation_direction, 0, 1); - - // 动画淡入淡出设置 - animation_fade_in = CLAMP_INT(config.animation_fade_in, 0, 1); - animation_fade_out = CLAMP_INT(config.animation_fade_out, 0, 1); - zoom_initial_ratio = CLAMP_FLOAT(config.zoom_initial_ratio, 0.1f, 1.0f); - zoom_end_ratio = CLAMP_FLOAT(config.zoom_end_ratio, 0.1f, 1.0f); - fadein_begin_opacity = CLAMP_FLOAT(config.fadein_begin_opacity, 0.0f, 1.0f); - fadeout_begin_opacity = + config.animations = CLAMP_INT(config.animations, 0, 1); + config.layer_animations = CLAMP_INT(config.layer_animations, 0, 1); + config.tag_animation_direction = + CLAMP_INT(config.tag_animation_direction, 0, 1); + config.animation_fade_in = CLAMP_INT(config.animation_fade_in, 0, 1); + config.animation_fade_out = CLAMP_INT(config.animation_fade_out, 0, 1); + config.zoom_initial_ratio = + CLAMP_FLOAT(config.zoom_initial_ratio, 0.1f, 1.0f); + config.zoom_end_ratio = CLAMP_FLOAT(config.zoom_end_ratio, 0.1f, 1.0f); + config.fadein_begin_opacity = + CLAMP_FLOAT(config.fadein_begin_opacity, 0.0f, 1.0f); + config.fadeout_begin_opacity = CLAMP_FLOAT(config.fadeout_begin_opacity, 0.0f, 1.0f); - - // 打开关闭动画类型 - animation_type_open = config.animation_type_open; - animation_type_close = config.animation_type_close; - - // layer打开关闭动画类型 - layer_animation_type_open = config.layer_animation_type_open; - layer_animation_type_close = config.layer_animation_type_close; - - // 动画时间限制在合理范围(1-50000ms) - animation_duration_move = + config.animation_duration_move = CLAMP_INT(config.animation_duration_move, 1, 50000); - animation_duration_open = + config.animation_duration_open = CLAMP_INT(config.animation_duration_open, 1, 50000); - animation_duration_tag = CLAMP_INT(config.animation_duration_tag, 1, 50000); - animation_duration_close = + config.animation_duration_tag = + CLAMP_INT(config.animation_duration_tag, 1, 50000); + config.animation_duration_close = CLAMP_INT(config.animation_duration_close, 1, 50000); - animation_duration_focus = + config.animation_duration_focus = CLAMP_INT(config.animation_duration_focus, 1, 50000); - - // 滚动布局设置 - scroller_default_proportion = + config.scroller_default_proportion = CLAMP_FLOAT(config.scroller_default_proportion, 0.1f, 1.0f); - scroller_default_proportion_single = + config.scroller_default_proportion_single = CLAMP_FLOAT(config.scroller_default_proportion_single, 0.1f, 1.0f); - scroller_ignore_proportion_single = + config.scroller_ignore_proportion_single = CLAMP_INT(config.scroller_ignore_proportion_single, 0, 1); - scroller_focus_center = CLAMP_INT(config.scroller_focus_center, 0, 1); - scroller_prefer_center = CLAMP_INT(config.scroller_prefer_center, 0, 1); - scroller_prefer_overspread = + config.scroller_focus_center = + CLAMP_INT(config.scroller_focus_center, 0, 1); + config.scroller_prefer_center = + CLAMP_INT(config.scroller_prefer_center, 0, 1); + config.scroller_prefer_overspread = CLAMP_INT(config.scroller_prefer_overspread, 0, 1); - edge_scroller_pointer_focus = + config.edge_scroller_pointer_focus = CLAMP_INT(config.edge_scroller_pointer_focus, 0, 1); - scroller_structs = CLAMP_INT(config.scroller_structs, 0, 1000); - - // 主从布局设置 - default_mfact = CLAMP_FLOAT(config.default_mfact, 0.1f, 0.9f); - default_nmaster = CLAMP_INT(config.default_nmaster, 1, 1000); - center_master_overspread = CLAMP_INT(config.center_master_overspread, 0, 1); - center_when_single_stack = CLAMP_INT(config.center_when_single_stack, 0, 1); - new_is_master = CLAMP_INT(config.new_is_master, 0, 1); - - // 概述模式设置 - hotarea_size = CLAMP_INT(config.hotarea_size, 1, 1000); - hotarea_corner = CLAMP_INT(config.hotarea_corner, 0, 3); - enable_hotarea = CLAMP_INT(config.enable_hotarea, 0, 1); - ov_tab_mode = CLAMP_INT(config.ov_tab_mode, 0, 1); - overviewgappi = CLAMP_INT(config.overviewgappi, 0, 1000); - overviewgappo = CLAMP_INT(config.overviewgappo, 0, 1000); - - // 杂项设置 - xwayland_persistence = CLAMP_INT(config.xwayland_persistence, 0, 1); - syncobj_enable = CLAMP_INT(config.syncobj_enable, 0, 1); - drag_tile_refresh_interval = + config.scroller_structs = CLAMP_INT(config.scroller_structs, 0, 1000); + config.default_mfact = CLAMP_FLOAT(config.default_mfact, 0.1f, 0.9f); + config.default_nmaster = CLAMP_INT(config.default_nmaster, 1, 1000); + config.center_master_overspread = + CLAMP_INT(config.center_master_overspread, 0, 1); + config.center_when_single_stack = + CLAMP_INT(config.center_when_single_stack, 0, 1); + config.new_is_master = CLAMP_INT(config.new_is_master, 0, 1); + config.hotarea_size = CLAMP_INT(config.hotarea_size, 1, 1000); + config.hotarea_corner = CLAMP_INT(config.hotarea_corner, 0, 3); + config.enable_hotarea = CLAMP_INT(config.enable_hotarea, 0, 1); + config.ov_tab_mode = CLAMP_INT(config.ov_tab_mode, 0, 1); + config.overviewgappi = CLAMP_INT(config.overviewgappi, 0, 1000); + config.overviewgappo = CLAMP_INT(config.overviewgappo, 0, 1000); + config.xwayland_persistence = CLAMP_INT(config.xwayland_persistence, 0, 1); + config.syncobj_enable = CLAMP_INT(config.syncobj_enable, 0, 1); + config.drag_tile_refresh_interval = CLAMP_FLOAT(config.drag_tile_refresh_interval, 1.0f, 16.0f); - drag_floating_refresh_interval = - CLAMP_FLOAT(config.drag_floating_refresh_interval, 1.0f, 16.0f); - drag_tile_to_tile = CLAMP_INT(config.drag_tile_to_tile, 0, 1); - drag_floating_refresh_interval = + config.drag_floating_refresh_interval = CLAMP_FLOAT(config.drag_floating_refresh_interval, 0.0f, 1000.0f); - allow_tearing = CLAMP_INT(config.allow_tearing, 0, 2); - allow_shortcuts_inhibit = CLAMP_INT(config.allow_shortcuts_inhibit, 0, 1); - allow_lock_transparent = CLAMP_INT(config.allow_lock_transparent, 0, 1); - axis_bind_apply_timeout = + config.drag_tile_to_tile = CLAMP_INT(config.drag_tile_to_tile, 0, 1); + config.allow_tearing = CLAMP_INT(config.allow_tearing, 0, 2); + config.allow_shortcuts_inhibit = + CLAMP_INT(config.allow_shortcuts_inhibit, 0, 1); + config.allow_lock_transparent = + CLAMP_INT(config.allow_lock_transparent, 0, 1); + config.axis_bind_apply_timeout = CLAMP_INT(config.axis_bind_apply_timeout, 0, 1000); - focus_on_activate = CLAMP_INT(config.focus_on_activate, 0, 1); - idleinhibit_ignore_visible = + config.focus_on_activate = CLAMP_INT(config.focus_on_activate, 0, 1); + config.idleinhibit_ignore_visible = CLAMP_INT(config.idleinhibit_ignore_visible, 0, 1); - sloppyfocus = CLAMP_INT(config.sloppyfocus, 0, 1); - warpcursor = CLAMP_INT(config.warpcursor, 0, 1); - drag_corner = CLAMP_INT(config.drag_corner, 0, 4); - drag_warp_cursor = CLAMP_INT(config.drag_warp_cursor, 0, 1); - focus_cross_monitor = CLAMP_INT(config.focus_cross_monitor, 0, 1); - exchange_cross_monitor = CLAMP_INT(config.exchange_cross_monitor, 0, 1); - scratchpad_cross_monitor = CLAMP_INT(config.scratchpad_cross_monitor, 0, 1); - focus_cross_tag = CLAMP_INT(config.focus_cross_tag, 0, 1); - view_current_to_back = CLAMP_INT(config.view_current_to_back, 0, 1); - enable_floating_snap = CLAMP_INT(config.enable_floating_snap, 0, 1); - snap_distance = CLAMP_INT(config.snap_distance, 0, 99999); - cursor_size = CLAMP_INT(config.cursor_size, 4, 512); - no_border_when_single = CLAMP_INT(config.no_border_when_single, 0, 1); - no_radius_when_single = CLAMP_INT(config.no_radius_when_single, 0, 1); - cursor_hide_timeout = - CLAMP_INT(config.cursor_hide_timeout, 0, 36000); // 0-10小时 - drag_tile_to_tile = CLAMP_INT(config.drag_tile_to_tile, 0, 1); - single_scratchpad = CLAMP_INT(config.single_scratchpad, 0, 1); - - // 键盘设置 - repeat_rate = CLAMP_INT(config.repeat_rate, 1, 1000); - repeat_delay = CLAMP_INT(config.repeat_delay, 1, 20000); - numlockon = CLAMP_INT(config.numlockon, 0, 1); - - // 触控板设置 - disable_trackpad = CLAMP_INT(config.disable_trackpad, 0, 1); - tap_to_click = CLAMP_INT(config.tap_to_click, 0, 1); - tap_and_drag = CLAMP_INT(config.tap_and_drag, 0, 1); - drag_lock = CLAMP_INT(config.drag_lock, 0, 1); - trackpad_natural_scrolling = + config.sloppyfocus = CLAMP_INT(config.sloppyfocus, 0, 1); + config.warpcursor = CLAMP_INT(config.warpcursor, 0, 1); + config.drag_corner = CLAMP_INT(config.drag_corner, 0, 4); + config.drag_warp_cursor = CLAMP_INT(config.drag_warp_cursor, 0, 1); + config.focus_cross_monitor = CLAMP_INT(config.focus_cross_monitor, 0, 1); + config.exchange_cross_monitor = + CLAMP_INT(config.exchange_cross_monitor, 0, 1); + config.scratchpad_cross_monitor = + CLAMP_INT(config.scratchpad_cross_monitor, 0, 1); + config.focus_cross_tag = CLAMP_INT(config.focus_cross_tag, 0, 1); + config.view_current_to_back = CLAMP_INT(config.view_current_to_back, 0, 1); + config.enable_floating_snap = CLAMP_INT(config.enable_floating_snap, 0, 1); + config.snap_distance = CLAMP_INT(config.snap_distance, 0, 99999); + config.cursor_size = CLAMP_INT(config.cursor_size, 4, 512); + config.no_border_when_single = + CLAMP_INT(config.no_border_when_single, 0, 1); + config.no_radius_when_single = + CLAMP_INT(config.no_radius_when_single, 0, 1); + config.cursor_hide_timeout = + CLAMP_INT(config.cursor_hide_timeout, 0, 36000); + config.single_scratchpad = CLAMP_INT(config.single_scratchpad, 0, 1); + config.repeat_rate = CLAMP_INT(config.repeat_rate, 1, 1000); + config.repeat_delay = CLAMP_INT(config.repeat_delay, 1, 20000); + config.numlockon = CLAMP_INT(config.numlockon, 0, 1); + config.disable_trackpad = CLAMP_INT(config.disable_trackpad, 0, 1); + config.tap_to_click = CLAMP_INT(config.tap_to_click, 0, 1); + config.tap_and_drag = CLAMP_INT(config.tap_and_drag, 0, 1); + config.drag_lock = CLAMP_INT(config.drag_lock, 0, 1); + config.trackpad_natural_scrolling = CLAMP_INT(config.trackpad_natural_scrolling, 0, 1); - disable_while_typing = CLAMP_INT(config.disable_while_typing, 0, 1); - left_handed = CLAMP_INT(config.left_handed, 0, 1); - middle_button_emulation = CLAMP_INT(config.middle_button_emulation, 0, 1); - swipe_min_threshold = CLAMP_INT(config.swipe_min_threshold, 1, 1000); - - // 鼠标设置 - mouse_natural_scrolling = CLAMP_INT(config.mouse_natural_scrolling, 0, 1); - accel_profile = CLAMP_INT(config.accel_profile, 0, 2); - accel_speed = CLAMP_FLOAT(config.accel_speed, -1.0f, 1.0f); - scroll_method = CLAMP_INT(config.scroll_method, 0, 4); - scroll_button = CLAMP_INT(config.scroll_button, 272, 276); - click_method = CLAMP_INT(config.click_method, 0, 2); - send_events_mode = CLAMP_INT(config.send_events_mode, 0, 2); - button_map = CLAMP_INT(config.button_map, 0, 1); - axis_scroll_factor = CLAMP_FLOAT(config.axis_scroll_factor, 0.1f, 10.0f); - - // 外观设置 - gappih = CLAMP_INT(config.gappih, 0, 1000); - gappiv = CLAMP_INT(config.gappiv, 0, 1000); - gappoh = CLAMP_INT(config.gappoh, 0, 1000); - gappov = CLAMP_INT(config.gappov, 0, 1000); - scratchpad_width_ratio = + config.disable_while_typing = CLAMP_INT(config.disable_while_typing, 0, 1); + config.left_handed = CLAMP_INT(config.left_handed, 0, 1); + config.middle_button_emulation = + CLAMP_INT(config.middle_button_emulation, 0, 1); + config.swipe_min_threshold = CLAMP_INT(config.swipe_min_threshold, 1, 1000); + config.mouse_natural_scrolling = + CLAMP_INT(config.mouse_natural_scrolling, 0, 1); + config.accel_profile = CLAMP_INT(config.accel_profile, 0, 2); + config.accel_speed = CLAMP_FLOAT(config.accel_speed, -1.0f, 1.0f); + config.scroll_method = CLAMP_INT(config.scroll_method, 0, 4); + config.scroll_button = CLAMP_INT(config.scroll_button, 272, 276); + config.click_method = CLAMP_INT(config.click_method, 0, 2); + config.send_events_mode = CLAMP_INT(config.send_events_mode, 0, 2); + config.button_map = CLAMP_INT(config.button_map, 0, 1); + config.axis_scroll_factor = + CLAMP_FLOAT(config.axis_scroll_factor, 0.1f, 10.0f); + config.gappih = CLAMP_INT(config.gappih, 0, 1000); + config.gappiv = CLAMP_INT(config.gappiv, 0, 1000); + config.gappoh = CLAMP_INT(config.gappoh, 0, 1000); + config.gappov = CLAMP_INT(config.gappov, 0, 1000); + config.scratchpad_width_ratio = CLAMP_FLOAT(config.scratchpad_width_ratio, 0.1f, 1.0f); - scratchpad_height_ratio = + config.scratchpad_height_ratio = CLAMP_FLOAT(config.scratchpad_height_ratio, 0.1f, 1.0f); - borderpx = CLAMP_INT(config.borderpx, 0, 200); - smartgaps = CLAMP_INT(config.smartgaps, 0, 1); - - blur = CLAMP_INT(config.blur, 0, 1); - blur_layer = CLAMP_INT(config.blur_layer, 0, 1); - blur_optimized = CLAMP_INT(config.blur_optimized, 0, 1); - border_radius = CLAMP_INT(config.border_radius, 0, 100); - blur_params.num_passes = CLAMP_INT(config.blur_params.num_passes, 0, 10); - blur_params.radius = CLAMP_INT(config.blur_params.radius, 0, 100); - blur_params.noise = CLAMP_FLOAT(config.blur_params.noise, 0, 1); - blur_params.brightness = CLAMP_FLOAT(config.blur_params.brightness, 0, 1); - blur_params.contrast = CLAMP_FLOAT(config.blur_params.contrast, 0, 1); - blur_params.saturation = CLAMP_FLOAT(config.blur_params.saturation, 0, 1); - shadows = CLAMP_INT(config.shadows, 0, 1); - shadow_only_floating = CLAMP_INT(config.shadow_only_floating, 0, 1); - layer_shadows = CLAMP_INT(config.layer_shadows, 0, 1); - shadows_size = CLAMP_INT(config.shadows_size, 0, 100); - shadows_blur = CLAMP_INT(config.shadows_blur, 0, 100); - shadows_position_x = CLAMP_INT(config.shadows_position_x, -1000, 1000); - shadows_position_y = CLAMP_INT(config.shadows_position_y, -1000, 1000); - focused_opacity = CLAMP_FLOAT(config.focused_opacity, 0.0f, 1.0f); - unfocused_opacity = CLAMP_FLOAT(config.unfocused_opacity, 0.0f, 1.0f); - memcpy(shadowscolor, config.shadowscolor, sizeof(shadowscolor)); - - // 复制颜色数组 - memcpy(rootcolor, config.rootcolor, sizeof(rootcolor)); - memcpy(bordercolor, config.bordercolor, sizeof(bordercolor)); - memcpy(focuscolor, config.focuscolor, sizeof(focuscolor)); - memcpy(maximizescreencolor, config.maximizescreencolor, - sizeof(maximizescreencolor)); - memcpy(urgentcolor, config.urgentcolor, sizeof(urgentcolor)); - memcpy(scratchpadcolor, config.scratchpadcolor, sizeof(scratchpadcolor)); - memcpy(globalcolor, config.globalcolor, sizeof(globalcolor)); - memcpy(overlaycolor, config.overlaycolor, sizeof(overlaycolor)); - - // 复制动画曲线 - memcpy(animation_curve_move, config.animation_curve_move, - sizeof(animation_curve_move)); - memcpy(animation_curve_open, config.animation_curve_open, - sizeof(animation_curve_open)); - memcpy(animation_curve_tag, config.animation_curve_tag, - sizeof(animation_curve_tag)); - memcpy(animation_curve_close, config.animation_curve_close, - sizeof(animation_curve_close)); - memcpy(animation_curve_focus, config.animation_curve_focus, - sizeof(animation_curve_focus)); - memcpy(animation_curve_opafadein, config.animation_curve_opafadein, - sizeof(animation_curve_opafadein)); - memcpy(animation_curve_opafadeout, config.animation_curve_opafadeout, - sizeof(animation_curve_opafadeout)); + config.borderpx = CLAMP_INT(config.borderpx, 0, 200); + config.smartgaps = CLAMP_INT(config.smartgaps, 0, 1); + config.blur = CLAMP_INT(config.blur, 0, 1); + config.blur_layer = CLAMP_INT(config.blur_layer, 0, 1); + config.blur_optimized = CLAMP_INT(config.blur_optimized, 0, 1); + config.border_radius = CLAMP_INT(config.border_radius, 0, 100); + config.blur_params.num_passes = + CLAMP_INT(config.blur_params.num_passes, 0, 10); + config.blur_params.radius = CLAMP_INT(config.blur_params.radius, 0, 100); + config.blur_params.noise = CLAMP_FLOAT(config.blur_params.noise, 0, 1); + config.blur_params.brightness = + CLAMP_FLOAT(config.blur_params.brightness, 0, 1); + config.blur_params.contrast = + CLAMP_FLOAT(config.blur_params.contrast, 0, 1); + config.blur_params.saturation = + CLAMP_FLOAT(config.blur_params.saturation, 0, 1); + config.shadows = CLAMP_INT(config.shadows, 0, 1); + config.shadow_only_floating = CLAMP_INT(config.shadow_only_floating, 0, 1); + config.layer_shadows = CLAMP_INT(config.layer_shadows, 0, 1); + config.shadows_size = CLAMP_INT(config.shadows_size, 0, 100); + config.shadows_blur = CLAMP_INT(config.shadows_blur, 0, 100); + config.shadows_position_x = + CLAMP_INT(config.shadows_position_x, -1000, 1000); + config.shadows_position_y = + CLAMP_INT(config.shadows_position_y, -1000, 1000); + config.focused_opacity = CLAMP_FLOAT(config.focused_opacity, 0.0f, 1.0f); + config.unfocused_opacity = + CLAMP_FLOAT(config.unfocused_opacity, 0.0f, 1.0f); } void set_value_default() { - /* animaion */ - config.animations = animations; // 是否启用动画 - config.layer_animations = layer_animations; // 是否启用layer动画 - config.animation_fade_in = animation_fade_in; // Enable animation fade in - config.animation_fade_out = animation_fade_out; // Enable animation fade out - config.tag_animation_direction = tag_animation_direction; // 标签动画方向 - config.zoom_initial_ratio = zoom_initial_ratio; // 动画起始窗口比例 - config.zoom_end_ratio = zoom_end_ratio; // 动画结束窗口比例 - config.fadein_begin_opacity = - fadein_begin_opacity; // Begin opac window ratio for animations - config.fadeout_begin_opacity = fadeout_begin_opacity; - config.animation_duration_move = - animation_duration_move; // Animation move speed - config.animation_duration_open = - animation_duration_open; // Animation open speed - config.animation_duration_tag = - animation_duration_tag; // Animation tag speed - config.animation_duration_close = - animation_duration_close; // Animation tag speed - config.animation_duration_focus = - animation_duration_focus; // Animation focus opacity speed + config.animations = 1; + config.layer_animations = 0; + config.animation_fade_in = 1; + config.animation_fade_out = 1; + config.tag_animation_direction = HORIZONTAL; + config.zoom_initial_ratio = 0.3f; + config.zoom_end_ratio = 0.8f; + config.fadein_begin_opacity = 0.5f; + config.fadeout_begin_opacity = 0.5f; + config.animation_duration_move = 500; + config.animation_duration_open = 400; + config.animation_duration_tag = 300; + config.animation_duration_close = 300; + config.animation_duration_focus = 0; - /* appearance */ - config.axis_bind_apply_timeout = - axis_bind_apply_timeout; // 滚轮绑定动作的触发的时间间隔 - config.focus_on_activate = - focus_on_activate; // 收到窗口激活请求是否自动跳转聚焦 - config.new_is_master = new_is_master; // 新窗口是否插在头部 - config.default_mfact = default_mfact; // master 窗口比例 - config.default_nmaster = default_nmaster; // 默认master数量 - config.center_master_overspread = - center_master_overspread; // 中心master时是否铺满 - config.center_when_single_stack = - center_when_single_stack; // 单个stack时是否居中 + config.axis_bind_apply_timeout = 100; + config.focus_on_activate = 1; + config.new_is_master = 1; + config.default_mfact = 0.55f; + config.default_nmaster = 1; + config.center_master_overspread = 0; + config.center_when_single_stack = 1; - config.numlockon = numlockon; // 是否打开右边小键盘 + config.log_level = WLR_ERROR; + config.numlockon = 0; + config.capslock = 0; - config.ov_tab_mode = ov_tab_mode; // alt tab切换模式 - config.hotarea_size = hotarea_size; // 热区大小,10x10 - config.hotarea_corner = hotarea_corner; - config.enable_hotarea = enable_hotarea; // 是否启用鼠标热区 - config.smartgaps = smartgaps; /* 1 means no outer gap when there is - only one window */ - config.sloppyfocus = sloppyfocus; /* focus follows mouse */ - config.gappih = gappih; /* horiz inner gap between windows */ - config.gappiv = gappiv; /* vert inner gap between windows */ - config.gappoh = - gappoh; /* horiz outer gap between windows and screen edge */ - config.gappov = gappov; /* vert outer gap between windows and screen edge */ - config.scratchpad_width_ratio = scratchpad_width_ratio; - config.scratchpad_height_ratio = scratchpad_height_ratio; + config.ov_tab_mode = 0; + config.hotarea_size = 10; + config.hotarea_corner = BOTTOM_LEFT; + config.enable_hotarea = 1; + config.smartgaps = 0; + config.sloppyfocus = 1; + config.gappih = 5; + config.gappiv = 5; + config.gappoh = 10; + config.gappov = 10; + config.scratchpad_width_ratio = 0.8f; + config.scratchpad_height_ratio = 0.9f; - config.scroller_structs = scroller_structs; - config.scroller_default_proportion = scroller_default_proportion; - config.scroller_default_proportion_single = - scroller_default_proportion_single; - config.scroller_ignore_proportion_single = - scroller_ignore_proportion_single; - config.scroller_focus_center = scroller_focus_center; - config.scroller_prefer_center = scroller_prefer_center; - config.scroller_prefer_overspread = scroller_prefer_overspread; - config.edge_scroller_pointer_focus = edge_scroller_pointer_focus; - config.focus_cross_monitor = focus_cross_monitor; - config.exchange_cross_monitor = exchange_cross_monitor; - config.scratchpad_cross_monitor = scratchpad_cross_monitor; - config.focus_cross_tag = focus_cross_tag; - config.axis_scroll_factor = axis_scroll_factor; - config.view_current_to_back = view_current_to_back; - config.single_scratchpad = single_scratchpad; - config.xwayland_persistence = xwayland_persistence; - config.syncobj_enable = syncobj_enable; - config.drag_tile_refresh_interval = drag_tile_refresh_interval; - config.drag_floating_refresh_interval = drag_floating_refresh_interval; - config.allow_tearing = allow_tearing; - config.allow_shortcuts_inhibit = allow_shortcuts_inhibit; - config.allow_lock_transparent = allow_lock_transparent; - config.no_border_when_single = no_border_when_single; - config.no_radius_when_single = no_radius_when_single; - config.snap_distance = snap_distance; - config.drag_tile_to_tile = drag_tile_to_tile; - config.enable_floating_snap = enable_floating_snap; - config.swipe_min_threshold = swipe_min_threshold; + config.scroller_structs = 20; + config.scroller_default_proportion = 0.9f; + config.scroller_default_proportion_single = 1.0f; + config.scroller_ignore_proportion_single = 1; + config.scroller_focus_center = 0; + config.scroller_prefer_center = 0; + config.scroller_prefer_overspread = 1; + config.edge_scroller_pointer_focus = 1; + config.focus_cross_monitor = 0; + config.exchange_cross_monitor = 0; + config.scratchpad_cross_monitor = 0; + config.focus_cross_tag = 0; + config.axis_scroll_factor = 1.0; + config.view_current_to_back = 0; + config.single_scratchpad = 1; + config.xwayland_persistence = 1; + config.syncobj_enable = 0; + config.drag_tile_refresh_interval = 8.0f; + config.drag_floating_refresh_interval = 8.0f; + config.allow_tearing = TEARING_DISABLED; + config.allow_shortcuts_inhibit = SHORTCUTS_INHIBIT_ENABLE; + config.allow_lock_transparent = 0; + config.no_border_when_single = 0; + config.no_radius_when_single = 0; + config.snap_distance = 30; + config.drag_tile_to_tile = 0; + config.enable_floating_snap = 0; + config.swipe_min_threshold = 1; - config.idleinhibit_ignore_visible = - idleinhibit_ignore_visible; /* 1 means idle inhibitors will - disable idle tracking even if it's - surface isn't visible - */ + config.idleinhibit_ignore_visible = 0; - config.borderpx = borderpx; - config.overviewgappi = overviewgappi; /* overview时 窗口与边缘 缝隙大小 */ - config.overviewgappo = overviewgappo; /* overview时 窗口与窗口 缝隙大小 */ - config.cursor_hide_timeout = cursor_hide_timeout; + config.borderpx = 4; + config.overviewgappi = 5; + config.overviewgappo = 30; + config.cursor_hide_timeout = 0; - config.warpcursor = warpcursor; /* Warp cursor to focused client */ - config.drag_corner = drag_corner; - config.drag_warp_cursor = drag_warp_cursor; + config.warpcursor = 1; + config.drag_corner = 3; + config.drag_warp_cursor = 1; - config.repeat_rate = repeat_rate; - config.repeat_delay = repeat_delay; + config.repeat_rate = 25; + config.repeat_delay = 600; - /* Trackpad */ - config.disable_trackpad = disable_trackpad; - config.tap_to_click = tap_to_click; - config.tap_and_drag = tap_and_drag; - config.drag_lock = drag_lock; - config.mouse_natural_scrolling = mouse_natural_scrolling; - config.cursor_size = cursor_size; - config.trackpad_natural_scrolling = trackpad_natural_scrolling; - config.disable_while_typing = disable_while_typing; - config.left_handed = left_handed; - config.middle_button_emulation = middle_button_emulation; - config.accel_profile = accel_profile; - config.accel_speed = accel_speed; - config.scroll_method = scroll_method; - config.scroll_button = scroll_button; - config.click_method = click_method; - config.send_events_mode = send_events_mode; - config.button_map = button_map; + config.disable_trackpad = 0; + config.tap_to_click = 1; + config.tap_and_drag = 1; + config.drag_lock = 1; + config.mouse_natural_scrolling = 0; + config.cursor_size = 24; + config.trackpad_natural_scrolling = 0; + config.disable_while_typing = 1; + config.left_handed = 0; + config.middle_button_emulation = 0; + config.accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; + config.accel_speed = 0.0; + config.scroll_method = LIBINPUT_CONFIG_SCROLL_2FG; + config.scroll_button = 274; + config.click_method = LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS; + config.send_events_mode = LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; + config.button_map = LIBINPUT_CONFIG_TAP_MAP_LRM; - config.blur = blur; - config.blur_layer = blur_layer; - config.blur_optimized = blur_optimized; - config.border_radius = border_radius; - config.blur_params.num_passes = blur_params_num_passes; - config.blur_params.radius = blur_params_radius; - config.blur_params.noise = blur_params_noise; - config.blur_params.brightness = blur_params_brightness; - config.blur_params.contrast = blur_params_contrast; - config.blur_params.saturation = blur_params_saturation; - config.shadows = shadows; - config.shadow_only_floating = shadow_only_floating; - config.layer_shadows = layer_shadows; - config.shadows_size = shadows_size; - config.shadows_blur = shadows_blur; - config.shadows_position_x = shadows_position_x; - config.shadows_position_y = shadows_position_y; - config.focused_opacity = focused_opacity; - config.unfocused_opacity = unfocused_opacity; - memcpy(config.shadowscolor, shadowscolor, sizeof(shadowscolor)); + config.blur = 0; + config.blur_layer = 0; + config.blur_optimized = 1; + config.border_radius = 0; + config.border_radius_location_default = CORNER_LOCATION_ALL; + config.blur_params.num_passes = 1; + config.blur_params.radius = 5; + config.blur_params.noise = 0.02f; + config.blur_params.brightness = 0.9f; + config.blur_params.contrast = 0.9f; + config.blur_params.saturation = 1.2f; + config.shadows = 0; + config.shadow_only_floating = 1; + config.layer_shadows = 0; + config.shadows_size = 10; + config.shadows_blur = 15.0f; + config.shadows_position_x = 0; + config.shadows_position_y = 0; + config.focused_opacity = 1.0f; + config.unfocused_opacity = 1.0f; - memcpy(config.animation_curve_move, animation_curve_move, - sizeof(animation_curve_move)); - memcpy(config.animation_curve_open, animation_curve_open, - sizeof(animation_curve_open)); - memcpy(config.animation_curve_tag, animation_curve_tag, - sizeof(animation_curve_tag)); - memcpy(config.animation_curve_close, animation_curve_close, - sizeof(animation_curve_close)); - memcpy(config.animation_curve_focus, animation_curve_focus, - sizeof(animation_curve_focus)); - memcpy(config.animation_curve_opafadein, animation_curve_opafadein, - sizeof(animation_curve_opafadein)); - memcpy(config.animation_curve_opafadeout, animation_curve_opafadeout, - sizeof(animation_curve_opafadeout)); + config.shadowscolor[0] = 0.0f; + config.shadowscolor[1] = 0.0f; + config.shadowscolor[2] = 0.0f; + config.shadowscolor[3] = 1.0f; - memcpy(config.rootcolor, rootcolor, sizeof(rootcolor)); - memcpy(config.bordercolor, bordercolor, sizeof(bordercolor)); - memcpy(config.focuscolor, focuscolor, sizeof(focuscolor)); - memcpy(config.maximizescreencolor, maximizescreencolor, - sizeof(maximizescreencolor)); - memcpy(config.urgentcolor, urgentcolor, sizeof(urgentcolor)); - memcpy(config.scratchpadcolor, scratchpadcolor, sizeof(scratchpadcolor)); - memcpy(config.globalcolor, globalcolor, sizeof(globalcolor)); - memcpy(config.overlaycolor, overlaycolor, sizeof(overlaycolor)); + config.animation_curve_move[0] = 0.46; + config.animation_curve_move[1] = 1.0; + config.animation_curve_move[2] = 0.29; + config.animation_curve_move[3] = 0.99; + config.animation_curve_open[0] = 0.46; + config.animation_curve_open[1] = 1.0; + config.animation_curve_open[2] = 0.29; + config.animation_curve_open[3] = 0.99; + config.animation_curve_tag[0] = 0.46; + config.animation_curve_tag[1] = 1.0; + config.animation_curve_tag[2] = 0.29; + config.animation_curve_tag[3] = 0.99; + config.animation_curve_close[0] = 0.46; + config.animation_curve_close[1] = 1.0; + config.animation_curve_close[2] = 0.29; + config.animation_curve_close[3] = 0.99; + config.animation_curve_focus[0] = 0.46; + config.animation_curve_focus[1] = 1.0; + config.animation_curve_focus[2] = 0.29; + config.animation_curve_focus[3] = 0.99; + config.animation_curve_opafadein[0] = 0.46; + config.animation_curve_opafadein[1] = 1.0; + config.animation_curve_opafadein[2] = 0.29; + config.animation_curve_opafadein[3] = 0.99; + config.animation_curve_opafadeout[0] = 0.5; + config.animation_curve_opafadeout[1] = 0.5; + config.animation_curve_opafadeout[2] = 0.5; + config.animation_curve_opafadeout[3] = 0.5; + + config.rootcolor[0] = 0x32 / 255.0f; + config.rootcolor[1] = 0x32 / 255.0f; + config.rootcolor[2] = 0x32 / 255.0f; + config.rootcolor[3] = 1.0f; + config.bordercolor[0] = 0x44 / 255.0f; + config.bordercolor[1] = 0x44 / 255.0f; + config.bordercolor[2] = 0x44 / 255.0f; + config.bordercolor[3] = 1.0f; + config.focuscolor[0] = 0xc6 / 255.0f; + config.focuscolor[1] = 0x6b / 255.0f; + config.focuscolor[2] = 0x25 / 255.0f; + config.focuscolor[3] = 1.0f; + config.maximizescreencolor[0] = 0x89 / 255.0f; + config.maximizescreencolor[1] = 0xaa / 255.0f; + config.maximizescreencolor[2] = 0x61 / 255.0f; + config.maximizescreencolor[3] = 1.0f; + config.urgentcolor[0] = 0xad / 255.0f; + config.urgentcolor[1] = 0x40 / 255.0f; + config.urgentcolor[2] = 0x1f / 255.0f; + config.urgentcolor[3] = 1.0f; + config.scratchpadcolor[0] = 0x51 / 255.0f; + config.scratchpadcolor[1] = 0x6c / 255.0f; + config.scratchpadcolor[2] = 0x93 / 255.0f; + config.scratchpadcolor[3] = 1.0f; + config.globalcolor[0] = 0xb1 / 255.0f; + config.globalcolor[1] = 0x53 / 255.0f; + config.globalcolor[2] = 0xa7 / 255.0f; + config.globalcolor[3] = 1.0f; + config.overlaycolor[0] = 0x14 / 255.0f; + config.overlaycolor[1] = 0xa5 / 255.0f; + config.overlaycolor[2] = 0x7c / 255.0f; + config.overlaycolor[3] = 1.0f; } void set_default_key_bindings(Config *config) { @@ -3479,13 +3470,14 @@ bool parse_config(void) { free_config(); - // 重置config结构体,确保所有指针初始化为NULL memset(&config, 0, sizeof(config)); - memset(&xkb_rules_rules, 0, sizeof(xkb_rules_rules)); - memset(&xkb_rules_model, 0, sizeof(xkb_rules_model)); - memset(&xkb_rules_layout, 0, sizeof(xkb_rules_layout)); - memset(&xkb_rules_variant, 0, sizeof(xkb_rules_variant)); - memset(&xkb_rules_options, 0, sizeof(xkb_rules_options)); + + // 重新将xkb_rules指针指向静态数组 + config.xkb_rules.layout = config.xkb_rules_layout; + config.xkb_rules.variant = config.xkb_rules_variant; + config.xkb_rules.options = config.xkb_rules_options; + config.xkb_rules.rules = config.xkb_rules_rules; + config.xkb_rules.model = config.xkb_rules_model; // 初始化动态数组的指针为NULL,避免野指针 config.window_rules = NULL; @@ -3549,7 +3541,7 @@ bool parse_config(void) { } void reset_blur_params(void) { - if (blur) { + if (config.blur) { Monitor *m = NULL; wl_list_for_each(m, &mons, link) { if (m->blur != NULL) { @@ -3559,9 +3551,9 @@ void reset_blur_params(void) { wlr_scene_node_reparent(&m->blur->node, layers[LyrBlur]); wlr_scene_optimized_blur_set_size(m->blur, m->m.width, m->m.height); wlr_scene_set_blur_data( - scene, blur_params.num_passes, blur_params.radius, - blur_params.noise, blur_params.brightness, blur_params.contrast, - blur_params.saturation); + scene, config.blur_params.num_passes, config.blur_params.radius, + config.blur_params.noise, config.blur_params.brightness, + config.blur_params.contrast, config.blur_params.saturation); } } else { Monitor *m = NULL; @@ -3628,11 +3620,12 @@ void reapply_cursor_style(void) { cursor_mgr = NULL; } - cursor_mgr = wlr_xcursor_manager_create(config.cursor_theme, cursor_size); + cursor_mgr = + wlr_xcursor_manager_create(config.cursor_theme, config.cursor_size); - if (cursor_size > 0) { + if (config.cursor_size > 0) { char size_str[16]; - snprintf(size_str, sizeof(size_str), "%d", cursor_size); + snprintf(size_str, sizeof(size_str), "%d", config.cursor_size); setenv("XCURSOR_SIZE", size_str, 1); } @@ -3653,11 +3646,13 @@ void reapply_cursor_style(void) { wlr_cursor_unset_image(cursor); } else { wl_event_source_timer_update(hide_cursor_source, - cursor_hide_timeout * 1000); + config.cursor_hide_timeout * 1000); } } -void reapply_rootbg(void) { wlr_scene_rect_set_color(root_bg, rootcolor); } +void reapply_rootbg(void) { + wlr_scene_rect_set_color(root_bg, config.rootcolor); +} void reapply_border(void) { Client *c = NULL; @@ -3666,7 +3661,7 @@ void reapply_border(void) { wl_list_for_each(c, &clients, link) { if (c && !c->iskilling) { if (!c->isnoborder && !c->isfullscreen) { - c->bw = borderpx; + c->bw = config.borderpx; } } } @@ -3679,7 +3674,7 @@ void reapply_keyboard(void) { continue; } wlr_keyboard_set_repeat_info((struct wlr_keyboard *)id->device_data, - repeat_rate, repeat_delay); + config.repeat_rate, config.repeat_delay); } } @@ -3708,12 +3703,12 @@ void reapply_master(void) { if (!m->wlr_output->enabled) { continue; } - m->pertag->nmasters[i] = default_nmaster; - m->pertag->mfacts[i] = default_mfact; - m->gappih = gappih; - m->gappiv = gappiv; - m->gappoh = gappoh; - m->gappov = gappov; + m->pertag->nmasters[i] = config.default_nmaster; + m->pertag->mfacts[i] = config.default_mfact; + m->gappih = config.gappih; + m->gappiv = config.gappiv; + m->gappoh = config.gappoh; + m->gappov = config.gappov; } } } @@ -3725,8 +3720,8 @@ void parse_tagrule(Monitor *m) { bool match_rule = false; for (i = 0; i <= LENGTH(tags); i++) { - m->pertag->nmasters[i] = default_nmaster; - m->pertag->mfacts[i] = default_mfact; + m->pertag->nmasters[i] = config.default_nmaster; + m->pertag->mfacts[i] = config.default_mfact; } for (i = 0; i < config.tag_rules_count; i++) { diff --git a/src/config/preset.h b/src/config/preset.h index f91da11a..6952518e 100644 --- a/src/config/preset.h +++ b/src/config/preset.h @@ -1,132 +1,9 @@ -// TODO: remove this file in the future, replace all global variables with -// config.xxx +#define MODKEY WLR_MODIFIER_ALT -/* speedie's mango config */ +static const char *tags[] = { + "1", "2", "3", "4", "5", "6", "7", "8", "9", +}; -#define COLOR(hex) \ - {((hex >> 24) & 0xFF) / 255.0f, ((hex >> 16) & 0xFF) / 255.0f, \ - ((hex >> 8) & 0xFF) / 255.0f, (hex & 0xFF) / 255.0f} - -/* animaion */ -char *animation_type_open = "slide"; // 是否启用动画 //slide,zoom -char *animation_type_close = "slide"; // 是否启用动画 //slide,zoom -char *layer_animation_type_open = "slide"; // 是否启用layer动画 //slide,zoom -char *layer_animation_type_close = "slide"; // 是否启用layer动画 //slide,zoom -int32_t animations = 1; // 是否启用动画 -int32_t layer_animations = 0; // 是否启用layer动画 -int32_t tag_animation_direction = HORIZONTAL; // 标签动画方向 -int32_t animation_fade_in = 1; // Enable animation fade in -int32_t animation_fade_out = 1; // Enable animation fade out -float zoom_initial_ratio = 0.3; // 动画起始窗口比例 -float zoom_end_ratio = 0.8; // 动画结束窗口比例 -float fadein_begin_opacity = 0.5; // Begin opac window ratio for animations -float fadeout_begin_opacity = 0.5; // Begin opac window ratio for animations -uint32_t animation_duration_move = 500; // Animation move speed -uint32_t animation_duration_open = 400; // Animation open speed -uint32_t animation_duration_tag = 300; // Animation tag speed -uint32_t animation_duration_close = 300; // Animation close speed -uint32_t animation_duration_focus = 0; // Animation focus opacity speed -double animation_curve_move[4] = {0.46, 1.0, 0.29, 0.99}; // 动画曲线 -double animation_curve_open[4] = {0.46, 1.0, 0.29, 0.99}; // 动画曲线 -double animation_curve_tag[4] = {0.46, 1.0, 0.29, 0.99}; // 动画曲线 -double animation_curve_close[4] = {0.46, 1.0, 0.29, 0.99}; // 动画曲线 -double animation_curve_focus[4] = {0.46, 1.0, 0.29, 0.99}; // 动画曲线 -double animation_curve_opafadein[4] = {0.46, 1.0, 0.29, 0.99}; // 动画曲线 -double animation_curve_opafadeout[4] = {0.5, 0.5, 0.5, 0.5}; // 动画曲线 - -/* appearance */ -uint32_t axis_bind_apply_timeout = 100; // 滚轮绑定动作的触发的时间间隔 -uint32_t focus_on_activate = 1; // 收到窗口激活请求是否自动跳转聚焦 -uint32_t new_is_master = 1; // 新窗口是否插在头部 -double default_mfact = 0.55f; // master 窗口比例 -uint32_t default_nmaster = 1; // 默认master数量 -int32_t center_master_overspread = 0; // 中心master时是否铺满 -int32_t center_when_single_stack = 1; // 单个stack时是否居中 -/* logging */ -int32_t log_level = WLR_ERROR; -uint32_t numlockon = 0; // 是否打开右边小键盘 -uint32_t capslock = 0; // 是否启用快捷键 - -uint32_t ov_tab_mode = 0; // alt tab切换模式 -uint32_t hotarea_size = 10; // 热区大小,10x10 -uint32_t hotarea_corner = BOTTOM_LEFT; -uint32_t enable_hotarea = 1; // 是否启用鼠标热区 -int32_t smartgaps = 0; /* 1 means no outer gap when there is only one window */ -int32_t sloppyfocus = 1; /* focus follows mouse */ -uint32_t gappih = 5; /* horiz inner gap between windows */ -uint32_t gappiv = 5; /* vert inner gap between windows */ -uint32_t gappoh = 10; /* horiz outer gap between windows and screen edge */ -uint32_t gappov = 10; /* vert outer gap between windows and screen edge */ -float scratchpad_width_ratio = 0.8; -float scratchpad_height_ratio = 0.9; - -int32_t scroller_structs = 20; -float scroller_default_proportion = 0.9; -float scroller_default_proportion_single = 1.0; -int32_t scroller_ignore_proportion_single = 1; -int32_t scroller_focus_center = 0; -int32_t scroller_prefer_center = 0; -int32_t scroller_prefer_overspread = 1; -int32_t focus_cross_monitor = 0; -int32_t focus_cross_tag = 0; -int32_t exchange_cross_monitor = 0; -int32_t scratchpad_cross_monitor = 0; -int32_t view_current_to_back = 0; -int32_t no_border_when_single = 0; -int32_t no_radius_when_single = 0; -int32_t snap_distance = 30; -int32_t enable_floating_snap = 0; -int32_t drag_tile_to_tile = 0; -uint32_t cursor_size = 24; -uint32_t cursor_hide_timeout = 0; - -uint32_t swipe_min_threshold = 1; - -int32_t idleinhibit_ignore_visible = - 0; /* 1 means idle inhibitors will disable idle tracking even if it's - surface isn't visible */ -uint32_t borderpx = 4; /* border pixel of windows */ -float rootcolor[] = COLOR(0x323232ff); -float bordercolor[] = COLOR(0x444444ff); -float focuscolor[] = COLOR(0xc66b25ff); -float maximizescreencolor[] = COLOR(0x89aa61ff); -float urgentcolor[] = COLOR(0xad401fff); -float scratchpadcolor[] = COLOR(0x516c93ff); -float globalcolor[] = COLOR(0xb153a7ff); -float overlaycolor[] = COLOR(0x14a57cff); -// char *cursor_theme = "Bibata-Modern-Ice"; - -int32_t overviewgappi = 5; /* overview时 窗口与边缘 缝隙大小 */ -int32_t overviewgappo = 30; /* overview时 窗口与窗口 缝隙大小 */ - -/* To conform the xdg-protocol, set the alpha to zero to restore the old - * behavior */ -float fullscreen_bg[] = {0.1, 0.1, 0.1, 1.0}; - -int32_t warpcursor = 1; -int32_t drag_corner = 3; -int32_t drag_warp_cursor = 1; -int32_t xwayland_persistence = 1; /* xwayland persistence */ -int32_t syncobj_enable = 0; -int32_t allow_lock_transparent = 0; -double drag_tile_refresh_interval = 8.0; -double drag_floating_refresh_interval = 8.0; -int32_t allow_tearing = TEARING_DISABLED; -int32_t allow_shortcuts_inhibit = SHORTCUTS_INHIBIT_ENABLE; - -/* keyboard */ - -/* - only layout can modify after fisrt init - other fields change will be ignored. -*/ -char xkb_rules_rules[256]; -char xkb_rules_model[256]; -char xkb_rules_layout[256]; -char xkb_rules_variant[256]; -char xkb_rules_options[256]; - -/* keyboard */ static const struct xkb_rule_names xkb_fallback_rules = { .layout = "us", .variant = NULL, @@ -134,106 +11,3 @@ static const struct xkb_rule_names xkb_fallback_rules = { .rules = NULL, .options = NULL, }; - -static const struct xkb_rule_names xkb_default_rules = { - .options = NULL, -}; - -struct xkb_rule_names xkb_rules = { - /* can specify fields: rules, model, layout, variant, options */ - /* example: - .options = "ctrl:nocaps", - */ - .rules = xkb_rules_rules, .model = xkb_rules_model, - .layout = xkb_rules_layout, .variant = xkb_rules_variant, - .options = xkb_rules_options, -}; - -int32_t repeat_rate = 25; -int32_t repeat_delay = 600; - -/* Trackpad */ -int32_t disable_trackpad = 0; -int32_t tap_to_click = 1; -int32_t tap_and_drag = 1; -int32_t drag_lock = 1; -int32_t mouse_natural_scrolling = 0; -int32_t trackpad_natural_scrolling = 0; -int32_t disable_while_typing = 1; -int32_t left_handed = 0; -int32_t middle_button_emulation = 0; -int32_t single_scratchpad = 1; -int32_t edge_scroller_pointer_focus = 1; - -/* You can choose between: -LIBINPUT_CONFIG_SCROLL_NO_SCROLL -LIBINPUT_CONFIG_SCROLL_2FG -LIBINPUT_CONFIG_SCROLL_EDGE -LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN -*/ -enum libinput_config_scroll_method scroll_method = LIBINPUT_CONFIG_SCROLL_2FG; -uint32_t scroll_button = 274; - -/* You can choose between: -LIBINPUT_CONFIG_CLICK_METHOD_NONE -LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS -LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER -*/ -enum libinput_config_click_method click_method = - LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS; - -double axis_scroll_factor = 1.0; - -/* You can choose between: -LIBINPUT_CONFIG_SEND_EVENTS_ENABLED -LIBINPUT_CONFIG_SEND_EVENTS_DISABLED -LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE -*/ -uint32_t send_events_mode = LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; - -/* You can choose between: -LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT -LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE -*/ -enum libinput_config_accel_profile accel_profile = - LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; -double accel_speed = 0.0; -/* You can choose between: -LIBINPUT_CONFIG_TAP_MAP_LRM -- 1/2/3 finger tap maps to left/right/middle -LIBINPUT_CONFIG_TAP_MAP_LMR -- 1/2/3 finger tap maps to left/middle/right -*/ -enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TAP_MAP_LRM; - -/* If you want to use the windows key for MODKEY, use WLR_MODIFIER_LOGO */ -#define MODKEY WLR_MODIFIER_ALT - -static const char *tags[] = { - "1", "2", "3", "4", "5", "6", "7", "8", "9", -}; - -float focused_opacity = 1.0; -float unfocused_opacity = 1.0; - -int32_t border_radius = 0; -int32_t border_radius_location_default = CORNER_LOCATION_ALL; -int32_t blur = 0; -int32_t blur_layer = 0; -int32_t blur_optimized = 1; - -struct blur_data blur_params; - -int32_t blur_params_num_passes = 1; -int32_t blur_params_radius = 5; -float blur_params_noise = 0.02; -float blur_params_brightness = 0.9; -float blur_params_contrast = 0.9; -float blur_params_saturation = 1.2; - -int32_t shadows = 0; -int32_t shadow_only_floating = 1; -int32_t layer_shadows = 0; -uint32_t shadows_size = 10; -double shadows_blur = 15; -int32_t shadows_position_x = 0; -int32_t shadows_position_y = 0; -float shadowscolor[] = COLOR(0x000000ff); diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 4f808bdf..449adf3b 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -3,7 +3,7 @@ int32_t bind_to_view(const Arg *arg) { return 0; uint32_t target = arg->ui; - if (view_current_to_back && selmon->pertag->curtag && + if (config.view_current_to_back && selmon->pertag->curtag && (target & TAGMASK) == (selmon->tagset[selmon->seltags])) { if (selmon->pertag->prevtag) target = 1 << (selmon->pertag->prevtag - 1); @@ -11,13 +11,13 @@ int32_t bind_to_view(const Arg *arg) { target = 0; } - if (!view_current_to_back && + if (!config.view_current_to_back && (target & TAGMASK) == (selmon->tagset[selmon->seltags])) { return 0; } if ((int32_t)target == INT_MIN && selmon->pertag->curtag == 0) { - if (view_current_to_back && selmon->pertag->prevtag) + if (config.view_current_to_back && selmon->pertag->prevtag) target = 1 << (selmon->pertag->prevtag - 1); else target = 0; @@ -96,7 +96,7 @@ int32_t destroy_all_virtual_output(const Arg *arg) { } int32_t defaultgaps(const Arg *arg) { - setgaps(gappoh, gappov, gappih, gappiv); + setgaps(config.gappoh, config.gappov, config.gappih, config.gappiv); return 0; } @@ -140,7 +140,7 @@ int32_t focusdir(const Arg *arg) { c = get_focused_stack_client(c); if (c) { focusclient(c, 1); - if (warpcursor) + if (config.warpcursor) warp_cursor(c); } else { if (config.focus_cross_tag) { @@ -196,7 +196,7 @@ int32_t focuslast(const Arg *arg) { } int32_t toggle_trackpad_enable(const Arg *arg) { - disable_trackpad = !disable_trackpad; + config.disable_trackpad = !config.disable_trackpad; return 0; } @@ -225,7 +225,7 @@ int32_t focusmon(const Arg *arg) { return 0; selmon = tm; - if (warpcursor) { + if (config.warpcursor) { warp_cursor_to_selmon(selmon); } c = focustop(selmon); @@ -258,7 +258,7 @@ int32_t focusstack(const Arg *arg) { return 0; focusclient(tc, 1); - if (warpcursor) + if (config.warpcursor) warp_cursor(tc); return 0; } @@ -390,7 +390,7 @@ int32_t moveresize(const Arg *arg) { /* Doesn't work for X11 output - the next absolute motion event * returns the cursor to where it started */ if (grabc->isfloating) { - rzcorner = drag_corner; + rzcorner = config.drag_corner; grabcx = (int)round(cursor->x); grabcy = (int)round(cursor->y); if (rzcorner == 4) @@ -404,7 +404,7 @@ int32_t moveresize(const Arg *arg) { ? 0 : 2); - if (drag_warp_cursor) { + if (config.drag_warp_cursor) { grabcx = rzcorner & 1 ? grabc->geom.x + grabc->geom.width : grabc->geom.x; grabcy = rzcorner & 2 ? grabc->geom.y + grabc->geom.height @@ -476,9 +476,9 @@ int32_t resizewin(const Arg *arg) { if (!c || c->isfullscreen || c->ismaximizescreen) return 0; - int32_t animations_state_backup = animations; + int32_t animations_state_backup = config.animations; if (!c->isfloating) - animations = 0; + config.animations = 0; if (ISTILED(c)) { switch (arg->ui) { @@ -505,7 +505,7 @@ int32_t resizewin(const Arg *arg) { break; } resize_tile_client(c, false, offsetx, offsety, 0); - animations = animations_state_backup; + config.animations = animations_state_backup; return 0; } @@ -536,7 +536,7 @@ int32_t resizewin(const Arg *arg) { c->iscustomsize = 1; c->float_geom = c->geom; resize(c, c->geom, 0); - animations = animations_state_backup; + config.animations = animations_state_backup; return 0; } @@ -608,7 +608,7 @@ int32_t set_proportion(const Arg *arg) { return 0; if (selmon->visible_tiling_clients == 1 && - !scroller_ignore_proportion_single) + !config.scroller_ignore_proportion_single) return 0; Client *tc = selmon->sel; @@ -616,7 +616,7 @@ int32_t set_proportion(const Arg *arg) { if (tc) { tc = get_scroll_stack_head(tc); uint32_t max_client_width = - selmon->w.width - 2 * scroller_structs - gappih; + selmon->w.width - 2 * config.scroller_structs - config.gappih; tc->scroller_proportion = arg->f; tc->geom.width = max_client_width * arg->f; arrange(selmon, false, false); @@ -650,7 +650,7 @@ int32_t smartmovewin(const Arg *arg) { if (c->geom.x + c->geom.width < tc->geom.x || c->geom.x > tc->geom.x + tc->geom.width) continue; - buttom = tc->geom.y + tc->geom.height + gappiv; + buttom = tc->geom.y + tc->geom.height + config.gappiv; if (top > buttom && ny < buttom) { tar = MAX(tar, buttom); }; @@ -670,7 +670,7 @@ int32_t smartmovewin(const Arg *arg) { if (c->geom.x + c->geom.width < tc->geom.x || c->geom.x > tc->geom.x + tc->geom.width) continue; - top = tc->geom.y - gappiv; + top = tc->geom.y - config.gappiv; if (buttom < top && (ny + c->geom.height) > top) { tar = MIN(tar, top - c->geom.height); }; @@ -690,7 +690,7 @@ int32_t smartmovewin(const Arg *arg) { if (c->geom.y + c->geom.height < tc->geom.y || c->geom.y > tc->geom.y + tc->geom.height) continue; - right = tc->geom.x + tc->geom.width + gappih; + right = tc->geom.x + tc->geom.width + config.gappih; if (left > right && nx < right) { tar = MAX(tar, right); }; @@ -709,7 +709,7 @@ int32_t smartmovewin(const Arg *arg) { if (c->geom.y + c->geom.height < tc->geom.y || c->geom.y > tc->geom.y + tc->geom.height) continue; - left = tc->geom.x - gappih; + left = tc->geom.x - config.gappih; if (right < left && (nx + c->geom.width) > left) { tar = MIN(tar, left - c->geom.width); }; @@ -757,14 +757,14 @@ int32_t smartresizewin(const Arg *arg) { if (c->geom.x + c->geom.width < tc->geom.x || c->geom.x > tc->geom.x + tc->geom.width) continue; - top = tc->geom.y - gappiv; + top = tc->geom.y - config.gappiv; if (buttom < top && (nh + c->geom.y) > top) { tar = MAX(tar, top - c->geom.y); }; } nh = tar == -99999 ? nh : tar; - if (c->geom.y + nh + gappov > selmon->w.y + selmon->w.height) - nh = selmon->w.y + selmon->w.height - c->geom.y - gappov; + if (c->geom.y + nh + config.gappov > selmon->w.y + selmon->w.height) + nh = selmon->w.y + selmon->w.height - c->geom.y - config.gappov; break; case LEFT: nw -= selmon->w.width / 16; @@ -780,15 +780,15 @@ int32_t smartresizewin(const Arg *arg) { if (c->geom.y + c->geom.height < tc->geom.y || c->geom.y > tc->geom.y + tc->geom.height) continue; - left = tc->geom.x - gappih; + left = tc->geom.x - config.gappih; if (right < left && (nw + c->geom.x) > left) { tar = MIN(tar, left - c->geom.x); }; } nw = tar == 99999 ? nw : tar; - if (c->geom.x + nw + gappoh > selmon->w.x + selmon->w.width) - nw = selmon->w.x + selmon->w.width - c->geom.x - gappoh; + if (c->geom.x + nw + config.gappoh > selmon->w.x + selmon->w.width) + nw = selmon->w.x + selmon->w.width - c->geom.x - config.gappoh; break; } @@ -1046,7 +1046,7 @@ int32_t switch_proportion_preset(const Arg *arg) { return 0; if (selmon->visible_tiling_clients == 1 && - !scroller_ignore_proportion_single) + !config.scroller_ignore_proportion_single) return 0; Client *tc = selmon->sel; @@ -1087,7 +1087,7 @@ int32_t switch_proportion_preset(const Arg *arg) { } uint32_t max_client_width = - selmon->w.width - 2 * scroller_structs - gappih; + selmon->w.width - 2 * config.scroller_structs - config.gappih; tc->scroller_proportion = target_proportion; tc->geom.width = max_client_width * target_proportion; arrange(selmon, false, false); @@ -1170,7 +1170,7 @@ int32_t tagmon(const Arg *arg) { focusclient(c, 1); arrange(selmon, false, false); } - if (warpcursor) { + if (config.warpcursor) { warp_cursor_to_selmon(c->mon); } return 0; @@ -1257,11 +1257,12 @@ int32_t toggle_scratchpad(const Arg *arg) { return 0; wl_list_for_each_safe(c, tmp, &clients, link) { - if (!scratchpad_cross_monitor && c->mon != selmon) { + if (!config.scratchpad_cross_monitor && c->mon != selmon) { continue; } - if (single_scratchpad && c->isnamedscratchpad && !c->isminimized) { + if (config.single_scratchpad && c->isnamedscratchpad && + !c->isminimized) { set_minimized(c); continue; } @@ -1662,7 +1663,8 @@ int32_t toggleoverview(const Arg *arg) { if (!selmon) return 0; - if (selmon->isoverview && ov_tab_mode && arg->i != 1 && selmon->sel) { + if (selmon->isoverview && config.ov_tab_mode && arg->i != 1 && + selmon->sel) { focusstack(&(Arg){.i = 1}); return 0; } diff --git a/src/ext-protocol/tearing.h b/src/ext-protocol/tearing.h index 8e02656a..5a9851e4 100644 --- a/src/ext-protocol/tearing.h +++ b/src/ext-protocol/tearing.h @@ -60,7 +60,7 @@ void handle_tearing_new_object(struct wl_listener *listener, void *data) { bool check_tearing_frame_allow(Monitor *m) { /* never allow tearing when disabled */ - if (!allow_tearing) { + if (!config.allow_tearing) { return false; } @@ -72,7 +72,7 @@ bool check_tearing_frame_allow(Monitor *m) { } /* allow tearing for any window when requested or forced */ - if (allow_tearing == TEARING_ENABLED) { + if (config.allow_tearing == TEARING_ENABLED) { if (c->force_tearing == STATE_UNSPECIFIED) { return c->tearing_hint; } else { @@ -87,7 +87,8 @@ bool check_tearing_frame_allow(Monitor *m) { if (c->force_tearing == STATE_UNSPECIFIED) { /* honor the tearing hint or the fullscreen-force preference */ - return c->tearing_hint || allow_tearing == TEARING_FULLSCREEN_ONLY; + return c->tearing_hint || + config.allow_tearing == TEARING_FULLSCREEN_ONLY; } /* honor tearing as requested by action */ diff --git a/src/fetch/client.h b/src/fetch/client.h index 11edb76b..0b142847 100644 --- a/src/fetch/client.h +++ b/src/fetch/client.h @@ -12,7 +12,7 @@ bool check_hit_no_border(Client *c) { } } - if (no_border_when_single && c && c->mon && + if (config.no_border_when_single && c && c->mon && ((ISSCROLLTILED(c) && c->mon->visible_scroll_tiling_clients == 1) || c->mon->visible_clients == 1)) { hit_no_border = true; @@ -39,7 +39,7 @@ Client *get_client_by_id_or_title(const char *arg_id, const char *arg_title) { const char *appid, *title; Client *c = NULL; wl_list_for_each(c, &clients, link) { - if (!scratchpad_cross_monitor && c->mon != selmon) { + if (!config.scratchpad_cross_monitor && c->mon != selmon) { continue; } @@ -167,7 +167,7 @@ Client *find_client_by_direction(Client *tc, const Arg *arg, bool findfloating, // 第一次遍历,计算客户端数量 wl_list_for_each(c, &clients, link) { if (c && (findfloating || !c->isfloating) && !c->isunglobal && - (focus_cross_monitor || c->mon == tc->mon) && + (config.focus_cross_monitor || c->mon == tc->mon) && (c->tags & c->mon->tagset[c->mon->seltags])) { last++; } @@ -188,7 +188,7 @@ Client *find_client_by_direction(Client *tc, const Arg *arg, bool findfloating, last = -1; wl_list_for_each(c, &clients, link) { if (c && (findfloating || !c->isfloating) && !c->isunglobal && - (focus_cross_monitor || c->mon == tc->mon) && + (config.focus_cross_monitor || c->mon == tc->mon) && (c->tags & c->mon->tagset[c->mon->seltags])) { last++; tempClients[last] = c; @@ -511,21 +511,21 @@ Client *get_next_stack_client(Client *c, bool reverse) { float *get_border_color(Client *c) { if (c->mon != selmon) { - return bordercolor; + return config.bordercolor; } else if (c->isurgent) { - return urgentcolor; + return config.urgentcolor; } else if (c->is_in_scratchpad && selmon && c == selmon->sel) { - return scratchpadcolor; + return config.scratchpadcolor; } else if (c->isglobal && selmon && c == selmon->sel) { - return globalcolor; + return config.globalcolor; } else if (c->isoverlay && selmon && c == selmon->sel) { - return overlaycolor; + return config.overlaycolor; } else if (c->ismaximizescreen && selmon && c == selmon->sel) { - return maximizescreencolor; + return config.maximizescreencolor; } else if (selmon && c == selmon->sel) { - return focuscolor; + return config.focuscolor; } else { - return bordercolor; + return config.bordercolor; } } diff --git a/src/layout/arrange.h b/src/layout/arrange.h index e164a0f6..87217c7b 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -309,7 +309,7 @@ void resize_tile_master_horizontal(Client *grabc, bool isdrag, int32_t offsetx, } if (last_apply_drap_time == 0 || - time - last_apply_drap_time > drag_tile_refresh_interval) { + time - last_apply_drap_time > config.drag_tile_refresh_interval) { arrange(grabc->mon, false, false); last_apply_drap_time = time; } @@ -478,7 +478,7 @@ void resize_tile_master_vertical(Client *grabc, bool isdrag, int32_t offsetx, } if (last_apply_drap_time == 0 || - time - last_apply_drap_time > drag_tile_refresh_interval) { + time - last_apply_drap_time > config.drag_tile_refresh_interval) { arrange(grabc->mon, false, false); last_apply_drap_time = time; } @@ -494,7 +494,7 @@ void resize_tile_scroller(Client *grabc, bool isdrag, int32_t offsetx, Client *stack_head = get_scroll_stack_head(grabc); if (grabc && grabc->mon->visible_tiling_clients == 1 && - !scroller_ignore_proportion_single) + !config.scroller_ignore_proportion_single) return; if (!start_drag_window && isdrag) { @@ -670,7 +670,7 @@ void resize_tile_scroller(Client *grabc, bool isdrag, int32_t offsetx, } if (last_apply_drap_time == 0 || - time - last_apply_drap_time > drag_tile_refresh_interval) { + time - last_apply_drap_time > config.drag_tile_refresh_interval) { arrange(grabc->mon, false, false); last_apply_drap_time = time; } diff --git a/src/layout/horizontal.h b/src/layout/horizontal.h index 8140934a..eaa7b5c2 100644 --- a/src/layout/horizontal.h +++ b/src/layout/horizontal.h @@ -7,9 +7,9 @@ void grid(Monitor *m) { Client *c = NULL; n = 0; int32_t target_gappo = - enablegaps ? m->isoverview ? overviewgappo : gappoh : 0; + enablegaps ? m->isoverview ? config.overviewgappo : config.gappoh : 0; int32_t target_gappi = - enablegaps ? m->isoverview ? overviewgappi : gappih : 0; + enablegaps ? m->isoverview ? config.overviewgappi : config.gappih : 0; float single_width_ratio = m->isoverview ? 0.7 : 0.9; float single_height_ratio = m->isoverview ? 0.8 : 0.9; @@ -123,9 +123,12 @@ void deck(Monitor *m) { int32_t cur_gappoh = enablegaps ? m->gappoh : 0; int32_t cur_gappov = enablegaps ? m->gappov : 0; - cur_gappih = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappih; - cur_gappoh = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; - cur_gappov = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; + cur_gappih = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappih; + cur_gappoh = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; + cur_gappov = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; n = m->visible_tiling_clients; @@ -188,12 +191,15 @@ void horizontal_scroll_adjust_fullandmax(Client *c, int32_t cur_gappoh = enablegaps ? m->gappoh : 0; int32_t cur_gappov = enablegaps ? m->gappov : 0; - cur_gappih = - smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappih; - cur_gappoh = - smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappoh; - cur_gappov = - smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappov; + cur_gappih = config.smartgaps && m->visible_scroll_tiling_clients == 1 + ? 0 + : cur_gappih; + cur_gappoh = config.smartgaps && m->visible_scroll_tiling_clients == 1 + ? 0 + : cur_gappoh; + cur_gappov = config.smartgaps && m->visible_scroll_tiling_clients == 1 + ? 0 + : cur_gappov; if (c->isfullscreen) { target_geom->height = m->m.height; @@ -289,14 +295,18 @@ void scroller(Monitor *m) { int32_t cur_gappov = enablegaps ? m->gappov : 0; int32_t cur_gappiv = enablegaps ? m->gappiv : 0; - cur_gappih = - smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappih; - cur_gappoh = - smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappoh; - cur_gappov = - smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappov; + cur_gappih = config.smartgaps && m->visible_scroll_tiling_clients == 1 + ? 0 + : cur_gappih; + cur_gappoh = config.smartgaps && m->visible_scroll_tiling_clients == 1 + ? 0 + : cur_gappoh; + cur_gappov = config.smartgaps && m->visible_scroll_tiling_clients == 1 + ? 0 + : cur_gappov; - int32_t max_client_width = m->w.width - 2 * scroller_structs - cur_gappih; + int32_t max_client_width = + m->w.width - 2 * config.scroller_structs - cur_gappih; n = m->visible_scroll_tiling_clients; @@ -320,13 +330,13 @@ void scroller(Monitor *m) { } } - if (n == 1 && !scroller_ignore_proportion_single && + if (n == 1 && !config.scroller_ignore_proportion_single && !tempClients[0]->isfullscreen && !tempClients[0]->ismaximizescreen) { c = tempClients[0]; single_proportion = c->scroller_proportion_single > 0.0f ? c->scroller_proportion_single - : scroller_default_proportion_single; + : config.scroller_default_proportion_single; target_geom.height = m->w.height - 2 * cur_gappov; target_geom.width = (m->w.width - 2 * cur_gappoh) * single_proportion; @@ -360,9 +370,9 @@ void scroller(Monitor *m) { for (i = 0; i < n; i++) { c = tempClients[i]; if (root_client == c) { - if (c->geom.x >= m->w.x + scroller_structs && + if (c->geom.x >= m->w.x + config.scroller_structs && c->geom.x + c->geom.width <= - m->w.x + m->w.width - scroller_structs) { + m->w.x + m->w.width - config.scroller_structs) { need_scroller = false; } else { need_scroller = true; @@ -373,7 +383,8 @@ void scroller(Monitor *m) { } bool need_apply_overspread = - scroller_prefer_overspread && m->visible_scroll_tiling_clients > 1 && + config.scroller_prefer_overspread && + m->visible_scroll_tiling_clients > 1 && (focus_client_index == 0 || focus_client_index == n - 1) && tempClients[focus_client_index]->scroller_proportion < 1.0f; @@ -403,16 +414,16 @@ void scroller(Monitor *m) { } bool need_apply_center = - scroller_focus_center || m->visible_scroll_tiling_clients == 1 || - (scroller_prefer_center && !need_apply_overspread && + config.scroller_focus_center || m->visible_scroll_tiling_clients == 1 || + (config.scroller_prefer_center && !need_apply_overspread && (!m->prevsel || (ISSCROLLTILED(m->prevsel) && (m->prevsel->scroller_proportion * max_client_width) + (tempClients[focus_client_index]->scroller_proportion * max_client_width) > - m->w.width - 2 * scroller_structs - cur_gappih))); + m->w.width - 2 * config.scroller_structs - cur_gappih))); - if (n == 1 && scroller_ignore_proportion_single) { + if (n == 1 && config.scroller_ignore_proportion_single) { need_scroller = true; } @@ -439,14 +450,14 @@ void scroller(Monitor *m) { target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2; } else if (need_apply_overspread) { if (over_overspread_to_left) { - target_geom.x = m->w.x + scroller_structs; + target_geom.x = m->w.x + config.scroller_structs; } else { target_geom.x = m->w.x + (m->w.width - tempClients[focus_client_index]->scroller_proportion * max_client_width - - scroller_structs); + config.scroller_structs); } } else { @@ -456,8 +467,8 @@ void scroller(Monitor *m) { tempClients[focus_client_index] ->scroller_proportion * max_client_width - - scroller_structs) - : m->w.x + scroller_structs; + config.scroller_structs) + : m->w.x + config.scroller_structs; } horizontal_check_scroller_root_inside_mon( tempClients[focus_client_index], &target_geom); @@ -522,10 +533,14 @@ void center_tile(Monitor *m) { int32_t cur_gappoh = enablegaps ? m->gappoh : 0; // 外部水平间隙 // 智能间隙处理 - cur_gappiv = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappiv; - cur_gappih = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappih; - cur_gappov = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; - cur_gappoh = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; + cur_gappiv = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappiv; + cur_gappih = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappih; + cur_gappov = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; + cur_gappoh = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; int32_t nmasters = m->pertag->nmasters[m->pertag->curtag]; mfact = fc->master_mfact_per > 0.0f ? fc->master_mfact_per @@ -538,7 +553,8 @@ void center_tile(Monitor *m) { tw = mw; // 判断是否需要主区域铺满 - int32_t should_overspread = center_master_overspread && (n <= nmasters); + int32_t should_overspread = + config.center_master_overspread && (n <= nmasters); int32_t master_surplus_height = (m->w.height - 2 * cur_gappov - cur_gappiv * ie * (master_num - 1)); @@ -564,7 +580,7 @@ void center_tile(Monitor *m) { mx = cur_gappoh + tw + cur_gappih * ie; } else if (n - nmasters == 1) { // 单个堆叠窗口的处理 - if (center_when_single_stack) { + if (config.center_when_single_stack) { // stack在右边,master居中,左边空着 tw = (m->w.width - mw) / 2 - cur_gappoh - cur_gappih * ie; mx = cur_gappoh + tw + cur_gappih * ie; // master居中 @@ -641,7 +657,7 @@ void center_tile(Monitor *m) { } int32_t stack_x; - if (center_when_single_stack) { + if (config.center_when_single_stack) { // 放在右侧(master居中时,stack在右边) stack_x = m->w.x + mx + mw + cur_gappih * ie; } else { @@ -745,10 +761,14 @@ void tile(Monitor *m) { int32_t cur_gappov = enablegaps ? m->gappov : 0; int32_t cur_gappoh = enablegaps ? m->gappoh : 0; - cur_gappiv = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappiv; - cur_gappih = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappih; - cur_gappov = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; - cur_gappoh = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; + cur_gappiv = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappiv; + cur_gappih = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappih; + cur_gappov = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; + cur_gappoh = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; wl_list_for_each(fc, &clients, link) { @@ -855,10 +875,14 @@ void right_tile(Monitor *m) { int32_t cur_gappov = enablegaps ? m->gappov : 0; int32_t cur_gappoh = enablegaps ? m->gappoh : 0; - cur_gappiv = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappiv; - cur_gappih = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappih; - cur_gappov = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; - cur_gappoh = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; + cur_gappiv = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappiv; + cur_gappih = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappih; + cur_gappov = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; + cur_gappoh = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; wl_list_for_each(fc, &clients, link) { @@ -953,8 +977,10 @@ monocle(Monitor *m) { int32_t cur_gappov = enablegaps ? m->gappov : 0; int32_t cur_gappoh = enablegaps ? m->gappoh : 0; - cur_gappoh = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; - cur_gappov = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; + cur_gappoh = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; + cur_gappov = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; wl_list_for_each(c, &clients, link) { if (!VISIBLEON(c, m) || !ISTILED(c)) diff --git a/src/layout/vertical.h b/src/layout/vertical.h index 4759e7a5..3d8863af 100644 --- a/src/layout/vertical.h +++ b/src/layout/vertical.h @@ -19,10 +19,14 @@ void vertical_tile(Monitor *m) { int32_t cur_gapoh = enablegaps ? m->gappoh : 0; int32_t cur_gapov = enablegaps ? m->gappov : 0; - cur_gapih = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gapih; - cur_gapiv = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gapiv; - cur_gapoh = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gapoh; - cur_gapov = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gapov; + cur_gapih = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gapih; + cur_gapiv = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gapiv; + cur_gapoh = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gapoh; + cur_gapov = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gapov; wl_list_for_each(fc, &clients, link) { if (VISIBLEON(fc, m) && ISTILED(fc)) @@ -116,9 +120,12 @@ void vertical_deck(Monitor *m) { int32_t cur_gappoh = enablegaps ? m->gappoh : 0; int32_t cur_gappov = enablegaps ? m->gappov : 0; - cur_gappiv = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappiv; - cur_gappoh = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; - cur_gappov = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; + cur_gappiv = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappiv; + cur_gappoh = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; + cur_gappov = + config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; n = m->visible_tiling_clients; @@ -175,12 +182,15 @@ void vertical_scroll_adjust_fullandmax(Client *c, struct wlr_box *target_geom) { int32_t cur_gappov = enablegaps ? m->gappov : 0; int32_t cur_gappoh = enablegaps ? m->gappoh : 0; - cur_gappiv = - smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappiv; - cur_gappov = - smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappov; - cur_gappoh = - smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappoh; + cur_gappiv = config.smartgaps && m->visible_scroll_tiling_clients == 1 + ? 0 + : cur_gappiv; + cur_gappov = config.smartgaps && m->visible_scroll_tiling_clients == 1 + ? 0 + : cur_gappov; + cur_gappoh = config.smartgaps && m->visible_scroll_tiling_clients == 1 + ? 0 + : cur_gappoh; if (c->isfullscreen) { target_geom->width = m->m.width; @@ -276,14 +286,18 @@ void vertical_scroller(Monitor *m) { int32_t cur_gappoh = enablegaps ? m->gappoh : 0; int32_t cur_gappih = enablegaps ? m->gappih : 0; - cur_gappiv = - smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappiv; - cur_gappov = - smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappov; - cur_gappoh = - smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappoh; + cur_gappiv = config.smartgaps && m->visible_scroll_tiling_clients == 1 + ? 0 + : cur_gappiv; + cur_gappov = config.smartgaps && m->visible_scroll_tiling_clients == 1 + ? 0 + : cur_gappov; + cur_gappoh = config.smartgaps && m->visible_scroll_tiling_clients == 1 + ? 0 + : cur_gappoh; - int32_t max_client_height = m->w.height - 2 * scroller_structs - cur_gappiv; + int32_t max_client_height = + m->w.height - 2 * config.scroller_structs - cur_gappiv; n = m->visible_scroll_tiling_clients; @@ -304,13 +318,13 @@ void vertical_scroller(Monitor *m) { } } - if (n == 1 && !scroller_ignore_proportion_single && + if (n == 1 && !config.scroller_ignore_proportion_single && !tempClients[0]->isfullscreen && !tempClients[0]->ismaximizescreen) { c = tempClients[0]; single_proportion = c->scroller_proportion_single > 0.0f ? c->scroller_proportion_single - : scroller_default_proportion_single; + : config.scroller_default_proportion_single; target_geom.width = m->w.width - 2 * cur_gappoh; target_geom.height = (m->w.height - 2 * cur_gappov) * single_proportion; @@ -344,9 +358,9 @@ void vertical_scroller(Monitor *m) { for (i = 0; i < n; i++) { c = tempClients[i]; if (root_client == c) { - if (c->geom.y >= m->w.y + scroller_structs && + if (c->geom.y >= m->w.y + config.scroller_structs && c->geom.y + c->geom.height <= - m->w.y + m->w.height - scroller_structs) { + m->w.y + m->w.height - config.scroller_structs) { need_scroller = false; } else { need_scroller = true; @@ -357,7 +371,8 @@ void vertical_scroller(Monitor *m) { } bool need_apply_overspread = - scroller_prefer_overspread && m->visible_scroll_tiling_clients > 1 && + config.scroller_prefer_overspread && + m->visible_scroll_tiling_clients > 1 && (focus_client_index == 0 || focus_client_index == n - 1) && tempClients[focus_client_index]->scroller_proportion < 1.0f; @@ -387,16 +402,16 @@ void vertical_scroller(Monitor *m) { } bool need_apply_center = - scroller_focus_center || m->visible_scroll_tiling_clients == 1 || - (scroller_prefer_center && !need_apply_overspread && + config.scroller_focus_center || m->visible_scroll_tiling_clients == 1 || + (config.scroller_prefer_center && !need_apply_overspread && (!m->prevsel || (ISSCROLLTILED(m->prevsel) && (m->prevsel->scroller_proportion * max_client_height) + (tempClients[focus_client_index]->scroller_proportion * max_client_height) > - m->w.height - 2 * scroller_structs - cur_gappiv))); + m->w.height - 2 * config.scroller_structs - cur_gappiv))); - if (n == 1 && scroller_ignore_proportion_single) { + if (n == 1 && config.scroller_ignore_proportion_single) { need_scroller = true; } @@ -426,14 +441,14 @@ void vertical_scroller(Monitor *m) { target_geom.y = m->w.y + (m->w.height - target_geom.height) / 2; } else if (need_apply_overspread) { if (over_overspread_to_up) { - target_geom.y = m->w.y + scroller_structs; + target_geom.y = m->w.y + config.scroller_structs; } else { target_geom.y = m->w.y + (m->w.height - tempClients[focus_client_index]->scroller_proportion * max_client_height - - scroller_structs); + config.scroller_structs); } } else { target_geom.y = root_client->geom.y > m->w.y + (m->w.height) / 2 @@ -441,8 +456,8 @@ void vertical_scroller(Monitor *m) { tempClients[focus_client_index] ->scroller_proportion * max_client_height - - scroller_structs) - : m->w.y + scroller_structs; + config.scroller_structs) + : m->w.y + config.scroller_structs; } vertical_check_scroller_root_inside_mon(tempClients[focus_client_index], &target_geom); @@ -486,9 +501,9 @@ void vertical_grid(Monitor *m) { int32_t rows, cols, overrows; Client *c = NULL; int32_t target_gappo = - enablegaps ? m->isoverview ? overviewgappo : gappov : 0; + enablegaps ? m->isoverview ? config.overviewgappo : config.gappov : 0; int32_t target_gappi = - enablegaps ? m->isoverview ? overviewgappi : gappiv : 0; + enablegaps ? m->isoverview ? config.overviewgappi : config.gappiv : 0; float single_width_ratio = m->isoverview ? 0.7 : 0.9; float single_height_ratio = m->isoverview ? 0.8 : 0.9; diff --git a/src/mango.c b/src/mango.c index bdafcc58..aedf1d6e 100644 --- a/src/mango.c +++ b/src/mango.c @@ -906,6 +906,7 @@ static struct wl_event_source *keep_idle_inhibit_source; static bool cursor_hidden = false; static bool tag_combo = false; static const char *cli_config_path = NULL; +static bool cli_debug_log = false; static KeyMode keymode = { .mode = {'d', 'e', 'f', 'a', 'u', 'l', 't', '\0'}, .isdefault = true, @@ -933,6 +934,8 @@ struct Pertag { *ltidxs[LENGTH(tags) + 1]; /* matrix of tags and layouts indexes */ }; +#include "config/parse_config.h" + static struct wl_signal mango_print_status; static struct wl_listener print_status_listener = {.notify = @@ -995,7 +998,6 @@ static struct wl_event_source *sync_keymap; #include "animation/common.h" #include "animation/layer.h" #include "animation/tag.h" -#include "config/parse_config.h" #include "dispatch/bind_define.h" #include "ext-protocol/all.h" #include "fetch/fetch.h" @@ -1060,7 +1062,7 @@ void show_scratchpad(Client *c) { if (c->isfullscreen || c->ismaximizescreen) { c->isfullscreen = 0; // 清除窗口全屏标志 c->ismaximizescreen = 0; - c->bw = c->isnoborder ? 0 : borderpx; + c->bw = c->isnoborder ? 0 : config.borderpx; } /* return if fullscreen */ @@ -1068,10 +1070,10 @@ void show_scratchpad(Client *c) { setfloating(c, 1); c->geom.width = c->iscustomsize ? c->float_geom.width - : c->mon->w.width * scratchpad_width_ratio; - c->geom.height = c->iscustomsize - ? c->float_geom.height - : c->mon->w.height * scratchpad_height_ratio; + : c->mon->w.width * config.scratchpad_width_ratio; + c->geom.height = + c->iscustomsize ? c->float_geom.height + : c->mon->w.height * config.scratchpad_height_ratio; // 重新计算居中的坐标 c->float_geom = c->geom = c->animainit_geom = c->animation.current = setclient_coordinate_center(c, c->mon, c->geom, 0, 0); @@ -1139,7 +1141,7 @@ void swallow(Client *c, Client *w) { bool switch_scratchpad_client_state(Client *c) { - if (scratchpad_cross_monitor && selmon && c->mon != selmon && + if (config.scratchpad_cross_monitor && selmon && c->mon != selmon && c->is_in_scratchpad) { // 保存原始monitor用于尺寸计算 Monitor *oldmon = c->mon; @@ -1193,12 +1195,12 @@ void apply_named_scratchpad(Client *target_client) { Client *c = NULL; wl_list_for_each(c, &clients, link) { - if (!scratchpad_cross_monitor && c->mon != selmon) { + if (!config.scratchpad_cross_monitor && c->mon != selmon) { continue; } - if (single_scratchpad && c->is_in_scratchpad && c->is_scratchpad_show && - c != target_client) { + if (config.single_scratchpad && c->is_in_scratchpad && + c->is_scratchpad_show && c != target_client) { set_minimized(c); } } @@ -1259,30 +1261,30 @@ void toggle_hotarea(int32_t x_root, int32_t y_root) { // 根据热角位置计算不同的热区坐标 unsigned hx, hy; - switch (hotarea_corner) { + switch (config.hotarea_corner) { case BOTTOM_RIGHT: // 右下角 - hx = selmon->m.x + selmon->m.width - hotarea_size; - hy = selmon->m.y + selmon->m.height - hotarea_size; + hx = selmon->m.x + selmon->m.width - config.hotarea_size; + hy = selmon->m.y + selmon->m.height - config.hotarea_size; break; case TOP_LEFT: // 左上角 - hx = selmon->m.x + hotarea_size; - hy = selmon->m.y + hotarea_size; + hx = selmon->m.x + config.hotarea_size; + hy = selmon->m.y + config.hotarea_size; break; case TOP_RIGHT: // 右上角 - hx = selmon->m.x + selmon->m.width - hotarea_size; - hy = selmon->m.y + hotarea_size; + hx = selmon->m.x + selmon->m.width - config.hotarea_size; + hy = selmon->m.y + config.hotarea_size; break; case BOTTOM_LEFT: // 左下角(默认) default: - hx = selmon->m.x + hotarea_size; - hy = selmon->m.y + selmon->m.height - hotarea_size; + hx = selmon->m.x + config.hotarea_size; + hy = selmon->m.y + selmon->m.height - config.hotarea_size; break; } // 判断鼠标是否在热区内 int in_hotarea = 0; - switch (hotarea_corner) { + switch (config.hotarea_corner) { case BOTTOM_RIGHT: // 右下角 in_hotarea = (y_root > hy && x_root > hx && x_root <= (selmon->m.x + selmon->m.width) && @@ -1304,10 +1306,11 @@ void toggle_hotarea(int32_t x_root, int32_t y_root) { break; } - if (enable_hotarea == 1 && selmon->is_in_hotarea == 0 && in_hotarea) { + if (config.enable_hotarea == 1 && selmon->is_in_hotarea == 0 && + in_hotarea) { toggleoverview(&arg); selmon->is_in_hotarea = 1; - } else if (enable_hotarea == 1 && selmon->is_in_hotarea == 1 && + } else if (config.enable_hotarea == 1 && selmon->is_in_hotarea == 1 && !in_hotarea) { selmon->is_in_hotarea = 0; } @@ -1600,7 +1603,7 @@ void apply_window_snap(Client *c) { int32_t snap_up_mon = 0, snap_down_mon = 0, snap_left_mon = 0, snap_right_mon = 0; - uint32_t cbw = !render_border || c->fake_no_border ? borderpx : 0; + uint32_t cbw = !render_border || c->fake_no_border ? config.borderpx : 0; uint32_t tcbw; uint32_t cx, cy, cw, ch, tcx, tcy, tcw, tch; cx = c->geom.x + cbw; @@ -1612,14 +1615,14 @@ void apply_window_snap(Client *c) { if (!c || !c->mon || !client_surface(c)->mapped || c->iskilling) return; - if (!c->isfloating || !enable_floating_snap) + if (!c->isfloating || !config.enable_floating_snap) return; wl_list_for_each(tc, &clients, link) { if (tc && tc->isfloating && !tc->iskilling && client_surface(tc)->mapped && VISIBLEON(tc, c->mon)) { - tcbw = !render_border || tc->fake_no_border ? borderpx : 0; + tcbw = !render_border || tc->fake_no_border ? config.borderpx : 0; tcx = tc->geom.x + tcbw; tcy = tc->geom.y + tcbw; tcw = tc->geom.width - 2 * tcbw; @@ -1673,19 +1676,19 @@ void apply_window_snap(Client *c) { if (snap_right_screen >= 0 && snap_right_screen < snap_right) snap_right = snap_right_screen; - if (snap_left < snap_right && snap_left < snap_distance) { + if (snap_left < snap_right && snap_left < config.snap_distance) { c->geom.x = c->geom.x - snap_left; } - if (snap_right <= snap_left && snap_right < snap_distance) { + if (snap_right <= snap_left && snap_right < config.snap_distance) { c->geom.x = c->geom.x + snap_right; } - if (snap_up < snap_down && snap_up < snap_distance) { + if (snap_up < snap_down && snap_up < config.snap_distance) { c->geom.y = c->geom.y - snap_up; } - if (snap_down <= snap_up && snap_down < snap_distance) { + if (snap_down <= snap_up && snap_down < config.snap_distance) { c->geom.y = c->geom.y + snap_down; } @@ -1815,7 +1818,8 @@ axisnotify(struct wl_listener *listener, void *data) { a = &config.axis_bindings[ji]; if (CLEANMASK(mods) == CLEANMASK(a->mod) && // 按键一致 adir == a->dir && a->func) { // 滚轮方向判断一致且处理函数存在 - if (event->time_msec - axis_apply_time > axis_bind_apply_timeout || + if (event->time_msec - axis_apply_time > + config.axis_bind_apply_timeout || axis_apply_dir * event->delta < 0) { a->func(&a->arg); axis_apply_time = event->time_msec; @@ -1835,9 +1839,10 @@ axisnotify(struct wl_listener *listener, void *data) { /* Notify the client with pointer focus of the axis event. */ wlr_seat_pointer_notify_axis( seat, // 滚轮事件发送给客户端也就是窗口 - event->time_msec, event->orientation, event->delta * axis_scroll_factor, - roundf(event->delta_discrete * axis_scroll_factor), event->source, - event->relative_direction); + event->time_msec, event->orientation, + event->delta * config.axis_scroll_factor, + roundf(event->delta_discrete * config.axis_scroll_factor), + event->source, event->relative_direction); } int32_t ongesture(struct wlr_pointer_swipe_end_event *event) { @@ -1855,7 +1860,8 @@ int32_t ongesture(struct wlr_pointer_swipe_end_event *event) { } // Require absolute distance movement beyond a small thresh-hold - if (adx * adx + ady * ady < swipe_min_threshold * swipe_min_threshold) { + if (adx * adx + ady * ady < + config.swipe_min_threshold * config.swipe_min_threshold) { return handled; } @@ -1992,7 +1998,7 @@ void place_drag_tile_client(Client *c) { bool check_trackpad_disabled(struct wlr_pointer *pointer) { struct libinput_device *device; - if (!disable_trackpad) + if (!config.disable_trackpad) return false; if (wlr_input_device_is_libinput(&pointer->base) && @@ -2111,7 +2117,7 @@ buttonpress(struct wl_listener *listener, void *data) { grabc = NULL; start_drag_window = false; last_apply_drap_time = 0; - if (tmpc->drag_to_tile && drag_tile_to_tile) { + if (tmpc->drag_to_tile && config.drag_tile_to_tile) { place_drag_tile_client(tmpc); } else { apply_window_snap(tmpc); @@ -2144,7 +2150,7 @@ void checkidleinhibitor(struct wlr_surface *exclude) { toplevel_from_wlr_surface(inhibitor->surface, &c, NULL); - if (idleinhibit_ignore_visible) { + if (config.idleinhibit_ignore_visible) { inhibited = 1; break; } @@ -2342,7 +2348,7 @@ static void iter_layer_scene_buffers(struct wlr_scene_buffer *buffer, wlr_scene_buffer_set_backdrop_blur(buffer, true); wlr_scene_buffer_set_backdrop_blur_ignore_transparent(buffer, true); - if (blur_optimized) { + if (config.blur_optimized) { wlr_scene_buffer_set_backdrop_blur_optimized(buffer, true); } else { wlr_scene_buffer_set_backdrop_blur_optimized(buffer, false); @@ -2350,7 +2356,7 @@ static void iter_layer_scene_buffers(struct wlr_scene_buffer *buffer, } void layer_flush_blur_background(LayerSurface *l) { - if (!blur) + if (!config.blur) return; // 如果背景层发生变化,标记优化的模糊背景缓存需要更新 @@ -2406,15 +2412,16 @@ void maplayersurfacenotify(struct wl_listener *listener, void *data) { if (layer_surface->current.exclusive_zone == 0 && layer_surface->current.layer != ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM && layer_surface->current.layer != ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND) { - l->shadow = wlr_scene_shadow_create(l->scene, 0, 0, border_radius, - shadows_blur, shadowscolor); + l->shadow = + wlr_scene_shadow_create(l->scene, 0, 0, config.border_radius, + config.shadows_blur, config.shadowscolor); wlr_scene_node_lower_to_bottom(&l->shadow->node); wlr_scene_node_set_enabled(&l->shadow->node, true); } // 初始化动画 - if (animations && layer_animations && !l->noanim) { - l->animation.duration = animation_duration_open; + if (config.animations && config.layer_animations && !l->noanim) { + l->animation.duration = config.animation_duration_open; l->animation.action = OPEN; layer_set_pending_state(l); } @@ -2460,7 +2467,8 @@ void commitlayersurfacenotify(struct wl_listener *listener, void *data) { get_layer_target_geometry(l, &box); - if (animations && layer_animations && !l->noanim && l->mapped && + if (config.animations && config.layer_animations && !l->noanim && + l->mapped && layer_surface->current.layer != ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM && layer_surface->current.layer != ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND && !wlr_box_equal(&box, &l->geom)) { @@ -2470,13 +2478,12 @@ void commitlayersurfacenotify(struct wl_listener *listener, void *data) { l->geom.width = box.width; l->geom.height = box.height; l->animation.action = MOVE; - l->animation.duration = animation_duration_move; + l->animation.duration = config.animation_duration_move; l->need_output_flush = true; layer_set_pending_state(l); } - if (blur && blur_layer) { - // 设置非背景layer模糊 + if (config.blur && config.blur_layer) { if (!l->noblur && layer_surface->current.layer != ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM && @@ -2754,22 +2761,21 @@ KeyboardGroup *createkeyboardgroup(void) { group->wlr_group = wlr_keyboard_group_create(); group->wlr_group->data = group; - /* Prepare an XKB keymap and assign it to the keyboard group. */ context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - if (!(keymap = xkb_keymap_new_from_names(context, &xkb_rules, + if (!(keymap = xkb_keymap_new_from_names(context, &config.xkb_rules, XKB_KEYMAP_COMPILE_NO_FLAGS))) die("failed to compile keymap"); wlr_keyboard_set_keymap(&group->wlr_group->keyboard, keymap); - if (numlockon) { + if (config.numlockon) { xkb_mod_index_t mod_index = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_NUM); if (mod_index != XKB_MOD_INVALID) locked_mods |= (uint32_t)1 << mod_index; } - if (capslock) { + if (config.capslock) { xkb_mod_index_t mod_index = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_CAPS); if (mod_index != XKB_MOD_INVALID) @@ -2783,8 +2789,8 @@ KeyboardGroup *createkeyboardgroup(void) { xkb_keymap_unref(keymap); xkb_context_unref(context); - wlr_keyboard_set_repeat_info(&group->wlr_group->keyboard, repeat_rate, - repeat_delay); + wlr_keyboard_set_repeat_info(&group->wlr_group->keyboard, + config.repeat_rate, config.repeat_delay); /* Set up listeners for keyboard events */ LISTEN(&group->wlr_group->keyboard.events.key, &group->key, keypress); @@ -2974,11 +2980,10 @@ void createmon(struct wl_listener *listener, void *data) { for (i = 0; i < LENGTH(m->layers); i++) wl_list_init(&m->layers[i]); - /* Initialize monitor state using configured rules */ - m->gappih = gappih; - m->gappiv = gappiv; - m->gappoh = gappoh; - m->gappov = gappov; + m->gappih = config.gappih; + m->gappiv = config.gappiv; + m->gappoh = config.gappoh; + m->gappov = config.gappov; m->isoverview = 0; m->sel = NULL; m->is_in_hotarea = 0; @@ -3040,8 +3045,8 @@ void createmon(struct wl_listener *listener, void *data) { } for (i = 0; i <= LENGTH(tags); i++) { - m->pertag->nmasters[i] = default_nmaster; - m->pertag->mfacts[i] = default_mfact; + m->pertag->nmasters[i] = config.default_nmaster; + m->pertag->mfacts[i] = config.default_mfact; m->pertag->ltidxs[i] = &layouts[0]; } @@ -3069,12 +3074,11 @@ void createmon(struct wl_listener *listener, void *data) { else wlr_output_layout_add(output_layout, wlr_output, m->m.x, m->m.y); - if (blur) { + if (config.blur) { m->blur = wlr_scene_optimized_blur_create(&scene->tree, 0, 0); wlr_scene_node_set_position(&m->blur->node, m->m.x, m->m.y); wlr_scene_node_reparent(&m->blur->node, layers[LyrBlur]); wlr_scene_optimized_blur_set_size(m->blur, m->m.width, m->m.height); - // wlr_scene_node_set_enabled(&m->blur->node, 1); } m->ext_group = wlr_ext_workspace_group_handle_v1_create( ext_manager, EXT_WORKSPACE_ENABLE_CAPS); @@ -3100,7 +3104,7 @@ createnotify(struct wl_listener *listener, void *data) { /* Allocate a Client for this surface */ c = toplevel->base->data = ecalloc(1, sizeof(*c)); c->surface.xdg = toplevel->base; - c->bw = borderpx; + c->bw = config.borderpx; LISTEN(&toplevel->base->surface->events.commit, &c->commit, commitnotify); LISTEN(&toplevel->base->surface->events.map, &c->map, mapnotify); @@ -3146,44 +3150,49 @@ void destroyinputdevice(struct wl_listener *listener, void *data) { void configure_pointer(struct libinput_device *device) { if (libinput_device_config_tap_get_finger_count(device)) { - libinput_device_config_tap_set_enabled(device, tap_to_click); - libinput_device_config_tap_set_drag_enabled(device, tap_and_drag); - libinput_device_config_tap_set_drag_lock_enabled(device, drag_lock); - libinput_device_config_tap_set_button_map(device, button_map); + libinput_device_config_tap_set_enabled(device, config.tap_to_click); + libinput_device_config_tap_set_drag_enabled(device, + config.tap_and_drag); + libinput_device_config_tap_set_drag_lock_enabled(device, + config.drag_lock); + libinput_device_config_tap_set_button_map(device, config.button_map); libinput_device_config_scroll_set_natural_scroll_enabled( - device, trackpad_natural_scrolling); + device, config.trackpad_natural_scrolling); } else { libinput_device_config_scroll_set_natural_scroll_enabled( - device, mouse_natural_scrolling); + device, config.mouse_natural_scrolling); } if (libinput_device_config_dwt_is_available(device)) - libinput_device_config_dwt_set_enabled(device, disable_while_typing); + libinput_device_config_dwt_set_enabled(device, + config.disable_while_typing); if (libinput_device_config_left_handed_is_available(device)) - libinput_device_config_left_handed_set(device, left_handed); + libinput_device_config_left_handed_set(device, config.left_handed); if (libinput_device_config_middle_emulation_is_available(device)) libinput_device_config_middle_emulation_set_enabled( - device, middle_button_emulation); + device, config.middle_button_emulation); if (libinput_device_config_scroll_get_methods(device) != LIBINPUT_CONFIG_SCROLL_NO_SCROLL) - libinput_device_config_scroll_set_method(device, scroll_method); + libinput_device_config_scroll_set_method(device, config.scroll_method); if (libinput_device_config_scroll_get_methods(device) == LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN) - libinput_device_config_scroll_set_button(device, scroll_button); + libinput_device_config_scroll_set_button(device, config.scroll_button); if (libinput_device_config_click_get_methods(device) != LIBINPUT_CONFIG_CLICK_METHOD_NONE) - libinput_device_config_click_set_method(device, click_method); + libinput_device_config_click_set_method(device, config.click_method); if (libinput_device_config_send_events_get_modes(device)) - libinput_device_config_send_events_set_mode(device, send_events_mode); + libinput_device_config_send_events_set_mode(device, + config.send_events_mode); - if (accel_profile && libinput_device_config_accel_is_available(device)) { - libinput_device_config_accel_set_profile(device, accel_profile); - libinput_device_config_accel_set_speed(device, accel_speed); + if (config.accel_profile && + libinput_device_config_accel_is_available(device)) { + libinput_device_config_accel_set_profile(device, config.accel_profile); + libinput_device_config_accel_set_speed(device, config.accel_speed); } else { // profile cannot be directly applied to 0, need to set to 1 first libinput_device_config_accel_set_profile(device, 1); @@ -3835,7 +3844,7 @@ void keypress(struct wl_listener *listener, void *data) { wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); // ov tab mode detect moe key release - if (ov_tab_mode && !locked && group == kb_group && + if (config.ov_tab_mode && !locked && group == kb_group && event->state == WL_KEYBOARD_KEY_STATE_RELEASED && (keycode == 133 || keycode == 37 || keycode == 64 || keycode == 50 || keycode == 134 || keycode == 105 || keycode == 108 || keycode == 62) && @@ -3919,7 +3928,7 @@ void pending_kill_client(Client *c) { void locksession(struct wl_listener *listener, void *data) { struct wlr_session_lock_v1 *session_lock = data; SessionLock *lock; - if (!allow_lock_transparent) { + if (!config.allow_lock_transparent) { wlr_scene_node_set_enabled(&locked_bg->node, true); } if (cur_lock) { @@ -3956,10 +3965,10 @@ static void iter_xdg_scene_buffers(struct wlr_scene_buffer *buffer, int32_t sx, if (wlr_subsurface_try_from_wlr_surface(surface) != NULL) return; - if (blur && c && !c->noblur) { + if (config.blur && c && !c->noblur) { wlr_scene_buffer_set_backdrop_blur(buffer, true); wlr_scene_buffer_set_backdrop_blur_ignore_transparent(buffer, false); - if (blur_optimized) { + if (config.blur_optimized) { wlr_scene_buffer_set_backdrop_blur_optimized(buffer, true); } else { wlr_scene_buffer_set_backdrop_blur_optimized(buffer, false); @@ -4006,13 +4015,13 @@ void init_client_properties(Client *c) { c->is_restoring_from_ov = 0; c->isurgent = 0; c->need_output_flush = 0; - c->scroller_proportion = scroller_default_proportion; + c->scroller_proportion = config.scroller_default_proportion; c->is_pending_open_animation = true; c->drag_to_tile = false; c->scratchpad_switching_mon = false; c->fake_no_border = false; - c->focused_opacity = focused_opacity; - c->unfocused_opacity = unfocused_opacity; + c->focused_opacity = config.focused_opacity; + c->unfocused_opacity = config.unfocused_opacity; c->nofocus = 0; c->nofadein = 0; c->nofadeout = 0; @@ -4047,9 +4056,9 @@ void init_client_properties(Client *c) { c->next_in_stack = NULL; c->prev_in_stack = NULL; memset(c->oldmonname, 0, sizeof(c->oldmonname)); - memcpy(c->opacity_animation.initial_border_color, bordercolor, + memcpy(c->opacity_animation.initial_border_color, config.bordercolor, sizeof(c->opacity_animation.initial_border_color)); - memcpy(c->opacity_animation.current_border_color, bordercolor, + memcpy(c->opacity_animation.current_border_color, config.bordercolor, sizeof(c->opacity_animation.current_border_color)); c->opacity_animation.initial_opacity = c->unfocused_opacity; c->opacity_animation.current_opacity = c->unfocused_opacity; @@ -4079,7 +4088,7 @@ mapnotify(struct wl_listener *listener, void *data) { c->bw = 0; c->isnoborder = 1; } else { - c->bw = borderpx; + c->bw = config.borderpx; } if (client_should_global(c)) { @@ -4111,21 +4120,22 @@ mapnotify(struct wl_listener *listener, void *data) { } // extra node - c->border = wlr_scene_rect_create(c->scene, 0, 0, - c->isurgent ? urgentcolor : bordercolor); + c->border = wlr_scene_rect_create( + c->scene, 0, 0, c->isurgent ? config.urgentcolor : config.bordercolor); wlr_scene_node_lower_to_bottom(&c->border->node); wlr_scene_node_set_position(&c->border->node, 0, 0); - wlr_scene_rect_set_corner_radius(c->border, border_radius, - border_radius_location_default); + wlr_scene_rect_set_corner_radius(c->border, config.border_radius, + config.border_radius_location_default); wlr_scene_node_set_enabled(&c->border->node, true); - c->shadow = wlr_scene_shadow_create(c->scene, 0, 0, border_radius, - shadows_blur, shadowscolor); + c->shadow = + wlr_scene_shadow_create(c->scene, 0, 0, config.border_radius, + config.shadows_blur, config.shadowscolor); wlr_scene_node_lower_to_bottom(&c->shadow->node); wlr_scene_node_set_enabled(&c->shadow->node, true); - if (new_is_master && selmon && !is_scroller_layout(selmon)) + if (config.new_is_master && selmon && !is_scroller_layout(selmon)) // tile at the top wl_list_insert(&clients, &c->link); // 新窗口是master,头部入栈 else if (selmon && is_scroller_layout(selmon) && @@ -4338,7 +4348,7 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); /* Update selmon (even while dragging a window) */ - if (sloppyfocus) + if (config.sloppyfocus) selmon = xytomon(cursor->x, cursor->y); } @@ -4374,7 +4384,8 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, if (grabc->isfloating) { grabc->iscustomsize = 1; if (last_apply_drap_time == 0 || - time - last_apply_drap_time > drag_floating_refresh_interval) { + time - last_apply_drap_time > + config.drag_floating_refresh_interval) { resize_floating_window(grabc); last_apply_drap_time = time; } @@ -4400,7 +4411,7 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, should_lock = true; } - if (!(!edge_scroller_pointer_focus && c && c->mon && + if (!(!config.edge_scroller_pointer_focus && c && c->mon && is_scroller_layout(c->mon) && !INSIDEMON(c))) pointerfocus(c, surface, sx, sy, time); @@ -4506,7 +4517,7 @@ void pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, uint32_t time) { struct timespec now; - if (sloppyfocus && !start_drag_window && c && time && c->scene && + if (config.sloppyfocus && !start_drag_window && c && time && c->scene && c->scene->node.enabled && !c->animation.tagining && (surface != seat->pointer_state.focused_surface) && !client_is_unmanaged(c) && VISIBLEON(c, c->mon)) @@ -4632,7 +4643,7 @@ void rendermon(struct wl_listener *listener, void *data) { // 绘制客户端 wl_list_for_each(c, &clients, link) { need_more_frames = client_draw_frame(c) || need_more_frames; - if (!animations && !grabc && c->configure_serial && + if (!config.animations && !grabc && c->configure_serial && client_is_rendered_on_mon(c, m)) { monitor_check_skip_frame_timeout(m); goto skip; @@ -4644,7 +4655,7 @@ void rendermon(struct wl_listener *listener, void *data) { } // 只有在需要帧时才构建和提交状态 - if (allow_tearing && frame_allow_tearing) { + if (config.allow_tearing && frame_allow_tearing) { apply_tear_state(m); } else { wlr_scene_output_commit(m->scene_output, NULL); @@ -4720,7 +4731,7 @@ void exchange_two_client(Client *c1, Client *c2) { float stack_proportion = 0.0f; if (c1 == NULL || c2 == NULL || - (!exchange_cross_monitor && c1->mon != c2->mon)) { + (!config.exchange_cross_monitor && c1->mon != c2->mon)) { return; } @@ -4822,7 +4833,7 @@ void exchange_two_client(Client *c1, Client *c2) { } // 处理跨监视器交换 - if (exchange_cross_monitor) { + if (config.exchange_cross_monitor) { tmp_mon = c2->mon; tmp_tags = c2->tags; setmon(c2, c1->mon, c1->tags, false); @@ -5002,7 +5013,7 @@ setfloating(Client *c, int32_t floating) { if (c->isfullscreen || c->ismaximizescreen) { c->isfullscreen = 0; // 清除窗口全屏标志 c->ismaximizescreen = 0; - c->bw = c->isnoborder ? 0 : borderpx; + c->bw = c->isnoborder ? 0 : config.borderpx; } exit_scroller_stack(c); @@ -5016,11 +5027,13 @@ setfloating(Client *c, int32_t floating) { // restore to the memeroy geom if (c->float_geom.width > 0 && c->float_geom.height > 0) { - if (c->mon && c->float_geom.width >= c->mon->w.width - gappoh) { + if (c->mon && + c->float_geom.width >= c->mon->w.width - config.gappoh) { c->float_geom.width = c->mon->w.width * 0.9; window_size_outofrange = true; } - if (c->mon && c->float_geom.height >= c->mon->w.height - gappov) { + if (c->mon && + c->float_geom.height >= c->mon->w.height - config.gappov) { c->float_geom.height = c->mon->w.height * 0.9; window_size_outofrange = true; } @@ -5082,10 +5095,10 @@ setfloating(Client *c, int32_t floating) { } void reset_maximizescreen_size(Client *c) { - c->geom.x = c->mon->w.x + gappoh; - c->geom.y = c->mon->w.y + gappov; - c->geom.width = c->mon->w.width - 2 * gappoh; - c->geom.height = c->mon->w.height - 2 * gappov; + c->geom.x = c->mon->w.x + config.gappoh; + c->geom.y = c->mon->w.y + config.gappov; + c->geom.width = c->mon->w.width - 2 * config.gappoh; + c->geom.height = c->mon->w.height - 2 * config.gappov; resize(c, c->geom, 0); } @@ -5131,16 +5144,16 @@ void setmaximizescreen(Client *c, int32_t maximizescreen) { if (c->isfloating) c->float_geom = c->geom; - maximizescreen_box.x = c->mon->w.x + gappoh; - maximizescreen_box.y = c->mon->w.y + gappov; - maximizescreen_box.width = c->mon->w.width - 2 * gappoh; - maximizescreen_box.height = c->mon->w.height - 2 * gappov; - wlr_scene_node_raise_to_top(&c->scene->node); // 将视图提升到顶层 + maximizescreen_box.x = c->mon->w.x + config.gappoh; + maximizescreen_box.y = c->mon->w.y + config.gappov; + maximizescreen_box.width = c->mon->w.width - 2 * config.gappoh; + maximizescreen_box.height = c->mon->w.height - 2 * config.gappov; + wlr_scene_node_raise_to_top(&c->scene->node); if (!is_scroller_layout(c->mon) || c->isfloating) resize(c, maximizescreen_box, 0); c->ismaximizescreen = 1; } else { - c->bw = c->isnoborder ? 0 : borderpx; + c->bw = c->isnoborder ? 0 : config.borderpx; c->ismaximizescreen = 0; if (c->isfloating) setfloating(c, 1); @@ -5207,7 +5220,7 @@ void setfullscreen(Client *c, int32_t fullscreen) // 用自定义全屏代理自 resize(c, c->mon->m, 1); c->isfullscreen = 1; } else { - c->bw = c->isnoborder ? 0 : borderpx; + c->bw = c->isnoborder ? 0 : config.borderpx; c->isfullscreen = 0; if (c->isfloating) setfloating(c, 1); @@ -5270,9 +5283,8 @@ void reset_keyboard_layout(void) { return; } - // 现在安全地创建真正的keymap struct xkb_keymap *new_keymap = xkb_keymap_new_from_names( - context, &xkb_rules, XKB_KEYMAP_COMPILE_NO_FLAGS); + context, &config.xkb_rules, XKB_KEYMAP_COMPILE_NO_FLAGS); if (!new_keymap) { // 理论上这里不应该失败,因为前面已经验证过了 wlr_log(WLR_ERROR, @@ -5455,6 +5467,9 @@ void setup(void) { setenv("XDG_CURRENT_DESKTOP", "mango", 1); parse_config(); + if (cli_debug_log) { + config.log_level = WLR_DEBUG; + } init_baked_points(); int32_t drm_fd, i, sig[] = {SIGCHLD, SIGINT, SIGTERM, SIGPIPE}; @@ -5464,7 +5479,7 @@ void setup(void) { for (i = 0; i < LENGTH(sig); i++) sigaction(sig[i], &sa, NULL); - wlr_log_init(log_level, NULL); + wlr_log_init(config.log_level, NULL); /* The Wayland display is managed by libwayland. It handles accepting * clients from the Unix socket, manging Wayland globals, and so on. */ @@ -5490,7 +5505,7 @@ void setup(void) { /* Initialize the scene graph used to lay out windows */ scene = wlr_scene_create(); - root_bg = wlr_scene_rect_create(&scene->tree, 0, 0, rootcolor); + root_bg = wlr_scene_rect_create(&scene->tree, 0, 0, config.rootcolor); for (i = 0; i < NUM_LAYERS; i++) layers[i] = wlr_scene_tree_create(&scene->tree); drag_icon = wlr_scene_tree_create(&scene->tree); @@ -5515,7 +5530,7 @@ void setup(void) { scene, wlr_linux_dmabuf_v1_create_with_renderer(dpy, 4, drw)); } - if (syncobj_enable && (drm_fd = wlr_renderer_get_drm_fd(drw)) >= 0 && + if (config.syncobj_enable && (drm_fd = wlr_renderer_get_drm_fd(drw)) >= 0 && drw->features.timeline && backend->features.timeline) wlr_linux_drm_syncobj_manager_v1_create(dpy, 1, drm_fd); @@ -5635,7 +5650,8 @@ void setup(void) { * (necessary for HiDPI support). Scaled cursors will be loaded with * each output. */ // cursor_mgr = wlr_xcursor_manager_create(cursor_theme, 24); - cursor_mgr = wlr_xcursor_manager_create(config.cursor_theme, cursor_size); + cursor_mgr = + wlr_xcursor_manager_create(config.cursor_theme, config.cursor_size); /* * wlr_cursor *only* displays an image on screen. It does not move @@ -5709,10 +5725,10 @@ void setup(void) { wl_signal_add(&output_mgr->events.apply, &output_mgr_apply); wl_signal_add(&output_mgr->events.test, &output_mgr_test); - // blur - wlr_scene_set_blur_data(scene, blur_params.num_passes, blur_params.radius, - blur_params.noise, blur_params.brightness, - blur_params.contrast, blur_params.saturation); + wlr_scene_set_blur_data( + scene, config.blur_params.num_passes, config.blur_params.radius, + config.blur_params.noise, config.blur_params.brightness, + config.blur_params.contrast, config.blur_params.saturation); /* create text_input-, and input_method-protocol relevant globals */ input_method_manager = wlr_input_method_manager_v2_create(dpy); @@ -5745,7 +5761,8 @@ void setup(void) { * Initialise the XWayland X server. * It will be started when the first X client is started. */ - xwayland = wlr_xwayland_create(dpy, compositor, !xwayland_persistence); + xwayland = + wlr_xwayland_create(dpy, compositor, !config.xwayland_persistence); if (xwayland) { wl_signal_add(&xwayland->events.ready, &xwayland_ready); wl_signal_add(&xwayland->events.new_surface, &new_xwayland_surface); @@ -5830,7 +5847,7 @@ void overview_backup(Client *c) { c->isfullscreen = 0; // 清除窗口全屏标志 c->ismaximizescreen = 0; } - c->bw = c->isnoborder ? 0 : borderpx; + c->bw = c->isnoborder ? 0 : config.borderpx; client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | WLR_EDGE_RIGHT); @@ -5871,7 +5888,7 @@ void overview_restore(Client *c, const Arg *arg) { if (c->bw == 0 && !c->isfullscreen) { // 如果是在ov模式中创建的窗口,没有bw记录 - c->bw = c->isnoborder ? 0 : borderpx; + c->bw = c->isnoborder ? 0 : config.borderpx; } if (c->isfloating && !c->force_tiled_state) { @@ -5881,7 +5898,7 @@ void overview_restore(Client *c, const Arg *arg) { void handlecursoractivity(void) { wl_event_source_timer_update(hide_cursor_source, - cursor_hide_timeout * 1000); + config.cursor_hide_timeout * 1000); if (!cursor_hidden) return; @@ -5972,7 +5989,7 @@ void unmapnotify(struct wl_listener *listener, void *data) { Client *prev_in_stack = c->prev_in_stack; c->iskilling = 1; - if (animations && !c->is_clip_to_hide && !c->isminimized && + if (config.animations && !c->is_clip_to_hide && !c->isminimized && (!c->mon || VISIBLEON(c, c->mon))) init_fadeout_client(c); @@ -6073,7 +6090,7 @@ void updatemons(struct wl_listener *listener, void *data) { * positions, focus, and the stored configuration in wlroots' * output-manager implementation. */ - struct wlr_output_configuration_v1 *config = + struct wlr_output_configuration_v1 *output_config = wlr_output_configuration_v1_create(); Client *c = NULL; struct wlr_output_configuration_head_v1 *config_head; @@ -6084,8 +6101,8 @@ void updatemons(struct wl_listener *listener, void *data) { wl_list_for_each(m, &mons, link) { if (m->wlr_output->enabled || m->asleep) continue; - config_head = - wlr_output_configuration_head_v1_create(config, m->wlr_output); + config_head = wlr_output_configuration_head_v1_create(output_config, + m->wlr_output); config_head->state.enabled = 0; /* Remove this output from the layout to avoid cursor enter inside * it */ @@ -6113,8 +6130,8 @@ void updatemons(struct wl_listener *listener, void *data) { wl_list_for_each(m, &mons, link) { if (!m->wlr_output->enabled) continue; - config_head = - wlr_output_configuration_head_v1_create(config, m->wlr_output); + config_head = wlr_output_configuration_head_v1_create(output_config, + m->wlr_output); oldx = m->m.x; oldy = m->m.y; @@ -6147,7 +6164,7 @@ void updatemons(struct wl_listener *listener, void *data) { */ wlr_scene_output_set_position(m->scene_output, m->m.x, m->m.y); - if (blur && m->blur) { + if (config.blur && m->blur) { wlr_scene_node_set_position(&m->blur->node, m->m.x, m->m.y); wlr_scene_optimized_blur_set_size(m->blur, m->m.width, m->m.height); } @@ -6200,7 +6217,7 @@ void updatemons(struct wl_listener *listener, void *data) { * it's at the wrong position after all. */ wlr_cursor_move(cursor, NULL, 0, 0); - wlr_output_manager_v1_set_configuration(output_mgr, config); + wlr_output_manager_v1_set_configuration(output_mgr, output_config); } void updatetitle(struct wl_listener *listener, void *data) { @@ -6226,7 +6243,7 @@ urgent(struct wl_listener *listener, void *data) { if (!c || !c->foreign_toplevel) return; - if (focus_on_activate && !c->istagsilent && c != selmon->sel) { + if (config.focus_on_activate && !c->istagsilent && c != selmon->sel) { if (!(c->mon == selmon && c->tags & c->mon->tagset[c->mon->seltags])) view_in_mon(&(Arg){.ui = c->tags}, true, c->mon, true); focusclient(c, 1); @@ -6325,7 +6342,7 @@ void handle_keyboard_shortcuts_inhibit_new_inhibitor( struct wlr_keyboard_shortcuts_inhibitor_v1 *inhibitor = data; - if (allow_shortcuts_inhibit == SHORTCUTS_INHIBIT_DISABLE) { + if (config.allow_shortcuts_inhibit == SHORTCUTS_INHIBIT_DISABLE) { return; } @@ -6445,7 +6462,7 @@ void activatex11(struct wl_listener *listener, void *data) { } } - if (focus_on_activate && !c->istagsilent && c != selmon->sel) { + if (config.focus_on_activate && !c->istagsilent && c != selmon->sel) { if (!(c->mon == selmon && c->tags & c->mon->tagset[c->mon->seltags])) view_in_mon(&(Arg){.ui = c->tags}, true, c->mon, true); wlr_xwayland_surface_activate(c->surface.xwayland, 1); @@ -6591,7 +6608,7 @@ int32_t main(int32_t argc, char *argv[]) { if (c == 's') { startup_cmd = optarg; } else if (c == 'd') { - log_level = WLR_DEBUG; + cli_debug_log = true; } else if (c == 'v') { printf("mango " VERSION "\n"); return EXIT_SUCCESS; From 057e53745be191528c4b0ebe5b4f20ccd13b3286 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 13 Mar 2026 19:41:45 +0800 Subject: [PATCH 091/328] docs: update refer repo --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index f4848cf2..d308370d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -36,7 +36,7 @@ Beyond basic window management, mangowm provides a rich set of features designed This project is built upon the hard work of several open-source projects: - **[wlroots](https://gitlab.freedesktop.org/wlroots/wlroots)** — Implementation of the Wayland protocol. -- **[owl](https://github.com/dqrk0jeste/owl)** — Basal window animation reference. +- **[mwc](https://github.com/nikoloc/mwc)** — Basal window animation reference. - **[dwl](https://codeberg.org/dwl/dwl)** — Basal dwl features. - **[sway](https://github.com/swaywm/sway)** — Sample implementation of the Wayland protocol. - **[scenefx](https://github.com/wlrfx/scenefx)** — Library to simplify adding window effects. From e4da095d41a32e4e10aaf54d40006d419fbb9008 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 13 Mar 2026 19:53:12 +0800 Subject: [PATCH 092/328] docs: udpate build dependency --- docs/installation.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/installation.md b/docs/installation.md index 1297eafe..5e0e0e88 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -183,16 +183,12 @@ pikman install mangowc If your distribution isn't listed above, or you want the latest unreleased changes, you can build mangowm from source. > **Info:** Ensure the following dependencies are installed before proceeding: -> - `glibc` > - `wayland` > - `wayland-protocols` > - `libinput` > - `libdrm` > - `libxkbcommon` > - `pixman` -> - `git` -> - `meson` -> - `ninja` > - `libdisplay-info` > - `libliftoff` > - `hwdata` From 537e4aa0794a3da4370dc21d02fdf4f16036b3d1 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 13 Mar 2026 20:07:14 +0800 Subject: [PATCH 093/328] docs: update some describe --- docs/configuration/miscellaneous.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration/miscellaneous.md b/docs/configuration/miscellaneous.md index 9e06781d..2e5a1e92 100644 --- a/docs/configuration/miscellaneous.md +++ b/docs/configuration/miscellaneous.md @@ -37,7 +37,7 @@ description: Advanced settings for XWayland, focus behavior, and system integrat | `view_current_to_back` | `0` | Toggling the current tag switches back to the previously viewed tag. | | `scratchpad_cross_monitor` | `0` | Share the scratchpad pool across all monitors. | | `single_scratchpad` | `1` | Only allow one scratchpad (named or standard) to be visible at a time. | -| `circle_layout` | - | A comma-separated list of layouts `switch_layout` cycles through. | +| `circle_layout` | - | A comma-separated list of layouts `switch_layout` cycles through,the value sample:`tile,scroller`. | ## Window Behavior From d28c8e43a1b194cf62729ebed8e4b27b06dcc117 Mon Sep 17 00:00:00 2001 From: atheeq-rhxn Date: Fri, 13 Mar 2026 18:02:08 +0530 Subject: [PATCH 094/328] chore(sync-website): update commit message --- .github/workflows/sync-website.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/sync-website.yml b/.github/workflows/sync-website.yml index f641fb81..3577a65b 100644 --- a/.github/workflows/sync-website.yml +++ b/.github/workflows/sync-website.yml @@ -38,5 +38,7 @@ jobs: git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" git add apps/web/content/docs - git diff --staged --quiet || git commit -m "sync from mango @ ${{ github.sha }}" + git diff --staged --quiet || git commit \ + -m "docs: content update from mangowm/mango" \ + -m "${{ github.event.head_commit.message }}\nSource: ${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}" git push From 1b2aeeec7ed964712601c942e36fc451eceb9392 Mon Sep 17 00:00:00 2001 From: atheeq-rhxn Date: Fri, 13 Mar 2026 23:40:44 +0530 Subject: [PATCH 095/328] docs: add AerynOS installation --- docs/installation.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/installation.md b/docs/installation.md index 5e0e0e88..10d2bd47 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -1,6 +1,6 @@ --- title: Installation -description: Install mangowm on Arch, Fedora, Gentoo, Guix System, NixOS, PikaOS, or build from source. +description: Install mangowm on AerynOS, Arch, Fedora, Gentoo, Guix System, NixOS, PikaOS, or build from source. --- ## Package Installation @@ -9,6 +9,18 @@ mangowm is available as a pre-built package on several distributions. Choose you --- +### AerynOS + +mangowm is available in the **AerynOS package repository**. + +You can install it using the `moss` package manager: + +```bash +sudo moss install mangowm +``` + +--- + ### Arch Linux mangowm is available in the **Arch User Repository (AUR)**. From 427cf6f89ff4dc9cc774dbe58a9b38d60b5fdbc7 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 14 Mar 2026 08:05:42 +0800 Subject: [PATCH 096/328] docs: update fcitx env suggest --- docs/configuration/input.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/configuration/input.md b/docs/configuration/input.md index 3ebc0f23..6d5eefdb 100644 --- a/docs/configuration/input.md +++ b/docs/configuration/input.md @@ -136,6 +136,7 @@ To use Fcitx5 or IBus, set these environment variables in your config file. ```ini env=GTK_IM_MODULE,fcitx env=QT_IM_MODULE,fcitx +env=QT_IM_MODULES,wayland;fcitx env=SDL_IM_MODULE,fcitx env=XMODIFIERS,@im=fcitx env=GLFW_IM_MODULE,ibus From 5c52d578a8958a87d60b11b0c13a8cbb4f2b6730 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 14 Mar 2026 12:24:01 +0800 Subject: [PATCH 097/328] docs: fix some sample --- docs/configuration/monitors.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration/monitors.md b/docs/configuration/monitors.md index 92c2082c..5501c77f 100644 --- a/docs/configuration/monitors.md +++ b/docs/configuration/monitors.md @@ -241,7 +241,7 @@ yay -S xwayland-satellite ```ini env=DISPLAY,:2 -exec=xwayland-satellite :2 +exec-once=xwayland-satellite :2 monitorrule=name:eDP-1,width:1920,height:1080,refresh:60,x:0,y:0,scale:1.4,vrr:0,rr:0 ``` From b0ec0b4275629e915060bfc46f8586b8ad653ff7 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 14 Mar 2026 12:43:49 +0800 Subject: [PATCH 098/328] docs: remove some old describe --- docs/configuration/basics.md | 2 +- docs/visuals/theming.md | 3 --- docs/window-management/overview.md | 9 +-------- 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/docs/configuration/basics.md b/docs/configuration/basics.md index dbbe45f3..22b2679e 100644 --- a/docs/configuration/basics.md +++ b/docs/configuration/basics.md @@ -48,7 +48,7 @@ source-optional=~/.config/mango/optional.conf You can check your configuration for errors without starting mangowm: ```bash -mango -p /path/to/config.conf +mango -c /path/to/config.conf -p ``` Use with `source-optional` for shared configs across different setups. diff --git a/docs/visuals/theming.md b/docs/visuals/theming.md index 789ce478..8137eb4e 100644 --- a/docs/visuals/theming.md +++ b/docs/visuals/theming.md @@ -54,6 +54,3 @@ Set the size and theme of your mouse cursor. cursor_size=24 cursor_theme=Adwaita ``` - -> **Tip:** You may also want to set the `XCURSOR_SIZE` environment variable to match: -> `env=XCURSOR_SIZE,24` diff --git a/docs/window-management/overview.md b/docs/window-management/overview.md index 290d6139..7da6e690 100644 --- a/docs/window-management/overview.md +++ b/docs/window-management/overview.md @@ -26,11 +26,4 @@ description: Configure the overview mode for window navigation. When in overview mode: - **Left mouse button** — Jump to (focus) a window. -- **Right mouse button** — Close a window. - -To enable this behavior, add the following mouse bindings to your config: - -```ini -mousebind=NONE,btn_left,toggleoverview,1 -mousebind=NONE,btn_right,killclient,0 -``` +- **Right mouse button** — Close a window. \ No newline at end of file From 23af3c55957ef16bb61dee6bf7376e810c0c03dc Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 14 Mar 2026 12:50:31 +0800 Subject: [PATCH 099/328] opt: auto sync XCURSOR_SIZE and XCURSOR_THEME env from config --- docs/configuration/basics.md | 4 ++-- src/mango.c | 22 +++++++++++++++++++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/docs/configuration/basics.md b/docs/configuration/basics.md index 22b2679e..7afa343b 100644 --- a/docs/configuration/basics.md +++ b/docs/configuration/basics.md @@ -60,8 +60,8 @@ You can define environment variables directly within your config file. These are > **Warning:** Environment variables defined here will be **reset** every time you reload the configuration. ```ini -env=GTK_THEME,Adwaita:dark -env=XCURSOR_SIZE,24 +env=QT_IM_MODULES,wayland;fcitx +env=XMODIFIERS,@im=fcitx ``` ## Autostart diff --git a/src/mango.c b/src/mango.c index aedf1d6e..915c47b1 100644 --- a/src/mango.c +++ b/src/mango.c @@ -912,8 +912,13 @@ static KeyMode keymode = { .isdefault = true, }; -static char *env_vars[] = {"DISPLAY", "WAYLAND_DISPLAY", "XDG_CURRENT_DESKTOP", - "XDG_SESSION_TYPE", NULL}; +static char *env_vars[] = {"DISPLAY", + "WAYLAND_DISPLAY", + "XDG_CURRENT_DESKTOP", + "XDG_SESSION_TYPE", + "XCURSOR_THEME", + "XCURSOR_SIZE", + NULL}; static struct { enum wp_cursor_shape_device_v1_shape shape; struct wlr_surface *surface; @@ -5463,7 +5468,6 @@ void handle_print_status(struct wl_listener *listener, void *data) { void setup(void) { - setenv("XCURSOR_SIZE", "24", 1); setenv("XDG_CURRENT_DESKTOP", "mango", 1); parse_config(); @@ -5653,6 +5657,18 @@ void setup(void) { cursor_mgr = wlr_xcursor_manager_create(config.cursor_theme, config.cursor_size); + if (config.cursor_size > 0) { + char size_str[16]; + snprintf(size_str, sizeof(size_str), "%d", config.cursor_size); + setenv("XCURSOR_SIZE", size_str, 1); + } else { + setenv("XCURSOR_SIZE", "24", 1); + } + + if (config.cursor_theme) { + setenv("XCURSOR_THEME", config.cursor_theme, 1); + } + /* * wlr_cursor *only* displays an image on screen. It does not move * around when the pointer moves. However, we can attach input devices From 32c36ba48548f1b30e929c8a739f838bac72db2f Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 14 Mar 2026 13:09:41 +0800 Subject: [PATCH 100/328] opt: optimzie code struct --- src/config/parse_config.h | 26 ++++++++++++++++---------- src/mango.c | 17 +++-------------- 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index ffb8414b..6ae97406 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -3604,6 +3604,20 @@ void reapply_monitor_rules(void) { updatemons(NULL, NULL); } +void set_xcursor_env() { + if (config.cursor_size > 0) { + char size_str[16]; + snprintf(size_str, sizeof(size_str), "%d", config.cursor_size); + setenv("XCURSOR_SIZE", size_str, 1); + } else { + setenv("XCURSOR_SIZE", "24", 1); + } + + if (config.cursor_theme) { + setenv("XCURSOR_THEME", config.cursor_theme, 1); + } +} + void reapply_cursor_style(void) { if (hide_cursor_source) { wl_event_source_timer_update(hide_cursor_source, 0); @@ -3620,19 +3634,11 @@ void reapply_cursor_style(void) { cursor_mgr = NULL; } + set_xcursor_env(); + cursor_mgr = wlr_xcursor_manager_create(config.cursor_theme, config.cursor_size); - if (config.cursor_size > 0) { - char size_str[16]; - snprintf(size_str, sizeof(size_str), "%d", config.cursor_size); - setenv("XCURSOR_SIZE", size_str, 1); - } - - if (config.cursor_theme) { - setenv("XCURSOR_THEME", config.cursor_theme, 1); - } - Monitor *m = NULL; wl_list_for_each(m, &mons, link) { wlr_xcursor_manager_load(cursor_mgr, m->wlr_output->scale); diff --git a/src/mango.c b/src/mango.c index 915c47b1..3d36dd81 100644 --- a/src/mango.c +++ b/src/mango.c @@ -5653,22 +5653,11 @@ void setup(void) { * cursor images are available at all scale factors on the screen * (necessary for HiDPI support). Scaled cursors will be loaded with * each output. */ - // cursor_mgr = wlr_xcursor_manager_create(cursor_theme, 24); + + set_xcursor_env(); + cursor_mgr = wlr_xcursor_manager_create(config.cursor_theme, config.cursor_size); - - if (config.cursor_size > 0) { - char size_str[16]; - snprintf(size_str, sizeof(size_str), "%d", config.cursor_size); - setenv("XCURSOR_SIZE", size_str, 1); - } else { - setenv("XCURSOR_SIZE", "24", 1); - } - - if (config.cursor_theme) { - setenv("XCURSOR_THEME", config.cursor_theme, 1); - } - /* * wlr_cursor *only* displays an image on screen. It does not move * around when the pointer moves. However, we can attach input devices From 14fc6ca99f1eb969f7ec8edac4e836bdd0f736cc Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 14 Mar 2026 21:35:09 +0800 Subject: [PATCH 101/328] docs: add some note message to monitor rule --- docs/configuration/monitors.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/configuration/monitors.md b/docs/configuration/monitors.md index 5501c77f..28ef240b 100644 --- a/docs/configuration/monitors.md +++ b/docs/configuration/monitors.md @@ -48,11 +48,13 @@ monitorrule=name:Values,Parameter:Values,Parameter:Values > **Critical:** If you use XWayland applications, **never use negative coordinates** for your monitor positions. This is a known XWayland bug that causes click events to malfunction. Always arrange your monitors starting from `0,0` and extend into positive coordinates. +> **Note:** that "name" is a regular expression. If you want an exact match, you need to add `^` and `$` to the beginning and end of the expression, for example, `^eDP-1$` matches exactly the string `eDP-1`. + ### Examples ```ini # Laptop display: 1080p, 60Hz, positioned at origin -monitorrule=name:eDP-1,width:1920,height:1080,refresh:60,x:0,y:10 +monitorrule=name:^eDP-1$,width:1920,height:1080,refresh:60,x:0,y:10 # Match by make and model instead of name monitorrule=make:Chimei Innolux Corporation,model:0x15F5,width:1920,height:1080,refresh:60,x:0,y:0 From 7a1a3f0ca4c1a9bb65bf417faf3021faf87057d9 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 15 Mar 2026 14:25:57 +0800 Subject: [PATCH 102/328] opt: preset _JAVA_AWT_WM_NONREPARENTING to 1 --- src/mango.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mango.c b/src/mango.c index 3d36dd81..df1315a1 100644 --- a/src/mango.c +++ b/src/mango.c @@ -5469,6 +5469,7 @@ void handle_print_status(struct wl_listener *listener, void *data) { void setup(void) { setenv("XDG_CURRENT_DESKTOP", "mango", 1); + setenv("_JAVA_AWT_WM_NONREPARENTING", "1", 1); parse_config(); if (cli_debug_log) { From d4321cafacd974a24fd2fe5a3c295396040379b0 Mon Sep 17 00:00:00 2001 From: atheeq-rhxn Date: Sun, 15 Mar 2026 18:05:01 +0530 Subject: [PATCH 103/328] docs: add index files & separations for better navigation --- docs/bindings/index.mdx | 15 +++++++++++++++ docs/configuration/index.mdx | 21 +++++++++++++++++++++ docs/meta.json | 3 +++ docs/visuals/index.mdx | 19 +++++++++++++++++++ docs/window-management/index.mdx | 19 +++++++++++++++++++ 5 files changed, 77 insertions(+) create mode 100644 docs/bindings/index.mdx create mode 100644 docs/configuration/index.mdx create mode 100644 docs/visuals/index.mdx create mode 100644 docs/window-management/index.mdx diff --git a/docs/bindings/index.mdx b/docs/bindings/index.mdx new file mode 100644 index 00000000..4c3a5bda --- /dev/null +++ b/docs/bindings/index.mdx @@ -0,0 +1,15 @@ +--- +title: Bindings & Input +description: Keybindings, mouse gestures, and input devices. +icon: Keyboard +--- + +Configure how you interact with mangowm using flexible keybindings and input options. + + + + + + + + diff --git a/docs/configuration/index.mdx b/docs/configuration/index.mdx new file mode 100644 index 00000000..2bcd3a7e --- /dev/null +++ b/docs/configuration/index.mdx @@ -0,0 +1,21 @@ +--- +title: Configuration +description: Configure mangowm with config files, environment variables, and autostart. +icon: Settings +--- + +Configure mangowm through config files, environment variables, and autostart. + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/meta.json b/docs/meta.json index f8aac0ca..74818a9d 100644 --- a/docs/meta.json +++ b/docs/meta.json @@ -1,13 +1,16 @@ { "title": "mangowm", "pages": [ + "---Getting Started---", "index", "installation", "quick-start", + "---Configuration---", "configuration", "visuals", "window-management", "bindings", + "---Reference---", "ipc", "faq" ] diff --git a/docs/visuals/index.mdx b/docs/visuals/index.mdx new file mode 100644 index 00000000..f71ae2f8 --- /dev/null +++ b/docs/visuals/index.mdx @@ -0,0 +1,19 @@ +--- +title: Visuals +description: Customize borders, colors, effects, and animations. +icon: Palette +--- + +Customize the look of your desktop. + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/window-management/index.mdx b/docs/window-management/index.mdx new file mode 100644 index 00000000..b96c5891 --- /dev/null +++ b/docs/window-management/index.mdx @@ -0,0 +1,19 @@ +--- +title: Window Management +description: Layouts, rules, and window behavior. +icon: LayoutGrid +--- + +Window management with layouts, rules, and scratchpad support. + + + + + + + + + + + + \ No newline at end of file From 40a518fa84f2f5e6baed6754954b8fd856de5f9f Mon Sep 17 00:00:00 2001 From: atheeq-rhxn Date: Sun, 15 Mar 2026 18:20:50 +0530 Subject: [PATCH 104/328] fix: simplify sync commit message to use source URL only --- .github/workflows/sync-website.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync-website.yml b/.github/workflows/sync-website.yml index 3577a65b..57c99e71 100644 --- a/.github/workflows/sync-website.yml +++ b/.github/workflows/sync-website.yml @@ -40,5 +40,5 @@ jobs: git add apps/web/content/docs git diff --staged --quiet || git commit \ -m "docs: content update from mangowm/mango" \ - -m "${{ github.event.head_commit.message }}\nSource: ${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}" + -m "${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}" git push From 0d8aedf691bb740290ad5c447d157a8d26a35ffe Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 15 Mar 2026 23:26:16 +0800 Subject: [PATCH 105/328] fix: shouldn't arrange the closing monitor --- src/layout/arrange.h | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/layout/arrange.h b/src/layout/arrange.h index 87217c7b..04f4554b 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -15,6 +15,9 @@ void restore_size_per(Monitor *m, Client *c) { if (!m || !c) return; + if (!m->wlr_output->enabled) + return; + wl_list_for_each(fc, &clients, link) { if (VISIBLEON(fc, m) && ISTILED(fc)) { fc->old_ismaster = fc->ismaster; @@ -811,11 +814,6 @@ void pre_caculate_before_arrange(Monitor *m, bool want_animation, int32_t master_num = 0; int32_t stack_num = 0; - if (!m) - return; - - if (!m->wlr_output->enabled) - return; m->visible_clients = 0; m->visible_tiling_clients = 0; m->visible_scroll_tiling_clients = 0; @@ -912,6 +910,12 @@ void pre_caculate_before_arrange(Monitor *m, bool want_animation, void // 17 arrange(Monitor *m, bool want_animation, bool from_view) { + if (!m) + return; + + if (!m->wlr_output->enabled) + return; + pre_caculate_before_arrange(m, want_animation, from_view, false); if (m->isoverview) { From 028cf6e49ed91de967a38516522e2f91d1a830a7 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 16 Mar 2026 11:56:41 +0800 Subject: [PATCH 106/328] bump version to 0.12.7 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index c538c927..5d09d536 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('mango', ['c', 'cpp'], - version : '0.12.6', + version : '0.12.7', ) subdir('protocols') From 47b5c9aa2e0135247a87cb01de9e32dacc674524 Mon Sep 17 00:00:00 2001 From: beeb5k Date: Mon, 16 Mar 2026 16:46:01 +0530 Subject: [PATCH 107/328] 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 108/328] 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 109/328] 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 110/328] 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 111/328] 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 112/328] 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 113/328] 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 114/328] 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 115/328] 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 116/328] 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 117/328] 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 118/328] 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 119/328] 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 120/328] 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 121/328] 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 122/328] 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 123/328] 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 124/328] 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 125/328] 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 126/328] 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 127/328] 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 128/328] 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 129/328] 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 130/328] 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 131/328] 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 132/328] 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 133/328] 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 134/328] 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 135/328] 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 136/328] 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 137/328] 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 138/328] 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 139/328] 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 140/328] 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; } From edd0a0cd5d79521313577ab938dcc9db0a0e5104 Mon Sep 17 00:00:00 2001 From: therylith Date: Wed, 8 Apr 2026 10:32:20 +0800 Subject: [PATCH 141/328] docs: update recommended package name The upstream package 'swww' has been renamed to 'awww'.This commit: - Updates the package name for rolling-release distributions like archlinux - Keeps 'swww' for other distros that haven't updated yet. --- docs/quick-start.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quick-start.md b/docs/quick-start.md index 85f67cc2..bc192474 100644 --- a/docs/quick-start.md +++ b/docs/quick-start.md @@ -64,7 +64,7 @@ To get a fully functional desktop experience, we recommend installing the follow | Terminal Emulator | foot, wezterm, alacritty, kitty, ghostty | | Status Bar | waybar, eww, quickshell, ags | | Desktop Shell | Noctalia, DankMaterialShell | -| Wallpaper Setup | swww, swaybg | +| Wallpaper Setup | awww(swww), swaybg | | Notification Daemon | swaync, dunst, mako | | Desktop Portal | xdg-desktop-portal, xdg-desktop-portal-wlr, xdg-desktop-portal-gtk | | Clipboard | wl-clipboard, wl-clip-persist, cliphist | From aeba0e82dd8d317f704a6853e78159693d2c3844 Mon Sep 17 00:00:00 2001 From: ernestoCruz05 Date: Fri, 10 Apr 2026 16:58:07 +0100 Subject: [PATCH 142/328] refactor(mmsg): simplify dispatch arguments parsing --- mmsg/mmsg.c | 143 +++++++++++++--------------------------------------- 1 file changed, 36 insertions(+), 107 deletions(-) diff --git a/mmsg/mmsg.c b/mmsg/mmsg.c index 0191a635..69f1d1d0 100644 --- a/mmsg/mmsg.c +++ b/mmsg/mmsg.c @@ -603,120 +603,49 @@ int32_t main(int32_t argc, char *argv[]) { mode = SET; char *arg = EARGF(usage()); - // Trim leading and trailing whitespace from entire argument first - while (isspace(*arg)) - arg++; - char *end = arg + strlen(arg) - 1; - while (end > arg && isspace(*end)) - end--; - *(end + 1) = '\0'; + dispatch_cmd = dispatch_arg1 = dispatch_arg2 = dispatch_arg3 = + dispatch_arg4 = dispatch_arg5 = ""; - dispatch_cmd = arg; - char *comma1 = strchr(arg, ','); - if (comma1) { - *comma1 = '\0'; + char *tokens[6] = {0}; + int count = 0; - // Trim trailing whitespace from command - end = dispatch_cmd + strlen(dispatch_cmd) - 1; - while (end > dispatch_cmd && isspace(*end)) - end--; - *(end + 1) = '\0'; + while (arg && count < 6) { + char *comma = (count < 5) ? strchr(arg, ',') : NULL; + if (comma) { + *comma = '\0'; + tokens[count++] = arg; + arg = comma + 1; + } else { + tokens[count++] = arg; + break; + } + } - dispatch_arg1 = comma1 + 1; - // Trim leading whitespace from arg1 - while (isspace(*dispatch_arg1)) - dispatch_arg1++; - - // Trim trailing whitespace from arg1 before looking for next - // comma - end = dispatch_arg1 + strlen(dispatch_arg1) - 1; - while (end > dispatch_arg1 && isspace(*end)) - end--; - *(end + 1) = '\0'; - - char *comma2 = strchr(dispatch_arg1, ','); - if (comma2) { - *comma2 = '\0'; - dispatch_arg2 = comma2 + 1; - // Trim leading whitespace from arg2 - while (isspace(*dispatch_arg2)) - dispatch_arg2++; - - // Trim trailing whitespace from arg2 before looking for - // next comma - end = dispatch_arg2 + strlen(dispatch_arg2) - 1; - while (end > dispatch_arg2 && isspace(*end)) + for (int i = 0; i < count; i++) { + char *str = tokens[i]; + while (isspace((unsigned char)*str)) + str++; + if (*str) { + char *end = str + strlen(str) - 1; + while (end > str && isspace((unsigned char)*end)) end--; *(end + 1) = '\0'; - - char *comma3 = strchr(dispatch_arg2, ','); - if (comma3) { - *comma3 = '\0'; - dispatch_arg3 = comma3 + 1; - // Trim leading whitespace from arg3 - while (isspace(*dispatch_arg3)) - dispatch_arg3++; - - // Trim trailing whitespace from arg3 before looking for - // next comma - end = dispatch_arg3 + strlen(dispatch_arg3) - 1; - while (end > dispatch_arg3 && isspace(*end)) - end--; - *(end + 1) = '\0'; - - char *comma4 = strchr(dispatch_arg3, ','); - if (comma4) { - *comma4 = '\0'; - dispatch_arg4 = comma4 + 1; - // Trim leading whitespace from arg4 - while (isspace(*dispatch_arg4)) - dispatch_arg4++; - - // Trim trailing whitespace from arg4 before looking - // for next comma - end = dispatch_arg4 + strlen(dispatch_arg4) - 1; - while (end > dispatch_arg4 && isspace(*end)) - end--; - *(end + 1) = '\0'; - - char *comma5 = strchr(dispatch_arg4, ','); - if (comma5) { - *comma5 = '\0'; - dispatch_arg5 = comma5 + 1; - // Trim leading whitespace from arg5 - while (isspace(*dispatch_arg5)) - dispatch_arg5++; - - // Trim trailing whitespace from arg5 - end = dispatch_arg5 + strlen(dispatch_arg5) - 1; - while (end > dispatch_arg5 && isspace(*end)) - end--; - *(end + 1) = '\0'; - } else { - dispatch_arg5 = ""; - } - } else { - dispatch_arg4 = ""; - dispatch_arg5 = ""; - } - } else { - dispatch_arg3 = ""; - dispatch_arg4 = ""; - dispatch_arg5 = ""; - } - } else { - dispatch_arg2 = ""; - dispatch_arg3 = ""; - dispatch_arg4 = ""; - dispatch_arg5 = ""; } - } else { - dispatch_arg1 = ""; - dispatch_arg2 = ""; - dispatch_arg3 = ""; - dispatch_arg4 = ""; - dispatch_arg5 = ""; + tokens[i] = str; } + + if (count > 0) + dispatch_cmd = tokens[0]; + if (count > 1) + dispatch_arg1 = tokens[1]; + if (count > 2) + dispatch_arg2 = tokens[2]; + if (count > 3) + dispatch_arg3 = tokens[3]; + if (count > 4) + dispatch_arg4 = tokens[4]; + if (count > 5) + dispatch_arg5 = tokens[5]; } break; case 'O': From 906dcb35a22dc4c28b2f0b434bb65fd3c44939ea Mon Sep 17 00:00:00 2001 From: killertofus Date: Sun, 12 Apr 2026 20:00:02 -0600 Subject: [PATCH 143/328] added opensuse instructions --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 843780ba..227f0d8c 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,12 @@ Finally, install the package: emerge --ask --verbose gui-wm/mangowm ``` +## openSUSE +The package is in the community-maintained repository called obs. + +```bash +sudo opi in mangowm +``` ## Fedora Linux The package is in the third-party Terra repository. First, add the [Terra Repository](https://terra.fyralabs.com/). From 700a5c452b09b51cace2b4e11d85af2861aad52e Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 19 Apr 2026 13:17:18 +0800 Subject: [PATCH 144/328] docs: remove useless tip --- docs/faq.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index 13c6391b..9c9288de 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -41,11 +41,10 @@ blur_optimized=0 ### My games are lagging or stuttering -Try enabling **SyncObj** timeline support and **Adaptive Sync** (VRR) if your monitor supports it. +Try enabling **SyncObj** timeline support. ```ini syncobj_enable=1 -adaptive_sync=1 ``` --- From 60f31cd1bf3a070feb816e3b1952179948743f62 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 21 Apr 2026 09:13:27 +0800 Subject: [PATCH 145/328] opt: dont apply viewback dispatch when no tag change --- src/mango.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/mango.c b/src/mango.c index 8fdff709..b9c77759 100644 --- a/src/mango.c +++ b/src/mango.c @@ -6317,10 +6317,14 @@ void view_in_mon(const Arg *arg, bool want_animation, Monitor *m, } if (arg->ui == UINT32_MAX) { - m->pertag->prevtag = get_tags_first_tag_num(m->tagset[m->seltags]); - m->seltags ^= 1; /* toggle sel tagset */ - m->pertag->curtag = get_tags_first_tag_num(m->tagset[m->seltags]); - goto toggleseltags; + if (m->tagset[0] != m->tagset[1]) { + m->pertag->prevtag = get_tags_first_tag_num(m->tagset[m->seltags]); + m->seltags ^= 1; /* toggle sel tagset */ + m->pertag->curtag = get_tags_first_tag_num(m->tagset[m->seltags]); + goto toggleseltags; + } else { + return; + } } if ((m->tagset[m->seltags] & arg->ui & TAGMASK) != 0) { From 461dcb6989325a5fd2fdbb75104f1187e37de0b4 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 21 Apr 2026 17:32:57 +0800 Subject: [PATCH 146/328] fix: opensilent client should insert fstack tail --- src/mango.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/mango.c b/src/mango.c index b9c77759..a73a71a4 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1569,12 +1569,17 @@ 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)) && - mon && - (!c->istagsilent || !newtags || - newtags & mon->tagset[mon->seltags])); + bool should_init_get_focus = + !c->isopensilent && + !(client_is_x11_popup(c) && client_should_ignore_focus(c)) && mon && + (!c->istagsilent || !newtags || newtags & mon->tagset[mon->seltags]); + + if (!should_init_get_focus) { + wl_list_remove(&c->flink); + wl_list_insert(fstack.prev, &c->flink); + } + + setmon(c, mon, newtags, should_init_get_focus); if (!c->isfloating) { c->old_stack_inner_per = c->stack_inner_per; @@ -4211,6 +4216,7 @@ mapnotify(struct wl_listener *listener, void *data) { } } else wl_list_insert(clients.prev, &c->link); // 尾部入栈 + wl_list_insert(&fstack, &c->flink); applyrules(c); From 4b8603181c9cb1c638a290f24cefae211d11c5b9 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 22 Apr 2026 10:15:55 +0800 Subject: [PATCH 147/328] opt: always notify x11 client position change --- src/client/client.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/client/client.h b/src/client/client.h index 965b4106..27e8ef33 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -312,11 +312,6 @@ static inline uint32_t client_set_size(Client *c, uint32_t width, 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; From ac61923e2f5cb73d0fb5c9dd692d19a9079a6a55 Mon Sep 17 00:00:00 2001 From: mdouda Date: Wed, 22 Apr 2026 18:43:33 +0200 Subject: [PATCH 148/328] active constraint nesting for input capture --- src/mango.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/mango.c b/src/mango.c index a73a71a4..85fc00ac 100644 --- a/src/mango.c +++ b/src/mango.c @@ -4384,20 +4384,23 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, if (active_constraint && cursor_mode != CurResize && cursor_mode != CurMove) { - toplevel_from_wlr_surface(active_constraint->surface, &c, NULL); - if (c && active_constraint->surface == - seat->pointer_state.focused_surface) { - sx = cursor->x - c->geom.x - c->bw; - sy = cursor->y - c->geom.y - c->bw; - if (wlr_region_confine(&active_constraint->region, sx, sy, - sx + dx, sy + dy, &sx_confined, - &sy_confined)) { - dx = sx_confined - sx; - dy = sy_confined - sy; - } + if (active_constraint->surface == + seat->pointer_state.focused_surface) { if (active_constraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED) return; + + toplevel_from_wlr_surface(active_constraint->surface, &c, NULL); + if (c) { + sx = cursor->x - c->geom.x - c->bw; + sy = cursor->y - c->geom.y - c->bw; + if (wlr_region_confine(&active_constraint->region, sx, sy, + sx + dx, sy + dy, &sx_confined, + &sy_confined)) { + dx = sx_confined - sx; + dy = sy_confined - sy; + } + } } } From b9c6a2c1964cb59536c5d3e02d2ff981a7eb36d2 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 24 Apr 2026 11:02:48 +0800 Subject: [PATCH 149/328] bump version to 0.12.9 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 64394cd9..ca9f2c2b 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('mango', ['c', 'cpp'], - version : '0.12.8', + version : '0.12.9', ) subdir('protocols') From 971457e32f7abcb40e3ab0e0c734b90adb991b40 Mon Sep 17 00:00:00 2001 From: Duke B Date: Sat, 2 May 2026 20:49:39 -0400 Subject: [PATCH 150/328] docs: fix cd directory in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 227f0d8c..bb48cd97 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,7 @@ meson build -Dprefix=/usr sudo ninja -C build install git clone https://github.com/mangowm/mango.git -cd mangowm +cd mango meson build -Dprefix=/usr sudo ninja -C build install ``` From 2619c652318fb28a0bf0a78f6f57a532f3acabee Mon Sep 17 00:00:00 2001 From: Duke B Date: Sun, 3 May 2026 15:33:06 -0400 Subject: [PATCH 151/328] opt: distinguish the acceleration of trackpad and mouse --- docs/configuration/input.md | 43 ++++++++--------- docs/configuration/miscellaneous.md | 1 - src/config/parse_config.h | 60 ++++++++++++++++------- src/mango.c | 74 +++++++++++++++++------------ 4 files changed, 107 insertions(+), 71 deletions(-) diff --git a/docs/configuration/input.md b/docs/configuration/input.md index ac30f179..ee12906a 100644 --- a/docs/configuration/input.md +++ b/docs/configuration/input.md @@ -35,6 +35,19 @@ xkb_rules_options=caps:escape,ctrl:nocaps --- +### Mouse Settings + +Configuration for external mice. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `mouse_natural_scrolling` | `0` | Invert scrolling direction. | +| `mouse_accel_profile` | `2` | `0` (None), `1` (Flat), `2` (Adaptive). | +| `mouse_accel_speed` | `0.0` | Speed adjustment (-1.0 to 1.0). | +| `left_handed` | `0` | Swap left and right buttons. | +| `axis_scroll_factor` | `1.0` | Scroll factor for axis scroll speed (0.1–10.0). | +--- + ### Trackpad Settings Specific settings for laptop touchpads. Some settings may require a relogin to take effect. @@ -45,15 +58,19 @@ 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). | +| `trackpad_accel_profile` | `2` | `0` (None), `1` (Flat), `2` (Adaptive). | +| `trackpad_accel_speed` | `0.0` | Speed adjustment (-1.0 to 1.0). | | `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). | +| `send_events_mode` | `0` | `0` (Enabled), `1` (Disabled), `2` (Disabled on external mouse). | | `drag_lock` | `1` | Lock dragging after tapping. | | `disable_while_typing` | `1` | Disable trackpad while typing. | | `left_handed` | `0` | Swap left/right buttons. | | `middle_button_emulation` | `0` | Emulate middle button. | -| `swipe_min_threshold` | `1` | Minimum swipe threshold. | - +| `swipe_min_threshold` | `1` | Minimum swipe threshold when use gesture. | +| `button_map` | `0` | `0` (Left/right/middle), `1` (Left/middle/right). | +| `trackpad_scroll_factor` | `1.0` | Scroll factor for trackpad scroll speed (0.1–10.0). | --- **Detailed descriptions:** @@ -79,9 +96,9 @@ Specific settings for laptop touchpads. Some settings may require a relogin to t - `1` — Button areas: use software-defined areas on the touchpad to generate button events. - `2` — Clickfinger: the number of fingers determines which button is pressed. -- `accel_profile` values: +- `mouse_accel_profile` or `trackpad_scroll_profile` values: - `0` — No acceleration. - - `1` — Flat: no dynamic acceleration. Pointer speed = original input speed × (1 + `accel_speed`). + - `1` — Flat: no dynamic acceleration. Pointer speed = original input speed × (1 + `mouse_accel_speed`). - `2` — Adaptive: slow movement results in less acceleration, fast movement results in more. - `button_map` values: @@ -94,24 +111,6 @@ Specific settings for laptop touchpads. Some settings may require a relogin to t - `2` — Disable this device when an external pointer device is plugged in. --- - -### Mouse Settings - -Configuration for external mice. - -| Setting | Default | Description | -| :--- | :--- | :--- | -| `mouse_natural_scrolling` | `0` | Invert scrolling direction. | -| `accel_profile` | `2` | `0` (None), `1` (Flat), `2` (Adaptive). | -| `accel_speed` | `0.0` | Speed adjustment (-1.0 to 1.0). | -| `left_handed` | `0` | Swap left and right buttons. | -| `middle_button_emulation` | `0` | Emulate middle button. | -| `swipe_min_threshold` | `1` | Minimum swipe threshold. | -| `send_events_mode` | `0` | `0` (Enabled), `1` (Disabled), `2` (Disabled on external mouse). | -| `button_map` | `0` | `0` (Left/right/middle), `1` (Left/middle/right). | - ---- - --- ## Keyboard Layout Switching diff --git a/docs/configuration/miscellaneous.md b/docs/configuration/miscellaneous.md index 2e5a1e92..3be0facc 100644 --- a/docs/configuration/miscellaneous.md +++ b/docs/configuration/miscellaneous.md @@ -25,7 +25,6 @@ description: Advanced settings for XWayland, focus behavior, and system integrat | `drag_corner` | `3` | Corner for drag-to-tile detection (0: none, 1–3: corners, 4: auto-detect). | | `drag_warp_cursor` | `1` | Warp cursor when dragging windows to tile. | | `axis_bind_apply_timeout` | `100` | Timeout (ms) for detecting consecutive scroll events for axis bindings. | -| `axis_scroll_factor` | `1.0` | Scroll factor for axis scroll speed (0.1–10.0). | ## Multi-Monitor & Tags diff --git a/src/config/parse_config.h b/src/config/parse_config.h index e02b5017..b16cf476 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -262,26 +262,33 @@ typedef struct { int32_t repeat_delay; uint32_t numlockon; - /* Trackpad */ - int32_t disable_trackpad; - int32_t tap_to_click; - int32_t tap_and_drag; - int32_t drag_lock; - int32_t mouse_natural_scrolling; - int32_t trackpad_natural_scrolling; + /* common pointer */ int32_t disable_while_typing; int32_t left_handed; int32_t middle_button_emulation; - uint32_t accel_profile; - double accel_speed; uint32_t scroll_method; uint32_t scroll_button; uint32_t click_method; uint32_t send_events_mode; - uint32_t button_map; + /* mouse */ + int32_t mouse_natural_scrolling; + uint32_t mouse_accel_profile; + double mouse_accel_speed; double axis_scroll_factor; + /* Trackpad */ + int32_t trackpad_natural_scrolling; + uint32_t trackpad_accel_profile; + double trackpad_accel_speed; + double trackpad_scroll_factor; + int32_t disable_trackpad; + int32_t tap_to_click; + int32_t tap_and_drag; + int32_t drag_lock; + uint32_t button_map; + + /* window effects */ int32_t blur; int32_t blur_layer; int32_t blur_optimized; @@ -297,6 +304,7 @@ typedef struct { int32_t shadows_position_y; float shadowscolor[4]; + /* appearance */ int32_t smartgaps; uint32_t gappih; uint32_t gappiv; @@ -1661,10 +1669,14 @@ bool parse_option(Config *config, char *key, char *value) { config->left_handed = atoi(value); } else if (strcmp(key, "middle_button_emulation") == 0) { config->middle_button_emulation = atoi(value); - } else if (strcmp(key, "accel_profile") == 0) { - config->accel_profile = atoi(value); - } else if (strcmp(key, "accel_speed") == 0) { - config->accel_speed = atof(value); + } else if (strcmp(key, "mouse_accel_profile") == 0) { + config->mouse_accel_profile = atoi(value); + } else if (strcmp(key, "mouse_accel_speed") == 0) { + config->mouse_accel_speed = atof(value); + } else if (strcmp(key, "trackpad_accel_profile") == 0) { + config->trackpad_accel_profile = atoi(value); + } else if (strcmp(key, "trackpad_accel_speed") == 0) { + config->trackpad_accel_speed = atof(value); } else if (strcmp(key, "scroll_method") == 0) { config->scroll_method = atoi(value); } else if (strcmp(key, "scroll_button") == 0) { @@ -1677,6 +1689,8 @@ bool parse_option(Config *config, char *key, char *value) { config->button_map = atoi(value); } else if (strcmp(key, "axis_scroll_factor") == 0) { config->axis_scroll_factor = atof(value); + } else if (strcmp(key, "trackpad_scroll_factor") == 0) { + config->trackpad_scroll_factor = atof(value); } else if (strcmp(key, "gappih") == 0) { config->gappih = atoi(value); } else if (strcmp(key, "gappiv") == 0) { @@ -3210,8 +3224,13 @@ void override_config(void) { config.swipe_min_threshold = CLAMP_INT(config.swipe_min_threshold, 1, 1000); config.mouse_natural_scrolling = CLAMP_INT(config.mouse_natural_scrolling, 0, 1); - config.accel_profile = CLAMP_INT(config.accel_profile, 0, 2); - config.accel_speed = CLAMP_FLOAT(config.accel_speed, -1.0f, 1.0f); + config.mouse_accel_profile = CLAMP_INT(config.mouse_accel_profile, 0, 2); + config.mouse_accel_speed = + CLAMP_FLOAT(config.mouse_accel_speed, -1.0f, 1.0f); + config.trackpad_accel_profile = + CLAMP_INT(config.trackpad_accel_profile, 0, 2); + config.trackpad_accel_speed = + CLAMP_FLOAT(config.trackpad_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, 279); config.click_method = CLAMP_INT(config.click_method, 0, 2); @@ -3219,6 +3238,8 @@ void override_config(void) { config.button_map = CLAMP_INT(config.button_map, 0, 1); config.axis_scroll_factor = CLAMP_FLOAT(config.axis_scroll_factor, 0.1f, 10.0f); + config.trackpad_scroll_factor = + CLAMP_FLOAT(config.trackpad_scroll_factor, 0.1f, 10.0f); config.gappih = CLAMP_INT(config.gappih, 0, 1000); config.gappiv = CLAMP_INT(config.gappiv, 0, 1000); config.gappoh = CLAMP_INT(config.gappoh, 0, 1000); @@ -3311,6 +3332,7 @@ void set_value_default() { config.scratchpad_cross_monitor = 0; config.focus_cross_tag = 0; config.axis_scroll_factor = 1.0; + config.trackpad_scroll_factor = 1.0; config.view_current_to_back = 0; config.single_scratchpad = 1; config.xwayland_persistence = 1; @@ -3351,8 +3373,10 @@ void set_value_default() { config.disable_while_typing = 1; config.left_handed = 0; config.middle_button_emulation = 0; - config.accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; - config.accel_speed = 0.0; + config.mouse_accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; + config.mouse_accel_speed = 0.0; + config.trackpad_accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; + config.trackpad_accel_speed = 0.0; config.scroll_method = LIBINPUT_CONFIG_SCROLL_2FG; config.scroll_button = 274; config.click_method = LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS; diff --git a/src/mango.c b/src/mango.c index 85fc00ac..f671ae44 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1838,9 +1838,21 @@ void arrangelayers(Monitor *m) { arrangelayer(m, &m->layers[i], &usable_area, 0); } +bool pointer_is_trackpad(struct wlr_pointer *pointer) { + struct libinput_device *device; + + if (wlr_input_device_is_libinput(&pointer->base) && + (device = wlr_libinput_get_device_handle(&pointer->base))) { + if (libinput_device_config_tap_get_finger_count(device) > 0) { + return true; + } + } + + return false; +} + void // 鼠标滚轮事件 axisnotify(struct wl_listener *listener, void *data) { - /* This event is forwarded by the cursor when a pointer emits an axis event, * for example when you move the scroll wheel. */ struct wlr_pointer_axis_event *event = data; @@ -1849,6 +1861,7 @@ axisnotify(struct wl_listener *listener, void *data) { AxisBinding *a; int32_t ji; uint32_t adir; + double target_scroll_factor; // IDLE_NOTIFY_ACTIVITY; handlecursoractivity(); wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); @@ -1895,12 +1908,17 @@ axisnotify(struct wl_listener *listener, void *data) { * implemented checking the event's orientation and the delta of the event */ /* Notify the client with pointer focus of the axis event. */ + + target_scroll_factor = pointer_is_trackpad(event->pointer) + ? config.axis_scroll_factor + : config.trackpad_scroll_factor; + wlr_seat_pointer_notify_axis( seat, // 滚轮事件发送给客户端也就是窗口 event->time_msec, event->orientation, event->delta * config.axis_scroll_factor, - roundf(event->delta_discrete * config.axis_scroll_factor), - event->source, event->relative_direction); + roundf(event->delta_discrete * target_scroll_factor), event->source, + event->relative_direction); } int32_t ongesture(struct wlr_pointer_swipe_end_event *event) { @@ -2054,21 +2072,11 @@ void place_drag_tile_client(Client *c) { } bool check_trackpad_disabled(struct wlr_pointer *pointer) { - struct libinput_device *device; - - if (!config.disable_trackpad) + if (!config.disable_trackpad) { return false; - - if (wlr_input_device_is_libinput(&pointer->base) && - (device = wlr_libinput_get_device_handle(&pointer->base))) { - - // 如果是触摸板且被禁用,忽略事件 - if (libinput_device_config_tap_get_finger_count(device) > 0) { - return true; // 不处理事件 - } } - return false; + return pointer_is_trackpad(pointer); } void // 鼠标按键事件 @@ -3206,6 +3214,22 @@ void destroyinputdevice(struct wl_listener *listener, void *data) { free(input_dev); } +void pointer_set_accel(struct libinput_device *device, bool natural_scrolling, + uint32_t mouse_accel_profile, double mouse_accel_speed) { + libinput_device_config_scroll_set_natural_scroll_enabled(device, + natural_scrolling); + if (mouse_accel_profile && + libinput_device_config_accel_is_available(device)) { + libinput_device_config_accel_set_profile(device, mouse_accel_profile); + libinput_device_config_accel_set_speed(device, mouse_accel_speed); + } else { + // profile cannot be directly applied to 0, need to set to 1 first + libinput_device_config_accel_set_profile(device, 1); + libinput_device_config_accel_set_profile(device, 0); + libinput_device_config_accel_set_speed(device, 0); + } +} + void configure_pointer(struct libinput_device *device) { if (libinput_device_config_tap_get_finger_count(device)) { libinput_device_config_tap_set_enabled(device, config.tap_to_click); @@ -3214,11 +3238,12 @@ void configure_pointer(struct libinput_device *device) { libinput_device_config_tap_set_drag_lock_enabled(device, config.drag_lock); libinput_device_config_tap_set_button_map(device, config.button_map); - libinput_device_config_scroll_set_natural_scroll_enabled( - device, config.trackpad_natural_scrolling); + pointer_set_accel(device, config.trackpad_natural_scrolling, + config.trackpad_accel_profile, + config.trackpad_accel_speed); } else { - libinput_device_config_scroll_set_natural_scroll_enabled( - device, config.mouse_natural_scrolling); + pointer_set_accel(device, config.mouse_natural_scrolling, + config.mouse_accel_profile, config.mouse_accel_speed); } if (libinput_device_config_dwt_is_available(device)) @@ -3246,17 +3271,6 @@ void configure_pointer(struct libinput_device *device) { if (libinput_device_config_send_events_get_modes(device)) libinput_device_config_send_events_set_mode(device, config.send_events_mode); - - if (config.accel_profile && - libinput_device_config_accel_is_available(device)) { - libinput_device_config_accel_set_profile(device, config.accel_profile); - libinput_device_config_accel_set_speed(device, config.accel_speed); - } else { - // profile cannot be directly applied to 0, need to set to 1 first - libinput_device_config_accel_set_profile(device, 1); - libinput_device_config_accel_set_profile(device, 0); - libinput_device_config_accel_set_speed(device, 0); - } } void createpointer(struct wlr_pointer *pointer) { From ef7a1c61e58d6b7b63712f86e8d5c50347885310 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 4 May 2026 12:34:09 +0800 Subject: [PATCH 152/328] fix: fix scroll factor --- src/mango.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mango.c b/src/mango.c index f671ae44..2a946ebe 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1910,13 +1910,13 @@ axisnotify(struct wl_listener *listener, void *data) { /* Notify the client with pointer focus of the axis event. */ target_scroll_factor = pointer_is_trackpad(event->pointer) - ? config.axis_scroll_factor - : config.trackpad_scroll_factor; + ? config.trackpad_scroll_factor + : config.axis_scroll_factor; wlr_seat_pointer_notify_axis( seat, // 滚轮事件发送给客户端也就是窗口 event->time_msec, event->orientation, - event->delta * config.axis_scroll_factor, + event->delta * target_scroll_factor, roundf(event->delta_discrete * target_scroll_factor), event->source, event->relative_direction); } From 2a3493fee2c495763353446f5a704042647257a0 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 5 May 2026 08:55:16 +0800 Subject: [PATCH 153/328] opt: avoid layer re-enter output when cleanuping monitor --- src/mango.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/mango.c b/src/mango.c index 2a946ebe..1f49f672 100644 --- a/src/mango.c +++ b/src/mango.c @@ -539,6 +539,7 @@ struct Monitor { struct wlr_scene_optimized_blur *blur; char last_surface_ws_name[256]; struct wlr_ext_workspace_group_handle_v1 *ext_group; + bool iscleanuping; }; typedef struct { @@ -1824,6 +1825,9 @@ void arrangelayers(Monitor *m) { if (!m->wlr_output->enabled) return; + if (m->iscleanuping) + return; + /* Arrange exclusive surfaces from top->bottom */ for (i = 3; i >= 0; i--) arrangelayer(m, &m->layers[i], &usable_area, 1); @@ -2331,6 +2335,8 @@ void cleanupmon(struct wl_listener *listener, void *data) { LayerSurface *l = NULL, *tmp = NULL; uint32_t i; + m->iscleanuping = true; + /* m->layers[i] are intentionally not unlinked */ for (i = 0; i < LENGTH(m->layers); i++) { wl_list_for_each_safe(l, tmp, &m->layers[i], link) @@ -3032,6 +3038,7 @@ void createmon(struct wl_listener *listener, void *data) { struct wl_event_loop *loop = wl_display_get_event_loop(dpy); m = wlr_output->data = ecalloc(1, sizeof(*m)); + m->iscleanuping = false; m->skip_frame_timeout = wl_event_loop_add_timer(loop, monitor_skip_frame_timeout_callback, m); m->skiping_frame = false; From 586ee8e6991e67cc1e76a84804bce7c861e0c9bb Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 6 May 2026 09:49:36 +0800 Subject: [PATCH 154/328] fix: update ext-workspace --- src/ext-protocol/ext-workspace.h | 108 ++++++++------ src/ext-protocol/wlr_ext_workspace_v1.c | 180 +++++++++++------------- src/ext-protocol/wlr_ext_workspace_v1.h | 74 ++++++++-- 3 files changed, 203 insertions(+), 159 deletions(-) diff --git a/src/ext-protocol/ext-workspace.h b/src/ext-protocol/ext-workspace.h index 28aaeeeb..d938ce51 100644 --- a/src/ext-protocol/ext-workspace.h +++ b/src/ext-protocol/ext-workspace.h @@ -7,15 +7,11 @@ typedef struct Monitor Monitor; struct workspace { - struct wl_list link; // Link in global workspaces list - uint32_t tag; // Numeric identifier (1-9, 0=overview) - Monitor *m; // Associated monitor - struct wlr_ext_workspace_handle_v1 *ext_workspace; // Protocol object - /* Event listeners */ - struct wl_listener activate; - struct wl_listener deactivate; - struct wl_listener assign; - struct wl_listener remove; + struct wl_list link; + uint32_t tag; + Monitor *m; + struct wlr_ext_workspace_handle_v1 *ext_workspace; + struct wl_listener commit; }; struct wlr_ext_workspace_manager_v1 *ext_manager; @@ -43,30 +39,60 @@ void toggle_workspace(struct workspace *target) { } } -static void handle_ext_workspace_activate(struct wl_listener *listener, - void *data) { - struct workspace *workspace = - wl_container_of(listener, workspace, activate); +static void handle_ext_commit(struct wl_listener *listener, void *data) { + struct wlr_ext_workspace_v1_commit_event *event = data; + struct wlr_ext_workspace_v1_request *request; - if (workspace->m->isoverview) { - return; + wl_list_for_each(request, event->requests, link) { + switch (request->type) { + case WLR_EXT_WORKSPACE_V1_REQUEST_ACTIVATE: { + if (!request->activate.workspace) { + break; + } + + struct workspace *workspace = NULL; + struct workspace *w; + wl_list_for_each(w, &workspaces, link) { + if (w->ext_workspace == request->activate.workspace) { + workspace = w; + break; + } + } + + if (!workspace || workspace->m->isoverview) { + break; + } + + goto_workspace(workspace); + wlr_log(WLR_INFO, "ext activating workspace %d", workspace->tag); + break; + } + case WLR_EXT_WORKSPACE_V1_REQUEST_DEACTIVATE: { + if (!request->deactivate.workspace) { + break; + } + + struct workspace *workspace = NULL; + struct workspace *w; + wl_list_for_each(w, &workspaces, link) { + if (w->ext_workspace == request->deactivate.workspace) { + workspace = w; + break; + } + } + + if (!workspace || workspace->m->isoverview) { + break; + } + + toggle_workspace(workspace); + wlr_log(WLR_INFO, "ext deactivating workspace %d", workspace->tag); + break; + } + default: + break; + } } - - goto_workspace(workspace); - wlr_log(WLR_INFO, "ext activating workspace %d", workspace->tag); -} - -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); } static const char *get_name_from_tag(uint32_t tag) { @@ -76,8 +102,6 @@ static const char *get_name_from_tag(uint32_t tag) { } void destroy_workspace(struct workspace *workspace) { - wl_list_remove(&workspace->activate.link); - wl_list_remove(&workspace->deactivate.link); wlr_ext_workspace_handle_v1_destroy(workspace->ext_workspace); wl_list_remove(&workspace->link); free(workspace); @@ -112,17 +136,12 @@ static void add_workspace_by_tag(int32_t tag, Monitor *m) { workspace->m = m; workspace->ext_workspace = wlr_ext_workspace_handle_v1_create( ext_manager, name, EXT_WORKSPACE_ENABLE_CAPS); + + workspace->ext_workspace->data = workspace; + wlr_ext_workspace_handle_v1_set_group(workspace->ext_workspace, m->ext_group); wlr_ext_workspace_handle_v1_set_name(workspace->ext_workspace, name); - - workspace->activate.notify = handle_ext_workspace_activate; - wl_signal_add(&workspace->ext_workspace->events.activate, - &workspace->activate); - - workspace->deactivate.notify = handle_ext_workspace_deactivate; - wl_signal_add(&workspace->ext_workspace->events.deactivate, - &workspace->deactivate); } void dwl_ext_workspace_printstatus(Monitor *m) { @@ -180,8 +199,11 @@ void refresh_monitors_workspaces_status(Monitor *m) { } void workspaces_init() { - /* Create the global workspace manager with activation capability */ ext_manager = wlr_ext_workspace_manager_v1_create(dpy, 1); - /* Initialize the global workspaces list */ + wl_list_init(&workspaces); + + static struct wl_listener commit_listener; + commit_listener.notify = handle_ext_commit; + wl_signal_add(&ext_manager->events.commit, &commit_listener); } \ No newline at end of file diff --git a/src/ext-protocol/wlr_ext_workspace_v1.c b/src/ext-protocol/wlr_ext_workspace_v1.c index 2d781b34..dca93e93 100644 --- a/src/ext-protocol/wlr_ext_workspace_v1.c +++ b/src/ext-protocol/wlr_ext_workspace_v1.c @@ -1,7 +1,3 @@ -// bash on: https://gitlab.freedesktop.org/tokyo4j/wlroots/-/tree/ext-workspace -// TODO: remove this file -// refer: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/5115 - #include "wlr_ext_workspace_v1.h" #include "ext-workspace-v1-protocol.h" #include @@ -11,27 +7,6 @@ #define EXT_WORKSPACE_V1_VERSION 1 -enum wlr_ext_workspace_v1_request_type { - WLR_EXT_WORKSPACE_V1_REQUEST_CREATE_WORKSPACE, - WLR_EXT_WORKSPACE_V1_REQUEST_ACTIVATE, - WLR_EXT_WORKSPACE_V1_REQUEST_DEACTIVATE, - WLR_EXT_WORKSPACE_V1_REQUEST_ASSIGN, - WLR_EXT_WORKSPACE_V1_REQUEST_REMOVE, -}; - -struct wlr_ext_workspace_v1_request { - enum wlr_ext_workspace_v1_request_type type; - - // CREATE_WORKSPACE - char *name; - // CREATE_WORKSPACE / ASSIGN - struct wlr_ext_workspace_group_handle_v1 *group; - // ACTIVATE / DEACTIVATE / ASSIGN / REMOVE - struct wlr_ext_workspace_handle_v1 *workspace; - - struct wl_list link; // wlr_ext_workspace_manager_v1_resource.requests -}; - struct wlr_ext_workspace_v1_group_output { struct wlr_output *output; struct wlr_ext_workspace_group_handle_v1 *group; @@ -117,7 +92,7 @@ static void workspace_handle_activate(struct wl_client *client, return; } req->type = WLR_EXT_WORKSPACE_V1_REQUEST_ACTIVATE; - req->workspace = workspace_res->workspace; + req->activate.workspace = workspace_res->workspace; wl_list_insert(workspace_res->manager->requests.prev, &req->link); } @@ -136,7 +111,7 @@ workspace_handle_deactivate(struct wl_client *client, return; } req->type = WLR_EXT_WORKSPACE_V1_REQUEST_DEACTIVATE; - req->workspace = workspace_res->workspace; + req->deactivate.workspace = workspace_res->workspace; wl_list_insert(workspace_res->manager->requests.prev, &req->link); } @@ -157,8 +132,8 @@ static void workspace_handle_assign(struct wl_client *client, return; } req->type = WLR_EXT_WORKSPACE_V1_REQUEST_ASSIGN; - req->group = group_res->group; - req->workspace = workspace_res->workspace; + req->assign.group = group_res->group; + req->assign.workspace = workspace_res->workspace; wl_list_insert(workspace_res->manager->requests.prev, &req->link); } @@ -176,7 +151,7 @@ static void workspace_handle_remove(struct wl_client *client, return; } req->type = WLR_EXT_WORKSPACE_V1_REQUEST_REMOVE; - req->workspace = workspace_res->workspace; + req->remove.workspace = workspace_res->workspace; wl_list_insert(workspace_res->manager->requests.prev, &req->link); } @@ -202,14 +177,14 @@ static void group_handle_create_workspace(struct wl_client *client, wl_resource_post_no_memory(group_resource); return; } - req->name = strdup(name); - if (!req->name) { + req->create_workspace.name = strdup(name); + if (!req->create_workspace.name) { free(req); wl_resource_post_no_memory(group_resource); return; } req->type = WLR_EXT_WORKSPACE_V1_REQUEST_CREATE_WORKSPACE; - req->group = group_res->group; + req->create_workspace.group = group_res->group; wl_list_insert(group_res->manager->requests.prev, &req->link); } @@ -313,10 +288,55 @@ static struct wlr_ext_workspace_group_v1_resource *create_group_resource( return group_res; } -static void destroy_request(struct wlr_ext_workspace_v1_request *req) { - wl_list_remove(&req->link); - free(req->name); - free(req); +static void +destroy_requests(struct wlr_ext_workspace_manager_v1_resource *manager_res) { + struct wlr_ext_workspace_v1_request *req, *tmp; + wl_list_for_each_safe(req, tmp, &manager_res->requests, link) { + if (req->type == WLR_EXT_WORKSPACE_V1_REQUEST_CREATE_WORKSPACE) { + free(req->create_workspace.name); + } + wl_list_remove(&req->link); + free(req); + } +} + +static void +clear_requests_by(struct wlr_ext_workspace_manager_v1_resource *manager_res, + struct wlr_ext_workspace_group_handle_v1 *group, + struct wlr_ext_workspace_handle_v1 *workspace) { + struct wlr_ext_workspace_v1_request *req, *tmp; + wl_list_for_each_safe(req, tmp, &manager_res->requests, link) { + switch (req->type) { + case WLR_EXT_WORKSPACE_V1_REQUEST_CREATE_WORKSPACE: + if (group && req->create_workspace.group == group) { + req->create_workspace.group = NULL; + } + break; + case WLR_EXT_WORKSPACE_V1_REQUEST_ACTIVATE: + if (workspace && req->activate.workspace == workspace) { + req->activate.workspace = NULL; + } + break; + case WLR_EXT_WORKSPACE_V1_REQUEST_DEACTIVATE: + if (workspace && req->deactivate.workspace == workspace) { + req->deactivate.workspace = NULL; + } + break; + case WLR_EXT_WORKSPACE_V1_REQUEST_ASSIGN: + if (workspace && req->assign.workspace == workspace) { + req->assign.workspace = NULL; + } + if (group && req->assign.group == group) { + req->assign.group = NULL; + } + break; + case WLR_EXT_WORKSPACE_V1_REQUEST_REMOVE: + if (workspace && req->remove.workspace == workspace) { + req->remove.workspace = NULL; + } + break; + } + } } static void manager_handle_commit(struct wl_client *client, @@ -327,32 +347,11 @@ static void manager_handle_commit(struct wl_client *client, return; } - struct wlr_ext_workspace_v1_request *req, *tmp; - wl_list_for_each_safe(req, tmp, &manager_res->requests, link) { - switch (req->type) { - case WLR_EXT_WORKSPACE_V1_REQUEST_CREATE_WORKSPACE:; - struct wlr_ext_workspace_group_handle_v1_create_workspace_event - event = { - .name = req->name, - }; - wl_signal_emit_mutable(&req->group->events.create_workspace, - &event); - break; - case WLR_EXT_WORKSPACE_V1_REQUEST_ACTIVATE: - wl_signal_emit_mutable(&req->workspace->events.activate, NULL); - break; - case WLR_EXT_WORKSPACE_V1_REQUEST_DEACTIVATE: - wl_signal_emit_mutable(&req->workspace->events.deactivate, NULL); - break; - case WLR_EXT_WORKSPACE_V1_REQUEST_ASSIGN: - wl_signal_emit_mutable(&req->workspace->events.assign, req->group); - break; - case WLR_EXT_WORKSPACE_V1_REQUEST_REMOVE: - wl_signal_emit_mutable(&req->workspace->events.remove, NULL); - break; - } - destroy_request(req); - } + struct wlr_ext_workspace_v1_commit_event commit_event = { + .requests = &manager_res->requests, + }; + wl_signal_emit_mutable(&manager_res->manager->events.commit, &commit_event); + destroy_requests(manager_res); } static void handle_idle(void *data) { @@ -406,10 +405,8 @@ static const struct ext_workspace_manager_v1_interface manager_impl = { static void destroy_manager_resource( struct wlr_ext_workspace_manager_v1_resource *manager_res) { - struct wlr_ext_workspace_v1_request *req, *tmp; - wl_list_for_each_safe(req, tmp, &manager_res->requests, link) { - destroy_request(req); - } + destroy_requests(manager_res); + struct wlr_ext_workspace_v1_resource *workspace_res, *tmp2; wl_list_for_each_safe(workspace_res, tmp2, &manager_res->workspace_resources, @@ -531,6 +528,7 @@ static void manager_handle_display_destroy(struct wl_listener *listener, wl_container_of(listener, manager, display_destroy); wl_signal_emit_mutable(&manager->events.destroy, NULL); + assert(wl_list_empty(&manager->events.commit.listener_list)); assert(wl_list_empty(&manager->events.destroy.listener_list)); struct wlr_ext_workspace_group_handle_v1 *group, *tmp; @@ -583,6 +581,7 @@ wlr_ext_workspace_manager_v1_create(struct wl_display *display, wl_list_init(&manager->groups); wl_list_init(&manager->workspaces); wl_list_init(&manager->resources); + wl_signal_init(&manager->events.commit); wl_signal_init(&manager->events.destroy); return manager; @@ -601,7 +600,6 @@ wlr_ext_workspace_group_handle_v1_create( wl_list_init(&group->outputs); wl_list_init(&group->resources); - wl_signal_init(&group->events.create_workspace); wl_signal_init(&group->events.destroy); wl_list_insert(manager->groups.prev, &group->link); @@ -689,7 +687,6 @@ void wlr_ext_workspace_group_handle_v1_destroy( wl_signal_emit_mutable(&group->events.destroy, NULL); - assert(wl_list_empty(&group->events.create_workspace.listener_list)); assert(wl_list_empty(&group->events.destroy.listener_list)); struct wlr_ext_workspace_handle_v1 *workspace; @@ -708,12 +705,7 @@ void wlr_ext_workspace_group_handle_v1_destroy( struct wlr_ext_workspace_manager_v1_resource *manager_res; wl_list_for_each(manager_res, &group->manager->resources, link) { - struct wlr_ext_workspace_v1_request *req, *tmp2; - wl_list_for_each_safe(req, tmp2, &manager_res->requests, link) { - if (req->group == group) { - destroy_request(req); - } - } + clear_requests_by(manager_res, group, NULL); } struct wlr_ext_workspace_v1_group_output *group_output, *tmp3; @@ -822,13 +814,9 @@ wlr_ext_workspace_handle_v1_create(struct wlr_ext_workspace_manager_v1 *manager, wl_list_init(&workspace->resources); wl_array_init(&workspace->coordinates); - wl_signal_init(&workspace->events.activate); - wl_signal_init(&workspace->events.deactivate); - wl_signal_init(&workspace->events.remove); - wl_signal_init(&workspace->events.assign); wl_signal_init(&workspace->events.destroy); - wl_list_insert(&manager->workspaces, &workspace->link); + wl_list_insert(manager->workspaces.prev, &workspace->link); struct wlr_ext_workspace_manager_v1_resource *manager_res; wl_list_for_each(manager_res, &manager->resources, link) { @@ -855,10 +843,6 @@ void wlr_ext_workspace_handle_v1_destroy( wl_signal_emit_mutable(&workspace->events.destroy, NULL); - assert(wl_list_empty(&workspace->events.activate.listener_list)); - assert(wl_list_empty(&workspace->events.deactivate.listener_list)); - assert(wl_list_empty(&workspace->events.remove.listener_list)); - assert(wl_list_empty(&workspace->events.assign.listener_list)); assert(wl_list_empty(&workspace->events.destroy.listener_list)); if (workspace->group) { @@ -873,12 +857,7 @@ void wlr_ext_workspace_handle_v1_destroy( struct wlr_ext_workspace_manager_v1_resource *manager_res; wl_list_for_each(manager_res, &workspace->manager->resources, link) { - struct wlr_ext_workspace_v1_request *req, *tmp2; - wl_list_for_each_safe(req, tmp2, &manager_res->requests, link) { - if (req->workspace == workspace) { - destroy_request(req); - } - } + clear_requests_by(manager_res, NULL, workspace); } manager_schedule_done(workspace->manager); @@ -929,23 +908,22 @@ void wlr_ext_workspace_handle_v1_set_name( manager_schedule_done(workspace->manager); } -static bool array_equal(struct wl_array *a, struct wl_array *b) { - return (a->size == b->size) && - (a->size == 0 || memcmp(a->data, b->data, a->size) == 0); -} - void wlr_ext_workspace_handle_v1_set_coordinates( - struct wlr_ext_workspace_handle_v1 *workspace, - struct wl_array *coordinates) { - assert(coordinates); - - if (array_equal(&workspace->coordinates, coordinates)) { + struct wlr_ext_workspace_handle_v1 *workspace, const uint32_t *coords, + size_t coords_len) { + size_t size = coords_len * sizeof(coords[0]); + if (size == workspace->coordinates.size && + (size == 0 || memcmp(workspace->coordinates.data, coords, size) == 0)) { return; } wl_array_release(&workspace->coordinates); wl_array_init(&workspace->coordinates); - wl_array_copy(&workspace->coordinates, coordinates); + struct wl_array arr = { + .data = (void *)coords, + .size = size, + }; + wl_array_copy(&workspace->coordinates, &arr); struct wlr_ext_workspace_v1_resource *workspace_res; wl_list_for_each(workspace_res, &workspace->resources, link) { diff --git a/src/ext-protocol/wlr_ext_workspace_v1.h b/src/ext-protocol/wlr_ext_workspace_v1.h index a2a733b3..183d5801 100644 --- a/src/ext-protocol/wlr_ext_workspace_v1.h +++ b/src/ext-protocol/wlr_ext_workspace_v1.h @@ -1,21 +1,69 @@ -// bash on: https://gitlab.freedesktop.org/tokyo4j/wlroots/-/tree/ext-workspace -// TODO: remove this file -// refer: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/5115 +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_EXT_WORKSPACE_V1_H +#define WLR_TYPES_WLR_EXT_WORKSPACE_V1_H #include #include struct wlr_output; +enum wlr_ext_workspace_v1_request_type { + WLR_EXT_WORKSPACE_V1_REQUEST_CREATE_WORKSPACE, + WLR_EXT_WORKSPACE_V1_REQUEST_ACTIVATE, + WLR_EXT_WORKSPACE_V1_REQUEST_DEACTIVATE, + WLR_EXT_WORKSPACE_V1_REQUEST_ASSIGN, + WLR_EXT_WORKSPACE_V1_REQUEST_REMOVE, +}; + +struct wlr_ext_workspace_v1_request { + enum wlr_ext_workspace_v1_request_type type; + struct wl_list link; // wlr_ext_workspace_manager_v1_resource.requests + union { + struct { + char *name; + struct wlr_ext_workspace_group_handle_v1 + *group; // NULL if destroyed + } create_workspace; + struct { + struct wlr_ext_workspace_handle_v1 *workspace; // NULL if destroyed + } activate; + struct { + struct wlr_ext_workspace_handle_v1 *workspace; // NULL if destroyed + } deactivate; + struct { + struct wlr_ext_workspace_handle_v1 *workspace; // NULL if destroyed + struct wlr_ext_workspace_group_handle_v1 + *group; // NULL if destroyed + } assign; + struct { + struct wlr_ext_workspace_handle_v1 *workspace; // NULL if destroyed + } remove; + }; +}; + +struct wlr_ext_workspace_v1_commit_event { + struct wl_list *requests; // wlr_ext_workspace_v1_request.link +}; + struct wlr_ext_workspace_manager_v1 { struct wl_global *global; struct wl_list groups; // wlr_ext_workspace_group_handle_v1.link struct wl_list workspaces; // wlr_ext_workspace_handle_v1.link struct { + struct wl_signal commit; // wlr_ext_workspace_v1_commit_event struct wl_signal destroy; } events; + void *data; + struct { struct wl_list resources; // wlr_ext_workspace_manager_v1_resource.link struct wl_event_source *idle_source; @@ -24,21 +72,17 @@ struct wlr_ext_workspace_manager_v1 { }; }; -struct wlr_ext_workspace_group_handle_v1_create_workspace_event { - const char *name; -}; - struct wlr_ext_workspace_group_handle_v1 { struct wlr_ext_workspace_manager_v1 *manager; uint32_t caps; // ext_workspace_group_handle_v1_group_capabilities struct { - struct wl_signal - create_workspace; // wlr_ext_workspace_group_handle_v1_create_workspace_event struct wl_signal destroy; } events; struct wl_list link; // wlr_ext_workspace_manager_v1.groups + void *data; + struct { struct wl_list outputs; // wlr_ext_workspace_v1_group_output.link struct wl_list resources; // wlr_ext_workspace_manager_v1_resource.link @@ -55,15 +99,13 @@ struct wlr_ext_workspace_handle_v1 { uint32_t state; // ext_workspace_handle_v1_state struct { - struct wl_signal activate; - struct wl_signal deactivate; - struct wl_signal remove; - struct wl_signal assign; // wlr_ext_workspace_group_handle_v1 struct wl_signal destroy; } events; struct wl_list link; // wlr_ext_workspace_manager_v1.workspaces + void *data; + struct { struct wl_list resources; // wlr_ext_workspace_v1_resource.link }; @@ -96,11 +138,13 @@ void wlr_ext_workspace_handle_v1_set_group( void wlr_ext_workspace_handle_v1_set_name( struct wlr_ext_workspace_handle_v1 *workspace, const char *name); void wlr_ext_workspace_handle_v1_set_coordinates( - struct wlr_ext_workspace_handle_v1 *workspace, - struct wl_array *coordinates); + struct wlr_ext_workspace_handle_v1 *workspace, const uint32_t *coords, + size_t coords_len); void wlr_ext_workspace_handle_v1_set_active( struct wlr_ext_workspace_handle_v1 *workspace, bool enabled); void wlr_ext_workspace_handle_v1_set_urgent( struct wlr_ext_workspace_handle_v1 *workspace, bool enabled); void wlr_ext_workspace_handle_v1_set_hidden( struct wlr_ext_workspace_handle_v1 *workspace, bool enabled); + +#endif From 975142d46a9d0a1a60a32727d9db52fd84555e6b Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 6 May 2026 11:51:13 +0800 Subject: [PATCH 155/328] feat: support drag tile position for scroller layout --- src/dispatch/bind_define.h | 66 ++++++--------- src/mango.c | 168 ++++++++++++++++++++++++++++++++----- 2 files changed, 173 insertions(+), 61 deletions(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index f5992e29..3f9eb7ef 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1763,21 +1763,15 @@ int32_t toggle_monitor(const Arg *arg) { return 0; } -int32_t scroller_stack(const Arg *arg) { - if (!selmon) - return 0; - Client *c = selmon->sel; - Client *stack_head = NULL; - Client *source_stack_head = NULL; - if (!c || !c->mon || c->isfloating || !is_scroller_layout(selmon)) - return 0; +int32_t scroller_apply_stack(Client *c, Client *target_client, + int32_t direction) { + Client *source_stack_head = NULL; + Client *stack_head = NULL; bool is_horizontal_layout = c->mon->pertag->ltidxs[c->mon->pertag->curtag]->id == SCROLLER ? true : false; - Client *target_client = find_client_by_direction(c, arg, false, true); - if (target_client) { stack_head = get_scroll_stack_head(target_client); } @@ -1796,32 +1790,32 @@ int32_t scroller_stack(const Arg *arg) { setmaximizescreen(c, 0); } - if (c->prev_in_stack) { - if ((is_horizontal_layout && arg->i == LEFT) || - (!is_horizontal_layout && arg->i == UP)) { + if (c->prev_in_stack && direction != UNDIR) { + if ((is_horizontal_layout && direction == LEFT) || + (!is_horizontal_layout && direction == UP)) { exit_scroller_stack(c); wl_list_remove(&c->link); wl_list_insert(source_stack_head->link.prev, &c->link); arrange(selmon, false, false); - } else if ((is_horizontal_layout && arg->i == RIGHT) || - (!is_horizontal_layout && arg->i == DOWN)) { + } else if ((is_horizontal_layout && direction == RIGHT) || + (!is_horizontal_layout && direction == DOWN)) { exit_scroller_stack(c); wl_list_remove(&c->link); wl_list_insert(&source_stack_head->link, &c->link); arrange(selmon, false, false); } return 0; - } else if (c->next_in_stack) { + } else if (c->next_in_stack && direction != UNDIR) { Client *next_in_stack = c->next_in_stack; - if ((is_horizontal_layout && arg->i == LEFT) || - (!is_horizontal_layout && arg->i == UP)) { + if ((is_horizontal_layout && direction == LEFT) || + (!is_horizontal_layout && direction == UP)) { exit_scroller_stack(c); wl_list_remove(&c->link); wl_list_insert(next_in_stack->link.prev, &c->link); arrange(selmon, false, false); - } else if ((is_horizontal_layout && arg->i == RIGHT) || - (!is_horizontal_layout && arg->i == DOWN)) { + } else if ((is_horizontal_layout && direction == RIGHT) || + (!is_horizontal_layout && direction == DOWN)) { exit_scroller_stack(c); wl_list_remove(&c->link); wl_list_insert(&next_in_stack->link, &c->link); @@ -1832,37 +1826,31 @@ int32_t scroller_stack(const Arg *arg) { if (!target_client || target_client->mon != c->mon) { return 0; - } else { - c->isglobal = target_client->isglobal = 0; - c->isunglobal = target_client->isglobal = 0; - c->tags = target_client->tags = get_tags_first_tag(target_client->tags); } - exit_scroller_stack(c); - // Find the tail of target_client's stack Client *stack_tail = target_client; while (stack_tail->next_in_stack) { stack_tail = stack_tail->next_in_stack; } - // Add c to the stack - stack_tail->next_in_stack = c; - c->prev_in_stack = stack_tail; - c->next_in_stack = NULL; + scroller_insert_stack(c, stack_tail, false); - if (stack_head->ismaximizescreen) { - setmaximizescreen(stack_head, 0); - } - - if (stack_head->isfullscreen) { - setfullscreen(stack_head, 0); - } - - arrange(selmon, false, false); return 0; } +int32_t scroller_stack(const Arg *arg) { + if (!selmon) + return 0; + Client *c = selmon->sel; + if (!c || !c->mon || c->isfloating || !is_scroller_layout(selmon)) + return 0; + + Client *target_client = find_client_by_direction(c, arg, false, true); + + return scroller_apply_stack(c, target_client, arg->i); +} + int32_t toggle_all_floating(const Arg *arg) { if (!selmon || !selmon->sel) return 0; diff --git a/src/mango.c b/src/mango.c index 1f49f672..16ce7ff1 100644 --- a/src/mango.c +++ b/src/mango.c @@ -810,6 +810,8 @@ static void pre_caculate_before_arrange(Monitor *m, bool want_animation, 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); +static void scroller_insert_stack(Client *c, Client *target_client, + bool insert_before); #include "data/static_keymap.h" #include "dispatch/bind_declare.h" @@ -2045,33 +2047,155 @@ void hold_end(struct wl_listener *listener, void *data) { event->time_msec, event->cancelled); } -void place_drag_tile_client(Client *c) { - Client *tc = NULL; - Client *closest_client = NULL; - long min_distant = LONG_MAX; - long temp_distant; - int32_t x, y; +Client *find_closest_tiled_client(Client *c) { + Client *tc, *closest = NULL; + long min_dist = LONG_MAX; wl_list_for_each(tc, &clients, link) { - if (tc != c && ISTILED(tc) && VISIBLEON(tc, c->mon)) { - x = tc->geom.x + (int32_t)(tc->geom.width / 2) - cursor->x; - y = tc->geom.y + (int32_t)(tc->geom.height / 2) - cursor->y; - temp_distant = x * x + y * y; - if (temp_distant < min_distant) { - min_distant = temp_distant; - closest_client = tc; - } + if (tc == c || !ISTILED(tc) || !VISIBLEON(tc, c->mon)) + continue; + + int32_t dx = tc->geom.x + (int32_t)(tc->geom.width / 2) - cursor->x; + int32_t dy = tc->geom.y + (int32_t)(tc->geom.height / 2) - cursor->y; + long dist = (long)dx * dx + (long)dy * dy; + + if (dist < min_dist) { + min_dist = dist; + closest = tc; } } - if (closest_client && closest_client->link.prev != &c->link) { - wl_list_remove(&c->link); - c->link.next = &closest_client->link; - c->link.prev = closest_client->link.prev; - closest_client->link.prev->next = &c->link; - closest_client->link.prev = &c->link; - } else if (closest_client) { - exchange_two_client(c, closest_client); + + return closest; +} + +void scroller_insert_stack(Client *c, Client *target_client, + bool insert_before) { + Client *stack_head = NULL; + + if (!target_client || target_client->mon != c->mon) { + return; + } else { + c->isglobal = target_client->isglobal = 0; + c->isunglobal = target_client->isunglobal = 0; + c->tags = target_client->tags = get_tags_first_tag(target_client->tags); } + + if (c->isfullscreen) { + setfullscreen(c, 0); + } + + if (c->ismaximizescreen) { + setmaximizescreen(c, 0); + } + + exit_scroller_stack(c); + stack_head = get_scroll_stack_head(target_client); + + if (insert_before) { + if (target_client->prev_in_stack) { + target_client->prev_in_stack->next_in_stack = c; + } + c->prev_in_stack = target_client->prev_in_stack; + c->next_in_stack = target_client; + target_client->prev_in_stack = c; + + } else { + if (target_client->next_in_stack) { + target_client->next_in_stack->prev_in_stack = c; + } + c->next_in_stack = target_client->next_in_stack; + c->prev_in_stack = target_client; + target_client->next_in_stack = c; + } + + if (stack_head->ismaximizescreen) { + setmaximizescreen(stack_head, 0); + } + + if (stack_head->isfullscreen) { + setfullscreen(stack_head, 0); + } + + arrange(c->mon, false, false); + + return; +} + +void try_scroller_drop(Client *c, Client *closest, int vertical) { + double ratio_main, ratio_cross; + int32_t main_pos, cross_pos; + int32_t main_size, cross_size; + Client *stack_head = NULL; + + if (vertical) { + main_pos = cursor->y; + cross_pos = cursor->x; + main_size = closest->geom.height; + cross_size = closest->geom.width; + } else { + main_pos = cursor->x; + cross_pos = cursor->y; + main_size = closest->geom.width; + cross_size = closest->geom.height; + } + + ratio_main = + (double)(main_pos - (vertical ? closest->geom.y : closest->geom.x)) / + main_size; + ratio_cross = + (double)(cross_pos - (vertical ? closest->geom.x : closest->geom.y)) / + cross_size; + + if (ratio_main > 0.3 && ratio_main < 0.7 && + (ratio_cross < 0.3 || ratio_cross > 0.7)) { + setfloating(c, 0); + if (ratio_cross < 0.3) { + scroller_insert_stack(c, closest, true); + } else { + scroller_insert_stack(c, closest, false); + } + return; + } + + stack_head = get_scroll_stack_head(closest); + + if (ratio_main < 0.3) { + wl_list_remove(&c->link); + wl_list_insert(stack_head->link.prev, &c->link); + } else { + wl_list_remove(&c->link); + wl_list_insert(&stack_head->link, &c->link); + } + + setfloating(c, 0); +} + +void place_drag_tile_client(Client *c) { + Client *closest = find_closest_tiled_client(c); + + if (closest && closest->mon) { + const Layout *layout = + closest->mon->pertag->ltidxs[closest->mon->pertag->curtag]; + + if (layout) { + if (layout->id == SCROLLER) { + try_scroller_drop(c, closest, 0); + return; + } + if (layout->id == VERTICAL_SCROLLER) { + try_scroller_drop(c, closest, 1); + return; + } + } + + if (closest->link.prev != &c->link) { + wl_list_remove(&c->link); + wl_list_insert(closest->link.prev, &c->link); + } else { + exchange_two_client(c, closest); + } + } + setfloating(c, 0); } From aa5b90761399709fac63c7c0143d6efb81a5846d Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 6 May 2026 14:10:08 +0800 Subject: [PATCH 156/328] feat: add rect indicator for drag tile to tile --- assets/config.conf | 1 + docs/visuals/theming.md | 3 + src/animation/client.h | 84 ++++++++++++++++++++++++ src/config/parse_config.h | 16 +++++ src/mango.c | 134 ++++++++++++++++++++++++-------------- 5 files changed, 189 insertions(+), 49 deletions(-) diff --git a/assets/config.conf b/assets/config.conf index eb326d16..b3f2ff56 100644 --- a/assets/config.conf +++ b/assets/config.conf @@ -119,6 +119,7 @@ scratchpad_height_ratio=0.9 borderpx=4 rootcolor=0x201b14ff bordercolor=0x444444ff +dropcolor=0x8FBA7C55 focuscolor=0xc9b890ff maximizescreencolor=0x89aa61ff urgentcolor=0xad401fff diff --git a/docs/visuals/theming.md b/docs/visuals/theming.md index 8137eb4e..03750e4e 100644 --- a/docs/visuals/theming.md +++ b/docs/visuals/theming.md @@ -26,6 +26,9 @@ rootcolor=0x323232ff # Inactive window border bordercolor=0x444444ff +# Drop shadow when dragging windows +dropcolor=0x8FBA7C55 + # Active window border focuscolor=0xc66b25ff diff --git a/src/animation/client.h b/src/animation/client.h index e94f872a..efa838b8 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -510,6 +510,90 @@ struct ivec2 clip_to_hide(Client *c, struct wlr_box *clip_box) { return offset; } +void client_set_drop_area(Client *c) { + + bool first_draw = false; + int32_t drop_direction = UNDIR; + + if (!c->enable_drop_area_draw && !c->droparea->node.enabled) { + return; + } + + if (!c->enable_drop_area_draw && c->droparea->node.enabled) { + wlr_scene_node_lower_to_bottom(&c->droparea->node); + wlr_scene_node_set_enabled(&c->droparea->node, false); + return; + } else if (c->enable_drop_area_draw && !c->droparea->node.enabled) { + wlr_scene_node_raise_to_top(&c->droparea->node); + wlr_scene_node_set_enabled(&c->droparea->node, true); + first_draw = true; + } + + int32_t bw = (int32_t)c->bw; + int32_t client_width = c->geom.width - 2 * bw; + int32_t client_height = c->geom.height - 2 * bw; + + // 光标在窗口客户区内的相对坐标 + double rel_x = cursor->x - c->geom.x - bw; + double rel_y = cursor->y - c->geom.y - bw; + + struct wlr_box drop_box; + + // 左边缘:x 在 0~30% 客户区宽度,且 y 在 30%~70% 客户区高度 + if (rel_x < client_width * 0.3 && rel_y > client_height * 0.3 && + rel_y < client_height * 0.7) { + drop_box.x = bw; + drop_box.y = bw; + drop_box.width = client_width * 0.3; + drop_box.height = client_height; + drop_direction = LEFT; + } + // 右边缘:x 在 70%~100% 客户区宽度,且 y 在 30%~70% 客户区高度 + else if (rel_x > client_width * 0.7 && rel_y > client_height * 0.3 && + rel_y < client_height * 0.7) { + drop_box.x = bw + client_width * 0.7; + drop_box.y = bw; + drop_box.width = client_width * 0.3; + drop_box.height = client_height; + drop_direction = RIGHT; + } + // 上边缘:x 在 30%~70% 客户区宽度,且 y 在 0~30% 客户区高度 + else if (rel_x > client_width * 0.3 && rel_x < client_width * 0.7 && + rel_y < client_height * 0.3) { + drop_box.x = bw; + drop_box.y = bw; + drop_box.width = client_width; + drop_box.height = client_height * 0.3; + drop_direction = UP; + } + // 下边缘:x 在 30%~70% 客户区宽度,且 y 在 70%~100% 客户区高度 + else if (rel_x > client_width * 0.3 && rel_x < client_width * 0.7 && + rel_y > client_height * 0.7) { + drop_box.x = bw; + drop_box.y = bw + client_height * 0.7; + drop_box.width = client_width; + drop_box.height = client_height * 0.3; + drop_direction = DOWN; + } + // 中心(或其他角落):显示在中间区域 + else { + drop_box.x = bw + client_width * 0.3; + drop_box.y = bw + client_height * 0.3; + drop_box.width = client_width * 0.4; + drop_box.height = client_height * 0.4; + drop_direction = UNDIR; + } + + if (!first_draw && c->drop_direction == drop_direction) { + return; + } else { + c->drop_direction = drop_direction; + } + + wlr_scene_node_set_position(&c->droparea->node, drop_box.x, drop_box.y); + wlr_scene_rect_set_size(c->droparea, drop_box.width, drop_box.height); +} + void client_apply_clip(Client *c, float factor) { if (c->iskilling || !client_surface(c)->mapped) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index b16cf476..d90bbcd4 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -315,6 +315,7 @@ typedef struct { float scratchpad_height_ratio; float rootcolor[4]; float bordercolor[4]; + float dropcolor[4]; float focuscolor[4]; float maximizescreencolor[4]; float urgentcolor[4]; @@ -1740,6 +1741,17 @@ bool parse_option(Config *config, char *key, char *value) { } else { convert_hex_to_rgba(config->bordercolor, color); } + } else if (strcmp(key, "dropcolor") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid dropcolor " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->dropcolor, color); + } } else if (strcmp(key, "focuscolor") == 0) { int64_t color = parse_color(value); if (color == -1) { @@ -3446,6 +3458,10 @@ void set_value_default() { config.bordercolor[1] = 0x44 / 255.0f; config.bordercolor[2] = 0x44 / 255.0f; config.bordercolor[3] = 1.0f; + config.dropcolor[0] = 0x8f / 255.0f; + config.dropcolor[1] = 0xba / 255.0f; + config.dropcolor[2] = 0x7c / 255.0f; + config.dropcolor[3] = 0.5f; config.focuscolor[0] = 0xc6 / 255.0f; config.focuscolor[1] = 0x6b / 255.0f; config.focuscolor[2] = 0x25 / 255.0f; diff --git a/src/mango.c b/src/mango.c index 16ce7ff1..73c08a32 100644 --- a/src/mango.c +++ b/src/mango.c @@ -314,6 +314,7 @@ struct Client { Monitor *mon; struct wlr_scene_tree *scene; struct wlr_scene_rect *border; /* top, bottom, left, right */ + struct wlr_scene_rect *droparea; struct wlr_scene_shadow *shadow; struct wlr_scene_tree *scene_surface; struct wl_list link; @@ -422,6 +423,8 @@ struct Client { bool isfocusing; struct Client *next_in_stack; struct Client *prev_in_stack; + bool enable_drop_area_draw; + int32_t drop_direction; }; typedef struct { @@ -873,7 +876,7 @@ static KeyboardGroup *kb_group; static struct wl_list inputdevices; static struct wl_list keyboard_shortcut_inhibitors; static uint32_t cursor_mode; -static Client *grabc; +static Client *grabc, *dropc; static int32_t rzcorner; static int32_t grabcx, grabcy; /* client-relative */ static int32_t drag_begin_cursorx, drag_begin_cursory; /* client-relative */ @@ -2122,49 +2125,41 @@ void scroller_insert_stack(Client *c, Client *target_client, } void try_scroller_drop(Client *c, Client *closest, int vertical) { - double ratio_main, ratio_cross; - int32_t main_pos, cross_pos; - int32_t main_size, cross_size; - Client *stack_head = NULL; + + Client *stack_head = get_scroll_stack_head(closest); if (vertical) { - main_pos = cursor->y; - cross_pos = cursor->x; - main_size = closest->geom.height; - cross_size = closest->geom.width; - } else { - main_pos = cursor->x; - cross_pos = cursor->y; - main_size = closest->geom.width; - cross_size = closest->geom.height; - } - - ratio_main = - (double)(main_pos - (vertical ? closest->geom.y : closest->geom.x)) / - main_size; - ratio_cross = - (double)(cross_pos - (vertical ? closest->geom.x : closest->geom.y)) / - cross_size; - - if (ratio_main > 0.3 && ratio_main < 0.7 && - (ratio_cross < 0.3 || ratio_cross > 0.7)) { - setfloating(c, 0); - if (ratio_cross < 0.3) { + if (closest->drop_direction == LEFT) { + setfloating(c, 0); scroller_insert_stack(c, closest, true); - } else { + return; + } else if (closest->drop_direction == RIGHT) { + setfloating(c, 0); scroller_insert_stack(c, closest, false); + return; + } else if (closest->drop_direction == UP) { + wl_list_remove(&c->link); + wl_list_insert(stack_head->link.prev, &c->link); + } else if (closest->drop_direction == DOWN) { + wl_list_remove(&c->link); + wl_list_insert(&stack_head->link, &c->link); } - return; - } - - stack_head = get_scroll_stack_head(closest); - - if (ratio_main < 0.3) { - wl_list_remove(&c->link); - wl_list_insert(stack_head->link.prev, &c->link); } else { - wl_list_remove(&c->link); - wl_list_insert(&stack_head->link, &c->link); + if (closest->drop_direction == UP) { + setfloating(c, 0); + scroller_insert_stack(c, closest, true); + return; + } else if (closest->drop_direction == DOWN) { + setfloating(c, 0); + scroller_insert_stack(c, closest, false); + return; + } else if (closest->drop_direction == LEFT) { + wl_list_remove(&c->link); + wl_list_insert(stack_head->link.prev, &c->link); + } else if (closest->drop_direction == RIGHT) { + wl_list_remove(&c->link); + wl_list_insert(&stack_head->link, &c->link); + } } setfloating(c, 0); @@ -2177,22 +2172,27 @@ void place_drag_tile_client(Client *c) { const Layout *layout = closest->mon->pertag->ltidxs[closest->mon->pertag->curtag]; - if (layout) { - if (layout->id == SCROLLER) { - try_scroller_drop(c, closest, 0); - return; - } - if (layout->id == VERTICAL_SCROLLER) { - try_scroller_drop(c, closest, 1); - return; - } + if (closest->drop_direction == UNDIR) { + exchange_two_client(c, closest); + setfloating(c, 0); + return; } - if (closest->link.prev != &c->link) { + if (layout->id == SCROLLER) { + try_scroller_drop(c, closest, 0); + return; + } + if (layout->id == VERTICAL_SCROLLER) { + try_scroller_drop(c, closest, 1); + return; + } + + if (closest->drop_direction == LEFT || closest->drop_direction == UP) { wl_list_remove(&c->link); wl_list_insert(closest->link.prev, &c->link); } else { - exchange_two_client(c, closest); + wl_list_remove(&c->link); + wl_list_insert(&closest->link, &c->link); } } @@ -2317,6 +2317,11 @@ buttonpress(struct wl_listener *listener, void *data) { apply_window_snap(tmpc); } tmpc->drag_to_tile = false; + if (dropc) { + dropc->enable_drop_area_draw = false; + client_set_drop_area(dropc); + dropc = NULL; + } return; } else { cursor_mode = CurNormal; @@ -4182,6 +4187,8 @@ static void iter_xdg_scene_buffers(struct wlr_scene_buffer *buffer, int32_t sx, } void init_client_properties(Client *c) { + c->drop_direction = UNDIR; + c->enable_drop_area_draw = false; c->isfocusing = false; c->isfloating = 0; c->isfakefullscreen = 0; @@ -4323,6 +4330,12 @@ mapnotify(struct wl_listener *listener, void *data) { } // extra node + + c->droparea = wlr_scene_rect_create(c->scene, 0, 0, config.dropcolor); + wlr_scene_node_lower_to_bottom(&c->droparea->node); + wlr_scene_node_set_position(&c->droparea->node, 0, 0); + wlr_scene_node_set_enabled(&c->droparea->node, false); + c->border = wlr_scene_rect_create( c->scene, 0, 0, c->isurgent ? config.urgentcolor : config.bordercolor); wlr_scene_node_lower_to_bottom(&c->border->node); @@ -4513,6 +4526,7 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy, double dx_unaccel, double dy_unaccel) { double sx = 0, sy = 0, sx_confined, sy_confined; Client *c = NULL, *w = NULL; + Client *closet_drop_client = NULL; LayerSurface *l = NULL; struct wlr_surface *surface = NULL; struct wlr_pointer_constraint_v1 *constraint; @@ -4584,6 +4598,24 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, .y = (int32_t)round(cursor->y) - grabcy, .width = grabc->geom.width, .height = grabc->geom.height}; + if (config.drag_tile_to_tile && grabc->drag_to_tile) { + closet_drop_client = find_closest_tiled_client(grabc); + if (closet_drop_client && dropc && closet_drop_client != dropc) { + dropc->enable_drop_area_draw = false; + client_set_drop_area(dropc); + dropc = closet_drop_client; + dropc->enable_drop_area_draw = true; + client_set_drop_area(dropc); + } else if (closet_drop_client) { + dropc = closet_drop_client; + dropc->enable_drop_area_draw = true; + client_set_drop_area(dropc); + } else if (dropc) { + dropc->enable_drop_area_draw = false; + client_set_drop_area(dropc); + dropc = NULL; + } + } resize(grabc, grabc->float_geom, 1); return; } else if (cursor_mode == CurResize) { @@ -6210,6 +6242,10 @@ void unmapnotify(struct wl_listener *listener, void *data) { grabc = NULL; } + if (c == dropc) { + dropc = NULL; + } + wl_list_for_each(m, &mons, link) { if (!m->wlr_output->enabled) { continue; From b9300aac8292d6d11a71252d6ee179c23f6076ba Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 6 May 2026 16:43:41 +0800 Subject: [PATCH 157/328] feat: add option drag_tile_small --- assets/config.conf | 1 + docs/configuration/miscellaneous.md | 1 + src/config/parse_config.h | 5 +++++ src/dispatch/bind_define.h | 9 +++++++++ src/mango.c | 2 ++ 5 files changed, 18 insertions(+) diff --git a/assets/config.conf b/assets/config.conf index b3f2ff56..e0bcdadd 100644 --- a/assets/config.conf +++ b/assets/config.conf @@ -86,6 +86,7 @@ enable_floating_snap=0 snap_distance=30 cursor_size=24 drag_tile_to_tile=1 +drag_tile_small=1 # keyboard repeat_rate=25 diff --git a/docs/configuration/miscellaneous.md b/docs/configuration/miscellaneous.md index 3be0facc..de95f358 100644 --- a/docs/configuration/miscellaneous.md +++ b/docs/configuration/miscellaneous.md @@ -22,6 +22,7 @@ description: Advanced settings for XWayland, focus behavior, and system integrat | `warpcursor` | `1` | Warp the cursor to the center of the window when focus changes via keyboard. | | `cursor_hide_timeout` | `0` | Hide the cursor after `N` seconds of inactivity (`0` to disable). | | `drag_tile_to_tile` | `0` | Allow dragging a tiled window onto another to swap their positions. | +| `drag_tile_small` | `1` | Allow dragging a tiled window temporarily to small size.| | `drag_corner` | `3` | Corner for drag-to-tile detection (0: none, 1–3: corners, 4: auto-detect). | | `drag_warp_cursor` | `1` | Warp cursor when dragging windows to tile. | | `axis_bind_apply_timeout` | `100` | Timeout (ms) for detecting consecutive scroll events for axis bindings. | diff --git a/src/config/parse_config.h b/src/config/parse_config.h index d90bbcd4..f70a17d6 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -226,6 +226,7 @@ typedef struct { int32_t snap_distance; int32_t enable_floating_snap; int32_t drag_tile_to_tile; + int32_t drag_tile_small; uint32_t swipe_min_threshold; float focused_opacity; float unfocused_opacity; @@ -1445,6 +1446,8 @@ bool parse_option(Config *config, char *key, char *value) { config->enable_floating_snap = atoi(value); } else if (strcmp(key, "drag_tile_to_tile") == 0) { config->drag_tile_to_tile = atoi(value); + } else if (strcmp(key, "drag_tile_small") == 0) { + config->drag_tile_small = atoi(value); } else if (strcmp(key, "swipe_min_threshold") == 0) { config->swipe_min_threshold = atoi(value); } else if (strcmp(key, "focused_opacity") == 0) { @@ -3189,6 +3192,7 @@ void override_config(void) { config.drag_floating_refresh_interval = CLAMP_FLOAT(config.drag_floating_refresh_interval, 0.0f, 1000.0f); config.drag_tile_to_tile = CLAMP_INT(config.drag_tile_to_tile, 0, 1); + config.drag_tile_small = CLAMP_INT(config.drag_tile_small, 0, 1); config.allow_tearing = CLAMP_INT(config.allow_tearing, 0, 2); config.allow_shortcuts_inhibit = CLAMP_INT(config.allow_shortcuts_inhibit, 0, 1); @@ -3358,6 +3362,7 @@ void set_value_default() { config.no_radius_when_single = 0; config.snap_distance = 30; config.drag_tile_to_tile = 0; + config.drag_tile_small = 1; config.enable_floating_snap = 0; config.swipe_min_threshold = 1; diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 3f9eb7ef..f8822af3 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -378,11 +378,20 @@ int32_t moveresize(const Arg *arg) { grabc->drag_to_tile = true; exit_scroller_stack(grabc); setfloating(grabc, 1); + grabc->drag_tile_float_backup_geom = grabc->float_geom; grabc->old_stack_inner_per = 0.0f; grabc->old_master_inner_per = 0.0f; set_size_per(grabc->mon, grabc); } + if (grabc && grabc->drag_to_tile && config.drag_tile_small) { + grabc->geom.x = cursor->x - 150; + grabc->geom.y = cursor->y - 150; + grabc->geom.width = 300; + grabc->geom.height = 300; + resize(grabc, grabc->geom, 1); + } + switch (cursor_mode = arg->ui) { case CurMove: diff --git a/src/mango.c b/src/mango.c index 73c08a32..b4bc67bf 100644 --- a/src/mango.c +++ b/src/mango.c @@ -425,6 +425,7 @@ struct Client { struct Client *prev_in_stack; bool enable_drop_area_draw; int32_t drop_direction; + struct wlr_box drag_tile_float_backup_geom; }; typedef struct { @@ -2313,6 +2314,7 @@ buttonpress(struct wl_listener *listener, void *data) { last_apply_drap_time = 0; if (tmpc->drag_to_tile && config.drag_tile_to_tile) { place_drag_tile_client(tmpc); + tmpc->float_geom = tmpc->drag_tile_float_backup_geom; } else { apply_window_snap(tmpc); } From 42c02e3dc20eb09c0191b027e387c0268f8e0fb5 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 6 May 2026 21:20:11 +0800 Subject: [PATCH 158/328] opt: optimize drop area judge --- src/animation/client.h | 80 ++++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 41 deletions(-) diff --git a/src/animation/client.h b/src/animation/client.h index efa838b8..9108c98c 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -511,7 +511,6 @@ struct ivec2 clip_to_hide(Client *c, struct wlr_box *clip_box) { } void client_set_drop_area(Client *c) { - bool first_draw = false; int32_t drop_direction = UNDIR; @@ -539,56 +538,55 @@ void client_set_drop_area(Client *c) { struct wlr_box drop_box; - // 左边缘:x 在 0~30% 客户区宽度,且 y 在 30%~70% 客户区高度 - if (rel_x < client_width * 0.3 && rel_y > client_height * 0.3 && - rel_y < client_height * 0.7) { - drop_box.x = bw; - drop_box.y = bw; - drop_box.width = client_width * 0.3; - drop_box.height = client_height; - drop_direction = LEFT; - } - // 右边缘:x 在 70%~100% 客户区宽度,且 y 在 30%~70% 客户区高度 - else if (rel_x > client_width * 0.7 && rel_y > client_height * 0.3 && - rel_y < client_height * 0.7) { - drop_box.x = bw + client_width * 0.7; - drop_box.y = bw; - drop_box.width = client_width * 0.3; - drop_box.height = client_height; - drop_direction = RIGHT; - } - // 上边缘:x 在 30%~70% 客户区宽度,且 y 在 0~30% 客户区高度 - else if (rel_x > client_width * 0.3 && rel_x < client_width * 0.7 && - rel_y < client_height * 0.3) { - drop_box.x = bw; - drop_box.y = bw; - drop_box.width = client_width; - drop_box.height = client_height * 0.3; - drop_direction = UP; - } - // 下边缘:x 在 30%~70% 客户区宽度,且 y 在 70%~100% 客户区高度 - else if (rel_x > client_width * 0.3 && rel_x < client_width * 0.7 && - rel_y > client_height * 0.7) { - drop_box.x = bw; - drop_box.y = bw + client_height * 0.7; - drop_box.width = client_width; - drop_box.height = client_height * 0.3; - drop_direction = DOWN; - } - // 中心(或其他角落):显示在中间区域 - else { + // 中心区域:x和y都在30%~70%之间 → 无方向 + if (rel_x > client_width * 0.3 && rel_x < client_width * 0.7 && + rel_y > client_height * 0.3 && rel_y < client_height * 0.7) { drop_box.x = bw + client_width * 0.3; drop_box.y = bw + client_height * 0.3; drop_box.width = client_width * 0.4; drop_box.height = client_height * 0.4; drop_direction = UNDIR; + } else { + // 否则根据到各边的距离决定方向 + double dist_left = rel_x; + double dist_right = client_width - rel_x; + double dist_top = rel_y; + double dist_bottom = client_height - rel_y; + + // 找出最小距离的方向(相等时按左、右、上、下的优先级顺序) + if (dist_left <= dist_right && dist_left <= dist_top && + dist_left <= dist_bottom) { + drop_direction = LEFT; + drop_box.x = bw; + drop_box.y = bw; + drop_box.width = client_width * 0.3; + drop_box.height = client_height; + } else if (dist_right <= dist_top && dist_right <= dist_bottom) { + drop_direction = RIGHT; + drop_box.x = bw + client_width * 0.7; + drop_box.y = bw; + drop_box.width = client_width * 0.3; + drop_box.height = client_height; + } else if (dist_top <= dist_bottom) { + drop_direction = UP; + drop_box.x = bw; + drop_box.y = bw; + drop_box.width = client_width; + drop_box.height = client_height * 0.3; + } else { + drop_direction = DOWN; + drop_box.x = bw; + drop_box.y = bw + client_height * 0.7; + drop_box.width = client_width; + drop_box.height = client_height * 0.3; + } } + // 如果方向和上次相同且不是第一次绘制,则跳过更新 if (!first_draw && c->drop_direction == drop_direction) { return; - } else { - c->drop_direction = drop_direction; } + c->drop_direction = drop_direction; wlr_scene_node_set_position(&c->droparea->node, drop_box.x, drop_box.y); wlr_scene_rect_set_size(c->droparea, drop_box.width, drop_box.height); From 18fa5a22c2236ca8efb53e1dd23366f01c21a5ec Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 7 May 2026 11:24:41 +0800 Subject: [PATCH 159/328] fix: foreign toplevel mon change --- src/dispatch/bind_define.h | 10 +++++--- src/ext-protocol/foreign-toplevel.h | 34 +++++++++++++++++-------- src/mango.c | 39 +++++++++++++++++++++-------- 3 files changed, 58 insertions(+), 25 deletions(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index f8822af3..1ab84c9d 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1109,7 +1109,7 @@ int32_t tag(const Arg *arg) { } int32_t tagmon(const Arg *arg) { - Monitor *m = NULL, *cm = NULL; + Monitor *m = NULL, *cm = NULL, *oldmon = NULL; if (!selmon) return 0; Client *c = focustop(selmon); @@ -1117,6 +1117,8 @@ int32_t tagmon(const Arg *arg) { if (!c) return 0; + oldmon = c->mon; + if (arg->i != UNDIR) { m = dirtomon(arg->i); } else if (arg->v) { @@ -1151,12 +1153,12 @@ int32_t tagmon(const Arg *arg) { setmon(c, m, newtags, true); client_update_oldmonname_record(c, m); - reset_foreign_tolevel(c); + reset_foreign_tolevel(c, oldmon, c->mon); c->float_geom.width = - (int32_t)(c->float_geom.width * c->mon->w.width / selmon->w.width); + (int32_t)(c->float_geom.width * c->mon->w.width / oldmon->w.width); c->float_geom.height = - (int32_t)(c->float_geom.height * c->mon->w.height / selmon->w.height); + (int32_t)(c->float_geom.height * c->mon->w.height / oldmon->w.height); selmon = c->mon; c->float_geom = setclient_coordinate_center(c, c->mon, c->float_geom, 0, 0); diff --git a/src/ext-protocol/foreign-toplevel.h b/src/ext-protocol/foreign-toplevel.h index 89f3839a..0c09e0c2 100644 --- a/src/ext-protocol/foreign-toplevel.h +++ b/src/ext-protocol/foreign-toplevel.h @@ -6,7 +6,7 @@ void handle_foreign_activate_request(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, foreign_activate_request); uint32_t target; - if (c->swallowing) + if (c->swallowing || !c->mon) return; if (c->isminimized) { @@ -28,7 +28,7 @@ void handle_foreign_maximize_request(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, foreign_maximize_request); struct wlr_foreign_toplevel_handle_v1_maximized_event *event = data; - if (c->swallowing) + if (c->swallowing || !c->mon) return; if (c->ismaximizescreen && !event->maximized) { @@ -46,7 +46,7 @@ void handle_foreign_minimize_request(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, foreign_minimize_request); struct wlr_foreign_toplevel_handle_v1_minimized_event *event = data; - if (c->swallowing) + if (c->swallowing || !c->mon) return; if (!c->isminimized && event->minimized) { @@ -71,7 +71,7 @@ void handle_foreign_fullscreen_request(struct wl_listener *listener, Client *c = wl_container_of(listener, c, foreign_fullscreen_request); struct wlr_foreign_toplevel_handle_v1_fullscreen_event *event = data; - if (c->swallowing) + if (c->swallowing || !c->mon) return; if (c->isfullscreen && !event->fullscreen) { @@ -98,10 +98,6 @@ void handle_foreign_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&c->foreign_fullscreen_request.link); wl_list_remove(&c->foreign_close_request.link); wl_list_remove(&c->foreign_destroy.link); -} - -void remove_foreign_topleve(Client *c) { - wlr_foreign_toplevel_handle_v1_destroy(c->foreign_toplevel); c->foreign_toplevel = NULL; } @@ -144,7 +140,23 @@ void add_foreign_toplevel(Client *c) { } } -void reset_foreign_tolevel(Client *c) { - remove_foreign_topleve(c); - add_foreign_toplevel(c); +void reset_foreign_tolevel(Client *c, Monitor *oldmon, Monitor *newmon) { + if (!c) + return; + + if (!c->foreign_toplevel) { + add_foreign_toplevel(c); + return; + } + + if (oldmon == newmon) + return; + + if (oldmon) + wlr_foreign_toplevel_handle_v1_output_leave(c->foreign_toplevel, + oldmon->wlr_output); + + if (newmon) + wlr_foreign_toplevel_handle_v1_output_enter(c->foreign_toplevel, + newmon->wlr_output); } diff --git a/src/mango.c b/src/mango.c index b4bc67bf..12811cdc 100644 --- a/src/mango.c +++ b/src/mango.c @@ -653,8 +653,7 @@ static void motionnotify(uint32_t time, struct wlr_input_device *device, double sy_unaccel); static void motionrelative(struct wl_listener *listener, void *data); -static void reset_foreign_tolevel(Client *c); -static void remove_foreign_topleve(Client *c); +static void reset_foreign_tolevel(Client *c, Monitor *oldmon, Monitor *newmon); static void add_foreign_topleve(Client *c); static void exchange_two_client(Client *c1, Client *c2); static void outputmgrapply(struct wl_listener *listener, void *data); @@ -1159,8 +1158,12 @@ void swallow(Client *c, Client *w) { wl_list_insert(&w->link, &c->link); wl_list_insert(&w->flink, &c->flink); - if (w->foreign_toplevel) - remove_foreign_topleve(w); + if (w->foreign_toplevel) { + wlr_foreign_toplevel_handle_v1_output_leave(w->foreign_toplevel, + w->mon->wlr_output); + wlr_foreign_toplevel_handle_v1_destroy(w->foreign_toplevel); + w->foreign_toplevel = NULL; + } wlr_scene_node_set_enabled(&w->scene->node, false); wlr_scene_node_set_enabled(&c->scene->node, true); @@ -1168,6 +1171,10 @@ void swallow(Client *c, Client *w) { if (!c->foreign_toplevel && c->mon) add_foreign_toplevel(c); + else if (c->foreign_toplevel && c->mon) { + wlr_foreign_toplevel_handle_v1_output_enter(c->foreign_toplevel, + c->mon->wlr_output); + } client_pending_fullscreen_state(c, w->isfullscreen); client_pending_maximized_state(c, w->ismaximizescreen); @@ -1182,7 +1189,7 @@ bool switch_scratchpad_client_state(Client *c) { Monitor *oldmon = c->mon; c->scratchpad_switching_mon = true; c->mon = selmon; - reset_foreign_tolevel(c); + reset_foreign_tolevel(c, oldmon, c->mon); client_update_oldmonname_record(c, selmon); // 根据新monitor调整窗口尺寸 @@ -2524,7 +2531,13 @@ void closemon(Monitor *m) { if (c->mon == m) { if (selmon == NULL) { - remove_foreign_topleve(c); + if (c->foreign_toplevel) { + wlr_foreign_toplevel_handle_v1_output_leave( + c->foreign_toplevel, c->mon->wlr_output); + wlr_foreign_toplevel_handle_v1_destroy(c->foreign_toplevel); + c->foreign_toplevel = NULL; + } + c->mon = NULL; } else { client_change_mon(c, selmon); @@ -4452,7 +4465,11 @@ void set_minimized(Client *c) { 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); + + if (c->foreign_toplevel) + wlr_foreign_toplevel_handle_v1_set_activated(c->foreign_toplevel, + false); + wl_list_remove(&c->link); // 从原来位置移除 wl_list_insert(clients.prev, &c->link); // 插入尾部 } @@ -5612,7 +5629,7 @@ void setmon(Client *c, Monitor *m, uint32_t newtags, bool focus) { arrange(oldmon, false, false); if (m) { /* Make sure window actually overlaps with the monitor */ - reset_foreign_tolevel(c); + reset_foreign_tolevel(c, oldmon, m); resize(c, c->geom, 0); client_reset_mon_tags(c, m, newtags); check_match_tag_floating_rule(c, m); @@ -5659,7 +5676,9 @@ void show_hide_client(Client *c) { } client_pending_minimized_state(c, 0); focusclient(c, 1); - wlr_foreign_toplevel_handle_v1_set_activated(c->foreign_toplevel, true); + + if (c->foreign_toplevel) + wlr_foreign_toplevel_handle_v1_set_activated(c->foreign_toplevel, true); } void create_output(struct wlr_backend *backend, void *data) { @@ -6436,7 +6455,7 @@ void updatemons(struct wl_listener *listener, void *data) { wl_list_for_each(c, &clients, link) { if (!c->mon && client_surface(c)->mapped) { c->mon = selmon; - reset_foreign_tolevel(c); + reset_foreign_tolevel(c, NULL, c->mon); } if (c->tags == 0 && !c->is_in_scratchpad) { c->tags = selmon->tagset[selmon->seltags]; From 3db79ef168efe773a66cc98ae11506036ab576bf Mon Sep 17 00:00:00 2001 From: faugusto-oliveira Date: Fri, 8 May 2026 15:16:17 -0300 Subject: [PATCH 160/328] Set foreign_toplevel of unfocused monitor windows to false --- src/mango.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/mango.c b/src/mango.c index 12811cdc..36deda3c 100644 --- a/src/mango.c +++ b/src/mango.c @@ -3747,8 +3747,14 @@ void focusclient(Client *c, int32_t lift) { wl_list_for_each(um, &mons, link) { if (um->wlr_output->enabled && um != selmon && um->sel && !um->sel->iskilling && um->sel->isfocusing) { + um->sel->isfocusing = false; client_set_unfocused_opacity_animation(um->sel); + + if (um->sel->foreign_toplevel) { + wlr_foreign_toplevel_handle_v1_set_activated( + um->sel->foreign_toplevel, false); + } } } From 1fc42b76e54ef074da68a4ed5eda606c65564df5 Mon Sep 17 00:00:00 2001 From: ernestoCruz05 Date: Sat, 9 May 2026 12:14:57 +0100 Subject: [PATCH 161/328] feat: opt-in config field to have typical dwindle tiling --- src/animation/client.h | 39 +++++++++++++++++++++++++++++++++++++++ src/config/parse_config.h | 6 ++++++ src/mango.c | 2 +- 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/animation/client.h b/src/animation/client.h index 9108c98c..ba48959d 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -538,6 +538,11 @@ void client_set_drop_area(Client *c) { struct wlr_box drop_box; + const Layout *cur_layout = + c->mon ? c->mon->pertag->ltidxs[c->mon->pertag->curtag] : NULL; + bool dwindle_familiar = cur_layout && cur_layout->id == DWINDLE && + config.dwindle_drop_simple_split; + // 中心区域:x和y都在30%~70%之间 → 无方向 if (rel_x > client_width * 0.3 && rel_x < client_width * 0.7 && rel_y > client_height * 0.3 && rel_y < client_height * 0.7) { @@ -546,6 +551,40 @@ void client_set_drop_area(Client *c) { drop_box.width = client_width * 0.4; drop_box.height = client_height * 0.4; drop_direction = UNDIR; + } else if (dwindle_familiar) { + // Mirror dwindle_assign's split_h = (aw >= ah) rule so the preview + // matches the auto-rotated split that dwindle will produce. + bool split_h = c->geom.width >= c->geom.height; + float ratio = config.dwindle_split_ratio; + if (split_h) { + if (rel_x < client_width * 0.5) { + drop_direction = LEFT; + drop_box.x = bw; + drop_box.y = bw; + drop_box.width = (int32_t)(client_width * ratio); + drop_box.height = client_height; + } else { + drop_direction = RIGHT; + drop_box.x = bw + (int32_t)(client_width * ratio); + drop_box.y = bw; + drop_box.width = client_width - (int32_t)(client_width * ratio); + drop_box.height = client_height; + } + } else { + if (rel_y < client_height * 0.5) { + drop_direction = UP; + drop_box.x = bw; + drop_box.y = bw; + drop_box.width = client_width; + drop_box.height = (int32_t)(client_height * ratio); + } else { + drop_direction = DOWN; + drop_box.x = bw; + drop_box.y = bw + (int32_t)(client_height * ratio); + drop_box.width = client_width; + drop_box.height = client_height - (int32_t)(client_height * ratio); + } + } } else { // 否则根据到各边的距离决定方向 double dist_left = rel_x; diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 207d06f7..ceaec81c 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -248,6 +248,7 @@ typedef struct { int32_t dwindle_preserve_split; int32_t dwindle_smart_split; int32_t dwindle_smart_resize; + int32_t dwindle_drop_simple_split; float dwindle_split_ratio; uint32_t hotarea_size; @@ -1633,6 +1634,8 @@ bool parse_option(Config *config, char *key, char *value) { config->dwindle_smart_split = atoi(value); } else if (strcmp(key, "dwindle_smart_resize") == 0) { config->dwindle_smart_resize = atoi(value); + } else if (strcmp(key, "dwindle_drop_simple_split") == 0) { + config->dwindle_drop_simple_split = atoi(value); } else if (strcmp(key, "dwindle_split_ratio") == 0) { config->dwindle_split_ratio = atof(value); } else if (strcmp(key, "hotarea_size") == 0) { @@ -3205,6 +3208,8 @@ void override_config(void) { CLAMP_INT(config.dwindle_preserve_split, 0, 1); config.dwindle_smart_split = CLAMP_INT(config.dwindle_smart_split, 0, 1); config.dwindle_smart_resize = CLAMP_INT(config.dwindle_smart_resize, 0, 1); + config.dwindle_drop_simple_split = + CLAMP_INT(config.dwindle_drop_simple_split, 0, 1); config.dwindle_split_ratio = CLAMP_FLOAT(config.dwindle_split_ratio, 0.05f, 0.95f); config.hotarea_size = CLAMP_INT(config.hotarea_size, 1, 1000); @@ -3351,6 +3356,7 @@ void set_value_default() { config.dwindle_preserve_split = 0; config.dwindle_smart_split = 0; config.dwindle_smart_resize = 0; + config.dwindle_drop_simple_split = 0; config.dwindle_split_ratio = 0.5f; config.log_level = WLR_ERROR; diff --git a/src/mango.c b/src/mango.c index 17b99dca..7463262c 100644 --- a/src/mango.c +++ b/src/mango.c @@ -2229,7 +2229,7 @@ void place_drag_tile_client(Client *c) { closest->drop_direction == RIGHT); dwindle_insert(&c->mon->pertag->dwindle_root[tag], c, closest, config.dwindle_split_ratio, insert_before, split_h, - true); + !config.dwindle_drop_simple_split); setfloating(c, 0); return; } From 73ed3ce35babb1e4e7345f10daa79766bcd58a3a Mon Sep 17 00:00:00 2001 From: ernestoCruz05 Date: Sat, 9 May 2026 11:55:59 +0800 Subject: [PATCH 162/328] feat: dwindle layout support --- src/config/parse_config.h | 41 +++- src/dispatch/bind_define.h | 4 + src/layout/arrange.h | 17 ++ src/layout/dwindle.h | 433 +++++++++++++++++++++++++++++++++++++ src/layout/layout.h | 3 + src/mango.c | 55 ++++- 6 files changed, 550 insertions(+), 3 deletions(-) create mode 100644 src/layout/dwindle.h diff --git a/src/config/parse_config.h b/src/config/parse_config.h index f70a17d6..207d06f7 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -242,6 +242,14 @@ typedef struct { int32_t center_master_overspread; int32_t center_when_single_stack; + /* dwindle layout */ + int32_t dwindle_vsplit; + int32_t dwindle_hsplit; + int32_t dwindle_preserve_split; + int32_t dwindle_smart_split; + int32_t dwindle_smart_resize; + float dwindle_split_ratio; + uint32_t hotarea_size; uint32_t hotarea_corner; uint32_t enable_hotarea; @@ -1615,6 +1623,18 @@ bool parse_option(Config *config, char *key, char *value) { config->center_master_overspread = atoi(value); } else if (strcmp(key, "center_when_single_stack") == 0) { config->center_when_single_stack = atoi(value); + } else if (strcmp(key, "dwindle_vsplit") == 0) { + config->dwindle_vsplit = atoi(value); + } else if (strcmp(key, "dwindle_hsplit") == 0) { + config->dwindle_hsplit = atoi(value); + } else if (strcmp(key, "dwindle_preserve_split") == 0) { + config->dwindle_preserve_split = atoi(value); + } else if (strcmp(key, "dwindle_smart_split") == 0) { + config->dwindle_smart_split = atoi(value); + } else if (strcmp(key, "dwindle_smart_resize") == 0) { + config->dwindle_smart_resize = atoi(value); + } else if (strcmp(key, "dwindle_split_ratio") == 0) { + config->dwindle_split_ratio = atof(value); } else if (strcmp(key, "hotarea_size") == 0) { config->hotarea_size = atoi(value); } else if (strcmp(key, "hotarea_corner") == 0) { @@ -3179,6 +3199,14 @@ void override_config(void) { config.center_when_single_stack = CLAMP_INT(config.center_when_single_stack, 0, 1); config.new_is_master = CLAMP_INT(config.new_is_master, 0, 1); + config.dwindle_vsplit = CLAMP_INT(config.dwindle_vsplit, 0, 2); + config.dwindle_hsplit = CLAMP_INT(config.dwindle_hsplit, 0, 2); + config.dwindle_preserve_split = + CLAMP_INT(config.dwindle_preserve_split, 0, 1); + config.dwindle_smart_split = CLAMP_INT(config.dwindle_smart_split, 0, 1); + config.dwindle_smart_resize = CLAMP_INT(config.dwindle_smart_resize, 0, 1); + config.dwindle_split_ratio = + CLAMP_FLOAT(config.dwindle_split_ratio, 0.05f, 0.95f); config.hotarea_size = CLAMP_INT(config.hotarea_size, 1, 1000); config.hotarea_corner = CLAMP_INT(config.hotarea_corner, 0, 3); config.enable_hotarea = CLAMP_INT(config.enable_hotarea, 0, 1); @@ -3318,6 +3346,13 @@ void set_value_default() { config.center_master_overspread = 0; config.center_when_single_stack = 1; + config.dwindle_vsplit = 0; + config.dwindle_hsplit = 0; + config.dwindle_preserve_split = 0; + config.dwindle_smart_split = 0; + config.dwindle_smart_resize = 0; + config.dwindle_split_ratio = 0.5f; + config.log_level = WLR_ERROR; config.numlockon = 0; config.capslock = 0; @@ -3716,7 +3751,7 @@ void reapply_rootbg(void) { wlr_scene_rect_set_color(root_bg, config.rootcolor); } -void reapply_border(void) { +void reapply_property(void) { Client *c = NULL; // reset border width when config change @@ -3725,6 +3760,8 @@ void reapply_border(void) { if (!c->isnoborder && !c->isfullscreen) { c->bw = config.borderpx; } + + wlr_scene_rect_set_color(c->droparea, config.dropcolor); } } } @@ -3870,7 +3907,7 @@ void reset_option(void) { run_exec(); reapply_cursor_style(); - reapply_border(); + reapply_property(); reapply_rootbg(); reapply_keyboard(); reapply_pointer(); diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 1ab84c9d..3c1d2154 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -112,6 +112,10 @@ int32_t exchange_client(const Arg *arg) { Client *tc = direction_select(arg); tc = get_focused_stack_client(tc); + + if (!tc) + return 0; + exchange_two_client(c, tc); return 0; } diff --git a/src/layout/arrange.h b/src/layout/arrange.h index 04f4554b..9e0c6fa7 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -488,6 +488,21 @@ void resize_tile_master_vertical(Client *grabc, bool isdrag, int32_t offsetx, } } +void resize_tile_dwindle(Client *grabc, bool isdrag, int32_t offsetx, + int32_t offsety, uint32_t time, bool isvertical) { + + if (!isdrag) { + dwindle_resize_client_step(grabc->mon, grabc, offsetx, offsety); + return; + } + + if (last_apply_drap_time == 0 || + time - last_apply_drap_time > config.drag_tile_refresh_interval) { + dwindle_resize_client(grabc->mon, grabc); + last_apply_drap_time = time; + } +} + void resize_tile_scroller(Client *grabc, bool isdrag, int32_t offsetx, int32_t offsety, uint32_t time, bool isvertical) { Client *tc = NULL; @@ -706,6 +721,8 @@ void resize_tile_client(Client *grabc, bool isdrag, int32_t offsetx, resize_tile_scroller(grabc, isdrag, offsetx, offsety, time, false); } else if (current_layout->id == VERTICAL_SCROLLER) { resize_tile_scroller(grabc, isdrag, offsetx, offsety, time, true); + } else if (current_layout->id == DWINDLE) { + resize_tile_dwindle(grabc, isdrag, offsetx, offsety, time, true); } } diff --git a/src/layout/dwindle.h b/src/layout/dwindle.h new file mode 100644 index 00000000..94d65be6 --- /dev/null +++ b/src/layout/dwindle.h @@ -0,0 +1,433 @@ +typedef struct DwindleNode DwindleNode; +struct DwindleNode { + bool is_split; + bool split_h; + bool split_locked; + float ratio; + float drag_init_ratio; + int32_t container_x; + int32_t container_y; + int32_t container_w; + int32_t container_h; + DwindleNode *parent; + DwindleNode *first; + DwindleNode *second; + Client *client; +}; + +static DwindleNode *dwindle_locked_h_node = NULL; +static DwindleNode *dwindle_locked_v_node = NULL; + +static DwindleNode *dwindle_new_leaf(Client *c) { + DwindleNode *n = calloc(1, sizeof(DwindleNode)); + n->client = c; + return n; +} + +static DwindleNode *dwindle_find_leaf(DwindleNode *node, Client *c) { + if (!node) + return NULL; + if (!node->is_split) + return node->client == c ? node : NULL; + DwindleNode *r = dwindle_find_leaf(node->first, c); + return r ? r : dwindle_find_leaf(node->second, c); +} + +static DwindleNode *dwindle_first_leaf(DwindleNode *node) { + if (!node) + return NULL; + while (node->is_split) + node = node->first; + return node; +} + +static void dwindle_free_tree(DwindleNode *node) { + if (!node) + return; + dwindle_free_tree(node->first); + dwindle_free_tree(node->second); + free(node); +} + +static void dwindle_insert(DwindleNode **root, Client *new_c, Client *focused, + float ratio, bool as_first, bool split_h, + bool lock) { + DwindleNode *new_leaf = dwindle_new_leaf(new_c); + + if (!*root) { + *root = new_leaf; + return; + } + + DwindleNode *target = focused ? dwindle_find_leaf(*root, focused) : NULL; + if (!target) + target = dwindle_first_leaf(*root); + + DwindleNode *split = calloc(1, sizeof(DwindleNode)); + split->is_split = true; + split->ratio = ratio; + split->split_h = split_h; + split->split_locked = lock; + + if (as_first) { + split->first = new_leaf; + split->second = target; + } else { + split->first = target; + split->second = new_leaf; + } + split->parent = target->parent; + target->parent = split; + new_leaf->parent = split; + + if (!split->parent) { + *root = split; + } else { + if (split->parent->first == target) + split->parent->first = split; + else + split->parent->second = split; + } +} + +static void dwindle_remove(DwindleNode **root, Client *c) { + DwindleNode *leaf = dwindle_find_leaf(*root, c); + if (!leaf) + return; + + DwindleNode *parent = leaf->parent; + if (!parent) { + free(leaf); + *root = NULL; + return; + } + + DwindleNode *sibling = + (parent->first == leaf) ? parent->second : parent->first; + DwindleNode *grandparent = parent->parent; + sibling->parent = grandparent; + + /* Preserve split direction on sibling split-nodes when requested. */ + if (!sibling->is_split || + (!config.dwindle_preserve_split && !config.dwindle_smart_split)) { + sibling->container_w = 0; + sibling->container_h = 0; + } + + if (!grandparent) { + *root = sibling; + } else { + if (grandparent->first == parent) + grandparent->first = sibling; + else + grandparent->second = sibling; + } + + free(leaf); + free(parent); +} + +static void dwindle_assign(DwindleNode *node, int32_t ax, int32_t ay, + int32_t aw, int32_t ah, int32_t gap_h, + int32_t gap_v) { + if (!node) + return; + + if (!node->is_split) { + if (node->client) { + struct wlr_box box = {ax, ay, MAX(1, aw), MAX(1, ah)}; + resize(node->client, box, 0); + } + return; + } + + if (!node->split_locked && node->container_w == 0 && node->container_h == 0) + node->split_h = (aw >= ah); + node->container_x = ax; + node->container_y = ay; + node->container_w = aw; + node->container_h = ah; + if (node->split_h) { + int32_t w1 = MAX(1, (int32_t)(aw * node->ratio) - gap_h / 2); + dwindle_assign(node->first, ax, ay, w1, ah, gap_h, gap_v); + dwindle_assign(node->second, ax + w1 + gap_h, ay, aw - w1 - gap_h, ah, + gap_h, gap_v); + } else { + int32_t h1 = MAX(1, (int32_t)(ah * node->ratio) - gap_v / 2); + dwindle_assign(node->first, ax, ay, aw, h1, gap_h, gap_v); + dwindle_assign(node->second, ax, ay + h1 + gap_v, aw, ah - h1 - gap_v, + gap_h, gap_v); + } +} + +static void dwindle_move_client(DwindleNode **root, Client *c, Client *target, + float ratio, int32_t dir) { + if (!c || !target || c == target) + return; + if (!dwindle_find_leaf(*root, c) || !dwindle_find_leaf(*root, target)) + return; + dwindle_remove(root, c); + bool as_first = (dir == UP || dir == LEFT); + bool split_h = (dir == LEFT || dir == RIGHT); + dwindle_insert(root, c, target, ratio, as_first, split_h, true); +} + +static void dwindle_swap_clients(DwindleNode **root, Client *a, Client *b) { + DwindleNode *la = dwindle_find_leaf(*root, a); + DwindleNode *lb = dwindle_find_leaf(*root, b); + if (!la || !lb || la == lb) + return; + la->client = b; + lb->client = a; +} + +static void dwindle_resize_client(Monitor *m, Client *c) { + uint32_t tag = m->pertag->curtag; + DwindleNode *leaf = dwindle_find_leaf(m->pertag->dwindle_root[tag], c); + if (!leaf) + return; + + if (!start_drag_window) { + start_drag_window = true; + dwindle_locked_h_node = NULL; + dwindle_locked_v_node = NULL; + drag_begin_cursorx = cursor->x; + drag_begin_cursory = cursor->y; + DwindleNode *node = leaf->parent; + while (node) { + if (node->split_h && !dwindle_locked_h_node) { + dwindle_locked_h_node = node; + node->drag_init_ratio = node->ratio; + } + if (!node->split_h && !dwindle_locked_v_node) { + dwindle_locked_v_node = node; + node->drag_init_ratio = node->ratio; + } + if (dwindle_locked_h_node && dwindle_locked_v_node) + break; + node = node->parent; + } + } + + if (!dwindle_locked_h_node && !dwindle_locked_v_node) + return; + + if (dwindle_locked_h_node) { + float cw = (float)MAX(1, dwindle_locked_h_node->container_w); + float ox = (float)(cursor->x - drag_begin_cursorx); + if (config.dwindle_smart_resize) { + /* Move the boundary toward the cursor: invert direction when + * the drag started on the right side of the split line. */ + float split_x = dwindle_locked_h_node->container_x + + cw * dwindle_locked_h_node->drag_init_ratio; + if (drag_begin_cursorx >= split_x) + ox = -ox; + } + dwindle_locked_h_node->ratio = + dwindle_locked_h_node->drag_init_ratio + ox / cw; + dwindle_locked_h_node->ratio = + CLAMP_FLOAT(dwindle_locked_h_node->ratio, 0.05f, 0.95f); + } + + if (dwindle_locked_v_node) { + float ch = (float)MAX(1, dwindle_locked_v_node->container_h); + float oy = (float)(cursor->y - drag_begin_cursory); + if (config.dwindle_smart_resize) { + /* Same logic for the vertical split line. */ + float split_y = dwindle_locked_v_node->container_y + + ch * dwindle_locked_v_node->drag_init_ratio; + if (drag_begin_cursory >= split_y) + oy = -oy; + } + dwindle_locked_v_node->ratio = + dwindle_locked_v_node->drag_init_ratio + oy / ch; + dwindle_locked_v_node->ratio = + CLAMP_FLOAT(dwindle_locked_v_node->ratio, 0.05f, 0.95f); + } + + int32_t n = m->visible_tiling_clients; + int32_t gap_ih = enablegaps ? m->gappih : 0; + int32_t gap_iv = enablegaps ? m->gappiv : 0; + int32_t gap_oh = enablegaps ? m->gappoh : 0; + int32_t gap_ov = enablegaps ? m->gappov : 0; + if (config.smartgaps && n == 1) + gap_ih = gap_iv = gap_oh = gap_ov = 0; + + dwindle_assign(m->pertag->dwindle_root[tag], m->w.x + gap_oh, + m->w.y + gap_ov, m->w.width - 2 * gap_oh, + m->w.height - 2 * gap_ov, gap_ih, gap_iv); +} + +static void dwindle_resize_client_step(Monitor *m, Client *c, int32_t dx, + int32_t dy) { + uint32_t tag = m->pertag->curtag; + DwindleNode *leaf = dwindle_find_leaf(m->pertag->dwindle_root[tag], c); + if (!leaf) + return; + + DwindleNode *h_node = NULL; + DwindleNode *v_node = NULL; + DwindleNode *node = leaf->parent; + + while (node) { + if (node->split_h && !h_node) + h_node = node; + if (!node->split_h && !v_node) + v_node = node; + if (h_node && v_node) + break; + node = node->parent; + } + + if (!h_node && !v_node) + return; + + if (h_node && dx) { + float cw = (float)MAX(1, h_node->container_w); + float delta = (float)dx / cw; + h_node->ratio = CLAMP_FLOAT(h_node->ratio + delta, 0.05f, 0.95f); + } + + if (v_node && dy) { + float ch = (float)MAX(1, v_node->container_h); + float delta = (float)dy / ch; + v_node->ratio = CLAMP_FLOAT(v_node->ratio + delta, 0.05f, 0.95f); + } + + int32_t n_clients = m->visible_tiling_clients; + int32_t gap_ih = enablegaps ? m->gappih : 0; + int32_t gap_iv = enablegaps ? m->gappiv : 0; + int32_t gap_oh = enablegaps ? m->gappoh : 0; + int32_t gap_ov = enablegaps ? m->gappov : 0; + if (config.smartgaps && n_clients == 1) + gap_ih = gap_iv = gap_oh = gap_ov = 0; + + dwindle_assign(m->pertag->dwindle_root[tag], m->w.x + gap_oh, + m->w.y + gap_ov, m->w.width - 2 * gap_oh, + m->w.height - 2 * gap_ov, gap_ih, gap_iv); +} + +static void dwindle_remove_client(Client *c) { + Monitor *m; + wl_list_for_each(m, &mons, link) { + for (uint32_t t = 0; t < LENGTH(tags) + 1; t++) + dwindle_remove(&m->pertag->dwindle_root[t], c); + } +} + +/* Insert a new client respecting dwindle_vsplit, dwindle_hsplit, and + * dwindle_smart_split config options. */ +static void dwindle_insert_with_config(DwindleNode **root, Client *new_c, + Client *focused, float ratio) { + bool as_first = false; + bool split_h = false; + bool lock = false; + + if (focused) { + struct wlr_box *fg = &focused->geom; + double fcx = fg->x + fg->width * 0.5; + double fcy = fg->y + fg->height * 0.5; + + if (config.dwindle_smart_split) { + double nx = (cursor->x - fcx) / (fg->width * 0.5); + double ny = (cursor->y - fcy) / (fg->height * 0.5); + + if (fabs(ny) > fabs(nx)) { + split_h = false; // vertical split + as_first = (ny < 0); // top → new window on top + } else { + split_h = true; // horizontal split + as_first = (nx < 0); // left → new window on left + } + lock = true; // lock split direction + } else { + // normal mode, auto split + bool likely_h = (fg->width >= fg->height); + if (likely_h) { + if (config.dwindle_hsplit == 0) + as_first = (cursor->x < fcx); + else + as_first = (config.dwindle_hsplit == 2); + } else { + if (config.dwindle_vsplit == 0) + as_first = (cursor->y < fcy); + else + as_first = (config.dwindle_vsplit == 2); + } + // split_h and lock are false, decided by width/height ratio + } + } + + dwindle_insert(root, new_c, focused, ratio, as_first, split_h, lock); +} + +void dwindle(Monitor *m) { + int32_t n = m->visible_tiling_clients; + if (n == 0) + return; + + uint32_t tag = m->pertag->curtag; + DwindleNode **root = &m->pertag->dwindle_root[tag]; + float ratio = config.dwindle_split_ratio; + + Client *vis[512]; + int32_t count = 0; + Client *c; + wl_list_for_each(c, &clients, link) { + if (VISIBLEON(c, m) && ISTILED(c)) + vis[count++] = c; + if (count >= 512) + break; + } + + { + DwindleNode *leaves[512]; + int32_t lc = 0; + + DwindleNode *stack[1024]; + int32_t sp = 0; + if (*root) + stack[sp++] = *root; + while (sp > 0) { + DwindleNode *nd = stack[--sp]; + if (!nd->is_split) { + leaves[lc++] = nd; + } else { + if (nd->second) + stack[sp++] = nd->second; + if (nd->first) + stack[sp++] = nd->first; + } + } + + for (int32_t i = 0; i < lc; i++) { + bool found = false; + for (int32_t j = 0; j < count; j++) + if (vis[j] == leaves[i]->client) { + found = true; + break; + } + if (!found) + dwindle_remove(root, leaves[i]->client); + } + } + + Client *focused = focustop(m); + if (focused && !dwindle_find_leaf(*root, focused)) + focused = m->sel; + for (int32_t i = 0; i < count; i++) { + if (!dwindle_find_leaf(*root, vis[i])) + dwindle_insert_with_config(root, vis[i], focused, ratio); + } + + int32_t gap_ih = enablegaps ? m->gappih : 0; + int32_t gap_iv = enablegaps ? m->gappiv : 0; + int32_t gap_oh = enablegaps ? m->gappoh : 0; + int32_t gap_ov = enablegaps ? m->gappov : 0; + if (config.smartgaps && n == 1) + gap_ih = gap_iv = gap_oh = gap_ov = 0; + + dwindle_assign(*root, m->w.x + gap_oh, m->w.y + gap_ov, + m->w.width - 2 * gap_oh, m->w.height - 2 * gap_ov, gap_ih, + gap_iv); +} diff --git a/src/layout/layout.h b/src/layout/layout.h index f896ac27..557ce571 100644 --- a/src/layout/layout.h +++ b/src/layout/layout.h @@ -12,6 +12,7 @@ static void vertical_grid(Monitor *m); static void vertical_scroller(Monitor *m); static void vertical_deck(Monitor *mon); static void tgmix(Monitor *m); +static void dwindle(Monitor *m); /* layout(s) */ Layout overviewlayout = {"󰃇", overview, "overview"}; @@ -29,6 +30,7 @@ enum { VERTICAL_DECK, RIGHT_TILE, TGMIX, + DWINDLE, }; Layout layouts[] = { @@ -47,4 +49,5 @@ Layout layouts[] = { {"VG", vertical_grid, "vertical_grid", VERTICAL_GRID}, // 垂直格子布局 {"VK", vertical_deck, "vertical_deck", VERTICAL_DECK}, // 垂直卡片布局 {"TG", tgmix, "tgmix", TGMIX}, // 混合布局 + {"DW", dwindle, "dwindle", DWINDLE}, }; \ No newline at end of file diff --git a/src/mango.c b/src/mango.c index 36deda3c..17b99dca 100644 --- a/src/mango.c +++ b/src/mango.c @@ -560,6 +560,8 @@ typedef struct { struct wl_listener destroy; } SessionLock; +typedef struct DwindleNode DwindleNode; + /* function declarations */ static void applybounds( Client *c, @@ -815,6 +817,11 @@ static void client_pending_maximized_state(Client *c, int32_t ismaximized); static void client_pending_minimized_state(Client *c, int32_t isminimized); static void scroller_insert_stack(Client *c, Client *target_client, bool insert_before); +static void dwindle_move_client(DwindleNode **root, Client *c, Client *target, + float ratio, int32_t dir); +static void dwindle_resize_client_step(Monitor *m, Client *c, int32_t dx, + int32_t dy); +static void dwindle_resize_client(Monitor *m, Client *c); #include "data/static_keymap.h" #include "dispatch/bind_declare.h" @@ -946,6 +953,7 @@ struct Pertag { 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 */ + struct DwindleNode *dwindle_root[LENGTH(tags) + 1]; const Layout *ltidxs[LENGTH(tags) + 1]; /* matrix of tags and layouts indexes */ }; @@ -1018,6 +1026,7 @@ static struct wl_event_source *sync_keymap; #include "ext-protocol/all.h" #include "fetch/fetch.h" #include "layout/arrange.h" +#include "layout/dwindle.h" #include "layout/horizontal.h" #include "layout/vertical.h" @@ -1179,6 +1188,17 @@ void swallow(Client *c, Client *w) { client_pending_fullscreen_state(c, w->isfullscreen); client_pending_maximized_state(c, w->ismaximizescreen); client_pending_minimized_state(c, w->isminimized); + + Monitor *m; + wl_list_for_each(m, &mons, link) { + for (uint32_t t = 0; t < LENGTH(tags) + 1; t++) { + DwindleNode **root = &m->pertag->dwindle_root[t]; + dwindle_remove(root, c); + DwindleNode *wn = dwindle_find_leaf(*root, w); + if (wn) + wn->client = c; + } + } } bool switch_scratchpad_client_state(Client *c) { @@ -2066,6 +2086,13 @@ Client *find_closest_tiled_client(Client *c) { if (tc == c || !ISTILED(tc) || !VISIBLEON(tc, c->mon)) continue; + if (cursor->x >= tc->geom.x && + cursor->x < tc->geom.x + tc->geom.width && + cursor->y >= tc->geom.y && + cursor->y < tc->geom.y + tc->geom.height) { + return tc; + } + int32_t dx = tc->geom.x + (int32_t)(tc->geom.width / 2) - cursor->x; int32_t dy = tc->geom.y + (int32_t)(tc->geom.height / 2) - cursor->y; long dist = (long)dx * dx + (long)dy * dy; @@ -2181,8 +2208,8 @@ void place_drag_tile_client(Client *c) { closest->mon->pertag->ltidxs[closest->mon->pertag->curtag]; if (closest->drop_direction == UNDIR) { - exchange_two_client(c, closest); setfloating(c, 0); + exchange_two_client(c, closest); return; } @@ -2194,6 +2221,18 @@ void place_drag_tile_client(Client *c) { try_scroller_drop(c, closest, 1); return; } + if (layout->id == DWINDLE) { + uint32_t tag = c->mon->pertag->curtag; + bool insert_before = (closest->drop_direction == LEFT || + closest->drop_direction == UP); + bool split_h = (closest->drop_direction == LEFT || + closest->drop_direction == RIGHT); + dwindle_insert(&c->mon->pertag->dwindle_root[tag], c, closest, + config.dwindle_split_ratio, insert_before, split_h, + true); + setfloating(c, 0); + return; + } if (closest->drop_direction == LEFT || closest->drop_direction == UP) { wl_list_remove(&c->link); @@ -2507,6 +2546,9 @@ void cleanupmon(struct wl_listener *listener, void *data) { m->skip_frame_timeout = NULL; } m->wlr_output->data = NULL; + + for (uint32_t t = 0; t < LENGTH(tags) + 1; t++) + dwindle_free_tree(m->pertag->dwindle_root[t]); free(m->pertag); free(m); } @@ -5101,10 +5143,20 @@ void exchange_two_client(Client *c1, Client *c2) { tmp_tags = c2->tags; setmon(c2, c1->mon, c1->tags, false); setmon(c1, tmp_mon, tmp_tags, false); + if (c1->mon && + c1->mon->pertag->ltidxs[c1->mon->pertag->curtag]->id == DWINDLE) + dwindle_swap_clients( + &c1->mon->pertag->dwindle_root[c1->mon->pertag->curtag], c1, + c2); arrange(c1->mon, false, false); arrange(c2->mon, false, false); focusclient(c1, 0); } else { + if (c1->mon && + c1->mon->pertag->ltidxs[c1->mon->pertag->curtag]->id == DWINDLE) + dwindle_swap_clients( + &c1->mon->pertag->dwindle_root[c1->mon->pertag->curtag], c1, + c2); arrange(c1->mon, false, false); } @@ -6343,6 +6395,7 @@ void unmapnotify(struct wl_listener *listener, void *data) { c->next_in_stack = NULL; c->prev_in_stack = NULL; + dwindle_remove_client(c); wlr_scene_node_destroy(&c->scene->node); printstatus(); motionnotify(0, NULL, 0, 0, 0, 0); From 4981b07a58fd17194d82cb95904793d623ad585e Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 9 May 2026 22:07:21 +0800 Subject: [PATCH 163/328] break change: remove tgmix layout --- README.md | 2 +- docs/ipc.md | 2 +- docs/window-management/layouts.md | 2 +- src/fetch/client.h | 11 +---------- src/layout/arrange.h | 3 +-- src/layout/horizontal.h | 13 +------------ src/layout/layout.h | 3 --- 7 files changed, 6 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index bb48cd97..d3f6e7cf 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ https://github.com/user-attachments/assets/bb83004a-0563-4b48-ad89-6461a9b78b1f - vertical_tile - vertical_grid - vertical_scroller -- tgmix +- dwindle # Installation diff --git a/docs/ipc.md b/docs/ipc.md index 72beefb5..8bb0f5c1 100644 --- a/docs/ipc.md +++ b/docs/ipc.md @@ -64,7 +64,7 @@ mmsg -s -t 2^ ### Layouts -Switch layouts programmatically. Layout codes: `S` (Scroller), `T` (Tile), `G` (Grid), `M` (Monocle), `K` (Deck), `CT` (Center Tile), `RT` (Right Tile), `VS` (Vertical Scroller), `VT` (Vertical Tile), `VG` (Vertical Grid), `VK` (Vertical Deck), `TG` (TGMix). +Switch layouts programmatically. Layout codes: `S` (Scroller), `T` (Tile), `G` (Grid), `M` (Monocle), `K` (Deck), `CT` (Center Tile), `RT` (Right Tile), `VS` (Vertical Scroller), `VT` (Vertical Tile), `VG` (Vertical Grid), `VK` (Vertical Deck), `DW` (Dwindle). ```bash # Switch to Scroller diff --git a/docs/window-management/layouts.md b/docs/window-management/layouts.md index 26c05fe4..9b957b29 100644 --- a/docs/window-management/layouts.md +++ b/docs/window-management/layouts.md @@ -18,7 +18,7 @@ mangowm supports a variety of layouts that can be assigned per tag. - `vertical_scroller` - `vertical_grid` - `vertical_deck` -- `tgmix` +- `dwindle` --- diff --git a/src/fetch/client.h b/src/fetch/client.h index 8fe831be..be9be420 100644 --- a/src/fetch/client.h +++ b/src/fetch/client.h @@ -558,7 +558,7 @@ bool client_is_in_same_stack(Client *sc, Client *tc, Client *fc) { if (id != SCROLLER && id != VERTICAL_SCROLLER && id != TILE && id != VERTICAL_TILE && id != DECK && id != VERTICAL_DECK && - id != CENTER_TILE && id != RIGHT_TILE && id != TGMIX) + id != CENTER_TILE && id != RIGHT_TILE) return false; if (id == SCROLLER || id == VERTICAL_SCROLLER) { @@ -583,15 +583,6 @@ bool client_is_in_same_stack(Client *sc, Client *tc, Client *fc) { return true; } - if (id == TGMIX) { - if (tc->ismaster ^ sc->ismaster) - return false; - if (fc && !(fc->ismaster ^ sc->ismaster)) - return false; - if (!sc->ismaster && sc->mon->visible_tiling_clients <= 3) - return true; - } - if (id == CENTER_TILE) { if (tc->ismaster ^ sc->ismaster) return false; diff --git a/src/layout/arrange.h b/src/layout/arrange.h index 9e0c6fa7..b822a729 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -707,8 +707,7 @@ void resize_tile_client(Client *grabc, bool isdrag, int32_t offsetx, const Layout *current_layout = grabc->mon->pertag->ltidxs[grabc->mon->pertag->curtag]; if (current_layout->id == TILE || current_layout->id == DECK || - current_layout->id == CENTER_TILE || current_layout->id == RIGHT_TILE || - (current_layout->id == TGMIX && grabc->mon->visible_tiling_clients <= 3) + current_layout->id == CENTER_TILE || current_layout->id == RIGHT_TILE ) { resize_tile_master_horizontal(grabc, isdrag, offsetx, offsety, time, diff --git a/src/layout/horizontal.h b/src/layout/horizontal.h index eaa7b5c2..e69b4cdd 100644 --- a/src/layout/horizontal.h +++ b/src/layout/horizontal.h @@ -993,15 +993,4 @@ monocle(Monitor *m) { } if ((c = focustop(m))) wlr_scene_node_raise_to_top(&c->scene->node); -} - -void tgmix(Monitor *m) { - int32_t n = m->visible_tiling_clients; - if (n <= 3) { - tile(m); - return; - } else { - grid(m); - return; - } -} +} \ No newline at end of file diff --git a/src/layout/layout.h b/src/layout/layout.h index 557ce571..16773dc7 100644 --- a/src/layout/layout.h +++ b/src/layout/layout.h @@ -11,7 +11,6 @@ static void vertical_overview(Monitor *m); static void vertical_grid(Monitor *m); static void vertical_scroller(Monitor *m); static void vertical_deck(Monitor *mon); -static void tgmix(Monitor *m); static void dwindle(Monitor *m); /* layout(s) */ @@ -29,7 +28,6 @@ enum { VERTICAL_GRID, VERTICAL_DECK, RIGHT_TILE, - TGMIX, DWINDLE, }; @@ -48,6 +46,5 @@ Layout layouts[] = { {"VT", vertical_tile, "vertical_tile", VERTICAL_TILE}, // 垂直平铺布局 {"VG", vertical_grid, "vertical_grid", VERTICAL_GRID}, // 垂直格子布局 {"VK", vertical_deck, "vertical_deck", VERTICAL_DECK}, // 垂直卡片布局 - {"TG", tgmix, "tgmix", TGMIX}, // 混合布局 {"DW", dwindle, "dwindle", DWINDLE}, }; \ No newline at end of file From 637dc0a09a5e46fbf2e8375e7e5b9a03e13d4288 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 9 May 2026 22:11:18 +0800 Subject: [PATCH 164/328] opt: set dwindle_drop_simple_split default to 1 --- 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 ceaec81c..006968f0 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -3356,7 +3356,7 @@ void set_value_default() { config.dwindle_preserve_split = 0; config.dwindle_smart_split = 0; config.dwindle_smart_resize = 0; - config.dwindle_drop_simple_split = 0; + config.dwindle_drop_simple_split = 1; config.dwindle_split_ratio = 0.5f; config.log_level = WLR_ERROR; From b0f8d3f8a3f9655a63a6b4061889b85843d9cb4d Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 9 May 2026 22:16:00 +0800 Subject: [PATCH 165/328] opt: more simple drop area --- src/animation/client.h | 86 ++++++++++++++++++++++++++++++------------ 1 file changed, 61 insertions(+), 25 deletions(-) diff --git a/src/animation/client.h b/src/animation/client.h index ba48959d..8ce81cd2 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -514,6 +514,9 @@ void client_set_drop_area(Client *c) { bool first_draw = false; int32_t drop_direction = UNDIR; + if (!c || !c->mon) + return; + if (!c->enable_drop_area_draw && !c->droparea->node.enabled) { return; } @@ -538,22 +541,21 @@ void client_set_drop_area(Client *c) { struct wlr_box drop_box; - const Layout *cur_layout = - c->mon ? c->mon->pertag->ltidxs[c->mon->pertag->curtag] : NULL; - bool dwindle_familiar = cur_layout && cur_layout->id == DWINDLE && - config.dwindle_drop_simple_split; + const Layout *cur_layout = c->mon->pertag->ltidxs[c->mon->pertag->curtag]; + bool dwindle_familiar = + cur_layout->id == DWINDLE && config.dwindle_drop_simple_split; - // 中心区域:x和y都在30%~70%之间 → 无方向 - if (rel_x > client_width * 0.3 && rel_x < client_width * 0.7 && - rel_y > client_height * 0.3 && rel_y < client_height * 0.7) { - drop_box.x = bw + client_width * 0.3; - drop_box.y = bw + client_height * 0.3; - drop_box.width = client_width * 0.4; - drop_box.height = client_height * 0.4; - drop_direction = UNDIR; - } else if (dwindle_familiar) { - // Mirror dwindle_assign's split_h = (aw >= ah) rule so the preview - // matches the auto-rotated split that dwindle will produce. + uint32_t nmaster = c->mon->pertag->nmasters[c->mon->pertag->curtag]; + + bool should_swap = + (cur_layout->id == DECK || cur_layout->id == VERTICAL_DECK || + cur_layout->id == MONOCLE || cur_layout->id == GRID || + cur_layout->id == VERTICAL_GRID) || + ((cur_layout->id == TILE || cur_layout->id == VERTICAL_TILE || + cur_layout->id == CENTER_TILE || cur_layout->id == RIGHT_TILE) && + nmaster == 1 && c->ismaster); + + if (dwindle_familiar) { bool split_h = c->geom.width >= c->geom.height; float ratio = config.dwindle_split_ratio; if (split_h) { @@ -582,46 +584,80 @@ void client_set_drop_area(Client *c) { drop_box.x = bw; drop_box.y = bw + (int32_t)(client_height * ratio); drop_box.width = client_width; - drop_box.height = client_height - (int32_t)(client_height * ratio); + drop_box.height = + client_height - (int32_t)(client_height * ratio); } } + } else if (should_swap) { + drop_box.x = bw; + drop_box.y = bw; + drop_box.width = client_width; + drop_box.height = client_height; + drop_direction = UNDIR; + } else if (cur_layout->id == TILE || cur_layout->id == DECK || + cur_layout->id == CENTER_TILE || cur_layout->id == RIGHT_TILE) { + if (rel_y < client_height * 0.5) { + drop_direction = UP; + drop_box.x = bw; + drop_box.y = bw; + drop_box.width = client_width; + drop_box.height = client_height / 2; + } else { + drop_direction = DOWN; + drop_box.x = bw; + drop_box.y = bw + client_height / 2; + drop_box.width = client_width; + drop_box.height = client_height / 2; + } + } else if (cur_layout->id == VERTICAL_TILE || + cur_layout->id == VERTICAL_DECK) { + if (rel_x < client_width * 0.5) { + drop_direction = LEFT; + drop_box.x = bw; + drop_box.y = bw; + drop_box.width = client_width / 2; + drop_box.height = client_height; + } else { + drop_direction = RIGHT; + drop_box.x = bw + client_width / 2; + drop_box.y = bw; + drop_box.width = client_width / 2; + drop_box.height = client_height; + } } else { - // 否则根据到各边的距离决定方向 double dist_left = rel_x; double dist_right = client_width - rel_x; double dist_top = rel_y; double dist_bottom = client_height - rel_y; - // 找出最小距离的方向(相等时按左、右、上、下的优先级顺序) if (dist_left <= dist_right && dist_left <= dist_top && dist_left <= dist_bottom) { drop_direction = LEFT; drop_box.x = bw; drop_box.y = bw; - drop_box.width = client_width * 0.3; + drop_box.width = client_width / 2; drop_box.height = client_height; } else if (dist_right <= dist_top && dist_right <= dist_bottom) { drop_direction = RIGHT; - drop_box.x = bw + client_width * 0.7; + drop_box.x = bw + client_width / 2; drop_box.y = bw; - drop_box.width = client_width * 0.3; + drop_box.width = client_width / 2; drop_box.height = client_height; } else if (dist_top <= dist_bottom) { drop_direction = UP; drop_box.x = bw; drop_box.y = bw; drop_box.width = client_width; - drop_box.height = client_height * 0.3; + drop_box.height = client_height / 2; } else { drop_direction = DOWN; drop_box.x = bw; - drop_box.y = bw + client_height * 0.7; + drop_box.y = bw + client_height / 2; drop_box.width = client_width; - drop_box.height = client_height * 0.3; + drop_box.height = client_height / 2; } } - // 如果方向和上次相同且不是第一次绘制,则跳过更新 if (!first_draw && c->drop_direction == drop_direction) { return; } From be575c4d24eda25571a0a46aac80f52717abf372 Mon Sep 17 00:00:00 2001 From: Ernesto Cruz Date: Sat, 9 May 2026 15:34:00 +0000 Subject: [PATCH 166/328] docs: dwindle docs --- docs/window-management/layouts.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/docs/window-management/layouts.md b/docs/window-management/layouts.md index 9b957b29..9b0b8537 100644 --- a/docs/window-management/layouts.md +++ b/docs/window-management/layouts.md @@ -83,6 +83,35 @@ default_nmaster=1 --- +## Dwindle Layout + +The Dwindle layout arranges windows as a binary tree of recursive splits. Each new window splits the focused window's container, producing a spiral-like tiling. + +### Configuration + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `dwindle_split_ratio` | `0.5` | Ratio used for new splits (`0.05`–`0.95`). | +| `dwindle_smart_split` | `0` | Pick the split axis from the cursor's position inside the focused window. The new window appears on the cursor's side. | +| `dwindle_hsplit` | `0` | Side-by-side splits: where the new window goes. `0` = follow cursor, `1` = right, `2` = left. | +| `dwindle_vsplit` | `0` | Top/bottom splits: where the new window goes. `0` = follow cursor, `1` = below, `2` = above. | +| `dwindle_preserve_split` | `0` | Keep the sibling's split orientation when a window is closed. | +| `dwindle_smart_resize` | `0` | When dragging to resize, move the split toward the cursor regardless of which side was grabbed. | +| `dwindle_drop_simple_split` | `1` | Drag-to-tile drop preview. `1` = 2-zone preview matching `dwindle_split_ratio`, `0` = 4-quadrant preview. | + +```ini +# Example dwindle configuration +dwindle_split_ratio=0.5 +dwindle_smart_split=0 +dwindle_hsplit=0 +dwindle_vsplit=0 +dwindle_preserve_split=0 +dwindle_smart_resize=0 +dwindle_drop_simple_split=1 +``` + +--- + ## Switching Layouts You can switch layouts dynamically or set a default for specific tags using [Tag Rules](/docs/window-management/rules#tag-rules). From cabafb2393ff71615af4959a51885c8d1eefc9a0 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 10 May 2026 09:34:01 +0800 Subject: [PATCH 167/328] opt: swallow inherit global --- src/mango.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mango.c b/src/mango.c index 7463262c..34d4d764 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1158,6 +1158,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; + c->isglobal = w->isglobal; if (w->next_in_stack) w->next_in_stack->prev_in_stack = c; From bf831cb148d4bf6918a9ed52e6326f03619732ec Mon Sep 17 00:00:00 2001 From: max-amb Date: Sun, 10 May 2026 13:39:08 +0100 Subject: [PATCH 168/328] Updated flake.lock nixpkgs input Made because project wasn't building due to the updated name for libxcbwm --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 2917a6fd..03d3a7bc 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1750386251, - "narHash": "sha256-1ovgdmuDYVo5OUC5NzdF+V4zx2uT8RtsgZahxidBTyw=", + "lastModified": 1778274207, + "narHash": "sha256-I4puXmX1iovcCHZlRmztO3vW0mAbbRvq4F8wgIMQ1MM=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "076e8c6678d8c54204abcb4b1b14c366835a58bb", + "rev": "b3da656039dc7a6240f27b2ef8cc6a3ef3bccae7", "type": "github" }, "original": { From d0c5b1ccdc47dc8a88d471a99b1fb1473b000f51 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 10 May 2026 19:07:10 +0800 Subject: [PATCH 169/328] opt: redo scroller and dwindle layout --- src/config/parse_config.h | 1 - src/dispatch/bind_define.h | 274 ++++++------ src/fetch/client.h | 21 +- src/layout/arrange.h | 174 ++++---- src/layout/horizontal.h | 319 ------------- src/layout/scroll.h | 888 +++++++++++++++++++++++++++++++++++++ src/layout/vertical.h | 318 ------------- src/mango.c | 438 +++++++++--------- 8 files changed, 1352 insertions(+), 1081 deletions(-) create mode 100644 src/layout/scroll.h diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 006968f0..976bf5f3 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -693,7 +693,6 @@ uint32_t parse_mod(const char *mod_str) { } } } else { - // 完整的 modifier 检查(保留原始所有检查项) if (!strcmp(token, "super") || !strcmp(token, "super_l") || !strcmp(token, "super_r")) { mod |= WLR_MODIFIER_LOGO; diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 3c1d2154..e4723a15 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -629,15 +629,106 @@ int32_t set_proportion(const Arg *arg) { return 0; Client *tc = selmon->sel; + if (!tc) + return 0; - if (tc) { - tc = get_scroll_stack_head(tc); - uint32_t max_client_width = - selmon->w.width - 2 * config.scroller_structs - config.gappih; - tc->scroller_proportion = arg->f; - tc->geom.width = max_client_width * arg->f; - arrange(selmon, false, false); + /* 获取堆叠头部客户端 */ + tc = scroll_get_stack_head_client(tc); + if (!tc) + return 0; + + Monitor *m = tc->mon; + uint32_t tag = m->pertag->curtag; + struct TagScrollerState *st = m->pertag->scroller_state[tag]; + struct ScrollerStackNode *node = NULL; + + if (st) + node = find_scroller_node(st, tc); + + /* 同时更新节点和客户端字段 */ + if (node) + node->scroller_proportion = arg->f; + tc->scroller_proportion = arg->f; + + /* 可选的即时几何更新,arrange 时会重新计算 */ + uint32_t max_client_width = + m->w.width - 2 * config.scroller_structs - config.gappih; + tc->geom.width = max_client_width * arg->f; + + arrange(m, false, false); + return 0; +} + +int32_t switch_proportion_preset(const Arg *arg) { + float target_proportion = 0; + if (!selmon) + return 0; + + if (config.scroller_proportion_preset_count == 0) + return 0; + + if (selmon->isoverview || !is_scroller_layout(selmon)) + return 0; + + if (selmon->visible_tiling_clients == 1 && + !config.scroller_ignore_proportion_single) + return 0; + + Client *tc = selmon->sel; + if (!tc) + return 0; + + tc = scroll_get_stack_head_client(tc); + if (!tc) + return 0; + + Monitor *m = tc->mon; + uint32_t tag = m->pertag->curtag; + struct TagScrollerState *st = m->pertag->scroller_state[tag]; + struct ScrollerStackNode *node = NULL; + + if (st) + node = find_scroller_node(st, tc); + + /* 优先从节点读取当前比例,以确保切换基于正确的值 */ + float current_proportion = + node ? node->scroller_proportion : tc->scroller_proportion; + + /* 查找预设目标 */ + for (int32_t i = 0; i < config.scroller_proportion_preset_count; i++) { + if (config.scroller_proportion_preset[i] == current_proportion) { + if (arg->i == NEXT) { + if (i == config.scroller_proportion_preset_count - 1) + target_proportion = config.scroller_proportion_preset[0]; + else + target_proportion = + config.scroller_proportion_preset[i + 1]; + } else { + if (i == 0) + target_proportion = + config.scroller_proportion_preset + [config.scroller_proportion_preset_count - 1]; + else + target_proportion = + config.scroller_proportion_preset[i - 1]; + } + break; + } } + + if (target_proportion == 0.0f) + target_proportion = config.scroller_proportion_preset[0]; + + /* 更新节点和客户端 */ + if (node) + node->scroller_proportion = target_proportion; + tc->scroller_proportion = target_proportion; + + uint32_t max_client_width = + m->w.width - 2 * config.scroller_structs - config.gappih; + tc->geom.width = max_client_width * target_proportion; + + arrange(m, false, false); return 0; } @@ -835,7 +926,7 @@ int32_t centerwin(const Arg *arg) { if (!is_scroller_layout(selmon)) return 0; - Client *stack_head = get_scroll_stack_head(c); + Client *stack_head = scroll_get_stack_head_client(c); if (selmon->pertag->ltidxs[selmon->pertag->curtag]->id == SCROLLER) { stack_head->geom.x = selmon->w.x + (selmon->w.width - stack_head->geom.width) / 2; @@ -1042,68 +1133,6 @@ int32_t switch_layout(const Arg *arg) { return 0; } -int32_t switch_proportion_preset(const Arg *arg) { - float target_proportion = 0; - if (!selmon) - return 0; - - if (config.scroller_proportion_preset_count == 0) { - return 0; - } - - if (selmon->isoverview || !is_scroller_layout(selmon)) - return 0; - - if (selmon->visible_tiling_clients == 1 && - !config.scroller_ignore_proportion_single) - return 0; - - Client *tc = selmon->sel; - - if (tc) { - tc = get_scroll_stack_head(tc); - for (int32_t i = 0; i < config.scroller_proportion_preset_count; i++) { - if (config.scroller_proportion_preset[i] == - tc->scroller_proportion) { - - if (arg->i == NEXT) { - if (i == config.scroller_proportion_preset_count - 1) { - target_proportion = - config.scroller_proportion_preset[0]; - break; - } else { - target_proportion = - config.scroller_proportion_preset[i + 1]; - break; - } - } else { - if (i == 0) { - target_proportion = - config.scroller_proportion_preset - [config.scroller_proportion_preset_count - 1]; - break; - } else { - target_proportion = - config.scroller_proportion_preset[i - 1]; - break; - } - } - } - } - - if (target_proportion == 0) { - target_proportion = config.scroller_proportion_preset[0]; - } - - uint32_t max_client_width = - selmon->w.width - 2 * config.scroller_structs - config.gappih; - tc->scroller_proportion = target_proportion; - tc->geom.width = max_client_width * target_proportion; - arrange(selmon, false, false); - } - return 0; -} - int32_t tag(const Arg *arg) { if (!selmon) return 0; @@ -1202,7 +1231,6 @@ int32_t tagsilent(const Arg *arg) { clear_fullscreen_flag(fc); } } - exit_scroller_stack(target_client); focusclient(focustop(selmon), 1); arrange(target_client->mon, false, false); return 0; @@ -1353,11 +1381,6 @@ int32_t toggleglobal(const Arg *arg) { selmon->sel->isnamedscratchpad = 0; } selmon->sel->isglobal ^= 1; - if (selmon->sel->isglobal && - (selmon->sel->prev_in_stack || selmon->sel->next_in_stack)) { - exit_scroller_stack(selmon->sel); - arrange(selmon, false, false); - } setborder_color(selmon->sel); return 0; } @@ -1780,76 +1803,69 @@ int32_t toggle_monitor(const Arg *arg) { int32_t scroller_apply_stack(Client *c, Client *target_client, int32_t direction) { + if (!c || !c->mon || c->isfloating || !is_scroller_layout(c->mon)) + return 0; - Client *source_stack_head = NULL; - Client *stack_head = NULL; - bool is_horizontal_layout = - c->mon->pertag->ltidxs[c->mon->pertag->curtag]->id == SCROLLER ? true - : false; + Monitor *m = c->mon; + uint32_t tag = m->pertag->curtag; + struct TagScrollerState *st = ensure_scroller_state(m, tag); - if (target_client) { - stack_head = get_scroll_stack_head(target_client); - } + /* 获取当前节点 */ + struct ScrollerStackNode *cnode = find_scroller_node(st, c); + struct ScrollerStackNode *tnode = + target_client ? find_scroller_node(st, target_client) : NULL; - source_stack_head = get_scroll_stack_head(c); + bool is_horizontal = (m->pertag->ltidxs[tag]->id == SCROLLER); - if (source_stack_head == stack_head) { + /* 若方向为 UNDIR 且有目标,直接插入到目标尾部 */ + if (direction == UNDIR && target_client && target_client->mon == c->mon) { + scroller_insert_stack(c, target_client, false); return 0; } - if (c->isfullscreen) { - setfullscreen(c, 0); - } + /* 处理从堆叠中移出的情况(方向 LEFT/UP 或 RIGHT/DOWN) */ + if (cnode && (cnode->prev_in_stack || cnode->next_in_stack)) { + bool to_left_or_up = (is_horizontal && direction == LEFT) || + (!is_horizontal && direction == UP); + bool to_right_or_down = (is_horizontal && direction == RIGHT) || + (!is_horizontal && direction == DOWN); - if (c->ismaximizescreen) { - setmaximizescreen(c, 0); - } + if (to_left_or_up || to_right_or_down) { + /* 找到当前堆叠的头节点,以便移动全局链表位置 */ + struct ScrollerStackNode *head = cnode; + while (head->prev_in_stack) + head = head->prev_in_stack; + Client *source_stack_head = head->client; - if (c->prev_in_stack && direction != UNDIR) { - if ((is_horizontal_layout && direction == LEFT) || - (!is_horizontal_layout && direction == UP)) { - exit_scroller_stack(c); + /* 从 tag 状态中移除该客户端对应的节点 */ + scroller_node_remove(st, cnode); + /* 重新创建一个独立的节点(无堆叠关系) */ + scroller_node_create(st, c); + + /* 调整全局客户端链表顺序:移到源堆叠头的前面或后面 */ wl_list_remove(&c->link); - wl_list_insert(source_stack_head->link.prev, &c->link); - arrange(selmon, false, false); + if (to_left_or_up) + wl_list_insert(source_stack_head->link.prev, &c->link); + else + wl_list_insert(&source_stack_head->link, &c->link); - } else if ((is_horizontal_layout && direction == RIGHT) || - (!is_horizontal_layout && direction == DOWN)) { - exit_scroller_stack(c); - wl_list_remove(&c->link); - wl_list_insert(&source_stack_head->link, &c->link); - arrange(selmon, false, false); + /* 同步到客户端字段并重排 */ + sync_scroller_state_to_clients(m, tag); + arrange(m, false, false); + return 0; } - return 0; - } else if (c->next_in_stack && direction != UNDIR) { - Client *next_in_stack = c->next_in_stack; - if ((is_horizontal_layout && direction == LEFT) || - (!is_horizontal_layout && direction == UP)) { - exit_scroller_stack(c); - wl_list_remove(&c->link); - wl_list_insert(next_in_stack->link.prev, &c->link); - arrange(selmon, false, false); - } else if ((is_horizontal_layout && direction == RIGHT) || - (!is_horizontal_layout && direction == DOWN)) { - exit_scroller_stack(c); - wl_list_remove(&c->link); - wl_list_insert(&next_in_stack->link, &c->link); - arrange(selmon, false, false); - } - return 0; } - if (!target_client || target_client->mon != c->mon) { + if (!target_client || target_client->mon != c->mon) return 0; - } - // Find the tail of target_client's stack - Client *stack_tail = target_client; - while (stack_tail->next_in_stack) { - stack_tail = stack_tail->next_in_stack; - } + /* 找到目标堆叠的尾部节点 */ + struct ScrollerStackNode *tail = tnode; + while (tail->next_in_stack) + tail = tail->next_in_stack; - scroller_insert_stack(c, stack_tail, false); + /* 通过封装好的插入函数实现(尾部插入) */ + scroller_insert_stack(c, tail->client, false); return 0; } diff --git a/src/fetch/client.h b/src/fetch/client.h index be9be420..524efa2a 100644 --- a/src/fetch/client.h +++ b/src/fetch/client.h @@ -538,18 +538,6 @@ bool client_only_in_one_tag(Client *c) { } } -Client *get_scroll_stack_head(Client *c) { - Client *scroller_stack_head = c; - - if (!scroller_stack_head) - return NULL; - - while (scroller_stack_head->prev_in_stack) { - scroller_stack_head = scroller_stack_head->prev_in_stack; - } - return scroller_stack_head; -} - bool client_is_in_same_stack(Client *sc, Client *tc, Client *fc) { if (!sc || !tc) return false; @@ -562,10 +550,11 @@ bool client_is_in_same_stack(Client *sc, Client *tc, Client *fc) { return false; if (id == SCROLLER || id == VERTICAL_SCROLLER) { - Client *source_stack_head = get_scroll_stack_head(sc); - Client *target_stack_head = get_scroll_stack_head(tc); - Client *fc_head = fc ? get_scroll_stack_head(fc) : NULL; - if (fc && fc->prev_in_stack && fc_head == source_stack_head) + Client *source_stack_head = scroll_get_stack_head_client(sc); + Client *target_stack_head = scroll_get_stack_head_client(tc); + Client *fc_head = fc ? scroll_get_stack_head_client(fc) : NULL; + + if (fc && fc_head == source_stack_head) return false; if (source_stack_head == target_stack_head) return true; diff --git a/src/layout/arrange.h b/src/layout/arrange.h index b822a729..56fda657 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -505,36 +505,51 @@ void resize_tile_dwindle(Client *grabc, bool isdrag, int32_t offsetx, void resize_tile_scroller(Client *grabc, bool isdrag, int32_t offsetx, int32_t offsety, uint32_t time, bool isvertical) { - Client *tc = NULL; + if (!grabc || grabc->isfullscreen || grabc->ismaximizescreen) + return; + if (grabc->mon->isoverview) + return; + + Monitor *m = grabc->mon; + uint32_t tag = m->pertag->curtag; + struct TagScrollerState *st = m->pertag->scroller_state[tag]; + if (!st) + return; + + struct ScrollerStackNode *curnode = find_scroller_node(st, grabc); + if (!curnode) + return; + + struct ScrollerStackNode *headnode = curnode; + while (headnode->prev_in_stack) + headnode = headnode->prev_in_stack; + + Client *stack_head_client = headnode->client; + + if (m->visible_tiling_clients == 1 && + !config.scroller_ignore_proportion_single) + return; + float delta_x, delta_y; float new_scroller_proportion; float new_stack_proportion; - Client *stack_head = get_scroll_stack_head(grabc); - - if (grabc && grabc->mon->visible_tiling_clients == 1 && - !config.scroller_ignore_proportion_single) - return; if (!start_drag_window && isdrag) { drag_begin_cursorx = cursor->x; drag_begin_cursory = cursor->y; start_drag_window = true; - // 记录初始状态 - stack_head->old_scroller_pproportion = stack_head->scroller_proportion; - grabc->old_stack_proportion = grabc->stack_proportion; + headnode->client->old_scroller_pproportion = + headnode->scroller_proportion; + grabc->old_stack_proportion = curnode->stack_proportion; grabc->cursor_in_left_half = cursor->x < grabc->geom.x + grabc->geom.width / 2; grabc->cursor_in_upper_half = cursor->y < grabc->geom.y + grabc->geom.height / 2; - // 记录初始几何信息 grabc->drag_begin_geom = grabc->geom; } else { - // 计算相对于屏幕尺寸的比例变化 - // 计算相对于屏幕尺寸的比例变化 if (isdrag) { - offsetx = cursor->x - drag_begin_cursorx; offsety = cursor->y - drag_begin_cursory; } else { @@ -542,37 +557,33 @@ void resize_tile_scroller(Client *grabc, bool isdrag, int32_t offsetx, grabc->old_master_inner_per = grabc->master_inner_per; grabc->old_stack_inner_per = grabc->stack_inner_per; grabc->drag_begin_geom = grabc->geom; - stack_head->old_scroller_pproportion = - stack_head->scroller_proportion; - grabc->old_stack_proportion = grabc->stack_proportion; + stack_head_client->old_scroller_pproportion = + headnode->scroller_proportion; + grabc->old_stack_proportion = curnode->stack_proportion; grabc->cursor_in_upper_half = false; grabc->cursor_in_left_half = false; } if (isvertical) { delta_y = (float)(offsety) * - (stack_head->old_scroller_pproportion) / + (headnode->client->old_scroller_pproportion) / grabc->drag_begin_geom.height; delta_x = (float)(offsetx) * (grabc->old_stack_proportion) / grabc->drag_begin_geom.width; } else { delta_x = (float)(offsetx) * - (stack_head->old_scroller_pproportion) / + (headnode->client->old_scroller_pproportion) / grabc->drag_begin_geom.width; delta_y = (float)(offsety) * (grabc->old_stack_proportion) / grabc->drag_begin_geom.height; } - bool moving_up; - bool moving_down; - bool moving_left; - bool moving_right; - + bool moving_up, moving_down, moving_left, moving_right; if (!isdrag) { - moving_up = offsety < 0 ? true : false; - moving_down = offsety > 0 ? true : false; - moving_left = offsetx < 0 ? true : false; - moving_right = offsetx > 0 ? true : false; + moving_up = offsety < 0; + moving_down = offsety > 0; + moving_left = offsetx < 0; + moving_right = offsetx > 0; } else { moving_up = cursor->y < drag_begin_cursory; moving_down = cursor->y > drag_begin_cursory; @@ -582,10 +593,8 @@ void resize_tile_scroller(Client *grabc, bool isdrag, int32_t offsetx, if ((grabc->cursor_in_upper_half && moving_up) || (!grabc->cursor_in_upper_half && moving_down)) { - // 光标在窗口上方且向上移动,或在窗口下方且向下移动 → 增加高度 delta_y = fabsf(delta_y); } else { - // 其他情况 → 减小高度 delta_y = -fabsf(delta_y); } @@ -597,99 +606,93 @@ void resize_tile_scroller(Client *grabc, bool isdrag, int32_t offsetx, } if (isvertical) { - if (!grabc->next_in_stack && grabc->prev_in_stack && !isdrag) { + if (!curnode->next_in_stack && curnode->prev_in_stack && !isdrag) { delta_x = delta_x * -1.0f; } - if (!grabc->next_in_stack && grabc->prev_in_stack && isdrag) { - if (moving_right) { + if (!curnode->next_in_stack && curnode->prev_in_stack && isdrag) { + if (moving_right) delta_x = -fabsf(delta_x); - } else { + else delta_x = fabsf(delta_x); - } } - if (!grabc->prev_in_stack && grabc->next_in_stack && isdrag) { - if (moving_left) { + if (!curnode->prev_in_stack && curnode->next_in_stack && isdrag) { + if (moving_left) delta_x = -fabsf(delta_x); - } else { + else delta_x = fabsf(delta_x); - } } - if (isdrag) { - if (moving_up) { + if (moving_up) delta_y = -fabsf(delta_y); - } else { + else delta_y = fabsf(delta_y); - } } - } else { - if (!grabc->next_in_stack && grabc->prev_in_stack && !isdrag) { + if (!curnode->next_in_stack && curnode->prev_in_stack && !isdrag) { delta_y = delta_y * -1.0f; } - if (!grabc->next_in_stack && grabc->prev_in_stack && isdrag) { - if (moving_down) { + if (!curnode->next_in_stack && curnode->prev_in_stack && isdrag) { + if (moving_down) delta_y = -fabsf(delta_y); - } else { + else delta_y = fabsf(delta_y); - } } - if (!grabc->prev_in_stack && grabc->next_in_stack && isdrag) { - if (moving_up) { + if (!curnode->prev_in_stack && curnode->next_in_stack && isdrag) { + if (moving_up) delta_y = -fabsf(delta_y); - } else { + else delta_y = fabsf(delta_y); - } } - if (isdrag) { - if (moving_left) { + if (moving_left) delta_x = -fabsf(delta_x); - } else { + else delta_x = fabsf(delta_x); - } } } - // 直接设置新的比例,基于初始值 + 变化量 if (isvertical) { new_scroller_proportion = - stack_head->old_scroller_pproportion + delta_y; + headnode->client->old_scroller_pproportion + delta_y; new_stack_proportion = grabc->old_stack_proportion + delta_x; - } else { new_scroller_proportion = - stack_head->old_scroller_pproportion + delta_x; + headnode->client->old_scroller_pproportion + delta_x; new_stack_proportion = grabc->old_stack_proportion + delta_y; } - // 应用限制,确保比例在合理范围内 new_scroller_proportion = fmaxf(0.1f, fminf(1.0f, new_scroller_proportion)); new_stack_proportion = fmaxf(0.1f, fminf(0.9f, new_stack_proportion)); - grabc->stack_proportion = new_stack_proportion; + curnode->stack_proportion = new_stack_proportion; + headnode->scroller_proportion = new_scroller_proportion; - stack_head->scroller_proportion = new_scroller_proportion; - - wl_list_for_each(tc, &clients, link) { - if (!isdrag && new_stack_proportion != 1.0f && - grabc->old_stack_proportion != 1.0f && tc != grabc && - ISTILED(tc) && get_scroll_stack_head(tc) == stack_head) { - tc->stack_proportion = (1.0f - new_stack_proportion) / - (1.0f - grabc->old_stack_proportion) * - tc->stack_proportion; + /* 调整同一堆叠内其他节点的 stack_proportion */ + /* 调整整个堆叠内除当前窗口外的所有节点 */ + if (!isdrag && grabc->old_stack_proportion != 1.0f) { + for (struct ScrollerStackNode *tc = headnode; tc; + tc = tc->next_in_stack) { + if (tc != curnode) { + tc->stack_proportion = + (1.0f - new_stack_proportion) / + (1.0f - grabc->old_stack_proportion) * + tc->stack_proportion; + } } } + /* 同步回全局字段*/ + sync_scroller_state_to_clients(m, tag); + if (!isdrag) { - arrange(grabc->mon, false, false); + arrange(m, false, false); return; } if (last_apply_drap_time == 0 || time - last_apply_drap_time > config.drag_tile_refresh_interval) { - arrange(grabc->mon, false, false); + arrange(m, false, false); last_apply_drap_time = time; } } @@ -834,11 +837,15 @@ void pre_caculate_before_arrange(Monitor *m, bool want_animation, m->visible_tiling_clients = 0; m->visible_scroll_tiling_clients = 0; - wl_list_for_each(c, &clients, link) { + uint32_t tag = m->pertag->curtag; + struct TagScrollerState *st = m->pertag->scroller_state[tag]; - if (!client_only_in_one_tag(c) || c->isglobal || c->isunglobal) { - exit_scroller_stack(c); - } + const Layout *cur_layout = m->pertag->ltidxs[m->pertag->curtag]; + if (cur_layout->id == SCROLLER || cur_layout->id == VERTICAL_SCROLLER) { + update_scroller_state(m); + } + + wl_list_for_each(c, &clients, link) { if (from_view && (c->isglobal || c->isunglobal)) { set_size_per(m, c); @@ -862,10 +869,15 @@ void pre_caculate_before_arrange(Monitor *m, bool want_animation, if (ISTILED(c)) { m->visible_tiling_clients++; - } - if (ISSCROLLTILED(c) && !c->prev_in_stack) { - m->visible_scroll_tiling_clients++; + /* 更新可见滚动客户端计数 */ + if (st) { + struct ScrollerStackNode *n = find_scroller_node(st, c); + if (n && !n->prev_in_stack) /* 是堆叠头部 */ + m->visible_scroll_tiling_clients++; + } else if (ISSCROLLTILED(c)) { + m->visible_scroll_tiling_clients++; + } } } } @@ -879,7 +891,6 @@ void pre_caculate_before_arrange(Monitor *m, bool want_animation, if (c->mon == m) { if (VISIBLEON(c, m)) { if (ISTILED(c)) { - if (i < nmasters) { master_num++; total_master_inner_percent += c->master_inner_per; @@ -898,7 +909,6 @@ void pre_caculate_before_arrange(Monitor *m, bool want_animation, c->stack_inner_per; } } - i++; } diff --git a/src/layout/horizontal.h b/src/layout/horizontal.h index e69b4cdd..37c8793e 100644 --- a/src/layout/horizontal.h +++ b/src/layout/horizontal.h @@ -184,325 +184,6 @@ void deck(Monitor *m) { } } -void horizontal_scroll_adjust_fullandmax(Client *c, - struct wlr_box *target_geom) { - Monitor *m = c->mon; - int32_t cur_gappih = enablegaps ? m->gappih : 0; - int32_t cur_gappoh = enablegaps ? m->gappoh : 0; - int32_t cur_gappov = enablegaps ? m->gappov : 0; - - cur_gappih = config.smartgaps && m->visible_scroll_tiling_clients == 1 - ? 0 - : cur_gappih; - cur_gappoh = config.smartgaps && m->visible_scroll_tiling_clients == 1 - ? 0 - : cur_gappoh; - cur_gappov = config.smartgaps && m->visible_scroll_tiling_clients == 1 - ? 0 - : cur_gappov; - - if (c->isfullscreen) { - target_geom->height = m->m.height; - 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 arrange_stack(Client *scroller_stack_head, struct wlr_box geometry, - int32_t gappiv) { - int32_t stack_size = 0; - Client *iter = scroller_stack_head; - - while (iter) { - stack_size++; - iter = iter->next_in_stack; - } - - if (stack_size == 0) - return; - - float total_proportion = 0.0f; - iter = scroller_stack_head; - while (iter) { - if (iter->stack_proportion <= 0.0f || iter->stack_proportion >= 1.0f) { - iter->stack_proportion = - stack_size == 1 ? 1.0f : 1.0f / (stack_size - 1); - } - total_proportion += iter->stack_proportion; - iter = iter->next_in_stack; - } - - iter = scroller_stack_head; - while (iter) { - iter->stack_proportion = iter->stack_proportion / total_proportion; - iter = iter->next_in_stack; - } - - int32_t client_height; - int32_t current_y = geometry.y; - int32_t remain_client_height = geometry.height - (stack_size - 1) * gappiv; - float remain_proportion = 1.0f; - - iter = scroller_stack_head; - while (iter) { - - client_height = - remain_client_height * (iter->stack_proportion / remain_proportion); - - struct wlr_box client_geom = {.x = geometry.x, - .y = current_y, - .width = geometry.width, - .height = client_height}; - resize(iter, client_geom, 0); - remain_proportion -= iter->stack_proportion; - remain_client_height -= client_height; - current_y += client_height + gappiv; - iter = iter->next_in_stack; - } -} - -void horizontal_check_scroller_root_inside_mon(Client *c, - struct wlr_box *geometry) { - if (!GEOMINSIDEMON(geometry, c->mon)) { - geometry->x = c->mon->w.x + (c->mon->w.width - geometry->width) / 2; - } -} - -// 滚动布局 -void scroller(Monitor *m) { - int32_t i, n, j; - float single_proportion = 1.0; - - Client *c = NULL, *root_client = NULL; - Client **tempClients = NULL; // 初始化为 NULL - struct wlr_box target_geom; - int32_t focus_client_index = 0; - bool need_scroller = false; - bool over_overspread_to_left = false; - int32_t cur_gappih = enablegaps ? m->gappih : 0; - int32_t cur_gappoh = enablegaps ? m->gappoh : 0; - int32_t cur_gappov = enablegaps ? m->gappov : 0; - int32_t cur_gappiv = enablegaps ? m->gappiv : 0; - - cur_gappih = config.smartgaps && m->visible_scroll_tiling_clients == 1 - ? 0 - : cur_gappih; - cur_gappoh = config.smartgaps && m->visible_scroll_tiling_clients == 1 - ? 0 - : cur_gappoh; - cur_gappov = config.smartgaps && m->visible_scroll_tiling_clients == 1 - ? 0 - : cur_gappov; - - int32_t max_client_width = - m->w.width - 2 * config.scroller_structs - cur_gappih; - - n = m->visible_scroll_tiling_clients; - - if (n == 0) { - return; // 没有需要处理的客户端,直接返回 - } - - // 动态分配内存 - tempClients = malloc(n * sizeof(Client *)); - if (!tempClients) { - // 处理内存分配失败的情况 - return; - } - - // 第二次遍历,填充 tempClients - j = 0; - wl_list_for_each(c, &clients, link) { - if (VISIBLEON(c, m) && ISSCROLLTILED(c) && !c->prev_in_stack) { - tempClients[j] = c; - j++; - } - } - - if (n == 1 && !config.scroller_ignore_proportion_single && - !tempClients[0]->isfullscreen && !tempClients[0]->ismaximizescreen) { - c = tempClients[0]; - - single_proportion = c->scroller_proportion_single > 0.0f - ? c->scroller_proportion_single - : config.scroller_default_proportion_single; - - target_geom.height = m->w.height - 2 * cur_gappov; - target_geom.width = (m->w.width - 2 * cur_gappoh) * single_proportion; - 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; - horizontal_check_scroller_root_inside_mon(c, &target_geom); - arrange_stack(c, target_geom, cur_gappiv); - free(tempClients); // 释放内存 - return; - } - - if (m->sel && !client_is_unmanaged(m->sel) && ISSCROLLTILED(m->sel)) { - root_client = m->sel; - } 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); - } - - // root_client might be in a stack, find the stack head - if (root_client) { - root_client = get_scroll_stack_head(root_client); - } - - if (!root_client) { - free(tempClients); // 释放内存 - return; - } - - for (i = 0; i < n; i++) { - c = tempClients[i]; - if (root_client == c) { - if (c->geom.x >= m->w.x + config.scroller_structs && - c->geom.x + c->geom.width <= - m->w.x + m->w.width - config.scroller_structs) { - need_scroller = false; - } else { - need_scroller = true; - } - focus_client_index = i; - break; - } - } - - bool need_apply_overspread = - config.scroller_prefer_overspread && - m->visible_scroll_tiling_clients > 1 && - (focus_client_index == 0 || focus_client_index == n - 1) && - tempClients[focus_client_index]->scroller_proportion < 1.0f; - - if (need_apply_overspread) { - - if (focus_client_index == 0) { - over_overspread_to_left = true; - } else { - over_overspread_to_left = false; - } - - if (over_overspread_to_left && - (!INSIDEMON(tempClients[1]) || - (tempClients[1]->scroller_proportion + - tempClients[focus_client_index]->scroller_proportion >= - 1.0f))) { - need_scroller = true; - } else if (!over_overspread_to_left && - (!INSIDEMON(tempClients[focus_client_index - 1]) || - (tempClients[focus_client_index - 1]->scroller_proportion + - tempClients[focus_client_index]->scroller_proportion >= - 1.0f))) { - need_scroller = true; - } else { - need_apply_overspread = false; - } - } - - bool need_apply_center = - config.scroller_focus_center || m->visible_scroll_tiling_clients == 1 || - (config.scroller_prefer_center && !need_apply_overspread && - (!m->prevsel || - (ISSCROLLTILED(m->prevsel) && - (m->prevsel->scroller_proportion * max_client_width) + - (tempClients[focus_client_index]->scroller_proportion * - max_client_width) > - m->w.width - 2 * config.scroller_structs - cur_gappih))); - - if (n == 1 && config.scroller_ignore_proportion_single) { - need_scroller = true; - } - - if (start_drag_window) - need_scroller = false; - - 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; - horizontal_scroll_adjust_fullandmax(tempClients[focus_client_index], - &target_geom); - if (tempClients[focus_client_index]->isfullscreen) { - target_geom.x = m->m.x; - horizontal_check_scroller_root_inside_mon( - tempClients[focus_client_index], &target_geom); - arrange_stack(tempClients[focus_client_index], target_geom, cur_gappiv); - } else if (tempClients[focus_client_index]->ismaximizescreen) { - target_geom.x = m->w.x + cur_gappoh; - horizontal_check_scroller_root_inside_mon( - tempClients[focus_client_index], &target_geom); - arrange_stack(tempClients[focus_client_index], target_geom, cur_gappiv); - } else if (need_scroller) { - if (need_apply_center) { - target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2; - } else if (need_apply_overspread) { - if (over_overspread_to_left) { - target_geom.x = m->w.x + config.scroller_structs; - } else { - target_geom.x = - m->w.x + - (m->w.width - - tempClients[focus_client_index]->scroller_proportion * - max_client_width - - config.scroller_structs); - } - - } else { - target_geom.x = tempClients[focus_client_index]->geom.x > - m->w.x + (m->w.width) / 2 - ? m->w.x + (m->w.width - - tempClients[focus_client_index] - ->scroller_proportion * - max_client_width - - config.scroller_structs) - : m->w.x + config.scroller_structs; - } - horizontal_check_scroller_root_inside_mon( - tempClients[focus_client_index], &target_geom); - arrange_stack(tempClients[focus_client_index], target_geom, cur_gappiv); - } else { - target_geom.x = c->geom.x; - horizontal_check_scroller_root_inside_mon( - tempClients[focus_client_index], &target_geom); - arrange_stack(tempClients[focus_client_index], target_geom, cur_gappiv); - } - - for (i = 1; i <= focus_client_index; i++) { - 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; - - arrange_stack(c, target_geom, cur_gappiv); - } - - 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; - arrange_stack(c, target_geom, cur_gappiv); - } - - free(tempClients); // 最后释放内存 -} - void center_tile(Monitor *m) { int32_t i, n = 0, h, r, ie = enablegaps, mw, mx, my, oty, ety, tw; Client *c = NULL; diff --git a/src/layout/scroll.h b/src/layout/scroll.h new file mode 100644 index 00000000..84e84e2c --- /dev/null +++ b/src/layout/scroll.h @@ -0,0 +1,888 @@ +/* 获取或创建指定 monitor 某个 tag 的 scroller 状态 */ +static struct TagScrollerState *ensure_scroller_state(Monitor *m, + uint32_t tag) { + if (!m->pertag->scroller_state[tag]) { + struct TagScrollerState *st = + calloc(1, sizeof(struct TagScrollerState)); + m->pertag->scroller_state[tag] = st; + } + return m->pertag->scroller_state[tag]; +} + +/* 在 tag 状态中查找客户端对应的节点(无则返回 NULL) */ +static struct ScrollerStackNode *find_scroller_node(struct TagScrollerState *st, + Client *c) { + if (!st) + return NULL; + for (struct ScrollerStackNode *n = st->all_first; n; n = n->all_next) + if (n->client == c) + return n; + return NULL; +} + +/* 创建一个新节点并插入到 tag 状态的 all 链表中 */ +static struct ScrollerStackNode * +scroller_node_create(struct TagScrollerState *st, Client *c) { + struct ScrollerStackNode *n = calloc(1, sizeof(*n)); + n->client = c; + n->scroller_proportion = c->scroller_proportion; + n->stack_proportion = c->stack_proportion; + n->scroller_proportion_single = c->scroller_proportion_single; + n->next_in_stack = NULL; + n->prev_in_stack = NULL; + n->all_next = st->all_first; + st->all_first = n; + st->count++; + return n; +} + +/* 从 tag 状态中移除一个节点并释放 */ +static void scroller_node_remove(struct TagScrollerState *st, + struct ScrollerStackNode *target) { + if (!st || !target) + return; + + /* 保存邻居 */ + struct ScrollerStackNode *prev = target->prev_in_stack; + struct ScrollerStackNode *next = target->next_in_stack; + + /* 从堆叠链表中摘除 */ + if (prev) + prev->next_in_stack = next; + if (next) + next->prev_in_stack = prev; + + /* 从 all 链表摘除 */ + struct ScrollerStackNode **indirect = &st->all_first; + while (*indirect && *indirect != target) + indirect = &(*indirect)->all_next; + if (*indirect == target) { + *indirect = target->all_next; + st->count--; + } + free(target); +} + +/* 清空一个 tag 的全部 scroller 状态 */ +static void clear_scroller_state(struct TagScrollerState *st) { + if (!st) + return; + struct ScrollerStackNode *n = st->all_first; + while (n) { + struct ScrollerStackNode *next = n->all_next; + free(n); + n = next; + } + free(st); +} + +/* 在 Monitor 销毁时清理所有 tag 的 scroller 状态 */ +static void cleanup_monitor_scroller(Monitor *m) { + for (int t = 0; t < LENGTH(tags) + 1; t++) { + if (m->pertag->scroller_state[t]) { + clear_scroller_state(m->pertag->scroller_state[t]); + m->pertag->scroller_state[t] = NULL; + } + } +} + +/* 将某个 tag 的状态同步回所有客户端的全局字段 */ +static void sync_scroller_state_to_clients(Monitor *m, uint32_t tag) { + struct TagScrollerState *st = m->pertag->scroller_state[tag]; + if (!st) + return; + for (struct ScrollerStackNode *n = st->all_first; n; n = n->all_next) { + Client *c = n->client; + c->scroller_proportion = n->scroller_proportion; + c->stack_proportion = n->stack_proportion; + c->scroller_proportion_single = n->scroller_proportion_single; + } +} + +void vertical_scroll_adjust_fullandmax(Client *c, struct wlr_box *target_geom) { + Monitor *m = c->mon; + int32_t cur_gappiv = enablegaps ? m->gappiv : 0; + int32_t cur_gappov = enablegaps ? m->gappov : 0; + int32_t cur_gappoh = enablegaps ? m->gappoh : 0; + + cur_gappiv = config.smartgaps && m->visible_scroll_tiling_clients == 1 + ? 0 + : cur_gappiv; + cur_gappov = config.smartgaps && m->visible_scroll_tiling_clients == 1 + ? 0 + : cur_gappov; + cur_gappoh = config.smartgaps && m->visible_scroll_tiling_clients == 1 + ? 0 + : cur_gappoh; + + if (c->isfullscreen) { + target_geom->width = m->m.width; + 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_check_scroller_root_inside_mon(Client *c, + struct wlr_box *geometry) { + if (!GEOMINSIDEMON(geometry, c->mon)) { + geometry->y = c->mon->w.y + (c->mon->w.height - geometry->height) / 2; + } +} + +void horizontal_scroll_adjust_fullandmax(Client *c, + struct wlr_box *target_geom) { + Monitor *m = c->mon; + int32_t cur_gappih = enablegaps ? m->gappih : 0; + int32_t cur_gappoh = enablegaps ? m->gappoh : 0; + int32_t cur_gappov = enablegaps ? m->gappov : 0; + + cur_gappih = config.smartgaps && m->visible_scroll_tiling_clients == 1 + ? 0 + : cur_gappih; + cur_gappoh = config.smartgaps && m->visible_scroll_tiling_clients == 1 + ? 0 + : cur_gappoh; + cur_gappov = config.smartgaps && m->visible_scroll_tiling_clients == 1 + ? 0 + : cur_gappov; + + if (c->isfullscreen) { + target_geom->height = m->m.height; + 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 horizontal_check_scroller_root_inside_mon(Client *c, + struct wlr_box *geometry) { + if (!GEOMINSIDEMON(geometry, c->mon)) { + geometry->x = c->mon->w.x + (c->mon->w.width - geometry->width) / 2; + } +} + +void arrange_stack_node(struct ScrollerStackNode *head, struct wlr_box geometry, + int32_t gappiv) { + int32_t stack_size = 0; + struct ScrollerStackNode *iter = head; + while (iter) { + stack_size++; + iter = iter->next_in_stack; + } + if (stack_size == 0) + return; + + /* 归一化比例 */ + float total_proportion = 0.0f; + iter = head; + while (iter) { + if (iter->stack_proportion <= 0.0f || iter->stack_proportion >= 1.0f) + iter->stack_proportion = + stack_size == 1 ? 1.0f : 1.0f / (stack_size - 1); + total_proportion += iter->stack_proportion; + iter = iter->next_in_stack; + } + iter = head; + while (iter) { + iter->stack_proportion /= total_proportion; + iter = iter->next_in_stack; + } + + /* 竖向排列(水平堆叠) */ + int32_t client_height; + int32_t current_y = geometry.y; + int32_t remain_client_height = geometry.height - (stack_size - 1) * gappiv; + float remain_proportion = 1.0f; + + iter = head; + while (iter) { + client_height = + remain_client_height * (iter->stack_proportion / remain_proportion); + struct wlr_box client_geom = {.x = geometry.x, + .y = current_y, + .width = geometry.width, + .height = client_height}; + resize(iter->client, client_geom, 0); + remain_proportion -= iter->stack_proportion; + remain_client_height -= client_height; + current_y += client_height + gappiv; + iter = iter->next_in_stack; + } +} + +void arrange_stack_vertical_node(struct ScrollerStackNode *head, + struct wlr_box geometry, int32_t gappih) { + int32_t stack_size = 0; + struct ScrollerStackNode *iter = head; + while (iter) { + stack_size++; + iter = iter->next_in_stack; + } + if (stack_size == 0) + return; + + /* 归一化比例 */ + float total_proportion = 0.0f; + iter = head; + while (iter) { + if (iter->stack_proportion <= 0.0f || iter->stack_proportion >= 1.0f) + iter->stack_proportion = + stack_size == 1 ? 1.0f : 1.0f / (stack_size - 1); + total_proportion += iter->stack_proportion; + iter = iter->next_in_stack; + } + iter = head; + while (iter) { + iter->stack_proportion /= total_proportion; + iter = iter->next_in_stack; + } + + /* 横向排列(垂直堆叠) */ + int32_t client_width; + int32_t current_x = geometry.x; + int32_t remain_client_width = geometry.width - (stack_size - 1) * gappih; + float remain_proportion = 1.0f; + + iter = head; + while (iter) { + client_width = + remain_client_width * (iter->stack_proportion / remain_proportion); + struct wlr_box client_geom = {.y = geometry.y, + .x = current_x, + .height = geometry.height, + .width = client_width}; + resize(iter->client, client_geom, 0); + remain_proportion -= iter->stack_proportion; + remain_client_width -= client_width; + current_x += client_width + gappih; + iter = iter->next_in_stack; + } +} + +void scroller(Monitor *m) { + uint32_t tag = m->pertag->curtag; + struct TagScrollerState *st = ensure_scroller_state(m, tag); + Client *c = NULL; + + /* 按全局客户端链表顺序收集所有堆叠头,确保视觉顺序正确 */ + struct ScrollerStackNode *heads[64]; + int32_t n_heads = 0; + wl_list_for_each(c, &clients, link) { + if (VISIBLEON(c, m) && ISSCROLLTILED(c)) { + struct ScrollerStackNode *node = find_scroller_node(st, c); + if (node && !node->prev_in_stack) { + bool already = false; + for (int k = 0; k < n_heads; k++) { + if (heads[k] == node) { + already = true; + break; + } + } + if (!already) + heads[n_heads++] = node; + } + } + } + + if (n_heads == 0) { + sync_scroller_state_to_clients(m, tag); + return; + } + + m->visible_scroll_tiling_clients = n_heads; + + int32_t cur_gappih = enablegaps ? m->gappih : 0; + int32_t cur_gappoh = enablegaps ? m->gappoh : 0; + int32_t cur_gappov = enablegaps ? m->gappov : 0; + int32_t cur_gappiv = enablegaps ? m->gappiv : 0; + if (config.smartgaps && n_heads == 1) { + cur_gappih = cur_gappoh = cur_gappov = 0; + } + int32_t max_client_width = + m->w.width - 2 * config.scroller_structs - cur_gappih; + + /* 单客户端特例 */ + if (n_heads == 1 && !config.scroller_ignore_proportion_single && + !heads[0]->client->isfullscreen && + !heads[0]->client->ismaximizescreen) { + struct ScrollerStackNode *head = heads[0]; + float single_proportion = + head->scroller_proportion_single > 0.0f + ? head->scroller_proportion_single + : config.scroller_default_proportion_single; + struct wlr_box target_geom; + target_geom.height = m->w.height - 2 * cur_gappov; + 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; + horizontal_check_scroller_root_inside_mon(head->client, &target_geom); + arrange_stack_node(head, target_geom, cur_gappiv); + sync_scroller_state_to_clients(m, tag); + return; + } + + struct ScrollerStackNode *root_node = NULL; + if (m->sel && ISSCROLLTILED(m->sel)) { + root_node = find_scroller_node(st, m->sel); + if (root_node) { + while (root_node->prev_in_stack) + root_node = root_node->prev_in_stack; + } + } + if (!root_node && m->prevsel && ISSCROLLTILED(m->prevsel)) { + root_node = find_scroller_node(st, m->prevsel); + if (root_node) { + while (root_node->prev_in_stack) + root_node = root_node->prev_in_stack; + } + } + if (!root_node) + root_node = heads[n_heads / 2]; /* 简单回退 */ + + int32_t focus_index = -1; + for (int i = 0; i < n_heads; i++) { + if (heads[i] == root_node) { + focus_index = i; + break; + } + } + if (focus_index < 0) + focus_index = n_heads / 2; + + /* 判断是否需要滚动、overspread、center */ + bool need_scroller = false; + bool over_overspread_to_left = false; + Client *root_client = root_node->client; + + if (root_client->geom.x >= m->w.x + config.scroller_structs && + root_client->geom.x + root_client->geom.width <= + m->w.x + m->w.width - config.scroller_structs) { + need_scroller = false; + } else { + need_scroller = true; + } + + bool need_apply_overspread = + config.scroller_prefer_overspread && n_heads > 1 && + (focus_index == 0 || focus_index == n_heads - 1) && + heads[focus_index]->scroller_proportion < 1.0f; + + if (need_apply_overspread) { + if (focus_index == 0) { + over_overspread_to_left = true; + } else { + over_overspread_to_left = false; + } + if (over_overspread_to_left && + (!INSIDEMON(heads[1]->client) || + (heads[1]->scroller_proportion + heads[0]->scroller_proportion >= + 1.0f))) { + need_scroller = true; + } else if (!over_overspread_to_left && + (!INSIDEMON(heads[focus_index - 1]->client) || + (heads[focus_index - 1]->scroller_proportion + + heads[focus_index]->scroller_proportion >= + 1.0f))) { + need_scroller = true; + } else { + need_apply_overspread = false; + } + } + + bool need_apply_center = + config.scroller_focus_center || n_heads == 1 || + (config.scroller_prefer_center && !need_apply_overspread && + (!m->prevsel || + (ISSCROLLTILED(m->prevsel) && + (m->prevsel->scroller_proportion * max_client_width) + + (heads[focus_index]->scroller_proportion * + max_client_width) > + m->w.width - 2 * config.scroller_structs - cur_gappih))); + + if (n_heads == 1 && config.scroller_ignore_proportion_single) { + need_scroller = true; + } + if (start_drag_window) + need_scroller = false; + + struct wlr_box target_geom; + target_geom.height = m->w.height - 2 * cur_gappov; + target_geom.width = + max_client_width * heads[focus_index]->scroller_proportion; + target_geom.y = m->w.y + (m->w.height - target_geom.height) / 2; + horizontal_scroll_adjust_fullandmax(heads[focus_index]->client, + &target_geom); + + if (heads[focus_index]->client->isfullscreen) { + target_geom.x = m->m.x; + horizontal_check_scroller_root_inside_mon(heads[focus_index]->client, + &target_geom); + arrange_stack_node(heads[focus_index], target_geom, cur_gappiv); + } else if (heads[focus_index]->client->ismaximizescreen) { + target_geom.x = m->w.x + cur_gappoh; + horizontal_check_scroller_root_inside_mon(heads[focus_index]->client, + &target_geom); + arrange_stack_node(heads[focus_index], target_geom, cur_gappiv); + } else if (need_scroller) { + if (need_apply_center) { + target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2; + } else if (need_apply_overspread) { + if (over_overspread_to_left) { + target_geom.x = m->w.x + config.scroller_structs; + } else { + target_geom.x = + m->w.x + (m->w.width - + heads[focus_index]->scroller_proportion * + max_client_width - + config.scroller_structs); + } + } else { + target_geom.x = + root_client->geom.x > m->w.x + (m->w.width) / 2 + ? m->w.x + (m->w.width - + heads[focus_index]->scroller_proportion * + max_client_width - + config.scroller_structs) + : m->w.x + config.scroller_structs; + } + horizontal_check_scroller_root_inside_mon(heads[focus_index]->client, + &target_geom); + arrange_stack_node(heads[focus_index], target_geom, cur_gappiv); + } else { + target_geom.x = root_client->geom.x; + horizontal_check_scroller_root_inside_mon(heads[focus_index]->client, + &target_geom); + arrange_stack_node(heads[focus_index], target_geom, cur_gappiv); + } + + /* 排列左侧的堆叠 */ + for (int i = 1; i <= focus_index; i++) { + struct ScrollerStackNode *cur = heads[focus_index - i]; + struct wlr_box left_geom; + left_geom.height = m->w.height - 2 * cur_gappov; + left_geom.width = max_client_width * cur->scroller_proportion; + horizontal_scroll_adjust_fullandmax(cur->client, &left_geom); + left_geom.x = heads[focus_index - i + 1]->client->geom.x - cur_gappih - + left_geom.width; + arrange_stack_node(cur, left_geom, cur_gappiv); + } + + /* 排列右侧的堆叠 */ + for (int i = 1; i < n_heads - focus_index; i++) { + struct ScrollerStackNode *cur = heads[focus_index + i]; + struct wlr_box right_geom; + right_geom.height = m->w.height - 2 * cur_gappov; + right_geom.width = max_client_width * cur->scroller_proportion; + horizontal_scroll_adjust_fullandmax(cur->client, &right_geom); + right_geom.x = heads[focus_index + i - 1]->client->geom.x + cur_gappih + + heads[focus_index + i - 1]->client->geom.width; + arrange_stack_node(cur, right_geom, cur_gappiv); + } + + sync_scroller_state_to_clients(m, tag); +} + +void vertical_scroller(Monitor *m) { + uint32_t tag = m->pertag->curtag; + struct TagScrollerState *st = ensure_scroller_state(m, tag); + Client *c = NULL; + + /* 按全局顺序收集堆叠头 */ + struct ScrollerStackNode *heads[64]; + int32_t n_heads = 0; + wl_list_for_each(c, &clients, link) { + if (VISIBLEON(c, m) && ISSCROLLTILED(c)) { + struct ScrollerStackNode *node = find_scroller_node(st, c); + if (node && !node->prev_in_stack) { + bool already = false; + for (int k = 0; k < n_heads; k++) + if (heads[k] == node) + already = true; + if (!already) + heads[n_heads++] = node; + } + } + } + + if (n_heads == 0) { + sync_scroller_state_to_clients(m, tag); + return; + } + + m->visible_scroll_tiling_clients = n_heads; + + int32_t cur_gappiv = enablegaps ? m->gappiv : 0; + int32_t cur_gappov = enablegaps ? m->gappov : 0; + int32_t cur_gappoh = enablegaps ? m->gappoh : 0; + int32_t cur_gappih = enablegaps ? m->gappih : 0; + if (config.smartgaps && n_heads == 1) { + cur_gappiv = cur_gappov = cur_gappoh = 0; + } + int32_t max_client_height = + m->w.height - 2 * config.scroller_structs - cur_gappiv; + + if (n_heads == 1 && !config.scroller_ignore_proportion_single && + !heads[0]->client->isfullscreen && + !heads[0]->client->ismaximizescreen) { + struct ScrollerStackNode *head = heads[0]; + float single_proportion = + head->scroller_proportion_single > 0.0f + ? head->scroller_proportion_single + : config.scroller_default_proportion_single; + struct wlr_box target_geom; + target_geom.width = m->w.width - 2 * cur_gappoh; + target_geom.height = (m->w.height - 2 * cur_gappov) * single_proportion; + target_geom.y = m->w.y + (m->w.height - target_geom.height) / 2; + target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2; + vertical_check_scroller_root_inside_mon(head->client, &target_geom); + arrange_stack_vertical_node(head, target_geom, cur_gappih); + sync_scroller_state_to_clients(m, tag); + return; + } + + struct ScrollerStackNode *root_node = NULL; + if (m->sel && ISSCROLLTILED(m->sel)) { + root_node = find_scroller_node(st, m->sel); + if (root_node) { + while (root_node->prev_in_stack) + root_node = root_node->prev_in_stack; + } + } + if (!root_node && m->prevsel && ISSCROLLTILED(m->prevsel)) { + root_node = find_scroller_node(st, m->prevsel); + if (root_node) { + while (root_node->prev_in_stack) + root_node = root_node->prev_in_stack; + } + } + if (!root_node) + root_node = heads[n_heads / 2]; + + int32_t focus_index = -1; + for (int i = 0; i < n_heads; i++) { + if (heads[i] == root_node) { + focus_index = i; + break; + } + } + if (focus_index < 0) + focus_index = n_heads / 2; + + bool need_scroller = false; + bool over_overspread_to_up = false; + Client *root_client = root_node->client; + + if (root_client->geom.y >= m->w.y + config.scroller_structs && + root_client->geom.y + root_client->geom.height <= + m->w.y + m->w.height - config.scroller_structs) { + need_scroller = false; + } else { + need_scroller = true; + } + + bool need_apply_overspread = + config.scroller_prefer_overspread && n_heads > 1 && + (focus_index == 0 || focus_index == n_heads - 1) && + heads[focus_index]->scroller_proportion < 1.0f; + + if (need_apply_overspread) { + if (focus_index == 0) { + over_overspread_to_up = true; + } else { + over_overspread_to_up = false; + } + if (over_overspread_to_up && + (!INSIDEMON(heads[1]->client) || + (heads[1]->scroller_proportion + heads[0]->scroller_proportion >= + 1.0f))) { + need_scroller = true; + } else if (!over_overspread_to_up && + (!INSIDEMON(heads[focus_index - 1]->client) || + (heads[focus_index - 1]->scroller_proportion + + heads[focus_index]->scroller_proportion >= + 1.0f))) { + need_scroller = true; + } else { + need_apply_overspread = false; + } + } + + bool need_apply_center = + config.scroller_focus_center || n_heads == 1 || + (config.scroller_prefer_center && !need_apply_overspread && + (!m->prevsel || + (ISSCROLLTILED(m->prevsel) && + (m->prevsel->scroller_proportion * max_client_height) + + (heads[focus_index]->scroller_proportion * + max_client_height) > + m->w.height - 2 * config.scroller_structs - cur_gappiv))); + + if (n_heads == 1 && config.scroller_ignore_proportion_single) { + need_scroller = true; + } + if (start_drag_window) + need_scroller = false; + + struct wlr_box target_geom; + target_geom.width = m->w.width - 2 * cur_gappoh; + target_geom.height = + max_client_height * heads[focus_index]->scroller_proportion; + target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2; + vertical_scroll_adjust_fullandmax(heads[focus_index]->client, &target_geom); + + if (heads[focus_index]->client->isfullscreen) { + target_geom.y = m->m.y; + vertical_check_scroller_root_inside_mon(heads[focus_index]->client, + &target_geom); + arrange_stack_vertical_node(heads[focus_index], target_geom, + cur_gappih); + } else if (heads[focus_index]->client->ismaximizescreen) { + target_geom.y = m->w.y + cur_gappov; + vertical_check_scroller_root_inside_mon(heads[focus_index]->client, + &target_geom); + arrange_stack_vertical_node(heads[focus_index], target_geom, + cur_gappih); + } else if (need_scroller) { + if (need_apply_center) { + target_geom.y = m->w.y + (m->w.height - target_geom.height) / 2; + } else if (need_apply_overspread) { + if (over_overspread_to_up) { + target_geom.y = m->w.y + config.scroller_structs; + } else { + target_geom.y = + m->w.y + (m->w.height - + heads[focus_index]->scroller_proportion * + max_client_height - + config.scroller_structs); + } + } else { + target_geom.y = + root_client->geom.y > m->w.y + (m->w.height) / 2 + ? m->w.y + (m->w.height - + heads[focus_index]->scroller_proportion * + max_client_height - + config.scroller_structs) + : m->w.y + config.scroller_structs; + } + vertical_check_scroller_root_inside_mon(heads[focus_index]->client, + &target_geom); + arrange_stack_vertical_node(heads[focus_index], target_geom, + cur_gappih); + } else { + target_geom.y = root_client->geom.y; + vertical_check_scroller_root_inside_mon(heads[focus_index]->client, + &target_geom); + arrange_stack_vertical_node(heads[focus_index], target_geom, + cur_gappih); + } + + for (int i = 1; i <= focus_index; i++) { + struct ScrollerStackNode *cur = heads[focus_index - i]; + struct wlr_box up_geom; + up_geom.width = m->w.width - 2 * cur_gappoh; + up_geom.height = max_client_height * cur->scroller_proportion; + vertical_scroll_adjust_fullandmax(cur->client, &up_geom); + up_geom.y = heads[focus_index - i + 1]->client->geom.y - cur_gappiv - + up_geom.height; + arrange_stack_vertical_node(cur, up_geom, cur_gappih); + } + + for (int i = 1; i < n_heads - focus_index; i++) { + struct ScrollerStackNode *cur = heads[focus_index + i]; + struct wlr_box down_geom; + down_geom.width = m->w.width - 2 * cur_gappoh; + down_geom.height = max_client_height * cur->scroller_proportion; + vertical_scroll_adjust_fullandmax(cur->client, &down_geom); + down_geom.y = heads[focus_index + i - 1]->client->geom.y + cur_gappiv + + heads[focus_index + i - 1]->client->geom.height; + arrange_stack_vertical_node(cur, down_geom, cur_gappih); + } + + sync_scroller_state_to_clients(m, tag); +} + +void scroller_remove_client(Client *c) { + Monitor *m; + wl_list_for_each(m, &mons, link) { + for (uint32_t t = 0; t < LENGTH(tags) + 1; t++) { + struct TagScrollerState *st = m->pertag->scroller_state[t]; + if (!st) + continue; + struct ScrollerStackNode *node = find_scroller_node(st, c); + if (node) { + scroller_node_remove(st, node); + } + } + } +} + +void scroller_insert_stack(Client *c, Client *target_client, + bool insert_before) { + if (!target_client || target_client->mon != c->mon) + return; + + if (c->isfullscreen) + setfullscreen(c, 0); + if (c->ismaximizescreen) + setmaximizescreen(c, 0); + + Monitor *m = c->mon; + uint32_t tag = m->pertag->curtag; + struct TagScrollerState *st = ensure_scroller_state(m, tag); + + struct ScrollerStackNode *cnode = find_scroller_node(st, c); + if (cnode) + scroller_node_remove(st, cnode); + + struct ScrollerStackNode *tnode = find_scroller_node(st, target_client); + if (!tnode) + tnode = scroller_node_create(st, target_client); + + struct ScrollerStackNode *newnode = scroller_node_create(st, c); + /* 将新节点插入到 tnode 的前面或后面 */ + if (insert_before) { + newnode->next_in_stack = tnode; + newnode->prev_in_stack = tnode->prev_in_stack; + if (tnode->prev_in_stack) + tnode->prev_in_stack->next_in_stack = newnode; + tnode->prev_in_stack = newnode; + } else { + newnode->prev_in_stack = tnode; + newnode->next_in_stack = tnode->next_in_stack; + if (tnode->next_in_stack) + tnode->next_in_stack->prev_in_stack = newnode; + tnode->next_in_stack = newnode; + } + + /* 处理堆叠头部的全屏/最大化状态*/ + struct ScrollerStackNode *head = tnode; + while (head->prev_in_stack) + head = head->prev_in_stack; + Client *stack_head = head->client; + if (stack_head->ismaximizescreen) + setmaximizescreen(stack_head, 0); + if (stack_head->isfullscreen) + setfullscreen(stack_head, 0); + + /* 同步到 Client 字段 */ + sync_scroller_state_to_clients(m, tag); + + arrange(m, false, false); +} + +void scroller_drop_tile(Client *c, Client *closest, int vertical) { + + Client *stack_head = scroll_get_stack_head_client(closest); + + if (vertical) { + if (closest->drop_direction == LEFT) { + setfloating(c, 0); + scroller_insert_stack(c, closest, true); + return; + } else if (closest->drop_direction == RIGHT) { + setfloating(c, 0); + scroller_insert_stack(c, closest, false); + return; + } else if (closest->drop_direction == UP) { + wl_list_remove(&c->link); + wl_list_insert(stack_head->link.prev, &c->link); + } else if (closest->drop_direction == DOWN) { + wl_list_remove(&c->link); + wl_list_insert(&stack_head->link, &c->link); + } + } else { + if (closest->drop_direction == UP) { + setfloating(c, 0); + scroller_insert_stack(c, closest, true); + return; + } else if (closest->drop_direction == DOWN) { + setfloating(c, 0); + scroller_insert_stack(c, closest, false); + return; + } else if (closest->drop_direction == LEFT) { + wl_list_remove(&c->link); + wl_list_insert(stack_head->link.prev, &c->link); + } else if (closest->drop_direction == RIGHT) { + wl_list_remove(&c->link); + wl_list_insert(&stack_head->link, &c->link); + } + } + + setfloating(c, 0); +} + +Client *scroll_get_stack_head_client(Client *c) { + if (!c || !c->mon) + return c; + uint32_t tag = c->mon->pertag->curtag; + struct TagScrollerState *st = c->mon->pertag->scroller_state[tag]; + if (st) { + struct ScrollerStackNode *n = find_scroller_node(st, c); + if (n) { + while (n->prev_in_stack) + n = n->prev_in_stack; + return n->client; + } + } + return c; +} + +static void update_scroller_state(Monitor *m) { + uint32_t tag = m->pertag->curtag; + struct TagScrollerState *st = ensure_scroller_state(m, tag); + + /* 收集当前可见的所有 scroller 平铺窗口 */ + Client *vis[512]; + int32_t count = 0; + Client *c; + wl_list_for_each(c, &clients, link) { + if (VISIBLEON(c, m) && ISSCROLLTILED(c)) + vis[count++] = c; + if (count == 512) + break; + } + + /* 移除不再可见的节点 */ + struct ScrollerStackNode *n = st->all_first; + while (n) { + bool found = false; + for (int i = 0; i < count; i++) { + if (vis[i] == n->client) { + found = true; + break; + } + } + struct ScrollerStackNode *next = n->all_next; + if (!found) + scroller_node_remove(st, n); + n = next; + } + + /* 为新的可见窗口创建节点 */ + for (int i = 0; i < count; i++) { + if (!find_scroller_node(st, vis[i])) { + scroller_node_create(st, vis[i]); + } + } +} \ No newline at end of file diff --git a/src/layout/vertical.h b/src/layout/vertical.h index 3d8863af..36114dde 100644 --- a/src/layout/vertical.h +++ b/src/layout/vertical.h @@ -176,324 +176,6 @@ void vertical_deck(Monitor *m) { } } -void vertical_scroll_adjust_fullandmax(Client *c, struct wlr_box *target_geom) { - Monitor *m = c->mon; - int32_t cur_gappiv = enablegaps ? m->gappiv : 0; - int32_t cur_gappov = enablegaps ? m->gappov : 0; - int32_t cur_gappoh = enablegaps ? m->gappoh : 0; - - cur_gappiv = config.smartgaps && m->visible_scroll_tiling_clients == 1 - ? 0 - : cur_gappiv; - cur_gappov = config.smartgaps && m->visible_scroll_tiling_clients == 1 - ? 0 - : cur_gappov; - cur_gappoh = config.smartgaps && m->visible_scroll_tiling_clients == 1 - ? 0 - : cur_gappoh; - - if (c->isfullscreen) { - target_geom->width = m->m.width; - 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 arrange_stack_vertical(Client *scroller_stack_head, - struct wlr_box geometry, int32_t gappih) { - int32_t stack_size = 0; - Client *iter = scroller_stack_head; - - while (iter) { - stack_size++; - iter = iter->next_in_stack; - } - - if (stack_size == 0) - return; - - float total_proportion = 0.0f; - iter = scroller_stack_head; - while (iter) { - if (iter->stack_proportion <= 0.0f || iter->stack_proportion >= 1.0f) { - iter->stack_proportion = - stack_size == 1 ? 1.0f : 1.0f / (stack_size - 1); - } - total_proportion += iter->stack_proportion; - iter = iter->next_in_stack; - } - - iter = scroller_stack_head; - while (iter) { - iter->stack_proportion = iter->stack_proportion / total_proportion; - iter = iter->next_in_stack; - } - - int32_t client_width; - int32_t current_x = geometry.x; - int32_t remain_client_width = geometry.width - (stack_size - 1) * gappih; - float remain_proportion = 1.0f; - - iter = scroller_stack_head; - while (iter) { - - client_width = - remain_client_width * (iter->stack_proportion / remain_proportion); - - struct wlr_box client_geom = {.y = geometry.y, - .x = current_x, - .height = geometry.height, - .width = client_width}; - resize(iter, client_geom, 0); - remain_proportion -= iter->stack_proportion; - remain_client_width -= client_width; - current_x += client_width + gappih; - iter = iter->next_in_stack; - } -} - -void vertical_check_scroller_root_inside_mon(Client *c, - struct wlr_box *geometry) { - if (!GEOMINSIDEMON(geometry, c->mon)) { - geometry->y = c->mon->w.y + (c->mon->w.height - geometry->height) / 2; - } -} - -// 竖屏滚动布局 -void vertical_scroller(Monitor *m) { - int32_t i, n, j; - float single_proportion = 1.0; - - Client *c = NULL, *root_client = NULL; - Client **tempClients = NULL; - struct wlr_box target_geom; - int32_t focus_client_index = 0; - bool need_scroller = false; - bool over_overspread_to_up = false; - int32_t cur_gappiv = enablegaps ? m->gappiv : 0; - int32_t cur_gappov = enablegaps ? m->gappov : 0; - int32_t cur_gappoh = enablegaps ? m->gappoh : 0; - int32_t cur_gappih = enablegaps ? m->gappih : 0; - - cur_gappiv = config.smartgaps && m->visible_scroll_tiling_clients == 1 - ? 0 - : cur_gappiv; - cur_gappov = config.smartgaps && m->visible_scroll_tiling_clients == 1 - ? 0 - : cur_gappov; - cur_gappoh = config.smartgaps && m->visible_scroll_tiling_clients == 1 - ? 0 - : cur_gappoh; - - int32_t max_client_height = - m->w.height - 2 * config.scroller_structs - cur_gappiv; - - n = m->visible_scroll_tiling_clients; - - if (n == 0) { - return; - } - - tempClients = malloc(n * sizeof(Client *)); - if (!tempClients) { - return; - } - - j = 0; - wl_list_for_each(c, &clients, link) { - if (VISIBLEON(c, m) && ISSCROLLTILED(c) && !c->prev_in_stack) { - tempClients[j] = c; - j++; - } - } - - if (n == 1 && !config.scroller_ignore_proportion_single && - !tempClients[0]->isfullscreen && !tempClients[0]->ismaximizescreen) { - c = tempClients[0]; - - single_proportion = c->scroller_proportion_single > 0.0f - ? c->scroller_proportion_single - : config.scroller_default_proportion_single; - - target_geom.width = m->w.width - 2 * cur_gappoh; - target_geom.height = (m->w.height - 2 * cur_gappov) * single_proportion; - 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; - vertical_check_scroller_root_inside_mon(c, &target_geom); - arrange_stack_vertical(c, target_geom, cur_gappih); - free(tempClients); - return; - } - - if (m->sel && !client_is_unmanaged(m->sel) && ISSCROLLTILED(m->sel)) { - root_client = m->sel; - } 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); - } - - // root_client might be in a stack, find the stack head - if (root_client) { - root_client = get_scroll_stack_head(root_client); - } - - if (!root_client) { - free(tempClients); - return; - } - - for (i = 0; i < n; i++) { - c = tempClients[i]; - if (root_client == c) { - if (c->geom.y >= m->w.y + config.scroller_structs && - c->geom.y + c->geom.height <= - m->w.y + m->w.height - config.scroller_structs) { - need_scroller = false; - } else { - need_scroller = true; - } - focus_client_index = i; - break; - } - } - - bool need_apply_overspread = - config.scroller_prefer_overspread && - m->visible_scroll_tiling_clients > 1 && - (focus_client_index == 0 || focus_client_index == n - 1) && - tempClients[focus_client_index]->scroller_proportion < 1.0f; - - if (need_apply_overspread) { - - if (focus_client_index == 0) { - over_overspread_to_up = true; - } else { - over_overspread_to_up = false; - } - - if (over_overspread_to_up && - (!INSIDEMON(tempClients[1]) || - (tempClients[1]->scroller_proportion + - tempClients[focus_client_index]->scroller_proportion >= - 1.0f))) { - need_scroller = true; - } else if (!over_overspread_to_up && - (!INSIDEMON(tempClients[focus_client_index - 1]) || - (tempClients[focus_client_index - 1]->scroller_proportion + - tempClients[focus_client_index]->scroller_proportion >= - 1.0f))) { - need_scroller = true; - } else { - need_apply_overspread = false; - } - } - - bool need_apply_center = - config.scroller_focus_center || m->visible_scroll_tiling_clients == 1 || - (config.scroller_prefer_center && !need_apply_overspread && - (!m->prevsel || - (ISSCROLLTILED(m->prevsel) && - (m->prevsel->scroller_proportion * max_client_height) + - (tempClients[focus_client_index]->scroller_proportion * - max_client_height) > - m->w.height - 2 * config.scroller_structs - cur_gappiv))); - - if (n == 1 && config.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; - vertical_scroll_adjust_fullandmax(tempClients[focus_client_index], - &target_geom); - - if (tempClients[focus_client_index]->isfullscreen) { - target_geom.y = m->m.y; - vertical_check_scroller_root_inside_mon(tempClients[focus_client_index], - &target_geom); - arrange_stack_vertical(tempClients[focus_client_index], target_geom, - cur_gappih); - } else if (tempClients[focus_client_index]->ismaximizescreen) { - target_geom.y = m->w.y + cur_gappov; - vertical_check_scroller_root_inside_mon(tempClients[focus_client_index], - &target_geom); - arrange_stack_vertical(tempClients[focus_client_index], target_geom, - cur_gappih); - } else if (need_scroller) { - if (need_apply_center) { - target_geom.y = m->w.y + (m->w.height - target_geom.height) / 2; - } else if (need_apply_overspread) { - if (over_overspread_to_up) { - target_geom.y = m->w.y + config.scroller_structs; - } else { - target_geom.y = - m->w.y + - (m->w.height - - tempClients[focus_client_index]->scroller_proportion * - max_client_height - - config.scroller_structs); - } - } else { - target_geom.y = root_client->geom.y > m->w.y + (m->w.height) / 2 - ? m->w.y + (m->w.height - - tempClients[focus_client_index] - ->scroller_proportion * - max_client_height - - config.scroller_structs) - : m->w.y + config.scroller_structs; - } - vertical_check_scroller_root_inside_mon(tempClients[focus_client_index], - &target_geom); - arrange_stack_vertical(tempClients[focus_client_index], target_geom, - cur_gappih); - } else { - target_geom.y = c->geom.y; - vertical_check_scroller_root_inside_mon(tempClients[focus_client_index], - &target_geom); - arrange_stack_vertical(tempClients[focus_client_index], target_geom, - cur_gappih); - } - - for (i = 1; i <= focus_client_index; i++) { - 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; - - arrange_stack_vertical(c, target_geom, cur_gappih); - } - - 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; - arrange_stack_vertical(c, target_geom, cur_gappih); - } - - free(tempClients); -} - void vertical_grid(Monitor *m) { int32_t i, n; int32_t cx, cy, cw, ch; diff --git a/src/mango.c b/src/mango.c index 34d4d764..2a972aae 100644 --- a/src/mango.c +++ b/src/mango.c @@ -114,7 +114,9 @@ (A && !(A)->isfloating && !(A)->isminimized && !(A)->iskilling && \ !(A)->isunglobal) #define VISIBLEON(C, M) \ - ((C) && (M) && (C)->mon == (M) && ((C)->tags & (M)->tagset[(M)->seltags])) + ((C) && (M) && (C)->mon == (M) && \ + (((C)->tags & (M)->tagset[(M)->seltags] || (C)->isglobal || \ + (C)->isunglobal))) #define LENGTH(X) (sizeof X / sizeof X[0]) #define END(A) ((A) + LENGTH(A)) #define TAGMASK ((1 << LENGTH(tags)) - 1) @@ -421,8 +423,6 @@ struct Client { int32_t allow_shortcuts_inhibit; float scroller_proportion_single; bool isfocusing; - struct Client *next_in_stack; - struct Client *prev_in_stack; bool enable_drop_area_draw; int32_t drop_direction; struct wlr_box drag_tile_float_backup_geom; @@ -561,6 +561,21 @@ typedef struct { } SessionLock; typedef struct DwindleNode DwindleNode; +struct ScrollerStackNode { + Client *client; + float scroller_proportion; + float stack_proportion; + float scroller_proportion_single; + + struct ScrollerStackNode *next_in_stack; + struct ScrollerStackNode *prev_in_stack; + struct ScrollerStackNode *all_next; +}; + +struct TagScrollerState { + struct ScrollerStackNode *all_first; /* 所有节点的单链表头 */ + int count; +}; /* function declarations */ static void applybounds( @@ -798,7 +813,7 @@ static void request_fresh_all_monitors(void); static Client *find_client_by_direction(Client *tc, const Arg *arg, bool findfloating, bool ignore_align); static void exit_scroller_stack(Client *c); -static Client *get_scroll_stack_head(Client *c); +static Client *scroll_get_stack_head_client(Client *c); static bool client_only_in_one_tag(Client *c); static Client *get_focused_stack_client(Client *sc); static bool client_is_in_same_stack(Client *sc, Client *tc, Client *fc); @@ -823,6 +838,16 @@ static void dwindle_resize_client_step(Monitor *m, Client *c, int32_t dx, int32_t dy); static void dwindle_resize_client(Monitor *m, Client *c); +static struct TagScrollerState *ensure_scroller_state(Monitor *m, uint32_t tag); +static struct ScrollerStackNode *find_scroller_node(struct TagScrollerState *st, + Client *c); +static void sync_scroller_state_to_clients(Monitor *m, uint32_t tag); +static void scroller_node_remove(struct TagScrollerState *st, + struct ScrollerStackNode *target); +static struct ScrollerStackNode * +scroller_node_create(struct TagScrollerState *st, Client *c); +static void update_scroller_state(Monitor *m); + #include "data/static_keymap.h" #include "dispatch/bind_declare.h" #include "layout/layout.h" @@ -945,19 +970,17 @@ static struct { #include "client/client.h" #include "config/preset.h" - 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 */ - 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 */ + uint32_t curtag, prevtag; + int32_t nmasters[LENGTH(tags) + 1]; + float mfacts[LENGTH(tags) + 1]; + int32_t no_hide[LENGTH(tags) + 1]; + int32_t no_render_border[LENGTH(tags) + 1]; + int32_t open_as_floating[LENGTH(tags) + 1]; struct DwindleNode *dwindle_root[LENGTH(tags) + 1]; - const Layout - *ltidxs[LENGTH(tags) + 1]; /* matrix of tags and layouts indexes */ + const Layout *ltidxs[LENGTH(tags) + 1]; + struct TagScrollerState *scroller_state[LENGTH(tags) + 1]; }; - #include "config/parse_config.h" static struct wl_signal mango_print_status; @@ -1028,6 +1051,7 @@ static struct wl_event_source *sync_keymap; #include "layout/arrange.h" #include "layout/dwindle.h" #include "layout/horizontal.h" +#include "layout/scroll.h" #include "layout/vertical.h" void client_change_mon(Client *c, Monitor *m) { @@ -1156,15 +1180,12 @@ void swallow(Client *c, Client *w) { c->master_inner_per = w->master_inner_per; c->master_mfact_per = w->master_mfact_per; c->scroller_proportion = w->scroller_proportion; - c->next_in_stack = w->next_in_stack; - c->prev_in_stack = w->prev_in_stack; c->isglobal = w->isglobal; - if (w->next_in_stack) - w->next_in_stack->prev_in_stack = c; - if (w->prev_in_stack) - w->prev_in_stack->next_in_stack = c; + /* 调整 w 的邻居指针,让它们指向 c */ c->stack_proportion = w->stack_proportion; + + /* 全局链表替换 */ wl_list_insert(&w->link, &c->link); wl_list_insert(&w->flink, &c->flink); @@ -1190,16 +1211,39 @@ void swallow(Client *c, Client *w) { client_pending_maximized_state(c, w->ismaximizescreen); client_pending_minimized_state(c, w->isminimized); + /* ---------- 跨 tag 同步:dwindle 与 scroller ---------- */ Monitor *m; wl_list_for_each(m, &mons, link) { for (uint32_t t = 0; t < LENGTH(tags) + 1; t++) { + /* dwindle */ DwindleNode **root = &m->pertag->dwindle_root[t]; dwindle_remove(root, c); - DwindleNode *wn = dwindle_find_leaf(*root, w); + DwindleNode *dnode = dwindle_find_leaf(*root, w); + if (dnode) + dnode->client = c; + + /* scroller */ + struct TagScrollerState *st = m->pertag->scroller_state[t]; + if (!st) + continue; + + /* 先移除 c 在任意 tag 中的旧节点 */ + struct ScrollerStackNode *cn = find_scroller_node(st, c); + if (cn) + scroller_node_remove(st, cn); + + /* 将 w 的节点(如果存在)转给 c */ + struct ScrollerStackNode *wn = find_scroller_node(st, w); if (wn) wn->client = c; } } + + /* 同步当前活动 tag 的全局客户端字段 */ + if (c->mon) { + uint32_t curtag = c->mon->pertag->curtag; + sync_scroller_state_to_clients(c->mon, curtag); + } } bool switch_scratchpad_client_state(Client *c) { @@ -2107,100 +2151,6 @@ Client *find_closest_tiled_client(Client *c) { return closest; } -void scroller_insert_stack(Client *c, Client *target_client, - bool insert_before) { - Client *stack_head = NULL; - - if (!target_client || target_client->mon != c->mon) { - return; - } else { - c->isglobal = target_client->isglobal = 0; - c->isunglobal = target_client->isunglobal = 0; - c->tags = target_client->tags = get_tags_first_tag(target_client->tags); - } - - if (c->isfullscreen) { - setfullscreen(c, 0); - } - - if (c->ismaximizescreen) { - setmaximizescreen(c, 0); - } - - exit_scroller_stack(c); - stack_head = get_scroll_stack_head(target_client); - - if (insert_before) { - if (target_client->prev_in_stack) { - target_client->prev_in_stack->next_in_stack = c; - } - c->prev_in_stack = target_client->prev_in_stack; - c->next_in_stack = target_client; - target_client->prev_in_stack = c; - - } else { - if (target_client->next_in_stack) { - target_client->next_in_stack->prev_in_stack = c; - } - c->next_in_stack = target_client->next_in_stack; - c->prev_in_stack = target_client; - target_client->next_in_stack = c; - } - - if (stack_head->ismaximizescreen) { - setmaximizescreen(stack_head, 0); - } - - if (stack_head->isfullscreen) { - setfullscreen(stack_head, 0); - } - - arrange(c->mon, false, false); - - return; -} - -void try_scroller_drop(Client *c, Client *closest, int vertical) { - - Client *stack_head = get_scroll_stack_head(closest); - - if (vertical) { - if (closest->drop_direction == LEFT) { - setfloating(c, 0); - scroller_insert_stack(c, closest, true); - return; - } else if (closest->drop_direction == RIGHT) { - setfloating(c, 0); - scroller_insert_stack(c, closest, false); - return; - } else if (closest->drop_direction == UP) { - wl_list_remove(&c->link); - wl_list_insert(stack_head->link.prev, &c->link); - } else if (closest->drop_direction == DOWN) { - wl_list_remove(&c->link); - wl_list_insert(&stack_head->link, &c->link); - } - } else { - if (closest->drop_direction == UP) { - setfloating(c, 0); - scroller_insert_stack(c, closest, true); - return; - } else if (closest->drop_direction == DOWN) { - setfloating(c, 0); - scroller_insert_stack(c, closest, false); - return; - } else if (closest->drop_direction == LEFT) { - wl_list_remove(&c->link); - wl_list_insert(stack_head->link.prev, &c->link); - } else if (closest->drop_direction == RIGHT) { - wl_list_remove(&c->link); - wl_list_insert(&stack_head->link, &c->link); - } - } - - setfloating(c, 0); -} - void place_drag_tile_client(Client *c) { Client *closest = find_closest_tiled_client(c); @@ -2215,11 +2165,11 @@ void place_drag_tile_client(Client *c) { } if (layout->id == SCROLLER) { - try_scroller_drop(c, closest, 0); + scroller_drop_tile(c, closest, 0); return; } if (layout->id == VERTICAL_SCROLLER) { - try_scroller_drop(c, closest, 1); + scroller_drop_tile(c, closest, 1); return; } if (layout->id == DWINDLE) { @@ -2550,6 +2500,9 @@ void cleanupmon(struct wl_listener *listener, void *data) { for (uint32_t t = 0; t < LENGTH(tags) + 1; t++) dwindle_free_tree(m->pertag->dwindle_root[t]); + + cleanup_monitor_scroller(m); + free(m->pertag); free(m); } @@ -3292,7 +3245,11 @@ void createmon(struct wl_listener *listener, void *data) { wlr_output_state_finish(&state); wl_list_insert(&mons, &m->link); + m->pertag = calloc(1, sizeof(Pertag)); + for (int i = 0; i < LENGTH(tags) + 1; i++) + m->pertag->scroller_state[i] = NULL; + if (chvt_backup_tag && regex_match(chvt_backup_selmon, m->wlr_output->name)) { m->tagset[0] = m->tagset[1] = (1 << (chvt_backup_tag - 1)) & TAGMASK; @@ -4327,8 +4284,6 @@ void init_client_properties(Client *c) { c->float_geom.x = 0; c->float_geom.y = 0; c->stack_proportion = 0.0f; - c->next_in_stack = NULL; - c->prev_in_stack = NULL; memset(c->oldmonname, 0, sizeof(c->oldmonname)); memcpy(c->opacity_animation.initial_border_color, config.bordercolor, sizeof(c->opacity_animation.initial_border_color)); @@ -4423,7 +4378,7 @@ mapnotify(struct wl_listener *listener, void *data) { if (selmon->sel && ISSCROLLTILED(selmon->sel) && VISIBLEON(selmon->sel, selmon)) { - at_client = get_scroll_stack_head(selmon->sel); + at_client = scroll_get_stack_head_client(selmon->sel); } else { at_client = center_tiled_select(selmon); } @@ -5027,91 +4982,137 @@ void setborder_color(Client *c) { } void exchange_two_client(Client *c1, Client *c2) { - Monitor *tmp_mon = NULL; uint32_t tmp_tags; double master_inner_per = 0.0f; double master_mfact_per = 0.0f; double stack_inner_per = 0.0f; - float scroller_proportion = 0.0f; - float stack_proportion = 0.0f; + struct ScrollerStackNode *n1 = NULL; + struct ScrollerStackNode *n2 = NULL; + struct TagScrollerState *st1 = NULL; + struct TagScrollerState *st2 = NULL; if (c1 == NULL || c2 == NULL || (!config.exchange_cross_monitor && c1->mon != c2->mon)) { return; } - if (c1->mon != c2->mon && (c1->prev_in_stack || c2->prev_in_stack || - c1->next_in_stack || c2->next_in_stack)) - return; - - Client *c1head = get_scroll_stack_head(c1); - Client *c2head = get_scroll_stack_head(c2); - - // 交换布局参数 - if (c1head == c2head) { - scroller_proportion = c1->scroller_proportion; - stack_proportion = c1->stack_proportion; - - c1->scroller_proportion = c2->scroller_proportion; - c1->stack_proportion = c2->stack_proportion; - c2->scroller_proportion = scroller_proportion; - c2->stack_proportion = stack_proportion; - } - master_inner_per = c1->master_inner_per; master_mfact_per = c1->master_mfact_per; stack_inner_per = c1->stack_inner_per; - c1->master_inner_per = c2->master_inner_per; c1->master_mfact_per = c2->master_mfact_per; c1->stack_inner_per = c2->stack_inner_per; - c2->master_inner_per = master_inner_per; c2->master_mfact_per = master_mfact_per; c2->stack_inner_per = stack_inner_per; - // 交换栈链表连接 - Client *tmp1_next_in_stack = c1->next_in_stack; - Client *tmp1_prev_in_stack = c1->prev_in_stack; - Client *tmp2_next_in_stack = c2->next_in_stack; - Client *tmp2_prev_in_stack = c2->prev_in_stack; + bool c1_scroller = c1->mon && is_scroller_layout(c1->mon); + bool c2_scroller = c2->mon && is_scroller_layout(c2->mon); + Monitor *m1 = c1->mon; + Monitor *m2 = c2->mon; + uint32_t tag1 = m1->pertag->curtag; + uint32_t tag2 = m2->pertag->curtag; - // 处理相邻节点的情况 - if (c1->next_in_stack == c2) { - c1->next_in_stack = tmp2_next_in_stack; - c2->next_in_stack = c1; - c1->prev_in_stack = c2; - c2->prev_in_stack = tmp1_prev_in_stack; - if (tmp1_prev_in_stack) - tmp1_prev_in_stack->next_in_stack = c2; - if (tmp2_next_in_stack) - tmp2_next_in_stack->prev_in_stack = c1; - } else if (c2->next_in_stack == c1) { - c2->next_in_stack = tmp1_next_in_stack; - c1->next_in_stack = c2; - c2->prev_in_stack = c1; - c1->prev_in_stack = tmp2_prev_in_stack; - if (tmp2_prev_in_stack) - tmp2_prev_in_stack->next_in_stack = c1; - if (tmp1_next_in_stack) - tmp1_next_in_stack->prev_in_stack = c2; - } else if (is_scroller_layout(c1->mon) && - (c1->prev_in_stack || c2->prev_in_stack)) { - Client *c1head = get_scroll_stack_head(c1); - Client *c2head = get_scroll_stack_head(c2); - exchange_two_client(c1head, c2head); - focusclient(c1, 0); - return; + if (c1_scroller) { + st1 = ensure_scroller_state(m1, tag1); + n1 = find_scroller_node(st1, c1); } - // 交换全局链表连接 + if (c2_scroller) { + st2 = ensure_scroller_state(m2, tag2); + n2 = find_scroller_node(st2, c2); + } + + if (!n1 || !n2) + goto exchange_common; + + if (n1 && n2) { + + /* 跨显示器且任一方有堆叠关系时不允许交换 */ + if (m1 != m2 && (n1->prev_in_stack || n2->prev_in_stack || + n1->next_in_stack || n2->next_in_stack)) + return; + + /* 获取各自的堆叠头节点 */ + struct ScrollerStackNode *head1 = n1; + while (head1->prev_in_stack) + head1 = head1->prev_in_stack; + struct ScrollerStackNode *head2 = n2; + while (head2->prev_in_stack) + head2 = head2->prev_in_stack; + + /* 同一堆叠内交换 */ + if (head1 == head2) { + float tmp_scroller = n1->scroller_proportion; + float tmp_stack = n1->stack_proportion; + n1->scroller_proportion = n2->scroller_proportion; + n1->stack_proportion = n2->stack_proportion; + n2->scroller_proportion = tmp_scroller; + n2->stack_proportion = tmp_stack; + + /* 交换堆叠链表指针 */ + struct ScrollerStackNode *p1 = n1->prev_in_stack; + struct ScrollerStackNode *next1 = n1->next_in_stack; + struct ScrollerStackNode *p2 = n2->prev_in_stack; + struct ScrollerStackNode *next2 = n2->next_in_stack; + + if (n1->next_in_stack == n2) { + n1->next_in_stack = next2; + n2->prev_in_stack = p1; + n1->prev_in_stack = n2; + n2->next_in_stack = n1; + if (p1) + p1->next_in_stack = n2; + if (next2) + next2->prev_in_stack = n1; + } else if (n2->next_in_stack == n1) { + n2->next_in_stack = next1; + n1->prev_in_stack = p2; + n2->prev_in_stack = n1; + n1->next_in_stack = n2; + if (p2) + p2->next_in_stack = n1; + if (next1) + next1->prev_in_stack = n2; + } else { + if (p1) + p1->next_in_stack = n2; + if (next1) + next1->prev_in_stack = n2; + if (p2) + p2->next_in_stack = n1; + if (next2) + next2->prev_in_stack = n1; + n1->prev_in_stack = p2; + n1->next_in_stack = next2; + n2->prev_in_stack = p1; + n2->next_in_stack = next1; + } + + sync_scroller_state_to_clients(m1, tag1); + } else { + /* 不同堆叠:交换两个堆叠整体位置 */ + if (n1 != head1 || n2 != head2) { + /* 当前不是头部,递归交换头部 */ + exchange_two_client(head1->client, head2->client); + return; + } + } + } + +exchange_common: + + /* 跨显示器且任一方有堆叠关系时不允许交换 */ + if (m1 != m2 && ((n1 && n1->prev_in_stack) || (n2 && n2->prev_in_stack) || + (n1 && n1->next_in_stack) || (n2 && n2->next_in_stack))) + return; + struct wl_list *tmp1_prev = c1->link.prev; struct wl_list *tmp2_prev = c2->link.prev; struct wl_list *tmp1_next = c1->link.next; struct wl_list *tmp2_next = c2->link.next; - // 处理相邻节点的情况 if (c1->link.next == &c2->link) { c1->link.next = c2->link.next; c1->link.prev = &c2->link; @@ -5126,39 +5127,47 @@ void exchange_two_client(Client *c1, Client *c2) { c1->link.prev = tmp2_prev; tmp2_prev->next = &c1->link; tmp1_next->prev = &c2->link; - } else { // 不为相邻节点 + } else { c2->link.next = tmp1_next; c2->link.prev = tmp1_prev; c1->link.next = tmp2_next; c1->link.prev = tmp2_prev; - tmp1_prev->next = &c2->link; tmp1_next->prev = &c2->link; tmp2_prev->next = &c1->link; tmp2_next->prev = &c1->link; } - // 处理跨监视器交换 if (config.exchange_cross_monitor) { + DwindleNode **c1_root = &m1->pertag->dwindle_root[m1->pertag->curtag]; + DwindleNode *c1node = dwindle_find_leaf(*c1_root, c1); + + DwindleNode **c2_root = &m2->pertag->dwindle_root[m2->pertag->curtag]; + DwindleNode *c2node = dwindle_find_leaf(*c2_root, c2); + + if (c1node) + c1node->client = c2; + + if (c2node) + c2node->client = c1; + tmp_mon = c2->mon; tmp_tags = c2->tags; - setmon(c2, c1->mon, c1->tags, false); - setmon(c1, tmp_mon, tmp_tags, false); - if (c1->mon && - c1->mon->pertag->ltidxs[c1->mon->pertag->curtag]->id == DWINDLE) - dwindle_swap_clients( - &c1->mon->pertag->dwindle_root[c1->mon->pertag->curtag], c1, - c2); + c2->mon = c1->mon; + c1->mon = tmp_mon; + c2->tags = c1->tags; + c1->tags = tmp_tags; + arrange(c1->mon, false, false); arrange(c2->mon, false, false); - focusclient(c1, 0); } else { if (c1->mon && - c1->mon->pertag->ltidxs[c1->mon->pertag->curtag]->id == DWINDLE) + c1->mon->pertag->ltidxs[c1->mon->pertag->curtag]->id == DWINDLE) { dwindle_swap_clients( &c1->mon->pertag->dwindle_root[c1->mon->pertag->curtag], c1, c2); - arrange(c1->mon, false, false); + arrange(c1->mon, false, false); + } } // In order to facilitate repeated exchanges for get_focused_stack_client @@ -5427,24 +5436,18 @@ void reset_maximizescreen_size(Client *c) { } void exit_scroller_stack(Client *c) { - // If c is already in a stack, remove it. - if (c->prev_in_stack) { - c->prev_in_stack->next_in_stack = c->next_in_stack; - } + if (!c || !c->mon) + return; - if (!c->prev_in_stack && c->next_in_stack) { - c->next_in_stack->scroller_proportion = c->scroller_proportion; - wl_list_remove(&c->next_in_stack->link); - wl_list_insert(&c->link, &c->next_in_stack->link); + uint32_t tag = c->mon->pertag->curtag; + struct TagScrollerState *st = c->mon->pertag->scroller_state[tag]; + if (st) { + struct ScrollerStackNode *n = find_scroller_node(st, c); + if (n) { + scroller_node_remove(st, n); + return; /* 节点已移除,客户端指针已在函数内清空 */ + } } - - if (c->next_in_stack) { - c->next_in_stack->prev_in_stack = c->prev_in_stack; - } - - c->prev_in_stack = NULL; - c->next_in_stack = NULL; - c->stack_proportion = 0.0f; } void setmaximizescreen(Client *c, int32_t maximizescreen) { @@ -6105,7 +6108,6 @@ void tag_client(const Arg *arg, Client *target_client) { Client *fc = NULL; if (target_client && arg->ui & TAGMASK) { - exit_scroller_stack(target_client); target_client->tags = arg->ui & TAGMASK; target_client->istagswitching = 1; @@ -6300,9 +6302,15 @@ void unmapnotify(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, unmap); Monitor *m = NULL; Client *nextfocus = NULL; - Client *next_in_stack = c->next_in_stack; - Client *prev_in_stack = c->prev_in_stack; c->iskilling = 1; + struct ScrollerStackNode *target_node = + c->mon ? find_scroller_node( + c->mon->pertag->scroller_state[c->mon->pertag->curtag], c) + : NULL; + struct ScrollerStackNode *prev_node = + target_node ? target_node->prev_in_stack : NULL; + struct ScrollerStackNode *next_node = + target_node ? target_node->next_in_stack : NULL; if (config.animations && !c->is_clip_to_hide && !c->isminimized && (!c->mon || VISIBLEON(c, c->mon))) @@ -6314,7 +6322,8 @@ void unmapnotify(struct wl_listener *listener, void *data) { c->swallowedby->mon = c->mon; swallow(c->swallowedby, c); } else { - exit_scroller_stack(c); + scroller_remove_client(c); + dwindle_remove_client(c); } if (c == grabc) { @@ -6339,10 +6348,10 @@ void unmapnotify(struct wl_listener *listener, void *data) { } if (c->mon && c->mon == selmon) { - if (next_in_stack && !c->swallowedby) { - nextfocus = next_in_stack; - } else if (prev_in_stack && !c->swallowedby) { - nextfocus = prev_in_stack; + if (next_node && !c->swallowedby) { + nextfocus = next_node->client; + } else if (prev_node && !c->swallowedby) { + nextfocus = prev_node->client; } else { nextfocus = focustop(selmon); } @@ -6393,10 +6402,7 @@ void unmapnotify(struct wl_listener *listener, void *data) { } c->stack_proportion = 0.0f; - c->next_in_stack = NULL; - c->prev_in_stack = NULL; - dwindle_remove_client(c); wlr_scene_node_destroy(&c->scene->node); printstatus(); motionnotify(0, NULL, 0, 0, 0, 0); From 4967fd49bacba7da33cbff42f403e6a36bc2e237 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 11 May 2026 12:51:55 +0800 Subject: [PATCH 170/328] update readme --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d3f6e7cf..37c96c6c 100644 --- a/README.md +++ b/README.md @@ -325,6 +325,8 @@ If you find this project helpful to you, you can offer sponsorship in the follow image -Thanks to the following friends for their sponsorship of this project +Thanks to the following friends for their sponsorship of this project: -[@tonybanters](https://github.com/tonybanters) +

+ +

\ No newline at end of file From 374392a7ec725c22c00a5e693ada1fcb2cee3677 Mon Sep 17 00:00:00 2001 From: Ruixi-rebirth Date: Mon, 11 May 2026 13:57:16 +0800 Subject: [PATCH 171/328] refactor: README --- README.md | 370 +++++++++++------------------------------------------- 1 file changed, 70 insertions(+), 300 deletions(-) diff --git a/README.md b/README.md index 37c96c6c..1707850c 100644 --- a/README.md +++ b/README.md @@ -1,332 +1,102 @@ -# Mango Wayland Compositor -
- MangoWM Logo +
+ Mango Logo + +

Mango Wayland Compositor

+ +

A fast, feature-rich Wayland compositor built on dwl

+ +Stars +License +Packaged in +Discord +
-This project's development is based on [dwl](https://codeberg.org/dwl/dwl/). - - -1. **Lightweight & Fast Build** - - - _Mango_ is as lightweight as _dwl_, and can be built completely within a few seconds. Despite this, _Mango_ does not compromise on functionality. - -2. **Feature Highlights** - - In addition to basic WM functionality, Mango provides: - - Excellent xwayland support. - - Base tags not workspaces (supports separate window layouts for each tag) - - Smooth and customizable complete animations (window open/move/close, tag enter/leave,layer open/close/move) - - Excellent input method support (text input v2/v3) - - Flexible window layouts with easy switching (scroller, master-stack, monocle,center-master, etc.) - - Rich window states (swallow, minimize, maximize, unglobal, global, fakefullscreen, overlay, etc.) - - Simple yet powerful external configuration(support shortcuts hot-reload) - - Sway-like scratchpad and named scratchpad - - Ipc support(get/send message from/to compositor by external program) - - Hycov-like overview - - Window effects from scenefx (blur, shadow, corner radius, opacity) - - Zero flickering - every frame is perfect. +--- https://github.com/user-attachments/assets/bb83004a-0563-4b48-ad89-6461a9b78b1f -# Mango's Vision +> See all layouts in action at [mangowm.github.io](https://mangowm.github.io/) -**Mango's primary goal is stability**: After months of testing and development—and aside from a few lingering GPU compatibility issues—it should now be stable enough. I don't plan on making many breaking changes. +## Why Mango? -**Mango's preference is practicality**: I tend to add features that genuinely help with daily workflows—things that make our work more convenient. +Mango starts where dwl ends. It keeps the lightweight, fast-build philosophy while adding the features that make a compositor actually usable day-to-day — without the bloat. -**Mango won't cater to every user preference**: For niche feature requests, I'll take a wait-and-see approach. I'll only consider adding them if they get a significant number of upvotes. +- **Lightweight & fast** — as lean as dwl, builds in seconds, no functionality compromised +- **Excellent xwayland support** — run X11 apps without friction +- **Tags, not workspaces** — each tag maintains its own independent window layout +- **Smooth animations** — window open/move/close, tag transitions, layer surfaces +- **Flexible layouts** — scroller, master-stack, monocle, dwindle, grid, and more +- **Rich window states** — swallow, minimize, maximize, global, overlay, fakefullscreen +- **Window effects** — blur, shadow, corner radius, opacity (via scenefx) +- **Excellent input method support** — text-input v2/v3 +- **Sway-like scratchpad** — named scratchpad support included +- **Hycov-style overview** — see all windows at a glance +- **IPC** — send/receive messages from external programs +- **Hot-reload config** — no restart needed for keybinding changes +- **Zero flickering** — every frame is correct -# Our discord -[mangowm](https://discord.gg/CPjbDxesh5) +## Vision -# Supported layouts -- tile -- scroller -- monocle -- grid -- deck -- center_tile -- vertical_tile -- vertical_grid -- vertical_scroller -- dwindle +**Stability first.** After months of testing, Mango is solid enough for daily use. Breaking changes will be minimal. -# Installation +**Practicality over novelty.** Features get added when they genuinely improve daily workflows — not for the sake of completeness. + +**Focused scope.** Niche requests are evaluated by community interest. Significant upvotes move things forward. + +## Installation [![Packaging status](https://repology.org/badge/vertical-allrepos/mangowm.svg)](https://repology.org/project/mangowm/versions) -## Dependencies +### Arch Linux -- wayland -- wayland-protocols -- libinput -- libdrm -- libxkbcommon -- pixman -- libdisplay-info -- libliftoff -- hwdata -- seatd -- pcre2 -- xorg-xwayland -- libxcb - -## Arch Linux -The package is in the Arch User Repository and is available for manual download [here](https://aur.archlinux.org/packages/mangowm-git) or through a AUR helper like yay: ```bash yay -S mangowm-git - ``` -## Gentoo Linux -The package is in the community-maintained repository called GURU. -First, add GURU repository: +### Other distributions -```bash -emerge --ask --verbose eselect-repository -eselect repository enable guru -emerge --sync guru -``` +See the [Installation Guide](https://mangowm.github.io/docs/installation) for Fedora, Gentoo, Guix, NixOS, openSUSE, PikaOS, AerynOS, and building from source. -Then, add `gui-libs/scenefx` and `gui-wm/mangowm` to the `package.accept_keywords`. +## Documentation -Finally, install the package: +- **[mangowm.github.io](https://mangowm.github.io/)** — website docs with configuration reference, keybindings, layouts, IPC, and more +- **[GitHub Wiki](https://github.com/mangowm/mango/wiki/)** — community-maintained wiki -```bash -emerge --ask --verbose gui-wm/mangowm -``` +## Community -## openSUSE -The package is in the community-maintained repository called obs. +Join us on **[Discord](https://discord.gg/CPjbDxesh5)** -```bash -sudo opi in mangowm -``` -## Fedora Linux -The package is in the third-party Terra repository. -First, add the [Terra Repository](https://terra.fyralabs.com/). +## Acknowledgements -Then, install the package: +- [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots) — Wayland protocol implementation +- [dwl](https://codeberg.org/dwl/dwl) — the foundation Mango builds on +- [scenefx](https://github.com/wlrfx/scenefx) — window effects library +- [owl](https://github.com/dqrk0jeste/owl) — animation groundwork +- [sway](https://github.com/swaywm/sway) — protocol reference -```bash -dnf install mangowm -``` +## Sponsor -## Guix System -The package definition is described in the source repository. -First, add `mangowm` channel to `channels.scm` file: +If Mango makes your desktop better, consider supporting its development. -```scheme -;; In $HOME/.config/guix/channels.scm -(cons (channel - (name 'mangowm) - (url "https://github.com/mangowm/mango.git") - (branch "main")) - ... ;; Your other channels - %default-channels) -``` +Thanks to everyone who has sponsored this project: -Then, run `guix pull` and after update you can either run -`guix install mangowm` or add it to your configuration via: + + + + + +
+ +
+ tonybanters +
+
-```scheme -(use-modules (mangowm)) ;; Add mangowm module +Crypto donations accepted — scan the QR code or send to the address shown: -;; Add mangowm to packages list -(packages (cons* - mangowm-git - ... ;; Other packages you specified - %base-packages)) -``` - -And then rebuild your system. - -## Other - -```bash -git clone -b 0.19.3 https://gitlab.freedesktop.org/wlroots/wlroots.git -cd wlroots -meson build -Dprefix=/usr -sudo ninja -C build install - -git clone -b 0.4.1 https://github.com/wlrfx/scenefx.git -cd scenefx -meson build -Dprefix=/usr -sudo ninja -C build install - -git clone https://github.com/mangowm/mango.git -cd mango -meson build -Dprefix=/usr -sudo ninja -C build install -``` - -## Suggested Tools - -### Hybrid component -- [dms-shell](https://github.com/AvengeMedia/DankMaterialShell) - -### Independent component -- Application launcher (rofi, bemenu, wmenu, fuzzel) -- Terminal emulator (foot, wezterm, alacritty, kitty, ghostty) -- Status bar (waybar, eww, quickshell, ags), waybar is preferred -- Wallpaper setup (swww, swaybg) -- Notification daemon (swaync, dunst,mako) -- Desktop portal (xdg-desktop-portal, xdg-desktop-portal-wlr, xdg-desktop-portal-gtk) -- Clipboard (wl-clipboard, wl-clip-persist, cliphist) -- Gamma control/night light (wlsunset, gammastep) -- Miscellaneous (xfce-polkit, wlogout) - -## Some Common Default Keybindings - -- alt+return: open foot terminal -- alt+space: open rofi launcher -- alt+q: kill client -- alt+left/right/up/down: focus direction -- super+m: quit mango - -## My Dotfiles - -### Daily -- Dependencies - -```bash -yay -S rofi foot xdg-desktop-portal-wlr swaybg waybar wl-clip-persist cliphist wl-clipboard wlsunset xfce-polkit swaync pamixer wlr-dpms sway-audio-idle-inhibit-git swayidle dimland-git brightnessctl swayosd wlr-randr grim slurp satty swaylock-effects-git wlogout sox -``` - -### Dms -- Dependencies -```bash -yay -S foot xdg-desktop-portal-wlr swaybg wl-clip-persist cliphist wl-clipboard sway-audio-idle-inhibit-git brightnessctl grim slurp satty matugen-bin dms-shell-git - -``` -- use my dms config - -```bash -git clone -b dms https://github.com/DreamMaoMao/mango-config.git ~/.config/mango -``` -- use my daily config - -```bash -git clone https://github.com/DreamMaoMao/mango-config.git ~/.config/mango -``` - - -## Config Documentation - -Refer to the repo wiki [wiki](https://github.com/mangowm/mango/wiki/) - -or the website docs [docs](https://mangowm.github.io/) - -# NixOS + Home-manager - -The repo contains a flake that provides a NixOS module and a home-manager module for mango. -Use the NixOS module to install mango with other necessary components of a working Wayland environment. -Use the home-manager module to declare configuration and autostart for mango. - -Here's an example of using the modules in a flake: - -```nix -{ - inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - home-manager = { - url = "github:nix-community/home-manager"; - inputs.nixpkgs.follows = "nixpkgs"; - }; - flake-parts.url = "github:hercules-ci/flake-parts"; - mango = { - url = "github:mangowm/mango"; - inputs.nixpkgs.follows = "nixpkgs"; - }; - }; - outputs = - inputs@{ self, flake-parts, ... }: - flake-parts.lib.mkFlake { inherit inputs; } { - debug = true; - systems = [ "x86_64-linux" ]; - flake = { - nixosConfigurations = { - hostname = inputs.nixpkgs.lib.nixosSystem { - system = "x86_64-linux"; - modules = [ - inputs.home-manager.nixosModules.home-manager - - # Add mango nixos module - inputs.mango.nixosModules.mango - { - programs.mango.enable = true; - } - { - home-manager = { - useGlobalPkgs = true; - useUserPackages = true; - backupFileExtension = "backup"; - users."username".imports = - [ - ( - { ... }: - { - wayland.windowManager.mango = { - enable = true; - settings = '' - # see config.conf - ''; - autostart_sh = '' - # see autostart.sh - # Note: here no need to add shebang - ''; - }; - } - ) - ] - ++ [ - # Add mango hm module - inputs.mango.hmModules.mango - ]; - }; - } - ]; - }; - }; - }; - }; -} -``` - -# Packaging mango - -To package mango for other distributions, you can check the reference setup for: - -- [nix](https://github.com/mangowm/mango/blob/main/nix/default.nix) -- [arch](https://aur.archlinux.org/cgit/aur.git/tree/PKGBUILD?h=mangowm-git). -- [gentoo](https://data.gpo.zugaina.org/guru/gui-wm/mangowm) - -You might need to package `scenefx` for your distribution, check availability [here](https://github.com/wlrfx/scenefx.git). - -If you encounter build errors when packaging `mango`, feel free to create an issue and ask a question, but -Read The Friendly Manual on packaging software in your distribution first. - -# Thanks to These Reference Repositories - -- https://gitlab.freedesktop.org/wlroots/wlroots - Implementation of Wayland protocol - -- https://github.com/dqrk0jeste/owl - Basal window animation - -- https://codeberg.org/dwl/dwl - Basal dwl feature - -- https://github.com/swaywm/sway - Sample of Wayland protocol - -- https://github.com/wlrfx/scenefx - Make it simple to add window effect. - - -# Sponsor -At present, I can only accept sponsorship through an encrypted connection. -If you find this project helpful to you, you can offer sponsorship in the following ways. - -image - - -Thanks to the following friends for their sponsorship of this project: - -

- -

\ No newline at end of file +
+Show QR code +
+sponsor QR +
From 151889fd85d662e7634562a9db046cfbbbbffc94 Mon Sep 17 00:00:00 2001 From: Ruixi-rebirth Date: Mon, 11 May 2026 14:49:21 +0800 Subject: [PATCH 172/328] docs: improve sponsor section --- README.md | 18 ++++++++++++------ assets/crypto_sponserme_qrcode.png | Bin 0 -> 25610 bytes 2 files changed, 12 insertions(+), 6 deletions(-) create mode 100644 assets/crypto_sponserme_qrcode.png diff --git a/README.md b/README.md index 1707850c..8229f4b0 100644 --- a/README.md +++ b/README.md @@ -93,10 +93,16 @@ Thanks to everyone who has sponsored this project: -Crypto donations accepted — scan the QR code or send to the address shown: +Crypto donations accepted: -
-Show QR code -
-sponsor QR -
+ + + + + +
+ Network: BEP20 (BSC)
+ Address: 0xf9cda472f2556671d2504afc4c35340ec5615da1 +
+ sponsor QR +
diff --git a/assets/crypto_sponserme_qrcode.png b/assets/crypto_sponserme_qrcode.png new file mode 100644 index 0000000000000000000000000000000000000000..970b97439d3c3aba8dbe385861f494629d3dc73a GIT binary patch literal 25610 zcmeAS@N?(olHy`uVBq!ia0y~yV2lD`4mJh`hWNm#Wef}o44y8IAr*0NV>$PPe*PAJ z_OY(l(n=+j^^C1cUak~ae`3Cona4e6C$6PJqB9@4a;9q8D0cK`Mm17!?yct{hr5V?>%2@{{7Fgz2EZd&)<6eJvRI4%BaYnc&`FmbYtaqbrEtu$+-G>L$k`#)}OZ3g@H*7W8gzO0Brqkl|pW;GkXYV4@%; zzgmkmeQ!_wTotz3?dlJA_1R{*i$wST znKZ$4cFE+jKN4qW?{DCfwOJA8KhGv)eO|0qFMd1>Bqi+2v zS~3O+47@TH4yzv(K0X%s{9LS1k+jb)%aRuYS67vat&QC5wo{Jb+LxFA7ccXjZMC-Y zNQdB+)z|+{nI=}hfOUe1eq7A%d#gfMe=c2a6+7AYdEVV!mp-4jUmts3eSVG8+uQNk z#&fovU3EV!@T`>SmRu>b92ageoq~DV%XtI#1@US{m>k{lM#Dk+-@WN>iVJMhWHh#$ zdK_uyp4-~6Z8GZxzAX&5&T;PKRlD2Lux%Ra1wVd{jA~VmjPDT)xuI`M*-M*umA+2n z-12tr@;`64-(S%tm?8bowQ*aT)rGeDi|M8692c~Q=b2efoY3l#1^J47r!N+ zSR}fD_f=%Cncq(@L+0Ec1xXi=eOBvq)fUTL^V?hJR)nc|&VHT+SNm6gx!Bgh#` z^%-AiTPEIp?XB62>TXwUv9*=cQ*sp@q`R**y?^}1N?<{+eXK{#mPdLEO z@SC^9g|Iad6W{Im?C0faSbVZ+?~xmBhqK@N+y8ZWF!$AwFI7GJ`0f7~$X=QFNq1Vs zg9D7StV&l+6B2oJk?%_1ww#+rx~@FeHr`(%|b#95WRc)+t|c|pkKb+Nl2 z)!j66$c|!JSAA-V=9m8ZKZk$rVw_~98L>fOZOqO|tHRc1MNRws+{~dn`|A#_E`T&}UAT24E9v5`49^C$$!tg5we4<2>`yqdNVMDR_5^LQwGmr3GWGq) zxY{3mxb4{zqaulASLRzLI7YA^vg){FcgX5O{r}U65!>D_o_xtMhMg}1@$r_$VT<=e+&@nykyqdW&W$G?v}ikl3=LZt8>3&u7i=tHkW@ zHhG-I|HHG4lmC-JVQ%v-u@I9PX1TM*jT+idTHac zv$HR&PLEl{8^PgKfAW}>gTh2D1+^8~$1EOH+^_rn_LgF?$c0A_qcaGOt;!(^&aI=0NQPo=G|muitCdoIEqf()isf$B?@{ zlE!-~L#plE8FlrZh_6dkb&&qPckx_@hOqNi9IxJSTv%nASuD|JzTJiK?e+Njxz##R zj8Pjinwd6692e{kTe~x5W5!=mt_xc4rt-da(1|LPJ;F7~@r&>R(FMI%7-HCyE*|?) z(%B!j)}m0hHXB$E*s9#+$bd z??ge;L3MvG!&-*iZ)HA0Om8PDyJuWo6?$tyTTXmRk8Gd3{X5m!Zi{l}z7Puf{rAB=Xeyf7yi|2IwkwSb@!=5t=un}sDd%iioKCnm-%IQ{T& z`_$st>bG0JJZR?6y3RaLW0n647E!wiOJdbKeYBLOse7sptM`RE zPL-Z65YyVH$`Zc!Cn(6~?sGMndvsChEjI(g=FH-{uuC(n;hJMT(-yu{r`^ICZ{=uc`}wkF+!dMqxD8~|CgBd`Po_FusoTcRf<}N72P-TEjVDBeeFVW zzipb2!J93~JB%}2A5`2czh8T5p@1SQQ*}F^?3T+f{WkvX;}+9V=vh8#ZS?j_w%_kK z??}|r3|z!w`|ro&D=Pw(m!D=&-@aUtTUgE0}G45L8?J{{BAPY<%KRz7hUzv2Y>z3KNHO&7je_!AK&$U}jw`iBz z>~jy+Dj&2`31`sNW0{?Gb5rV;^7nDlvPbT)NefGV=`lVR&?l&JVO#F)u+yt;N?uF| zzv}Qkpz%)p?y}sb)YnQ69)5d!JKU1x#QXbozjYN?ZdpC!l*w%d-FM6#pBUywu<}25 z6_0hvy|v{cU!%#~_xu0Xt+_ns#rF?~`K#lft!uho$HKT`iT8B9lE=N~L5)nTQ*uvD z*MI-^bIs3Bmb&ccpM1Yxzh0Kvt%37GY~!_f2Av!UmXFhd{VZD#%(&#)w<+WC+6`~| z}ZtwTF zs)w!OQ^Gje{8KpAojv-1@%mIADVqv|ukY^8<`UIPF^&n^!pW5}`BBm(aYcvM1?Hes zmweaGY-vu;F|h?(FJ3yCuk7%;fFCN(v?c3or|atbp_Qwyt(v|pPsr!&h6wXA8)z}G z9;8g!bV7(Jsl~wAwnazWrPJkgEfypn?_0Vl^>o_fZFBZ4V~A2o|M%Sf|H{}F-vdpZ z_gMRqcNRU}a>sj3`sr!9B_AF*KCLp~uPYG}=?@c+d3Zj4ZPeBk-XBZZs;=M8-~Tpt zw^vTZmIuMJET%eM+`S>KU_w@|6Iah#$X<$?zn+3Ay#?gp1k>Dx=~kt#K_sz z%rMKju^?8zNwj26T*)@ZTala71iwrUPPx0Q^vV)X;kQM?&lbBbt8n2`Jn!HqkmHl1 z<5K!kg<}D$h0?3lMNdziTKVCC&G$RSEQ_{zoy)b z^y1>;-UnhV7wUdKo&M$5>-EA-Ng+%hoF}yUi580G^+f1Lo>uB%05u-F@(Iedy|G;#F&fB7R@som9Po<+@GA1%?8LplvhNB&Tlv8SRiYh)-(M>O!&uxY7Z-wie*#2OP=9;%bFqkeiEmT z!Jd<)3ZQBsSDNdBmE9cAE3pmNKBjS8SS6ZqsW)Ov(nTw~I?H9d8P+~tYt+~ou_cR@ z`vOQMOi*ZnXt!Aq^P|O~?>T25HaPBXq`8~>g4I1S#wbulJb`V(rHyXAQn%E-8p8N3 z&h6xr`KXh*8)OEk(KU-f%bN5Oj|zQNMqXa@j_P3F_8tm{c*;qwt0KeHE}84XG57UJ7o%Q;>O^s?cDlY^wnXKn zuEXpGX+==0=;MvFq>IO#SAUcYZZNZ}679a`1X8WxsS{@{ijlYe;?n5iFmtK{{yv#U88 zUCi~8gjY;F5yNhrc1B_L3zzBHZui&k|2J!vdHy^IUk2FzmSba~VNJ6bWaihaJ zhP9w3gV{V8P(sbgnJ(=lwZXlw9_mGHKEylk0zee&+mh?eXz`?cc)aJYzITNke7dsBFC~Ewb(#kDf zc4=d;YxJrR&8kl))wh23`Eg#wD|Y+I9b(Uq+}{!S_SV+L_W%D}wwRgSEN5GF;le`a zsfQPw;gD-f5u$oUnEMMb=J=Ndyo?1LTHFfdI;N_PrpU+vG)684; zc`=c)Gfb%*->?$!OyP2MT?oZCm829$a#^^ftrVmv)I0 zJJ?KEd;feot$)vWv9N?l@v}35W-o7T)poSsA+DFiyye2$cilXnr|U(}{3NcI7Sm;X zK`&~{iPe@{GAB!3?OknX;_y}{U{46s0$JOtEoN0b{AT+k)coeW*d?Q{m2-Vv?5RRO z`@bc#>}stZMqAu#x)rrz{?yq=o(Wtyw#e0XwZGlZBax<#Q>D#yS>p8$xAR|TTARsJ zlUID#%pu!&y6^0(tHXuw>6J9C*f7&LU2FD&Z?`8XItwvvF}p4jQRo(Y)&NvU&gPZ$ zmS&9F>YBaSpD}9dv>cr%X<6U_hGxw0y5AIETZG2TTJ_XDw z%5S*$r8?w91PTXHL>DSlS!E2+o?&%M`b3n>8>q6!8xyxl9-`JQOx9Fa# z_q2@fML$11wY{`?wa-i=SMLN+bNo2-+K`o#inLzv?kZe*yiv$F>x#y8??ux2doEV; z%w}{r=H9^C6cCb>e6eZypKHxl{X^Vo~T$_JC9^ZN}cLCpFfg3FjJmHc1jHZ^o zvrUjWc4G0;!_^WQR|T~?o;ZYb^f~nKynS#$FLqbR&ISMe{tlk57rTY~--pBenNLnk zJY}X3rY!L={{OG(n|S3u+}xZlESD61@bH4}B*v98j@%K`y~^I*@%(NbC>#xodTzBZA$h2^=x+joQ7*@wma^>y}kXq+1lvs z>%_m!;#hDLJeV?j>G8e2XG__)aV!A=*N{2cf%KV9Xb zl)_??pfk0>ojXpyiMQm^>TvzXvK&pN40iG!-`8ysznt_?h@t=I*X!}u&9)ZG&d8|P z!QXH5i9^wOuUC#v6pLg`$wk*&U*pPdrplFiPiK(+E_ddP^&h-TRi)G?z-D)GPBJ%?ii|WFz?-*eqK)hhI4rBArm$` z_W%Fi?S6mFjMZM6QN(JdalT;g&2@hN-hVg!l+k>`z4}^YI=GMO*vw{T<X&<7vc4TtF7P>lXZDf%%XUt+S9pd+=RQWP|9(0ip}2j7)8P{SB>%XhFITmnDnzx(F z%Y!wr@-SP*Y}4#zz5jet)-f|iU7wqL(VoLVa6zbcRKv9-VJkDi1*O3YKn=GwRooX= zT@`7`W64;%ZDFu7W0d;W%GS8z5RP@XpU#_e&#|Es)FPiZ=dSdu(1vTiznQk={o4FW z*Fjrs?Nv9=M{Z18^32^EuKCu7?FBVt)|z<J#*|YvnJ1bd3kwts7vXl;^%&s1LEZ3!Wr+r znU%e6;m+dcR=lmhW0!aczO7|=UUYw7?XAjBPfxpRYHIprJEj*bYyL6GO!vsMT?dc4 zPAZ)Hz)sE>G&2e}0x}_k=&u>bbYKEwwCurZHP((mmEZQ>}m(Do>{r zP1&9x_;{}@``%Zu;d z17-KVD@|7WYy>teVAf|~==k70O($@jWk&-mPu#AO%vHh5`@+nBTrxTNcKiLjtfW6O z2l($UuxjsCNH|!sKXB&bqvG*b{DOBDr6!xz&MsvW+>l_*e}Mn`TJ8lKcq0s0vld)( zeR!xfciPh2&v(o3OZI98A8=A?6myi4ulup^+pK#v-|e?l_3krbXER{R6*wxH`%d80 z;vNaZL(5if;x%Jdx-hT)U*+;bo1;%I-u$@mVY7$U7<_OJOEF#b(7UR0=ftIO+k7&$n zX%&axt-Z67@{~c{h}_Q{7moe$sFY>aKBpnJ_Lp0S>+5Zv%`bGtatqZ>xh`0(^l!N4 zTl>hW;A;G$U!XplTGo2k+3q?~?r(4Z&^o%PG^B{(E-0lj-*7YJS9Flx{K`p|HA7Z( z#)5aC)(7XUtia`h3rer-V_3VGN%%@EXz<-J+PC4_^@a<(uSENSN6)ueKQTDNyNr2r zM)>+4fzpy`o6p48{XD81-fZzNTqtCkgGt!iTU#I3-F$qk_p{QMkR-ZsV1{r4xJm*pwf))0$SV+tuaPFXt<&9TsBdK6S#g zv$M0?nboBdcUTE7*t%1>Fi+V*I-Ql>Vh_XGsF_S#vhH$RVEZ9(%xVfl?wVPr@Ayx( zK5(o*Z#NUD2=w6XaLxYxWzLf_rY$#jGcttyQQ&v_;cs!_(S5rEkId^Hh;jZuxaKBn zBB)izc|t5Ewo2t4Ogdun{{No; z-w)i}-ekCJex6XhyM0QDbDCwtx^t&MCA8=QUnhaBk&_s5!*m&WCYIkgZ_DGrD)H-@ zdc!oqi?3Iln$Om8_ERKB`OAu1g@8MQs|NLOcm^!E9l{8PL zEft6TKOYeFoT9j*?jdu|j{MM-=QS8!{p36IvdN$4Ipa0_5>-E>uPOXpJylncF+f(k|XWJum_1TqR|I&F}4nqG7+@%FR@A-Bs8#Gh# zwB~RdukxQ4OPt&JzML^WpK<%a)|*o_eoYDXGmP8ryHlEnslhqALHZf%iKxcxA6$zV z*P2&UEzV)mt2(&J^~1q$Z*OOQcyRE`%jNT{rmg>WnNt+y7$+s)wfjjqqp@0d~#F2U-OxFVN|nU!2Fz# zkB+K+m9Rfv8e4MF^;B8%@xHI`X0Dqv$@c)$z7pT>)pvK5mb_d#J?r|MqAbf(;TK}8 zPDdmj6p?Jp(7&T`<#^D^$n?3c8ljgLI5IC@G(KS?{q3R03=$0-vcA8&`{kH){)GvO&Ndf} zVrP5q?@(d-AEd6~yM$Sl!SnsSy;~iu)6Ph|y1IJ$HnCa0g>KT`_m&-ET69QECn6!J z|Kn##<1`0{e8gISrz=95I`nGIUrhGFp-qmKiXtp;?(er> z8?!SgTH|uba@GuAsY~^G4y!NpM{LQH*51yrw*CW4!_n223}X9kWQY}Y%sa}%_;2gm z3ye|w^X>nh5j&-`&T3ajx2fLq`HOQF%6V`1>utCW)?t0C%CGUJQ zsQY%CFX+oTUI*?8t*IOnT3xv(*dC0wedx{mm-$0QPknx(_g8ba?Jw)sEc?GU(%XIm zYqd(g9b0(Bj%Cy9uC7hEU^~z4XT_>XHo>!tvx7f#O?|P5>*-3a^(!@u`9d0AM?b2V z>Q?%AW$^Ng^PA4oUt?J+E%r?NzvJ}> z!C!@^#b%wF8oJ#0fZqL$bNN20XI8#Z+OZ~|$$#2>%dMI`>!ydU^jfShp=pwDwHh1^MlHOYhcPn*UI!qtPv)*uCOL&7N&tuH6B%v#!3#(wlvEN06#k z7-p%`<{HyW-r&XQ87pl%NwH347KJ;gOl&aT- zxAniJOD%rqXq{(Im-%t}^a-CcN59Hg6f}tG#dy^I{}s4 z+^cV9?Oq!nUD=i;TD9Wi(vU`GaR=dU5eDs=a}Oqzy}ZW|e!Uj0?lg1p*>vakhr|yb=C@xH z`~KcC(HqZ>_#8eYfzqtej`GD#Nzz_v>b}@kmUVo49z} z)sIbo`Bum*RNnai!bzq8JIuRZ>$sD{lLKA zD(_@aRn|KhFWU-@BT51DidES%YHgPotX_Ea<!vzG7}%k8P*m15MCzf&~zfx)}8=J&5Oaa!!Jn0?`6U2cO|18dc6zgwI7uC0$> zpWAF$@}T|P=jZ2_pPH(jecbUW$Bx{PqPwNnFWxP`|Mp?0*N3M+jtKi-5VPYBIC)e& z{>|O+jxOtYj~lLSvCOr9e{-|?-VcYkPd%#;aL8brXf2qeWxlWQv77!Q?sjc0POc2$ z!oB}w7$)s3eqNQ(eRzF_w5Y;wv1mEw9VsW&$_y*kHBHt_OceZc{!k6W^$FMiKh!l3 z&P?I%-ekAK-E6~dW|`=Qc!4H!y@(jC#!`!#9|ig^4!eTpDr;tQahp9}6Sw!)kFad5 z)XRIT%k_6GG5Nea^wZ7s`GNg%wp-%)d}o_|y=TUnu{a~J;aZyY@%?9Cuix+2u}|~# z`iy&Sy)!;({Qv&@kJQcVQzExcF+3CA?R3`3?s8AP`#;@#ug}|*$Nf9+-6gWje^=Sk z;L6MoH{H0LIs`jgu2=35j&W@WTYJ!jN%vLTOBbD}*0LjuN?RF<>szKDn$;?=^QCJJ zV~k>%qm=Lc9gXj1nauoWQKix&Jp1PXP2&f%p7*YGwOG;KKS45w`{S(t{mDP)Ki2OL z@(_Dx&YRs9p8Ui0;o-Kaua!V0l%SNyVg;vyShLiM_>CFOmr`;bKX72F(^dI<;=@Ek zsY-T9nUhBs?5_AOw=4bA@-MQ#&5U<*3q5S!x^tbz>jz)whb>UJ`*-{27yI0JSGw5w z3H2>JH0w}2H#hrp7KRvcLGVD?*<-?A!arZ!mVLdijZgN~&opDs4b%Q_`1gSEmGxik zUo#f({l;8lyXBOKx%9{T6Hk4+-S#tRbD_P_EVo_ihmLn|S>rsv@nNai@9g3h zlhC}N%|o4uZ$87M+*?~VT1f=!f||Nv4QA5(-{Ltl4rYO7Ksik|1*|%HMz5KfXH9bb zznN|4X1hzzcrbtJp{ZBDbLMHk2>WQE_IaV$>YiEJ50BKIV@Pa?iizAPWP6TX^|{e( zrAgKdtHpRGvCG#?D0m>W8Z<`Dbs_EX%b(hex<#|?{(iX}=l9U#zRsUb+}rXNHf~P4 z{NJMex#4<+q$Q!VB>fmSr5m_D7kU=4e8IeMvoCqKMYL}mz4NMd&Y{oK-&$A1p4wvc z()roKO?#$QTP%1e=l45YDJgbA(7{!%8G11sb(V6szy5d&no8Wv@K#%S-lKMhzgq0; zZmG|&S>)?5=Wg$cEPgMZ3`s4=1BcG%gf_ZA*SfsS*O-ry+u!woTl$<3mZy>;L^cW!B*{>-e-q-3%%YyUbaOA8fUlRr2o6&Fa*?CB>Bq$J(9G7hbvU zeZSt{+EPKW_(=Gaiw}gK+S{MH_ObW*ywi4pe%~IYzBpx}#9glRm0zyK%Ngd&LqH<4>Ht(f;YytPhNlE@H9o6?g=b7rXU-%7~xXG&v{uM4o^{ zbm!rYhlf5|?!Kr|wjfo}<3MP^``jN_msK_`D32{XDq8aU?e=O%BdvormzVorH*z^= z{odw$!39sJNcQC-uE&&1e<&KQzhD3VZ{vigzUHsG_4g^bsIFvT6g)fI{JURU?9#fw zzm6#uY%Y0HBCsR8FlYa{Pmc9-p19}9%uz=itpErGa2h&%9&67o~EFEAbaOr#&3aYy~-!OH8>ZsF)p6FIC9Qf z#XsW8>mTUE_}7{D+dW{Ito-xL$?TvXrx(RP_FlpMo{`0@Wj#-o%TbHW z@PDnsu^{*UzP-;6U2x5L$jvx0Yh9CvPPm``H360jP4?X$mruBSdU)RZ?ygeK&JU%b zK@a{}J?=4{#GVlxuJ`chikCVYjO_paDSrRxu`Wm01x8k_dCP8n*c@a+OkSxp6*y!pEaRR*bh#+ppJ;_WJunrB2_dpt6!#;o-OR z8$BB&ekMwu5limw64k7q;=Ekvb+=%KA>Xb=PKT#l-5;E#UcyKM_ zm|$Fg@yVIpmAw!5MeVO**)+AWQt+p1xBgkvIpOB_9J)EQ-W11XD(%T+nlRb5i6Lrs z_Q{E&Vk{ZCdAT*W=lCr6`nqmmohaWCqkRodkvr?|ZJR4M&v^E-zY}NWoqg44)>{2F z^?!Q*FWx;acPHvKMVLr-9-jHndamHcpC=Nf&b+SqyL|r3glPgdJ=B<!5Gr zeXi)6vpM=2onG!gymQ0x%0HQJwB;?*_5L1UFJCqxA!2Ln`PI(MQf7}tx?@6&CI`-Y zHrMmOQl{ztKW>8-`sAAMN^~1@U0~y$wXvtns8MYFi4W%=3h3Oi=aw$e->n??FV&Lw z$EW%djZHt2p0pYjgeDlgJ3H-7ta$vB#>5>9(r(5QP^|s~n3zPp?#o6>Uzm8t+E|KBBKH7P&{VbPuqt zFIg)O;~Vuqx0fv7wX`Tu5j(Z>1AFvBTg#c>>n%^6_P#ku^QLc_x{F}mZ#{XZxU+TP zOM-az8!_m9lKZjs;-B66kWtJuX6~m3JQKGn-qY}_Q1;*c@n(~~dGPA;drML|NN^H=mbS9BS|S(q(<4%0-$% znPI(#{GO!?yqu2M+CF`d`0K;9*(=S&(^}?4g#NPcD`+?L`Ce6Zug2@?s$Ef9Qr{)- ztDAzczSGqcs&q?=h#|D9rq&-h_6!jknSMUF|wtn}w4X%HMeuss> ze|{#CXN$$0tWzhqu77Iy**#)g>y5dEs?V!*=gym8E0ocA#^d&n1NF-QKpo4`KWA65h5KcY|D&%ycVF-26dZmYlAcm`3<6mpx`b+Lj!6 z@cJdo9oG**p-c`Av$p!pwbH!Zu#Dwbucqs)m&HB}N3JhRIle6W_$_DofHl?@lNG!`ZFshwt!r%O7;#oDsQVU*(?X}`f`eaqJ z_aEcT|I#IEbaEp33!RteAOHEkz~I|?&H0z5(v(}coYOwKR!ooX{{QD??)IRy9eZ|P zNUyjW|DRW5Iiv25WuUcBnX}nGSxypsxk@f!QsUvZi(R7HbDnW`-P=0#^{vL$YbGD{ zu3sv<`{&sc3dar`1b@@J%(891a`WE~LFFxHeQ)m0dDf)LRF?=^!gaQE=Ig(GKZ;J~ zO}%o$Q+VnR>1pCdPnBz~giR4Cy42OGb>|G~PM(kBV`b14+81+RU87n0 zwMAWfwgq#EFADm@RWEgU?W#|{xhwyD_A{luQG!;!|tBTTIZfl zxaJyTq&ii3XPK;}7kfqDS)Md&iJC$OF|8LTdUw_ze0+R`#(%xTS5}L#-JI0QbxAYF zFg$hFRo1E67wvcKiwVqoAGs}(!EP?|o^122_1|TG{f#*BUHa_bz$w>1T+Q8H9Pv&w zvfi`{qI8N9yAD6Tf_0-_ObO3x9_o@SC=ZU z;FAy zsk%+p(q;A!g&fY^EM3J%Tk1bZ^cPyJ2@f*hy71_{yTsP{Nvmrv{Qu|ft~_bR@2oTD zzOs)vo!4O}U+>hrDtsy1Q*DC-Vh@A= z{h7zTfaUjw@(s5a+lJS)_KW?yn)K&>`IQxJ{qdEevd&Y)iqrWwwC5ncd;t}bkJeW zG5#Jgzif__{cju5CzZE~_T9dv8nmM9<>eQ5J}s}-V`q3-w{49hV>5#g<2KgI@!!oi z{7%rEUKW4GzI#UG#tvqURU1rgr}9Sx?~%K6{&n{fb47!HQ`X+wH^=LJIor)O4|hn$ zv;DkT|JyYDq$p^ystIq$Yln^cM@lEyUT5Sx%&=vm-7VWc-}f8u&1IbY&3MhhjAgmA z$}_HR3relp_%<{|&32;Cj=vnQi&UO(lHL4){njBK<_Vnr8}q+NFo{U0oKLv0Hu9N? z)|mrRR>w{`S4+Nl{&!>Bw@t4Z_Fb-dv;FApYQ}&2!)gS~-h4W}lINe?HvS8r1^Ry) zhQFvyx~B6h_5Hpt%-b%^h-}@j(I>Z9a{E0m-o-yMCQmFCn5TC^t$O+nzB~IIK-~x9 zOa-y)j6%yB?ukmKbv5#TQoO)$PV$4z^Bq?&?sj5Jm1@8HY~k#zxt{$^#%D})_DpHt zEYMfb@h)%9hs{fOwmttdb#s&M^FQ07AE-Z@CjB!%uE4Qsf3StT(@UpTTLo5*`Jt;! zLiJ}lg&Ulh_H@7aHwku0hOYXzk4^oPt1do{;Y^Y=mNwFIbYZ@AA!hj}#@k=k#jeo1 zd<--PG37|!M-)`kwIk^>#+G43kwLh<}SGg$Tnc!Ba_Pbrp z|5*d;fw}y%Cm#9#W&h{KNKUqWug}{|mfN+(F!cEyPK|_OrX0zPkNcDVOrIe8`oG$* z$^UmBzWx5fp}E#J*Bk}+P1Exct!j#7tvk@fe~01vh5d#`Zd?a-HIfR%|NL&3f8k!H z-{KIazVP*mswWb@Z!gU}m$ceWCgX1dtBRMSLWll|nLS4ySXI8=dVP!M^7#6{rB(gP z?@A6D@Jg9{(28^UH2d)LzpcV^lV#@4sr&UZj_gVK zmFe+|o@&>d^F2Sa*mYY)%G7e*(nghI3o2B*RkW@*@?KC2Qh#`zmz!m2Mc&S*({vPf zcuf2u_&F~B|9SS31E=q#GDO9&B!_e6m0h;nx{_T!Wp3;u6?XX_Zw%%ipY_h0clV34 zf8MX2S!ytUZGgSe8@;&OlV3W#Hnn}Y^@PW=4*Q4c+Rr^_9C_C?T_WY+^|K5&KbW?E z&M;QnKV`qbyR zPw((kY!20Dqw_ESxod6fEv~pDYr)wa3mf+7@@z`BeDj01toYBd{P=}Ty{opm<*>;u zP-oKn;rk$D-!Y%MRJ%FOCugb8t7wX={d!ecmNnyQfwIHuE4?>M6~xxQ-0kzf(kOaM zf|SjN1I%A;B==W3%+~Z(iofQyjW7R4iO}C8j@&a}20mnX9bYfuW7r^?*8QO?EA*ve zUwwYSwl&i=+*B18@J4Q#p{{iy?Ul?b(QUKux~$)H^qbNDHHr0`HE)(b`NO*7zjVx< z5C1LJGaafs9NrPLW%CaQUI**qXFiNk-N!*YY0SFWjx6F`$+A9y;g(U1v-X@yhU`M? z-?sA~Ff}OebjrWRys?1INyLmPdK>G^Ygf12d3k03%hZyKFQ0N+9$3UV>C~3$&%RLt z(?XV6H{XA-dfll>4mMZj3-2?~*>|9`%gyjaUP6FLbIvu>Y3`3?{QlgPDLt&Tk7GiA z*_Rg=-%)H1(lqOgwn=stl3yqaw686TYEs$MRQdlwqd;C zci#Tq5_iL;d%v;T_&hdg&kWh0!dbQOt7wTLySWSV?Y(!toMq*=eK3L^8OV0fFi?UoQJ!UJP2+zg@9bRl4@WW-gH>%cD(}YoE7J_Lh|XkzTl| z_Wj=Pm)O$cTDn2A8w_g&(~P7#TxT!jR#9VLzr!%xgG2v9hnPZe?ZGL_=H*=dcdA5v z_w_Fyq$m2VPMVjyZ%cBCV)M_|F+`B~oY zEPgJvv)PPa|HM@_Nuv~xY1dzVu-k2!`SrB^exG_HUN2S0jeG$MlMc0TE>o4Z??1-K z`kP_jtk#tvh}m&^D~9ppQXva**K|0Uibg1gV!yVcnW8o-+IU)hFhFv&jKcf ziu)Wk9U{^TWbJAy#JnwP=4kIIa_JV+-PB^+zWrpU%`LsyT^8(u3GzxC%PZz?J5il* zc=6iE&C7~E2tS)^k*T%zE<;>T+#LSHKEf)Wie&;Gs7ScHbK&rtU8-7hU~|sN(D>>4 z@p9!0T%SZdKR0)-e)QB2GJ*{isctVPpI|Zmu+ZI5SGo8D4ST+4@iQj(4MC*W9 zDtDTq&5@gX^z`EIoi2Q^ljDy^X;2`_p=9 z{^IM4A~$z5%sF$hVEg^L>X65W*fL&UUG3g2t{6p{#QkV<)cTe!)=sI^pc9dlf7hzUJ587K2s> zvtEjBFspOumTvi#QBm5qTRG-WK=>1ZiW#+Ajc0ZioY-!+x6Q6pi}^~Axs7a2bm6u2 z?Rxu8SSx(ijke%3Q-0K4oYV8GP;jlafbP}AEs`Q8#`&F@A$|M@zi-C7|v zapw*m_$ifVvslfx=;YCmof(DwW@2#`OnRaQJZ*fkUdrx$A$bvuw-V;1Ue#dQ^6~2W zt1rMd-(gwe*Ro%1V$#G=afak-35oc0?_!DH+1m{aj+YrcjXtPo&v@XQP?>Vc+W7ue zV*U%i-+2C4{+!QN-RQ^d&9!OKqIylIXYT)PAwF@<@txu|a`_e)9&Hehyua=_i}8-S zsT>!gY*!hY3oYo4EIn8%vY^-g!l6yGEee%l&vkA+xcaQl!Yy~AcQ(E4cwfI@`o8)8 zyDb&V+g#31NWH@t7vL-vn*9HR<{6HNa|Z?4FYVlXY@fQ63wzX$+U#Q1yWy6Xud>c5 zRl4)?>P_WyP2)2^ic%u1^X-8xpXN*u1*y9j(&#RKaFDB-@Ms;e%Z?4&tW5fer@!lqqwT4S6mRrR*eQE#^`Ur{ z_1p8^eSaL#H7e}k-(r0umT6X8%8uMEk*pcXyT!NdWy#1j6+K?Yw55|V>O;nC^Za)c zzj;q(x~rX?!Dvu;d*b0QCeaNV!JF;vk5?TPTD1C({;X!*Zrf?o#Vho}Z_9q|FDp?D zZr!8uB=8!$p2?1z+wYc1HyY;&F-A>pyJN>Ml#%}7pzaY>3(dqYdJ{hW|2+SHh3AA= zmYU1l`g;Pbk1+Y^7|rE=^^!&XaK|0bmWEeLc6(Nw{r1{x<+Wz#rPBF(CQcB&=USH_ z_p)8S?uAjyb@6svwaOW%E1Dl&U3t0p&1&7}g`JyUD9&x#`u9a*RWJYAwi?sPpRb8o z6dp*KrCdEfoxkLBq3tHMr>8G&p1ysN)ti4B57H0w7P~VPZ|vVzU^y4G!)29d!+crK z-0qm=nf?oUf84Q5J4VwB=e3@&%6<6u;PckRg~DI13;w<+&OT?| z`E_siRy~~>Zu6|m&#Lg~L#@zN8naDuZcK>Uo;O$3zNmuz!!`SVKa$t#@3l&=-~G7H zdQaY}bcyVvvr3IBUzJ4d&s-hIdXAC1^Nc(9yG42ujO#agyk?)5taK!VRXo+{!_k1* z$~{Gk6?Sr++V!>#VhVT zl0^^89~4>f8|;4I6Y`x``ha=HM^;V+<`ef0FP&^R+o-i*tpyA7V<+{FHPM^Xdhc$M zi7I`4?d^4Q;az7`7xZZIyn_I;ygL=cWNB-J%tZdyr*SO9#o#m zVRu6=?L*WFcCY{Q_BZVF`P*E!NtM-V#&ds;1NWt*R3^EJ{R)=*w_oXv_=>nC8h7Kq z=a(3}*G*0Awh3-L@#Qqr38g90OJ~May;NOa#k7uJ$>H_3|2bX8To<%Tbi~&F3f0&i zy+y9W$bd2DGcgL*Cruz3Z-2-APMHsjJ+{sfp&&TH01ulk8 zhLDI&-akbDs_QBLll@%~UgR|EYW25EY;~db#}>T4yyL>Zmf#00rVaLA*Mu=-YbaVA z*s8>CGUp(B4DYF_OS!ef7I4>1GJ zv&pv{lP;!(&93igd11(Lz|8o(+3H!&4YS>L-&nt4j-<~1PY-W(=|$RSpAJ-q=W7nWFL$N@#=D1~P=@d=ITQlT@ z8KY{phOZ6PKNYxky>y}Mi*3^w)<#)x5MkWH5VM27tEj+1@!S10Hv0pszJIDuQm8jh z`@`4zHEjderQ6GP^VVE)`e7J#Y*o#2=L!jf$`D-!t%^yHkN2xT`E>Ou&$r{?eLPnj z4)GoSpz!xxyVQ+6S_c<0dAw7oUClD*!0&gv{j1e|?V<$w{U0w;5Ve?dDYWj1(N(pd zGbb-^_^?(;;Mn;Gvx1jhPrqz)@|&el_-mF>iGWh(HQX=Q7nC)e{Lgyx6HBArVwt{M zyS^?oJ!~y4xGhz5@r0d%*CsLktF&oj*|BNWl=g|&E1u6Sulifsa=m`f`@8igonYR3!g8bed8^kt)0bA%-miQv3tB%~CL?{FQA_^Cj)SsR zB_FhuUkJUkj`#V+U?CH`EoWxgj(^Qn$e&s zlcBtx%Od%M6n*@;y5wEzTe|O};@$Y|c5ZD`%A*R$uF5SXC5}6@>g361<$fN{BJwG;PL+IZ*N}mJOj<< zPm~hqHWL%pSkuptThnM(Q}V<1VbFp6{Wa!suWSFC$MAkA|2<9P9j_g8#BV)D2D7-= zfd<7dFD-3hafz?}dbQJLQ^)UeQP6@S#;EJdH5Tn>$bHmsZ9bUQUkeU7Rc(oy|wl8Cd-{&kUq)lU=@e#wFebq*v%&M z%2-Tb=80y z+Oje0{JQxd)9(5Sc3+F~S3kXIYg~ehGvloY)AalP4QA&YX3v`Cb>8;-9J6ApxStdL zM7g`Lv7eu!ApPXw&zN>E)emQD|9-t57bGy1K}E_WuCO;$lWwpDPiF`VpL z_HyZTp-GPFPE8C^Ka%EGz0y3te3cMOuTI>a8$Ty$6+c@irW<9^e|d@LWZ^r}R}(lM zn}+`Q@#V7rUT5p6Tk`METes(vS8glo^KQ-xhL9f@LfMa%tYAobr1<^i<>RY8mka%P z6uv%guI}}~EgKEo@9(SK8s%`Vn_a#p;C9-_OYxb<-v#9H&$Zaim-FeO(kGUMeGEG) zx_r+X%!zqiz%n&vYu82%wT^aGK|w9E^%quTwgy~s@epw6KCCxIpXsmGa*st$1?>EC zK6|Ubn@v0GskZlE0Mq5ep+24a-TUQy`)xjTl$y-XvIQ+w=`ou7WCs^F)0Vv3pq0k; zj$g$Vgk}ddgz4P!krL>>CR=u7QRr)fj#-IG7tMmTB6Ons9;^}Cm~r=juTGTtU)HP9 zpdAXzrV}>2iG8%#R&~Ym2+KZ(+&iH;rj4LDV$jWY;5&RQlCdpY8NNk9RDSJ`jM#~$ zpuL8mBGZGJvshMvRmo!I?Hwgsyb2r)N?rt5J)cuNN3Qb7*7foG<1F1K*xWid*IN3z zRne0ZEgv%`dPJ-DN}KbwxMzF4>NUNxK0bc8m`a1BS&qb1z1XOmd^We1&##L*z1Z{~ z!)Z2;2Htl|{Q`=PPhqG&H^=hW@7klH;TP1-R9f9=z^+-o)e zem<{mwJ4nw@fH|vZUM(?EA15G|SHze9NeZIaX()ewF!guDL!@rLjnOF3v8GhuS82NhX z^telQJUjFnIQO+Y*4>Y(`EzS(ji2bi82NQ!#0 zZaG)3A!!l;%zimrj?}^sCIN>jqFzZaj;LL4T$vVOrgxU(mxaJ(&L+{EH3gSv z<*d7H!nUs_>*^}k>}zWR&0NLwL|l(d_t?YwUHW2})ynAYdN-%&b0w{68695viy+LfyGxe zv1@Vgqx_I`*P_=?PfypL9=0k(Q(tqJ-ibqMD}4g4&%4}E20kw;e7$g^l;caqTNY-X zsX5R1nKBsr9(2E6HSNY4)rq+W7P`7lt2iAR9_#9{JYr+g(F%W8+5DGY_s_*=%yDlB zTd2es_4r<(nMcZjCWhRPH}XT8s<|#`iOzFNy11%*am1F3rOFPg-3>NoJnsIGbn)2E zw1^4Y{dI2L*s2NI%HYeKbg|2IwktEV&XLc3Q_W-xntx`^xNNG>o|t6-inHD;4Ki&z z-*O6do0YM4f%XJHvO2MbF;SM|wZddJaO8YjA5rUHD4P+(UXsCI|LWLc){RmobF4~V zX{6uUS8M(D-rnfyRYhq(1awmH5q{PJ9Dg;Kz_oXEEZySCmsm!Eub(HrX@hkK<=H_4i1?d}v-uaf*; z8ZTh>{dSt5*}CSf+1G`dl1iBP*65u`dVX&1F)k)Eo2Z!Y&(6-?n(cVBnM+hFO>)vf`d8kMdI{yBB4<)U9MmxU(`?{nw*z{TRK|H^dKI+m;)>p38a9QMQcc zsKa}|-+TR*bMETnYAn~&Y|R|Hy99T&JfFcnb#dvK4b$Do7&ZI#HqVN< z#csUGnhPpFKNI9K*~P^(XQpww6>l>0mK8#m_EZ+Xwb?OU?XYu0*g_{zE{m9-#2B^p z8B503-KSnG7VQpO>kzCdy)onJ(wrpL-6!gjF0N8Od}L8*`-3jm)s;*~O9iZLI$t}) z8=rC842pH>m5kuReKH%UJ-*mh=+WZP`pS;}YsdZwED!~qWph@0%h_o{kQ0G&=ZWMg zw{JvkZG^r4ZN-R9l`f1Cbe-8px5 zY&_0dI`Q%G{>zt^dfRT=FXy5i5tFXV&j_I7_QEyq;%}$n^Ku>-EQemhghs ziZRs(s4FChYlof5lq-36d;9vbo|MNIAJy&ue9roo>HnY4=bMzhiO8Mp*vux#@z2U} zDmRPFiz7!Cvo8NNJAdCrHoF8_4u^{;Cn~#dxoUeseBbXWQ$*fcY%XIPwH%+ z$qGrE-TURX)cvhWy}ZnKwva}g)ycx6lB^lN(T71L?e-t8j8R|ZL>7d;URJYoR>+1G z|9Tnr9$i#=z+z*@Vr_q&sK3iV`%UFHX2>#l>O`?WjVu!doojGntrF(^kdf33+v#+_cT%Q*#Tr=FXbsrSN<;OXR)N zQ#2RPWnWUmqPv5m<9a}XKvt-TSc%ygy@%Ozi7IoXK4pU`tSo3{q zc-%!l_J0dKOOg}h3d-1dBpg=ES}1gf{RQaEty5;=F$Ihd-PM=REt3jev%;^vec#p9 z;mbig`Hyd81PujCFpK%@I{X~8aW!;ZOytdye}8^%srZAm&7FPS=)$YT zP9^;2cAGU*KmT~#FRfg>Au~s5TZ#Ee^>?owPJUB+oOr>tZ$XILZper)PzPMKsjq@A>Ff^=9MoE#LPzG%_9g zdMjpo=H39EciUIC^cN-(VnPr2@vBF6!dwn&fXMg3oa4W;};xV(8J!eJ0 zCraFNYY1Cw&3Nm^chM@Y3tg_Sw>1l0h}MbPI_c;l-vews30$C3jbU40O*^Rk>3z@F z{r?51Uix@Hwa*x|Y}YDp;pBUu6~f<^ zM&POMp5V(?_p0C9R{j6`ee2l+vMy7mcnK(c`1}37bma*%G2I;ptr+WzpPZO@>y(h0 z19#Meo9Xk{&Rt#h_EyQmR`IOOFQ@Cr&yzCES|WSA?O95(L|d0s^tK$syFqWCFJN95 zwbe_yr-5s2g;>mLsW-Q_o}R%_ePu;p$hsKI<=LCw@*HE;N?NfbbahzC=d$T|Ksmea# zZ0F|pMR)vDlhb7qtNZ&ke3n6CQ>NA9=W2fIdG~U^uKaw~eD3B?$^r|0XPez>V4bqt z{X+Zd1x8HkeAQ1Lv2r-TF4Xdx<>X8Eez~=|$%hxq8mIBBjbE?#zL#@C@hy=(lQkB& zuDeFlGkN-uq!w(iT@Kdr~srt(B^D1F>sDC&9RK--Rdrduv?(>OD7uP1T2 zZIC?9mXUjmbJbbMF`wJ7^R;i%@8Bz*n!S}K}vy98!8w|Q%em|dIf9YT|`&8$H?0Ju3rNmG3=Ddh~KBt%ubli*C;mq3&+q+(S zv1Q)bVQBmDi14kYI?>zCXrEszEL|lo!Ds*X%jB{-k(<-NEv#IhFYEebt%Kg*+xrr< zSuv>a;h|Q}pNmSH=A3+eZSCc4FQLagJdT=EyQRY5_xJbjYaSk(%65gpU`s}z`hr$|na(fVqFPUu>@PlV zd;N#%mdej*yFQ<@{u=L8pR?rdZ+^+^k9f9dNHTgeJasB+dcb(RIbAF8V>oNn?CK0F zgUyUlv$?pL*?1IYbJ$*Ofv&9n6|u>0vETeF1f!zg)FG6CbiLt?{JUSB{P8qr3_Ir4vHQO`ycB*xGt(0 z)>QR+`;STV1eWs>Q1J-)uSk1=Y( z?cF6WA5Gj4oAUeH+r}55#)hF@@|6{Vx6F2c&LUZ^?B1twTd^SJ)02~13Lm?r&gRzJ z(ePpMz;kg zw?wtW0zikQ-0%4)Qgiw1i;Ki_+eVcxRTYwDtpkB3?w49chu7csfRe~fXX-@#QDvoXVTCm-nK3$u+Gcja6*W*l8qdK)ydA6*aXV`z)z zev^~_c#-QuTK@5ZY*1PS?eGStyqmWTnYJ9V3V0*&@!~3ZRft?phQo{4$|<)%hi#rc zwzy=`$_;#zT1o{@pY49LIrp|%|F3UH#p5;Fxs@2rlo|vyTP7)6d8*0f3S7^v?r+-k zCda8@;>Wy?kB)Bh?%pZ;ZtKdVwzH+|Q-AD^+L{GA(CPEn1<3(xm_u3|HgvC634Qeb z&MB*cy`C@h9adjk#IW`@pYVdv*ZvLH67yAm8JtO6slE%8AJ{hbFX~kUb(p8`RdrY$ zwt5HOm*{iPL~o^C?t1O8R<%%8qja*{O6}<%mcLpPZ6+#qq;$c(8c+aaGe6k41ytcqQ;0VS!Yis&WJKEl2~@Zcf!w%!=8uWiub)xpic&kKEUAZOZwzLS%u^o`nzG)mMD`@Zm6jw(zw0(yO81cINSo zj53qsK2&xJsc!K+agl9lazNrC*Swui#iXoCR&2Y(pw+jasogJUo%KYA?p4w&7A=^x z!0YCX9ee?kIF@#n6djMX%{=H-EB)ns@Yb-sCSM&6Srt@DlqUmzVkadS9=zOFF^<+9zG+>o|9xwXVs7bDe88Oz>FX zy4F%8qi6dKhTN;GLODxp=axVhNf+(}m5(zI99a>0nhjKzO^a3yHfFpPv1QstchI4s zJHHy7aa2{`)#duTGpyHa{RPK6?F)_xEGWIa1aj2q)l;C3Z0|PfoP7yLN*C0vJ5svf zg0J6`i&iz?J6umo3CORlV&}ZDs#MtymilLbd&|nE6JE!#TZBYyOmbbPV=mXSR1?%U z7CUVxB%iAjb^2&~BEzj-Y4c0o`g>EBcb6IB57v0Ldu&m_erCVlZH+TQ~{@#CU_Vu!zDy#QJZ_fjr_mOd1Of#q@ zYHQZgT;sGiyT9C)f8A58)^e}($qB(*n^L)D?P_l9P?*m4pkdjPB?4X*t5vo$y)C^S z>wezmbI+$;juZZWyPdx>et%t*X!G$5q2K^#rN*-F@9u7?{ayBUQu_IMz7-9nL7UTj ztKM$C?lXO!$^6Ap$M*mKn{QR|VF75EQCid?LFXmQRGC-T*1qQ1V!-C(sO2X<=bmJC z?ZZ~_EzY^OwscyRy;+evS-sxSYTovSLQhV{sK;7DHx#xrz0KWz*KLLLvfoXf%Yw`t zS#@sY%$*ZveCF9Kfrsr5y|ur;-82c`#`xfYgO9pt)|CZUSBGccJ~P|AU&<`!1gj`ZJs$g){mcg zB``BanWu7J*!A-KjcCXaCHTziM^*ucy@Hl{nkF-QwYX<T%udiUlWGwkW&z zEiu1ebD24CYE0S9)VK)iRrxDe;%9xoUthnDDT?LUo#OMhA)8V>pO&7TWqR5;PC#X; z&>QPF_H!9lOxKNGmOI&CL20eVM2#m*<=(agrXDRWg$@@%z>nUlVp+vbox zi#uO-N7>vPArS^=o6k??%n;mbUh|`1t!vuk*9scbJ&iWFr_VzumUH6!_Y`ibTct1gPjXS{VIe+J~(#lNc=bEWhw zdV}g&`QOjDeePuAUe=7{{h5jjY`d?m@$9V?csDcRnxn0s_>L2qdmT&PU0+{s_4>ht zq7x4y*q^;#7r0pM^5eSear0tl9eL%G`sc8x@6Pk36Zp2={&(c0Y=c?<w;S4%9mU?YE$WQyx;Cu$1=aUZWk7NIoaImJF{4KO-}Oywh*(6oo7q! zzg%#R^3yu@{(nUMs=&Q%X{$`CHhg#9(%Y;Nvm|nJ8jIfAzWTitACshx+dP?TUADs~ z^`d#N*Sx4{pTxD2a( z(d@eB5ikBr9*|ope3_>?ypG;l_Cj|1)l&x`S6HxT9XM3_U^oAV=oog(4|1m`xiT+m zoV Date: Mon, 11 May 2026 16:12:03 +0800 Subject: [PATCH 173/328] update readme --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 8229f4b0..b1b162df 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,12 @@ Thanks to everyone who has sponsored this project: tonybanters + + +
+ vinthara +
+ From f1970d772a45c2e98dce04002b8c45f295119473 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 11 May 2026 17:45:07 +0800 Subject: [PATCH 174/328] update docs --- docs/configuration/miscellaneous.md | 1 - docs/window-management/layouts.md | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/configuration/miscellaneous.md b/docs/configuration/miscellaneous.md index de95f358..e1be2907 100644 --- a/docs/configuration/miscellaneous.md +++ b/docs/configuration/miscellaneous.md @@ -37,7 +37,6 @@ description: Advanced settings for XWayland, focus behavior, and system integrat | `view_current_to_back` | `0` | Toggling the current tag switches back to the previously viewed tag. | | `scratchpad_cross_monitor` | `0` | Share the scratchpad pool across all monitors. | | `single_scratchpad` | `1` | Only allow one scratchpad (named or standard) to be visible at a time. | -| `circle_layout` | - | A comma-separated list of layouts `switch_layout` cycles through,the value sample:`tile,scroller`. | ## Window Behavior diff --git a/docs/window-management/layouts.md b/docs/window-management/layouts.md index 9b0b8537..b5f14a4d 100644 --- a/docs/window-management/layouts.md +++ b/docs/window-management/layouts.md @@ -113,6 +113,9 @@ dwindle_drop_simple_split=1 --- ## Switching Layouts +| Setting | Default | Description | +| :--- | :--- | :--- | +| `circle_layout` | - | A comma-separated list of layouts `switch_layout` cycles through,the value sample:`tile,scroller`. | You can switch layouts dynamically or set a default for specific tags using [Tag Rules](/docs/window-management/rules#tag-rules). @@ -120,6 +123,7 @@ You can switch layouts dynamically or set a default for specific tags using [Tag ```ini # Cycle through layouts +circle_layout=grid,scroller,tile bind=SUPER,n,switch_layout # Set specific layout From 4fbc257ff2864a42e14fbb93b775c4ba45747fdd Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 11 May 2026 22:39:16 +0800 Subject: [PATCH 175/328] fix: miss re-arrange after exchange --- src/mango.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mango.c b/src/mango.c index 2a972aae..42f37539 100644 --- a/src/mango.c +++ b/src/mango.c @@ -5091,6 +5091,7 @@ void exchange_two_client(Client *c1, Client *c2) { } sync_scroller_state_to_clients(m1, tag1); + arrange(m1, false, false); } else { /* 不同堆叠:交换两个堆叠整体位置 */ if (n1 != head1 || n2 != head2) { @@ -5166,8 +5167,8 @@ exchange_common: dwindle_swap_clients( &c1->mon->pertag->dwindle_root[c1->mon->pertag->curtag], c1, c2); - arrange(c1->mon, false, false); } + arrange(c1->mon, false, false); } // In order to facilitate repeated exchanges for get_focused_stack_client From 398232fe0233c2e49f0526318fa7da9d1d2eca5e Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 12 May 2026 09:01:29 +0800 Subject: [PATCH 176/328] opt: more smooth resieze when drag stack window --- src/layout/arrange.h | 302 +++++++++++++++++++++++++------------------ 1 file changed, 174 insertions(+), 128 deletions(-) diff --git a/src/layout/arrange.h b/src/layout/arrange.h index 56fda657..41598d5e 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -122,14 +122,13 @@ void resize_tile_master_horizontal(Client *grabc, bool isdrag, int32_t offsetx, bool begin_find_nextnext = false; bool begin_find_prevprev = false; - // 从当前节点的下一个开始遍历 + /* 寻找 next / nextnext */ for (node = grabc->link.next; node != &clients; node = node->next) { tc = wl_container_of(node, tc, link); if (begin_find_nextnext && VISIBLEON(tc, grabc->mon) && ISTILED(tc)) { nextnext = tc; break; } - if (!begin_find_nextnext && VISIBLEON(tc, grabc->mon) && ISTILED(tc)) { next = tc; begin_find_nextnext = true; @@ -137,15 +136,13 @@ void resize_tile_master_horizontal(Client *grabc, bool isdrag, int32_t offsetx, } } - // 从当前节点的上一个开始遍历 + /* 寻找 prev / prevprev */ for (node = grabc->link.prev; node != &clients; node = node->prev) { tc = wl_container_of(node, tc, link); - if (begin_find_prevprev && VISIBLEON(tc, grabc->mon) && ISTILED(tc)) { prevprev = tc; break; } - if (!begin_find_prevprev && VISIBLEON(tc, grabc->mon) && ISTILED(tc)) { prev = tc; begin_find_prevprev = true; @@ -157,7 +154,6 @@ void resize_tile_master_horizontal(Client *grabc, bool isdrag, int32_t offsetx, drag_begin_cursorx = cursor->x; drag_begin_cursory = cursor->y; start_drag_window = true; - // 记录初始状态 grabc->old_master_mfact_per = grabc->master_mfact_per; grabc->old_master_inner_per = grabc->master_inner_per; grabc->old_stack_inner_per = grabc->stack_inner_per; @@ -165,12 +161,9 @@ void resize_tile_master_horizontal(Client *grabc, bool isdrag, int32_t offsetx, cursor->y < grabc->geom.y + grabc->geom.height / 2; grabc->cursor_in_left_half = cursor->x < grabc->geom.x + grabc->geom.width / 2; - // 记录初始几何信息 grabc->drag_begin_geom = grabc->geom; } else { - // 计算相对于屏幕尺寸的比例变化 if (isdrag) { - offsetx = cursor->x - drag_begin_cursorx; offsety = cursor->y - drag_begin_cursory; } else { @@ -193,97 +186,122 @@ void resize_tile_master_horizontal(Client *grabc, bool isdrag, int32_t offsetx, delta_y = (float)(offsety) * (grabc->old_stack_inner_per) / grabc->drag_begin_geom.height; } - bool moving_up; - bool moving_down; + bool moving_up, moving_down; if (!isdrag) { - moving_up = offsety < 0 ? true : false; - moving_down = offsety > 0 ? true : false; + moving_up = offsety < 0; + moving_down = offsety > 0; } else { moving_up = cursor->y < drag_begin_cursory; moving_down = cursor->y > drag_begin_cursory; } if (grabc->ismaster && !prev) { - if (moving_up) { + if (moving_up) delta_y = -fabsf(delta_y); - } else { + else delta_y = fabsf(delta_y); - } } else if (grabc->ismaster && next && !next->ismaster) { - if (moving_up) { + if (moving_up) delta_y = fabsf(delta_y); - } else { + else delta_y = -fabsf(delta_y); - } } else if (!grabc->ismaster && prev && prev->ismaster) { - if (moving_up) { + if (moving_up) delta_y = -fabsf(delta_y); - } else { + else delta_y = fabsf(delta_y); - } } else if (!grabc->ismaster && !next) { - if (moving_up) { + if (moving_up) delta_y = fabsf(delta_y); - } else { + else delta_y = -fabsf(delta_y); - } } else if (type == CENTER_TILE && !grabc->ismaster && !nextnext) { - if (moving_up) { + if (moving_up) delta_y = fabsf(delta_y); - } else { + else delta_y = -fabsf(delta_y); - } } else if (type == CENTER_TILE && !grabc->ismaster && prevprev && prevprev->ismaster) { - if (moving_up) { + if (moving_up) delta_y = -fabsf(delta_y); - } else { + else delta_y = fabsf(delta_y); - } } else if ((grabc->cursor_in_upper_half && moving_up) || (!grabc->cursor_in_upper_half && moving_down)) { - // 光标在窗口上方且向上移动,或在窗口下方且向下移动 → 增加高度 - delta_y = fabsf(delta_y); - delta_y = delta_y * 2; + delta_y = fabsf(delta_y) * 2; } else { - // 其他情况 → 减小高度 - delta_y = -fabsf(delta_y); - delta_y = delta_y * 2; + delta_y = -fabsf(delta_y) * 2; } - if (!grabc->ismaster && grabc->isleftstack && type == CENTER_TILE) { + if (!grabc->ismaster && grabc->isleftstack && type == CENTER_TILE) delta_x = delta_x * -1.0f; - } - if (grabc->ismaster && type == CENTER_TILE && - grabc->cursor_in_left_half) { + grabc->cursor_in_left_half) delta_x = delta_x * -1.0f; - } - - if (grabc->ismaster && type == CENTER_TILE) { + if (grabc->ismaster && type == CENTER_TILE) delta_x = delta_x * 2; - } - - if (type == RIGHT_TILE) { + if (type == RIGHT_TILE) delta_x = delta_x * -1.0f; - } - // 直接设置新的比例,基于初始值 + 变化量 float new_master_mfact_per = grabc->old_master_mfact_per + delta_x; float new_master_inner_per = grabc->old_master_inner_per + delta_y; float new_stack_inner_per = grabc->old_stack_inner_per + delta_y; - // 应用限制,确保比例在合理范围内 new_master_mfact_per = fmaxf(0.1f, fminf(0.9f, new_master_mfact_per)); new_master_inner_per = fmaxf(0.1f, fminf(0.9f, new_master_inner_per)); new_stack_inner_per = fmaxf(0.1f, fminf(0.9f, new_stack_inner_per)); - // 应用到所有平铺窗口 - wl_list_for_each(tc, &clients, link) { - if (VISIBLEON(tc, grabc->mon) && ISTILED(tc)) { - - if (!isdrag && tc != grabc) { + // 实时缩放同组其他窗口的比例,保持组内总和为 1, + // 不然增加的比例并不是排布后的比例 + if (isdrag) { + if (grabc->ismaster) { + /* 主窗口组:调整所有主窗口的 master_inner_per */ + float cur_other_sum = 1.0f - grabc->master_inner_per; + float new_other_sum = 1.0f - new_master_inner_per; + if (cur_other_sum > 0.001f) { + float scale = new_other_sum / cur_other_sum; + wl_list_for_each(tc, &clients, link) { + if (VISIBLEON(tc, grabc->mon) && ISTILED(tc) && + tc->ismaster && tc != grabc) + tc->master_inner_per *= scale; + } + } + } else { + /* 栈窗口组:根据布局类型分开处理 */ + if (type == CENTER_TILE) { + /* 仅缩放同侧栈窗口的 stack_inner_per */ + float cur_other_sum = 1.0f - grabc->stack_inner_per; + float new_other_sum = 1.0f - new_stack_inner_per; + if (cur_other_sum > 0.001f) { + float scale = new_other_sum / cur_other_sum; + wl_list_for_each(tc, &clients, link) { + if (VISIBLEON(tc, grabc->mon) && ISTILED(tc) && + !tc->ismaster && tc != grabc && + tc->isleftstack == grabc->isleftstack) + tc->stack_inner_per *= scale; + } + } + } else { + /* TILE / RIGHT_TILE / DECK:所有栈窗口共用一个比例组 */ + float cur_other_sum = 1.0f - grabc->stack_inner_per; + float new_other_sum = 1.0f - new_stack_inner_per; + if (cur_other_sum > 0.001f) { + float scale = new_other_sum / cur_other_sum; + wl_list_for_each(tc, &clients, link) { + if (VISIBLEON(tc, grabc->mon) && ISTILED(tc) && + !tc->ismaster && tc != grabc) + tc->stack_inner_per *= scale; + } + } + } + } + } else { + /* 键盘步进 */ + wl_list_for_each(tc, &clients, link) { + if (!VISIBLEON(tc, grabc->mon) || !ISTILED(tc)) + continue; + if (tc != grabc) { if (!tc->ismaster && new_stack_inner_per != 1.0f && grabc->old_stack_inner_per != 1.0f && (type != CENTER_TILE || @@ -298,14 +316,19 @@ void resize_tile_master_horizontal(Client *grabc, bool isdrag, int32_t offsetx, (1.0f - grabc->old_master_inner_per) * tc->master_inner_per; } - - tc->master_mfact_per = new_master_mfact_per; } } + /* 将新比例应用到抓取窗口本身 */ grabc->master_inner_per = new_master_inner_per; grabc->stack_inner_per = new_stack_inner_per; + /* 广播 master_mfact_per 到所有平铺窗口 */ + wl_list_for_each(tc, &clients, link) { + if (VISIBLEON(tc, grabc->mon) && ISTILED(tc)) + tc->master_mfact_per = new_master_mfact_per; + } + if (!isdrag) { arrange(grabc->mon, false, false); return; @@ -327,20 +350,18 @@ void resize_tile_master_vertical(Client *grabc, bool isdrag, int32_t offsetx, Client *prev = NULL; struct wl_list *node; - // 从当前节点的下一个开始遍历 + /* 寻找 next */ for (node = grabc->link.next; node != &clients; node = node->next) { tc = wl_container_of(node, tc, link); - if (VISIBLEON(tc, grabc->mon) && ISTILED(tc)) { next = tc; break; } } - // 从当前节点的上一个开始遍历 + /* 寻找 prev */ for (node = grabc->link.prev; node != &clients; node = node->prev) { tc = wl_container_of(node, tc, link); - if (VISIBLEON(tc, grabc->mon) && ISTILED(tc)) { prev = tc; break; @@ -351,8 +372,6 @@ void resize_tile_master_vertical(Client *grabc, bool isdrag, int32_t offsetx, drag_begin_cursorx = cursor->x; drag_begin_cursory = cursor->y; start_drag_window = true; - - // 记录初始状态 grabc->old_master_mfact_per = grabc->master_mfact_per; grabc->old_master_inner_per = grabc->master_inner_per; grabc->old_stack_inner_per = grabc->stack_inner_per; @@ -360,13 +379,9 @@ void resize_tile_master_vertical(Client *grabc, bool isdrag, int32_t offsetx, cursor->y < grabc->geom.y + grabc->geom.height / 2; grabc->cursor_in_left_half = cursor->x < grabc->geom.x + grabc->geom.width / 2; - // 记录初始几何信息 grabc->drag_begin_geom = grabc->geom; } else { - // 计算相对于屏幕尺寸的比例变化 - // 计算相对于屏幕尺寸的比例变化 if (isdrag) { - offsetx = cursor->x - drag_begin_cursorx; offsety = cursor->y - drag_begin_cursory; } else { @@ -379,7 +394,6 @@ void resize_tile_master_vertical(Client *grabc, bool isdrag, int32_t offsetx, } if (grabc->ismaster) { - // 垂直版本:左右移动调整高度比例,上下移动调整宽度比例 delta_x = (float)(offsetx) * (grabc->old_master_inner_per) / grabc->drag_begin_geom.width; delta_y = (float)(offsety) * (grabc->old_master_mfact_per) / @@ -391,70 +405,84 @@ void resize_tile_master_vertical(Client *grabc, bool isdrag, int32_t offsetx, grabc->drag_begin_geom.height; } - bool moving_left; - bool moving_right; - + bool moving_left, moving_right; if (!isdrag) { - moving_left = offsetx < 0 ? true : false; - moving_right = offsetx > 0 ? true : false; + moving_left = offsetx < 0; + moving_right = offsetx > 0; } else { moving_left = cursor->x < drag_begin_cursorx; moving_right = cursor->x > drag_begin_cursorx; } - // 调整主区域和栈区域的高度比例(垂直分割) if (grabc->ismaster && !prev) { - if (moving_left) { - delta_x = -fabsf(delta_x); // 向上移动减少主区域高度 - } else { - delta_x = fabsf(delta_x); // 向下移动增加主区域高度 - } + if (moving_left) + delta_x = -fabsf(delta_x); + else + delta_x = fabsf(delta_x); } else if (grabc->ismaster && next && !next->ismaster) { - if (moving_left) { - delta_x = fabsf(delta_x); // 向上移动增加主区域高度 - } else { - delta_x = -fabsf(delta_x); // 向下移动减少主区域高度 - } + if (moving_left) + delta_x = fabsf(delta_x); + else + delta_x = -fabsf(delta_x); } else if (!grabc->ismaster && prev && prev->ismaster) { - if (moving_left) { - delta_x = -fabsf(delta_x); // 向上移动减少栈区域高度 - } else { - delta_x = fabsf(delta_x); // 向下移动增加栈区域高度 - } + if (moving_left) + delta_x = -fabsf(delta_x); + else + delta_x = fabsf(delta_x); } else if (!grabc->ismaster && !next) { - if (moving_left) { - delta_x = fabsf(delta_x); // 向上移动增加栈区域高度 - } else { - delta_x = -fabsf(delta_x); // 向下移动减少栈区域高度 - } + if (moving_left) + delta_x = fabsf(delta_x); + else + delta_x = -fabsf(delta_x); } else if ((grabc->cursor_in_left_half && moving_left) || (!grabc->cursor_in_left_half && moving_right)) { - // 光标在窗口左侧且向左移动,或在窗口右侧且向右移动 → 增加宽度 - delta_x = fabsf(delta_x); - delta_x = delta_x * 2; + delta_x = fabsf(delta_x) * 2; } else { - // 其他情况 → 减小宽度 - delta_x = -fabsf(delta_x); - delta_x = delta_x * 2; + delta_x = -fabsf(delta_x) * 2; } - // 直接设置新的比例,基于初始值 + 变化量 - float new_master_mfact_per = grabc->old_master_mfact_per + - delta_y; // 垂直:delta_y调整主区域高度 - float new_master_inner_per = grabc->old_master_inner_per + - delta_x; // 垂直:delta_x调整主区域内部宽度 - float new_stack_inner_per = grabc->old_stack_inner_per + - delta_x; // 垂直:delta_x调整栈区域内部宽度 + float new_master_mfact_per = grabc->old_master_mfact_per + delta_y; + float new_master_inner_per = grabc->old_master_inner_per + delta_x; + float new_stack_inner_per = grabc->old_stack_inner_per + delta_x; - // 应用限制,确保比例在合理范围内 new_master_mfact_per = fmaxf(0.1f, fminf(0.9f, new_master_mfact_per)); new_master_inner_per = fmaxf(0.1f, fminf(0.9f, new_master_inner_per)); new_stack_inner_per = fmaxf(0.1f, fminf(0.9f, new_stack_inner_per)); - // 应用到所有平铺窗口 - wl_list_for_each(tc, &clients, link) { - if (VISIBLEON(tc, grabc->mon) && ISTILED(tc)) { - if (!isdrag && tc != grabc && type != CENTER_TILE) { + // 实时缩放同组其他窗口的比例,保持组内总和为 1, + // 不然增加的比例并不是排布后的比例 + + if (isdrag) { + if (grabc->ismaster) { + float cur_other_sum = 1.0f - grabc->master_inner_per; + float new_other_sum = 1.0f - new_master_inner_per; + if (cur_other_sum > 0.001f) { + float scale = new_other_sum / cur_other_sum; + wl_list_for_each(tc, &clients, link) { + if (VISIBLEON(tc, grabc->mon) && ISTILED(tc) && + tc->ismaster && tc != grabc) + tc->master_inner_per *= scale; + } + } + } else { + /* 所有栈窗口(垂直布局没有左侧/右侧区分) */ + float cur_other_sum = 1.0f - grabc->stack_inner_per; + float new_other_sum = 1.0f - new_stack_inner_per; + if (cur_other_sum > 0.001f) { + float scale = new_other_sum / cur_other_sum; + wl_list_for_each(tc, &clients, link) { + if (VISIBLEON(tc, grabc->mon) && ISTILED(tc) && + !tc->ismaster && tc != grabc) + tc->stack_inner_per *= scale; + } + } + } + } else { + /* 键盘步进 */ + wl_list_for_each(tc, &clients, link) { + if (!VISIBLEON(tc, grabc->mon) || !ISTILED(tc)) + continue; + if (tc != grabc) { if (!tc->ismaster && new_stack_inner_per != 1.0f && grabc->old_stack_inner_per != 1.0f) tc->stack_inner_per = (1 - new_stack_inner_per) / @@ -467,14 +495,18 @@ void resize_tile_master_vertical(Client *grabc, bool isdrag, int32_t offsetx, (1.0f - grabc->old_master_inner_per) * tc->master_inner_per; } - - tc->master_mfact_per = new_master_mfact_per; } } grabc->master_inner_per = new_master_inner_per; grabc->stack_inner_per = new_stack_inner_per; + /* 广播 master_mfact_per */ + wl_list_for_each(tc, &clients, link) { + if (VISIBLEON(tc, grabc->mon) && ISTILED(tc)) + tc->master_mfact_per = new_master_mfact_per; + } + if (!isdrag) { arrange(grabc->mon, false, false); return; @@ -665,24 +697,38 @@ void resize_tile_scroller(Client *grabc, bool isdrag, int32_t offsetx, fmaxf(0.1f, fminf(1.0f, new_scroller_proportion)); new_stack_proportion = fmaxf(0.1f, fminf(0.9f, new_stack_proportion)); - curnode->stack_proportion = new_stack_proportion; - headnode->scroller_proportion = new_scroller_proportion; - - /* 调整同一堆叠内其他节点的 stack_proportion */ - /* 调整整个堆叠内除当前窗口外的所有节点 */ - if (!isdrag && grabc->old_stack_proportion != 1.0f) { - for (struct ScrollerStackNode *tc = headnode; tc; - tc = tc->next_in_stack) { - if (tc != curnode) { - tc->stack_proportion = - (1.0f - new_stack_proportion) / - (1.0f - grabc->old_stack_proportion) * - tc->stack_proportion; + // 保持总和为 1,避免后续 arrange 归一化吞掉位移 + if (isdrag) { + float current_other_sum = 1.0f - curnode->stack_proportion; + float new_other_sum = 1.0f - new_stack_proportion; + if (current_other_sum > 0.001f) { + float scale = new_other_sum / current_other_sum; + for (struct ScrollerStackNode *tc = headnode; tc; + tc = tc->next_in_stack) { + if (tc != curnode) { + tc->stack_proportion *= scale; + } + } + } + } else { + // 键盘步进 + if (grabc->old_stack_proportion != 1.0f) { + for (struct ScrollerStackNode *tc = headnode; tc; + tc = tc->next_in_stack) { + if (tc != curnode) { + tc->stack_proportion = + (1.0f - new_stack_proportion) / + (1.0f - grabc->old_stack_proportion) * + tc->stack_proportion; + } } } } - /* 同步回全局字段*/ + curnode->stack_proportion = new_stack_proportion; + headnode->scroller_proportion = new_scroller_proportion; + + /* 同步回全局字段 */ sync_scroller_state_to_clients(m, tag); if (!isdrag) { From a25e2a9b1cb608e81d21a075f96349b1ceeafca7 Mon Sep 17 00:00:00 2001 From: Alessio Molinari Date: Wed, 21 Jan 2026 22:09:14 +0100 Subject: [PATCH 177/328] feat(window_rule): support width/height as fractions in window rules. --- src/config/parse_config.h | 8 ++++---- src/mango.c | 8 ++++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 15857ae4..b2229a0d 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -80,8 +80,8 @@ typedef struct { const char *monitor; int32_t offsetx; int32_t offsety; - int32_t width; - int32_t height; + float width; + float height; int32_t nofocus; int32_t nofadein; int32_t nofadeout; @@ -1805,9 +1805,9 @@ void parse_option(Config *config, char *key, char *value) { } else if (strcmp(key, "no_force_center") == 0) { rule->no_force_center = atoi(val); } else if (strcmp(key, "width") == 0) { - rule->width = atoi(val); + rule->width = atof(val); } else if (strcmp(key, "height") == 0) { - rule->height = atoi(val); + rule->height = atof(val); } else if (strcmp(key, "isnoborder") == 0) { rule->isnoborder = atoi(val); } else if (strcmp(key, "isnoshadow") == 0) { diff --git a/src/mango.c b/src/mango.c index f61e27f5..9ca1d416 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1360,10 +1360,14 @@ void applyrules(Client *c) { // set geometry of floating client - if (r->width > 0) + if (r->width > 1) c->float_geom.width = r->width; - if (r->height > 0) + else if (r->width > 0 && r->width <= 1) + c->float_geom.width = round(mon->m.width * r->width); + if (r->height > 1) c->float_geom.height = r->height; + else if (r->height > 0 && r->height <= 1) + c->float_geom.height = round(mon->m.height * r->height); if (r->width > 0 || r->height > 0) { c->iscustomsize = 1; From 129d3da44ece6580d03b69194f4b2fec6e33de07 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 12 May 2026 09:48:29 +0800 Subject: [PATCH 178/328] update docs --- docs/window-management/rules.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/window-management/rules.md b/docs/window-management/rules.md index 93e81eba..4a295157 100644 --- a/docs/window-management/rules.md +++ b/docs/window-management/rules.md @@ -40,8 +40,8 @@ windowrule=Parameter:Values,Parameter:Values,appid:Values,title:Values | Parameter | Type | Values | Description | | :--- | :--- | :--- | :--- | -| `width` | integer | 0-9999 | Window width when it becomes a floating window | -| `height` | integer | 0-9999 | Window height when it becomes a floating window | +| `width` | float | 0-9999 | Window width when it becomes a floating window,if the value below 1, it will be the percentage of the screen width,otherwise it will be the pixel value | +| `height` | float | 0-9999 | Window height when it becomes a floating window,if the value below 1, it will be the percentage of the screen height,otherwise it will be the pixel value | | `offsetx` | integer | -999-999 | X offset from center (%), 100 is the edge of screen with outer gap | | `offsety` | integer | -999-999 | Y offset from center (%), 100 is the edge of screen with outer gap | | `monitor` | string | Any | Assign to monitor by [monitor spec](/docs/configuration/monitors#monitor-spec-format) (name, make, model, or serial) | From 2b68d1b615eb09360dbfba90ef129082befae782 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 12 May 2026 14:23:47 +0800 Subject: [PATCH 179/328] opt: more reasonable multi-tag window animations --- src/animation/tag.h | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/animation/tag.h b/src/animation/tag.h index 18eef56a..1a973784 100644 --- a/src/animation/tag.h +++ b/src/animation/tag.h @@ -5,6 +5,16 @@ void set_tagin_animation(Monitor *m, Client *c) { return; } + if ((c->isglobal || c->isunglobal) || + (c->tags & (1 << (m->pertag->prevtag - 1)) && + c->tags & (1 << (m->pertag->curtag - 1)))) { + c->animation.tagouting = false; + c->animation.tagouted = false; + c->animation.tagining = false; + c->animation.action = MOVE; + return; + } + if (m->pertag->curtag > m->pertag->prevtag) { c->animainit_geom.x = config.tag_animation_direction == VERTICAL @@ -55,6 +65,17 @@ void set_arrange_visible(Monitor *m, Client *c, bool want_animation) { } void set_tagout_animation(Monitor *m, Client *c) { + + if ((c->isglobal || c->isunglobal) || + (c->tags & (1 << (m->pertag->prevtag - 1)) && + c->tags & (1 << (m->pertag->curtag - 1)))) { + c->animation.tagouting = false; + c->animation.tagouted = false; + c->animation.tagining = false; + c->animation.action = MOVE; + return; + } + if (m->pertag->curtag > m->pertag->prevtag) { c->pending = c->geom; c->pending.x = @@ -82,6 +103,7 @@ void set_tagout_animation(Monitor *m, Client *c) { } void set_arrange_hidden(Monitor *m, Client *c, bool want_animation) { + if ((c->tags & (1 << (m->pertag->prevtag - 1))) && m->pertag->prevtag != 0 && m->pertag->curtag != 0 && config.animations) { From 8eb0323e03b6d4df2785bf7d7968eb6a9b8f211d Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 12 May 2026 14:31:18 +0800 Subject: [PATCH 180/328] fix: shouldn't exchange tag if in same mon in exchange_two_client --- src/mango.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mango.c b/src/mango.c index 29a634aa..cf9bf6a3 100644 --- a/src/mango.c +++ b/src/mango.c @@ -5143,7 +5143,7 @@ exchange_common: tmp2_next->prev = &c1->link; } - if (config.exchange_cross_monitor) { + if (config.exchange_cross_monitor && c1->mon != c2->mon) { DwindleNode **c1_root = &m1->pertag->dwindle_root[m1->pertag->curtag]; DwindleNode *c1node = dwindle_find_leaf(*c1_root, c1); From 0a687f808d94764a22c8e7eb8375a5c0d1bfdfc7 Mon Sep 17 00:00:00 2001 From: Ruixi-rebirth Date: Tue, 12 May 2026 15:34:14 +0800 Subject: [PATCH 181/328] feat(nix): generate module options docs and sync to website - Add nix/generate-hm-options.nix to export HM and NixOS module options as JSON via nixosOptionsDoc - Expose hm-options-json and nixos-options-json packages in flake.nix - Add CI workflow to auto-build and push JSONs to mangowm.github.io on module changes - Add missing description to hm-modules enable option --- .github/workflows/sync-nix-options.yml | 59 ++++++++++++++++++++++++++ .gitignore | 1 + flake.nix | 8 ++++ nix/generate-options.nix | 28 ++++++++++++ nix/hm-modules.nix | 1 + 5 files changed, 97 insertions(+) create mode 100644 .github/workflows/sync-nix-options.yml create mode 100644 nix/generate-options.nix diff --git a/.github/workflows/sync-nix-options.yml b/.github/workflows/sync-nix-options.yml new file mode 100644 index 00000000..3e05f0e1 --- /dev/null +++ b/.github/workflows/sync-nix-options.yml @@ -0,0 +1,59 @@ +name: Sync Nix module options + +on: + push: + branches: [main] + paths: + - nix/hm-modules.nix + - nix/nixos-modules.nix + - nix/generate-options.nix + - flake.nix + - flake.lock + +concurrency: + group: sync-nix-options + cancel-in-progress: true + +jobs: + sync-nix-options: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - uses: cachix/install-nix-action@v30 + with: + extra_nix_config: | + experimental-features = nix-command flakes + + - name: Build options JSONs + run: | + HM_OUT=$(nix build .#hm-options-json --no-link --print-out-paths) + NIXOS_OUT=$(nix build .#nixos-options-json --no-link --print-out-paths) + cp "$HM_OUT/share/doc/nixos/options.json" /tmp/hm-options.json + cp "$NIXOS_OUT/share/doc/nixos/options.json" /tmp/nixos-options.json + + - name: Checkout website + uses: actions/checkout@v4 + with: + repository: mangowm/mangowm.github.io + path: website + token: ${{ secrets.WEBSITE_SYNC_TOKEN }} + fetch-depth: 1 + + - name: Copy JSONs to website + run: | + cp /tmp/hm-options.json website/apps/web/src/hm-options.json + cp /tmp/nixos-options.json website/apps/web/src/nixos-options.json + + - name: Commit and push + working-directory: website + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add apps/web/src/hm-options.json apps/web/src/nixos-options.json + git diff --staged --quiet || git commit \ + -m "nix: update module options JSON" \ + -m "${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}" + git push diff --git a/.gitignore b/.gitignore index 7681f948..274cc4ee 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /.cache /.vscode /result +/result-* config.h mango mango.o diff --git a/flake.nix b/flake.nix index b7158bbd..33b97c9b 100644 --- a/flake.nix +++ b/flake.nix @@ -43,6 +43,14 @@ }; packages = { inherit mango; + hm-options-json = pkgs.callPackage (import ./nix/generate-options.nix self) { + module = ./nix/hm-modules.nix; + optionPrefix = "wayland.windowManager.mango."; + }; + nixos-options-json = pkgs.callPackage (import ./nix/generate-options.nix self) { + module = ./nix/nixos-modules.nix; + optionPrefix = "programs.mango."; + }; }; devShells.default = mango.overrideAttrs shellOverride; formatter = pkgs.alejandra; diff --git a/nix/generate-options.nix b/nix/generate-options.nix new file mode 100644 index 00000000..c2b87a4f --- /dev/null +++ b/nix/generate-options.nix @@ -0,0 +1,28 @@ +self: +{ + pkgs, + lib ? pkgs.lib, + module, + optionPrefix, +}: +let + eval = lib.evalModules { + modules = [ + (import module self) + { _module.check = false; } + ]; + specialArgs = { inherit pkgs; }; + }; + + optionsDoc = pkgs.nixosOptionsDoc { + options = eval.options; + transformOptions = + opt: + opt + // { + visible = opt.visible && !opt.internal; + name = lib.removePrefix optionPrefix opt.name; + }; + }; +in +optionsDoc.optionsJSON diff --git a/nix/hm-modules.nix b/nix/hm-modules.nix index f00d9c68..f9a341a3 100644 --- a/nix/hm-modules.nix +++ b/nix/hm-modules.nix @@ -22,6 +22,7 @@ in enable = mkOption { type = types.bool; default = false; + description = "Whether to enable mangowm, a Wayland compositor based on dwl."; }; package = lib.mkOption { type = lib.types.package; From 3a1920f024ee9e09446729f2e5b2fdc42f45c6eb Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 12 May 2026 16:14:18 +0800 Subject: [PATCH 182/328] fix: error when move head out of stack in scroller layout --- src/dispatch/bind_define.h | 59 +++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 32 deletions(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index e4723a15..fd17d03e 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1808,15 +1808,25 @@ int32_t scroller_apply_stack(Client *c, Client *target_client, Monitor *m = c->mon; uint32_t tag = m->pertag->curtag; + + bool is_horizontal = (m->pertag->ltidxs[tag]->id == SCROLLER); + + if (is_horizontal && (direction == UP || direction == DOWN)) + return 0; + if (!is_horizontal && (direction == LEFT || direction == RIGHT)) + return 0; + struct TagScrollerState *st = ensure_scroller_state(m, tag); /* 获取当前节点 */ struct ScrollerStackNode *cnode = find_scroller_node(st, c); + + if (!cnode) + return 0; + struct ScrollerStackNode *tnode = target_client ? find_scroller_node(st, target_client) : NULL; - bool is_horizontal = (m->pertag->ltidxs[tag]->id == SCROLLER); - /* 若方向为 UNDIR 且有目标,直接插入到目标尾部 */ if (direction == UNDIR && target_client && target_client->mon == c->mon) { scroller_insert_stack(c, target_client, false); @@ -1824,39 +1834,25 @@ int32_t scroller_apply_stack(Client *c, Client *target_client, } /* 处理从堆叠中移出的情况(方向 LEFT/UP 或 RIGHT/DOWN) */ - if (cnode && (cnode->prev_in_stack || cnode->next_in_stack)) { - bool to_left_or_up = (is_horizontal && direction == LEFT) || - (!is_horizontal && direction == UP); - bool to_right_or_down = (is_horizontal && direction == RIGHT) || - (!is_horizontal && direction == DOWN); - - if (to_left_or_up || to_right_or_down) { - /* 找到当前堆叠的头节点,以便移动全局链表位置 */ - struct ScrollerStackNode *head = cnode; - while (head->prev_in_stack) - head = head->prev_in_stack; - Client *source_stack_head = head->client; - - /* 从 tag 状态中移除该客户端对应的节点 */ - scroller_node_remove(st, cnode); - /* 重新创建一个独立的节点(无堆叠关系) */ - scroller_node_create(st, c); - - /* 调整全局客户端链表顺序:移到源堆叠头的前面或后面 */ + if (cnode->prev_in_stack || cnode->next_in_stack) { + struct ScrollerStackNode *move_out_refer_node = + cnode->prev_in_stack ? cnode->prev_in_stack : cnode->next_in_stack; + scroller_node_remove(st, cnode); + Client *stack_head = + scroll_get_stack_head_client(move_out_refer_node->client); + if (direction == LEFT || direction == UP) { wl_list_remove(&c->link); - if (to_left_or_up) - wl_list_insert(source_stack_head->link.prev, &c->link); - else - wl_list_insert(&source_stack_head->link, &c->link); - - /* 同步到客户端字段并重排 */ - sync_scroller_state_to_clients(m, tag); - arrange(m, false, false); - return 0; + wl_list_insert(stack_head->link.prev, &c->link); + } else if (direction == RIGHT || direction == DOWN) { + wl_list_remove(&c->link); + wl_list_insert(&stack_head->link, &c->link); } + sync_scroller_state_to_clients(m, tag); + arrange(m, false, false); + return 0; } - if (!target_client || target_client->mon != c->mon) + if (!tnode || target_client->mon != c->mon) return 0; /* 找到目标堆叠的尾部节点 */ @@ -1866,7 +1862,6 @@ int32_t scroller_apply_stack(Client *c, Client *target_client, /* 通过封装好的插入函数实现(尾部插入) */ scroller_insert_stack(c, tail->client, false); - return 0; } From 30f00ba50a01112e62265945157ddaf16bb30c26 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 12 May 2026 16:30:26 +0800 Subject: [PATCH 183/328] opt: optimize scroller insert reset --- src/dispatch/bind_define.h | 25 +++++++++++++++++---- src/layout/scroll.h | 45 +++++++++++++++++++++++++++++++------- src/mango.c | 1 + 3 files changed, 59 insertions(+), 12 deletions(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index fd17d03e..ffeff6f0 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1838,14 +1838,26 @@ int32_t scroller_apply_stack(Client *c, Client *target_client, struct ScrollerStackNode *move_out_refer_node = cnode->prev_in_stack ? cnode->prev_in_stack : cnode->next_in_stack; scroller_node_remove(st, cnode); + + // 必须先更新,不然里面节点还存着的是cnode的信息, + // 会造成stach_head/stack_tail指向的客户端不对 + update_scroller_state(c->mon); + Client *stack_head = scroll_get_stack_head_client(move_out_refer_node->client); + Client *stack_tail = + scroll_get_stack_tail_client(move_out_refer_node->client); + if (direction == LEFT || direction == UP) { - wl_list_remove(&c->link); - wl_list_insert(stack_head->link.prev, &c->link); + if (c != stack_head) { + wl_list_remove(&c->link); + wl_list_insert(stack_head->link.prev, &c->link); + } } else if (direction == RIGHT || direction == DOWN) { - wl_list_remove(&c->link); - wl_list_insert(&stack_head->link, &c->link); + if (c != stack_tail) { + wl_list_remove(&c->link); + wl_list_insert(&stack_tail->link, &c->link); + } } sync_scroller_state_to_clients(m, tag); arrange(m, false, false); @@ -1862,6 +1874,11 @@ int32_t scroller_apply_stack(Client *c, Client *target_client, /* 通过封装好的插入函数实现(尾部插入) */ scroller_insert_stack(c, tail->client, false); + + if (c != tail->client) { + wl_list_remove(&c->link); + wl_list_insert(&tail->client->link, &c->link); + } return 0; } diff --git a/src/layout/scroll.h b/src/layout/scroll.h index 84e84e2c..e4a6d75f 100644 --- a/src/layout/scroll.h +++ b/src/layout/scroll.h @@ -793,7 +793,12 @@ void scroller_insert_stack(Client *c, Client *target_client, void scroller_drop_tile(Client *c, Client *closest, int vertical) { + // 必须先更新,不然里面节点还存着的是cnode的信息, + // 会造成stach_head/stack_tail指向的客户端不对 + update_scroller_state(c->mon); + Client *stack_head = scroll_get_stack_head_client(closest); + Client *stack_tail = scroll_get_stack_tail_client(closest); if (vertical) { if (closest->drop_direction == LEFT) { @@ -805,11 +810,15 @@ void scroller_drop_tile(Client *c, Client *closest, int vertical) { scroller_insert_stack(c, closest, false); return; } else if (closest->drop_direction == UP) { - wl_list_remove(&c->link); - wl_list_insert(stack_head->link.prev, &c->link); + if (c != stack_head) { + wl_list_remove(&c->link); + wl_list_insert(stack_head->link.prev, &c->link); + } } else if (closest->drop_direction == DOWN) { - wl_list_remove(&c->link); - wl_list_insert(&stack_head->link, &c->link); + if (c != stack_tail) { + wl_list_remove(&c->link); + wl_list_insert(&stack_head->link, &c->link); + } } } else { if (closest->drop_direction == UP) { @@ -821,11 +830,15 @@ void scroller_drop_tile(Client *c, Client *closest, int vertical) { scroller_insert_stack(c, closest, false); return; } else if (closest->drop_direction == LEFT) { - wl_list_remove(&c->link); - wl_list_insert(stack_head->link.prev, &c->link); + if (c != stack_head) { + wl_list_remove(&c->link); + wl_list_insert(stack_head->link.prev, &c->link); + } } else if (closest->drop_direction == RIGHT) { - wl_list_remove(&c->link); - wl_list_insert(&stack_head->link, &c->link); + if (c != stack_tail) { + wl_list_remove(&c->link); + wl_list_insert(&stack_head->link, &c->link); + } } } @@ -848,6 +861,22 @@ Client *scroll_get_stack_head_client(Client *c) { return c; } +Client *scroll_get_stack_tail_client(Client *c) { + if (!c || !c->mon) + return c; + uint32_t tag = c->mon->pertag->curtag; + struct TagScrollerState *st = c->mon->pertag->scroller_state[tag]; + if (st) { + struct ScrollerStackNode *n = find_scroller_node(st, c); + if (n) { + while (n->next_in_stack) + n = n->next_in_stack; + return n->client; + } + } + return c; +} + static void update_scroller_state(Monitor *m) { uint32_t tag = m->pertag->curtag; struct TagScrollerState *st = ensure_scroller_state(m, tag); diff --git a/src/mango.c b/src/mango.c index cf9bf6a3..4781a5d9 100644 --- a/src/mango.c +++ b/src/mango.c @@ -847,6 +847,7 @@ static void scroller_node_remove(struct TagScrollerState *st, static struct ScrollerStackNode * scroller_node_create(struct TagScrollerState *st, Client *c); static void update_scroller_state(Monitor *m); +Client *scroll_get_stack_tail_client(Client *c); #include "data/static_keymap.h" #include "dispatch/bind_declare.h" From c3ec4c61420cfd0bfe8a10febffe18c76459e7c8 Mon Sep 17 00:00:00 2001 From: atheeq <168955553+xtheeq@users.noreply.github.com> Date: Tue, 12 May 2026 19:48:45 +0530 Subject: [PATCH 184/328] =?UTF-8?q?refactor(nix):=20replace=20sync=20nix?= =?UTF-8?q?=20options=20with=20direct=20md=20generation=20to=20=E2=80=A6?= =?UTF-8?q?=20(#1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(nix): replace sync nix options with direct md generation to docs * fix(docs): add trailing comman in meta * fix(ci): sort nix options and fix default value check --- .github/scripts/generate-nix-options-docs.py | 80 +++++++++++++++++++ .../workflows/generate-nix-options-docs.yml | 58 ++++++++++++++ .github/workflows/sync-nix-options.yml | 59 -------------- .gitignore | 1 - docs/meta.json | 1 + nix/hm-modules.nix | 1 - 6 files changed, 139 insertions(+), 61 deletions(-) create mode 100755 .github/scripts/generate-nix-options-docs.py create mode 100644 .github/workflows/generate-nix-options-docs.yml delete mode 100644 .github/workflows/sync-nix-options.yml diff --git a/.github/scripts/generate-nix-options-docs.py b/.github/scripts/generate-nix-options-docs.py new file mode 100755 index 00000000..23cd3b90 --- /dev/null +++ b/.github/scripts/generate-nix-options-docs.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +""" +Converts NixOS options JSON into clean, table-formatted Markdown. +""" + +import json +import sys +import re + +def clean_description(desc: str) -> str: + """Removes Nix tags, fixes dangling periods, and formats blockquotes.""" + if not desc: + return "*No description provided.*" + + desc = re.sub(r'\{[a-zA-Z]+\}', '', desc).replace('\n.', '.') + + lines = desc.splitlines() + cleaned = [] + in_note = False + + for line in lines: + if line.startswith("::: {.note"): + in_note = True + cleaned.append("> **Note:**\n>") + elif line.startswith(":::"): + in_note = False + else: + cleaned.append(f"> {line}" if in_note else line) + + return "\n".join(cleaned) + +def format_default_value(default_data) -> str: + """Safely formats the default value, handling HTML escaping for tables.""" + if default_data is None: + return "*None*" + + val_text = default_data.get("text", "") if isinstance(default_data, dict) and default_data.get("_type") == "literalExpression" else str(default_data) + val_text = val_text.replace('|', '|') + + if '\n' in val_text: + safe_html = val_text.replace('<', '<').replace('>', '>').replace('\n', '
') + return f"{safe_html}" + + return f"`{val_text}`" + +def main(): + if len(sys.argv) != 4: + sys.exit("Usage: format_docs.py ") + + input_json, output_md, title = sys.argv[1:4] + + with open(input_json, 'r', encoding='utf-8') as f: + data = json.load(f) + + with open(output_md, 'a', encoding='utf-8') as out: + out.write(f"## {title}\n\n") + + for key, opt in sorted(data.items()): + if key.startswith("_module"): + continue + + desc = clean_description(opt.get("description", "")) + opt_type = str(opt.get("type", "unknown")).replace('|', '|') + default_val = format_default_value(opt.get("default")) + + markdown_block = ( + f"### `{key}`\n\n" + f"{desc}\n\n" + f"| Attribute | Value |\n" + f"| :--- | :--- |\n" + f"| **Type** | `{opt_type}` |\n" + f"| **Default** | {default_val} |\n\n" + f"---\n\n" + ) + out.write(markdown_block) + + print(f"Appended {title} to {output_md} successfully.") + +if __name__ == "__main__": + main() diff --git a/.github/workflows/generate-nix-options-docs.yml b/.github/workflows/generate-nix-options-docs.yml new file mode 100644 index 00000000..e5fd0df2 --- /dev/null +++ b/.github/workflows/generate-nix-options-docs.yml @@ -0,0 +1,58 @@ +name: Generate Nix Options Docs + +on: + push: + paths: + - 'nix/**-modules.nix' + - 'nix/generate-options.nix' + - 'flake.nix' + pull_request: + paths: + - 'nix/**-modules.nix' + +jobs: + update-docs: + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + + - name: Install Nix + uses: cachix/install-nix-action@v30 + with: + extra_nix_config: | + experimental-features = nix-command flakes + + - name: Build Options JSON + run: | + nix build .#nixos-options-json --out-link result-nixos + nix build .#hm-options-json --out-link result-hm + + - name: Format to Markdown + run: | + OUTPUT_FILE="docs/nix-options.md" + + cat << 'EOF' > $OUTPUT_FILE + --- + title: Nix Module Options + description: NixOS and Home Manager configuration options for mangowm. + --- + + > **Note:** This document is automatically generated from the Nix module source code. + + EOF + + python3 ./.github/scripts/generate-nix-options-docs.py result-nixos/share/doc/nixos/options.json $OUTPUT_FILE "NixOS Module Options" + python3 ./.github/scripts/generate-nix-options-docs.py result-hm/share/doc/nixos/options.json $OUTPUT_FILE "Home Manager Options" + + - name: Auto-commit changes + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: "docs: auto-generate Nix module options" + file_pattern: 'docs/*-options.md' + branch: ${{ github.head_ref }} diff --git a/.github/workflows/sync-nix-options.yml b/.github/workflows/sync-nix-options.yml deleted file mode 100644 index 3e05f0e1..00000000 --- a/.github/workflows/sync-nix-options.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: Sync Nix module options - -on: - push: - branches: [main] - paths: - - nix/hm-modules.nix - - nix/nixos-modules.nix - - nix/generate-options.nix - - flake.nix - - flake.lock - -concurrency: - group: sync-nix-options - cancel-in-progress: true - -jobs: - sync-nix-options: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - uses: cachix/install-nix-action@v30 - with: - extra_nix_config: | - experimental-features = nix-command flakes - - - name: Build options JSONs - run: | - HM_OUT=$(nix build .#hm-options-json --no-link --print-out-paths) - NIXOS_OUT=$(nix build .#nixos-options-json --no-link --print-out-paths) - cp "$HM_OUT/share/doc/nixos/options.json" /tmp/hm-options.json - cp "$NIXOS_OUT/share/doc/nixos/options.json" /tmp/nixos-options.json - - - name: Checkout website - uses: actions/checkout@v4 - with: - repository: mangowm/mangowm.github.io - path: website - token: ${{ secrets.WEBSITE_SYNC_TOKEN }} - fetch-depth: 1 - - - name: Copy JSONs to website - run: | - cp /tmp/hm-options.json website/apps/web/src/hm-options.json - cp /tmp/nixos-options.json website/apps/web/src/nixos-options.json - - - name: Commit and push - working-directory: website - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git add apps/web/src/hm-options.json apps/web/src/nixos-options.json - git diff --staged --quiet || git commit \ - -m "nix: update module options JSON" \ - -m "${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}" - git push diff --git a/.gitignore b/.gitignore index 274cc4ee..7681f948 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ /.cache /.vscode /result -/result-* config.h mango mango.o diff --git a/docs/meta.json b/docs/meta.json index 74818a9d..8c10c621 100644 --- a/docs/meta.json +++ b/docs/meta.json @@ -11,6 +11,7 @@ "window-management", "bindings", "---Reference---", + "nix-options", "ipc", "faq" ] diff --git a/nix/hm-modules.nix b/nix/hm-modules.nix index f9a341a3..f00d9c68 100644 --- a/nix/hm-modules.nix +++ b/nix/hm-modules.nix @@ -22,7 +22,6 @@ in enable = mkOption { type = types.bool; default = false; - description = "Whether to enable mangowm, a Wayland compositor based on dwl."; }; package = lib.mkOption { type = lib.types.package; From a165d0b196ef726a7f1c98802b78eb347af3c343 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 12 May 2026 22:26:00 +0800 Subject: [PATCH 185/328] fix: ensure the global link own same order like scroller tag link --- src/layout/scroll.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/layout/scroll.h b/src/layout/scroll.h index e4a6d75f..0020a7b1 100644 --- a/src/layout/scroll.h +++ b/src/layout/scroll.h @@ -767,12 +767,16 @@ void scroller_insert_stack(Client *c, Client *target_client, if (tnode->prev_in_stack) tnode->prev_in_stack->next_in_stack = newnode; tnode->prev_in_stack = newnode; + wl_list_remove(&c->link); + wl_list_insert(tnode->client->link.prev, &c->link); } else { newnode->prev_in_stack = tnode; newnode->next_in_stack = tnode->next_in_stack; if (tnode->next_in_stack) tnode->next_in_stack->prev_in_stack = newnode; tnode->next_in_stack = newnode; + wl_list_remove(&c->link); + wl_list_insert(&tnode->client->link, &c->link); } /* 处理堆叠头部的全屏/最大化状态*/ @@ -817,7 +821,7 @@ void scroller_drop_tile(Client *c, Client *closest, int vertical) { } else if (closest->drop_direction == DOWN) { if (c != stack_tail) { wl_list_remove(&c->link); - wl_list_insert(&stack_head->link, &c->link); + wl_list_insert(&stack_tail->link, &c->link); } } } else { @@ -837,7 +841,7 @@ void scroller_drop_tile(Client *c, Client *closest, int vertical) { } else if (closest->drop_direction == RIGHT) { if (c != stack_tail) { wl_list_remove(&c->link); - wl_list_insert(&stack_head->link, &c->link); + wl_list_insert(&stack_tail->link, &c->link); } } } From 4f3813a272e5738cea193cd2a08ce8a5f5780ca8 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 12 May 2026 22:58:37 +0800 Subject: [PATCH 186/328] opt: keep the header order when the scroll header is pop out stack --- src/mango.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/mango.c b/src/mango.c index 4781a5d9..64f0e71e 100644 --- a/src/mango.c +++ b/src/mango.c @@ -4383,16 +4383,13 @@ mapnotify(struct wl_listener *listener, void *data) { if (selmon->sel && ISSCROLLTILED(selmon->sel) && VISIBLEON(selmon->sel, selmon)) { - at_client = scroll_get_stack_head_client(selmon->sel); + at_client = scroll_get_stack_tail_client(selmon->sel); } else { at_client = center_tiled_select(selmon); } if (at_client) { - at_client->link.next->prev = &c->link; - c->link.prev = &at_client->link; - c->link.next = at_client->link.next; - at_client->link.next = &c->link; + wl_list_insert(&at_client->link, &c->link); } else { wl_list_insert(clients.prev, &c->link); // 尾部入栈 } From 34e9246e4ae2be36d79fdd46d3c521e31ecbde85 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 12 May 2026 23:21:25 +0800 Subject: [PATCH 187/328] update readme --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index b1b162df..531383ee 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,15 @@ Mango starts where dwl ends. It keeps the lightweight, fast-build philosophy whi ```bash yay -S mangowm-git ``` +#### use my config +- install dependencies +``` +yay -S rofi foot xdg-desktop-portal-wlr swaybg waybar wl-clip-persist cliphist wl-clipboard wlsunset xfce-polkit swaync pamixer wlr-dpms sway-audio-idle-inhibit-git swayidle dimland-git brightnessctl swayosd wlr-randr grim slurp satty swaylock-effects-git wlogout sox +``` +- clone config +``` +git clone https://github.com/DreamMaoMao/mango-config.git ~/.config/mango +``` ### Other distributions From d5db98cfb774fdcbc6af174d4a6a59fba94e2e54 Mon Sep 17 00:00:00 2001 From: Ruixi-rebirth <ruixirebirth@gmail.com> Date: Tue, 12 May 2026 22:12:07 +0800 Subject: [PATCH 188/328] fix(scripts): improve nix options doc generator - Restore missing description on hm enable option (fixes nix build warning) - Rewrite format_default_value into format_value with example support and nix code blocks - Fix Nix markup stripping: {tag}`content` now preserves backtick content - Generalize ::: block handling beyond {.note} to any block type - Switch output from Markdown table to labeled fields format --- .github/scripts/generate-nix-options-docs.py | 130 +++++++++++------- .../workflows/generate-nix-options-docs.yml | 19 +-- nix/hm-modules.nix | 1 + 3 files changed, 87 insertions(+), 63 deletions(-) diff --git a/.github/scripts/generate-nix-options-docs.py b/.github/scripts/generate-nix-options-docs.py index 23cd3b90..6f5d35bd 100755 --- a/.github/scripts/generate-nix-options-docs.py +++ b/.github/scripts/generate-nix-options-docs.py @@ -1,80 +1,112 @@ #!/usr/bin/env python3 """ -Converts NixOS options JSON into clean, table-formatted Markdown. +Converts NixOS options JSON into clean Markdown documentation. """ import json import sys import re +SECTIONS = [ + { + "title": "NixOS", + "subtitle": "**System-level options via `programs.mango`.**", + }, + { + "title": "Home Manager", + "subtitle": "**Configure mangowm declaratively via `wayland.windowManager.mango`.**", + }, +] + +HEADER = ( + "---\n" + "title: Nix Module Options\n" + "description: NixOS and Home Manager configuration options for mangowm.\n" + "---\n\n" + "> **Note:** This document is automatically generated from the Nix module source code.\n\n" +) + def clean_description(desc: str) -> str: - """Removes Nix tags, fixes dangling periods, and formats blockquotes.""" + """Strips Nix inline markup tags, fixes dangling periods, and formats blockquotes.""" if not desc: return "*No description provided.*" - desc = re.sub(r'\{[a-zA-Z]+\}', '', desc).replace('\n.', '.') + # Strip Nix inline markup: {tag}`content` → `content`; bare tags → "" + desc = re.sub(r'\{(?:var|option|manpage|file|env|command|program)\}(`[^`]+`)', r'\1', desc) + desc = re.sub(r'\{(?:var|option|manpage|file|env|command|program)\}', '', desc) + # Remove period left on its own line after tag removal + desc = re.sub(r'\n\s*\.(\s|$)', r'.\1', desc) lines = desc.splitlines() cleaned = [] - in_note = False - + in_block = False + for line in lines: - if line.startswith("::: {.note"): - in_note = True - cleaned.append("> **Note:**\n>") + m = re.match(r':::\s*\{\.(\w+)\}', line) + if m: + block_type = m.group(1).capitalize() + in_block = True + cleaned.append(f"> **{block_type}:**\n>") elif line.startswith(":::"): - in_note = False + in_block = False else: - cleaned.append(f"> {line}" if in_note else line) + cleaned.append(f"> {line}" if in_block else line) return "\n".join(cleaned) -def format_default_value(default_data) -> str: - """Safely formats the default value, handling HTML escaping for tables.""" - if default_data is None: - return "*None*" +def format_value(val_data) -> str: + """Formats a value as inline code or a nix code block.""" + if val_data is None: + return None - val_text = default_data.get("text", "") if isinstance(default_data, dict) and default_data.get("_type") == "literalExpression" else str(default_data) - val_text = val_text.replace('|', '|') + if isinstance(val_data, dict) and val_data.get("_type") == "literalMD": + return val_data.get("text", "").strip() + elif isinstance(val_data, dict) and val_data.get("_type") == "literalExpression": + text = val_data.get("text", "").strip() + elif isinstance(val_data, bool): + text = "true" if val_data else "false" + elif val_data == {} or val_data == []: + text = "{ }" if val_data == {} else "[ ]" + else: + text = str(val_data).strip() - if '\n' in val_text: - safe_html = val_text.replace('<', '<').replace('>', '>').replace('\n', '<br>') - return f"<code>{safe_html}</code>" - - return f"`{val_text}`" + if '\n' in text: + return f"\n```nix\n{text}\n```" + return f"`{text}`" + +def write_section(out, data, title, subtitle): + out.write(f"## {title}\n\n{subtitle}\n\n") + for key, opt in sorted(data.items()): + if key.startswith("_module"): + continue + + desc = clean_description(opt.get("description", "")) + opt_type = str(opt.get("type", "unknown")) + default_val = format_value(opt.get("default")) + example_val = format_value(opt.get("example")) + + block = f"### `{key}`\n\n{desc}\n\n**Type:** `{opt_type}`\n\n" + if default_val is not None: + block += f"**Default:** {default_val}\n\n" + if example_val is not None: + block += f"**Example:** {example_val}\n\n" + block += "---\n\n" + out.write(block) def main(): if len(sys.argv) != 4: - sys.exit("Usage: format_docs.py <input.json> <output.md> <title>") + sys.exit("Usage: generate-nix-options-docs.py <nixos.json> <hm.json> <output.md>") - input_json, output_md, title = sys.argv[1:4] + nixos_json, hm_json, output_md = sys.argv[1:4] + inputs = [nixos_json, hm_json] - with open(input_json, 'r', encoding='utf-8') as f: - data = json.load(f) - - with open(output_md, 'a', encoding='utf-8') as out: - out.write(f"## {title}\n\n") - - for key, opt in sorted(data.items()): - if key.startswith("_module"): - continue - - desc = clean_description(opt.get("description", "")) - opt_type = str(opt.get("type", "unknown")).replace('|', '|') - default_val = format_default_value(opt.get("default")) - - markdown_block = ( - f"### `{key}`\n\n" - f"{desc}\n\n" - f"| Attribute | Value |\n" - f"| :--- | :--- |\n" - f"| **Type** | `{opt_type}` |\n" - f"| **Default** | {default_val} |\n\n" - f"---\n\n" - ) - out.write(markdown_block) - - print(f"Appended {title} to {output_md} successfully.") + with open(output_md, 'w', encoding='utf-8') as out: + out.write(HEADER) + for path, section in zip(inputs, SECTIONS): + with open(path, 'r', encoding='utf-8') as f: + data = json.load(f) + write_section(out, data, section["title"], section["subtitle"]) + print(f"Written {section['title']} section.") if __name__ == "__main__": main() diff --git a/.github/workflows/generate-nix-options-docs.yml b/.github/workflows/generate-nix-options-docs.yml index e5fd0df2..465146be 100644 --- a/.github/workflows/generate-nix-options-docs.yml +++ b/.github/workflows/generate-nix-options-docs.yml @@ -6,6 +6,7 @@ on: - 'nix/**-modules.nix' - 'nix/generate-options.nix' - 'flake.nix' + - '.github/scripts/generate-nix-options-docs.py' pull_request: paths: - 'nix/**-modules.nix' @@ -35,20 +36,10 @@ jobs: - name: Format to Markdown run: | - OUTPUT_FILE="docs/nix-options.md" - - cat << 'EOF' > $OUTPUT_FILE - --- - title: Nix Module Options - description: NixOS and Home Manager configuration options for mangowm. - --- - - > **Note:** This document is automatically generated from the Nix module source code. - - EOF - - python3 ./.github/scripts/generate-nix-options-docs.py result-nixos/share/doc/nixos/options.json $OUTPUT_FILE "NixOS Module Options" - python3 ./.github/scripts/generate-nix-options-docs.py result-hm/share/doc/nixos/options.json $OUTPUT_FILE "Home Manager Options" + python3 ./.github/scripts/generate-nix-options-docs.py \ + result-nixos/share/doc/nixos/options.json \ + result-hm/share/doc/nixos/options.json \ + docs/nix-options.md - name: Auto-commit changes uses: stefanzweifel/git-auto-commit-action@v5 diff --git a/nix/hm-modules.nix b/nix/hm-modules.nix index f00d9c68..f9a341a3 100644 --- a/nix/hm-modules.nix +++ b/nix/hm-modules.nix @@ -22,6 +22,7 @@ in enable = mkOption { type = types.bool; default = false; + description = "Whether to enable mangowm, a Wayland compositor based on dwl."; }; package = lib.mkOption { type = lib.types.package; From 406866d96a27b52149c8e0f48751e125580ebed8 Mon Sep 17 00:00:00 2001 From: Ruixi-rebirth <ruixirebirth@gmail.com> Date: Wed, 13 May 2026 05:11:57 +0800 Subject: [PATCH 189/328] docs(nix): add startup guide and clarify addLoginEntry - Replace 'Extra options' with greetd and getty autologin examples - Add bash/zsh and fish variants for getty autologin - Add link to Nix Module Options reference - Clarify addLoginEntry only has effect when a DM is configured --- docs/installation.md | 75 +++++++++++++++++++++++++++++++++++++++++-- nix/hm-modules.nix | 2 +- nix/nixos-modules.nix | 2 +- 3 files changed, 74 insertions(+), 5 deletions(-) diff --git a/docs/installation.md b/docs/installation.md index 6f3927a0..903d7cb9 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -58,6 +58,7 @@ dnf install mangowm The package is hosted in the community-maintained **GURU** repository. 1. **Add the GURU repository** + ```bash emerge --ask --verbose eselect-repository eselect repository enable guru @@ -82,6 +83,7 @@ The package definition is described in the source repository. 1. **Add mango channel** Add to `$HOME/.config/guix/channels.scm`: + ```scheme (cons (channel (name 'mangowm) @@ -92,11 +94,13 @@ The package definition is described in the source repository. 2. **Install** After running `guix pull`, you can install mangowm: + ```bash guix install mangowm ``` Or add it to your system configuration using the mangowm module: + ```scheme (use-modules (mangowm)) @@ -115,6 +119,7 @@ The package definition is described in the source repository. The repository provides a Flake with a NixOS module. 1. **Add flake input** + ```nix # flake.nix { @@ -132,6 +137,7 @@ The repository provides a Flake with a NixOS module. 2. **Import the NixOS module** **Option A — Import in `configuration.nix`:** + ```nix # configuration.nix (or any other file that you import) {inputs, ...}: { @@ -145,6 +151,7 @@ The repository provides a Flake with a NixOS module. ``` **Option B — Import directly in flake:** + ```nix # flake.nix { @@ -165,6 +172,7 @@ The repository provides a Flake with a NixOS module. ``` 3. **Enable the module** + ```nix # configuration.nix (or any other file that you import) { @@ -172,9 +180,67 @@ The repository provides a Flake with a NixOS module. } ``` -4. **Extra options** - - `programs.mango.package` — the mango package to use, allows usage of custom mango drvs - - `programs.mango.addLoginEntry` (default: `true`) — adds login entry to the display manager +4. **Start mango on login** + + The following are common examples. Refer to the official NixOS documentation for full configuration options. + + **Option A — greetd:** Autologin on first start; login screen after logout. + + ```nix + services.greetd = { + enable = true; + settings = { + initial_session = { + command = "mango"; + user = "your-username"; # auto-login on first start, no password required + }; + default_session = { + command = "${pkgs.greetd.tuigreet}/bin/tuigreet --cmd mango"; + user = "greeter"; + }; + }; + }; + ``` + + **Option B — Display manager autologin:** Autologin via an existing display manager (e.g. SDDM, GDM). [`addLoginEntry`](/docs/nix-module#addloginentry) (default: `true`) automatically registers mango as a session. + + ```nix + services.displayManager = { + defaultSession = "mango"; # derived from mango.desktop filename + autoLogin = { + enable = true; + user = "your-username"; + }; + }; + ``` + + **Option C — getty autologin:** No login screen, boots directly into mango on TTY1. + + For bash/zsh: + + ```nix + services.getty.autologinUser = "your-username"; + + environment.loginShellInit = '' + [ "$(tty)" = /dev/tty1 ] && exec mango + ''; + ``` + + For fish: + + ```nix + services.getty.autologinUser = "your-username"; + + programs.fish.loginShellInit = '' + if test (tty) = /dev/tty1 + exec mango + end + ''; + ``` + +5. **All available options** + + See [Nix Module Options](/docs/nix-module) for the full list of NixOS and Home Manager options. --- @@ -195,6 +261,7 @@ pikman install mangowm If your distribution isn't listed above, or you want the latest unreleased changes, you can build mangowm from source. > **Info:** Ensure the following dependencies are installed before proceeding: +> > - `wayland` > - `wayland-protocols` > - `libinput` @@ -213,6 +280,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.3 https://gitlab.freedesktop.org/wlroots/wlroots.git cd wlroots @@ -222,6 +290,7 @@ You will need to build `wlroots` and `scenefx` manually as well. 2. **Build scenefx** This library handles the visual effects. + ```bash git clone -b 0.4.1 https://github.com/wlrfx/scenefx.git cd scenefx diff --git a/nix/hm-modules.nix b/nix/hm-modules.nix index f9a341a3..eba05e2f 100644 --- a/nix/hm-modules.nix +++ b/nix/hm-modules.nix @@ -102,7 +102,7 @@ in description = '' Mango configuration written in Nix. Entries with the same key should be written as lists. Variables and colors names should be - quoted. See <https://mangowc.vercel.app/docs> for more examples. + quoted. See <https://mangowm.github.io/docs> for more examples. ::: {.note} This option uses a structured format that is converted to Mango's diff --git a/nix/nixos-modules.nix b/nix/nixos-modules.nix index 7295fffa..9144bbf1 100644 --- a/nix/nixos-modules.nix +++ b/nix/nixos-modules.nix @@ -12,7 +12,7 @@ in { addLoginEntry = lib.mkOption { type = lib.types.bool; default = true; - description = "Whether to add a login entry to the display manager for mango"; + description = "Whether to add a login entry to the display manager for mango. Only has effect if a display manager is configured (e.g. SDDM, GDM via `services.displayManager`)."; }; package = lib.mkOption { type = lib.types.package; From 0cea4a8a0d0a1b4345e882a861ef9e257de9fe38 Mon Sep 17 00:00:00 2001 From: Ruixi-rebirth <75824585+Ruixi-rebirth@users.noreply.github.com> Date: Tue, 12 May 2026 22:09:44 +0000 Subject: [PATCH 190/328] docs: auto-generate Nix module options --- docs/nix-options.md | 296 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 296 insertions(+) create mode 100644 docs/nix-options.md diff --git a/docs/nix-options.md b/docs/nix-options.md new file mode 100644 index 00000000..2a5d2038 --- /dev/null +++ b/docs/nix-options.md @@ -0,0 +1,296 @@ +--- +title: Nix Module Options +description: NixOS and Home Manager configuration options for mangowm. +--- + +> **Note:** This document is automatically generated from the Nix module source code. + +## NixOS + +**System-level options via `programs.mango`.** + +### `addLoginEntry` + +Whether to add a login entry to the display manager for mango. Only has effect if a display manager is configured (e.g. SDDM, GDM via `services.displayManager`). + +**Type:** `boolean` + +**Default:** `true` + +--- + +### `enable` + +Whether to enable mango, a wayland compositor based on dwl. + +**Type:** `boolean` + +**Default:** `false` + +**Example:** `true` + +--- + +### `package` + +The mango package to use + +**Type:** `package` + +**Default:** `<derivation mango-nightly>` + +--- + +## Home Manager + +**Configure mangowm declaratively via `wayland.windowManager.mango`.** + +### `autostart_sh` + +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:** `strings concatenated with "\n"` + +**Default:** `""` + +**Example:** +```nix +'' + waybar & + dunst & +'' +``` + +--- + +### `bottomPrefixes` + +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. + +**Type:** `list of string` + +**Default:** `[ ]` + +**Example:** +```nix +[ + "source" +] +``` + +--- + +### `enable` + +Whether to enable mangowm, a Wayland compositor based on dwl. + +**Type:** `boolean` + +**Default:** `false` + +--- + +### `extraConfig` + +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. + +**Type:** `strings concatenated with "\n"` + +**Default:** `""` + +**Example:** +```nix +'' + # Advanced config that doesn't fit structured format + special_option = 1 +'' +``` + +--- + +### `package` + +The mango package to use + +**Type:** `package` + +**Default:** `<derivation mango-nightly>` + +--- + +### `settings` + +Mango configuration written in Nix. Entries with the same key +should be written as lists. Variables and colors names should be +quoted. See <https://mangowm.github.io/docs> 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` +> +> Keymodes (submaps) are supported via the special `keymode` attribute. Each keymode +> is a nested attribute set under `keymode` that contains its own bindings. + +**Type:** `Mango configuration value` + +**Default:** `{ }` + +**Example:** +```nix +{ + # 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" + "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" + ]; + }; + }; +} +``` + +--- + +### `systemd.enable` + +Whether to enable `mango-session.target` on +mango startup. This links to +`graphical-session.target`. +Some important environment variables will be imported to systemd +and dbus user environment before reaching the target, including +* `DISPLAY` +* `WAYLAND_DISPLAY` +* `XDG_CURRENT_DESKTOP` +* `XDG_SESSION_TYPE` +* `NIXOS_OZONE_WL` +You can extend this list using the `systemd.variables` option. + +**Type:** `boolean` + +**Default:** `true` + +**Example:** `false` + +--- + +### `systemd.extraCommands` + +Extra commands to run after D-Bus activation. + +**Type:** `list of string` + +**Default:** +```nix +[ + "systemctl --user reset-failed" + "systemctl --user start mango-session.target" +] +``` + +--- + +### `systemd.variables` + +Environment variables imported into the systemd and D-Bus user environment. + +**Type:** `list of string` + +**Default:** +```nix +[ + "DISPLAY" + "WAYLAND_DISPLAY" + "XDG_CURRENT_DESKTOP" + "XDG_SESSION_TYPE" + "NIXOS_OZONE_WL" + "XCURSOR_THEME" + "XCURSOR_SIZE" +] +``` + +**Example:** +```nix +[ + "--all" +] +``` + +--- + +### `systemd.xdgAutostart` + +Whether to enable autostart of applications using +`systemd-xdg-autostart-generator(8)`. + +**Type:** `boolean` + +**Default:** `false` + +**Example:** `true` + +--- + +### `topPrefixes` + +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. + +**Type:** `list of string` + +**Default:** `[ ]` + +**Example:** +```nix +[ + "source" +] +``` + +--- + From d7c5b603eb66f15a23d43488950db5c5a5784147 Mon Sep 17 00:00:00 2001 From: Ruixi-rebirth <ruixirebirth@gmail.com> Date: Wed, 13 May 2026 11:06:59 +0800 Subject: [PATCH 191/328] refactor(docs): switch to nixos-render-docs for nix options generation - Use nixos-render-docs commonmark as the rendering engine - Add GitHub source links (Declared by) for each option - Fix installation.md links pointing to /docs/nix-module -> /docs/nix-options --- .github/scripts/generate-nix-options-docs.py | 110 +---- .../workflows/generate-nix-options-docs.yml | 18 +- docs/installation.md | 4 +- docs/nix-options.md | 427 +++++++++++++----- nix/generate-options.nix | 14 + 5 files changed, 380 insertions(+), 193 deletions(-) diff --git a/.github/scripts/generate-nix-options-docs.py b/.github/scripts/generate-nix-options-docs.py index 6f5d35bd..268edb5b 100755 --- a/.github/scripts/generate-nix-options-docs.py +++ b/.github/scripts/generate-nix-options-docs.py @@ -1,22 +1,9 @@ #!/usr/bin/env python3 -""" -Converts NixOS options JSON into clean Markdown documentation. -""" - -import json -import sys +# Post-processes nixos-render-docs commonmark output into a single docs page. +# Input: two pre-rendered .md files (NixOS and HM), produced by nixos-render-docs. +# Steps: strip internal _module.args section, promote ## headings to ###. import re - -SECTIONS = [ - { - "title": "NixOS", - "subtitle": "**System-level options via `programs.mango`.**", - }, - { - "title": "Home Manager", - "subtitle": "**Configure mangowm declaratively via `wayland.windowManager.mango`.**", - }, -] +import sys HEADER = ( "---\n" @@ -26,87 +13,36 @@ HEADER = ( "> **Note:** This document is automatically generated from the Nix module source code.\n\n" ) -def clean_description(desc: str) -> str: - """Strips Nix inline markup tags, fixes dangling periods, and formats blockquotes.""" - if not desc: - return "*No description provided.*" +SECTIONS = [ + ("NixOS", "**System-level options via `programs.mango`.**"), + ("Home Manager", "**Configure mangowm declaratively via `wayland.windowManager.mango`.**"), +] - # Strip Nix inline markup: {tag}`content` → `content`; bare tags → "" - desc = re.sub(r'\{(?:var|option|manpage|file|env|command|program)\}(`[^`]+`)', r'\1', desc) - desc = re.sub(r'\{(?:var|option|manpage|file|env|command|program)\}', '', desc) - # Remove period left on its own line after tag removal - desc = re.sub(r'\n\s*\.(\s|$)', r'.\1', desc) - lines = desc.splitlines() - cleaned = [] - in_block = False +def process(md): + # Remove internal _module.args option injected by the module system + md = re.sub(r'## _module\\\.args.*?(?=\n## |\Z)', '', md, flags=re.DOTALL) + # Promote option headings (##) to subheadings (###) under the section (##) + md = re.sub(r'^## ', '### ', md, flags=re.MULTILINE) + return md.strip() - for line in lines: - m = re.match(r':::\s*\{\.(\w+)\}', line) - if m: - block_type = m.group(1).capitalize() - in_block = True - cleaned.append(f"> **{block_type}:**\n>") - elif line.startswith(":::"): - in_block = False - else: - cleaned.append(f"> {line}" if in_block else line) - - return "\n".join(cleaned) - -def format_value(val_data) -> str: - """Formats a value as inline code or a nix code block.""" - if val_data is None: - return None - - if isinstance(val_data, dict) and val_data.get("_type") == "literalMD": - return val_data.get("text", "").strip() - elif isinstance(val_data, dict) and val_data.get("_type") == "literalExpression": - text = val_data.get("text", "").strip() - elif isinstance(val_data, bool): - text = "true" if val_data else "false" - elif val_data == {} or val_data == []: - text = "{ }" if val_data == {} else "[ ]" - else: - text = str(val_data).strip() - - if '\n' in text: - return f"\n```nix\n{text}\n```" - return f"`{text}`" - -def write_section(out, data, title, subtitle): - out.write(f"## {title}\n\n{subtitle}\n\n") - for key, opt in sorted(data.items()): - if key.startswith("_module"): - continue - - desc = clean_description(opt.get("description", "")) - opt_type = str(opt.get("type", "unknown")) - default_val = format_value(opt.get("default")) - example_val = format_value(opt.get("example")) - - block = f"### `{key}`\n\n{desc}\n\n**Type:** `{opt_type}`\n\n" - if default_val is not None: - block += f"**Default:** {default_val}\n\n" - if example_val is not None: - block += f"**Example:** {example_val}\n\n" - block += "---\n\n" - out.write(block) def main(): if len(sys.argv) != 4: - sys.exit("Usage: generate-nix-options-docs.py <nixos.json> <hm.json> <output.md>") + sys.exit("Usage: generate-nix-options-docs.py <nixos.md> <hm.md> <output.md>") - nixos_json, hm_json, output_md = sys.argv[1:4] - inputs = [nixos_json, hm_json] + nixos_md, hm_md, output_md = sys.argv[1:4] with open(output_md, 'w', encoding='utf-8') as out: out.write(HEADER) - for path, section in zip(inputs, SECTIONS): + for path, (title, subtitle) in zip([nixos_md, hm_md], SECTIONS): with open(path, 'r', encoding='utf-8') as f: - data = json.load(f) - write_section(out, data, section["title"], section["subtitle"]) - print(f"Written {section['title']} section.") + md = f.read() + out.write(f"## {title}\n\n{subtitle}\n\n") + out.write(process(md)) + out.write('\n\n') + print(f"Written {title} section.") + if __name__ == "__main__": main() diff --git a/.github/workflows/generate-nix-options-docs.yml b/.github/workflows/generate-nix-options-docs.yml index 465146be..7249ef46 100644 --- a/.github/workflows/generate-nix-options-docs.yml +++ b/.github/workflows/generate-nix-options-docs.yml @@ -34,11 +34,25 @@ jobs: nix build .#nixos-options-json --out-link result-nixos nix build .#hm-options-json --out-link result-hm + - name: Render to CommonMark + run: | + echo '{}' > /tmp/manpage-urls.json + nix run nixpkgs#nixos-render-docs -- options commonmark \ + --manpage-urls /tmp/manpage-urls.json \ + --revision nightly \ + result-nixos/share/doc/nixos/options.json \ + /tmp/nixos-raw.md + nix run nixpkgs#nixos-render-docs -- options commonmark \ + --manpage-urls /tmp/manpage-urls.json \ + --revision nightly \ + result-hm/share/doc/nixos/options.json \ + /tmp/hm-raw.md + - name: Format to Markdown run: | python3 ./.github/scripts/generate-nix-options-docs.py \ - result-nixos/share/doc/nixos/options.json \ - result-hm/share/doc/nixos/options.json \ + /tmp/nixos-raw.md \ + /tmp/hm-raw.md \ docs/nix-options.md - name: Auto-commit changes diff --git a/docs/installation.md b/docs/installation.md index 903d7cb9..c5d4936c 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -202,7 +202,7 @@ The repository provides a Flake with a NixOS module. }; ``` - **Option B — Display manager autologin:** Autologin via an existing display manager (e.g. SDDM, GDM). [`addLoginEntry`](/docs/nix-module#addloginentry) (default: `true`) automatically registers mango as a session. + **Option B — Display manager autologin:** Autologin via an existing display manager (e.g. SDDM, GDM). [`addLoginEntry`](/docs/nix-options#addloginentry) (default: `true`) automatically registers mango as a session. ```nix services.displayManager = { @@ -240,7 +240,7 @@ The repository provides a Flake with a NixOS module. 5. **All available options** - See [Nix Module Options](/docs/nix-module) for the full list of NixOS and Home Manager options. + See [Nix Module Options](/docs/nix-options) for the full list of NixOS and Home Manager options. --- diff --git a/docs/nix-options.md b/docs/nix-options.md index 2a5d2038..2537d9d8 100644 --- a/docs/nix-options.md +++ b/docs/nix-options.md @@ -9,55 +9,163 @@ description: NixOS and Home Manager configuration options for mangowm. **System-level options via `programs.mango`.** -### `addLoginEntry` +### enable -Whether to add a login entry to the display manager for mango. Only has effect if a display manager is configured (e.g. SDDM, GDM via `services.displayManager`). -**Type:** `boolean` -**Default:** `true` +Whether to enable mango, a wayland compositor based on dwl\. ---- -### `enable` -Whether to enable mango, a wayland compositor based on dwl. +*Type:* +boolean -**Type:** `boolean` -**Default:** `false` -**Example:** `true` +*Default:* + +```nix +false +``` + + + +*Example:* + +```nix +true +``` + +*Declared by:* + - [\<mango/nix/nixos-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/nixos-modules.nix) + + + +### package ---- -### `package` The mango package to use -**Type:** `package` -**Default:** `<derivation mango-nightly>` ---- +*Type:* +package + + + +*Default:* + +```nix +<derivation mango-nightly> +``` + +*Declared by:* + - [\<mango/nix/nixos-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/nixos-modules.nix) + + + +### addLoginEntry + + + +Whether to add a login entry to the display manager for mango\. Only has effect if a display manager is configured (e\.g\. SDDM, GDM via ` services.displayManager `)\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +true +``` + +*Declared by:* + - [\<mango/nix/nixos-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/nixos-modules.nix) ## Home Manager **Configure mangowm declaratively via `wayland.windowManager.mango`.** -### `autostart_sh` +### enable -Shell script to run on mango startup. No shebang needed. + + +Whether to enable mangowm, a Wayland compositor based on dwl\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +false +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### package + + + +The mango package to use + + + +*Type:* +package + + + +*Default:* + +```nix +<derivation mango-nightly> +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### autostart_sh + + + +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. +` ~/.config/mango/autostart.sh ` and an ` exec-once ` line +will be automatically added to the config to execute it\. -**Type:** `strings concatenated with "\n"` -**Default:** `""` -**Example:** +*Type:* +strings concatenated with “\\n” + + + +*Default:* + +```nix +"" +``` + + + +*Example:* + ```nix '' waybar & @@ -65,47 +173,71 @@ will be automatically added to the config to execute it. '' ``` ---- +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) -### `bottomPrefixes` -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. -**Type:** `list of string` +### bottomPrefixes -**Default:** `[ ]` -**Example:** + +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\. + + + +*Type:* +list of string + + + +*Default:* + +```nix +[ ] +``` + + + +*Example:* + ```nix [ "source" ] ``` ---- +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) -### `enable` -Whether to enable mangowm, a Wayland compositor based on dwl. -**Type:** `boolean` +### extraConfig -**Default:** `false` ---- -### `extraConfig` +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\. -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. -**Type:** `strings concatenated with "\n"` -**Default:** `""` +*Type:* +strings concatenated with “\\n” + + + +*Default:* + +```nix +"" +``` + + + +*Example:* -**Example:** ```nix '' # Advanced config that doesn't fit structured format @@ -113,38 +245,43 @@ settings format, or for options that aren't yet supported by the module. '' ``` ---- +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) -### `package` -The mango package to use -**Type:** `package` +### settings -**Default:** `<derivation mango-nightly>` ---- -### `settings` +Mango configuration written in Nix\. Entries with the same key +should be written as lists\. Variables and colors names should be +quoted\. See [https://mangowm\.github\.io/docs](https://mangowm\.github\.io/docs) for more examples\. -Mango configuration written in Nix. Entries with the same key -should be written as lists. Variables and colors names should be -quoted. See <https://mangowm.github.io/docs> 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 ` -> **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` -> -> Keymodes (submaps) are supported via the special `keymode` attribute. Each keymode -> is a nested attribute set under `keymode` that contains its own bindings. +Keymodes (submaps) are supported via the special ` keymode ` attribute\. Each keymode +is a nested attribute set under ` keymode ` that contains its own bindings\. -**Type:** `Mango configuration value` -**Default:** `{ }` -**Example:** +*Type:* +Mango configuration value + + + +*Default:* + +```nix +{ } +``` + + + +*Example:* + ```nix { # Window effects @@ -193,39 +330,72 @@ quoted. See <https://mangowm.github.io/docs> for more examples. }; }; } + ``` ---- +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) -### `systemd.enable` -Whether to enable `mango-session.target` on -mango startup. This links to -`graphical-session.target`. + +### systemd\.enable + + + +Whether to enable ` mango-session.target ` on +mango startup\. This links to +` graphical-session.target `\. Some important environment variables will be imported to systemd and dbus user environment before reaching the target, including -* `DISPLAY` -* `WAYLAND_DISPLAY` -* `XDG_CURRENT_DESKTOP` -* `XDG_SESSION_TYPE` -* `NIXOS_OZONE_WL` -You can extend this list using the `systemd.variables` option. -**Type:** `boolean` + - ` DISPLAY ` + - ` WAYLAND_DISPLAY ` + - ` XDG_CURRENT_DESKTOP ` + - ` XDG_SESSION_TYPE ` + - ` NIXOS_OZONE_WL ` + You can extend this list using the ` systemd.variables ` option\. -**Default:** `true` -**Example:** `false` ---- +*Type:* +boolean -### `systemd.extraCommands` -Extra commands to run after D-Bus activation. -**Type:** `list of string` +*Default:* + +```nix +true +``` + + + +*Example:* + +```nix +false +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### systemd\.extraCommands + + + +Extra commands to run after D-Bus activation\. + + + +*Type:* +list of string + + + +*Default:* -**Default:** ```nix [ "systemctl --user reset-failed" @@ -233,15 +403,26 @@ Extra commands to run after D-Bus activation. ] ``` ---- +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) -### `systemd.variables` -Environment variables imported into the systemd and D-Bus user environment. -**Type:** `list of string` +### systemd\.variables + + + +Environment variables imported into the systemd and D-Bus user environment\. + + + +*Type:* +list of string + + + +*Default:* -**Default:** ```nix [ "DISPLAY" @@ -254,43 +435,85 @@ Environment variables imported into the systemd and D-Bus user environment. ] ``` -**Example:** + + +*Example:* + ```nix [ "--all" ] ``` ---- +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### systemd\.xdgAutostart + -### `systemd.xdgAutostart` Whether to enable autostart of applications using -`systemd-xdg-autostart-generator(8)`. +` systemd-xdg-autostart-generator(8) ` +\. -**Type:** `boolean` -**Default:** `false` -**Example:** `true` +*Type:* +boolean ---- -### `topPrefixes` -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. +*Default:* -**Type:** `list of string` +```nix +false +``` -**Default:** `[ ]` -**Example:** + +*Example:* + +```nix +true +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### topPrefixes + + + +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\. + + + +*Type:* +list of string + + + +*Default:* + +```nix +[ ] +``` + + + +*Example:* + ```nix [ "source" ] ``` ---- +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) diff --git a/nix/generate-options.nix b/nix/generate-options.nix index c2b87a4f..f8df3745 100644 --- a/nix/generate-options.nix +++ b/nix/generate-options.nix @@ -6,6 +6,9 @@ self: optionPrefix, }: let + # Absolute store path of the flake root, used to compute relative subpaths + repoPath = toString self; + eval = lib.evalModules { modules = [ (import module self) @@ -14,6 +17,15 @@ let specialArgs = { inherit pkgs; }; }; + # Relative path of the module file within the repo (e.g. "nix/hm-modules.nix") + moduleSubpath = lib.removePrefix "/" (lib.removePrefix repoPath (toString module)); + + # Declaration entry linking each option back to its source file on GitHub + moduleDeclaration = { + url = "https://github.com/mangowm/mango/blob/main/${moduleSubpath}"; + name = "<mango/${moduleSubpath}>"; + }; + optionsDoc = pkgs.nixosOptionsDoc { options = eval.options; transformOptions = @@ -21,7 +33,9 @@ let opt // { visible = opt.visible && !opt.internal; + # Strip the option prefix so docs show "enable" instead of "programs.mango.enable" name = lib.removePrefix optionPrefix opt.name; + declarations = [ moduleDeclaration ]; }; }; in From ad0f0732b30f24bd1908d0e09230f464117ed5de Mon Sep 17 00:00:00 2001 From: faugusto-oliveira <faugusto.oliveira@ooutlook.com> Date: Wed, 13 May 2026 02:28:38 -0300 Subject: [PATCH 192/328] fix: cursor constrain fail in some case --- src/mango.c | 39 +++++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/src/mango.c b/src/mango.c index 64f0e71e..f12d8e1c 100644 --- a/src/mango.c +++ b/src/mango.c @@ -3510,17 +3510,33 @@ void createpointerconstraint(struct wl_listener *listener, void *data) { pointer_constraint->constraint = data; LISTEN(&pointer_constraint->constraint->events.destroy, &pointer_constraint->destroy, destroypointerconstraint); + + if (!selmon || !selmon->sel) + return; + + struct wlr_surface *focused_surface = client_surface(selmon->sel); + if (focused_surface && + focused_surface == pointer_constraint->constraint->surface) { + cursorconstrain(pointer_constraint->constraint); + } } void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint) { if (active_constraint == constraint) return; - if (active_constraint) + if (active_constraint) { + if (constraint == NULL) { + cursorwarptohint(); + } wlr_pointer_constraint_v1_send_deactivated(active_constraint); + } active_constraint = constraint; - wlr_pointer_constraint_v1_send_activated(constraint); + + if (constraint) { + wlr_pointer_constraint_v1_send_activated(constraint); + } } void cursorframe(struct wl_listener *listener, void *data) { @@ -3803,6 +3819,9 @@ void focusclient(Client *c, int32_t lift) { // clear text input focus state dwl_im_relay_set_focus(dwl_input_method_relay, NULL); wlr_seat_keyboard_notify_clear_focus(seat); + if (active_constraint) { + cursorconstrain(NULL); + } return; } @@ -3819,6 +3838,18 @@ void focusclient(Client *c, int32_t lift) { /* Activate the new client */ client_activate_surface(client_surface(c), 1); + + if (active_constraint && active_constraint->surface != client_surface(c)) { + cursorconstrain(NULL); + } + + struct wlr_pointer_constraint_v1 *constraint; + wl_list_for_each(constraint, &pointer_constraints->constraints, link) { + if (constraint->surface == client_surface(c)) { + cursorconstrain(constraint); + break; + } + } } void // 0.6 @@ -4554,7 +4585,6 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, Client *closet_drop_client = NULL; LayerSurface *l = NULL; struct wlr_surface *surface = NULL; - struct wlr_pointer_constraint_v1 *constraint; bool should_lock = false; /* time is 0 in internal calls meant to restore pointer focus. */ @@ -4563,9 +4593,6 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, relative_pointer_mgr, seat, (uint64_t)time * 1000, dx, dy, dx_unaccel, dy_unaccel); - wl_list_for_each(constraint, &pointer_constraints->constraints, link) - cursorconstrain(constraint); - if (active_constraint && cursor_mode != CurResize && cursor_mode != CurMove) { if (active_constraint->surface == From 1814510b5cb61dfcde7348a097a13a1013eb896f Mon Sep 17 00:00:00 2001 From: xtheeq <atheeq.rhxn@gmail.com> Date: Wed, 13 May 2026 20:01:35 +0530 Subject: [PATCH 193/328] docs: add screenshot example binds and workflow --- docs/meta.json | 2 + docs/screenshot.md | 213 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 215 insertions(+) create mode 100644 docs/screenshot.md diff --git a/docs/meta.json b/docs/meta.json index 8c10c621..89c34444 100644 --- a/docs/meta.json +++ b/docs/meta.json @@ -10,6 +10,8 @@ "visuals", "window-management", "bindings", + "---Examples---", + "screenshot", "---Reference---", "nix-options", "ipc", diff --git a/docs/screenshot.md b/docs/screenshot.md new file mode 100644 index 00000000..f07cdf0c --- /dev/null +++ b/docs/screenshot.md @@ -0,0 +1,213 @@ +--- + +title: Screenshots +description: Example screenshot keybindings and capture workflows for mangowm. + +--- + +mangowm does not include a built-in screenshot tool. This keeps the compositor lean. +Instead, compose your own workflow from small Wayland utilities and bind them to keys; + +| Tool | Purpose | +| :--- | :--- | +| [`grim`](https://github.com/emersion/grim) | Capture the screen or a region to a file | +| [`slurp`](https://github.com/emersion/slurp) | Interactively select a region for `grim` | +| [`wl-copy`](https://github.com/bugaevc/wl-clipboard) | Copy screenshots directly to the clipboard | +| [`satty`](https://github.com/gabm/Satty) | Annotate screenshots before saving | +| [`wayfreeze`](https://github.com/nicbk/wayfreeze) | Freeze the screen before capture | + +Install the required with your package manager or from source. + +`grim` writes to the file path you give it, but **will not create missing directories**. Create one first: + +```bash +mkdir -p ~/Pictures/Screenshots +``` + +Any directory works. `~/Pictures/Screenshots/` is just a convention. + +## Quick Binds + +Short, single-step commands can be placed directly in `config.conf` with `spawn_shell`. +No script file needed. + +### Fullscreen + +Captures the entire display. + +```ini +bind=NONE,Print,spawn_shell,grim $HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png +``` + +### Region + +Select an area with `slurp` before capturing. + +```ini +bind=SHIFT,Print,spawn_shell,g=$(slurp -d) && [ -n "$g" ] && grim -g "$g" $HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png +``` + +### Pointer + +Captures the full screen including the cursor. + +```ini +bind=ALT,Print,spawn_shell,grim -c $HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png +``` + +### Clipboard + +Captures to a temporary file and copies the image to the clipboard; no file is saved. + +```ini +bind=CTRL,Print,spawn_shell,f=$(mktemp -t screenshot-XXXXXX.png) && grim "$f" && wl-copy < "$f" && rm -f "$f" +``` + +### Annotate + +Captures and opens `satty` for drawing before saving. + +```ini +bind=SUPER,Print,spawn_shell,f=$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png && grim "$f" && satty --filename "$f" --output-filename "$f" --actions-on-enter save-to-file --early-exit +``` + +## Script Binds + +When a command involves multi-step logic, geometry parsing, FIFOs, or screen freezing, +move it into a script and invoke it with `spawn` instead of `spawn_shell`. + +Create the scripts directory first: + +```bash +mkdir -p ~/.config/mango/scripts/screenshot +``` + +### Window + +Uses `mmsg` (ships with mango) to capture the focused window. + +`~/.config/mango/scripts/screenshot/window.sh`: + +```bash +#!/usr/bin/env bash +geometry=$(mmsg -x | awk '/x / {x=$3} /y / {y=$3} /width / {w=$3} /height / {h=$3} END {print x","y" "w"x"h}') +[ -z "$geometry" ] && exit 1 +grim -g "$geometry" "$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png" +``` + +```ini +bind=CTRL+SHIFT,Print,spawn,$HOME/.config/mango/scripts/screenshot/window.sh +``` + +### Freeze + +Freezes the screen with `wayfreeze` before capturing. + +`~/.config/mango/scripts/screenshot/freeze.sh`: + +```bash +#!/usr/bin/env bash +pipe=$(mktemp -u).fifo +mkfifo "$pipe" +wayfreeze --after-freeze-timeout 100 --after-freeze-cmd "echo > $pipe" & +wayfreeze_pid=$! +read -r < "$pipe" +grim "$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png" +kill "$wayfreeze_pid" 2>/dev/null +rm -f "$pipe" +``` + +```ini +bind=CTRL+SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/freeze.sh +``` + +### Freeze + Region + +Freeze, then select a region with `slurp`. Cleans up on cancel. + +`~/.config/mango/scripts/screenshot/freeze-region.sh`: + +```bash +#!/usr/bin/env bash +pipe=$(mktemp -u).fifo +mkfifo "$pipe" +wayfreeze --after-freeze-timeout 100 --after-freeze-cmd "echo > $pipe" & +wayfreeze_pid=$! +read -r < "$pipe" +geometry=$(slurp -d) +if [[ -z "$geometry" ]]; then + kill "$wayfreeze_pid" 2>/dev/null + rm -f "$pipe" + exit 1 +fi +grim -g "$geometry" "$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png" +kill "$wayfreeze_pid" 2>/dev/null +rm -f "$pipe" +``` + +```ini +bind=SHIFT+SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/freeze-region.sh +``` + +Make all three scripts executable: + +```bash +chmod +x ~/.config/mango/scripts/screenshot/*.sh +``` + +## All-in-One Script + +Prefer fewer files? A single script with subcommands covers every mode above. +Place it in the same directory and use it in place of the individual scripts. + +`~/.config/mango/scripts/screenshot/screenshot.sh`: + +```bash +#!/usr/bin/env bash +set -euo pipefail +mkdir -p "$HOME/Pictures/Screenshots" +filepath="$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png" + +case "${1:-fullscreen}" in + region) + g=$(slurp -d); [ -z "$g" ] && exit 1 + grim -g "$g" "$filepath" ;; + window) + g=$(mmsg -x | awk '/x / {x=$3} /y / {y=$3} /width / {w=$3} /height / {h=$3} END {print x","y" "w"x"h}') + [ -z "$g" ] && exit 1 + grim -g "$g" "$filepath" ;; + freeze) + p=$(mktemp -u).fifo; mkfifo "$p" + wayfreeze --after-freeze-timeout 100 --after-freeze-cmd "echo > $p" & wp=$! + read -r < "$p"; grim "$filepath" + kill "$wp" 2>/dev/null; rm -f "$p" ;; + freeze-region) + p=$(mktemp -u).fifo; mkfifo "$p" + wayfreeze --after-freeze-timeout 100 --after-freeze-cmd "echo > $p" & wp=$! + read -r < "$p"; g=$(slurp -d) + if [ -z "$g" ]; then kill "$wp" 2>/dev/null; rm -f "$p"; exit 1; fi + grim -g "$g" "$filepath" + kill "$wp" 2>/dev/null; rm -f "$p" ;; + annotate) + grim "$filepath"; satty --filename "$filepath" --output-filename "$filepath" --actions-on-enter save-to-file --early-exit ;; + *) grim "$filepath" ;; +esac +``` + +Make the script executable: + + +```bash +chmod +x ~/.config/mango/scripts/screenshot/screenshot.sh +``` + +Then add the binds to `config.conf`: + +```ini +bind=NONE,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh fullscreen +bind=SHIFT,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh region +bind=CTRL+SHIFT,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh window +bind=CTRL+SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh freeze +bind=SHIFT+SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh freeze-region +bind=SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh annotate +``` From 8a3b94bc6f0f32a706d6b24b86501870be4941c7 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 14 May 2026 11:06:48 +0800 Subject: [PATCH 194/328] feat: manual split --- assets/config.conf | 1 + src/animation/client.h | 99 +++++++++++++- src/config/parse_config.h | 31 ++++- src/dispatch/bind_declare.h | 3 +- src/dispatch/bind_define.h | 25 ++++ src/layout/dwindle.h | 254 +++++++++++++++++++++++++++++------- src/mango.c | 118 +++++++++++------ 7 files changed, 439 insertions(+), 92 deletions(-) diff --git a/assets/config.conf b/assets/config.conf index e0bcdadd..938a1f2f 100644 --- a/assets/config.conf +++ b/assets/config.conf @@ -121,6 +121,7 @@ borderpx=4 rootcolor=0x201b14ff bordercolor=0x444444ff dropcolor=0x8FBA7C55 +splitcolor=0xEB441EFF focuscolor=0xc9b890ff maximizescreencolor=0x89aa61ff urgentcolor=0xad401fff diff --git a/src/animation/client.h b/src/animation/client.h index 8ce81cd2..2b8b7b55 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -1,3 +1,4 @@ +#include "wlr/util/log.h" void client_actual_size(Client *c, int32_t *width, int32_t *height) { *width = c->animation.current.width - 2 * (int32_t)c->bw; @@ -284,8 +285,8 @@ void client_draw_shadow(Client *c) { struct wlr_box client_box = { .x = bwoffset, .y = bwoffset, - .width = width + (int32_t)c->bw - bwoffset, - .height = height + (int32_t)c->bw - bwoffset, + .width = width + 2 * (int32_t)c->bw - 2 * bwoffset, + .height = height + 2 * (int32_t)c->bw - 2 * bwoffset, }; struct wlr_box shadow_box = { @@ -350,11 +351,102 @@ void client_draw_shadow(Client *c) { wlr_scene_shadow_set_clipped_region(c->shadow, clipped_region); } +void apply_split_border(Client *c, bool hit_no_border) { + + if (c->iskilling || !c->mon || !client_surface(c)->mapped) + return; + + const Layout *layout = c->mon->pertag->ltidxs[c->mon->pertag->curtag]; + + if (hit_no_border || layout->id != DWINDLE || + !config.dwindle_manual_split) { + if (c->splitindicator[0]->node.enabled) { + wlr_scene_node_set_enabled(&c->splitindicator[0]->node, false); + } + if (c->splitindicator[1]->node.enabled) { + wlr_scene_node_set_enabled(&c->splitindicator[1]->node, false); + } + return; + } else { + + DwindleNode **root = + &c->mon->pertag->dwindle_root[c->mon->pertag->curtag]; + DwindleNode *dnode = dwindle_find_leaf(*root, c); + + if (!dnode) { + wlr_scene_node_set_enabled(&c->splitindicator[0]->node, false); + wlr_scene_node_set_enabled(&c->splitindicator[1]->node, false); + return; + } else { + if (dnode->custom_leaf_split_h) { + wlr_scene_node_set_enabled(&c->splitindicator[0]->node, false); + wlr_scene_node_set_enabled(&c->splitindicator[1]->node, true); + } else { + wlr_scene_node_set_enabled(&c->splitindicator[0]->node, true); + wlr_scene_node_set_enabled(&c->splitindicator[1]->node, false); + } + } + } + + struct wlr_box fullgeom = c->animation.current; + // 一但在GEZERO如果使用无符号,那么其他数据也会转换为无符号导致没有负数出错 + int32_t bw = (int32_t)c->bw; + + int32_t right_offset, bottom_offset, left_offset, top_offset; + + if (c == grabc) { + right_offset = 0; + bottom_offset = 0; + left_offset = 0; + top_offset = 0; + } else { + right_offset = + GEZERO(c->animation.current.x + c->animation.current.width - + c->mon->m.x - c->mon->m.width); + bottom_offset = + GEZERO(c->animation.current.y + c->animation.current.height - + c->mon->m.y - c->mon->m.height); + + left_offset = GEZERO(c->mon->m.x - c->animation.current.x); + top_offset = GEZERO(c->mon->m.y - c->animation.current.y); + } + + int32_t border_down_width = GEZERO(fullgeom.width - left_offset - + right_offset - 2 * config.border_radius); + int32_t border_down_height = + GEZERO(bw - bottom_offset - GEZERO(top_offset + bw - fullgeom.height)); + + int32_t border_right_width = + GEZERO(bw - right_offset - GEZERO(left_offset + bw - fullgeom.width)); + int32_t border_right_height = + GEZERO(fullgeom.height - top_offset - bottom_offset - + 2 * config.border_radius); + + int32_t border_down_x = GEZERO(config.border_radius - left_offset); + int32_t border_down_y = GEZERO(fullgeom.height - bw) + + GEZERO(top_offset + bw - fullgeom.height); + + int32_t border_right_x = + GEZERO(fullgeom.width - bw) + GEZERO(left_offset + bw - fullgeom.width); + int32_t border_right_y = GEZERO(config.border_radius - top_offset); + + set_rect_size(c->splitindicator[0], border_down_width, border_down_height); + set_rect_size(c->splitindicator[1], border_right_width, + border_right_height); + wlr_scene_node_set_position(&c->splitindicator[0]->node, border_down_x, + border_down_y); + wlr_scene_node_set_position(&c->splitindicator[1]->node, border_right_x, + border_right_y); +} + void apply_border(Client *c) { if (!c || c->iskilling || !client_surface(c)->mapped) return; bool hit_no_border = check_hit_no_border(c); + + apply_split_border(c, hit_no_border); + enum corner_location current_corner_location; if (c->isfullscreen || (config.no_radius_when_single && c->mon && c->mon->visible_tiling_clients == 1)) { @@ -1203,6 +1295,7 @@ bool client_draw_fadeout_frame(Client *c) { void client_set_focused_opacity_animation(Client *c) { float *border_color = get_border_color(c); + wlr_scene_node_lower_to_bottom(&c->border->node); if (!config.animations) { setborder_color(c); @@ -1224,7 +1317,7 @@ void client_set_focused_opacity_animation(Client *c) { void client_set_unfocused_opacity_animation(Client *c) { float *border_color = get_border_color(c); - + wlr_scene_node_raise_to_top(&c->border->node); if (!config.animations) { setborder_color(c); return; diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 9548f13d..c9426c6a 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -249,6 +249,7 @@ typedef struct { int32_t dwindle_smart_split; int32_t dwindle_smart_resize; int32_t dwindle_drop_simple_split; + int32_t dwindle_manual_split; float dwindle_split_ratio; uint32_t hotarea_size; @@ -326,6 +327,7 @@ typedef struct { float rootcolor[4]; float bordercolor[4]; float dropcolor[4]; + float splitcolor[4]; float focuscolor[4]; float maximizescreencolor[4]; float urgentcolor[4]; @@ -1222,6 +1224,8 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value, (*arg).i = parse_direction(arg_value); } else if (strcmp(func_name, "toggle_all_floating") == 0) { func = toggle_all_floating; + } else if (strcmp(func_name, "dwindle_toggle_split_direction") == 0) { + func = dwindle_toggle_split_direction; } else { return NULL; } @@ -1635,6 +1639,8 @@ bool parse_option(Config *config, char *key, char *value) { config->dwindle_smart_resize = atoi(value); } else if (strcmp(key, "dwindle_drop_simple_split") == 0) { config->dwindle_drop_simple_split = atoi(value); + } else if (strcmp(key, "dwindle_manual_split") == 0) { + config->dwindle_manual_split = atoi(value); } else if (strcmp(key, "dwindle_split_ratio") == 0) { config->dwindle_split_ratio = atof(value); } else if (strcmp(key, "hotarea_size") == 0) { @@ -1777,6 +1783,17 @@ bool parse_option(Config *config, char *key, char *value) { } else { convert_hex_to_rgba(config->dropcolor, color); } + } else if (strcmp(key, "splitcolor") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid splitcolor " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->splitcolor, color); + } } else if (strcmp(key, "focuscolor") == 0) { int64_t color = parse_color(value); if (color == -1) { @@ -3209,6 +3226,7 @@ void override_config(void) { config.dwindle_smart_resize = CLAMP_INT(config.dwindle_smart_resize, 0, 1); config.dwindle_drop_simple_split = CLAMP_INT(config.dwindle_drop_simple_split, 0, 1); + config.dwindle_manual_split = CLAMP_INT(config.dwindle_manual_split, 0, 1); config.dwindle_split_ratio = CLAMP_FLOAT(config.dwindle_split_ratio, 0.05f, 0.95f); config.hotarea_size = CLAMP_INT(config.hotarea_size, 1, 1000); @@ -3356,6 +3374,7 @@ void set_value_default() { config.dwindle_smart_split = 0; config.dwindle_smart_resize = 0; config.dwindle_drop_simple_split = 1; + config.dwindle_manual_split = 0; config.dwindle_split_ratio = 0.5f; config.log_level = WLR_ERROR; @@ -3503,10 +3522,14 @@ void set_value_default() { config.bordercolor[1] = 0x44 / 255.0f; config.bordercolor[2] = 0x44 / 255.0f; config.bordercolor[3] = 1.0f; - config.dropcolor[0] = 0x8f / 255.0f; - config.dropcolor[1] = 0xba / 255.0f; - config.dropcolor[2] = 0x7c / 255.0f; + config.dropcolor[0] = 0xd5 / 255.0f; + config.dropcolor[1] = 0x89 / 255.0f; + config.dropcolor[2] = 0x9d / 255.0f; config.dropcolor[3] = 0.5f; + config.splitcolor[0] = 0xeb / 255.0f; + config.splitcolor[1] = 0x44 / 255.0f; + config.splitcolor[2] = 0x1e / 255.0f; + config.splitcolor[3] = 1.0f; config.focuscolor[0] = 0xc6 / 255.0f; config.focuscolor[1] = 0x6b / 255.0f; config.focuscolor[2] = 0x25 / 255.0f; @@ -3767,6 +3790,8 @@ void reapply_property(void) { } wlr_scene_rect_set_color(c->droparea, config.dropcolor); + wlr_scene_rect_set_color(c->splitindicator[0], config.splitcolor); + wlr_scene_rect_set_color(c->splitindicator[1], config.splitcolor); } } } diff --git a/src/dispatch/bind_declare.h b/src/dispatch/bind_declare.h index dbeebd33..41945dda 100644 --- a/src/dispatch/bind_declare.h +++ b/src/dispatch/bind_declare.h @@ -70,4 +70,5 @@ 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 +int32_t toggle_all_floating(const Arg *arg); +int32_t dwindle_toggle_split_direction(const Arg *arg); \ No newline at end of file diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index ffeff6f0..22fc16a3 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1917,3 +1917,28 @@ int32_t toggle_all_floating(const Arg *arg) { } return 0; } + +int32_t dwindle_toggle_split_direction(const Arg *arg) { + if (!selmon || !selmon->sel) + return 0; + + Client *c = selmon->sel; + if (!c || !c->mon || c->isfloating) + return 0; + + const Layout *layout = c->mon->pertag->ltidxs[c->mon->pertag->curtag]; + + if (layout->id != DWINDLE) + return 0; + + DwindleNode **root = &selmon->pertag->dwindle_root[selmon->pertag->curtag]; + DwindleNode *leaf = dwindle_find_leaf(*root, c); + + if (!leaf) + return 0; + + leaf->custom_leaf_split_h = !leaf->custom_leaf_split_h; + bool hit_no_border = check_hit_no_border(c); + apply_split_border(c, hit_no_border); + return 0; +} \ No newline at end of file diff --git a/src/layout/dwindle.h b/src/layout/dwindle.h index 94d65be6..bf56d4dc 100644 --- a/src/layout/dwindle.h +++ b/src/layout/dwindle.h @@ -1,20 +1,3 @@ -typedef struct DwindleNode DwindleNode; -struct DwindleNode { - bool is_split; - bool split_h; - bool split_locked; - float ratio; - float drag_init_ratio; - int32_t container_x; - int32_t container_y; - int32_t container_w; - int32_t container_h; - DwindleNode *parent; - DwindleNode *first; - DwindleNode *second; - Client *client; -}; - static DwindleNode *dwindle_locked_h_node = NULL; static DwindleNode *dwindle_locked_v_node = NULL; @@ -24,6 +7,39 @@ static DwindleNode *dwindle_new_leaf(Client *c) { return n; } +// 统计同方向上的节点总和 (N_old) +static int count_block_items(DwindleNode *node, bool split_h) { + if (!node) + return 0; + if (!node->is_split || node->split_h != split_h) + return 1; + return count_block_items(node->first, split_h) + + count_block_items(node->second, split_h); +} + +// 向上查找方向块路径,并计算每个祖先节点的绝对占比 +static int get_block_path_and_ratios(DwindleNode *target, bool split_h, + DwindleNode **path, float *p) { + int path_len = 0; + path[path_len++] = target; + DwindleNode *curr = target->parent; + while (curr && curr->split_h == split_h) { + path[path_len++] = curr; + curr = curr->parent; + } + + p[path_len - 1] = 1.0f; // 方向块根节点占比为 100% + for (int i = path_len - 1; i > 0; i--) { + DwindleNode *S = path[i]; + DwindleNode *child = path[i - 1]; + if (S->first == child) + p[i - 1] = p[i] * S->ratio; + else + p[i - 1] = p[i] * (1.0f - S->ratio); + } + return path_len; +} + static DwindleNode *dwindle_find_leaf(DwindleNode *node, Client *c) { if (!node) return NULL; @@ -55,6 +71,7 @@ static void dwindle_insert(DwindleNode **root, Client *new_c, Client *focused, DwindleNode *new_leaf = dwindle_new_leaf(new_c); if (!*root) { + new_leaf->custom_leaf_split_h = true; *root = new_leaf; return; } @@ -63,11 +80,45 @@ static void dwindle_insert(DwindleNode **root, Client *new_c, Client *focused, if (!target) target = dwindle_first_leaf(*root); + // ================= 保持其他窗口比例缩减逻辑 ================= + DwindleNode *path[512]; + float p[512]; + int path_len = get_block_path_and_ratios(target, split_h, path, p); + + int n_old = 1; + if (path_len > 1) { + n_old = count_block_items(path[path_len - 1], split_h); + } + float N = (float)(n_old + 1); + + for (int i = path_len - 1; i > 0; i--) { + DwindleNode *S = path[i]; + DwindleNode *child = path[i - 1]; + float p_S = p[i]; + float p_first = p_S * S->ratio; + + if (S->first == child) { + float p_first_new = p_first * (N - 1.0f) / N + 1.0f / N; + float p_S_new = p_S * (N - 1.0f) / N + 1.0f / N; + S->ratio = p_first_new / p_S_new; + } else { + float p_first_new = p_first * (N - 1.0f) / N; + float p_S_new = p_S * (N - 1.0f) / N + 1.0f / N; + S->ratio = p_first_new / p_S_new; + } + if (S->ratio < 0.001f) + S->ratio = 0.001f; + if (S->ratio > 0.999f) + S->ratio = 0.999f; + } + // ============================================================ + DwindleNode *split = calloc(1, sizeof(DwindleNode)); split->is_split = true; - split->ratio = ratio; split->split_h = split_h; split->split_locked = lock; + split->custom_leaf_split_h = target->custom_leaf_split_h; + new_leaf->custom_leaf_split_h = target->custom_leaf_split_h; if (as_first) { split->first = new_leaf; @@ -76,6 +127,10 @@ static void dwindle_insert(DwindleNode **root, Client *new_c, Client *focused, split->first = target; split->second = new_leaf; } + + // 通用逻辑 + split->ratio = ratio; + split->parent = target->parent; target->parent = split; new_leaf->parent = split; @@ -102,12 +157,69 @@ static void dwindle_remove(DwindleNode **root, Client *c) { return; } + // 开始删除空间的比例回退逻辑 + + // 查找连续的同方向块路径 + bool split_h = parent->split_h; + DwindleNode *path[128]; + int path_len = 0; + path[path_len++] = parent; + DwindleNode *curr = parent->parent; + while (curr && curr->split_h == split_h) { + path[path_len++] = curr; + curr = curr->parent; + } + + // 计算各祖先的旧绝对占比 + float p[128]; + p[path_len - 1] = 1.0f; + for (int i = path_len - 1; i > 0; i--) { + DwindleNode *S = path[i]; + DwindleNode *child = path[i - 1]; + if (S->first == child) + p[i - 1] = p[i] * S->ratio; + else + p[i - 1] = p[i] * (1.0f - S->ratio); + } + + // 计算即将被删除的叶子节点,在该方向块中所占的绝对面积比例 (P_del) + float p_del = + p[0] * (parent->first == leaf ? parent->ratio : (1.0f - parent->ratio)); + if (p_del > 0.999f) + p_del = 0.999f; // 兜底 + + // 重算祖先比例:将 P_del 空出来的空间,按原定比例无缝分配给其他窗口 + for (int i = path_len - 1; i > 0; i--) { + DwindleNode *S = path[i]; + DwindleNode *child = path[i - 1]; + float p_S = p[i]; + float p_first = p_S * S->ratio; + + float denom = p_S - p_del; + if (denom < 0.0001f) + denom = 0.0001f; + + if (S->first == child) { + S->ratio = (p_first - p_del) / denom; + } else { + S->ratio = p_first / denom; + } + + if (S->ratio < 0.001f) + S->ratio = 0.001f; + if (S->ratio > 0.999f) + S->ratio = 0.999f; + } + + // 比例重算结束 + + // 基础的二叉树摘除节点逻辑 DwindleNode *sibling = (parent->first == leaf) ? parent->second : parent->first; DwindleNode *grandparent = parent->parent; + sibling->parent = grandparent; - /* Preserve split direction on sibling split-nodes when requested. */ if (!sibling->is_split || (!config.dwindle_preserve_split && !config.dwindle_smart_split)) { sibling->container_w = 0; @@ -319,45 +431,86 @@ static void dwindle_remove_client(Client *c) { * dwindle_smart_split config options. */ static void dwindle_insert_with_config(DwindleNode **root, Client *new_c, Client *focused, float ratio) { + + if (!new_c || !focused) + return; + bool as_first = false; bool split_h = false; bool lock = false; - if (focused) { - struct wlr_box *fg = &focused->geom; - double fcx = fg->x + fg->width * 0.5; - double fcy = fg->y + fg->height * 0.5; + struct wlr_box *fg = &focused->geom; + double fcx = fg->x + fg->width * 0.5; + double fcy = fg->y + fg->height * 0.5; - if (config.dwindle_smart_split) { - double nx = (cursor->x - fcx) / (fg->width * 0.5); - double ny = (cursor->y - fcy) / (fg->height * 0.5); + if (config.dwindle_smart_split) { + double nx = (cursor->x - fcx) / (fg->width * 0.5); + double ny = (cursor->y - fcy) / (fg->height * 0.5); - if (fabs(ny) > fabs(nx)) { - split_h = false; // vertical split - as_first = (ny < 0); // top → new window on top - } else { - split_h = true; // horizontal split - as_first = (nx < 0); // left → new window on left - } - lock = true; // lock split direction + if (fabs(ny) > fabs(nx)) { + split_h = false; // vertical split + as_first = (ny < 0); // top → new window on top } else { - // normal mode, auto split - bool likely_h = (fg->width >= fg->height); - if (likely_h) { - if (config.dwindle_hsplit == 0) - as_first = (cursor->x < fcx); - else - as_first = (config.dwindle_hsplit == 2); - } else { - if (config.dwindle_vsplit == 0) - as_first = (cursor->y < fcy); - else - as_first = (config.dwindle_vsplit == 2); - } - // split_h and lock are false, decided by width/height ratio + split_h = true; // horizontal split + as_first = (nx < 0); // left → new window on left + } + lock = true; // lock split direction + } else { + // normal mode, auto split + bool likely_h = (fg->width >= fg->height); + split_h = likely_h; + + if (likely_h) { + if (config.dwindle_hsplit == 0) + as_first = (cursor->x < fcx); + else + as_first = (config.dwindle_hsplit == 2); + } else { + if (config.dwindle_vsplit == 0) + as_first = (cursor->y < fcy); + else + as_first = (config.dwindle_vsplit == 2); } } + DwindleNode *target = focused ? dwindle_find_leaf(*root, focused) : NULL; + if (!target && *root) + target = dwindle_first_leaf(*root); + + // 当且仅当 manual_split=1 时,计算精确的 1/N 新节点比例 + if (config.dwindle_manual_split && target) { + split_h = target->custom_leaf_split_h; + lock = true; + as_first = false; + + // ================= 计算新节点的 1/N 比例 ================= + DwindleNode *path[128]; + float p[128]; + int path_len = get_block_path_and_ratios(target, split_h, path, p); + + int n_old = 1; + if (path_len > 1) { + n_old = count_block_items(path[path_len - 1], split_h); + } + float N = (float)(n_old + 1); + + float p_target_old = p[0]; + float p_split_new = p_target_old * (N - 1.0f) / N + 1.0f / N; + + if (as_first) { + ratio = (1.0f / N) / p_split_new; + } else { + ratio = (p_target_old * (N - 1.0f) / N) / p_split_new; + } + + if (ratio < 0.001f) + ratio = 0.001f; + if (ratio > 0.999f) + ratio = 0.999f; + // ========================================================= + } + + // 调用通用插入函数 dwindle_insert(root, new_c, focused, ratio, as_first, split_h, lock); } @@ -431,3 +584,8 @@ void dwindle(Monitor *m) { m->w.width - 2 * gap_oh, m->w.height - 2 * gap_ov, gap_ih, gap_iv); } + +void cleanup_monitor_dwindle(Monitor *m) { + for (uint32_t t = 0; t < LENGTH(tags) + 1; t++) + dwindle_free_tree(m->pertag->dwindle_root[t]); +} \ No newline at end of file diff --git a/src/mango.c b/src/mango.c index f12d8e1c..8c11c318 100644 --- a/src/mango.c +++ b/src/mango.c @@ -317,6 +317,7 @@ struct Client { struct wlr_scene_tree *scene; struct wlr_scene_rect *border; /* top, bottom, left, right */ struct wlr_scene_rect *droparea; + struct wlr_scene_rect *splitindicator[4]; struct wlr_scene_shadow *shadow; struct wlr_scene_tree *scene_surface; struct wl_list link; @@ -561,6 +562,23 @@ typedef struct { } SessionLock; typedef struct DwindleNode DwindleNode; +struct DwindleNode { + bool is_split; + bool split_h; + bool split_locked; + bool custom_leaf_split_h; + float ratio; + float drag_init_ratio; + int32_t container_x; + int32_t container_y; + int32_t container_w; + int32_t container_h; + DwindleNode *parent; + DwindleNode *first; + DwindleNode *second; + Client *client; +}; + struct ScrollerStackNode { Client *client; float scroller_proportion; @@ -848,6 +866,7 @@ static struct ScrollerStackNode * scroller_node_create(struct TagScrollerState *st, Client *c); static void update_scroller_state(Monitor *m); Client *scroll_get_stack_tail_client(Client *c); +static DwindleNode *dwindle_find_leaf(DwindleNode *node, Client *c); #include "data/static_keymap.h" #include "dispatch/bind_declare.h" @@ -1212,38 +1231,47 @@ void swallow(Client *c, Client *w) { client_pending_maximized_state(c, w->ismaximizescreen); client_pending_minimized_state(c, w->isminimized); - /* ---------- 跨 tag 同步:dwindle 与 scroller ---------- */ - Monitor *m; - wl_list_for_each(m, &mons, link) { + if (!w->mon) + return; + + const Layout *layout = w->mon->pertag->ltidxs[w->mon->pertag->curtag]; + + if (layout->id == DWINDLE || layout->id == SCROLLER || + layout->id == VERTICAL_SCROLLER) { + for (uint32_t t = 0; t < LENGTH(tags) + 1; t++) { /* dwindle */ - DwindleNode **root = &m->pertag->dwindle_root[t]; - dwindle_remove(root, c); - DwindleNode *dnode = dwindle_find_leaf(*root, w); - if (dnode) - dnode->client = c; - /* scroller */ - struct TagScrollerState *st = m->pertag->scroller_state[t]; - if (!st) - continue; + if (layout->id == DWINDLE) { - /* 先移除 c 在任意 tag 中的旧节点 */ - struct ScrollerStackNode *cn = find_scroller_node(st, c); - if (cn) - scroller_node_remove(st, cn); + DwindleNode **root = &w->mon->pertag->dwindle_root[t]; + dwindle_remove(root, c); + DwindleNode *dnode = dwindle_find_leaf(*root, w); + if (dnode) + dnode->client = c; + } - /* 将 w 的节点(如果存在)转给 c */ - struct ScrollerStackNode *wn = find_scroller_node(st, w); - if (wn) - wn->client = c; + // scroller + if (layout->id == SCROLLER || layout->id == VERTICAL_SCROLLER) { + struct TagScrollerState *st = w->mon->pertag->scroller_state[t]; + if (!st) + continue; + /* 先移除 c 在任意 tag 中的旧节点 */ + struct ScrollerStackNode *cn = find_scroller_node(st, c); + if (cn) + scroller_node_remove(st, cn); + + /* 将 w 的节点(如果存在)转给 c */ + struct ScrollerStackNode *wn = find_scroller_node(st, w); + if (wn) + wn->client = c; + } } } /* 同步当前活动 tag 的全局客户端字段 */ - if (c->mon) { - uint32_t curtag = c->mon->pertag->curtag; - sync_scroller_state_to_clients(c->mon, curtag); + if (layout->id == SCROLLER || layout->id == VERTICAL_SCROLLER) { + sync_scroller_state_to_clients(w->mon, w->mon->pertag->curtag); } } @@ -2503,9 +2531,7 @@ void cleanupmon(struct wl_listener *listener, void *data) { } m->wlr_output->data = NULL; - for (uint32_t t = 0; t < LENGTH(tags) + 1; t++) - dwindle_free_tree(m->pertag->dwindle_root[t]); - + cleanup_monitor_dwindle(m); cleanup_monitor_scroller(m); free(m->pertag); @@ -4334,6 +4360,7 @@ mapnotify(struct wl_listener *listener, void *data) { /* Called when the surface is mapped, or ready to display on-screen. */ Client *at_client = NULL; Client *c = wl_container_of(listener, c, map); + int32_t i = 0; /* Create scene tree for this client and its border */ c->scene = client_surface(c)->data = wlr_scene_tree_create(layers[LyrTile]); wlr_scene_node_set_enabled(&c->scene->node, c->type != XDGShell); @@ -4386,6 +4413,15 @@ mapnotify(struct wl_listener *listener, void *data) { // extra node + for (i = 0; i < 2; i++) { + c->splitindicator[i] = wlr_scene_rect_create( + c->scene, 0, 0, + c->isurgent ? config.urgentcolor : config.splitcolor); + c->splitindicator[i]->node.data = c; + wlr_scene_node_lower_to_bottom(&c->splitindicator[i]->node); + wlr_scene_node_set_enabled(&c->splitindicator[i]->node, false); + } + c->droparea = wlr_scene_rect_create(c->scene, 0, 0, config.dropcolor); wlr_scene_node_lower_to_bottom(&c->droparea->node); wlr_scene_node_set_position(&c->droparea->node, 0, 0); @@ -5168,18 +5204,27 @@ exchange_common: tmp2_next->prev = &c1->link; } - if (config.exchange_cross_monitor && c1->mon != c2->mon) { - DwindleNode **c1_root = &m1->pertag->dwindle_root[m1->pertag->curtag]; - DwindleNode *c1node = dwindle_find_leaf(*c1_root, c1); + const Layout *layout1 = c1->mon->pertag->ltidxs[c1->mon->pertag->curtag]; - DwindleNode **c2_root = &m2->pertag->dwindle_root[m2->pertag->curtag]; - DwindleNode *c2node = dwindle_find_leaf(*c2_root, c2); + const Layout *layout2 = c2->mon->pertag->ltidxs[c2->mon->pertag->curtag]; - if (c1node) - c1node->client = c2; + if (c1->mon != c2->mon) { - if (c2node) - c2node->client = c1; + if (layout1->id == DWINDLE && layout2->id == DWINDLE) { + DwindleNode **c1_root = + &m1->pertag->dwindle_root[m1->pertag->curtag]; + DwindleNode *c1node = dwindle_find_leaf(*c1_root, c1); + + DwindleNode **c2_root = + &m2->pertag->dwindle_root[m2->pertag->curtag]; + DwindleNode *c2node = dwindle_find_leaf(*c2_root, c2); + + if (c1node) + c1node->client = c2; + + if (c2node) + c2node->client = c1; + } tmp_mon = c2->mon; tmp_tags = c2->tags; @@ -5191,8 +5236,7 @@ exchange_common: arrange(c1->mon, false, false); arrange(c2->mon, false, false); } else { - if (c1->mon && - c1->mon->pertag->ltidxs[c1->mon->pertag->curtag]->id == DWINDLE) { + if (layout1->id == DWINDLE && layout2->id == DWINDLE) { dwindle_swap_clients( &c1->mon->pertag->dwindle_root[c1->mon->pertag->curtag], c1, c2); From 142cbb5e066de8a05e4030c2c36bbb4c7a13dd9c Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 14 May 2026 23:09:47 +0800 Subject: [PATCH 195/328] fix: fix potential wild Pointers --- src/layout/dwindle.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/layout/dwindle.h b/src/layout/dwindle.h index bf56d4dc..e3b9f281 100644 --- a/src/layout/dwindle.h +++ b/src/layout/dwindle.h @@ -151,6 +151,12 @@ static void dwindle_remove(DwindleNode **root, Client *c) { return; DwindleNode *parent = leaf->parent; + + if (dwindle_locked_h_node == leaf || dwindle_locked_h_node == parent) + dwindle_locked_h_node = NULL; + if (dwindle_locked_v_node == leaf || dwindle_locked_v_node == parent) + dwindle_locked_v_node = NULL; + if (!parent) { free(leaf); *root = NULL; From 42a46259fb2749387136be4c3a69dc1a273d154f Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 15 May 2026 07:51:22 +0800 Subject: [PATCH 196/328] update docs and config --- assets/config.conf | 15 +++++++++++++++ docs/bindings/keys.md | 1 + docs/window-management/layouts.md | 1 + 3 files changed, 17 insertions(+) diff --git a/assets/config.conf b/assets/config.conf index 938a1f2f..bc141e63 100644 --- a/assets/config.conf +++ b/assets/config.conf @@ -66,6 +66,14 @@ default_mfact=0.55 default_nmaster=1 smartgaps=0 +# Dwindle Layout Setting +dwindle_smart_split=0 +dwindle_drop_simple_split=1 +dwindle_manual_split=0 +dwindle_hsplit=1 +dwindle_vsplit=1 +dwindle_preserve_split=0 + # Overview Setting hotarea_size=10 enable_hotarea=1 @@ -184,6 +192,13 @@ bind=ALT,z,toggle_scratchpad # scroller layout bind=ALT,e,set_proportion,1.0 bind=ALT,x,switch_proportion_preset, +bind=alt+super+ctrl,Left,scroller_stack,left +bind=alt+super+ctrl,Right,scroller_stack,right +bind=alt+super+ctrl,Up,scroller_stack,up +bind=alt+super+ctrl,Down,scroller_stack,down + +#dwindle layout(manual split mode) +bind=alt+shift,Return,dwindle_toggle_split_direction # switch layout bind=SUPER,n,switch_layout diff --git a/docs/bindings/keys.md b/docs/bindings/keys.md index b3a4ab64..002c9564 100644 --- a/docs/bindings/keys.md +++ b/docs/bindings/keys.md @@ -147,6 +147,7 @@ bindr=Super,Super_L,spawn,rofi -show run | `scroller_stack` | `left/right/up/down` | Move window inside/outside scroller stack by direction. | | `incgaps` | `+/-value` | Adjust gap size. | | `togglegaps` | - | Toggle gaps. | +| `dwindle_toggle_split_direction` | - | Toggle split direction in dwindle layout. | ### System diff --git a/docs/window-management/layouts.md b/docs/window-management/layouts.md index b5f14a4d..4ed2b404 100644 --- a/docs/window-management/layouts.md +++ b/docs/window-management/layouts.md @@ -98,6 +98,7 @@ The Dwindle layout arranges windows as a binary tree of recursive splits. Each n | `dwindle_preserve_split` | `0` | Keep the sibling's split orientation when a window is closed. | | `dwindle_smart_resize` | `0` | When dragging to resize, move the split toward the cursor regardless of which side was grabbed. | | `dwindle_drop_simple_split` | `1` | Drag-to-tile drop preview. `1` = 2-zone preview matching `dwindle_split_ratio`, `0` = 4-quadrant preview. | +| `dwindle_manual_split` | `0` | Manually split windows mode. | ```ini # Example dwindle configuration From 89b24d514c4021e90eee009aa356b0562175e829 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 15 May 2026 07:59:35 +0800 Subject: [PATCH 197/328] opt: avoid manula caculate in normal dwindle mode --- src/layout/dwindle.h | 156 ++++++++++++++++++++++--------------------- 1 file changed, 80 insertions(+), 76 deletions(-) diff --git a/src/layout/dwindle.h b/src/layout/dwindle.h index e3b9f281..ddc17320 100644 --- a/src/layout/dwindle.h +++ b/src/layout/dwindle.h @@ -81,35 +81,37 @@ static void dwindle_insert(DwindleNode **root, Client *new_c, Client *focused, target = dwindle_first_leaf(*root); // ================= 保持其他窗口比例缩减逻辑 ================= - DwindleNode *path[512]; - float p[512]; - int path_len = get_block_path_and_ratios(target, split_h, path, p); + if (config.dwindle_manual_split) { + DwindleNode *path[512]; + float p[512]; + int path_len = get_block_path_and_ratios(target, split_h, path, p); - int n_old = 1; - if (path_len > 1) { - n_old = count_block_items(path[path_len - 1], split_h); - } - float N = (float)(n_old + 1); - - for (int i = path_len - 1; i > 0; i--) { - DwindleNode *S = path[i]; - DwindleNode *child = path[i - 1]; - float p_S = p[i]; - float p_first = p_S * S->ratio; - - if (S->first == child) { - float p_first_new = p_first * (N - 1.0f) / N + 1.0f / N; - float p_S_new = p_S * (N - 1.0f) / N + 1.0f / N; - S->ratio = p_first_new / p_S_new; - } else { - float p_first_new = p_first * (N - 1.0f) / N; - float p_S_new = p_S * (N - 1.0f) / N + 1.0f / N; - S->ratio = p_first_new / p_S_new; + int n_old = 1; + if (path_len > 1) { + n_old = count_block_items(path[path_len - 1], split_h); + } + float N = (float)(n_old + 1); + + for (int i = path_len - 1; i > 0; i--) { + DwindleNode *S = path[i]; + DwindleNode *child = path[i - 1]; + float p_S = p[i]; + float p_first = p_S * S->ratio; + + if (S->first == child) { + float p_first_new = p_first * (N - 1.0f) / N + 1.0f / N; + float p_S_new = p_S * (N - 1.0f) / N + 1.0f / N; + S->ratio = p_first_new / p_S_new; + } else { + float p_first_new = p_first * (N - 1.0f) / N; + float p_S_new = p_S * (N - 1.0f) / N + 1.0f / N; + S->ratio = p_first_new / p_S_new; + } + if (S->ratio < 0.001f) + S->ratio = 0.001f; + if (S->ratio > 0.999f) + S->ratio = 0.999f; } - if (S->ratio < 0.001f) - S->ratio = 0.001f; - if (S->ratio > 0.999f) - S->ratio = 0.999f; } // ============================================================ @@ -166,55 +168,57 @@ static void dwindle_remove(DwindleNode **root, Client *c) { // 开始删除空间的比例回退逻辑 // 查找连续的同方向块路径 - bool split_h = parent->split_h; - DwindleNode *path[128]; - int path_len = 0; - path[path_len++] = parent; - DwindleNode *curr = parent->parent; - while (curr && curr->split_h == split_h) { - path[path_len++] = curr; - curr = curr->parent; - } - - // 计算各祖先的旧绝对占比 - float p[128]; - p[path_len - 1] = 1.0f; - for (int i = path_len - 1; i > 0; i--) { - DwindleNode *S = path[i]; - DwindleNode *child = path[i - 1]; - if (S->first == child) - p[i - 1] = p[i] * S->ratio; - else - p[i - 1] = p[i] * (1.0f - S->ratio); - } - - // 计算即将被删除的叶子节点,在该方向块中所占的绝对面积比例 (P_del) - float p_del = - p[0] * (parent->first == leaf ? parent->ratio : (1.0f - parent->ratio)); - if (p_del > 0.999f) - p_del = 0.999f; // 兜底 - - // 重算祖先比例:将 P_del 空出来的空间,按原定比例无缝分配给其他窗口 - for (int i = path_len - 1; i > 0; i--) { - DwindleNode *S = path[i]; - DwindleNode *child = path[i - 1]; - float p_S = p[i]; - float p_first = p_S * S->ratio; - - float denom = p_S - p_del; - if (denom < 0.0001f) - denom = 0.0001f; - - if (S->first == child) { - S->ratio = (p_first - p_del) / denom; - } else { - S->ratio = p_first / denom; + if (config.dwindle_manual_split) { + bool split_h = parent->split_h; + DwindleNode *path[512]; + int path_len = 0; + path[path_len++] = parent; + DwindleNode *curr = parent->parent; + while (curr && curr->split_h == split_h) { + path[path_len++] = curr; + curr = curr->parent; } - if (S->ratio < 0.001f) - S->ratio = 0.001f; - if (S->ratio > 0.999f) - S->ratio = 0.999f; + // 计算各祖先的旧绝对占比 + float p[512]; + p[path_len - 1] = 1.0f; + for (int i = path_len - 1; i > 0; i--) { + DwindleNode *S = path[i]; + DwindleNode *child = path[i - 1]; + if (S->first == child) + p[i - 1] = p[i] * S->ratio; + else + p[i - 1] = p[i] * (1.0f - S->ratio); + } + + // 计算即将被删除的叶子节点,在该方向块中所占的绝对面积比例 (P_del) + float p_del = p[0] * (parent->first == leaf ? parent->ratio + : (1.0f - parent->ratio)); + if (p_del > 0.999f) + p_del = 0.999f; // 兜底 + + // 重算祖先比例:将 P_del 空出来的空间,按原定比例无缝分配给其他窗口 + for (int i = path_len - 1; i > 0; i--) { + DwindleNode *S = path[i]; + DwindleNode *child = path[i - 1]; + float p_S = p[i]; + float p_first = p_S * S->ratio; + + float denom = p_S - p_del; + if (denom < 0.0001f) + denom = 0.0001f; + + if (S->first == child) { + S->ratio = (p_first - p_del) / denom; + } else { + S->ratio = p_first / denom; + } + + if (S->ratio < 0.001f) + S->ratio = 0.001f; + if (S->ratio > 0.999f) + S->ratio = 0.999f; + } } // 比例重算结束 @@ -490,8 +494,8 @@ static void dwindle_insert_with_config(DwindleNode **root, Client *new_c, as_first = false; // ================= 计算新节点的 1/N 比例 ================= - DwindleNode *path[128]; - float p[128]; + DwindleNode *path[512]; + float p[512]; int path_len = get_block_path_and_ratios(target, split_h, path, p); int n_old = 1; From e17d443a430d87374a27b7a5a7b2db7639666d00 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 15 May 2026 08:07:18 +0800 Subject: [PATCH 198/328] uodate docs --- docs/visuals/theming.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/visuals/theming.md b/docs/visuals/theming.md index 03750e4e..676c575b 100644 --- a/docs/visuals/theming.md +++ b/docs/visuals/theming.md @@ -29,6 +29,9 @@ bordercolor=0x444444ff # Drop shadow when dragging windows dropcolor=0x8FBA7C55 +# Split window border color in manual dwindle layout +splitcolor=0xEB441EFF + # Active window border focuscolor=0xc66b25ff From d4ea89f3560c66ecbb8c7a37d375db1345fd50f3 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 15 May 2026 10:19:05 +0800 Subject: [PATCH 199/328] opt: dwindle_hsplit and dwindle_vsplit default to 1 --- docs/window-management/layouts.md | 4 ++-- src/config/parse_config.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/window-management/layouts.md b/docs/window-management/layouts.md index 4ed2b404..bf5283d7 100644 --- a/docs/window-management/layouts.md +++ b/docs/window-management/layouts.md @@ -93,8 +93,8 @@ The Dwindle layout arranges windows as a binary tree of recursive splits. Each n | :--- | :--- | :--- | | `dwindle_split_ratio` | `0.5` | Ratio used for new splits (`0.05`–`0.95`). | | `dwindle_smart_split` | `0` | Pick the split axis from the cursor's position inside the focused window. The new window appears on the cursor's side. | -| `dwindle_hsplit` | `0` | Side-by-side splits: where the new window goes. `0` = follow cursor, `1` = right, `2` = left. | -| `dwindle_vsplit` | `0` | Top/bottom splits: where the new window goes. `0` = follow cursor, `1` = below, `2` = above. | +| `dwindle_hsplit` | `1` | Side-by-side splits: where the new window goes. `0` = follow cursor, `1` = right, `2` = left. | +| `dwindle_vsplit` | `1` | Top/bottom splits: where the new window goes. `0` = follow cursor, `1` = below, `2` = above. | | `dwindle_preserve_split` | `0` | Keep the sibling's split orientation when a window is closed. | | `dwindle_smart_resize` | `0` | When dragging to resize, move the split toward the cursor regardless of which side was grabbed. | | `dwindle_drop_simple_split` | `1` | Drag-to-tile drop preview. `1` = 2-zone preview matching `dwindle_split_ratio`, `0` = 4-quadrant preview. | diff --git a/src/config/parse_config.h b/src/config/parse_config.h index c9426c6a..9d925457 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -3368,8 +3368,8 @@ void set_value_default() { config.center_master_overspread = 0; config.center_when_single_stack = 1; - config.dwindle_vsplit = 0; - config.dwindle_hsplit = 0; + config.dwindle_vsplit = 1; + config.dwindle_hsplit = 1; config.dwindle_preserve_split = 0; config.dwindle_smart_split = 0; config.dwindle_smart_resize = 0; From 1ce245af5cff99f8c66eb73a6c599ff77e90dada Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 15 May 2026 10:52:49 +0800 Subject: [PATCH 200/328] bump version to 0.13.0 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index ca9f2c2b..3cc9ed95 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('mango', ['c', 'cpp'], - version : '0.12.9', + version : '0.13.0', ) subdir('protocols') From 4be9e8035502a30f6b5ccb6a6e751715c2870e02 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 15 May 2026 15:03:46 +0800 Subject: [PATCH 201/328] fix: split border position error caculate --- src/animation/client.h | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/animation/client.h b/src/animation/client.h index 2b8b7b55..bb1d2231 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -411,24 +411,27 @@ void apply_split_border(Client *c, bool hit_no_border) { top_offset = GEZERO(c->mon->m.y - c->animation.current.y); } - int32_t border_down_width = GEZERO(fullgeom.width - left_offset - - right_offset - 2 * config.border_radius); + int32_t border_down_width = + GEZERO(fullgeom.width - 2 * config.border_radius - + GEZERO((left_offset + right_offset) - config.border_radius)); int32_t border_down_height = GEZERO(bw - bottom_offset - GEZERO(top_offset + bw - fullgeom.height)); int32_t border_right_width = GEZERO(bw - right_offset - GEZERO(left_offset + bw - fullgeom.width)); int32_t border_right_height = - GEZERO(fullgeom.height - top_offset - bottom_offset - - 2 * config.border_radius); + GEZERO(fullgeom.height - 2 * config.border_radius - + GEZERO((top_offset + bottom_offset) - config.border_radius)); - int32_t border_down_x = GEZERO(config.border_radius - left_offset); + int32_t border_down_x = GEZERO(config.border_radius + + GEZERO(left_offset - config.border_radius)); int32_t border_down_y = GEZERO(fullgeom.height - bw) + GEZERO(top_offset + bw - fullgeom.height); int32_t border_right_x = GEZERO(fullgeom.width - bw) + GEZERO(left_offset + bw - fullgeom.width); - int32_t border_right_y = GEZERO(config.border_radius - top_offset); + int32_t border_right_y = GEZERO(config.border_radius + + GEZERO(top_offset - config.border_radius)); set_rect_size(c->splitindicator[0], border_down_width, border_down_height); set_rect_size(c->splitindicator[1], border_right_width, From 58d989d21881e2b78bfc8a8f1c6bd8203a91f53b Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 16 May 2026 08:23:48 +0800 Subject: [PATCH 202/328] feat: fair layout --- src/animation/client.h | 1 + src/layout/arrange.h | 1 + src/layout/horizontal.h | 76 +++++++++++++++++++++++++++++++++++++++++ src/layout/layout.h | 6 ++++ src/layout/vertical.h | 76 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 160 insertions(+) diff --git a/src/animation/client.h b/src/animation/client.h index bb1d2231..a34a3348 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -645,6 +645,7 @@ void client_set_drop_area(Client *c) { bool should_swap = (cur_layout->id == DECK || cur_layout->id == VERTICAL_DECK || cur_layout->id == MONOCLE || cur_layout->id == GRID || + cur_layout->id == FAIR || cur_layout->id == VERTICAL_FAIR || cur_layout->id == VERTICAL_GRID) || ((cur_layout->id == TILE || cur_layout->id == VERTICAL_TILE || cur_layout->id == CENTER_TILE || cur_layout->id == RIGHT_TILE) && diff --git a/src/layout/arrange.h b/src/layout/arrange.h index 41598d5e..42d15f86 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -33,6 +33,7 @@ void restore_size_per(Monitor *m, Client *c) { if (current_layout->id == SCROLLER || current_layout->id == VERTICAL_SCROLLER || current_layout->id == GRID || + current_layout->id == FAIR || current_layout->id == VERTICAL_FAIR || current_layout->id == VERTICAL_GRID || current_layout->id == DECK || current_layout->id == VERTICAL_DECK || current_layout->id == MONOCLE) { return; diff --git a/src/layout/horizontal.h b/src/layout/horizontal.h index 37c8793e..3ff8bd44 100644 --- a/src/layout/horizontal.h +++ b/src/layout/horizontal.h @@ -674,4 +674,80 @@ monocle(Monitor *m) { } if ((c = focustop(m))) wlr_scene_node_raise_to_top(&c->scene->node); +} + +void fair(Monitor *m) { + int32_t i, n = 0; + Client *c = NULL; + + n = m->visible_tiling_clients; + if (n == 0) + return; + + // 间隙参数处理 + int32_t cur_gappiv = enablegaps ? m->gappiv : 0; + int32_t cur_gappih = enablegaps ? m->gappih : 0; + int32_t cur_gappov = enablegaps ? m->gappov : 0; + int32_t cur_gappoh = enablegaps ? m->gappoh : 0; + + // 智能间隙 + cur_gappiv = config.smartgaps && n == 1 ? 0 : cur_gappiv; + cur_gappih = config.smartgaps && n == 1 ? 0 : cur_gappih; + cur_gappov = config.smartgaps && n == 1 ? 0 : cur_gappov; + cur_gappoh = config.smartgaps && n == 1 ? 0 : cur_gappoh; + + // 计算最佳列数 cols = ceil(sqrt(n)) + int32_t cols; + for (cols = 0; cols <= n; cols++) { + if (cols * cols >= n) + break; + } + + int32_t base_rows = n / cols; // 每列的基础行数 + int32_t remainder = n % cols; // 多出来的窗口,分配给前 remainder 列 + + // 计算标准列宽 + int32_t col_width = + (m->w.width - 2 * cur_gappoh - (cols - 1) * cur_gappih) / cols; + + i = 0; + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || !ISTILED(c)) + continue; + + int32_t col_idx, row_idx, rows_in_this_col; + + // 判断当前窗口属于哪一列、哪一行 + if (i < remainder * (base_rows + 1)) { + col_idx = i / (base_rows + 1); + row_idx = i % (base_rows + 1); + rows_in_this_col = base_rows + 1; + } else { + int32_t offset = i - remainder * (base_rows + 1); + col_idx = remainder + offset / base_rows; + row_idx = offset % base_rows; + rows_in_this_col = base_rows; + } + + // 计算 X 坐标和宽度 (最后一列吃掉剩余像素,防止缝隙) + int32_t cx = m->w.x + cur_gappoh + col_idx * (col_width + cur_gappih); + int32_t cw = (col_idx == cols - 1) + ? (m->w.width - 2 * cur_gappoh - + col_idx * (col_width + cur_gappih)) + : col_width; + + // 计算 Y 坐标和高度 (最后一行吃掉剩余像素) + int32_t base_ch = (m->w.height - 2 * cur_gappov - + (rows_in_this_col - 1) * cur_gappiv) / + rows_in_this_col; + int32_t cy = m->w.y + cur_gappov + row_idx * (base_ch + cur_gappiv); + int32_t ch = (row_idx == rows_in_this_col - 1) + ? (m->w.height - 2 * cur_gappov - + row_idx * (base_ch + cur_gappiv)) + : base_ch; + + resize(c, (struct wlr_box){.x = cx, .y = cy, .width = cw, .height = ch}, + 0); + i++; + } } \ No newline at end of file diff --git a/src/layout/layout.h b/src/layout/layout.h index 16773dc7..1f82eeaa 100644 --- a/src/layout/layout.h +++ b/src/layout/layout.h @@ -12,6 +12,8 @@ static void vertical_grid(Monitor *m); static void vertical_scroller(Monitor *m); static void vertical_deck(Monitor *mon); static void dwindle(Monitor *m); +static void fair(Monitor *m); +static void vertical_fair(Monitor *m); /* layout(s) */ Layout overviewlayout = {"󰃇", overview, "overview"}; @@ -29,6 +31,8 @@ enum { VERTICAL_DECK, RIGHT_TILE, DWINDLE, + FAIR, + VERTICAL_FAIR, }; Layout layouts[] = { @@ -47,4 +51,6 @@ Layout layouts[] = { {"VG", vertical_grid, "vertical_grid", VERTICAL_GRID}, // 垂直格子布局 {"VK", vertical_deck, "vertical_deck", VERTICAL_DECK}, // 垂直卡片布局 {"DW", dwindle, "dwindle", DWINDLE}, + {"F", fair, "fair", FAIR}, + {"VF", vertical_fair, "vertical_fair", VERTICAL_FAIR}, }; \ No newline at end of file diff --git a/src/layout/vertical.h b/src/layout/vertical.h index 36114dde..484dfd85 100644 --- a/src/layout/vertical.h +++ b/src/layout/vertical.h @@ -281,4 +281,80 @@ void vertical_grid(Monitor *m) { i++; } } +} + +void vertical_fair(Monitor *m) { + int32_t i, n = 0; + Client *c = NULL; + + n = m->visible_tiling_clients; + if (n == 0) + return; + + // 间隙参数处理 + int32_t cur_gappiv = enablegaps ? m->gappiv : 0; + int32_t cur_gappih = enablegaps ? m->gappih : 0; + int32_t cur_gappov = enablegaps ? m->gappov : 0; + int32_t cur_gappoh = enablegaps ? m->gappoh : 0; + + // 智能间隙 + cur_gappiv = config.smartgaps && n == 1 ? 0 : cur_gappiv; + cur_gappih = config.smartgaps && n == 1 ? 0 : cur_gappih; + cur_gappov = config.smartgaps && n == 1 ? 0 : cur_gappov; + cur_gappoh = config.smartgaps && n == 1 ? 0 : cur_gappoh; + + // 计算最佳行数 rows = ceil(sqrt(n)) + int32_t rows; + for (rows = 0; rows <= n; rows++) { + if (rows * rows >= n) + break; + } + + int32_t base_cols = n / rows; // 每行的基础列数 + int32_t remainder = n % rows; // 多出来的窗口,分配给前 remainder 行 + + // 计算标准行高 + int32_t row_height = + (m->w.height - 2 * cur_gappov - (rows - 1) * cur_gappiv) / rows; + + i = 0; + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || !ISTILED(c)) + continue; + + int32_t row_idx, col_idx, cols_in_this_row; + + // 判断当前窗口属于哪一行、哪一列 + if (i < remainder * (base_cols + 1)) { + row_idx = i / (base_cols + 1); + col_idx = i % (base_cols + 1); + cols_in_this_row = base_cols + 1; + } else { + int32_t offset = i - remainder * (base_cols + 1); + row_idx = remainder + offset / base_cols; + col_idx = offset % base_cols; + cols_in_this_row = base_cols; + } + + // 计算 Y 坐标和高度 (最后一行吃掉剩余像素) + int32_t cy = m->w.y + cur_gappov + row_idx * (row_height + cur_gappiv); + int32_t ch = (row_idx == rows - 1) + ? (m->w.height - 2 * cur_gappov - + row_idx * (row_height + cur_gappiv)) + : row_height; + + // 计算 X 坐标和宽度 (最后一列吃掉剩余像素,防止缝隙) + int32_t base_cw = (m->w.width - 2 * cur_gappoh - + (cols_in_this_row - 1) * cur_gappih) / + cols_in_this_row; + int32_t cx = m->w.x + cur_gappoh + col_idx * (base_cw + cur_gappih); + int32_t cw = (col_idx == cols_in_this_row - 1) + ? (m->w.width - 2 * cur_gappoh - + col_idx * (base_cw + cur_gappih)) + : base_cw; + + resize(c, (struct wlr_box){.x = cx, .y = cy, .width = cw, .height = ch}, + 0); + i++; + } } \ No newline at end of file From 4ca7439575814b3b9d975c36a1c7f59e65dc61b1 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 16 May 2026 13:51:18 +0800 Subject: [PATCH 203/328] update readme --- docs/ipc.md | 2 +- docs/window-management/layouts.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/ipc.md b/docs/ipc.md index 8bb0f5c1..e3ec4a32 100644 --- a/docs/ipc.md +++ b/docs/ipc.md @@ -64,7 +64,7 @@ mmsg -s -t 2^ ### Layouts -Switch layouts programmatically. Layout codes: `S` (Scroller), `T` (Tile), `G` (Grid), `M` (Monocle), `K` (Deck), `CT` (Center Tile), `RT` (Right Tile), `VS` (Vertical Scroller), `VT` (Vertical Tile), `VG` (Vertical Grid), `VK` (Vertical Deck), `DW` (Dwindle). +Switch layouts programmatically. Layout codes: `S` (Scroller), `T` (Tile), `G` (Grid), `M` (Monocle), `K` (Deck), `CT` (Center Tile), `RT` (Right Tile), `VS` (Vertical Scroller), `VT` (Vertical Tile), `VG` (Vertical Grid), `VK` (Vertical Deck), `DW` (Dwindle), `F` (Fair), `VF` (Vertical Fair). ```bash # Switch to Scroller diff --git a/docs/window-management/layouts.md b/docs/window-management/layouts.md index bf5283d7..45b08ec2 100644 --- a/docs/window-management/layouts.md +++ b/docs/window-management/layouts.md @@ -19,6 +19,8 @@ mangowm supports a variety of layouts that can be assigned per tag. - `vertical_grid` - `vertical_deck` - `dwindle` +- `fair` +- `vertical_fair` --- From 469249b39ef284b28172d2c70963f5cec3ce15cd Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 16 May 2026 15:13:39 +0800 Subject: [PATCH 204/328] opt: dont let window get pointer focus in overview --- src/animation/client.h | 4 +++- src/dispatch/bind_define.h | 5 +++++ src/mango.c | 8 +++++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/animation/client.h b/src/animation/client.h index a34a3348..aed97c1f 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -1000,7 +1000,9 @@ void client_animation_next_tick(Client *c) { surface = pointer_c && pointer_c == c ? client_surface(pointer_c) : NULL; - if (surface && pointer_c == selmon->sel) { + + // avoid game window force grab pointer in overview mode + if (surface && pointer_c == selmon->sel && !selmon->isoverview) { wlr_seat_pointer_notify_enter(seat, surface, sx, sy); } diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 22fc16a3..ff5ccdf5 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1736,6 +1736,11 @@ int32_t toggleoverview(const Arg *arg) { // 正常视图到overview,退出所有窗口的浮动和全屏状态参与平铺, // overview到正常视图,还原之前退出的浮动和全屏窗口状态 if (selmon->isoverview) { + + // 让游戏窗口无法强制约束鼠标 + wlr_seat_pointer_clear_focus(seat); + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); + wl_list_for_each(c, &clients, link) { if (c && c->mon == selmon && !client_is_unmanaged(c) && !client_is_x11_popup(c) && !c->isunglobal) diff --git a/src/mango.c b/src/mango.c index 8c11c318..05da99a5 100644 --- a/src/mango.c +++ b/src/mango.c @@ -4863,7 +4863,13 @@ void pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, /* Let the client know that the mouse cursor has entered one * of its surfaces, and make keyboard focus follow if desired. * wlroots makes this a no-op if surface is already focused */ - wlr_seat_pointer_notify_enter(seat, surface, sx, sy); + + if (!c || !c->mon || !c->mon->isoverview) { + // don't let window get pointer focus, + // avoid game window force grab pointer in overview mode + wlr_seat_pointer_notify_enter(seat, surface, sx, sy); + } + wlr_seat_pointer_notify_motion(seat, time, sx, sy); } From 04a3074fb4dddbe8bffb4e00b7d0932e65604d4c Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 16 May 2026 16:31:16 +0800 Subject: [PATCH 205/328] opt: make big one as the last open window in fair layout --- src/layout/horizontal.h | 23 ++++++++++++++--------- src/layout/vertical.h | 23 ++++++++++++++--------- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/layout/horizontal.h b/src/layout/horizontal.h index 3ff8bd44..130d38f8 100644 --- a/src/layout/horizontal.h +++ b/src/layout/horizontal.h @@ -704,7 +704,11 @@ void fair(Monitor *m) { } int32_t base_rows = n / cols; // 每列的基础行数 - int32_t remainder = n % cols; // 多出来的窗口,分配给前 remainder 列 + int32_t remainder = n % cols; // 多出来的窗口 + + // 计算前半部分(大窗口)的列数和总窗口数 + int32_t first_group_cols = cols - remainder; + int32_t first_group_count = first_group_cols * base_rows; // 计算标准列宽 int32_t col_width = @@ -718,15 +722,16 @@ void fair(Monitor *m) { int32_t col_idx, row_idx, rows_in_this_col; // 判断当前窗口属于哪一列、哪一行 - if (i < remainder * (base_rows + 1)) { - col_idx = i / (base_rows + 1); - row_idx = i % (base_rows + 1); - rows_in_this_col = base_rows + 1; - } else { - int32_t offset = i - remainder * (base_rows + 1); - col_idx = remainder + offset / base_rows; - row_idx = offset % base_rows; + // 前半部分列拥有较少的行数(窗口更大),后半部分列承担余数(窗口更小) + if (i < first_group_count) { + col_idx = i / base_rows; + row_idx = i % base_rows; rows_in_this_col = base_rows; + } else { + int32_t offset = i - first_group_count; + col_idx = first_group_cols + (offset / (base_rows + 1)); + row_idx = offset % (base_rows + 1); + rows_in_this_col = base_rows + 1; } // 计算 X 坐标和宽度 (最后一列吃掉剩余像素,防止缝隙) diff --git a/src/layout/vertical.h b/src/layout/vertical.h index 484dfd85..74ed98a4 100644 --- a/src/layout/vertical.h +++ b/src/layout/vertical.h @@ -311,7 +311,11 @@ void vertical_fair(Monitor *m) { } int32_t base_cols = n / rows; // 每行的基础列数 - int32_t remainder = n % rows; // 多出来的窗口,分配给前 remainder 行 + int32_t remainder = n % rows; // 多出来的窗口 + + // 计算上半部分(大窗口)的行数和总窗口数 + int32_t first_group_rows = rows - remainder; + int32_t first_group_count = first_group_rows * base_cols; // 计算标准行高 int32_t row_height = @@ -325,15 +329,16 @@ void vertical_fair(Monitor *m) { int32_t row_idx, col_idx, cols_in_this_row; // 判断当前窗口属于哪一行、哪一列 - if (i < remainder * (base_cols + 1)) { - row_idx = i / (base_cols + 1); - col_idx = i % (base_cols + 1); - cols_in_this_row = base_cols + 1; - } else { - int32_t offset = i - remainder * (base_cols + 1); - row_idx = remainder + offset / base_cols; - col_idx = offset % base_cols; + // 上半部分行拥有较少的列数(窗口更宽),下半部分行承担余数(窗口更窄) + if (i < first_group_count) { + row_idx = i / base_cols; + col_idx = i % base_cols; cols_in_this_row = base_cols; + } else { + int32_t offset = i - first_group_count; + row_idx = first_group_rows + (offset / (base_cols + 1)); + col_idx = offset % (base_cols + 1); + cols_in_this_row = base_cols + 1; } // 计算 Y 坐标和高度 (最后一行吃掉剩余像素) From 65c9ac6dd2290efa3e338c656487561a045287f9 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 16 May 2026 17:32:39 +0800 Subject: [PATCH 206/328] feat: grid and fair layout support resize tile window --- src/layout/arrange.h | 301 ++++++++++++++++++++++++++++++++++++++++ src/layout/horizontal.h | 288 +++++++++++++++++++++++++++++++++----- src/layout/vertical.h | 243 +++++++++++++++++++++++--------- src/mango.c | 10 +- 4 files changed, 741 insertions(+), 101 deletions(-) diff --git a/src/layout/arrange.h b/src/layout/arrange.h index 42d15f86..e452708b 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -536,6 +536,302 @@ void resize_tile_dwindle(Client *grabc, bool isdrag, int32_t offsetx, } } +void resize_tile_grid_fair(Client *grabc, bool isdrag, int32_t offsetx, + int32_t offsety, uint32_t time) { + if (!grabc || grabc->isfullscreen || grabc->ismaximizescreen) + return; + Monitor *m = grabc->mon; + if (m->isoverview) + return; + + if (m->visible_tiling_clients <= 1) + return; + + // 获取当前布局 ID + const Layout *current_layout = m->pertag->ltidxs[m->pertag->curtag]; + + if (!start_drag_window && isdrag) { + drag_begin_cursorx = cursor->x; + drag_begin_cursory = cursor->y; + start_drag_window = true; + + Client *c; + wl_list_for_each(c, &clients, link) { + c->old_grid_col_per = + (c->grid_col_per > 0.0f) ? c->grid_col_per : 1.0f; + c->old_grid_row_per = + (c->grid_row_per > 0.0f) ? c->grid_row_per : 1.0f; + } + + grabc->old_grid_col_per = grabc->grid_col_per; + grabc->old_grid_row_per = grabc->grid_row_per; + + grabc->cursor_in_left_half = + cursor->x < grabc->geom.x + grabc->geom.width / 2; + grabc->cursor_in_upper_half = + cursor->y < grabc->geom.y + grabc->geom.height / 2; + grabc->drag_begin_geom = grabc->geom; + } else { + if (isdrag) { + offsetx = cursor->x - drag_begin_cursorx; + offsety = cursor->y - drag_begin_cursory; + } else { + grabc->drag_begin_geom = grabc->geom; + Client *c; + wl_list_for_each(c, &clients, link) { + c->old_grid_col_per = + (c->grid_col_per > 0.0f) ? c->grid_col_per : 1.0f; + c->old_grid_row_per = + (c->grid_row_per > 0.0f) ? c->grid_row_per : 1.0f; + } + grabc->cursor_in_upper_half = false; + grabc->cursor_in_left_half = false; + } + + // 以屏幕分辨率为基准算出缩放比变化的量 + float delta_x = (float)offsetx * grabc->old_grid_col_per / + grabc->drag_begin_geom.width; + float delta_y = (float)offsety * grabc->old_grid_row_per / + grabc->drag_begin_geom.height; + + int adj_c_idx = grabc->grid_col_idx; + int adj_r_idx = grabc->grid_row_idx; + float sign_x = 1.0f, sign_y = 1.0f; + + if (isdrag) { + if (grabc->cursor_in_left_half) { + adj_c_idx -= 1; + sign_x = -1.0f; + } else { + adj_c_idx += 1; + sign_x = 1.0f; + } + + if (grabc->cursor_in_upper_half) { + adj_r_idx -= 1; + sign_y = -1.0f; + } else { + adj_r_idx += 1; + sign_y = 1.0f; + } + } + // 键盘热键逻辑不变 + int max_col = -1, max_row = -1, min_col = INT32_MAX, + min_row = INT32_MAX; + Client *tmp; + wl_list_for_each(tmp, &clients, link) { + if (tmp->mon != m || !VISIBLEON(tmp, m) || !ISTILED(tmp)) + continue; + if (tmp->grid_col_idx > max_col) + max_col = tmp->grid_col_idx; + if (tmp->grid_row_idx > max_row) + max_row = tmp->grid_row_idx; + if (tmp->grid_col_idx < min_col) + min_col = tmp->grid_col_idx; + if (tmp->grid_row_idx < min_row) + min_row = tmp->grid_row_idx; + } + + adj_c_idx = grabc->grid_col_idx + 1; + adj_r_idx = grabc->grid_row_idx + 1; + sign_x = 1.0f; + sign_y = 1.0f; + + if (grabc->grid_col_idx == max_col) { + adj_c_idx = grabc->grid_col_idx - 1; + sign_x = -1.0f; + } + if (grabc->grid_row_idx == max_row) { + adj_r_idx = grabc->grid_row_idx - 1; + sign_y = -1.0f; + } + if (grabc->grid_col_idx == min_col) { + adj_c_idx = grabc->grid_col_idx + 1; + sign_x = 1.0f; + } + if (grabc->grid_row_idx == min_row) { + adj_r_idx = grabc->grid_row_idx + 1; + sign_y = 1.0f; + } + + float dx = delta_x * sign_x; + float dy = delta_y * sign_y; + + float my_old_col = grabc->old_grid_col_per; + float my_old_row = grabc->old_grid_row_per; + float adj_old_col = -1.0f, adj_old_row = -1.0f; + + Client *c; + wl_list_for_each(c, &clients, link) { + if (c->mon != m || !VISIBLEON(c, m) || !ISTILED(c)) + continue; + if (c->grid_col_idx == adj_c_idx && adj_old_col < 0) + adj_old_col = c->old_grid_col_per; + if (c->grid_row_idx == adj_r_idx && adj_old_row < 0) + adj_old_row = c->old_grid_row_per; + } + + // 应用列宽调节 + if (adj_old_col > 0.0f) { + float dx_clamped = dx; + if (my_old_col + dx_clamped < 0.1f) + dx_clamped = 0.1f - my_old_col; + if (adj_old_col - dx_clamped < 0.1f) + dx_clamped = adj_old_col - 0.1f; + + float new_my_col = my_old_col + dx_clamped; + float new_adj_col = adj_old_col - dx_clamped; + + // 处理被强行锁死在 1.0f 的列边界,头部是个错位窗口 + if (current_layout && current_layout->id == VERTICAL_FAIR) { + int32_t n_tiling = m->visible_tiling_clients; + int32_t l_rows; + for (l_rows = 0; l_rows <= n_tiling; l_rows++) { + if (l_rows * l_rows >= n_tiling) + break; + } + int32_t base_cols = n_tiling / l_rows; + // 当调节边界恰好处于非对称的锁死列(如 3 窗口下的 col 0 与 col + // 1 之间) + if ((grabc->grid_col_idx == base_cols - 1 && + adj_c_idx == base_cols) || + (grabc->grid_col_idx == base_cols && + adj_c_idx == base_cols - 1)) { + + float p_col = + (grabc->grid_col_idx == base_cols - 1) + ? (my_old_col + dx) / (my_old_col + adj_old_col) + : (adj_old_col - dx) / (my_old_col + adj_old_col); + if (p_col < 0.01f) + p_col = 0.01f; + if (p_col > 0.99f) + p_col = 0.99f; + + // 反推非线性真实权重值 + float new_r_var_per = p_col / (1.0f - p_col); + if (new_r_var_per < 0.1f) + new_r_var_per = 0.1f; + if (new_r_var_per > 10.0f) + new_r_var_per = 10.0f; + + if (grabc->grid_col_idx == base_cols - 1) { + new_my_col = new_r_var_per; + new_adj_col = 1.0f; + } else { + new_my_col = 1.0f; + new_adj_col = new_r_var_per; + } + } + } + + wl_list_for_each(c, &clients, link) { + if (c->mon != m || !VISIBLEON(c, m) || !ISTILED(c)) + continue; + if (c->grid_col_idx == grabc->grid_col_idx) + c->grid_col_per = new_my_col; + if (c->grid_col_idx == adj_c_idx) + c->grid_col_per = new_adj_col; + } + + wl_list_for_each(c, &clients, link) { + if (c->mon != m || !VISIBLEON(c, m) || !ISTILED(c)) + continue; + if (c->grid_row_idx == 0) { + if (c->grid_col_idx == grabc->grid_col_idx) + c->grid_col_per = new_my_col; + else if (c->grid_col_idx == adj_c_idx) + c->grid_col_per = new_adj_col; + } + } + } + + // 应用行高调节 + if (adj_old_row > 0.0f) { + float dy_clamped = dy; + if (my_old_row + dy_clamped < 0.1f) + dy_clamped = 0.1f - my_old_row; + if (adj_old_row - dy_clamped < 0.1f) + dy_clamped = adj_old_row - 0.1f; + + float new_my_row = my_old_row + dy_clamped; + float new_adj_row = adj_old_row - dy_clamped; + + // 处理被强行锁死在 1.0f 的行边界,头部是个错位窗口 + if (current_layout && current_layout->id == FAIR) { + int32_t n_tiling = m->visible_tiling_clients; + int32_t l_cols; + for (l_cols = 0; l_cols <= n_tiling; l_cols++) { + if (l_cols * l_cols >= n_tiling) + break; + } + int32_t base_rows = n_tiling / l_cols; + // 当调节边界恰好处于非对称的锁死行(如 3 窗口下的 row 0 与 row + // 1 之间) + if ((grabc->grid_row_idx == base_rows - 1 && + adj_r_idx == base_rows) || + (grabc->grid_row_idx == base_rows && + adj_r_idx == base_rows - 1)) { + + float p_row = + (grabc->grid_row_idx == base_rows - 1) + ? (my_old_row + dy) / (my_old_row + adj_old_row) + : (adj_old_row - dy) / (my_old_row + adj_old_row); + if (p_row < 0.01f) + p_row = 0.01f; + if (p_row > 0.99f) + p_row = 0.99f; + + // 反推非线性真实权重值 + float new_r_var_per = p_row / (1.0f - p_row); + if (new_r_var_per < 0.1f) + new_r_var_per = 0.1f; + if (new_r_var_per > 10.0f) + new_r_var_per = 10.0f; + + if (grabc->grid_row_idx == base_rows - 1) { + new_my_row = new_r_var_per; + new_adj_row = 1.0f; + } else { + new_my_row = 1.0f; + new_adj_row = new_r_var_per; + } + } + } + + wl_list_for_each(c, &clients, link) { + if (c->mon != m || !VISIBLEON(c, m) || !ISTILED(c)) + continue; + if (c->grid_row_idx == grabc->grid_row_idx) + c->grid_row_per = new_my_row; + if (c->grid_row_idx == adj_r_idx) + c->grid_row_per = new_adj_row; + } + + wl_list_for_each(c, &clients, link) { + if (c->mon != m || !VISIBLEON(c, m) || !ISTILED(c)) + continue; + if (c->grid_col_idx == 0) { + if (c->grid_row_idx == grabc->grid_row_idx) + c->grid_row_per = new_my_row; + else if (c->grid_row_idx == adj_r_idx) + c->grid_row_per = new_adj_row; + } + } + } + + if (!isdrag) { + arrange(m, false, false); + return; + } + + if (last_apply_drap_time == 0 || + time - last_apply_drap_time > config.drag_tile_refresh_interval) { + arrange(m, false, false); + last_apply_drap_time = time; + } + } +} + void resize_tile_scroller(Client *grabc, bool isdrag, int32_t offsetx, int32_t offsety, uint32_t time, bool isvertical) { if (!grabc || grabc->isfullscreen || grabc->ismaximizescreen) @@ -772,6 +1068,11 @@ void resize_tile_client(Client *grabc, bool isdrag, int32_t offsetx, resize_tile_scroller(grabc, isdrag, offsetx, offsety, time, true); } else if (current_layout->id == DWINDLE) { resize_tile_dwindle(grabc, isdrag, offsetx, offsety, time, true); + } else if (current_layout->id == GRID || + current_layout->id == VERTICAL_GRID || + current_layout->id == FAIR || + current_layout->id == VERTICAL_FAIR) { + resize_tile_grid_fair(grabc, isdrag, offsetx, offsety, time); } } diff --git a/src/layout/horizontal.h b/src/layout/horizontal.h index 130d38f8..ee37b168 100644 --- a/src/layout/horizontal.h +++ b/src/layout/horizontal.h @@ -1,5 +1,6 @@ + // 网格布局窗口大小和位置计算 -void grid(Monitor *m) { +void overview(Monitor *m) { int32_t i, n; int32_t cx, cy, cw, ch; int32_t dx; @@ -676,6 +677,195 @@ monocle(Monitor *m) { wlr_scene_node_raise_to_top(&c->scene->node); } +// 网格布局窗口大小和位置计算 +void grid(Monitor *m) { + int32_t i, n; + int32_t cw, ch; + int32_t cols, rows, overcols; + Client *c = NULL; + n = 0; + int32_t target_gappo = + enablegaps ? m->isoverview ? config.overviewgappo : config.gappoh : 0; + int32_t target_gappi = + enablegaps ? m->isoverview ? config.overviewgappi : config.gappih : 0; + float single_width_ratio = m->isoverview ? 0.7 : 0.9; + float single_height_ratio = m->isoverview ? 0.8 : 0.9; + + n = m->isoverview ? m->visible_clients : m->visible_tiling_clients; + + if (n == 0) + return; + + if (n == 1) { + wl_list_for_each(c, &clients, link) { + if (c->mon != m) + continue; + if (VISIBLEON(c, m) && !c->isunglobal && + ((m->isoverview && !client_is_x11_popup(c)) || ISTILED(c))) { + cw = (m->w.width - 2 * target_gappo) * single_width_ratio; + ch = (m->w.height - 2 * target_gappo) * single_height_ratio; + c->geom.x = m->w.x + (m->w.width - cw) / 2; + c->geom.y = m->w.y + (m->w.height - ch) / 2; + c->geom.width = cw; + c->geom.height = ch; + resize(c, c->geom, 0); + return; + } + } + } + + if (n == 2) { + float col_pers[2] = {1.0f, 1.0f}; + // 先提取这两个窗口现有的列比例 + i = 0; + wl_list_for_each(c, &clients, link) { + if (c->mon != m) + continue; + if (VISIBLEON(c, m) && !c->isunglobal && + ((m->isoverview && !client_is_x11_popup(c)) || ISTILED(c))) { + if (i < 2) + col_pers[i] = + (c->grid_col_per > 0.0f) ? c->grid_col_per : 1.0f; + i++; + } + } + + float sum_col = col_pers[0] + col_pers[1]; + float avail_w = m->w.width - 2 * target_gappo - target_gappi; + ch = + (m->w.height - 2 * target_gappo) * 0.65; // 依然保持 0.65 的美观高度 + + i = 0; + wl_list_for_each(c, &clients, link) { + if (c->mon != m) + continue; + if (VISIBLEON(c, m) && !c->isunglobal && + ((m->isoverview && !client_is_x11_popup(c)) || ISTILED(c))) { + c->grid_col_idx = i; + c->grid_row_idx = 0; + c->grid_col_per = col_pers[i]; + c->grid_row_per = 1.0f; + + // 根据分配的权重动态计算当前窗口的宽度 + cw = avail_w * (col_pers[i] / sum_col); + + if (i == 0) { + c->geom.x = m->w.x + target_gappo; + } else if (i == 1) { + // 第二个窗口的 X 坐标紧跟第一个窗口后面 + float cw0 = avail_w * (col_pers[0] / sum_col); + c->geom.x = m->w.x + target_gappo + cw0 + target_gappi; + } + c->geom.y = m->w.y + (m->w.height - ch) / 2 + target_gappo; + c->geom.width = cw; + c->geom.height = ch; + resize(c, c->geom, 0); + i++; + } + } + return; + } + + // 计算列数和行数 + for (cols = 0; cols <= n / 2; cols++) { + if (cols * cols >= n) + break; + } + rows = (cols && (cols - 1) * cols >= n) ? cols - 1 : cols; + overcols = n % cols; + + float col_pers[cols]; + float row_pers[rows]; + for (i = 0; i < cols; i++) + col_pers[i] = 1.0f; + for (i = 0; i < rows; i++) + row_pers[i] = 1.0f; + + // 提取首个窗口比例 + i = 0; + wl_list_for_each(c, &clients, link) { + if (c->mon != m) + continue; + if (VISIBLEON(c, m) && !c->isunglobal && + ((m->isoverview && !client_is_x11_popup(c)) || ISTILED(c))) { + int32_t c_idx = i % cols; + int32_t r_idx = i / cols; + if (r_idx == 0) + col_pers[c_idx] = + (c->grid_col_per > 0.0f) ? c->grid_col_per : 1.0f; + if (c_idx == 0) + row_pers[r_idx] = + (c->grid_row_per > 0.0f) ? c->grid_row_per : 1.0f; + i++; + } + } + + float sum_col = 0.0f, sum_row = 0.0f; + for (i = 0; i < cols; i++) + sum_col += col_pers[i]; + for (i = 0; i < rows; i++) + sum_row += row_pers[i]; + + float avail_w = m->w.width - 2 * target_gappo - (cols - 1) * target_gappi; + float avail_h = m->w.height - 2 * target_gappo - (rows - 1) * target_gappi; + + // 分配位置与尺寸 + i = 0; + wl_list_for_each(c, &clients, link) { + if (c->mon != m) + continue; + if (VISIBLEON(c, m) && !c->isunglobal && + ((m->isoverview && !client_is_x11_popup(c)) || ISTILED(c))) { + int32_t c_idx = i % cols; + int32_t r_idx = i / cols; + + // 矫正属性及标记索引 + c->grid_col_per = col_pers[c_idx]; + c->grid_row_per = row_pers[r_idx]; + c->grid_col_idx = c_idx; + c->grid_row_idx = r_idx; + + // X 坐标及宽度计算 + float fl_cx = m->w.x + target_gappo; + float fl_cw = 0.0f; + + if (overcols && i >= n - overcols) { + float over_w = 0.0f; + for (int j = 0; j < overcols; j++) + over_w += avail_w * (col_pers[j] / sum_col); + over_w += (overcols - 1) * target_gappi; + float dx = (m->w.width - over_w) / 2.0f - target_gappo; + + fl_cx += dx; + for (int j = 0; j < c_idx; j++) + fl_cx += avail_w * (col_pers[j] / sum_col) + target_gappi; + fl_cw = avail_w * (col_pers[c_idx] / sum_col); + } else { + for (int j = 0; j < c_idx; j++) + fl_cx += avail_w * (col_pers[j] / sum_col) + target_gappi; + fl_cw = (c_idx == cols - 1) + ? (m->w.x + m->w.width - target_gappo - fl_cx) + : avail_w * (col_pers[c_idx] / sum_col); + } + + // Y 坐标及高度计算 + float fl_cy = m->w.y + target_gappo; + for (int j = 0; j < r_idx; j++) + fl_cy += avail_h * (row_pers[j] / sum_row) + target_gappi; + float fl_ch = (r_idx == rows - 1) + ? (m->w.y + m->w.height - target_gappo - fl_cy) + : avail_h * (row_pers[r_idx] / sum_row); + + c->geom.x = (int32_t)fl_cx; + c->geom.y = (int32_t)fl_cy; + c->geom.width = (int32_t)fl_cw; + c->geom.height = (int32_t)fl_ch; + resize(c, c->geom, 0); + i++; + } + } +} + void fair(Monitor *m) { int32_t i, n = 0; Client *c = NULL; @@ -684,35 +874,62 @@ void fair(Monitor *m) { if (n == 0) return; - // 间隙参数处理 int32_t cur_gappiv = enablegaps ? m->gappiv : 0; int32_t cur_gappih = enablegaps ? m->gappih : 0; int32_t cur_gappov = enablegaps ? m->gappov : 0; int32_t cur_gappoh = enablegaps ? m->gappoh : 0; - // 智能间隙 cur_gappiv = config.smartgaps && n == 1 ? 0 : cur_gappiv; cur_gappih = config.smartgaps && n == 1 ? 0 : cur_gappih; cur_gappov = config.smartgaps && n == 1 ? 0 : cur_gappov; cur_gappoh = config.smartgaps && n == 1 ? 0 : cur_gappoh; - // 计算最佳列数 cols = ceil(sqrt(n)) int32_t cols; for (cols = 0; cols <= n; cols++) { if (cols * cols >= n) break; } - int32_t base_rows = n / cols; // 每列的基础行数 - int32_t remainder = n % cols; // 多出来的窗口 - - // 计算前半部分(大窗口)的列数和总窗口数 + int32_t base_rows = n / cols; + int32_t remainder = n % cols; int32_t first_group_cols = cols - remainder; int32_t first_group_count = first_group_cols * base_rows; + int32_t max_rows = base_rows + (remainder > 0 ? 1 : 0); - // 计算标准列宽 - int32_t col_width = - (m->w.width - 2 * cur_gappoh - (cols - 1) * cur_gappih) / cols; + float col_pers[cols]; + float row_pers[max_rows]; + for (i = 0; i < cols; i++) + col_pers[i] = 1.0f; + for (i = 0; i < max_rows; i++) + row_pers[i] = 1.0f; + + i = 0; + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || !ISTILED(c)) + continue; + int32_t col_idx, row_idx; + if (i < first_group_count) { + col_idx = i / base_rows; + row_idx = i % base_rows; + } else { + int32_t offset = i - first_group_count; + col_idx = first_group_cols + (offset / (base_rows + 1)); + row_idx = offset % (base_rows + 1); + } + + if (row_idx == 0) + col_pers[col_idx] = + (c->grid_col_per > 0.0f) ? c->grid_col_per : 1.0f; + if (col_idx == 0) + row_pers[row_idx] = + (c->grid_row_per > 0.0f) ? c->grid_row_per : 1.0f; + i++; + } + + float sum_col = 0.0f; + for (i = 0; i < cols; i++) + sum_col += col_pers[i]; + float avail_w = m->w.width - 2 * cur_gappoh - (cols - 1) * cur_gappih; i = 0; wl_list_for_each(c, &clients, link) { @@ -720,9 +937,6 @@ void fair(Monitor *m) { continue; int32_t col_idx, row_idx, rows_in_this_col; - - // 判断当前窗口属于哪一列、哪一行 - // 前半部分列拥有较少的行数(窗口更大),后半部分列承担余数(窗口更小) if (i < first_group_count) { col_idx = i / base_rows; row_idx = i % base_rows; @@ -734,25 +948,37 @@ void fair(Monitor *m) { rows_in_this_col = base_rows + 1; } - // 计算 X 坐标和宽度 (最后一列吃掉剩余像素,防止缝隙) - int32_t cx = m->w.x + cur_gappoh + col_idx * (col_width + cur_gappih); - int32_t cw = (col_idx == cols - 1) - ? (m->w.width - 2 * cur_gappoh - - col_idx * (col_width + cur_gappih)) - : col_width; + c->grid_col_per = col_pers[col_idx]; + c->grid_row_per = row_pers[row_idx]; + c->grid_col_idx = col_idx; + c->grid_row_idx = row_idx; - // 计算 Y 坐标和高度 (最后一行吃掉剩余像素) - int32_t base_ch = (m->w.height - 2 * cur_gappov - - (rows_in_this_col - 1) * cur_gappiv) / - rows_in_this_col; - int32_t cy = m->w.y + cur_gappov + row_idx * (base_ch + cur_gappiv); - int32_t ch = (row_idx == rows_in_this_col - 1) - ? (m->w.height - 2 * cur_gappov - - row_idx * (base_ch + cur_gappiv)) - : base_ch; + float fl_cx = m->w.x + cur_gappoh; + for (int j = 0; j < col_idx; j++) + fl_cx += avail_w * (col_pers[j] / sum_col) + cur_gappih; + float fl_cw = (col_idx == cols - 1) + ? (m->w.x + m->w.width - cur_gappoh - fl_cx) + : avail_w * (col_pers[col_idx] / sum_col); - resize(c, (struct wlr_box){.x = cx, .y = cy, .width = cw, .height = ch}, + float sum_row_this_col = 0.0f; + for (int j = 0; j < rows_in_this_col; j++) + sum_row_this_col += row_pers[j]; + + float avail_h = + m->w.height - 2 * cur_gappov - (rows_in_this_col - 1) * cur_gappiv; + float fl_cy = m->w.y + cur_gappov; + for (int j = 0; j < row_idx; j++) + fl_cy += avail_h * (row_pers[j] / sum_row_this_col) + cur_gappiv; + float fl_ch = (row_idx == rows_in_this_col - 1) + ? (m->w.y + m->w.height - cur_gappov - fl_cy) + : avail_h * (row_pers[row_idx] / sum_row_this_col); + + resize(c, + (struct wlr_box){.x = (int32_t)fl_cx, + .y = (int32_t)fl_cy, + .width = (int32_t)fl_cw, + .height = (int32_t)fl_ch}, 0); i++; } -} \ No newline at end of file +} diff --git a/src/layout/vertical.h b/src/layout/vertical.h index 74ed98a4..f9c19690 100644 --- a/src/layout/vertical.h +++ b/src/layout/vertical.h @@ -178,8 +178,7 @@ void vertical_deck(Monitor *m) { void vertical_grid(Monitor *m) { int32_t i, n; - int32_t cx, cy, cw, ch; - int32_t dy; + int32_t cw, ch; int32_t rows, cols, overrows; Client *c = NULL; int32_t target_gappo = @@ -190,17 +189,13 @@ void vertical_grid(Monitor *m) { float single_height_ratio = m->isoverview ? 0.8 : 0.9; n = m->isoverview ? m->visible_clients : m->visible_tiling_clients; - - if (n == 0) { + if (n == 0) return; - } if (n == 1) { wl_list_for_each(c, &clients, link) { - if (c->mon != m) continue; - if (VISIBLEON(c, m) && !c->isunglobal && ((m->isoverview && !client_is_x11_popup(c)) || ISTILED(c))) { ch = (m->w.height - 2 * target_gappo) * single_height_ratio; @@ -216,67 +211,143 @@ void vertical_grid(Monitor *m) { } if (n == 2) { - ch = (m->w.height - 2 * target_gappo - target_gappi) / 2; - cw = (m->w.width - 2 * target_gappo) * 0.65; + float row_pers[2] = {1.0f, 1.0f}; + // 先提取这两个窗口现有的行比例 i = 0; wl_list_for_each(c, &clients, link) { - if (c->mon != m) continue; - if (VISIBLEON(c, m) && !c->isunglobal && ((m->isoverview && !client_is_x11_popup(c)) || ISTILED(c))) { + if (i < 2) + row_pers[i] = + (c->grid_row_per > 0.0f) ? c->grid_row_per : 1.0f; + i++; + } + } + + float sum_row = row_pers[0] + row_pers[1]; + float avail_h = m->w.height - 2 * target_gappo - target_gappi; + cw = (m->w.width - 2 * target_gappo) * 0.65; // 依然保持 0.65 的美观宽度 + + i = 0; + wl_list_for_each(c, &clients, link) { + if (c->mon != m) + continue; + if (VISIBLEON(c, m) && !c->isunglobal && + ((m->isoverview && !client_is_x11_popup(c)) || ISTILED(c))) { + c->grid_col_idx = 0; + c->grid_row_idx = i; + c->grid_col_per = 1.0f; + c->grid_row_per = row_pers[i]; + + // 根据分配的权重动态计算当前窗口的高度 + ch = avail_h * (row_pers[i] / sum_row); + + c->geom.x = m->w.x + (m->w.width - cw) / 2 + target_gappo; if (i == 0) { - c->geom.x = m->w.x + (m->w.width - cw) / 2 + target_gappo; c->geom.y = m->w.y + target_gappo; - c->geom.width = cw; - c->geom.height = ch; - resize(c, c->geom, 0); } else if (i == 1) { - c->geom.x = m->w.x + (m->w.width - cw) / 2 + target_gappo; - c->geom.y = m->w.y + ch + target_gappo + target_gappi; - c->geom.width = cw; - c->geom.height = ch; - resize(c, c->geom, 0); + // 第二个窗口的 Y 坐标紧跟第一个窗口下面 + float ch0 = avail_h * (row_pers[0] / sum_row); + c->geom.y = m->w.y + target_gappo + ch0 + target_gappi; } + c->geom.width = cw; + c->geom.height = ch; + resize(c, c->geom, 0); i++; } } return; } - for (rows = 0; rows <= n / 2; rows++) { - if (rows * rows >= n) { + if (rows * rows >= n) break; - } } cols = (rows && (rows - 1) * rows >= n) ? rows - 1 : rows; - - cw = (m->w.width - 2 * target_gappo - (cols - 1) * target_gappi) / cols; - ch = (m->w.height - 2 * target_gappo - (rows - 1) * target_gappi) / rows; - overrows = n % rows; - if (overrows) { - dy = (m->w.height - overrows * ch - (overrows - 1) * target_gappi) / 2 - - target_gappo; - } + + float col_pers[cols]; + float row_pers[rows]; + for (i = 0; i < cols; i++) + col_pers[i] = 1.0f; + for (i = 0; i < rows; i++) + row_pers[i] = 1.0f; i = 0; wl_list_for_each(c, &clients, link) { if (c->mon != m) continue; - if (VISIBLEON(c, m) && !c->isunglobal && ((m->isoverview && !client_is_x11_popup(c)) || ISTILED(c))) { - cx = m->w.x + (i / rows) * (cw + target_gappi); - cy = m->w.y + (i % rows) * (ch + target_gappi); + int32_t c_idx = i / rows; + int32_t r_idx = i % rows; + if (r_idx == 0) + col_pers[c_idx] = + (c->grid_col_per > 0.0f) ? c->grid_col_per : 1.0f; + if (c_idx == 0) + row_pers[r_idx] = + (c->grid_row_per > 0.0f) ? c->grid_row_per : 1.0f; + i++; + } + } + + float sum_col = 0.0f, sum_row = 0.0f; + for (i = 0; i < cols; i++) + sum_col += col_pers[i]; + for (i = 0; i < rows; i++) + sum_row += row_pers[i]; + + float avail_w = m->w.width - 2 * target_gappo - (cols - 1) * target_gappi; + float avail_h = m->w.height - 2 * target_gappo - (rows - 1) * target_gappi; + + i = 0; + wl_list_for_each(c, &clients, link) { + if (c->mon != m) + continue; + if (VISIBLEON(c, m) && !c->isunglobal && + ((m->isoverview && !client_is_x11_popup(c)) || ISTILED(c))) { + int32_t c_idx = i / rows; + int32_t r_idx = i % rows; + + c->grid_col_per = col_pers[c_idx]; + c->grid_row_per = row_pers[r_idx]; + c->grid_col_idx = c_idx; + c->grid_row_idx = r_idx; + + float fl_cy = m->w.y + target_gappo; + float fl_ch = 0.0f; + if (overrows && i >= n - overrows) { - cy += dy; + float over_h = 0.0f; + for (int j = 0; j < overrows; j++) + over_h += avail_h * (row_pers[j] / sum_row); + over_h += (overrows - 1) * target_gappi; + float dy = (m->w.height - over_h) / 2.0f - target_gappo; + + fl_cy += dy; + for (int j = 0; j < r_idx; j++) + fl_cy += avail_h * (row_pers[j] / sum_row) + target_gappi; + fl_ch = avail_h * (row_pers[r_idx] / sum_row); + } else { + for (int j = 0; j < r_idx; j++) + fl_cy += avail_h * (row_pers[j] / sum_row) + target_gappi; + fl_ch = (r_idx == rows - 1) + ? (m->w.y + m->w.height - target_gappo - fl_cy) + : avail_h * (row_pers[r_idx] / sum_row); } - c->geom.x = cx + target_gappo; - c->geom.y = cy + target_gappo; - c->geom.width = cw; - c->geom.height = ch; + + float fl_cx = m->w.x + target_gappo; + for (int j = 0; j < c_idx; j++) + fl_cx += avail_w * (col_pers[j] / sum_col) + target_gappi; + float fl_cw = (c_idx == cols - 1) + ? (m->w.x + m->w.width - target_gappo - fl_cx) + : avail_w * (col_pers[c_idx] / sum_col); + + c->geom.x = (int32_t)fl_cx; + c->geom.y = (int32_t)fl_cy; + c->geom.width = (int32_t)fl_cw; + c->geom.height = (int32_t)fl_ch; resize(c, c->geom, 0); i++; } @@ -291,35 +362,62 @@ void vertical_fair(Monitor *m) { if (n == 0) return; - // 间隙参数处理 int32_t cur_gappiv = enablegaps ? m->gappiv : 0; int32_t cur_gappih = enablegaps ? m->gappih : 0; int32_t cur_gappov = enablegaps ? m->gappov : 0; int32_t cur_gappoh = enablegaps ? m->gappoh : 0; - // 智能间隙 cur_gappiv = config.smartgaps && n == 1 ? 0 : cur_gappiv; cur_gappih = config.smartgaps && n == 1 ? 0 : cur_gappih; cur_gappov = config.smartgaps && n == 1 ? 0 : cur_gappov; cur_gappoh = config.smartgaps && n == 1 ? 0 : cur_gappoh; - // 计算最佳行数 rows = ceil(sqrt(n)) int32_t rows; for (rows = 0; rows <= n; rows++) { if (rows * rows >= n) break; } - int32_t base_cols = n / rows; // 每行的基础列数 - int32_t remainder = n % rows; // 多出来的窗口 - - // 计算上半部分(大窗口)的行数和总窗口数 + int32_t base_cols = n / rows; + int32_t remainder = n % rows; int32_t first_group_rows = rows - remainder; int32_t first_group_count = first_group_rows * base_cols; + int32_t max_cols = base_cols + (remainder > 0 ? 1 : 0); - // 计算标准行高 - int32_t row_height = - (m->w.height - 2 * cur_gappov - (rows - 1) * cur_gappiv) / rows; + float row_pers[rows]; + float col_pers[max_cols]; + for (i = 0; i < rows; i++) + row_pers[i] = 1.0f; + for (i = 0; i < max_cols; i++) + col_pers[i] = 1.0f; + + i = 0; + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || !ISTILED(c)) + continue; + int32_t row_idx, col_idx; + if (i < first_group_count) { + row_idx = i / base_cols; + col_idx = i % base_cols; + } else { + int32_t offset = i - first_group_count; + row_idx = first_group_rows + (offset / (base_cols + 1)); + col_idx = offset % (base_cols + 1); + } + + if (col_idx == 0) + row_pers[row_idx] = + (c->grid_row_per > 0.0f) ? c->grid_row_per : 1.0f; + if (row_idx == 0) + col_pers[col_idx] = + (c->grid_col_per > 0.0f) ? c->grid_col_per : 1.0f; + i++; + } + + float sum_row = 0.0f; + for (i = 0; i < rows; i++) + sum_row += row_pers[i]; + float avail_h = m->w.height - 2 * cur_gappov - (rows - 1) * cur_gappiv; i = 0; wl_list_for_each(c, &clients, link) { @@ -327,9 +425,6 @@ void vertical_fair(Monitor *m) { continue; int32_t row_idx, col_idx, cols_in_this_row; - - // 判断当前窗口属于哪一行、哪一列 - // 上半部分行拥有较少的列数(窗口更宽),下半部分行承担余数(窗口更窄) if (i < first_group_count) { row_idx = i / base_cols; col_idx = i % base_cols; @@ -341,24 +436,36 @@ void vertical_fair(Monitor *m) { cols_in_this_row = base_cols + 1; } - // 计算 Y 坐标和高度 (最后一行吃掉剩余像素) - int32_t cy = m->w.y + cur_gappov + row_idx * (row_height + cur_gappiv); - int32_t ch = (row_idx == rows - 1) - ? (m->w.height - 2 * cur_gappov - - row_idx * (row_height + cur_gappiv)) - : row_height; + c->grid_row_per = row_pers[row_idx]; + c->grid_col_per = col_pers[col_idx]; + c->grid_row_idx = row_idx; + c->grid_col_idx = col_idx; - // 计算 X 坐标和宽度 (最后一列吃掉剩余像素,防止缝隙) - int32_t base_cw = (m->w.width - 2 * cur_gappoh - - (cols_in_this_row - 1) * cur_gappih) / - cols_in_this_row; - int32_t cx = m->w.x + cur_gappoh + col_idx * (base_cw + cur_gappih); - int32_t cw = (col_idx == cols_in_this_row - 1) - ? (m->w.width - 2 * cur_gappoh - - col_idx * (base_cw + cur_gappih)) - : base_cw; + float fl_cy = m->w.y + cur_gappov; + for (int j = 0; j < row_idx; j++) + fl_cy += avail_h * (row_pers[j] / sum_row) + cur_gappiv; + float fl_ch = (row_idx == rows - 1) + ? (m->w.y + m->w.height - cur_gappov - fl_cy) + : avail_h * (row_pers[row_idx] / sum_row); - resize(c, (struct wlr_box){.x = cx, .y = cy, .width = cw, .height = ch}, + float sum_col_this_row = 0.0f; + for (int j = 0; j < cols_in_this_row; j++) + sum_col_this_row += col_pers[j]; + + float avail_w = + m->w.width - 2 * cur_gappoh - (cols_in_this_row - 1) * cur_gappih; + float fl_cx = m->w.x + cur_gappoh; + for (int j = 0; j < col_idx; j++) + fl_cx += avail_w * (col_pers[j] / sum_col_this_row) + cur_gappih; + float fl_cw = (col_idx == cols_in_this_row - 1) + ? (m->w.x + m->w.width - cur_gappoh - fl_cx) + : avail_w * (col_pers[col_idx] / sum_col_this_row); + + resize(c, + (struct wlr_box){.x = (int32_t)fl_cx, + .y = (int32_t)fl_cy, + .width = (int32_t)fl_cw, + .height = (int32_t)fl_ch}, 0); i++; } diff --git a/src/mango.c b/src/mango.c index 05da99a5..bc6b4c66 100644 --- a/src/mango.c +++ b/src/mango.c @@ -427,6 +427,12 @@ struct Client { bool enable_drop_area_draw; int32_t drop_direction; struct wlr_box drag_tile_float_backup_geom; + float grid_col_per; + float grid_row_per; + float old_grid_col_per; + float old_grid_row_per; + int32_t grid_col_idx; + int32_t grid_row_idx; }; typedef struct { @@ -4270,6 +4276,8 @@ static void iter_xdg_scene_buffers(struct wlr_scene_buffer *buffer, int32_t sx, } void init_client_properties(Client *c) { + c->grid_col_per = 1.0f; + c->grid_row_per = 1.0f; c->drop_direction = UNDIR; c->enable_drop_area_draw = false; c->isfocusing = false; @@ -6207,8 +6215,6 @@ void tag_client(const Arg *arg, Client *target_client) { printstatus(); } -void overview(Monitor *m) { grid(m); } - // 目标窗口有其他窗口和它同个tag就返回0 uint32_t want_restore_fullscreen(Client *target_client) { Client *c = NULL; From c822f5d5b9eaf36f356e83f590543f269756c595 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 17 May 2026 09:53:58 +0800 Subject: [PATCH 207/328] feat: add dwindle_split_horizontal and dwindle_split_vertical --- src/config/parse_config.h | 4 ++++ src/dispatch/bind_declare.h | 4 +++- src/dispatch/bind_define.h | 46 ++++++++++++++++++++++++++++++------- 3 files changed, 45 insertions(+), 9 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 9d925457..9b82d81b 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -1226,6 +1226,10 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value, func = toggle_all_floating; } else if (strcmp(func_name, "dwindle_toggle_split_direction") == 0) { func = dwindle_toggle_split_direction; + } else if (strcmp(func_name, "dwindle_split_horizontal") == 0) { + func = dwindle_split_horizontal; + } else if (strcmp(func_name, "dwindle_split_vertical") == 0) { + func = dwindle_split_vertical; } else { return NULL; } diff --git a/src/dispatch/bind_declare.h b/src/dispatch/bind_declare.h index 41945dda..b79ad689 100644 --- a/src/dispatch/bind_declare.h +++ b/src/dispatch/bind_declare.h @@ -71,4 +71,6 @@ 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); -int32_t dwindle_toggle_split_direction(const Arg *arg); \ No newline at end of file +int32_t dwindle_toggle_split_direction(const Arg *arg); +int32_t dwindle_split_horizontal(const Arg *arg); +int32_t dwindle_split_vertical(const Arg *arg); \ No newline at end of file diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index ff5ccdf5..2161f39b 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1923,13 +1923,7 @@ int32_t toggle_all_floating(const Arg *arg) { return 0; } -int32_t dwindle_toggle_split_direction(const Arg *arg) { - if (!selmon || !selmon->sel) - return 0; - - Client *c = selmon->sel; - if (!c || !c->mon || c->isfloating) - return 0; +int32_t dwindle_set_split_direction(Client *c, bool istoggle, bool horizontal) { const Layout *layout = c->mon->pertag->ltidxs[c->mon->pertag->curtag]; @@ -1942,8 +1936,44 @@ int32_t dwindle_toggle_split_direction(const Arg *arg) { if (!leaf) return 0; - leaf->custom_leaf_split_h = !leaf->custom_leaf_split_h; + if (istoggle) { + leaf->custom_leaf_split_h = !leaf->custom_leaf_split_h; + } else if (horizontal) { + leaf->custom_leaf_split_h = true; + } else { + leaf->custom_leaf_split_h = false; + } bool hit_no_border = check_hit_no_border(c); apply_split_border(c, hit_no_border); return 0; +} + +int32_t dwindle_toggle_split_direction(const Arg *arg) { + if (!selmon || !selmon->sel) + return 0; + + Client *c = selmon->sel; + if (!c || !c->mon || c->isfloating) + return 0; + return dwindle_set_split_direction(selmon->sel, true, false); +} + +int32_t dwindle_split_horizontal(const Arg *arg) { + if (!selmon || !selmon->sel) + return 0; + + Client *c = selmon->sel; + if (!c || !c->mon || c->isfloating) + return 0; + return dwindle_set_split_direction(selmon->sel, false, true); +} + +int32_t dwindle_split_vertical(const Arg *arg) { + if (!selmon || !selmon->sel) + return 0; + + Client *c = selmon->sel; + if (!c || !c->mon || c->isfloating) + return 0; + return dwindle_set_split_direction(selmon->sel, false, false); } \ No newline at end of file From 26f5e232364ef071c5e32621b097f8d303471803 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 17 May 2026 09:56:05 +0800 Subject: [PATCH 208/328] opt: hide split border when client not tiling --- src/animation/client.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/animation/client.h b/src/animation/client.h index aed97c1f..5a83a11e 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -358,7 +358,7 @@ void apply_split_border(Client *c, bool hit_no_border) { const Layout *layout = c->mon->pertag->ltidxs[c->mon->pertag->curtag]; - if (hit_no_border || layout->id != DWINDLE || + if (hit_no_border || !ISTILED(c) || layout->id != DWINDLE || !config.dwindle_manual_split) { if (c->splitindicator[0]->node.enabled) { wlr_scene_node_set_enabled(&c->splitindicator[0]->node, false); From d7ee60e79a288d5e6f43690ccb0ba662367136b4 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 17 May 2026 22:34:57 +0800 Subject: [PATCH 209/328] opt: optimize exchange two client --- src/mango.c | 237 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 156 insertions(+), 81 deletions(-) diff --git a/src/mango.c b/src/mango.c index bc6b4c66..edd79958 100644 --- a/src/mango.c +++ b/src/mango.c @@ -5066,6 +5066,10 @@ void exchange_two_client(Client *c1, Client *c2) { double master_inner_per = 0.0f; double master_mfact_per = 0.0f; double stack_inner_per = 0.0f; + double grid_col_per = 0.0f; + double grid_row_per = 0.0f; + int32_t grid_col_idx = 0; + int32_t grid_row_idx = 0; struct ScrollerStackNode *n1 = NULL; struct ScrollerStackNode *n2 = NULL; struct TagScrollerState *st1 = NULL; @@ -5076,6 +5080,20 @@ void exchange_two_client(Client *c1, Client *c2) { return; } + /* 保存并交换 grid / master / stack 等比例属性 */ + grid_col_per = c1->grid_col_per; + grid_row_per = c1->grid_row_per; + grid_col_idx = c1->grid_col_idx; + grid_row_idx = c1->grid_row_idx; + c1->grid_col_per = c2->grid_col_per; + c1->grid_row_per = c2->grid_row_per; + c1->grid_col_idx = c2->grid_col_idx; + c1->grid_row_idx = c2->grid_row_idx; + c2->grid_col_per = grid_col_per; + c2->grid_row_per = grid_row_per; + c2->grid_col_idx = grid_col_idx; + c2->grid_row_idx = grid_row_idx; + master_inner_per = c1->master_inner_per; master_mfact_per = c1->master_mfact_per; stack_inner_per = c1->stack_inner_per; @@ -5097,23 +5115,21 @@ void exchange_two_client(Client *c1, Client *c2) { st1 = ensure_scroller_state(m1, tag1); n1 = find_scroller_node(st1, c1); } - if (c2_scroller) { st2 = ensure_scroller_state(m2, tag2); n2 = find_scroller_node(st2, c2); } - if (!n1 || !n2) - goto exchange_common; - + /* --------------------------------------------------------------- + * 情况1:两个客户端都在 scroller 布局中 + * --------------------------------------------------------------- */ if (n1 && n2) { - - /* 跨显示器且任一方有堆叠关系时不允许交换 */ + /* 跨显示器且任一方有堆叠关系(非单客户端)时不允许交换 */ if (m1 != m2 && (n1->prev_in_stack || n2->prev_in_stack || n1->next_in_stack || n2->next_in_stack)) return; - /* 获取各自的堆叠头节点 */ + /* 找到各自的堆叠头 */ struct ScrollerStackNode *head1 = n1; while (head1->prev_in_stack) head1 = head1->prev_in_stack; @@ -5121,14 +5137,15 @@ void exchange_two_client(Client *c1, Client *c2) { while (head2->prev_in_stack) head2 = head2->prev_in_stack; - /* 同一堆叠内交换 */ + /* --- 1a. 同一个堆叠内交换 --- */ if (head1 == head2) { - float tmp_scroller = n1->scroller_proportion; - float tmp_stack = n1->stack_proportion; + /* 交换 scroller/stack 比例 */ + float tmp_sc = n1->scroller_proportion; + float tmp_st = n1->stack_proportion; n1->scroller_proportion = n2->scroller_proportion; n1->stack_proportion = n2->stack_proportion; - n2->scroller_proportion = tmp_scroller; - n2->stack_proportion = tmp_stack; + n2->scroller_proportion = tmp_sc; + n2->stack_proportion = tmp_st; /* 交换堆叠链表指针 */ struct ScrollerStackNode *p1 = n1->prev_in_stack; @@ -5170,96 +5187,154 @@ void exchange_two_client(Client *c1, Client *c2) { } sync_scroller_state_to_clients(m1, tag1); - arrange(m1, false, false); - } else { - /* 不同堆叠:交换两个堆叠整体位置 */ - if (n1 != head1 || n2 != head2) { - /* 当前不是头部,递归交换头部 */ - exchange_two_client(head1->client, head2->client); - return; + /* 继续执行 exchange_common 以交换全局链表中的 c1 和 c2 */ + } + /* --- 1b. 不同堆叠之间的整体交换 --- */ + else { + Client *head1_c = head1->client; + Client *head2_c = head2->client; + Client *tail1_c = scroll_get_stack_tail_client(head1_c); + Client *tail2_c = scroll_get_stack_tail_client(head2_c); + + struct wl_list *p1 = head1_c->link.prev; + struct wl_list *n1_next = tail1_c->link.next; + struct wl_list *p2 = head2_c->link.prev; + struct wl_list *n2_next = tail2_c->link.next; + + if (n1_next == &head2_c->link) { + /* [堆1] -> [堆2] 相邻 */ + p2->next = n2_next; + n2_next->prev = p2; + p1->next = &head2_c->link; + head2_c->link.prev = p1; + tail2_c->link.next = &head1_c->link; + head1_c->link.prev = &tail2_c->link; + } else if (n2_next == &head1_c->link) { + /* [堆2] -> [堆1] 相邻 */ + p1->next = n1_next; + n1_next->prev = p1; + p2->next = &head1_c->link; + head1_c->link.prev = p2; + tail1_c->link.next = &head2_c->link; + head2_c->link.prev = &tail1_c->link; + } else { + /* 两个堆叠不相邻 */ + p1->next = &head2_c->link; + head2_c->link.prev = p1; + tail2_c->link.next = n1_next; + n1_next->prev = &tail2_c->link; + + p2->next = &head1_c->link; + head1_c->link.prev = p2; + tail1_c->link.next = n2_next; + n2_next->prev = &tail1_c->link; } + + /* 跨显示器时交换 mon / tags */ + if (m1 != m2) { + tmp_mon = c2->mon; + tmp_tags = c2->tags; + c2->mon = c1->mon; + c1->mon = tmp_mon; + c2->tags = c1->tags; + c1->tags = tmp_tags; + } + /* 整体交换已完成,跳过普通交换部分,直接 arrange */ + goto arrange_and_finish; } } -exchange_common: - + /* --------------------------------------------------------------- + * 情况2:至少一方不在 scroller 中(或双方均为 NULL) + * 执行普通的全局链表节点交换 + * --------------------------------------------------------------- */ /* 跨显示器且任一方有堆叠关系时不允许交换 */ if (m1 != m2 && ((n1 && n1->prev_in_stack) || (n2 && n2->prev_in_stack) || (n1 && n1->next_in_stack) || (n2 && n2->next_in_stack))) return; - struct wl_list *tmp1_prev = c1->link.prev; - struct wl_list *tmp2_prev = c2->link.prev; - struct wl_list *tmp1_next = c1->link.next; - struct wl_list *tmp2_next = c2->link.next; + { + struct wl_list *tmp1_prev = c1->link.prev; + struct wl_list *tmp2_prev = c2->link.prev; + struct wl_list *tmp1_next = c1->link.next; + struct wl_list *tmp2_next = c2->link.next; - if (c1->link.next == &c2->link) { - c1->link.next = c2->link.next; - c1->link.prev = &c2->link; - c2->link.next = &c1->link; - c2->link.prev = tmp1_prev; - tmp1_prev->next = &c2->link; - tmp2_next->prev = &c1->link; - } else if (c2->link.next == &c1->link) { - c2->link.next = c1->link.next; - c2->link.prev = &c1->link; - c1->link.next = &c2->link; - c1->link.prev = tmp2_prev; - tmp2_prev->next = &c1->link; - tmp1_next->prev = &c2->link; - } else { - c2->link.next = tmp1_next; - c2->link.prev = tmp1_prev; - c1->link.next = tmp2_next; - c1->link.prev = tmp2_prev; - tmp1_prev->next = &c2->link; - tmp1_next->prev = &c2->link; - tmp2_prev->next = &c1->link; - tmp2_next->prev = &c1->link; + if (c1->link.next == &c2->link) { + c1->link.next = c2->link.next; + c1->link.prev = &c2->link; + c2->link.next = &c1->link; + c2->link.prev = tmp1_prev; + tmp1_prev->next = &c2->link; + tmp2_next->prev = &c1->link; + } else if (c2->link.next == &c1->link) { + c2->link.next = c1->link.next; + c2->link.prev = &c1->link; + c1->link.next = &c2->link; + c1->link.prev = tmp2_prev; + tmp2_prev->next = &c1->link; + tmp1_next->prev = &c2->link; + } else { + c2->link.next = tmp1_next; + c2->link.prev = tmp1_prev; + c1->link.next = tmp2_next; + c1->link.prev = tmp2_prev; + tmp1_prev->next = &c2->link; + tmp1_next->prev = &c2->link; + tmp2_prev->next = &c1->link; + tmp2_next->prev = &c1->link; + } } - const Layout *layout1 = c1->mon->pertag->ltidxs[c1->mon->pertag->curtag]; + { + const Layout *layout1 = + c1->mon->pertag->ltidxs[c1->mon->pertag->curtag]; + const Layout *layout2 = + c2->mon->pertag->ltidxs[c2->mon->pertag->curtag]; - const Layout *layout2 = c2->mon->pertag->ltidxs[c2->mon->pertag->curtag]; - - if (c1->mon != c2->mon) { - - if (layout1->id == DWINDLE && layout2->id == DWINDLE) { - DwindleNode **c1_root = - &m1->pertag->dwindle_root[m1->pertag->curtag]; - DwindleNode *c1node = dwindle_find_leaf(*c1_root, c1); - - DwindleNode **c2_root = - &m2->pertag->dwindle_root[m2->pertag->curtag]; - DwindleNode *c2node = dwindle_find_leaf(*c2_root, c2); - - if (c1node) - c1node->client = c2; - - if (c2node) - c2node->client = c1; + if (c1->mon != c2->mon) { + if (layout1->id == DWINDLE && layout2->id == DWINDLE) { + DwindleNode **c1_root = + &m1->pertag->dwindle_root[m1->pertag->curtag]; + DwindleNode *c1node = dwindle_find_leaf(*c1_root, c1); + DwindleNode **c2_root = + &m2->pertag->dwindle_root[m2->pertag->curtag]; + DwindleNode *c2node = dwindle_find_leaf(*c2_root, c2); + if (c1node) + c1node->client = c2; + if (c2node) + c2node->client = c1; + } + tmp_mon = c2->mon; + tmp_tags = c2->tags; + c2->mon = c1->mon; + c1->mon = tmp_mon; + c2->tags = c1->tags; + c1->tags = tmp_tags; + arrange(c1->mon, false, false); + arrange(c2->mon, false, false); + } else { + if (layout1->id == DWINDLE && layout2->id == DWINDLE) { + dwindle_swap_clients( + &c1->mon->pertag->dwindle_root[c1->mon->pertag->curtag], c1, + c2); + } + arrange(c1->mon, false, false); } + } - tmp_mon = c2->mon; - tmp_tags = c2->tags; - c2->mon = c1->mon; - c1->mon = tmp_mon; - c2->tags = c1->tags; - c1->tags = tmp_tags; + /* 调整焦点顺序 */ + wl_list_remove(&c2->flink); + wl_list_insert(&c1->flink, &c2->flink); + return; +arrange_and_finish: + /* 整体交换后的统一 arrange 和焦点调整 */ + if (m1 != m2) { arrange(c1->mon, false, false); arrange(c2->mon, false, false); } else { - if (layout1->id == DWINDLE && layout2->id == DWINDLE) { - dwindle_swap_clients( - &c1->mon->pertag->dwindle_root[c1->mon->pertag->curtag], c1, - c2); - } arrange(c1->mon, false, false); } - - // In order to facilitate repeated exchanges for get_focused_stack_client - // set c2 focus order behind c1 wl_list_remove(&c2->flink); wl_list_insert(&c1->flink, &c2->flink); } From d9b40795acc39c611b6110243e8b71c4e4e9b9d9 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 18 May 2026 11:41:30 +0800 Subject: [PATCH 210/328] opt: optimzie fair layout caculate --- src/layout/horizontal.h | 171 ++++++++++++++++++++++++++-------------- src/layout/vertical.h | 161 ++++++++++++++++++++++++------------- 2 files changed, 219 insertions(+), 113 deletions(-) diff --git a/src/layout/horizontal.h b/src/layout/horizontal.h index ee37b168..00bea0e0 100644 --- a/src/layout/horizontal.h +++ b/src/layout/horizontal.h @@ -874,16 +874,17 @@ void fair(Monitor *m) { if (n == 0) return; + // 获取间距配置 int32_t cur_gappiv = enablegaps ? m->gappiv : 0; int32_t cur_gappih = enablegaps ? m->gappih : 0; int32_t cur_gappov = enablegaps ? m->gappov : 0; int32_t cur_gappoh = enablegaps ? m->gappoh : 0; - cur_gappiv = config.smartgaps && n == 1 ? 0 : cur_gappiv; - cur_gappih = config.smartgaps && n == 1 ? 0 : cur_gappih; - cur_gappov = config.smartgaps && n == 1 ? 0 : cur_gappov; - cur_gappoh = config.smartgaps && n == 1 ? 0 : cur_gappoh; + if (config.smartgaps && n == 1) { + cur_gappiv = cur_gappih = cur_gappov = cur_gappoh = 0; + } + // 计算网格行列数 int32_t cols; for (cols = 0; cols <= n; cols++) { if (cols * cols >= n) @@ -896,56 +897,130 @@ void fair(Monitor *m) { int32_t first_group_count = first_group_cols * base_rows; int32_t max_rows = base_rows + (remainder > 0 ? 1 : 0); + // 将有效客户端存入数组 + Client *arr[n]; + int32_t arr_idx = 0; + wl_list_for_each(c, &clients, link) { + if (VISIBLEON(c, m) && ISTILED(c)) { + arr[arr_idx++] = c; + if (arr_idx >= n) + break; // 安全边界 + } + } + + // 初始化比例数组 float col_pers[cols]; float row_pers[max_rows]; for (i = 0; i < cols; i++) - col_pers[i] = 1.0f; + col_pers[i] = 0.0f; for (i = 0; i < max_rows; i++) - row_pers[i] = 1.0f; + row_pers[i] = 0.0f; - i = 0; - wl_list_for_each(c, &clients, link) { - if (!VISIBLEON(c, m) || !ISTILED(c)) - continue; - int32_t col_idx, row_idx; - if (i < first_group_count) { - col_idx = i / base_rows; - row_idx = i % base_rows; - } else { - int32_t offset = i - first_group_count; - col_idx = first_group_cols + (offset / (base_rows + 1)); - row_idx = offset % (base_rows + 1); - } + // 直接基于数组进行两遍比例锁定 + for (i = 0; i < n; i++) { + c = arr[i]; + int32_t col_idx = + (i < first_group_count) + ? (i / base_rows) + : (first_group_cols + (i - first_group_count) / max_rows); + int32_t row_idx = (i < first_group_count) + ? (i % base_rows) + : ((i - first_group_count) % max_rows); - if (row_idx == 0) - col_pers[col_idx] = - (c->grid_col_per > 0.0f) ? c->grid_col_per : 1.0f; - if (col_idx == 0) - row_pers[row_idx] = - (c->grid_row_per > 0.0f) ? c->grid_row_per : 1.0f; - i++; + if (c->grid_col_idx == col_idx && c->grid_col_per > 0.0f) + col_pers[col_idx] = c->grid_col_per; + if (c->grid_row_idx == row_idx && c->grid_row_per > 0.0f) + row_pers[row_idx] = c->grid_row_per; + } + for (i = 0; i < n; i++) { + c = arr[i]; + int32_t col_idx = + (i < first_group_count) + ? (i / base_rows) + : (first_group_cols + (i - first_group_count) / max_rows); + int32_t row_idx = (i < first_group_count) + ? (i % base_rows) + : ((i - first_group_count) % max_rows); + + if (col_pers[col_idx] == 0.0f && c->grid_col_per > 0.0f) + col_pers[col_idx] = c->grid_col_per; + if (row_pers[row_idx] == 0.0f && c->grid_row_per > 0.0f) + row_pers[row_idx] = c->grid_row_per; } + // 兜底策略与总权重计算 float sum_col = 0.0f; - for (i = 0; i < cols; i++) + for (i = 0; i < cols; i++) { + if (col_pers[i] == 0.0f) + col_pers[i] = 1.0f; sum_col += col_pers[i]; + } + for (i = 0; i < max_rows; i++) { + if (row_pers[i] == 0.0f) + row_pers[i] = 1.0f; + } + + // 预计算所有列的 X 坐标和宽度 + float col_x[cols], col_w[cols]; float avail_w = m->w.width - 2 * cur_gappoh - (cols - 1) * cur_gappih; + float next_x = m->w.x + cur_gappoh; + for (i = 0; i < cols; i++) { + col_x[i] = next_x; + col_w[i] = (i == cols - 1) ? (m->w.x + m->w.width - cur_gappoh - next_x) + : (avail_w * (col_pers[i] / sum_col)); + next_x += col_w[i] + cur_gappih; + } - i = 0; - wl_list_for_each(c, &clients, link) { - if (!VISIBLEON(c, m) || !ISTILED(c)) - continue; + // 预计算两组不同的行几何参数(解决不同列行数不一致的问题) + float row_y_base[base_rows], row_h_base[base_rows]; + float sum_row_base = 0.0f; + for (i = 0; i < base_rows; i++) + sum_row_base += row_pers[i]; + float avail_h_base = + m->w.height - 2 * cur_gappov - (base_rows - 1) * cur_gappiv; + float next_y = m->w.y + cur_gappov; + for (i = 0; i < base_rows; i++) { + row_y_base[i] = next_y; + row_h_base[i] = (i == base_rows - 1) + ? (m->w.y + m->w.height - cur_gappov - next_y) + : (avail_h_base * (row_pers[i] / sum_row_base)); + next_y += row_h_base[i] + cur_gappiv; + } + + float row_y_max[max_rows], row_h_max[max_rows]; + if (remainder > 0) { + float sum_row_max = 0.0f; + for (i = 0; i < max_rows; i++) + sum_row_max += row_pers[i]; + float avail_h_max = + m->w.height - 2 * cur_gappov - (max_rows - 1) * cur_gappiv; + next_y = m->w.y + cur_gappov; + for (i = 0; i < max_rows; i++) { + row_y_max[i] = next_y; + row_h_max[i] = (i == max_rows - 1) + ? (m->w.y + m->w.height - cur_gappov - next_y) + : (avail_h_max * (row_pers[i] / sum_row_max)); + next_y += row_h_max[i] + cur_gappiv; + } + } + + // 最终渲染布局 + for (i = 0; i < n; i++) { + c = arr[i]; + int32_t col_idx, row_idx; + float fl_cx, fl_cy, fl_cw, fl_ch; - int32_t col_idx, row_idx, rows_in_this_col; if (i < first_group_count) { col_idx = i / base_rows; row_idx = i % base_rows; - rows_in_this_col = base_rows; + fl_cy = row_y_base[row_idx]; + fl_ch = row_h_base[row_idx]; } else { int32_t offset = i - first_group_count; - col_idx = first_group_cols + (offset / (base_rows + 1)); - row_idx = offset % (base_rows + 1); - rows_in_this_col = base_rows + 1; + col_idx = first_group_cols + (offset / max_rows); + row_idx = offset % max_rows; + fl_cy = row_y_max[row_idx]; + fl_ch = row_h_max[row_idx]; } c->grid_col_per = col_pers[col_idx]; @@ -953,25 +1028,8 @@ void fair(Monitor *m) { c->grid_col_idx = col_idx; c->grid_row_idx = row_idx; - float fl_cx = m->w.x + cur_gappoh; - for (int j = 0; j < col_idx; j++) - fl_cx += avail_w * (col_pers[j] / sum_col) + cur_gappih; - float fl_cw = (col_idx == cols - 1) - ? (m->w.x + m->w.width - cur_gappoh - fl_cx) - : avail_w * (col_pers[col_idx] / sum_col); - - float sum_row_this_col = 0.0f; - for (int j = 0; j < rows_in_this_col; j++) - sum_row_this_col += row_pers[j]; - - float avail_h = - m->w.height - 2 * cur_gappov - (rows_in_this_col - 1) * cur_gappiv; - float fl_cy = m->w.y + cur_gappov; - for (int j = 0; j < row_idx; j++) - fl_cy += avail_h * (row_pers[j] / sum_row_this_col) + cur_gappiv; - float fl_ch = (row_idx == rows_in_this_col - 1) - ? (m->w.y + m->w.height - cur_gappov - fl_cy) - : avail_h * (row_pers[row_idx] / sum_row_this_col); + fl_cx = col_x[col_idx]; + fl_cw = col_w[col_idx]; resize(c, (struct wlr_box){.x = (int32_t)fl_cx, @@ -979,6 +1037,5 @@ void fair(Monitor *m) { .width = (int32_t)fl_cw, .height = (int32_t)fl_ch}, 0); - i++; } -} +} \ No newline at end of file diff --git a/src/layout/vertical.h b/src/layout/vertical.h index f9c19690..733ee364 100644 --- a/src/layout/vertical.h +++ b/src/layout/vertical.h @@ -367,10 +367,9 @@ void vertical_fair(Monitor *m) { int32_t cur_gappov = enablegaps ? m->gappov : 0; int32_t cur_gappoh = enablegaps ? m->gappoh : 0; - cur_gappiv = config.smartgaps && n == 1 ? 0 : cur_gappiv; - cur_gappih = config.smartgaps && n == 1 ? 0 : cur_gappih; - cur_gappov = config.smartgaps && n == 1 ? 0 : cur_gappov; - cur_gappoh = config.smartgaps && n == 1 ? 0 : cur_gappoh; + if (config.smartgaps && n == 1) { + cur_gappiv = cur_gappih = cur_gappov = cur_gappoh = 0; + } int32_t rows; for (rows = 0; rows <= n; rows++) { @@ -384,56 +383,124 @@ void vertical_fair(Monitor *m) { int32_t first_group_count = first_group_rows * base_cols; int32_t max_cols = base_cols + (remainder > 0 ? 1 : 0); + Client *arr[n]; + int32_t arr_idx = 0; + wl_list_for_each(c, &clients, link) { + if (VISIBLEON(c, m) && ISTILED(c)) { + arr[arr_idx++] = c; + if (arr_idx >= n) + break; + } + } + float row_pers[rows]; float col_pers[max_cols]; for (i = 0; i < rows; i++) - row_pers[i] = 1.0f; + row_pers[i] = 0.0f; for (i = 0; i < max_cols; i++) - col_pers[i] = 1.0f; + col_pers[i] = 0.0f; - i = 0; - wl_list_for_each(c, &clients, link) { - if (!VISIBLEON(c, m) || !ISTILED(c)) - continue; - int32_t row_idx, col_idx; - if (i < first_group_count) { - row_idx = i / base_cols; - col_idx = i % base_cols; - } else { - int32_t offset = i - first_group_count; - row_idx = first_group_rows + (offset / (base_cols + 1)); - col_idx = offset % (base_cols + 1); - } + for (i = 0; i < n; i++) { + c = arr[i]; + int32_t row_idx = + (i < first_group_count) + ? (i / base_cols) + : (first_group_rows + (i - first_group_count) / max_cols); + int32_t col_idx = (i < first_group_count) + ? (i % base_cols) + : ((i - first_group_count) % max_cols); - if (col_idx == 0) - row_pers[row_idx] = - (c->grid_row_per > 0.0f) ? c->grid_row_per : 1.0f; - if (row_idx == 0) - col_pers[col_idx] = - (c->grid_col_per > 0.0f) ? c->grid_col_per : 1.0f; - i++; + if (c->grid_row_idx == row_idx && c->grid_row_per > 0.0f) + row_pers[row_idx] = c->grid_row_per; + if (c->grid_col_idx == col_idx && c->grid_col_per > 0.0f) + col_pers[col_idx] = c->grid_col_per; + } + for (i = 0; i < n; i++) { + c = arr[i]; + int32_t row_idx = + (i < first_group_count) + ? (i / base_cols) + : (first_group_rows + (i - first_group_count) / max_cols); + int32_t col_idx = (i < first_group_count) + ? (i % base_cols) + : ((i - first_group_count) % max_cols); + + if (row_pers[row_idx] == 0.0f && c->grid_row_per > 0.0f) + row_pers[row_idx] = c->grid_row_per; + if (col_pers[col_idx] == 0.0f && c->grid_col_per > 0.0f) + col_pers[col_idx] = c->grid_col_per; } float sum_row = 0.0f; - for (i = 0; i < rows; i++) + for (i = 0; i < rows; i++) { + if (row_pers[i] == 0.0f) + row_pers[i] = 1.0f; sum_row += row_pers[i]; + } + for (i = 0; i < max_cols; i++) { + if (col_pers[i] == 0.0f) + col_pers[i] = 1.0f; + } + + float row_y[rows], row_h[rows]; float avail_h = m->w.height - 2 * cur_gappov - (rows - 1) * cur_gappiv; + float next_y = m->w.y + cur_gappov; + for (i = 0; i < rows; i++) { + row_y[i] = next_y; + row_h[i] = (i == rows - 1) + ? (m->w.y + m->w.height - cur_gappov - next_y) + : (avail_h * (row_pers[i] / sum_row)); + next_y += row_h[i] + cur_gappiv; + } - i = 0; - wl_list_for_each(c, &clients, link) { - if (!VISIBLEON(c, m) || !ISTILED(c)) - continue; + float col_x_base[base_cols], col_w_base[base_cols]; + float sum_col_base = 0.0f; + for (i = 0; i < base_cols; i++) + sum_col_base += col_pers[i]; + float avail_w_base = + m->w.width - 2 * cur_gappoh - (base_cols - 1) * cur_gappih; + float next_x = m->w.x + cur_gappoh; + for (i = 0; i < base_cols; i++) { + col_x_base[i] = next_x; + col_w_base[i] = (i == base_cols - 1) + ? (m->w.x + m->w.width - cur_gappoh - next_x) + : (avail_w_base * (col_pers[i] / sum_col_base)); + next_x += col_w_base[i] + cur_gappih; + } + + float col_x_max[max_cols], col_w_max[max_cols]; + if (remainder > 0) { + float sum_col_max = 0.0f; + for (i = 0; i < max_cols; i++) + sum_col_max += col_pers[i]; + float avail_w_max = + m->w.width - 2 * cur_gappoh - (max_cols - 1) * cur_gappih; + next_x = m->w.x + cur_gappoh; + for (i = 0; i < max_cols; i++) { + col_x_max[i] = next_x; + col_w_max[i] = (i == max_cols - 1) + ? (m->w.x + m->w.width - cur_gappoh - next_x) + : (avail_w_max * (col_pers[i] / sum_col_max)); + next_x += col_w_max[i] + cur_gappih; + } + } + + for (i = 0; i < n; i++) { + c = arr[i]; + int32_t row_idx, col_idx; + float fl_cx, fl_cy, fl_cw, fl_ch; - int32_t row_idx, col_idx, cols_in_this_row; if (i < first_group_count) { row_idx = i / base_cols; col_idx = i % base_cols; - cols_in_this_row = base_cols; + fl_cx = col_x_base[col_idx]; + fl_cw = col_w_base[col_idx]; } else { int32_t offset = i - first_group_count; - row_idx = first_group_rows + (offset / (base_cols + 1)); - col_idx = offset % (base_cols + 1); - cols_in_this_row = base_cols + 1; + row_idx = first_group_rows + (offset / max_cols); + col_idx = offset % max_cols; + fl_cx = col_x_max[col_idx]; + fl_cw = col_w_max[col_idx]; } c->grid_row_per = row_pers[row_idx]; @@ -441,25 +508,8 @@ void vertical_fair(Monitor *m) { c->grid_row_idx = row_idx; c->grid_col_idx = col_idx; - float fl_cy = m->w.y + cur_gappov; - for (int j = 0; j < row_idx; j++) - fl_cy += avail_h * (row_pers[j] / sum_row) + cur_gappiv; - float fl_ch = (row_idx == rows - 1) - ? (m->w.y + m->w.height - cur_gappov - fl_cy) - : avail_h * (row_pers[row_idx] / sum_row); - - float sum_col_this_row = 0.0f; - for (int j = 0; j < cols_in_this_row; j++) - sum_col_this_row += col_pers[j]; - - float avail_w = - m->w.width - 2 * cur_gappoh - (cols_in_this_row - 1) * cur_gappih; - float fl_cx = m->w.x + cur_gappoh; - for (int j = 0; j < col_idx; j++) - fl_cx += avail_w * (col_pers[j] / sum_col_this_row) + cur_gappih; - float fl_cw = (col_idx == cols_in_this_row - 1) - ? (m->w.x + m->w.width - cur_gappoh - fl_cx) - : avail_w * (col_pers[col_idx] / sum_col_this_row); + fl_cy = row_y[row_idx]; + fl_ch = row_h[row_idx]; resize(c, (struct wlr_box){.x = (int32_t)fl_cx, @@ -467,6 +517,5 @@ void vertical_fair(Monitor *m) { .width = (int32_t)fl_cw, .height = (int32_t)fl_ch}, 0); - i++; } } \ No newline at end of file From 50e1b652b273cbf7f5bf334e9969e22daada4c7a Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 18 May 2026 11:44:26 +0800 Subject: [PATCH 211/328] bump version to 0.13.1 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 3cc9ed95..749fbdc0 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('mango', ['c', 'cpp'], - version : '0.13.0', + version : '0.13.1', ) subdir('protocols') From adeaaada4563e4901f798d72fd6f7a4e506634f4 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 18 May 2026 21:33:16 +0800 Subject: [PATCH 212/328] opt: optimize code struct for exchange_two_client --- src/action/client.h | 51 ++++++++ src/common/util.c | 32 +++++ src/common/util.h | 3 +- src/layout/dwindle.h | 31 ++++- src/layout/scroll.h | 142 ++++++++++++++++++++++ src/mango.c | 274 +++---------------------------------------- 6 files changed, 266 insertions(+), 267 deletions(-) create mode 100644 src/action/client.h diff --git a/src/action/client.h b/src/action/client.h new file mode 100644 index 00000000..9726976c --- /dev/null +++ b/src/action/client.h @@ -0,0 +1,51 @@ +static void client_swap_layout_properties(Client *c1, Client *c2) { + // Grid 属性交换 + double grid_col_per = c1->grid_col_per; + double grid_row_per = c1->grid_row_per; + int32_t grid_col_idx = c1->grid_col_idx; + int32_t grid_row_idx = c1->grid_row_idx; + + c1->grid_col_per = c2->grid_col_per; + c1->grid_row_per = c2->grid_row_per; + c1->grid_col_idx = c2->grid_col_idx; + c1->grid_row_idx = c2->grid_row_idx; + + c2->grid_col_per = grid_col_per; + c2->grid_row_per = grid_row_per; + c2->grid_col_idx = grid_col_idx; + c2->grid_row_idx = grid_row_idx; + + // Master / Stack 属性交换 + double master_inner_per = c1->master_inner_per; + double master_mfact_per = c1->master_mfact_per; + double stack_inner_per = c1->stack_inner_per; + + c1->master_inner_per = c2->master_inner_per; + c1->master_mfact_per = c2->master_mfact_per; + c1->stack_inner_per = c2->stack_inner_per; + + c2->master_inner_per = master_inner_per; + c2->master_mfact_per = master_mfact_per; + c2->stack_inner_per = stack_inner_per; +} + +static void client_swap_monitors_and_tags(Client *c1, Client *c2) { + Monitor *tmp_mon = c2->mon; + uint32_t tmp_tags = c2->tags; + c2->mon = c1->mon; + c1->mon = tmp_mon; + c2->tags = c1->tags; + c1->tags = tmp_tags; +} + +static void finish_exchange_arrange_and_focus(Client *c1, Client *c2, + Monitor *m1, Monitor *m2) { + if (m1 != m2) { + arrange(c1->mon, false, false); + arrange(c2->mon, false, false); + } else { + arrange(c1->mon, false, false); + } + wl_list_remove(&c2->flink); + wl_list_insert(&c1->flink, &c2->flink); +} \ No newline at end of file diff --git a/src/common/util.c b/src/common/util.c index 025aed6d..8e562b19 100644 --- a/src/common/util.c +++ b/src/common/util.c @@ -174,3 +174,35 @@ char *string_printf(const char *fmt, ...) { va_end(args); return str; } + +void wl_list_swap(struct wl_list *l1, struct wl_list *l2) { + struct wl_list *tmp1_prev = l1->prev; + struct wl_list *tmp2_prev = l2->prev; + struct wl_list *tmp1_next = l1->next; + struct wl_list *tmp2_next = l2->next; + + if (l1->next == l2) { /* l1 -> l2 相邻 */ + l1->next = l2->next; + l1->prev = l2; + l2->next = l1; + l2->prev = tmp1_prev; + tmp1_prev->next = l2; + tmp2_next->prev = l1; + } else if (l2->next == l1) { /* l2 -> l1 相邻 */ + l2->next = l1->next; + l2->prev = l1; + l1->next = l2; + l1->prev = tmp2_prev; + tmp2_prev->next = l1; + tmp1_next->prev = l2; + } else { /* 不相邻 */ + l2->next = tmp1_next; + l2->prev = tmp1_prev; + l1->next = tmp2_next; + l1->prev = tmp2_prev; + tmp1_prev->next = l2; + tmp1_next->prev = l2; + tmp2_prev->next = l1; + tmp2_next->prev = l1; + } +} \ No newline at end of file diff --git a/src/common/util.h b/src/common/util.h index cb232ac5..c7f83f2b 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -11,4 +11,5 @@ uint32_t timespec_to_ms(struct timespec *ts); char *join_strings(char *arr[], const char *sep); char *join_strings_with_suffix(char *arr[], const char *suffix, const char *sep); -char *string_printf(const char *fmt, ...); \ No newline at end of file +char *string_printf(const char *fmt, ...); +void wl_list_swap(struct wl_list *l1, struct wl_list *l2); \ No newline at end of file diff --git a/src/layout/dwindle.h b/src/layout/dwindle.h index ddc17320..4e2385f5 100644 --- a/src/layout/dwindle.h +++ b/src/layout/dwindle.h @@ -294,13 +294,32 @@ static void dwindle_move_client(DwindleNode **root, Client *c, Client *target, dwindle_insert(root, c, target, ratio, as_first, split_h, true); } -static void dwindle_swap_clients(DwindleNode **root, Client *a, Client *b) { - DwindleNode *la = dwindle_find_leaf(*root, a); - DwindleNode *lb = dwindle_find_leaf(*root, b); - if (!la || !lb || la == lb) +static void dwindle_swap_clients(Client *c1, Client *c2) { + + if (!c1 || !c2 || !c1->mon || !c2->mon || c1 == c2) return; - la->client = b; - lb->client = a; + + Monitor *m1 = c1->mon; + Monitor *m2 = c2->mon; + + DwindleNode **c1_root = &m1->pertag->dwindle_root[m1->pertag->curtag]; + DwindleNode *c1node = dwindle_find_leaf(*c1_root, c1); + DwindleNode **c2_root = &m2->pertag->dwindle_root[m2->pertag->curtag]; + DwindleNode *c2node = dwindle_find_leaf(*c2_root, c2); + + client_swap_layout_properties(c1, c2); + + if (c1node) + c1node->client = c2; + if (c2node) + c2node->client = c1; + + if (m1 != m2) { + client_swap_monitors_and_tags(c1, c2); + } + + wl_list_swap(&c1->link, &c2->link); + finish_exchange_arrange_and_focus(c1, c2, m1, m2); } static void dwindle_resize_client(Monitor *m, Client *c) { diff --git a/src/layout/scroll.h b/src/layout/scroll.h index 0020a7b1..f6852daa 100644 --- a/src/layout/scroll.h +++ b/src/layout/scroll.h @@ -918,4 +918,146 @@ static void update_scroller_state(Monitor *m) { scroller_node_create(st, vis[i]); } } +} + +static void scroller_swap_nodes_in_same_stack(struct ScrollerStackNode *n1, + struct ScrollerStackNode *n2) { + float tmp_sc = n1->scroller_proportion; + float tmp_st = n1->stack_proportion; + n1->scroller_proportion = n2->scroller_proportion; + n1->stack_proportion = n2->stack_proportion; + n2->scroller_proportion = tmp_sc; + n2->stack_proportion = tmp_st; + + struct ScrollerStackNode *p1 = n1->prev_in_stack; + struct ScrollerStackNode *next1 = n1->next_in_stack; + struct ScrollerStackNode *p2 = n2->prev_in_stack; + struct ScrollerStackNode *next2 = n2->next_in_stack; + + if (n1->next_in_stack == n2) { + n1->next_in_stack = next2; + n2->prev_in_stack = p1; + n1->prev_in_stack = n2; + n2->next_in_stack = n1; + if (p1) + p1->next_in_stack = n2; + if (next2) + next2->prev_in_stack = n1; + } else if (n2->next_in_stack == n1) { + n2->next_in_stack = next1; + n1->prev_in_stack = p2; + n2->prev_in_stack = n1; + n1->next_in_stack = n2; + if (p2) + p2->next_in_stack = n1; + if (next1) + next1->prev_in_stack = n2; + } else { + if (p1) + p1->next_in_stack = n2; + if (next1) + next1->prev_in_stack = n2; + if (p2) + p2->next_in_stack = n1; + if (next2) + next2->prev_in_stack = n1; + n1->prev_in_stack = p2; + n1->next_in_stack = next2; + n2->prev_in_stack = p1; + n2->next_in_stack = next1; + } +} + +static void scroller_swap_different_stacks(struct ScrollerStackNode *head1, + struct ScrollerStackNode *head2) { + Client *head1_c = head1->client; + Client *head2_c = head2->client; + Client *tail1_c = scroll_get_stack_tail_client(head1_c); + Client *tail2_c = scroll_get_stack_tail_client(head2_c); + + struct wl_list *p1 = head1_c->link.prev; + struct wl_list *n1_next = tail1_c->link.next; + struct wl_list *p2 = head2_c->link.prev; + struct wl_list *n2_next = tail2_c->link.next; + + if (n1_next == &head2_c->link) { + p2->next = n2_next; + n2_next->prev = p2; + p1->next = &head2_c->link; + head2_c->link.prev = p1; + tail2_c->link.next = &head1_c->link; + head1_c->link.prev = &tail2_c->link; + } else if (n2_next == &head1_c->link) { + p1->next = n1_next; + n1_next->prev = p1; + p2->next = &head1_c->link; + head1_c->link.prev = p2; + tail1_c->link.next = &head2_c->link; + head2_c->link.prev = &tail1_c->link; + } else { + p1->next = &head2_c->link; + head2_c->link.prev = p1; + tail2_c->link.next = n1_next; + n1_next->prev = &tail2_c->link; + + p2->next = &head1_c->link; + head1_c->link.prev = p2; + tail1_c->link.next = n2_next; + n2_next->prev = &tail1_c->link; + } +} + +void exchange_two_scroller_clients(Client *c1, Client *c2) { + + if (!c1 || !c2 || !c1->mon || !c2->mon) + return; + + struct ScrollerStackNode *n1 = NULL; + struct ScrollerStackNode *n2 = NULL; + Monitor *m1 = c1->mon; + Monitor *m2 = c2->mon; + uint32_t tag1 = m1->pertag->curtag; + uint32_t tag2 = m2->pertag->curtag; + + struct TagScrollerState *st1 = ensure_scroller_state(m1, tag1); + n1 = find_scroller_node(st1, c1); + + struct TagScrollerState *st2 = ensure_scroller_state(m2, tag2); + n2 = find_scroller_node(st2, c2); + + if (!n1 && !n2) + return; + + if (m1 != m2 && ((n1 && n1->prev_in_stack) || (n2 && n2->prev_in_stack) || + (n1 && n1->next_in_stack) || (n2 && n2->next_in_stack))) { + return; + } + + client_swap_layout_properties(c1, c2); + + if (n1 && n2) { + struct ScrollerStackNode *head1 = n1; + while (head1->prev_in_stack) + head1 = head1->prev_in_stack; + struct ScrollerStackNode *head2 = n2; + while (head2->prev_in_stack) + head2 = head2->prev_in_stack; + + if (head1 == head2) { + scroller_swap_nodes_in_same_stack(n1, n2); + sync_scroller_state_to_clients(m1, tag1); + wl_list_swap(&c1->link, &c2->link); + } else { + scroller_swap_different_stacks(head1, head2); + } + } else { + wl_list_swap(&c1->link, &c2->link); + } + + if (m1 != m2) { + client_swap_monitors_and_tags(c1, c2); + } + finish_exchange_arrange_and_focus(c1, c2, m1, m2); + + return; } \ No newline at end of file diff --git a/src/mango.c b/src/mango.c index edd79958..ccba268c 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1067,6 +1067,7 @@ static struct wlr_xwayland *xwayland; static struct wl_event_source *sync_keymap; #endif +#include "action/client.h" #include "animation/client.h" #include "animation/common.h" #include "animation/layer.h" @@ -5061,282 +5062,35 @@ void setborder_color(Client *c) { } void exchange_two_client(Client *c1, Client *c2) { - Monitor *tmp_mon = NULL; - uint32_t tmp_tags; - double master_inner_per = 0.0f; - double master_mfact_per = 0.0f; - double stack_inner_per = 0.0f; - double grid_col_per = 0.0f; - double grid_row_per = 0.0f; - int32_t grid_col_idx = 0; - int32_t grid_row_idx = 0; - struct ScrollerStackNode *n1 = NULL; - struct ScrollerStackNode *n2 = NULL; - struct TagScrollerState *st1 = NULL; - struct TagScrollerState *st2 = NULL; - if (c1 == NULL || c2 == NULL || (!config.exchange_cross_monitor && c1->mon != c2->mon)) { return; } - /* 保存并交换 grid / master / stack 等比例属性 */ - grid_col_per = c1->grid_col_per; - grid_row_per = c1->grid_row_per; - grid_col_idx = c1->grid_col_idx; - grid_row_idx = c1->grid_row_idx; - c1->grid_col_per = c2->grid_col_per; - c1->grid_row_per = c2->grid_row_per; - c1->grid_col_idx = c2->grid_col_idx; - c1->grid_row_idx = c2->grid_row_idx; - c2->grid_col_per = grid_col_per; - c2->grid_row_per = grid_row_per; - c2->grid_col_idx = grid_col_idx; - c2->grid_row_idx = grid_row_idx; - - master_inner_per = c1->master_inner_per; - master_mfact_per = c1->master_mfact_per; - stack_inner_per = c1->stack_inner_per; - c1->master_inner_per = c2->master_inner_per; - c1->master_mfact_per = c2->master_mfact_per; - c1->stack_inner_per = c2->stack_inner_per; - c2->master_inner_per = master_inner_per; - c2->master_mfact_per = master_mfact_per; - c2->stack_inner_per = stack_inner_per; - - bool c1_scroller = c1->mon && is_scroller_layout(c1->mon); - bool c2_scroller = c2->mon && is_scroller_layout(c2->mon); Monitor *m1 = c1->mon; Monitor *m2 = c2->mon; - uint32_t tag1 = m1->pertag->curtag; - uint32_t tag2 = m2->pertag->curtag; + const Layout *layout1 = m1->pertag->ltidxs[m1->pertag->curtag]; + const Layout *layout2 = m2->pertag->ltidxs[m2->pertag->curtag]; - if (c1_scroller) { - st1 = ensure_scroller_state(m1, tag1); - n1 = find_scroller_node(st1, c1); - } - if (c2_scroller) { - st2 = ensure_scroller_state(m2, tag2); - n2 = find_scroller_node(st2, c2); - } - - /* --------------------------------------------------------------- - * 情况1:两个客户端都在 scroller 布局中 - * --------------------------------------------------------------- */ - if (n1 && n2) { - /* 跨显示器且任一方有堆叠关系(非单客户端)时不允许交换 */ - if (m1 != m2 && (n1->prev_in_stack || n2->prev_in_stack || - n1->next_in_stack || n2->next_in_stack)) - return; - - /* 找到各自的堆叠头 */ - struct ScrollerStackNode *head1 = n1; - while (head1->prev_in_stack) - head1 = head1->prev_in_stack; - struct ScrollerStackNode *head2 = n2; - while (head2->prev_in_stack) - head2 = head2->prev_in_stack; - - /* --- 1a. 同一个堆叠内交换 --- */ - if (head1 == head2) { - /* 交换 scroller/stack 比例 */ - float tmp_sc = n1->scroller_proportion; - float tmp_st = n1->stack_proportion; - n1->scroller_proportion = n2->scroller_proportion; - n1->stack_proportion = n2->stack_proportion; - n2->scroller_proportion = tmp_sc; - n2->stack_proportion = tmp_st; - - /* 交换堆叠链表指针 */ - struct ScrollerStackNode *p1 = n1->prev_in_stack; - struct ScrollerStackNode *next1 = n1->next_in_stack; - struct ScrollerStackNode *p2 = n2->prev_in_stack; - struct ScrollerStackNode *next2 = n2->next_in_stack; - - if (n1->next_in_stack == n2) { - n1->next_in_stack = next2; - n2->prev_in_stack = p1; - n1->prev_in_stack = n2; - n2->next_in_stack = n1; - if (p1) - p1->next_in_stack = n2; - if (next2) - next2->prev_in_stack = n1; - } else if (n2->next_in_stack == n1) { - n2->next_in_stack = next1; - n1->prev_in_stack = p2; - n2->prev_in_stack = n1; - n1->next_in_stack = n2; - if (p2) - p2->next_in_stack = n1; - if (next1) - next1->prev_in_stack = n2; - } else { - if (p1) - p1->next_in_stack = n2; - if (next1) - next1->prev_in_stack = n2; - if (p2) - p2->next_in_stack = n1; - if (next2) - next2->prev_in_stack = n1; - n1->prev_in_stack = p2; - n1->next_in_stack = next2; - n2->prev_in_stack = p1; - n2->next_in_stack = next1; - } - - sync_scroller_state_to_clients(m1, tag1); - /* 继续执行 exchange_common 以交换全局链表中的 c1 和 c2 */ - } - /* --- 1b. 不同堆叠之间的整体交换 --- */ - else { - Client *head1_c = head1->client; - Client *head2_c = head2->client; - Client *tail1_c = scroll_get_stack_tail_client(head1_c); - Client *tail2_c = scroll_get_stack_tail_client(head2_c); - - struct wl_list *p1 = head1_c->link.prev; - struct wl_list *n1_next = tail1_c->link.next; - struct wl_list *p2 = head2_c->link.prev; - struct wl_list *n2_next = tail2_c->link.next; - - if (n1_next == &head2_c->link) { - /* [堆1] -> [堆2] 相邻 */ - p2->next = n2_next; - n2_next->prev = p2; - p1->next = &head2_c->link; - head2_c->link.prev = p1; - tail2_c->link.next = &head1_c->link; - head1_c->link.prev = &tail2_c->link; - } else if (n2_next == &head1_c->link) { - /* [堆2] -> [堆1] 相邻 */ - p1->next = n1_next; - n1_next->prev = p1; - p2->next = &head1_c->link; - head1_c->link.prev = p2; - tail1_c->link.next = &head2_c->link; - head2_c->link.prev = &tail1_c->link; - } else { - /* 两个堆叠不相邻 */ - p1->next = &head2_c->link; - head2_c->link.prev = p1; - tail2_c->link.next = n1_next; - n1_next->prev = &tail2_c->link; - - p2->next = &head1_c->link; - head1_c->link.prev = p2; - tail1_c->link.next = n2_next; - n2_next->prev = &tail1_c->link; - } - - /* 跨显示器时交换 mon / tags */ - if (m1 != m2) { - tmp_mon = c2->mon; - tmp_tags = c2->tags; - c2->mon = c1->mon; - c1->mon = tmp_mon; - c2->tags = c1->tags; - c1->tags = tmp_tags; - } - /* 整体交换已完成,跳过普通交换部分,直接 arrange */ - goto arrange_and_finish; - } - } - - /* --------------------------------------------------------------- - * 情况2:至少一方不在 scroller 中(或双方均为 NULL) - * 执行普通的全局链表节点交换 - * --------------------------------------------------------------- */ - /* 跨显示器且任一方有堆叠关系时不允许交换 */ - if (m1 != m2 && ((n1 && n1->prev_in_stack) || (n2 && n2->prev_in_stack) || - (n1 && n1->next_in_stack) || (n2 && n2->next_in_stack))) + if (layout1->id == SCROLLER || layout2->id == SCROLLER) { + exchange_two_scroller_clients(c1, c2); return; - - { - struct wl_list *tmp1_prev = c1->link.prev; - struct wl_list *tmp2_prev = c2->link.prev; - struct wl_list *tmp1_next = c1->link.next; - struct wl_list *tmp2_next = c2->link.next; - - if (c1->link.next == &c2->link) { - c1->link.next = c2->link.next; - c1->link.prev = &c2->link; - c2->link.next = &c1->link; - c2->link.prev = tmp1_prev; - tmp1_prev->next = &c2->link; - tmp2_next->prev = &c1->link; - } else if (c2->link.next == &c1->link) { - c2->link.next = c1->link.next; - c2->link.prev = &c1->link; - c1->link.next = &c2->link; - c1->link.prev = tmp2_prev; - tmp2_prev->next = &c1->link; - tmp1_next->prev = &c2->link; - } else { - c2->link.next = tmp1_next; - c2->link.prev = tmp1_prev; - c1->link.next = tmp2_next; - c1->link.prev = tmp2_prev; - tmp1_prev->next = &c2->link; - tmp1_next->prev = &c2->link; - tmp2_prev->next = &c1->link; - tmp2_next->prev = &c1->link; - } } - { - const Layout *layout1 = - c1->mon->pertag->ltidxs[c1->mon->pertag->curtag]; - const Layout *layout2 = - c2->mon->pertag->ltidxs[c2->mon->pertag->curtag]; - - if (c1->mon != c2->mon) { - if (layout1->id == DWINDLE && layout2->id == DWINDLE) { - DwindleNode **c1_root = - &m1->pertag->dwindle_root[m1->pertag->curtag]; - DwindleNode *c1node = dwindle_find_leaf(*c1_root, c1); - DwindleNode **c2_root = - &m2->pertag->dwindle_root[m2->pertag->curtag]; - DwindleNode *c2node = dwindle_find_leaf(*c2_root, c2); - if (c1node) - c1node->client = c2; - if (c2node) - c2node->client = c1; - } - tmp_mon = c2->mon; - tmp_tags = c2->tags; - c2->mon = c1->mon; - c1->mon = tmp_mon; - c2->tags = c1->tags; - c1->tags = tmp_tags; - arrange(c1->mon, false, false); - arrange(c2->mon, false, false); - } else { - if (layout1->id == DWINDLE && layout2->id == DWINDLE) { - dwindle_swap_clients( - &c1->mon->pertag->dwindle_root[c1->mon->pertag->curtag], c1, - c2); - } - arrange(c1->mon, false, false); - } + if (layout1->id == DWINDLE && layout2->id == DWINDLE) { + dwindle_swap_clients(c1, c2); + return; } - /* 调整焦点顺序 */ - wl_list_remove(&c2->flink); - wl_list_insert(&c1->flink, &c2->flink); - return; + client_swap_layout_properties(c1, c2); + + wl_list_swap(&c1->link, &c2->link); -arrange_and_finish: - /* 整体交换后的统一 arrange 和焦点调整 */ if (m1 != m2) { - arrange(c1->mon, false, false); - arrange(c2->mon, false, false); - } else { - arrange(c1->mon, false, false); + client_swap_monitors_and_tags(c1, c2); } - wl_list_remove(&c2->flink); - wl_list_insert(&c1->flink, &c2->flink); + + finish_exchange_arrange_and_focus(c1, c2, m1, m2); } void set_activation_env() { From 9e4fade55e53033ef4473d13285bba20467ab223 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 19 May 2026 13:59:38 +0800 Subject: [PATCH 213/328] fix: cransh in disable monitor when use scratchpad --- src/fetch/client.h | 7 +++++++ src/fetch/monitor.h | 2 +- src/mango.c | 3 ++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/fetch/client.h b/src/fetch/client.h index 524efa2a..e39e39b3 100644 --- a/src/fetch/client.h +++ b/src/fetch/client.h @@ -1,5 +1,12 @@ bool check_hit_no_border(Client *c) { bool hit_no_border = false; + + if (!c->mon) + return false; + + if (c->tags <= 0) + return false; + if (!render_border) { hit_no_border = true; } diff --git a/src/fetch/monitor.h b/src/fetch/monitor.h index d2b4fe62..6144ee40 100644 --- a/src/fetch/monitor.h +++ b/src/fetch/monitor.h @@ -54,7 +54,7 @@ uint32_t get_tags_first_tag_num(uint32_t source_tags) { tag = 0; if (!source_tags) { - return selmon->pertag->curtag; + return 0; } for (i = 0; !(tag & 1) && source_tags != 0 && i < LENGTH(tags); i++) { diff --git a/src/mango.c b/src/mango.c index ccba268c..0ae7d3d9 100644 --- a/src/mango.c +++ b/src/mango.c @@ -6389,7 +6389,8 @@ void updatemons(struct wl_listener *listener, void *data) { c->geom.x += mon_pos_offsetx; c->geom.y += mon_pos_offsety; c->float_geom = c->geom; - resize(c, c->geom, 1); + if (VISIBLEON(c, m)) + resize(c, c->geom, 1); } // restore window to old monitor From a6a765caff8f9c2988c13540e755648bbf516f5e Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 19 May 2026 10:02:21 +0800 Subject: [PATCH 214/328] feat: dont resize client when in overview --- assets/config.conf | 1 + docs/window-management/overview.md | 2 + src/animation/client.h | 68 +++++-- src/config/parse_config.h | 13 +- src/dispatch/bind_define.h | 7 +- src/fetch/common.h | 36 +++- src/layout/horizontal.h | 114 ----------- src/layout/overview.h | 298 +++++++++++++++++++++++++++++ src/mango.c | 44 ++++- 9 files changed, 446 insertions(+), 137 deletions(-) create mode 100644 src/layout/overview.h diff --git a/assets/config.conf b/assets/config.conf index bc141e63..bb1ecfde 100644 --- a/assets/config.conf +++ b/assets/config.conf @@ -78,6 +78,7 @@ dwindle_preserve_split=0 hotarea_size=10 enable_hotarea=1 ov_tab_mode=0 +ov_no_resize=0 overviewgappi=5 overviewgappo=30 diff --git a/docs/window-management/overview.md b/docs/window-management/overview.md index 7da6e690..98f99c15 100644 --- a/docs/window-management/overview.md +++ b/docs/window-management/overview.md @@ -13,6 +13,7 @@ description: Configure the overview mode for window navigation. | `ov_tab_mode` | integer | `0` | Overview tab mode (0: disable, 1: enable). | | `overviewgappi` | integer | `5` | Inner gap in overview mode. | | `overviewgappo` | integer | `30` | Outer gap in overview mode. | +| `ov_no_resize` | integer | `0` | Disable window resizing in overview mode. | ### Setting Descriptions @@ -20,6 +21,7 @@ description: Configure the overview mode for window navigation. - `hotarea_size` — Size of the hot area trigger zone in pixels. - `hotarea_corner` — Corner that triggers the hot area (0: top-left, 1: top-right, 2: bottom-left, 3: bottom-right). - `ov_tab_mode` — Circles focus through windows in overview; releasing the mod key exits overview. +- `ov_no_resize` — Disables resizing of windows in overview mode(use snap to display). ### Mouse Interaction in Overview diff --git a/src/animation/client.h b/src/animation/client.h index 5a83a11e..4d408484 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -73,6 +73,14 @@ int32_t is_special_animation_rule(Client *c) { } } +void set_overview_enter_animation(Client *c) { + struct wlr_box geo = c->geom; + c->animainit_geom.width = geo.width * 1.2; + c->animainit_geom.height = geo.height * 1.2; + c->animainit_geom.x = geo.x + (geo.width - c->animainit_geom.width) / 2; + c->animainit_geom.y = geo.y + (geo.height - c->animainit_geom.height) / 2; +} + void set_client_open_animation(Client *c, struct wlr_box geo) { int32_t slide_direction; int32_t horizontal, horizontal_value; @@ -231,6 +239,19 @@ void scene_buffer_apply_effect(struct wlr_scene_buffer *buffer, int32_t sx, buffer_data->corner_location); } +void scene_buffer_apply_overview_effect(struct wlr_scene_buffer *buffer, + int32_t sx, int32_t sy, void *data) { + BufferData *buffer_data = (BufferData *)data; + + if (buffer_data->width > 0 && buffer_data->height > 0) { + wlr_scene_buffer_set_dest_size(buffer, buffer_data->width, + buffer_data->height); + } + + wlr_scene_buffer_set_corner_radius(buffer, config.border_radius, + buffer_data->corner_location); +} + void buffer_set_effect(Client *c, BufferData data) { if (!c || c->iskilling) @@ -250,8 +271,13 @@ void buffer_set_effect(Client *c, BufferData data) { data.corner_location = CORNER_LOCATION_NONE; } - wlr_scene_node_for_each_buffer(&c->scene_surface->node, - scene_buffer_apply_effect, &data); + if (c->mon->isoverview && config.ov_no_resize) { + wlr_scene_node_for_each_buffer( + &c->scene_surface->node, scene_buffer_apply_overview_effect, &data); + } else { + wlr_scene_node_for_each_buffer(&c->scene_surface->node, + scene_buffer_apply_effect, &data); + } } void client_draw_shadow(Client *c) { @@ -793,7 +819,11 @@ void client_apply_clip(Client *c, float factor) { return; } - wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip_box); + if (!c->mon->isoverview || !config.ov_no_resize) { + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, + &clip_box); + } + buffer_set_effect(c, (BufferData){1.0f, 1.0f, clip_box.width, clip_box.height, current_corner_location, true}); @@ -841,7 +871,9 @@ void client_apply_clip(Client *c, float factor) { } // 应用窗口表面剪切 - wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip_box); + if (!c->mon->isoverview || !config.ov_no_resize) { + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip_box); + } // 获取剪切后的表面的实际大小用于计算缩放 int32_t acutal_surface_width = geometry.width - offset.x - offset.width; @@ -987,6 +1019,7 @@ void client_animation_next_tick(Client *c) { c->animation.tagining = false; c->animation.running = false; + c->animation.overining = false; if (c->animation.tagouting) { c->animation.tagouting = false; @@ -1125,9 +1158,8 @@ void client_set_pending_state(Client *c) { c->animation.should_animate = false; } else if (config.animations && c->animation.tagining) { c->animation.should_animate = true; - } else if (!config.animations || c == grabc || - (!c->is_pending_open_animation && - wlr_box_equal(&c->current, &c->pending))) { + } else if (c == grabc || (!c->is_pending_open_animation && + wlr_box_equal(&c->current, &c->pending))) { c->animation.should_animate = false; } else { c->animation.should_animate = true; @@ -1201,8 +1233,11 @@ void resize(Client *c, struct wlr_box geo, int32_t interact) { c->animation.begin_fade_in = false; } - if (c->animation.action == OPEN && !c->animation.tagining && - !c->animation.tagouting && wlr_box_equal(&c->geom, &c->current)) { + if (c->animation.overining) { + c->animation.action = OVERVIEW; + } else if (c->animation.action == OPEN && !c->animation.tagining && + !c->animation.tagouting && + wlr_box_equal(&c->geom, &c->current)) { c->animation.action = c->animation.action; } else if (c->animation.tagouting) { c->animation.duration = config.animation_duration_tag; @@ -1241,8 +1276,10 @@ void resize(Client *c, struct wlr_box geo, int32_t interact) { } // c->geom 是真实的窗口大小和位置,跟过度的动画无关,用于计算布局 - c->configure_serial = client_set_size(c, c->geom.width - 2 * c->bw, - c->geom.height - 2 * c->bw); + if (!c->mon->isoverview || !config.ov_no_resize) { + c->configure_serial = client_set_size(c, c->geom.width - 2 * c->bw, + c->geom.height - 2 * c->bw); + } if (c->configure_serial != 0) { c->mon->resizing_count_pending++; @@ -1285,6 +1322,15 @@ void resize(Client *c, struct wlr_box geo, int32_t interact) { c->animainit_geom = c->geom; } + if (config.animations && config.ov_no_resize && c->mon->isoverview && + c != c->mon->sel && c->animation.action == OVERVIEW) { + set_overview_enter_animation(c); + } + + if (!config.animations && config.ov_no_resize && c->mon->isoverview) { + c->animainit_geom = c->geom; + } + // 开始应用动画设置 client_set_pending_state(c); diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 9b82d81b..7c0000cb 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -252,10 +252,11 @@ typedef struct { int32_t dwindle_manual_split; float dwindle_split_ratio; - uint32_t hotarea_size; - uint32_t hotarea_corner; - uint32_t enable_hotarea; - uint32_t ov_tab_mode; + int32_t hotarea_size; + int32_t hotarea_corner; + int32_t enable_hotarea; + int32_t ov_tab_mode; + int32_t ov_no_resize; int32_t overviewgappi; int32_t overviewgappo; uint32_t cursor_hide_timeout; @@ -1655,6 +1656,8 @@ bool parse_option(Config *config, char *key, char *value) { config->enable_hotarea = atoi(value); } else if (strcmp(key, "ov_tab_mode") == 0) { config->ov_tab_mode = atoi(value); + } else if (strcmp(key, "ov_no_resize") == 0) { + config->ov_no_resize = atoi(value); } else if (strcmp(key, "overviewgappi") == 0) { config->overviewgappi = atoi(value); } else if (strcmp(key, "overviewgappo") == 0) { @@ -3237,6 +3240,7 @@ void override_config(void) { config.hotarea_corner = CLAMP_INT(config.hotarea_corner, 0, 3); config.enable_hotarea = CLAMP_INT(config.enable_hotarea, 0, 1); config.ov_tab_mode = CLAMP_INT(config.ov_tab_mode, 0, 1); + config.ov_no_resize = CLAMP_INT(config.ov_no_resize, 0, 1); config.overviewgappi = CLAMP_INT(config.overviewgappi, 0, 1000); config.overviewgappo = CLAMP_INT(config.overviewgappo, 0, 1000); config.xwayland_persistence = CLAMP_INT(config.xwayland_persistence, 0, 1); @@ -3386,6 +3390,7 @@ void set_value_default() { config.capslock = 0; config.ov_tab_mode = 0; + config.ov_no_resize = 0; config.hotarea_size = 10; config.hotarea_corner = BOTTOM_LEFT; config.enable_hotarea = 1; diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 2161f39b..60ebf915 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1743,15 +1743,18 @@ int32_t toggleoverview(const Arg *arg) { wl_list_for_each(c, &clients, link) { if (c && c->mon == selmon && !client_is_unmanaged(c) && - !client_is_x11_popup(c) && !c->isunglobal) + !client_is_x11_popup(c) && !c->isunglobal) { + c->animation.overining = true; overview_backup(c); + } } } else { wl_list_for_each(c, &clients, link) { if (c && c->mon == selmon && !c->iskilling && !client_is_unmanaged(c) && !c->isunglobal && - !client_is_x11_popup(c) && client_surface(c)->mapped) + !client_is_x11_popup(c) && client_surface(c)->mapped) { overview_restore(c, &(Arg){.ui = target}); + } } } diff --git a/src/fetch/common.h b/src/fetch/common.h index 57a1a8e6..aa3ae6ff 100644 --- a/src/fetch/common.h +++ b/src/fetch/common.h @@ -77,6 +77,19 @@ void get_layout_abbr(char *abbr, const char *full_name) { } } +Client *xytoclient(double x, double y) { + Client *c = NULL, *tmp = NULL; + wl_list_for_each_safe(c, tmp, &clients, link) { + if (VISIBLEON(c, c->mon) && c->animation.current.x <= x && + c->animation.current.y <= y && + c->animation.current.x + c->animation.current.width >= x && + c->animation.current.y + c->animation.current.height >= y) { + return c; + } + } + return NULL; +} + void xytonode(double x, double y, struct wlr_surface **psurface, Client **pc, LayerSurface **pl, double *nx, double *ny) { struct wlr_scene_node *node, *pnode; @@ -84,6 +97,7 @@ void xytonode(double x, double y, struct wlr_surface **psurface, Client **pc, Client *c = NULL; LayerSurface *l = NULL; int32_t layer; + Client *ovc = NULL; for (layer = NUM_LAYERS - 1; !surface && layer >= 0; layer--) { @@ -96,10 +110,16 @@ void xytonode(double x, double y, struct wlr_surface **psurface, Client **pc, if (!node->enabled) continue; - if (node->type == WLR_SCENE_NODE_BUFFER) - surface = wlr_scene_surface_try_from_buffer( - wlr_scene_buffer_from_node(node)) - ->surface; + if (node->type == WLR_SCENE_NODE_BUFFER) { + struct wlr_scene_surface *scene_surface = + wlr_scene_surface_try_from_buffer( + wlr_scene_buffer_from_node(node)); + if (scene_surface) { + surface = scene_surface->surface; + } else { + continue; + } + } /* start from the topmost layer, find a sureface that can be focused by pointer, @@ -130,4 +150,12 @@ void xytonode(double x, double y, struct wlr_surface **psurface, Client **pc, *pc = c; if (pl) *pl = l; + + if (selmon && selmon->isoverview && !l) { + ovc = xytoclient(x, y); + if (pc) + *pc = ovc; + if (psurface && ovc) + *psurface = client_surface(ovc); + } } \ No newline at end of file diff --git a/src/layout/horizontal.h b/src/layout/horizontal.h index 00bea0e0..87fe2a96 100644 --- a/src/layout/horizontal.h +++ b/src/layout/horizontal.h @@ -1,117 +1,3 @@ - -// 网格布局窗口大小和位置计算 -void overview(Monitor *m) { - int32_t i, n; - int32_t cx, cy, cw, ch; - int32_t dx; - int32_t cols, rows, overcols; - Client *c = NULL; - n = 0; - int32_t target_gappo = - enablegaps ? m->isoverview ? config.overviewgappo : config.gappoh : 0; - int32_t target_gappi = - enablegaps ? m->isoverview ? config.overviewgappi : config.gappih : 0; - float single_width_ratio = m->isoverview ? 0.7 : 0.9; - float single_height_ratio = m->isoverview ? 0.8 : 0.9; - - n = m->isoverview ? m->visible_clients : m->visible_tiling_clients; - - if (n == 0) { - return; // 没有需要处理的客户端,直接返回 - } - - if (n == 1) { - wl_list_for_each(c, &clients, link) { - - if (c->mon != m) - continue; - - if (VISIBLEON(c, m) && !c->isunglobal && - ((m->isoverview && !client_is_x11_popup(c)) || ISTILED(c))) { - cw = (m->w.width - 2 * target_gappo) * single_width_ratio; - ch = (m->w.height - 2 * target_gappo) * single_height_ratio; - c->geom.x = m->w.x + (m->w.width - cw) / 2; - c->geom.y = m->w.y + (m->w.height - ch) / 2; - c->geom.width = cw; - c->geom.height = ch; - resize(c, c->geom, 0); - return; - } - } - } - - if (n == 2) { - cw = (m->w.width - 2 * target_gappo - target_gappi) / 2; - ch = (m->w.height - 2 * target_gappo) * 0.65; - i = 0; - wl_list_for_each(c, &clients, link) { - if (c->mon != m) - continue; - - if (VISIBLEON(c, m) && !c->isunglobal && - ((m->isoverview && !client_is_x11_popup(c)) || ISTILED(c))) { - if (i == 0) { - c->geom.x = m->w.x + target_gappo; - c->geom.y = m->w.y + (m->w.height - ch) / 2 + target_gappo; - c->geom.width = cw; - c->geom.height = ch; - resize(c, c->geom, 0); - } else if (i == 1) { - c->geom.x = m->w.x + cw + target_gappo + target_gappi; - c->geom.y = m->w.y + (m->w.height - ch) / 2 + target_gappo; - c->geom.width = cw; - c->geom.height = ch; - resize(c, c->geom, 0); - } - i++; - } - } - return; - } - - // 计算列数和行数 - for (cols = 0; cols <= n / 2; cols++) { - if (cols * cols >= n) { - break; - } - } - rows = (cols && (cols - 1) * cols >= n) ? cols - 1 : cols; - - // 计算每个客户端的高度和宽度 - ch = (m->w.height - 2 * target_gappo - (rows - 1) * target_gappi) / rows; - cw = (m->w.width - 2 * target_gappo - (cols - 1) * target_gappi) / cols; - - // 处理多余的列 - overcols = n % cols; - if (overcols) { - dx = (m->w.width - overcols * cw - (overcols - 1) * target_gappi) / 2 - - target_gappo; - } - - // 调整每个客户端的位置和大小 - i = 0; - wl_list_for_each(c, &clients, link) { - - if (c->mon != m) - continue; - - if (VISIBLEON(c, m) && !c->isunglobal && - ((m->isoverview && !client_is_x11_popup(c)) || ISTILED(c))) { - cx = m->w.x + (i % cols) * (cw + target_gappi); - cy = m->w.y + (i / cols) * (ch + target_gappi); - if (overcols && i >= n - overcols) { - cx += dx; - } - c->geom.x = cx + target_gappo; - c->geom.y = cy + target_gappo; - c->geom.width = cw; - c->geom.height = ch; - resize(c, c->geom, 0); - i++; - } - } -} - void deck(Monitor *m) { int32_t mw, my; int32_t i, n = 0; diff --git a/src/layout/overview.h b/src/layout/overview.h new file mode 100644 index 00000000..72544251 --- /dev/null +++ b/src/layout/overview.h @@ -0,0 +1,298 @@ +void overview_scale(Monitor *m) { + int32_t target_gappo = config.overviewgappo; + int32_t target_gappi = config.overviewgappi; + + int orig_n = m->visible_clients; + + if (orig_n == 0) + return; + + size_t sz_c_arr = orig_n * sizeof(Client *); + size_t sz_aspects = orig_n * sizeof(float); + size_t sz_suffix_sums = (orig_n + 1) * sizeof(float); + size_t sz_best_items = orig_n * sizeof(int); + size_t sz_temp_items = orig_n * sizeof(int); + size_t sz_items = orig_n * sizeof(int); + size_t sz_A_sum = orig_n * sizeof(float); + + size_t total_size = sz_c_arr + sz_aspects + sz_suffix_sums + sz_best_items + + sz_temp_items + sz_items + sz_A_sum; + + void *buffer = malloc(total_size); + if (!buffer) { + return; + } + + Client **c_arr = (Client **)buffer; + float *aspects = (float *)((char *)buffer + sz_c_arr); + float *suffix_sums = (float *)((char *)aspects + sz_aspects); + int *best_items_per_row = (int *)((char *)suffix_sums + sz_suffix_sums); + int *temp_items_per_row = + (int *)((char *)best_items_per_row + sz_best_items); + int *items_per_row = (int *)((char *)temp_items_per_row + sz_temp_items); + float *A_sum = (float *)((char *)items_per_row + sz_items); + + int actual_n = 0; + Client *c = NULL; + + wl_list_for_each(c, &clients, link) { + if (c->mon != m) + continue; + if (VISIBLEON(c, m) && !c->isunglobal && !client_is_x11_popup(c)) { + + c_arr[actual_n] = c; + float aspect = 1.0f; + if (c->overview_backup_geom.height > 0 && + c->overview_backup_geom.width > 0) { + aspect = (float)c->overview_backup_geom.width / + c->overview_backup_geom.height; + } + if (aspect < 0.2f) + aspect = 0.2f; + if (aspect > 5.0f) + aspect = 5.0f; + + aspects[actual_n] = aspect; + actual_n++; + } + } + + int n = actual_n; + if (n == 0) { + free(buffer); + return; + } + + suffix_sums[n] = 0.0f; + for (int i = n - 1; i >= 0; i--) { + suffix_sums[i] = suffix_sums[i + 1] + aspects[i]; + } + + float max_avail_w = m->w.width - 2 * target_gappo; + float max_avail_h = m->w.height - 2 * target_gappo; + if (max_avail_w < 10) + max_avail_w = 10; + if (max_avail_h < 10) + max_avail_h = 10; + + int best_rows = 1; + float best_row_height = 0.0f; + best_items_per_row[0] = n; + + for (int R = 1; R <= n; R++) { + int start_idx = 0; + + for (int r = 0; r < R; r++) { + int rows_left = R - r; + + float S_rem = suffix_sums[start_idx]; + float target_sum = S_rem / rows_left; + + float current_sum = 0; + int count = 0; + + while (start_idx + count < n - (rows_left - 1)) { + float next_val = aspects[start_idx + count]; + if (rows_left == 1) { + current_sum += next_val; + count++; + continue; + } + + if (count > 0) { + float diff_without = fabs(current_sum - target_sum); + float diff_with = fabs(current_sum + next_val - target_sum); + if (diff_with > diff_without) { + break; + } + } + current_sum += next_val; + count++; + } + temp_items_per_row[r] = count; + start_idx += count; + } + + float min_h_max_w = 999999.0f; + start_idx = 0; + for (int r = 0; r < R; r++) { + float row_A_sum = suffix_sums[start_idx] - + suffix_sums[start_idx + temp_items_per_row[r]]; + start_idx += temp_items_per_row[r]; + + float gap_x_total = (temp_items_per_row[r] - 1) * target_gappi; + float w_avail = max_avail_w - gap_x_total; + if (w_avail < 1) + w_avail = 1; + + float h_limit = w_avail / row_A_sum; + if (h_limit < min_h_max_w) { + min_h_max_w = h_limit; + } + } + + float gap_y_total_temp = (R - 1) * target_gappi; + float h_avail = max_avail_h - gap_y_total_temp; + if (h_avail < 1) + h_avail = 1; + + float h_max_h = h_avail / R; + float final_h = min_h_max_w < h_max_h ? min_h_max_w : h_max_h; + + if (final_h > best_row_height) { + best_row_height = final_h; + best_rows = R; + for (int r = 0; r < R; r++) { + best_items_per_row[r] = temp_items_per_row[r]; + } + } + } + + int rows = best_rows; + float row_height = best_row_height; + + int current_render_idx = 0; + for (int r = 0; r < rows; r++) { + items_per_row[r] = best_items_per_row[r]; + A_sum[r] = suffix_sums[current_render_idx] - + suffix_sums[current_render_idx + items_per_row[r]]; + current_render_idx += items_per_row[r]; + } + + float gap_y_total = (rows - 1) * target_gappi; + float total_layout_height = rows * row_height + gap_y_total; + float start_y = m->w.y + (m->w.height - total_layout_height) / 2.0f; + + int current_idx = 0; + float current_y = start_y; + + for (int r = 0; r < rows; r++) { + float row_width = + row_height * A_sum[r] + (items_per_row[r] - 1) * target_gappi; + float current_x = m->w.x + (m->w.width - row_width) / 2.0f; + + for (int i = 0; i < items_per_row[r]; i++) { + Client *client = c_arr[current_idx]; + float aspect = aspects[current_idx]; + float client_width = row_height * aspect; + + struct wlr_box client_geom; + client_geom.x = (int)(current_x + 0.5f); + client_geom.y = (int)(current_y + 0.5f); + + float next_x = current_x + client_width; + client_geom.width = (int)(next_x + 0.5f) - client_geom.x; + + float next_y = current_y + row_height; + client_geom.height = (int)(next_y + 0.5f) - client_geom.y; + + resize(client, client_geom, 0); + + current_x = next_x + target_gappi; + current_idx++; + } + current_y += row_height + target_gappi; + } + + free(buffer); +} + +void overview_resize(Monitor *m) { + int32_t i, n; + int32_t cx, cy, cw, ch; + int32_t dx; + int32_t cols, rows, overcols; + Client *c = NULL; + + int32_t target_gappo = config.overviewgappo; + int32_t target_gappi = config.overviewgappi; + float single_width_ratio = 0.7; + float single_height_ratio = 0.8; + + n = m->visible_clients; + + if (n == 0) + return; + + if (n == 1) { + wl_list_for_each(c, &clients, link) { + if (c->mon != m) + continue; + if (VISIBLEON(c, m) && !c->isunglobal && !client_is_x11_popup(c)) { + cw = (m->w.width - 2 * target_gappo) * single_width_ratio; + ch = (m->w.height - 2 * target_gappo) * single_height_ratio; + c->geom.x = m->w.x + (m->w.width - cw) / 2; + c->geom.y = m->w.y + (m->w.height - ch) / 2; + c->geom.width = cw; + c->geom.height = ch; + resize(c, c->geom, 0); + return; + } + } + } + + if (n == 2) { + cw = (m->w.width - 2 * target_gappo - target_gappi) / 2; + ch = (m->w.height - 2 * target_gappo) * 0.65; + i = 0; + wl_list_for_each(c, &clients, link) { + if (c->mon != m) + continue; + if (VISIBLEON(c, m) && !c->isunglobal && !client_is_x11_popup(c)) { + if (i == 0) { + c->geom.x = m->w.x + target_gappo; + c->geom.y = m->w.y + (m->w.height - ch) / 2 + target_gappo; + } else if (i == 1) { + c->geom.x = m->w.x + cw + target_gappo + target_gappi; + c->geom.y = m->w.y + (m->w.height - ch) / 2 + target_gappo; + } + c->geom.width = cw; + c->geom.height = ch; + resize(c, c->geom, 0); + i++; + } + } + return; + } + + for (cols = 0; cols <= n / 2; cols++) { + if (cols * cols >= n) + break; + } + rows = (cols && (cols - 1) * cols >= n) ? cols - 1 : cols; + + ch = (m->w.height - 2 * target_gappo - (rows - 1) * target_gappi) / rows; + cw = (m->w.width - 2 * target_gappo - (cols - 1) * target_gappi) / cols; + + overcols = n % cols; + if (overcols) { + dx = (m->w.width - overcols * cw - (overcols - 1) * target_gappi) / 2 - + target_gappo; + } + + i = 0; + wl_list_for_each(c, &clients, link) { + if (c->mon != m) + continue; + if (VISIBLEON(c, m) && !c->isunglobal && !client_is_x11_popup(c)) { + cx = m->w.x + (i % cols) * (cw + target_gappi); + cy = m->w.y + (i / cols) * (ch + target_gappi); + if (overcols && i >= n - overcols) + cx += dx; + c->geom.x = cx + target_gappo; + c->geom.y = cy + target_gappo; + c->geom.width = cw; + c->geom.height = ch; + resize(c, c->geom, 0); + i++; + } + } +} + +void overview(Monitor *m) { + if (config.ov_no_resize) { + overview_scale(m); + } else { + overview_resize(m); + } +} \ No newline at end of file diff --git a/src/mango.c b/src/mango.c index 0ae7d3d9..7128c908 100644 --- a/src/mango.c +++ b/src/mango.c @@ -175,7 +175,7 @@ enum { }; /* EWMH atoms */ #endif enum { UP, DOWN, LEFT, RIGHT, UNDIR }; /* smartmovewin */ -enum { NONE, OPEN, MOVE, CLOSE, TAG, FOCUS, OPAFADEIN, OPAFADEOUT }; +enum { NONE, OPEN, MOVE, CLOSE, TAG, FOCUS, OPAFADEIN, OPAFADEOUT, OVERVIEW }; enum { UNFOLD, FOLD, INVALIDFOLD }; enum { PREV, NEXT }; enum { STATE_UNSPECIFIED = 0, STATE_ENABLED, STATE_DISABLED }; @@ -279,6 +279,7 @@ struct dwl_animation { bool tagouting; bool begin_fade_in; bool tag_from_rule; + bool overining; uint32_t time_started; uint32_t duration; struct wlr_box initial; @@ -320,6 +321,7 @@ struct Client { struct wlr_scene_rect *splitindicator[4]; struct wlr_scene_shadow *shadow; struct wlr_scene_tree *scene_surface; + struct wlr_scene_tree *overview_scene_surface; struct wl_list link; struct wl_list flink; struct wl_list fadeout_link; @@ -873,6 +875,7 @@ scroller_node_create(struct TagScrollerState *st, Client *c); static void update_scroller_state(Monitor *m); Client *scroll_get_stack_tail_client(Client *c); static DwindleNode *dwindle_find_leaf(DwindleNode *node, Client *c); +static void overview_backup_surface(Client *c); #include "data/static_keymap.h" #include "dispatch/bind_declare.h" @@ -1078,6 +1081,7 @@ static struct wl_event_source *sync_keymap; #include "layout/arrange.h" #include "layout/dwindle.h" #include "layout/horizontal.h" +#include "layout/overview.h" #include "layout/scroll.h" #include "layout/vertical.h" @@ -4279,6 +4283,7 @@ static void iter_xdg_scene_buffers(struct wlr_scene_buffer *buffer, int32_t sx, void init_client_properties(Client *c) { c->grid_col_per = 1.0f; c->grid_row_per = 1.0f; + c->overview_scene_surface = NULL; c->drop_direction = UNDIR; c->enable_drop_area_draw = false; c->isfocusing = false; @@ -4399,6 +4404,7 @@ mapnotify(struct wl_listener *listener, void *data) { // init client geom c->geom.width += 2 * c->bw; c->geom.height += 2 * c->bw; + c->overview_backup_geom = c->geom; /* Handle unmanaged clients first so we can return prior create borders */ @@ -4484,10 +4490,15 @@ mapnotify(struct wl_listener *listener, void *data) { // apply buffer effects of client wlr_scene_node_for_each_buffer(&c->scene_surface->node, iter_xdg_scene_buffers, c); + wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); // set border color setborder_color(c); + if (c->mon->isoverview && config.ov_no_resize) { + overview_backup_surface(c); + } + // make sure the animation is open type c->is_pending_open_animation = true; resize(c, c->geom, 0); @@ -4854,7 +4865,8 @@ void pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, if (config.sloppyfocus && !start_drag_window && c && time && c->scene && c->scene->node.enabled && !c->animation.tagining && - (surface != seat->pointer_state.focused_surface) && + (surface != seat->pointer_state.focused_surface || + (selmon && selmon->isoverview && selmon->sel != c)) && !client_is_unmanaged(c) && VISIBLEON(c, c->mon)) focusclient(c, 0); @@ -6061,6 +6073,23 @@ uint32_t want_restore_fullscreen(Client *target_client) { return 1; } +void overview_backup_surface(Client *c) { + struct wlr_box clip_box; + clip_box.x = 0; + clip_box.y = 0; + clip_box.width = c->geom.width - 2 * config.borderpx; + clip_box.height = c->geom.height - 2 * config.borderpx; + + c->overview_scene_surface = c->scene_surface; + wlr_scene_node_set_enabled(&c->scene_surface->node, true); + wlr_scene_node_set_position(&c->scene_surface->node, 0, 0); + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip_box); + c->scene_surface = + wlr_scene_tree_snapshot(&c->scene_surface->node, c->scene); + wlr_scene_node_set_enabled(&c->overview_scene_surface->node, false); + wlr_scene_node_set_enabled(&c->scene_surface->node, true); +} + // 普通视图切换到overview时保存窗口的旧状态 void overview_backup(Client *c) { c->overview_isfloatingbak = c->isfloating; @@ -6075,6 +6104,11 @@ void overview_backup(Client *c) { if (c->isfloating) { c->isfloating = 0; } + + if (config.ov_no_resize) { + overview_backup_surface(c); + } + if (c->isfullscreen || c->ismaximizescreen) { client_pending_fullscreen_state(c, 0); // 清除窗口全屏标志 client_pending_maximized_state(c, 0); @@ -6098,6 +6132,12 @@ void overview_restore(Client *c, const Arg *arg) { c->animation.tagining = false; c->is_restoring_from_ov = (arg->ui & c->tags & TAGMASK) == 0 ? true : false; + if (c->overview_scene_surface) { + wlr_scene_node_destroy(&c->scene_surface->node); + c->scene_surface = c->overview_scene_surface; + c->overview_scene_surface = NULL; + } + if (c->isfloating) { // XRaiseWindow(dpy, c->win); // 提升悬浮窗口到顶层 resize(c, c->overview_backup_geom, 0); From f328c2721cfa751450aee92d04c2908e9f9cc237 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 20 May 2026 12:37:37 +0800 Subject: [PATCH 215/328] fix: fix ov_no_resize in swallow --- src/animation/client.h | 4 ++++ src/mango.c | 20 ++++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/animation/client.h b/src/animation/client.h index 4d408484..4bb018f9 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -1064,6 +1064,10 @@ void init_fadeout_client(Client *c) { wlr_scene_node_set_enabled(&c->scene->node, true); client_set_border_color(c, config.bordercolor); + if (c->overview_scene_surface) { + wlr_scene_node_destroy(&c->overview_scene_surface->node); + c->overview_scene_surface = NULL; + } fadeout_client->scene = wlr_scene_tree_snapshot(&c->scene->node, layers[LyrFadeOut]); wlr_scene_node_set_enabled(&c->scene->node, false); diff --git a/src/mango.c b/src/mango.c index 7128c908..ea1c928c 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1212,10 +1212,21 @@ void swallow(Client *c, Client *w) { c->master_mfact_per = w->master_mfact_per; c->scroller_proportion = w->scroller_proportion; c->isglobal = w->isglobal; + c->overview_backup_geom = w->overview_backup_geom; /* 调整 w 的邻居指针,让它们指向 c */ c->stack_proportion = w->stack_proportion; + if (w->overview_scene_surface) { + wlr_scene_node_destroy(&w->scene_surface->node); + w->scene_surface = w->overview_scene_surface; + w->overview_scene_surface = NULL; + } + + if (c->mon && c->mon->isoverview) { + overview_backup_surface(c); + } + /* 全局链表替换 */ wl_list_insert(&w->link, &c->link); wl_list_insert(&w->flink, &c->flink); @@ -6074,11 +6085,16 @@ uint32_t want_restore_fullscreen(Client *target_client) { } void overview_backup_surface(Client *c) { + + if (c->overview_scene_surface) { + return; + } + struct wlr_box clip_box; clip_box.x = 0; clip_box.y = 0; - clip_box.width = c->geom.width - 2 * config.borderpx; - clip_box.height = c->geom.height - 2 * config.borderpx; + clip_box.width = c->overview_backup_geom.width - 2 * config.borderpx; + clip_box.height = c->overview_backup_geom.height - 2 * config.borderpx; c->overview_scene_surface = c->scene_surface; wlr_scene_node_set_enabled(&c->scene_surface->node, true); From 6aaf82eac218eef80349b581135b441e0e8c9fee Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 20 May 2026 17:23:54 +0800 Subject: [PATCH 216/328] opt: overview default config ov_no_resize to 1 ov_tab_mode to 1 enbale_hotarea to 0 --- assets/config.conf | 6 +++--- docs/window-management/overview.md | 6 +++--- src/config/parse_config.h | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/assets/config.conf b/assets/config.conf index bb1ecfde..7fff052d 100644 --- a/assets/config.conf +++ b/assets/config.conf @@ -76,9 +76,9 @@ dwindle_preserve_split=0 # Overview Setting hotarea_size=10 -enable_hotarea=1 -ov_tab_mode=0 -ov_no_resize=0 +enable_hotarea=0 +ov_tab_mode=1 +ov_no_resize=1 overviewgappi=5 overviewgappo=30 diff --git a/docs/window-management/overview.md b/docs/window-management/overview.md index 98f99c15..1ba416e9 100644 --- a/docs/window-management/overview.md +++ b/docs/window-management/overview.md @@ -8,12 +8,12 @@ description: Configure the overview mode for window navigation. | Setting | Type | Default | Description | | :--- | :--- | :--- | :--- | | `hotarea_size` | integer | `10` | Hot area size in pixels. | -| `enable_hotarea` | integer | `1` | Enable hot areas (0: disable, 1: enable). | +| `enable_hotarea` | integer | `0` | Enable hot areas (0: disable, 1: enable). | | `hotarea_corner` | integer | `2` | Hot area corner (0: top-left, 1: top-right, 2: bottom-left, 3: bottom-right). | -| `ov_tab_mode` | integer | `0` | Overview tab mode (0: disable, 1: enable). | +| `ov_tab_mode` | integer | `1` | Overview tab mode (0: disable, 1: enable). | | `overviewgappi` | integer | `5` | Inner gap in overview mode. | | `overviewgappo` | integer | `30` | Outer gap in overview mode. | -| `ov_no_resize` | integer | `0` | Disable window resizing in overview mode. | +| `ov_no_resize` | integer | `1` | Disable window resizing in overview mode. | ### Setting Descriptions diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 7c0000cb..21228d6c 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -3389,11 +3389,11 @@ void set_value_default() { config.numlockon = 0; config.capslock = 0; - config.ov_tab_mode = 0; - config.ov_no_resize = 0; + config.ov_tab_mode = 1; + config.ov_no_resize = 1; config.hotarea_size = 10; config.hotarea_corner = BOTTOM_LEFT; - config.enable_hotarea = 1; + config.enable_hotarea = 0; config.smartgaps = 0; config.sloppyfocus = 1; config.gappih = 5; From 806f07722449f39658113b8672a7dcde773759ca Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 20 May 2026 21:22:16 +0800 Subject: [PATCH 217/328] opt: optimize overview restore tagset --- 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 60ebf915..6710ba58 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1749,6 +1749,7 @@ int32_t toggleoverview(const Arg *arg) { } } } else { + selmon->tagset[selmon->seltags] = target; wl_list_for_each(c, &clients, link) { if (c && c->mon == selmon && !c->iskilling && !client_is_unmanaged(c) && !c->isunglobal && From 6bde7d344dcf316272a351af5c92e3c9a0a4633c Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 21 May 2026 08:29:14 +0800 Subject: [PATCH 218/328] fix: error buffer position for gtk app in overview --- src/mango.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/mango.c b/src/mango.c index ea1c928c..a9702dc7 100644 --- a/src/mango.c +++ b/src/mango.c @@ -6090,11 +6090,19 @@ void overview_backup_surface(Client *c) { return; } - struct wlr_box clip_box; - clip_box.x = 0; - clip_box.y = 0; - clip_box.width = c->overview_backup_geom.width - 2 * config.borderpx; - clip_box.height = c->overview_backup_geom.height - 2 * config.borderpx; + struct wlr_box geometry; + client_get_geometry(c, &geometry); + struct wlr_box clip_box = (struct wlr_box){ + .x = geometry.x, + .y = geometry.y, + .width = c->overview_backup_geom.width - 2 * config.borderpx, + .height = c->overview_backup_geom.height - 2 * config.borderpx, + }; + + if (client_is_x11(c)) { + clip_box.x = 0; + clip_box.y = 0; + } c->overview_scene_surface = c->scene_surface; wlr_scene_node_set_enabled(&c->scene_surface->node, true); From dc3e6d7395afe6f06be82d0aa9283fb25548945a Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 21 May 2026 10:57:22 +0800 Subject: [PATCH 219/328] opt: optimize arrange of overview --- src/layout/overview.h | 487 ++++++++++++++++++++++-------------------- 1 file changed, 259 insertions(+), 228 deletions(-) diff --git a/src/layout/overview.h b/src/layout/overview.h index 72544251..3bece798 100644 --- a/src/layout/overview.h +++ b/src/layout/overview.h @@ -1,292 +1,323 @@ + +typedef struct { + float x, y, w, h; +} OvPlacedRect; + +typedef struct { + float x, y; +} OvPoint; + +typedef struct { + Client *c; + float orig_w; + float orig_h; + float area; +} OvLayoutItem; + +static int compare_layout_items(const void *a, const void *b) { + float area_a = ((const OvLayoutItem *)a)->area; + float area_b = ((const OvLayoutItem *)b)->area; + if (area_a < area_b) + return 1; + if (area_a > area_b) + return -1; + return 0; +} + +static bool try_place(OvPlacedRect *placed, int placed_cnt, float w, float h, + float gap, float avail_w, float avail_h, + OvPlacedRect *out, OvPoint *cands, OvPoint *feas) { + int cand_cnt = 0; + cands[cand_cnt++] = (OvPoint){0.0f, 0.0f}; + + for (int i = 0; i < placed_cnt; i++) { + OvPlacedRect p = placed[i]; + cands[cand_cnt++] = (OvPoint){p.x + p.w + gap, p.y}; + cands[cand_cnt++] = (OvPoint){p.x, p.y + p.h + gap}; + cands[cand_cnt++] = (OvPoint){p.x + p.w + gap, p.y + p.h + gap}; + } + + int unique_cnt = 0; + for (int i = 0; i < cand_cnt; i++) { + bool dup = false; + for (int j = 0; j < unique_cnt; j++) { + if (fabs(cands[i].x - cands[j].x) < 0.5f && + fabs(cands[i].y - cands[j].y) < 0.5f) { + dup = true; + break; + } + } + if (!dup) + cands[unique_cnt++] = cands[i]; + } + cand_cnt = unique_cnt; + + int feas_cnt = 0; + for (int i = 0; i < cand_cnt; i++) { + float cx = cands[i].x; + float cy = cands[i].y; + + if (cx < 0 || cy < 0 || cx + w > avail_w || cy + h > avail_h) + continue; + + bool overlap = false; + for (int j = 0; j < placed_cnt; j++) { + OvPlacedRect p = placed[j]; + if (!(cx + w + gap <= p.x || cx >= p.x + p.w + gap || + cy + h + gap <= p.y || cy >= p.y + p.h + gap)) { + overlap = true; + break; + } + } + if (!overlap) { + feas[feas_cnt++] = (OvPoint){cx, cy}; + } + } + + if (feas_cnt == 0) + return false; + + int best = 0; + for (int i = 1; i < feas_cnt; i++) { + if (feas[i].y < feas[best].y || + (fabs(feas[i].y - feas[best].y) < 0.5f && + feas[i].x < feas[best].x)) { + best = i; + } + } + + out->x = feas[best].x; + out->y = feas[best].y; + out->w = w; + out->h = h; + return true; +} + void overview_scale(Monitor *m) { int32_t target_gappo = config.overviewgappo; int32_t target_gappi = config.overviewgappi; int orig_n = m->visible_clients; - if (orig_n == 0) return; - size_t sz_c_arr = orig_n * sizeof(Client *); - size_t sz_aspects = orig_n * sizeof(float); - size_t sz_suffix_sums = (orig_n + 1) * sizeof(float); - size_t sz_best_items = orig_n * sizeof(int); - size_t sz_temp_items = orig_n * sizeof(int); - size_t sz_items = orig_n * sizeof(int); - size_t sz_A_sum = orig_n * sizeof(float); - - size_t total_size = sz_c_arr + sz_aspects + sz_suffix_sums + sz_best_items + - sz_temp_items + sz_items + sz_A_sum; - - void *buffer = malloc(total_size); - if (!buffer) { + OvLayoutItem *items = calloc(orig_n, sizeof(OvLayoutItem)); + if (!items) return; - } - - Client **c_arr = (Client **)buffer; - float *aspects = (float *)((char *)buffer + sz_c_arr); - float *suffix_sums = (float *)((char *)aspects + sz_aspects); - int *best_items_per_row = (int *)((char *)suffix_sums + sz_suffix_sums); - int *temp_items_per_row = - (int *)((char *)best_items_per_row + sz_best_items); - int *items_per_row = (int *)((char *)temp_items_per_row + sz_temp_items); - float *A_sum = (float *)((char *)items_per_row + sz_items); - - int actual_n = 0; - Client *c = NULL; + int n = 0; + Client *c; wl_list_for_each(c, &clients, link) { if (c->mon != m) continue; if (VISIBLEON(c, m) && !c->isunglobal && !client_is_x11_popup(c)) { - - c_arr[actual_n] = c; - float aspect = 1.0f; - if (c->overview_backup_geom.height > 0 && - c->overview_backup_geom.width > 0) { - aspect = (float)c->overview_backup_geom.width / - c->overview_backup_geom.height; + items[n].c = c; + float w = c->overview_backup_geom.width; + float h = c->overview_backup_geom.height; + if (w <= 0 || h <= 0) { + w = 100.0f; + h = 100.0f; } - if (aspect < 0.2f) - aspect = 0.2f; - if (aspect > 5.0f) - aspect = 5.0f; - - aspects[actual_n] = aspect; - actual_n++; + items[n].orig_w = w; + items[n].orig_h = h; + items[n].area = w * h; + n++; } } - int n = actual_n; if (n == 0) { - free(buffer); + free(items); return; } - suffix_sums[n] = 0.0f; - for (int i = n - 1; i >= 0; i--) { - suffix_sums[i] = suffix_sums[i + 1] + aspects[i]; + qsort(items, n, sizeof(OvLayoutItem), compare_layout_items); + + float max_avail_w = fmaxf(1.0f, m->w.width - 2 * target_gappo); + float max_avail_h = fmaxf(1.0f, m->w.height - 2 * target_gappo); + + int max_points = 1 + 3 * n; + OvPlacedRect *placed = calloc(n, sizeof(OvPlacedRect)); + OvPoint *cands = calloc(max_points, sizeof(OvPoint)); + OvPoint *feas = calloc(max_points, sizeof(OvPoint)); + + if (!placed || !cands || !feas) { + free(items); + free(placed); + free(cands); + free(feas); + return; } - float max_avail_w = m->w.width - 2 * target_gappo; - float max_avail_h = m->w.height - 2 * target_gappo; - if (max_avail_w < 10) - max_avail_w = 10; - if (max_avail_h < 10) - max_avail_h = 10; + float low = 0.0f, high = 1.0f, best_s = 0.0f; + for (int iter = 0; iter < 50; iter++) { + float mid = (low + high) / 2.0f; + bool ok = true; + int placed_cnt = 0; - int best_rows = 1; - float best_row_height = 0.0f; - best_items_per_row[0] = n; - - for (int R = 1; R <= n; R++) { - int start_idx = 0; - - for (int r = 0; r < R; r++) { - int rows_left = R - r; - - float S_rem = suffix_sums[start_idx]; - float target_sum = S_rem / rows_left; - - float current_sum = 0; - int count = 0; - - while (start_idx + count < n - (rows_left - 1)) { - float next_val = aspects[start_idx + count]; - if (rows_left == 1) { - current_sum += next_val; - count++; - continue; - } - - if (count > 0) { - float diff_without = fabs(current_sum - target_sum); - float diff_with = fabs(current_sum + next_val - target_sum); - if (diff_with > diff_without) { - break; - } - } - current_sum += next_val; - count++; + for (int k = 0; k < n; k++) { + float w = items[k].orig_w * mid; + float h = items[k].orig_h * mid; + OvPlacedRect out; + if (!try_place(placed, placed_cnt, w, h, (float)target_gappi, + max_avail_w, max_avail_h, &out, cands, feas)) { + ok = false; + break; } - temp_items_per_row[r] = count; - start_idx += count; + placed[placed_cnt++] = out; } - float min_h_max_w = 999999.0f; - start_idx = 0; - for (int r = 0; r < R; r++) { - float row_A_sum = suffix_sums[start_idx] - - suffix_sums[start_idx + temp_items_per_row[r]]; - start_idx += temp_items_per_row[r]; - - float gap_x_total = (temp_items_per_row[r] - 1) * target_gappi; - float w_avail = max_avail_w - gap_x_total; - if (w_avail < 1) - w_avail = 1; - - float h_limit = w_avail / row_A_sum; - if (h_limit < min_h_max_w) { - min_h_max_w = h_limit; - } - } - - float gap_y_total_temp = (R - 1) * target_gappi; - float h_avail = max_avail_h - gap_y_total_temp; - if (h_avail < 1) - h_avail = 1; - - float h_max_h = h_avail / R; - float final_h = min_h_max_w < h_max_h ? min_h_max_w : h_max_h; - - if (final_h > best_row_height) { - best_row_height = final_h; - best_rows = R; - for (int r = 0; r < R; r++) { - best_items_per_row[r] = temp_items_per_row[r]; - } + if (ok) { + best_s = mid; + low = mid; + } else { + high = mid; } } - int rows = best_rows; - float row_height = best_row_height; + if (best_s > 0.0f) { + float box_w = 0, box_h = 0; + int placed_cnt = 0; - int current_render_idx = 0; - for (int r = 0; r < rows; r++) { - items_per_row[r] = best_items_per_row[r]; - A_sum[r] = suffix_sums[current_render_idx] - - suffix_sums[current_render_idx + items_per_row[r]]; - current_render_idx += items_per_row[r]; - } + for (int k = 0; k < n; k++) { + float w = items[k].orig_w * best_s; + float h = items[k].orig_h * best_s; + OvPlacedRect out; + try_place(placed, placed_cnt, w, h, (float)target_gappi, + max_avail_w, max_avail_h, &out, cands, feas); + placed[placed_cnt++] = out; - float gap_y_total = (rows - 1) * target_gappi; - float total_layout_height = rows * row_height + gap_y_total; - float start_y = m->w.y + (m->w.height - total_layout_height) / 2.0f; - - int current_idx = 0; - float current_y = start_y; - - for (int r = 0; r < rows; r++) { - float row_width = - row_height * A_sum[r] + (items_per_row[r] - 1) * target_gappi; - float current_x = m->w.x + (m->w.width - row_width) / 2.0f; - - for (int i = 0; i < items_per_row[r]; i++) { - Client *client = c_arr[current_idx]; - float aspect = aspects[current_idx]; - float client_width = row_height * aspect; - - struct wlr_box client_geom; - client_geom.x = (int)(current_x + 0.5f); - client_geom.y = (int)(current_y + 0.5f); - - float next_x = current_x + client_width; - client_geom.width = (int)(next_x + 0.5f) - client_geom.x; - - float next_y = current_y + row_height; - client_geom.height = (int)(next_y + 0.5f) - client_geom.y; - - resize(client, client_geom, 0); - - current_x = next_x + target_gappi; - current_idx++; + float r = out.x + w; + float b = out.y + h; + if (r > box_w) + box_w = r; + if (b > box_h) + box_h = b; + } + + float dx = (max_avail_w - box_w) / 2.0f; + float dy = (max_avail_h - box_h) / 2.0f; + float base_x = m->w.x + target_gappo + dx; + float base_y = m->w.y + target_gappo + dy; + + for (int k = 0; k < n; k++) { + Client *cl = items[k].c; + struct wlr_box geom; + geom.x = (int)(base_x + placed[k].x + 0.5f); + geom.y = (int)(base_y + placed[k].y + 0.5f); + float w = items[k].orig_w * best_s; + float h = items[k].orig_h * best_s; + geom.width = (int)(geom.x + w + 0.5f) - geom.x; + geom.height = (int)(geom.y + h + 0.5f) - geom.y; + resize(cl, geom, 0); } - current_y += row_height + target_gappi; } - free(buffer); + free(items); + free(placed); + free(cands); + free(feas); } void overview_resize(Monitor *m) { - int32_t i, n; - int32_t cx, cy, cw, ch; - int32_t dx; - int32_t cols, rows, overcols; - Client *c = NULL; - int32_t target_gappo = config.overviewgappo; int32_t target_gappi = config.overviewgappi; - float single_width_ratio = 0.7; - float single_height_ratio = 0.8; + float single_width_ratio = 0.7f; + float single_height_ratio = 0.8f; - n = m->visible_clients; - - if (n == 0) + int orig_n = m->visible_clients; + if (orig_n == 0) return; - if (n == 1) { - wl_list_for_each(c, &clients, link) { - if (c->mon != m) - continue; - if (VISIBLEON(c, m) && !c->isunglobal && !client_is_x11_popup(c)) { - cw = (m->w.width - 2 * target_gappo) * single_width_ratio; - ch = (m->w.height - 2 * target_gappo) * single_height_ratio; - c->geom.x = m->w.x + (m->w.width - cw) / 2; - c->geom.y = m->w.y + (m->w.height - ch) / 2; - c->geom.width = cw; - c->geom.height = ch; - resize(c, c->geom, 0); - return; - } + Client **c_arr = malloc(orig_n * sizeof(Client *)); + if (!c_arr) + return; + + int n = 0; + Client *c; + wl_list_for_each(c, &clients, link) { + if (c->mon != m) + continue; + if (VISIBLEON(c, m) && !c->isunglobal && !client_is_x11_popup(c)) { + c_arr[n++] = c; } } + if (n == 0) { + free(c_arr); + return; + } + + if (n == 1) { + int32_t cw = (m->w.width - 2 * target_gappo) * single_width_ratio; + int32_t ch = (m->w.height - 2 * target_gappo) * single_height_ratio; + c_arr[0]->geom.x = m->w.x + (m->w.width - cw) / 2; + c_arr[0]->geom.y = m->w.y + (m->w.height - ch) / 2; + c_arr[0]->geom.width = cw; + c_arr[0]->geom.height = ch; + resize(c_arr[0], c_arr[0]->geom, 0); + free(c_arr); + return; + } + if (n == 2) { - cw = (m->w.width - 2 * target_gappo - target_gappi) / 2; - ch = (m->w.height - 2 * target_gappo) * 0.65; - i = 0; - wl_list_for_each(c, &clients, link) { - if (c->mon != m) - continue; - if (VISIBLEON(c, m) && !c->isunglobal && !client_is_x11_popup(c)) { - if (i == 0) { - c->geom.x = m->w.x + target_gappo; - c->geom.y = m->w.y + (m->w.height - ch) / 2 + target_gappo; - } else if (i == 1) { - c->geom.x = m->w.x + cw + target_gappo + target_gappi; - c->geom.y = m->w.y + (m->w.height - ch) / 2 + target_gappo; - } - c->geom.width = cw; - c->geom.height = ch; - resize(c, c->geom, 0); - i++; - } - } + int32_t cw = (m->w.width - 2 * target_gappo - target_gappi) / 2; + int32_t ch = (m->w.height - 2 * target_gappo) * 0.65f; + + c_arr[0]->geom.x = m->w.x + target_gappo; + c_arr[0]->geom.y = m->w.y + (m->w.height - ch) / 2 + target_gappo; + c_arr[0]->geom.width = cw; + c_arr[0]->geom.height = ch; + resize(c_arr[0], c_arr[0]->geom, 0); + + c_arr[1]->geom.x = m->w.x + cw + target_gappo + target_gappi; + c_arr[1]->geom.y = m->w.y + (m->w.height - ch) / 2 + target_gappo; + c_arr[1]->geom.width = cw; + c_arr[1]->geom.height = ch; + resize(c_arr[1], c_arr[1]->geom, 0); + + free(c_arr); return; } - for (cols = 0; cols <= n / 2; cols++) { - if (cols * cols >= n) - break; + int32_t cols = 1; + while (cols * cols < n) { + cols++; } - rows = (cols && (cols - 1) * cols >= n) ? cols - 1 : cols; + int32_t rows = (n + cols - 1) / cols; - ch = (m->w.height - 2 * target_gappo - (rows - 1) * target_gappi) / rows; - cw = (m->w.width - 2 * target_gappo - (cols - 1) * target_gappi) / cols; + int32_t ch = + (m->w.height - 2 * target_gappo - (rows - 1) * target_gappi) / rows; + int32_t cw = + (m->w.width - 2 * target_gappo - (cols - 1) * target_gappi) / cols; - overcols = n % cols; + if (ch < 1) + ch = 1; + if (cw < 1) + cw = 1; + + int32_t overcols = n % cols; + int32_t dx = 0; if (overcols) { dx = (m->w.width - overcols * cw - (overcols - 1) * target_gappi) / 2 - target_gappo; } - i = 0; - wl_list_for_each(c, &clients, link) { - if (c->mon != m) - continue; - if (VISIBLEON(c, m) && !c->isunglobal && !client_is_x11_popup(c)) { - cx = m->w.x + (i % cols) * (cw + target_gappi); - cy = m->w.y + (i / cols) * (ch + target_gappi); - if (overcols && i >= n - overcols) - cx += dx; - c->geom.x = cx + target_gappo; - c->geom.y = cy + target_gappo; - c->geom.width = cw; - c->geom.height = ch; - resize(c, c->geom, 0); - i++; + for (int i = 0; i < n; i++) { + int32_t cx = m->w.x + (i % cols) * (cw + target_gappi); + int32_t cy = m->w.y + (i / cols) * (ch + target_gappi); + + if (overcols && i >= n - overcols) { + cx += dx; } + + c_arr[i]->geom.x = cx + target_gappo; + c_arr[i]->geom.y = cy + target_gappo; + c_arr[i]->geom.width = cw; + c_arr[i]->geom.height = ch; + resize(c_arr[i], c_arr[i]->geom, 0); } + + free(c_arr); } void overview(Monitor *m) { From d28cf7b4d2b62bafd463142046d2a80047216d85 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 21 May 2026 12:25:25 +0800 Subject: [PATCH 220/328] opt: center the last window in free space when arrange overview --- src/layout/overview.h | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/src/layout/overview.h b/src/layout/overview.h index 3bece798..88fc4258 100644 --- a/src/layout/overview.h +++ b/src/layout/overview.h @@ -175,7 +175,6 @@ void overview_scale(Monitor *m) { } if (best_s > 0.0f) { - float box_w = 0, box_h = 0; int placed_cnt = 0; for (int k = 0; k < n; k++) { @@ -185,9 +184,43 @@ void overview_scale(Monitor *m) { try_place(placed, placed_cnt, w, h, (float)target_gappi, max_avail_w, max_avail_h, &out, cands, feas); placed[placed_cnt++] = out; + } - float r = out.x + w; - float b = out.y + h; + if (n > 1) { + float grid_box_w = 0; + for (int k = 0; k < n - 1; k++) { + float r = placed[k].x + placed[k].w; + if (r > grid_box_w) + grid_box_w = r; + } + + OvPlacedRect *last = &placed[n - 1]; + float max_x = grid_box_w - last->w; + + if (max_x > last->x) { + for (int k = 0; k < n - 1; k++) { + OvPlacedRect p = placed[k]; + if (!(last->y + last->h + target_gappi <= p.y || + last->y >= p.y + p.h + target_gappi)) { + if (p.x > last->x) { + float limit = p.x - target_gappi - last->w; + if (limit < max_x) { + max_x = limit; + } + } + } + } + + if (max_x > last->x) { + last->x += (max_x - last->x) / 2.0f; + } + } + } + + float box_w = 0, box_h = 0; + for (int k = 0; k < n; k++) { + float r = placed[k].x + placed[k].w; + float b = placed[k].y + placed[k].h; if (r > box_w) box_w = r; if (b > box_h) From 9f31a8be568b36957058ffca32eee9fdebdd7edc Mon Sep 17 00:00:00 2001 From: werapi <sokneip@tuta.io> Date: Thu, 21 May 2026 13:35:58 +0800 Subject: [PATCH 221/328] feat: tablet support --- src/config/parse_config.h | 13 ++ src/ext-protocol/all.h | 3 +- src/ext-protocol/tablet.h | 439 ++++++++++++++++++++++++++++++++++++++ src/mango.c | 47 +++- 4 files changed, 491 insertions(+), 11 deletions(-) create mode 100644 src/ext-protocol/tablet.h diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 21228d6c..4beb71da 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -289,6 +289,9 @@ typedef struct { double mouse_accel_speed; double axis_scroll_factor; + /* tablet */ + char *tablet_map_to_mon; + /* Trackpad */ int32_t trackpad_natural_scrolling; uint32_t trackpad_accel_profile; @@ -1728,6 +1731,10 @@ bool parse_option(Config *config, char *key, char *value) { config->button_map = atoi(value); } else if (strcmp(key, "axis_scroll_factor") == 0) { config->axis_scroll_factor = atof(value); + } else if (strcmp(key, "tablet_map_to_mon") == 0) { + if (config->tablet_map_to_mon) + free(config->tablet_map_to_mon); + config->tablet_map_to_mon = strdup(value); } else if (strcmp(key, "trackpad_scroll_factor") == 0) { config->trackpad_scroll_factor = atof(value); } else if (strcmp(key, "gappih") == 0) { @@ -3169,6 +3176,11 @@ void free_config(void) { config.cursor_theme = NULL; } + if (config.tablet_map_to_mon) { + free(config.tablet_map_to_mon); + config.tablet_map_to_mon = NULL; + } + // 释放 circle_layout free_circle_layout(&config); @@ -3635,6 +3647,7 @@ bool parse_config(void) { config.tag_rules = NULL; config.tag_rules_count = 0; config.cursor_theme = NULL; + config.tablet_map_to_mon = NULL; strcpy(config.keymode, "default"); create_config_keymap(); diff --git a/src/ext-protocol/all.h b/src/ext-protocol/all.h index bc690fe7..6fdb0744 100644 --- a/src/ext-protocol/all.h +++ b/src/ext-protocol/all.h @@ -2,4 +2,5 @@ #include "ext-workspace.h" #include "foreign-toplevel.h" #include "tearing.h" -#include "text-input.h" \ No newline at end of file +#include "text-input.h" +#include "tablet.h" diff --git a/src/ext-protocol/tablet.h b/src/ext-protocol/tablet.h new file mode 100644 index 00000000..6aa7ae86 --- /dev/null +++ b/src/ext-protocol/tablet.h @@ -0,0 +1,439 @@ +#include <wlr/types/wlr_tablet_pad.h> +#include <wlr/types/wlr_tablet_tool.h> +#include <wlr/types/wlr_tablet_v2.h> + +static void createtablet(struct wlr_input_device *device); +static void destroytablet(struct wl_listener *listener, void *data); +static void createtabletpad(struct wlr_input_device *device); +static void destroytabletpad(struct wl_listener *listener, void *data); +static void tabletpadtabletdestroy(struct wl_listener *listener, void *data); +static void tabletpadattach(struct wl_listener *listener, void *data); +static void destroytabletsurfacenotify(struct wl_listener *listener, + void *data); +static void destroytablettool(struct wl_listener *listener, void *data); +static void tablettoolsetcursor(struct wl_listener *listener, void *data); + +static void tablettoolproximity(struct wl_listener *listener, void *data); +static void tablettoolaxis(struct wl_listener *listener, void *data); +static void tablettoolbutton(struct wl_listener *listener, void *data); +static void tablettooltip(struct wl_listener *listener, void *data); +static struct wlr_tablet_manager_v2 *tablet_mgr; + +struct Tablet { + struct wlr_tablet_v2_tablet *tablet_v2; + struct wl_listener destroy; + struct wl_list link; +}; +static struct wl_list tablets; + +struct TabletTool { + struct wlr_tablet_v2_tablet_tool *tool_v2; + struct Tablet *tablet; + struct wlr_surface *curr_surface; + struct wl_listener destroy; + struct wl_listener surface_destroy; + struct wl_listener set_cursor; + double tilt_x, tilt_y; +}; + +struct TabletPad { + struct wlr_tablet_v2_tablet_pad *pad_v2; + struct Tablet *tablet; + struct wl_listener tablet_destroy; + struct wl_listener attach; + struct wl_listener destroy; + struct wl_list link; +}; +static struct wl_list tablet_pads; + +static void attach_tablet_pad(struct TabletPad *tablet_pad, + struct Tablet *tablet); +static void tablettoolmotion(struct TabletTool *tool, bool change_x, + bool change_y, double x, double y, double dx, + double dy); + +static struct wl_listener tablet_tool_axis = {.notify = tablettoolaxis}; +static struct wl_listener tablet_tool_button = {.notify = tablettoolbutton}; +static struct wl_listener tablet_tool_proximity = {.notify = + tablettoolproximity}; +static struct wl_listener tablet_tool_tip = {.notify = tablettooltip}; + +void createtablet(struct wlr_input_device *device) { + struct Tablet *tablet = calloc(1, sizeof(struct Tablet)); + if (!tablet) { + wlr_log(WLR_ERROR, "could not allocate tablet"); + return; + } + + struct libinput_device *device_handle = NULL; + if (!wlr_input_device_is_libinput(device) || + !(device_handle = wlr_libinput_get_device_handle(device))) { + free(tablet); + return; + } + + tablet->tablet_v2 = wlr_tablet_create(tablet_mgr, seat, device); + tablet->tablet_v2->wlr_tablet->data = tablet; + tablet->destroy.notify = destroytablet; + wl_signal_add(&tablet->tablet_v2->wlr_device->events.destroy, + &tablet->destroy); + if (libinput_device_config_send_events_get_modes(device_handle)) { + libinput_device_config_send_events_set_mode(device_handle, + config.send_events_mode); + wlr_cursor_attach_input_device(cursor, device); + } + + wl_list_insert(&tablets, &tablet->link); + + /* Search for a sibling tablet pad */ + struct libinput_device_group *group = libinput_device_get_device_group( + wlr_libinput_get_device_handle(device)); + struct TabletPad *tablet_pad; + wl_list_for_each(tablet_pad, &tablet_pads, link) { + struct wlr_input_device *pad_device = tablet_pad->pad_v2->wlr_device; + if (!wlr_input_device_is_libinput(pad_device)) { + continue; + } + + struct libinput_device_group *pad_group = + libinput_device_get_device_group( + wlr_libinput_get_device_handle(pad_device)); + + if (pad_group == group) { + attach_tablet_pad(tablet_pad, tablet); + break; + } + } +} + +void destroytablet(struct wl_listener *listener, void *data) { + struct Tablet *tablet = wl_container_of(listener, tablet, destroy); + + wl_list_remove(&listener->link); + wl_list_remove(&tablet->link); + free(tablet); +} + +void tabletpadtabletdestroy(struct wl_listener *listener, void *data) { + struct TabletPad *tablet_pad = + wl_container_of(listener, tablet_pad, tablet_destroy); + + tablet_pad->tablet = NULL; + + wl_list_remove(&tablet_pad->tablet_destroy.link); + wl_list_init(&tablet_pad->tablet_destroy.link); +} + +void attach_tablet_pad(struct TabletPad *tablet_pad, struct Tablet *tablet) { + tablet_pad->tablet = tablet; + + wl_list_remove(&tablet_pad->tablet_destroy.link); + tablet_pad->tablet_destroy.notify = tabletpadtabletdestroy; + wl_signal_add(&tablet->tablet_v2->wlr_device->events.destroy, + &tablet_pad->tablet_destroy); +} + +void tabletpadattach(struct wl_listener *listener, void *data) { + struct TabletPad *tablet_pad = + wl_container_of(listener, tablet_pad, attach); + struct wlr_tablet_tool *wlr_tool = data; + struct TabletTool *tool = wlr_tool->data; + + if (!tool) { + return; + } + + attach_tablet_pad(tablet_pad, tool->tablet); +} + +void createtabletpad(struct wlr_input_device *device) { + struct TabletPad *tablet_pad = calloc(1, sizeof(struct TabletPad)); + if (!tablet_pad) { + wlr_log(WLR_ERROR, "could not allocate tablet_pad"); + return; + } + tablet_pad->pad_v2 = wlr_tablet_pad_create(tablet_mgr, seat, device); + tablet_pad->destroy.notify = destroytabletpad; + tablet_pad->attach.notify = tabletpadattach; + wl_list_init(&tablet_pad->tablet_destroy.link); + wl_signal_add(&tablet_pad->pad_v2->wlr_device->events.destroy, + &tablet_pad->destroy); + wl_signal_add(&tablet_pad->pad_v2->wlr_pad->events.attach_tablet, + &tablet_pad->attach); + wl_list_insert(&tablet_pads, &tablet_pad->link); + + /* Search for a sibling tablet */ + if (!wlr_input_device_is_libinput(tablet_pad->pad_v2->wlr_device)) { + /* We can only do this on libinput devices */ + return; + } + + struct libinput_device_group *group = libinput_device_get_device_group( + wlr_libinput_get_device_handle(tablet_pad->pad_v2->wlr_device)); + struct Tablet *tablet; + wl_list_for_each(tablet, &tablets, link) { + struct wlr_input_device *tablet_device = tablet->tablet_v2->wlr_device; + if (!wlr_input_device_is_libinput(tablet_device)) { + continue; + } + + struct libinput_device_group *tablet_group = + libinput_device_get_device_group( + wlr_libinput_get_device_handle(tablet_device)); + + if (tablet_group == group) { + attach_tablet_pad(tablet_pad, tablet); + break; + } + } +} + +void destroytabletpad(struct wl_listener *listener, void *data) { + struct TabletPad *tablet_pad = + wl_container_of(listener, tablet_pad, destroy); + + wl_list_remove(&listener->link); + wl_list_remove(&tablet_pad->link); + wl_list_remove(&tablet_pad->tablet_destroy.link); + wl_list_remove(&tablet_pad->attach.link); + free(tablet_pad); +} + +void destroytabletsurfacenotify(struct wl_listener *listener, void *data) { + struct TabletTool *tool = wl_container_of(listener, tool, surface_destroy); + wl_list_remove(&tool->surface_destroy.link); + tool->curr_surface = NULL; +} + +void destroytablettool(struct wl_listener *listener, void *data) { + struct TabletTool *tool = wl_container_of(listener, tool, destroy); + + if (tool->curr_surface) + wl_list_remove(&tool->surface_destroy.link); + wl_list_remove(&tool->set_cursor.link); + wl_list_remove(&listener->link); + free(tool); +} + +static void tablettoolsetcursor(struct wl_listener *listener, void *data) { + struct TabletTool *tool = wl_container_of(listener, tool, set_cursor); + struct wlr_tablet_v2_event_cursor *event = data; + + struct wlr_seat_client *focused_client = NULL; + if (tool->tool_v2->focused_surface) { + focused_client = wlr_seat_client_for_wl_client( + seat, + wl_resource_get_client(tool->tool_v2->focused_surface->resource)); + } + + if (focused_client != event->seat_client) + return; + + wlr_cursor_set_surface(cursor, event->surface, event->hotspot_x, + event->hotspot_y); +} + +void tablettoolmotion(struct TabletTool *tool, bool change_x, bool change_y, + double x, double y, double dx, double dy) { + struct wlr_surface *surface = NULL; + Client *c = NULL, *w = NULL; + LayerSurface *l = NULL; + struct Tablet *tablet = tool->tablet; + struct TabletPad *tablet_pad; + double sx, sy; + + if (!change_x && !change_y) + return; + + // TODO: apply constraints + switch (tool->tool_v2->wlr_tool->type) { + case WLR_TABLET_TOOL_TYPE_LENS: + case WLR_TABLET_TOOL_TYPE_MOUSE: + wlr_cursor_move(cursor, tablet->tablet_v2->wlr_device, dx, dy); + break; + default: + wlr_cursor_warp_absolute(cursor, tablet->tablet_v2->wlr_device, + change_x ? x : NAN, change_y ? y : NAN); + break; + } + + motionnotify(0, NULL, 0, 0, 0, 0); + + if (config.sloppyfocus) + selmon = xytomon(cursor->x, cursor->y); + + xytonode(cursor->x, cursor->y, &surface, &c, NULL, &sx, &sy); + if (cursor_mode == CurPressed && !seat->drag && + surface != seat->pointer_state.focused_surface && + toplevel_from_wlr_surface(seat->pointer_state.focused_surface, &w, + &l) >= 0) { + c = w; + surface = seat->pointer_state.focused_surface; + sx = cursor->x - (l ? l->scene->node.x : w->geom.x); + sy = cursor->y - (l ? l->scene->node.y : w->geom.y); + } + + if (config.sloppyfocus && c && c->scene->node.enabled && + (surface != seat->pointer_state.focused_surface || + (selmon && selmon->sel && c != selmon->sel)) && + !client_is_unmanaged(c)) + focusclient(c, 0); + + if (surface && !wlr_surface_accepts_tablet_v2(surface, tablet->tablet_v2)) + surface = NULL; + + if (surface != tool->curr_surface) { + if (tool->curr_surface) { + // TODO: wait until all buttons released before leaving + wlr_tablet_v2_tablet_tool_notify_proximity_out(tool->tool_v2); + wl_list_for_each(tablet_pad, &tablet_pads, link) { + if (tablet_pad->tablet && tablet_pad->tablet == tablet) + wlr_tablet_v2_tablet_pad_notify_leave(tablet_pad->pad_v2, + tool->curr_surface); + } + wl_list_remove(&tool->surface_destroy.link); + } + if (surface) { + wl_list_for_each(tablet_pad, &tablet_pads, link) { + if (tablet_pad->tablet && tablet_pad->tablet == tablet) + wlr_tablet_v2_tablet_pad_notify_enter( + tablet_pad->pad_v2, tablet->tablet_v2, surface); + } + wlr_tablet_v2_tablet_tool_notify_proximity_in( + tool->tool_v2, tablet->tablet_v2, surface); + wl_signal_add(&surface->events.destroy, &tool->surface_destroy); + } + tool->curr_surface = surface; + } + + if (surface) + wlr_tablet_v2_tablet_tool_notify_motion(tool->tool_v2, sx, sy); + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + handlecursoractivity(); +} + +void tablettoolproximity(struct wl_listener *listener, void *data) { + struct wlr_tablet_tool_proximity_event *event = data; + struct wlr_tablet_tool *wlr_tool = event->tool; + struct TabletTool *tool = wlr_tool->data; + Monitor *m_iter; + + if (!tool) { + tool = calloc(1, sizeof(struct TabletTool)); + if (!tool) { + wlr_log(WLR_ERROR, "could not allocate tablet_tool"); + return; + } + tool->tool_v2 = wlr_tablet_tool_create(tablet_mgr, seat, wlr_tool); + tool->surface_destroy.notify = destroytabletsurfacenotify; + tool->destroy.notify = destroytablettool; + tool->set_cursor.notify = tablettoolsetcursor; + tool->tablet = event->tablet->data; + wlr_tool->data = tool; + wl_signal_add(&tool->tool_v2->wlr_tool->events.destroy, &tool->destroy); + wl_signal_add(&tool->tool_v2->events.set_cursor, &tool->set_cursor); + + if (config.tablet_map_to_mon) { + wl_list_for_each(m_iter, &mons, link) { + if (match_monitor_spec(config.tablet_map_to_mon, m_iter)) { + wlr_log(WLR_DEBUG, "Mapping tablet %s to output %s", + event->tablet->base.name, config.tablet_map_to_mon); + wlr_cursor_map_input_to_output(cursor, &event->tablet->base, + m_iter->wlr_output); + break; + } + } + } + } + + switch (event->state) { + case WLR_TABLET_TOOL_PROXIMITY_OUT: + wlr_tablet_v2_tablet_tool_notify_proximity_out(tool->tool_v2); + if (tool->curr_surface) + wl_list_remove(&tool->surface_destroy.link); + tool->curr_surface = NULL; + break; + case WLR_TABLET_TOOL_PROXIMITY_IN: + tablettoolmotion(tool, true, true, event->x, event->y, 0, 0); + break; + } +} + +void tablettoolaxis(struct wl_listener *listener, void *data) { + struct wlr_tablet_tool_axis_event *event = data; + struct TabletTool *tool = event->tool->data; + if (!tool) + return; + + tablettoolmotion(tool, event->updated_axes & WLR_TABLET_TOOL_AXIS_X, + event->updated_axes & WLR_TABLET_TOOL_AXIS_Y, event->x, + event->y, event->dx, event->dy); + + if (event->updated_axes & WLR_TABLET_TOOL_AXIS_PRESSURE) + wlr_tablet_v2_tablet_tool_notify_pressure(tool->tool_v2, + event->pressure); + if (event->updated_axes & WLR_TABLET_TOOL_AXIS_DISTANCE) + wlr_tablet_v2_tablet_tool_notify_distance(tool->tool_v2, + event->distance); + if (event->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_X) + tool->tilt_x = event->tilt_x; + if (event->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_Y) + tool->tilt_y = event->tilt_y; + if (event->updated_axes & + (WLR_TABLET_TOOL_AXIS_TILT_X | WLR_TABLET_TOOL_AXIS_TILT_Y)) { + wlr_tablet_v2_tablet_tool_notify_tilt(tool->tool_v2, tool->tilt_x, + tool->tilt_y); + } + if (event->updated_axes & WLR_TABLET_TOOL_AXIS_ROTATION) + wlr_tablet_v2_tablet_tool_notify_rotation(tool->tool_v2, + event->rotation); + if (event->updated_axes & WLR_TABLET_TOOL_AXIS_SLIDER) + wlr_tablet_v2_tablet_tool_notify_slider(tool->tool_v2, event->slider); + if (event->updated_axes & WLR_TABLET_TOOL_AXIS_WHEEL) + wlr_tablet_v2_tablet_tool_notify_wheel(tool->tool_v2, + event->wheel_delta, 0); +} + +void tablettoolbutton(struct wl_listener *listener, void *data) { + struct wlr_tablet_tool_button_event *event = data; + struct TabletTool *tool = event->tool->data; + if (!tool) + return; + wlr_tablet_v2_tablet_tool_notify_button( + tool->tool_v2, event->button, + (enum zwp_tablet_pad_v2_button_state)event->state); +} + +void tablettooltip(struct wl_listener *listener, void *data) { + struct wlr_tablet_tool_tip_event *event = data; + struct TabletTool *tool = event->tool->data; + if (!tool) + return; + + struct wlr_pointer_button_event fakeptrbtnevent = { + .button = BTN_LEFT, + .state = event->state == WLR_TABLET_TOOL_TIP_UP + ? WL_POINTER_BUTTON_STATE_RELEASED + : WL_POINTER_BUTTON_STATE_PRESSED, + .time_msec = event->time_msec, + }; + + if (handle_buttonpress(&fakeptrbtnevent)) + return; + + if (!tool->curr_surface) { + wlr_seat_pointer_notify_button(seat, fakeptrbtnevent.time_msec, + fakeptrbtnevent.button, + fakeptrbtnevent.state); + return; + } + + if (event->state == WLR_TABLET_TOOL_TIP_UP) { + wlr_tablet_v2_tablet_tool_notify_up(tool->tool_v2); + return; + } + + wlr_tablet_v2_tablet_tool_notify_down(tool->tool_v2); + wlr_tablet_tool_v2_start_implicit_grab(tool->tool_v2); +} diff --git a/src/mango.c b/src/mango.c index a9702dc7..66d9c6da 100644 --- a/src/mango.c +++ b/src/mango.c @@ -72,6 +72,9 @@ #include <wlr/types/wlr_single_pixel_buffer_v1.h> #include <wlr/types/wlr_subcompositor.h> #include <wlr/types/wlr_switch.h> +#include <wlr/types/wlr_tablet_pad.h> +#include <wlr/types/wlr_tablet_tool.h> +#include <wlr/types/wlr_tablet_v2.h> #include <wlr/types/wlr_viewporter.h> #include <wlr/types/wlr_virtual_keyboard_v1.h> #include <wlr/types/wlr_virtual_pointer_v1.h> @@ -618,6 +621,7 @@ static void axisnotify(struct wl_listener *listener, void *data); // 滚轮事件处理 static void buttonpress(struct wl_listener *listener, void *data); // 鼠标按键事件处理 +static bool handle_buttonpress(struct wlr_pointer_button_event *event); static int32_t ongesture(struct wlr_pointer_swipe_end_event *event); static void swipe_begin(struct wl_listener *listener, void *data); static void swipe_update(struct wl_listener *listener, void *data); @@ -2263,6 +2267,13 @@ bool check_trackpad_disabled(struct wlr_pointer *pointer) { void // 鼠标按键事件 buttonpress(struct wl_listener *listener, void *data) { struct wlr_pointer_button_event *event = data; + + if (!handle_buttonpress(event)) + wlr_seat_pointer_notify_button(seat, event->time_msec, event->button, + event->state); +} + +bool handle_buttonpress(struct wlr_pointer_button_event *event) { struct wlr_keyboard *hard_keyboard, *keyboard; uint32_t hard_mods, mods; Client *c = NULL; @@ -2277,8 +2288,8 @@ buttonpress(struct wl_listener *listener, void *data) { handlecursoractivity(); wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); - if (check_trackpad_disabled(event->pointer)) { - return; + if (event->pointer && check_trackpad_disabled(event->pointer)) { + return true; } switch (event->state) { @@ -2325,12 +2336,12 @@ buttonpress(struct wl_listener *listener, void *data) { if (selmon->isoverview && event->button == BTN_LEFT && c) { toggleoverview(&(Arg){.i = 1}); - return; + return true; } if (selmon->isoverview && event->button == BTN_RIGHT && c) { pending_kill_client(c); - return; + return true; } if (CLEANMASK(mods) == CLEANMASK(m->mod) && @@ -2338,7 +2349,7 @@ buttonpress(struct wl_listener *listener, void *data) { (CLEANMASK(m->mod) != 0 || (event->button != BTN_LEFT && event->button != BTN_RIGHT))) { m->func(&m->arg); - return; + return true; } } break; @@ -2376,16 +2387,14 @@ buttonpress(struct wl_listener *listener, void *data) { client_set_drop_area(dropc); dropc = NULL; } - return; + return true; } else { cursor_mode = CurNormal; } break; } - /* If the event wasn't handled by the compositor, notify the client with - * pointer focus that a button press has occurred */ - wlr_seat_pointer_notify_button(seat, event->time_msec, event->button, - event->state); + /* If the event wasn't handled by the compositor, return false */ + return false; } void checkidleinhibitor(struct wlr_surface *exclude) { @@ -2451,6 +2460,10 @@ void cleanuplisteners(void) { wl_list_remove(&cursor_frame.link); wl_list_remove(&cursor_motion.link); wl_list_remove(&cursor_motion_absolute.link); + wl_list_remove(&tablet_tool_proximity.link); + wl_list_remove(&tablet_tool_axis.link); + wl_list_remove(&tablet_tool_button.link); + wl_list_remove(&tablet_tool_tip.link); wl_list_remove(&gpu_reset.link); wl_list_remove(&new_idle_inhibitor.link); wl_list_remove(&layout_change.link); @@ -3951,6 +3964,12 @@ void inputdevice(struct wl_listener *listener, void *data) { case WLR_INPUT_DEVICE_KEYBOARD: createkeyboard(wlr_keyboard_from_input_device(device)); break; + case WLR_INPUT_DEVICE_TABLET: + createtablet(device); + break; + case WLR_INPUT_DEVICE_TABLET_PAD: + createtabletpad(device); + break; case WLR_INPUT_DEVICE_POINTER: createpointer(wlr_pointer_from_input_device(device)); break; @@ -5742,6 +5761,7 @@ void setup(void) { * clients from the Unix socket, manging Wayland globals, and so on. */ dpy = wl_display_create(); event_loop = wl_display_get_event_loop(dpy); + tablet_mgr = wlr_tablet_v2_create(dpy); /* The backend is a wlroots feature which abstracts the underlying input * and output hardware. The autocreate option will choose the most * suitable backend based on the current environment, such as opening an @@ -5929,6 +5949,11 @@ void setup(void) { wl_signal_add(&cursor->events.button, &cursor_button); wl_signal_add(&cursor->events.axis, &cursor_axis); wl_signal_add(&cursor->events.frame, &cursor_frame); + wl_signal_add(&cursor->events.tablet_tool_proximity, + &tablet_tool_proximity); + wl_signal_add(&cursor->events.tablet_tool_axis, &tablet_tool_axis); + wl_signal_add(&cursor->events.tablet_tool_button, &tablet_tool_button); + wl_signal_add(&cursor->events.tablet_tool_tip, &tablet_tool_tip); // 这两句代码会造成obs窗口里的鼠标光标消失,不知道注释有什么影响 cursor_shape_mgr = wlr_cursor_shape_manager_v1_create(dpy, 1); @@ -5943,6 +5968,8 @@ void setup(void) { * to let us know when new input devices are available on the backend. */ wl_list_init(&inputdevices); + wl_list_init(&tablets); + wl_list_init(&tablet_pads); 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); From bb72b6378390a03387610bb55415a2d69225a579 Mon Sep 17 00:00:00 2001 From: xtheeq <atheeq.rhxn@gmail.com> Date: Thu, 21 May 2026 15:17:06 +0530 Subject: [PATCH 222/328] ci: restrict sync-website workflow to main repo --- .github/workflows/sync-website.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/sync-website.yml b/.github/workflows/sync-website.yml index 57c99e71..844dc2cc 100644 --- a/.github/workflows/sync-website.yml +++ b/.github/workflows/sync-website.yml @@ -12,6 +12,7 @@ concurrency: jobs: sync-website: + if: github.repository == 'mangowm/mango' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 From 483bd724b3e27c921922a8299a02f3a12953ee35 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 21 May 2026 18:01:36 +0800 Subject: [PATCH 223/328] fix: fix layer always null in xytonode --- src/config/parse_config.h | 2 +- src/ext-protocol/all.h | 2 +- src/fetch/common.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 4beb71da..c19e9ab1 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -289,7 +289,7 @@ typedef struct { double mouse_accel_speed; double axis_scroll_factor; - /* tablet */ + /* tablet */ char *tablet_map_to_mon; /* Trackpad */ diff --git a/src/ext-protocol/all.h b/src/ext-protocol/all.h index 6fdb0744..fac2779d 100644 --- a/src/ext-protocol/all.h +++ b/src/ext-protocol/all.h @@ -1,6 +1,6 @@ #include "dwl-ipc.h" #include "ext-workspace.h" #include "foreign-toplevel.h" +#include "tablet.h" #include "tearing.h" #include "text-input.h" -#include "tablet.h" diff --git a/src/fetch/common.h b/src/fetch/common.h index aa3ae6ff..43f58f82 100644 --- a/src/fetch/common.h +++ b/src/fetch/common.h @@ -131,8 +131,8 @@ void xytonode(double x, double y, struct wlr_surface **psurface, Client **pc, for (pnode = node; pnode && !c; pnode = &pnode->parent->node) c = pnode->data; if (c && c->type == LayerShell) { + l = (LayerSurface *)c; c = NULL; - l = pnode->data; } } From 5b83ba523637916b6692ad10aa75a1b2bef196c1 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 21 May 2026 18:12:35 +0800 Subject: [PATCH 224/328] fix: fix typo for idleinhibit_when_focus option in windowrule --- docs/window-management/rules.md | 2 +- src/config/parse_config.h | 8 ++++---- src/mango.c | 10 +++++----- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/window-management/rules.md b/docs/window-management/rules.md index 4a295157..acfd5a79 100644 --- a/docs/window-management/rules.md +++ b/docs/window-management/rules.md @@ -34,7 +34,7 @@ windowrule=Parameter:Values,Parameter:Values,appid:Values,title:Values | `noopenmaximized` | integer | `0` / `1` | Window does not open as maximized mode | | `single_scratchpad` | integer | `0` / `1` (default 1) | Only show one out of named scratchpads or the normal scratchpad | | `allow_shortcuts_inhibit` | integer | `0` / `1` (default 1) | Allow shortcuts to be inhibited by clients | -| `indleinhibit_when_focus` | integer | `0` / `1` (default 0) | Automatically keep idle inhibit active when this window is focused | +| `idleinhibit_when_focus` | integer | `0` / `1` (default 0) | Automatically keep idle inhibit active when this window is focused | ### Geometry & Position diff --git a/src/config/parse_config.h b/src/config/parse_config.h index c19e9ab1..ebbba3bc 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -78,7 +78,7 @@ typedef struct { int32_t ignore_maximize; int32_t ignore_minimize; int32_t isnosizehint; - int32_t indleinhibit_when_focus; + int32_t idleinhibit_when_focus; char *monitor; int32_t offsetx; int32_t offsety; @@ -2137,7 +2137,7 @@ bool parse_option(Config *config, char *key, char *value) { rule->ignore_maximize = -1; rule->ignore_minimize = -1; rule->isnosizehint = -1; - rule->indleinhibit_when_focus = -1; + rule->idleinhibit_when_focus = -1; rule->isterm = -1; rule->allow_csd = -1; rule->force_fakemaximize = -1; @@ -2248,8 +2248,8 @@ bool parse_option(Config *config, char *key, char *value) { rule->ignore_minimize = atoi(val); } else if (strcmp(key, "isnosizehint") == 0) { rule->isnosizehint = atoi(val); - } else if (strcmp(key, "indleinhibit_when_focus") == 0) { - rule->indleinhibit_when_focus = atoi(val); + } else if (strcmp(key, "idleinhibit_when_focus") == 0) { + rule->idleinhibit_when_focus = atoi(val); } else if (strcmp(key, "isterm") == 0) { rule->isterm = atoi(val); } else if (strcmp(key, "allow_csd") == 0) { diff --git a/src/mango.c b/src/mango.c index 66d9c6da..25d051c1 100644 --- a/src/mango.c +++ b/src/mango.c @@ -356,7 +356,7 @@ struct Client { struct wlr_foreign_toplevel_handle_v1 *foreign_toplevel; int32_t isfloating, isurgent, isfullscreen, isfakefullscreen, need_float_size_reduce, isminimized, isoverlay, isnosizehint, - ignore_maximize, ignore_minimize, indleinhibit_when_focus; + ignore_maximize, ignore_minimize, idleinhibit_when_focus; int32_t ismaximizescreen; int32_t overview_backup_bw; int32_t fullscreen_backup_x, fullscreen_backup_y, fullscreen_backup_w, @@ -1504,7 +1504,7 @@ static void apply_rule_properties(Client *c, const ConfigWinRule *r) { APPLY_INT_PROP(c, r, ignore_maximize); APPLY_INT_PROP(c, r, ignore_minimize); APPLY_INT_PROP(c, r, isnosizehint); - APPLY_INT_PROP(c, r, indleinhibit_when_focus); + APPLY_INT_PROP(c, r, idleinhibit_when_focus); APPLY_INT_PROP(c, r, isunglobal); APPLY_INT_PROP(c, r, noblur); APPLY_INT_PROP(c, r, allow_shortcuts_inhibit); @@ -4383,7 +4383,7 @@ void init_client_properties(Client *c) { c->force_tiled_state = 1; c->force_tearing = 0; c->allow_shortcuts_inhibit = SHORTCUTS_INHIBIT_ENABLE; - c->indleinhibit_when_focus = 0; + c->idleinhibit_when_focus = 0; c->scroller_proportion_single = 0.0f; c->float_geom.width = 0; c->float_geom.height = 0; @@ -6243,7 +6243,7 @@ int32_t hidecursor(void *data) { } void check_keep_idle_inhibit(Client *c) { - if (c && c->indleinhibit_when_focus && keep_idle_inhibit_source) { + if (c && c->idleinhibit_when_focus && keep_idle_inhibit_source) { wl_event_source_timer_update(keep_idle_inhibit_source, 1000); } } @@ -6260,7 +6260,7 @@ int32_t keep_idle_inhibit(void *data) { return 1; } - if (!selmon || !selmon->sel || !selmon->sel->indleinhibit_when_focus) { + if (!selmon || !selmon->sel || !selmon->sel->idleinhibit_when_focus) { wl_event_source_timer_update(keep_idle_inhibit_source, 0); return 1; } From 096ef0bf416b0928e18d73517b0c4f3fd0255938 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 21 May 2026 19:13:45 +0800 Subject: [PATCH 225/328] update docs --- docs/bindings/keys.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/bindings/keys.md b/docs/bindings/keys.md index 002c9564..616ea305 100644 --- a/docs/bindings/keys.md +++ b/docs/bindings/keys.md @@ -148,6 +148,8 @@ bindr=Super,Super_L,spawn,rofi -show run | `incgaps` | `+/-value` | Adjust gap size. | | `togglegaps` | - | Toggle gaps. | | `dwindle_toggle_split_direction` | - | Toggle split direction in dwindle layout. | +| `dwindle_split_horizontal` | - | Set split window direction to horizontal in dwindle layout. | +| `dwindle_split_vertical` | - | Set split window direction to vertical in dwindle layout. | ### System From 406a16033b804b6b6a79b08f862dec8adbd1c6d9 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 21 May 2026 21:28:38 +0800 Subject: [PATCH 226/328] opt: opitmize subtree surface scale in overview --- src/animation/client.h | 26 ++++++++++++++++++--- src/animation/common.h | 53 ++++++++++++++++++++++++++++++++---------- src/mango.c | 7 ++++++ 3 files changed, 71 insertions(+), 15 deletions(-) diff --git a/src/animation/client.h b/src/animation/client.h index 4bb018f9..c89f0cec 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -243,11 +243,31 @@ void scene_buffer_apply_overview_effect(struct wlr_scene_buffer *buffer, int32_t sx, int32_t sy, void *data) { BufferData *buffer_data = (BufferData *)data; - if (buffer_data->width > 0 && buffer_data->height > 0) { - wlr_scene_buffer_set_dest_size(buffer, buffer_data->width, - buffer_data->height); + if (buffer_data->width_scale >= 1.0 || buffer_data->height_scale >= 1.0) + return; + + int32_t surface_width = 0; + int32_t surface_height = 0; + bool is_subsurface = false; + + struct wlr_scene_tree *parent_tree = buffer->node.parent; + if (parent_tree->node.data != NULL) { + SnapshotMetadata *meta = (SnapshotMetadata *)parent_tree->node.data; + surface_width = meta->orig_width; + surface_height = meta->orig_height; + is_subsurface = meta->is_subsurface; } + surface_height = surface_height * buffer_data->height_scale; + surface_width = surface_width * buffer_data->width_scale; + + if (buffer_data->width > 0 && buffer_data->height > 0) { + wlr_scene_buffer_set_dest_size(buffer, surface_width, surface_height); + } + + if (is_subsurface) + return; + wlr_scene_buffer_set_corner_radius(buffer, config.border_radius, buffer_data->corner_location); } diff --git a/src/animation/common.h b/src/animation/common.h index 10b66b91..6120d79c 100644 --- a/src/animation/common.h +++ b/src/animation/common.h @@ -28,6 +28,12 @@ struct dvec2 calculate_animation_curve_at(double t, int32_t type) { return point; } +void handle_snapshot_meta_destroy(struct wl_listener *listener, void *data) { + SnapshotMetadata *meta = wl_container_of(listener, meta, destroy); + wl_list_remove(&meta->destroy.link); // 安全移除监听器 + free(meta); +} + void init_baked_points(void) { baked_points_move = calloc(BAKED_POINTS_COUNT, sizeof(*baked_points_move)); baked_points_open = calloc(BAKED_POINTS_COUNT, sizeof(*baked_points_open)); @@ -154,12 +160,43 @@ static bool scene_node_snapshot(struct wlr_scene_node *node, int32_t lx, struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node); - struct wlr_scene_buffer *snapshot_buffer = - wlr_scene_buffer_create(snapshot_tree, NULL); - if (snapshot_buffer == NULL) { + // 创建中间包装树节点 + struct wlr_scene_tree *wrapper = wlr_scene_tree_create(snapshot_tree); + if (wrapper == NULL) { return false; } - snapshot_node = &snapshot_buffer->node; + snapshot_node = &wrapper->node; // 坐标位移应用在外层包装盒上 + + // 收集表面状态并保存为元数据 + SnapshotMetadata *meta = calloc(1, sizeof(SnapshotMetadata)); + if (meta == NULL) { + wlr_scene_node_destroy(&wrapper->node); + return false; + } + meta->orig_width = scene_buffer->dst_width; + meta->orig_height = scene_buffer->dst_height; + + struct wlr_scene_surface *scene_surface = + wlr_scene_surface_try_from_buffer(scene_buffer); + if (scene_surface != NULL) { + meta->is_subsurface = + !!wlr_subsurface_try_from_wlr_surface(scene_surface->surface); + } + + // 绑定销毁回调监听,随包装节点销毁而释放内存 + meta->destroy.notify = handle_snapshot_meta_destroy; + wl_signal_add(&wrapper->node.events.destroy, &meta->destroy); + wrapper->node.data = meta; + + // 将真正的 buffer 挂靠在 wrapper 下面(相对坐标0,0) + struct wlr_scene_buffer *snapshot_buffer = + wlr_scene_buffer_create(wrapper, NULL); + if (snapshot_buffer == NULL) { + wlr_scene_node_destroy(&wrapper->node); + return false; + } + + // 保留原生的 data 指针(如 Client*),防止事件派发/焦点获取失效 snapshot_buffer->node.data = scene_buffer->node.data; wlr_scene_buffer_set_dest_size(snapshot_buffer, scene_buffer->dst_width, @@ -179,16 +216,8 @@ static bool scene_node_snapshot(struct wlr_scene_node *node, int32_t lx, scene_buffer->corner_radius, scene_buffer->corners); - // wlr_scene_buffer_set_backdrop_blur_optimized( - // snapshot_buffer, scene_buffer->backdrop_blur_optimized); - // wlr_scene_buffer_set_backdrop_blur_ignore_transparent( - // snapshot_buffer, scene_buffer->backdrop_blur_ignore_transparent); wlr_scene_buffer_set_backdrop_blur(snapshot_buffer, false); - snapshot_buffer->node.data = scene_buffer->node.data; - - struct wlr_scene_surface *scene_surface = - wlr_scene_surface_try_from_buffer(scene_buffer); if (scene_surface != NULL && scene_surface->surface->buffer != NULL) { wlr_scene_buffer_set_buffer(snapshot_buffer, &scene_surface->surface->buffer->base); diff --git a/src/mango.c b/src/mango.c index 25d051c1..2aa292ac 100644 --- a/src/mango.c +++ b/src/mango.c @@ -606,6 +606,13 @@ struct TagScrollerState { int count; }; +typedef struct { + int32_t orig_width; + int32_t orig_height; + bool is_subsurface; + struct wl_listener destroy; +} SnapshotMetadata; + /* function declarations */ static void applybounds( Client *c, From 72a7c96f98445673c317d6b96be1b9370626df74 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 21 May 2026 22:40:31 +0800 Subject: [PATCH 227/328] opt: dont send suspend state to client because some app like gnome not ready for this --- src/animation/client.h | 1 - src/animation/tag.h | 2 -- src/client/client.h | 9 --------- 3 files changed, 12 deletions(-) diff --git a/src/animation/client.h b/src/animation/client.h index c89f0cec..57a72744 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -1044,7 +1044,6 @@ void client_animation_next_tick(Client *c) { if (c->animation.tagouting) { c->animation.tagouting = false; wlr_scene_node_set_enabled(&c->scene->node, false); - client_set_suspended(c, true); c->animation.tagouted = true; c->animation.current = c->geom; } diff --git a/src/animation/tag.h b/src/animation/tag.h index 1a973784..df1fb8f6 100644 --- a/src/animation/tag.h +++ b/src/animation/tag.h @@ -46,7 +46,6 @@ void set_arrange_visible(Monitor *m, Client *c, bool want_animation) { wlr_scene_node_set_enabled(&c->scene->node, true); wlr_scene_node_set_enabled(&c->scene_surface->node, true); } - client_set_suspended(c, false); if (!c->animation.tag_from_rule && want_animation && m->pertag->prevtag != 0 && m->pertag->curtag != 0 && @@ -112,6 +111,5 @@ void set_arrange_hidden(Monitor *m, Client *c, bool want_animation) { set_tagout_animation(m, c); } else { wlr_scene_node_set_enabled(&c->scene->node, false); - client_set_suspended(c, true); } } diff --git a/src/client/client.h b/src/client/client.h index 27e8ef33..8e291e9e 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -380,15 +380,6 @@ static inline void client_set_tiled(Client *c, uint32_t edges) { } } -static inline void client_set_suspended(Client *c, int32_t suspended) { -#ifdef XWAYLAND - if (client_is_x11(c)) - return; -#endif - - wlr_xdg_toplevel_set_suspended(c->surface.xdg->toplevel, suspended); -} - static inline int32_t client_should_ignore_focus(Client *c) { #ifdef XWAYLAND From da1e1cadf7030d9e5775c8717dbd27c409276704 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 21 May 2026 22:49:15 +0800 Subject: [PATCH 228/328] opt: force apply buffer size to main surface in ov scale --- src/animation/client.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/animation/client.h b/src/animation/client.h index 57a72744..fc959b3f 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -261,8 +261,11 @@ void scene_buffer_apply_overview_effect(struct wlr_scene_buffer *buffer, surface_height = surface_height * buffer_data->height_scale; surface_width = surface_width * buffer_data->width_scale; - if (buffer_data->width > 0 && buffer_data->height > 0) { + if (is_subsurface && surface_width > 0 && surface_height > 0) { wlr_scene_buffer_set_dest_size(buffer, surface_width, surface_height); + } else if (buffer_data->height > 0 && buffer_data->width > 0) { + wlr_scene_buffer_set_dest_size(buffer, buffer_data->width, + buffer_data->height); } if (is_subsurface) From 33735a796f6dc325e51e3ea624d1e231140defda Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 22 May 2026 09:19:17 +0800 Subject: [PATCH 229/328] fix: fix ov scale when disable animaiton --- src/animation/client.h | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/animation/client.h b/src/animation/client.h index fc959b3f..53d9e2d2 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -243,9 +243,6 @@ void scene_buffer_apply_overview_effect(struct wlr_scene_buffer *buffer, int32_t sx, int32_t sy, void *data) { BufferData *buffer_data = (BufferData *)data; - if (buffer_data->width_scale >= 1.0 || buffer_data->height_scale >= 1.0) - return; - int32_t surface_width = 0; int32_t surface_height = 0; bool is_subsurface = false; @@ -825,7 +822,7 @@ void client_apply_clip(Client *c, float factor) { enum corner_location current_corner_location = set_client_corner_location(c); - if (!config.animations) { + if (!config.animations && !c->mon->isoverview) { c->animation.running = false; c->need_output_flush = false; c->animainit_geom = c->current = c->pending = c->animation.current = @@ -910,7 +907,7 @@ void client_apply_clip(Client *c, float factor) { buffer_data.height = clip_box.height; buffer_data.corner_location = current_corner_location; - if (factor == 1.0) { + if (factor == 1.0 && !c->mon->isoverview) { buffer_data.width_scale = 1.0; buffer_data.height_scale = 1.0; } else { From 4a9ab3dd9f39e67603b9b399fda9b730a1c952e8 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 23 May 2026 13:22:40 +0800 Subject: [PATCH 230/328] fix: should ignore empty input layer focus --- docs/installation.md | 1 + mangowm.scm | 1 + meson.build | 2 ++ src/fetch/common.h | 11 ++++++++++- 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/installation.md b/docs/installation.md index c5d4936c..ba51a109 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -273,6 +273,7 @@ If your distribution isn't listed above, or you want the latest unreleased chang > - `hwdata` > - `seatd` > - `pcre2` +> - `pixman` > - `xorg-xwayland` > - `libxcb` diff --git a/mangowm.scm b/mangowm.scm index 44031ed0..83bcf168 100644 --- a/mangowm.scm +++ b/mangowm.scm @@ -54,6 +54,7 @@ seatd pcre2 libxcb + pixman xcb-util-wm wlroots-0.19 scenefx)) diff --git a/meson.build b/meson.build index 749fbdc0..899439c8 100644 --- a/meson.build +++ b/meson.build @@ -39,6 +39,7 @@ libinput_dep = dependency('libinput',version: '>=1.27.1') libwayland_client_dep = dependency('wayland-client') pcre2_dep = dependency('libpcre2-8') libscenefx_dep = dependency('scenefx-0.4',version: '>=0.4.1') +pixman_dep = dependency('pixman-1') # 获取版本信息 @@ -110,6 +111,7 @@ executable('mango', libinput_dep, libwayland_client_dep, pcre2_dep, + pixman_dep, ], install : true, c_args : c_args, diff --git a/src/fetch/common.h b/src/fetch/common.h index 43f58f82..f58bd33a 100644 --- a/src/fetch/common.h +++ b/src/fetch/common.h @@ -90,6 +90,15 @@ Client *xytoclient(double x, double y) { return NULL; } +static bool layer_ignores_focus(LayerSurface *l) { + if (!l || !l->layer_surface) + return true; + struct wlr_surface *s = l->layer_surface->surface; + return !pixman_region32_not_empty(&s->input_region) || + l->layer_surface->current.keyboard_interactive == + ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE; +} + void xytonode(double x, double y, struct wlr_surface **psurface, Client **pc, LayerSurface **pl, double *nx, double *ny) { struct wlr_scene_node *node, *pnode; @@ -151,7 +160,7 @@ void xytonode(double x, double y, struct wlr_surface **psurface, Client **pc, if (pl) *pl = l; - if (selmon && selmon->isoverview && !l) { + if (selmon && selmon->isoverview && (!l || layer_ignores_focus(l))) { ovc = xytoclient(x, y); if (pc) *pc = ovc; From 373ebfac336ad8296263b6408a2e81ed5ef9c06b Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 23 May 2026 14:54:55 +0800 Subject: [PATCH 231/328] fix: error changse surface scene in minimized client will cause crash if close all client in overview, when has minimized client before toggle overview --- src/animation/client.h | 10 +++++----- src/dispatch/bind_define.h | 8 ++++++-- src/mango.c | 2 +- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/animation/client.h b/src/animation/client.h index 53d9e2d2..238da9fb 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -291,7 +291,7 @@ void buffer_set_effect(Client *c, BufferData data) { data.corner_location = CORNER_LOCATION_NONE; } - if (c->mon->isoverview && config.ov_no_resize) { + if (c->overview_scene_surface) { wlr_scene_node_for_each_buffer( &c->scene_surface->node, scene_buffer_apply_overview_effect, &data); } else { @@ -822,7 +822,7 @@ void client_apply_clip(Client *c, float factor) { enum corner_location current_corner_location = set_client_corner_location(c); - if (!config.animations && !c->mon->isoverview) { + if (!config.animations && !c->overview_scene_surface) { c->animation.running = false; c->need_output_flush = false; c->animainit_geom = c->current = c->pending = c->animation.current = @@ -839,7 +839,7 @@ void client_apply_clip(Client *c, float factor) { return; } - if (!c->mon->isoverview || !config.ov_no_resize) { + if (!c->overview_scene_surface) { wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip_box); } @@ -891,7 +891,7 @@ void client_apply_clip(Client *c, float factor) { } // 应用窗口表面剪切 - if (!c->mon->isoverview || !config.ov_no_resize) { + if (!c->overview_scene_surface) { wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip_box); } @@ -907,7 +907,7 @@ void client_apply_clip(Client *c, float factor) { buffer_data.height = clip_box.height; buffer_data.corner_location = current_corner_location; - if (factor == 1.0 && !c->mon->isoverview) { + if (factor == 1.0 && !c->overview_scene_surface) { buffer_data.width_scale = 1.0; buffer_data.height_scale = 1.0; } else { diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 6710ba58..01125cbc 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1265,6 +1265,9 @@ int32_t toggle_named_scratchpad(const Arg *arg) { char *arg_id = arg->v; char *arg_title = arg->v2; + if (selmon && selmon->isoverview) + return 0; + target_client = get_client_by_id_or_title(arg_id, arg_title); if (!target_client && arg->v3) { @@ -1743,7 +1746,8 @@ int32_t toggleoverview(const Arg *arg) { wl_list_for_each(c, &clients, link) { if (c && c->mon == selmon && !client_is_unmanaged(c) && - !client_is_x11_popup(c) && !c->isunglobal) { + !client_is_x11_popup(c) && !c->isunglobal && !c->isminimized && + client_surface(c)->mapped) { c->animation.overining = true; overview_backup(c); } @@ -1752,7 +1756,7 @@ int32_t toggleoverview(const Arg *arg) { selmon->tagset[selmon->seltags] = target; wl_list_for_each(c, &clients, link) { if (c && c->mon == selmon && !c->iskilling && - !client_is_unmanaged(c) && !c->isunglobal && + !client_is_unmanaged(c) && !c->isunglobal && !c->isminimized && !client_is_x11_popup(c) && client_surface(c)->mapped) { overview_restore(c, &(Arg){.ui = target}); } diff --git a/src/mango.c b/src/mango.c index 2aa292ac..59e11262 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1234,7 +1234,7 @@ void swallow(Client *c, Client *w) { w->overview_scene_surface = NULL; } - if (c->mon && c->mon->isoverview) { + if (c->mon && c->mon->isoverview && config.ov_no_resize) { overview_backup_surface(c); } From 2fefb42a6cd6ab00af2b1bcf70b24f01c67f1fbe Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 23 May 2026 15:21:39 +0800 Subject: [PATCH 232/328] feat: add option edge_scroller_focus_allow_speed --- assets/config.conf | 1 + docs/window-management/layouts.md | 2 ++ src/config/parse_config.h | 6 ++++++ src/mango.c | 12 ++++++++++-- 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/assets/config.conf b/assets/config.conf index 7fff052d..ab607f19 100644 --- a/assets/config.conf +++ b/assets/config.conf @@ -57,6 +57,7 @@ scroller_default_proportion=0.8 scroller_focus_center=0 scroller_prefer_center=0 edge_scroller_pointer_focus=1 +edge_scroller_focus_allow_speed=0.0 scroller_default_proportion_single=1.0 scroller_proportion_preset=0.5,0.8,1.0 diff --git a/docs/window-management/layouts.md b/docs/window-management/layouts.md index 45b08ec2..02f218a3 100644 --- a/docs/window-management/layouts.md +++ b/docs/window-management/layouts.md @@ -38,6 +38,7 @@ The Scroller layout positions windows in a scrollable strip, similar to PaperWM. | `scroller_prefer_center` | `0` | Center focused window only if it was outside the view. | | `scroller_prefer_overspread` | `1` | Allow windows to overspread when there's extra space. | | `edge_scroller_pointer_focus` | `1` | Focus windows even if partially off-screen. | +| `edge_scroller_focus_allow_speed` | `0.0` | Allow pointer focus to happen if the pointer moves at a speed greater than this value. | | `scroller_proportion_preset` | `0.5,0.8,1.0` | Presets for cycling window widths. | | `scroller_ignore_proportion_single` | `1` | Ignore proportion adjustments for single windows. | | `scroller_default_proportion_single` | `1.0` | Default proportion for single windows in scroller. **Requires `scroller_ignore_proportion_single=0` to take effect.** | @@ -56,6 +57,7 @@ scroller_focus_center=0 scroller_prefer_center=0 scroller_prefer_overspread=1 edge_scroller_pointer_focus=1 +edge_scroller_focus_allow_speed=0.0 scroller_default_proportion_single=1.0 scroller_proportion_preset=0.5,0.8,1.0 ``` diff --git a/src/config/parse_config.h b/src/config/parse_config.h index ebbba3bc..19f20f1a 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -216,6 +216,7 @@ typedef struct { int32_t scroller_prefer_center; int32_t scroller_prefer_overspread; int32_t edge_scroller_pointer_focus; + double edge_scroller_focus_allow_speed; int32_t focus_cross_monitor; int32_t exchange_cross_monitor; int32_t scratchpad_cross_monitor; @@ -1396,6 +1397,8 @@ bool parse_option(Config *config, char *key, char *value) { config->scroller_prefer_overspread = atoi(value); } else if (strcmp(key, "edge_scroller_pointer_focus") == 0) { config->edge_scroller_pointer_focus = atoi(value); + } else if (strcmp(key, "edge_scroller_focus_allow_speed") == 0) { + config->edge_scroller_focus_allow_speed = atof(value); } else if (strcmp(key, "focus_cross_monitor") == 0) { config->focus_cross_monitor = atoi(value); } else if (strcmp(key, "exchange_cross_monitor") == 0) { @@ -3229,6 +3232,8 @@ void override_config(void) { CLAMP_INT(config.scroller_prefer_overspread, 0, 1); config.edge_scroller_pointer_focus = CLAMP_INT(config.edge_scroller_pointer_focus, 0, 1); + config.edge_scroller_focus_allow_speed = + CLAMP_FLOAT(config.edge_scroller_focus_allow_speed, 0.0f, 1000.0f); config.scroller_structs = CLAMP_INT(config.scroller_structs, 0, 1000); config.default_mfact = CLAMP_FLOAT(config.default_mfact, 0.1f, 0.9f); config.default_nmaster = CLAMP_INT(config.default_nmaster, 1, 1000); @@ -3423,6 +3428,7 @@ void set_value_default() { config.scroller_prefer_center = 0; config.scroller_prefer_overspread = 1; config.edge_scroller_pointer_focus = 1; + config.edge_scroller_focus_allow_speed = 0.0f; config.focus_cross_monitor = 0; config.exchange_cross_monitor = 0; config.scratchpad_cross_monitor = 0; diff --git a/src/mango.c b/src/mango.c index 59e11262..3b2d5b6e 100644 --- a/src/mango.c +++ b/src/mango.c @@ -4789,14 +4789,22 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, } should_lock = false; + double speed = 0.0f; + + if (config.edge_scroller_pointer_focus) { + speed = sqrt(dx * dx + dy * dy); + } + if (!scroller_focus_lock || !(c && c->mon && !INSIDEMON(c))) { if (c && c->mon && is_scroller_layout(c->mon) && !INSIDEMON(c)) { should_lock = true; } - if (!(!config.edge_scroller_pointer_focus && c && c->mon && - is_scroller_layout(c->mon) && !INSIDEMON(c))) + if (!((!config.edge_scroller_pointer_focus || + speed < config.edge_scroller_focus_allow_speed) && + c && c->mon && is_scroller_layout(c->mon) && !INSIDEMON(c))) { pointerfocus(c, surface, sx, sy, time); + } if (should_lock && c && c->mon && ISTILED(c) && c == c->mon->sel) { scroller_focus_lock = 1; From 342cc24ccbbabe78d02d478f23385691fcd66580 Mon Sep 17 00:00:00 2001 From: ernestoCruz05 <a2023144672@isec.pt> Date: Sat, 23 May 2026 18:59:33 +0100 Subject: [PATCH 233/328] fix: don't remove the leaf from the tree when fullscreening it so the windows doesnst split randomly --- src/layout/dwindle.h | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/layout/dwindle.h b/src/layout/dwindle.h index 4e2385f5..74e79477 100644 --- a/src/layout/dwindle.h +++ b/src/layout/dwindle.h @@ -257,8 +257,11 @@ static void dwindle_assign(DwindleNode *node, int32_t ax, int32_t ay, if (!node->is_split) { if (node->client) { - struct wlr_box box = {ax, ay, MAX(1, aw), MAX(1, ah)}; - resize(node->client, box, 0); + if (!node->client->isfullscreen && + !node->client->ismaximizescreen) { + struct wlr_box box = {ax, ay, MAX(1, aw), MAX(1, ah)}; + resize(node->client, box, 0); + } } return; } @@ -589,8 +592,13 @@ void dwindle(Monitor *m) { found = true; break; } - if (!found) + if (!found) { + if (VISIBLEON(leaves[i]->client, m) && + (leaves[i]->client->isfullscreen || + leaves[i]->client->ismaximizescreen)) + continue; dwindle_remove(root, leaves[i]->client); + } } } From b868ff812b09ebfd8f5f41cd5e7ff21914bd3dc3 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 24 May 2026 07:47:30 +0800 Subject: [PATCH 234/328] opt: remember and restore size percent in fair and grid layout --- src/layout/arrange.h | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/layout/arrange.h b/src/layout/arrange.h index e452708b..8c331aa0 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -1,10 +1,11 @@ void save_old_size_per(Monitor *m) { Client *c = NULL; - wl_list_for_each(c, &clients, link) { if (VISIBLEON(c, m) && ISTILED(c)) { c->old_master_inner_per = c->master_inner_per; c->old_stack_inner_per = c->stack_inner_per; + c->old_grid_col_per = c->grid_col_per; + c->old_grid_row_per = c->grid_row_per; } } } @@ -32,13 +33,24 @@ void restore_size_per(Monitor *m, Client *c) { const Layout *current_layout = m->pertag->ltidxs[m->pertag->curtag]; if (current_layout->id == SCROLLER || - current_layout->id == VERTICAL_SCROLLER || current_layout->id == GRID || - current_layout->id == FAIR || current_layout->id == VERTICAL_FAIR || - current_layout->id == VERTICAL_GRID || current_layout->id == DECK || + current_layout->id == VERTICAL_SCROLLER || current_layout->id == DECK || current_layout->id == VERTICAL_DECK || current_layout->id == MONOCLE) { return; } + if (current_layout->id == GRID || current_layout->id == VERTICAL_GRID || + current_layout->id == FAIR || current_layout->id == VERTICAL_FAIR) { + wl_list_for_each(fc, &clients, link) { + if (VISIBLEON(fc, m) && ISTILED(fc)) { + if (fc->old_grid_col_per > 0.0f) + fc->grid_col_per = fc->old_grid_col_per; + if (fc->old_grid_row_per > 0.0f) + fc->grid_row_per = fc->old_grid_row_per; + } + } + return; + } + if (current_layout->id == CENTER_TILE) { wl_list_for_each(fc, &clients, link) { if (VISIBLEON(fc, m) && ISTILED(fc) && !c->ismaster) { From 78f7ed68254c1fdc1e21bfb36867a2d5a63bf862 Mon Sep 17 00:00:00 2001 From: ernestoCruz05 <a2023144672@isec.pt> Date: Fri, 15 May 2026 13:48:46 +0100 Subject: [PATCH 235/328] feat: carousel-like behaviour when swapping tags --- src/animation/tag.h | 11 +++++-- src/config/parse_config.h | 4 +++ src/dispatch/bind_define.h | 59 +++++++++++++++++++++++++++----------- src/mango.c | 2 ++ 4 files changed, 57 insertions(+), 19 deletions(-) diff --git a/src/animation/tag.h b/src/animation/tag.h index 1a973784..59587b33 100644 --- a/src/animation/tag.h +++ b/src/animation/tag.h @@ -15,7 +15,11 @@ void set_tagin_animation(Monitor *m, Client *c) { return; } - if (m->pertag->curtag > m->pertag->prevtag) { + bool going_forward = m->carousel_anim_dir + ? m->carousel_anim_dir > 0 + : m->pertag->curtag > m->pertag->prevtag; + + if (going_forward) { c->animainit_geom.x = config.tag_animation_direction == VERTICAL ? c->animation.current.x @@ -76,7 +80,10 @@ void set_tagout_animation(Monitor *m, Client *c) { return; } - if (m->pertag->curtag > m->pertag->prevtag) { + bool going_forward = m->carousel_anim_dir + ? m->carousel_anim_dir > 0 + : m->pertag->curtag > m->pertag->prevtag; + if (going_forward) { c->pending = c->geom; c->pending.x = config.tag_animation_direction == VERTICAL diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 9d925457..5a42dd7a 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -322,6 +322,7 @@ typedef struct { uint32_t gappoh; uint32_t gappov; uint32_t borderpx; + int32_t tag_carousel; float scratchpad_width_ratio; float scratchpad_height_ratio; float rootcolor[4]; @@ -1737,6 +1738,8 @@ bool parse_option(Config *config, char *key, char *value) { config->scratchpad_height_ratio = atof(value); } else if (strcmp(key, "borderpx") == 0) { config->borderpx = atoi(value); + } else if (strcmp(key, "tag_carousel") == 0) { + config->tag_carousel = atoi(value); } else if (strcmp(key, "rootcolor") == 0) { int64_t color = parse_color(value); if (color == -1) { @@ -3428,6 +3431,7 @@ void set_value_default() { config.idleinhibit_ignore_visible = 0; config.borderpx = 4; + config.tag_carousel = 0; config.overviewgappi = 5; config.overviewgappo = 30; config.cursor_hide_timeout = 0; diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 22fc16a3..5ec61aad 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1241,9 +1241,18 @@ int32_t tagtoleft(const Arg *arg) { return 0; if (selmon->sel != NULL && - __builtin_popcount(selmon->tagset[selmon->seltags] & TAGMASK) == 1 && - selmon->tagset[selmon->seltags] > 1) { - tag(&(Arg){.ui = selmon->tagset[selmon->seltags] >> 1, .i = arg->i}); + __builtin_popcount(selmon->tagset[selmon->seltags] & TAGMASK) == 1) { + uint32_t target = selmon->tagset[selmon->seltags] >> 1; + + if (target == 0) { + if (!config.tag_carousel) + return 0; + target = (1 << (LENGTH(tags) - 1)) & TAGMASK; + selmon->carousel_anim_dir = -1; + } + + tag(&(Arg){.ui = target & TAGMASK, .i = arg->i}); + selmon->carousel_anim_dir = 0; } return 0; } @@ -1253,9 +1262,18 @@ int32_t tagtoright(const Arg *arg) { return 0; if (selmon->sel != NULL && - __builtin_popcount(selmon->tagset[selmon->seltags] & TAGMASK) == 1 && - selmon->tagset[selmon->seltags] & (TAGMASK >> 1)) { - tag(&(Arg){.ui = selmon->tagset[selmon->seltags] << 1, .i = arg->i}); + __builtin_popcount(selmon->tagset[selmon->seltags] & TAGMASK) == 1) { + uint32_t target = selmon->tagset[selmon->seltags] << 1; + + if (!(target & TAGMASK)) { + if (!config.tag_carousel) + return 0; + target = 1; + selmon->carousel_anim_dir = 1; + } + + tag(&(Arg){.ui = target & TAGMASK, .i = arg->i}); + selmon->carousel_anim_dir = 0; } return 0; } @@ -1495,22 +1513,24 @@ int32_t viewtoleft(const Arg *arg) { if (!selmon) return 0; - uint32_t target = selmon->tagset[selmon->seltags]; - - if (selmon->isoverview || selmon->pertag->curtag == 0) { + if (selmon->isoverview || selmon->pertag->curtag == 0) return 0; - } + uint32_t target = selmon->tagset[selmon->seltags]; target >>= 1; if (target == 0) { - return 0; + if (!config.tag_carousel) + return 0; + target = (1 << (LENGTH(tags) - 1)) & TAGMASK; + selmon->carousel_anim_dir = -1; } - if (!selmon || (target) == selmon->tagset[selmon->seltags]) + if (target == selmon->tagset[selmon->seltags]) return 0; view(&(Arg){.ui = target & TAGMASK, .i = arg->i}, true); + selmon->carousel_anim_dir = 0; return 0; } @@ -1518,19 +1538,24 @@ int32_t viewtoright(const Arg *arg) { if (!selmon) return 0; - if (selmon->isoverview || selmon->pertag->curtag == 0) { + if (selmon->isoverview || selmon->pertag->curtag == 0) return 0; - } + uint32_t target = selmon->tagset[selmon->seltags]; target <<= 1; - if (!selmon || (target) == selmon->tagset[selmon->seltags]) - return 0; if (!(target & TAGMASK)) { - return 0; + if (!config.tag_carousel) + return 0; + target = 1; + selmon->carousel_anim_dir = 1; } + if (target == selmon->tagset[selmon->seltags]) + return 0; + view(&(Arg){.ui = target & TAGMASK, .i = arg->i}, true); + selmon->carousel_anim_dir = 0; return 0; } diff --git a/src/mango.c b/src/mango.c index 8c11c318..500ac4dd 100644 --- a/src/mango.c +++ b/src/mango.c @@ -545,6 +545,7 @@ struct Monitor { char last_surface_ws_name[256]; struct wlr_ext_workspace_group_handle_v1 *ext_group; bool iscleanuping; + int8_t carousel_anim_dir; }; typedef struct { @@ -3215,6 +3216,7 @@ void createmon(struct wl_listener *listener, void *data) { m->skiping_frame = false; m->resizing_count_pending = 0; m->resizing_count_current = 0; + m->carousel_anim_dir = 0; m->wlr_output = wlr_output; m->wlr_output->data = m; From d18bb85479923a168e8f2b95809668130bb5e061 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 24 May 2026 11:16:13 +0800 Subject: [PATCH 236/328] update docs --- docs/configuration/miscellaneous.md | 1 + src/config/parse_config.h | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/configuration/miscellaneous.md b/docs/configuration/miscellaneous.md index e1be2907..1001c4c1 100644 --- a/docs/configuration/miscellaneous.md +++ b/docs/configuration/miscellaneous.md @@ -46,5 +46,6 @@ description: Advanced settings for XWayland, focus behavior, and system integrat | `snap_distance` | `30` | Max distance (pixels) to trigger floating snap. | | `no_border_when_single` | `0` | Remove window borders when only one window is visible on the tag. | | `idleinhibit_ignore_visible` | `0` | Allow invisible clients (e.g., background audio players) to inhibit idle. | +| `tag_carousel` | `0` | Enable tag carousel (cycling through tags). | | `drag_tile_refresh_interval` | `8.0` | Interval (1.0–16.0) to refresh tiled window resize during drag. Too small may cause application lag. | | `drag_floating_refresh_interval` | `8.0` | Interval (1.0–16.0) to refresh floating window resize during drag. Too small may cause application lag. | \ No newline at end of file diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 59e20143..49dcea74 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -327,7 +327,6 @@ typedef struct { uint32_t gappoh; uint32_t gappov; uint32_t borderpx; - int32_t tag_carousel; float scratchpad_width_ratio; float scratchpad_height_ratio; float rootcolor[4]; @@ -386,6 +385,7 @@ typedef struct { int32_t single_scratchpad; int32_t xwayland_persistence; int32_t syncobj_enable; + int32_t tag_carousel; float drag_tile_refresh_interval; float drag_floating_refresh_interval; int32_t allow_tearing; @@ -1450,6 +1450,8 @@ bool parse_option(Config *config, char *key, char *value) { config->xwayland_persistence = atoi(value); } else if (strcmp(key, "syncobj_enable") == 0) { config->syncobj_enable = atoi(value); + } else if (strcmp(key, "tag_carousel") == 0) { + config->tag_carousel = atoi(value); } else if (strcmp(key, "drag_tile_refresh_interval") == 0) { config->drag_tile_refresh_interval = atof(value); } else if (strcmp(key, "drag_floating_refresh_interval") == 0) { @@ -1755,8 +1757,6 @@ bool parse_option(Config *config, char *key, char *value) { config->scratchpad_height_ratio = atof(value); } else if (strcmp(key, "borderpx") == 0) { config->borderpx = atoi(value); - } else if (strcmp(key, "tag_carousel") == 0) { - config->tag_carousel = atoi(value); } else if (strcmp(key, "rootcolor") == 0) { int64_t color = parse_color(value); if (color == -1) { @@ -3442,6 +3442,7 @@ void set_value_default() { config.single_scratchpad = 1; config.xwayland_persistence = 1; config.syncobj_enable = 0; + config.tag_carousel = 0; config.drag_tile_refresh_interval = 8.0f; config.drag_floating_refresh_interval = 8.0f; config.allow_tearing = TEARING_DISABLED; @@ -3458,7 +3459,6 @@ void set_value_default() { config.idleinhibit_ignore_visible = 0; config.borderpx = 4; - config.tag_carousel = 0; config.overviewgappi = 5; config.overviewgappo = 30; config.cursor_hide_timeout = 0; From 313e0b3f8f11eef9f11e3669fe93704411bfa184 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 24 May 2026 12:13:30 +0800 Subject: [PATCH 237/328] opt: more simple size percent keep logic --- src/action/client.h | 9 ++ src/layout/arrange.h | 106 ++------------- src/layout/horizontal.h | 278 ++++++++++++++++++++++------------------ src/layout/vertical.h | 114 ++++++++-------- src/mango.c | 30 +---- 5 files changed, 232 insertions(+), 305 deletions(-) diff --git a/src/action/client.h b/src/action/client.h index 9726976c..ce71a5c6 100644 --- a/src/action/client.h +++ b/src/action/client.h @@ -48,4 +48,13 @@ static void finish_exchange_arrange_and_focus(Client *c1, Client *c2, } wl_list_remove(&c2->flink); wl_list_insert(&c1->flink, &c2->flink); +} + +void client_tile_resize(Client *c, struct wlr_box geo, int32_t interact) { + if (!ISSCROLLTILED(c)) + return; + + if (!c->isfullscreen && !c->ismaximizescreen) { + resize(c, geo, interact); + } } \ No newline at end of file diff --git a/src/layout/arrange.h b/src/layout/arrange.h index 8c331aa0..3343316e 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -1,98 +1,3 @@ -void save_old_size_per(Monitor *m) { - Client *c = NULL; - wl_list_for_each(c, &clients, link) { - if (VISIBLEON(c, m) && ISTILED(c)) { - c->old_master_inner_per = c->master_inner_per; - c->old_stack_inner_per = c->stack_inner_per; - c->old_grid_col_per = c->grid_col_per; - c->old_grid_row_per = c->grid_row_per; - } - } -} - -void restore_size_per(Monitor *m, Client *c) { - Client *fc = NULL; - - if (!m || !c) - return; - - if (!m->wlr_output->enabled) - return; - - wl_list_for_each(fc, &clients, link) { - if (VISIBLEON(fc, m) && ISTILED(fc)) { - fc->old_ismaster = fc->ismaster; - } - } - - c->old_master_inner_per = c->master_inner_per; - c->old_stack_inner_per = c->stack_inner_per; - - pre_caculate_before_arrange(m, false, false, true); - - const Layout *current_layout = m->pertag->ltidxs[m->pertag->curtag]; - - if (current_layout->id == SCROLLER || - current_layout->id == VERTICAL_SCROLLER || current_layout->id == DECK || - current_layout->id == VERTICAL_DECK || current_layout->id == MONOCLE) { - return; - } - - if (current_layout->id == GRID || current_layout->id == VERTICAL_GRID || - current_layout->id == FAIR || current_layout->id == VERTICAL_FAIR) { - wl_list_for_each(fc, &clients, link) { - if (VISIBLEON(fc, m) && ISTILED(fc)) { - if (fc->old_grid_col_per > 0.0f) - fc->grid_col_per = fc->old_grid_col_per; - if (fc->old_grid_row_per > 0.0f) - fc->grid_row_per = fc->old_grid_row_per; - } - } - return; - } - - if (current_layout->id == CENTER_TILE) { - wl_list_for_each(fc, &clients, link) { - if (VISIBLEON(fc, m) && ISTILED(fc) && !c->ismaster) { - set_size_per(m, fc); - } - } - return; - } - - // it is possible that the current floating window is moved to another tag, - // but the tag has not executed save_old_size_per - // so it must be judged whether their old size values are initial values - - if (!c->ismaster && c->old_stack_inner_per < 1.0 && - c->old_stack_inner_per > 0.0f && c->stack_inner_per < 1.0 && - c->stack_inner_per > 0.0f) { - c->stack_inner_per = (1.0 - c->stack_inner_per) * - c->old_stack_inner_per / - (1.0 - c->old_stack_inner_per); - } - - if (c->ismaster && c->old_master_inner_per < 1.0 && - c->old_master_inner_per > 0.0f && c->master_inner_per < 1.0 && - c->master_inner_per > 0.0f) { - c->master_inner_per = (1.0 - c->master_inner_per) * - c->old_master_inner_per / - (1.0 - c->old_master_inner_per); - } - - wl_list_for_each(fc, &clients, link) { - if (VISIBLEON(fc, m) && ISTILED(fc) && fc != c && !fc->ismaster && - fc->old_ismaster && fc->old_stack_inner_per < 1.0 && - fc->old_stack_inner_per > 0.0f && fc->stack_inner_per < 1.0 && - fc->stack_inner_per > 0.0f) { - fc->stack_inner_per = (1.0 - fc->stack_inner_per) * - fc->old_stack_inner_per / - (1.0 - fc->old_stack_inner_per); - fc->old_ismaster = false; - } - } -} - void set_size_per(Monitor *m, Client *c) { Client *fc = NULL; bool found = false; @@ -1114,7 +1019,7 @@ void reset_size_per_mon(Monitor *m, int32_t tile_cilent_num, if (m->pertag->ltidxs[m->pertag->curtag]->id != CENTER_TILE) { wl_list_for_each(c, &clients, link) { - if (VISIBLEON(c, m) && ISTILED(c)) { + if (VISIBLEON(c, m) && ISFAKETILED(c)) { if (total_master_inner_percent > 0.0 && i < nmasters) { c->ismaster = true; @@ -1137,7 +1042,7 @@ void reset_size_per_mon(Monitor *m, int32_t tile_cilent_num, } } else { wl_list_for_each(c, &clients, link) { - if (VISIBLEON(c, m) && ISTILED(c)) { + if (VISIBLEON(c, m) && ISFAKETILED(c)) { if (total_master_inner_percent > 0.0 && i < nmasters) { c->ismaster = true; @@ -1196,6 +1101,7 @@ void pre_caculate_before_arrange(Monitor *m, bool want_animation, m->visible_clients = 0; m->visible_tiling_clients = 0; m->visible_scroll_tiling_clients = 0; + m->visible_fake_tiling_clients = 0; uint32_t tag = m->pertag->curtag; struct TagScrollerState *st = m->pertag->scroller_state[tag]; @@ -1239,6 +1145,10 @@ void pre_caculate_before_arrange(Monitor *m, bool want_animation, m->visible_scroll_tiling_clients++; } } + + if (ISFAKETILED(c)) { + m->visible_fake_tiling_clients++; + } } } @@ -1250,7 +1160,7 @@ void pre_caculate_before_arrange(Monitor *m, bool want_animation, if (c->mon == m) { if (VISIBLEON(c, m)) { - if (ISTILED(c)) { + if (ISFAKETILED(c)) { if (i < nmasters) { master_num++; total_master_inner_percent += c->master_inner_per; diff --git a/src/layout/horizontal.h b/src/layout/horizontal.h index 87fe2a96..01f0e8a6 100644 --- a/src/layout/horizontal.h +++ b/src/layout/horizontal.h @@ -10,21 +10,24 @@ void deck(Monitor *m) { int32_t cur_gappoh = enablegaps ? m->gappoh : 0; int32_t cur_gappov = enablegaps ? m->gappov : 0; - cur_gappih = - config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappih; - cur_gappoh = - config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; - cur_gappov = - config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; + cur_gappih = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappih; + cur_gappoh = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappoh; + cur_gappov = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappov; - n = m->visible_tiling_clients; + n = m->visible_fake_tiling_clients; if (n == 0) return; wl_list_for_each(fc, &clients, link) { - if (VISIBLEON(fc, m) && ISTILED(fc)) + if (VISIBLEON(fc, m) && ISFAKETILED(fc)) break; } @@ -40,12 +43,12 @@ void deck(Monitor *m) { i = my = 0; wl_list_for_each(c, &clients, link) { - if (!VISIBLEON(c, m) || !ISTILED(c)) + if (!VISIBLEON(c, m) || !ISFAKETILED(c)) continue; if (i < nmasters) { c->master_mfact_per = mfact; // Master area clients - resize( + client_tile_resize( c, (struct wlr_box){.x = m->w.x + cur_gappoh, .y = m->w.y + cur_gappov + my, @@ -57,13 +60,14 @@ void deck(Monitor *m) { } else { // Stack area clients c->master_mfact_per = mfact; - resize(c, - (struct wlr_box){.x = m->w.x + mw + cur_gappoh + cur_gappih, - .y = m->w.y + cur_gappov, - .width = m->w.width - mw - 2 * cur_gappoh - - cur_gappih, - .height = m->w.height - 2 * cur_gappov}, - 0); + client_tile_resize( + c, + (struct wlr_box){.x = m->w.x + mw + cur_gappoh + cur_gappih, + .y = m->w.y + cur_gappov, + .width = m->w.width - mw - 2 * cur_gappoh - + cur_gappih, + .height = m->w.height - 2 * cur_gappov}, + 0); if (c == focustop(m)) wlr_scene_node_raise_to_top(&c->scene->node); } @@ -79,7 +83,7 @@ void center_tile(Monitor *m) { int32_t master_num = 0; int32_t stack_num = 0; - n = m->visible_tiling_clients; + n = m->visible_fake_tiling_clients; master_num = m->pertag->nmasters[m->pertag->curtag]; master_num = n > master_num ? master_num : n; @@ -90,7 +94,7 @@ void center_tile(Monitor *m) { // 获取第一个可见的平铺客户端用于主区域宽度百分比 wl_list_for_each(fc, &clients, link) { - if (VISIBLEON(fc, m) && ISTILED(fc)) + if (VISIBLEON(fc, m) && ISFAKETILED(fc)) break; } @@ -101,14 +105,18 @@ void center_tile(Monitor *m) { int32_t cur_gappoh = enablegaps ? m->gappoh : 0; // 外部水平间隙 // 智能间隙处理 - cur_gappiv = - config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappiv; - cur_gappih = - config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappih; - cur_gappov = - config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; - cur_gappoh = - config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; + cur_gappiv = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappiv; + cur_gappih = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappih; + cur_gappov = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappov; + cur_gappoh = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappoh; int32_t nmasters = m->pertag->nmasters[m->pertag->curtag]; mfact = fc->master_mfact_per > 0.0f ? fc->master_mfact_per @@ -174,7 +182,7 @@ void center_tile(Monitor *m) { i = 0; wl_list_for_each(c, &clients, link) { - if (!VISIBLEON(c, m) || !ISTILED(c)) + if (!VISIBLEON(c, m) || !ISFAKETILED(c)) continue; if (i < nmasters) { @@ -196,12 +204,12 @@ void center_tile(Monitor *m) { c->master_mfact_per = mfact; } - resize(c, - (struct wlr_box){.x = m->w.x + mx, - .y = m->w.y + my, - .width = mw, - .height = h}, - 0); + client_tile_resize(c, + (struct wlr_box){.x = m->w.x + mx, + .y = m->w.y + my, + .width = mw, + .height = h}, + 0); my += c->geom.height + cur_gappiv * ie; } else { // 堆叠区域窗口 @@ -233,12 +241,12 @@ void center_tile(Monitor *m) { stack_x = m->w.x + mx + mw + cur_gappih * ie; } - resize(c, - (struct wlr_box){.x = stack_x, - .y = m->w.y + ety, - .width = tw, - .height = h}, - 0); + client_tile_resize(c, + (struct wlr_box){.x = stack_x, + .y = m->w.y + ety, + .width = tw, + .height = h}, + 0); ety += c->geom.height + cur_gappiv * ie; } else { // 多个堆叠窗口:交替放在左右两侧 @@ -266,12 +274,12 @@ void center_tile(Monitor *m) { int32_t stack_x = m->w.x + mx + mw + cur_gappih * ie; - resize(c, - (struct wlr_box){.x = stack_x, - .y = m->w.y + ety, - .width = tw, - .height = h}, - 0); + client_tile_resize(c, + (struct wlr_box){.x = stack_x, + .y = m->w.y + ety, + .width = tw, + .height = h}, + 0); ety += c->geom.height + cur_gappiv * ie; } else { // 左侧堆叠窗口 @@ -294,12 +302,12 @@ void center_tile(Monitor *m) { } int32_t stack_x = m->w.x + cur_gappoh; - resize(c, - (struct wlr_box){.x = stack_x, - .y = m->w.y + oty, - .width = tw, - .height = h}, - 0); + client_tile_resize(c, + (struct wlr_box){.x = stack_x, + .y = m->w.y + oty, + .width = tw, + .height = h}, + 0); oty += c->geom.height + cur_gappiv * ie; } } @@ -316,7 +324,7 @@ void tile(Monitor *m) { int32_t master_num = 0; int32_t stack_num = 0; - n = m->visible_tiling_clients; + n = m->visible_fake_tiling_clients; master_num = m->pertag->nmasters[m->pertag->curtag]; master_num = n > master_num ? master_num : n; stack_num = n - master_num; @@ -329,18 +337,22 @@ void tile(Monitor *m) { int32_t cur_gappov = enablegaps ? m->gappov : 0; int32_t cur_gappoh = enablegaps ? m->gappoh : 0; - cur_gappiv = - config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappiv; - cur_gappih = - config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappih; - cur_gappov = - config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; - cur_gappoh = - config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; + cur_gappiv = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappiv; + cur_gappih = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappih; + cur_gappov = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappov; + cur_gappoh = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappoh; wl_list_for_each(fc, &clients, link) { - if (VISIBLEON(fc, m) && ISTILED(fc)) + if (VISIBLEON(fc, m) && ISFAKETILED(fc)) break; } @@ -365,7 +377,7 @@ void tile(Monitor *m) { float slave_surplus_ratio = 1.0; wl_list_for_each(c, &clients, link) { - if (!VISIBLEON(c, m) || !ISTILED(c)) + if (!VISIBLEON(c, m) || !ISFAKETILED(c)) continue; if (i < m->pertag->nmasters[m->pertag->curtag]) { r = MIN(n, m->pertag->nmasters[m->pertag->curtag]) - i; @@ -384,12 +396,12 @@ void tile(Monitor *m) { cur_gappiv * ie * (r - 1)); c->master_mfact_per = mfact; } - resize(c, - (struct wlr_box){.x = m->w.x + cur_gappoh, - .y = m->w.y + my, - .width = mw - cur_gappih * ie, - .height = h}, - 0); + client_tile_resize(c, + (struct wlr_box){.x = m->w.x + cur_gappoh, + .y = m->w.y + my, + .width = mw - cur_gappih * ie, + .height = h}, + 0); my += c->geom.height + cur_gappiv * ie; } else { r = n - i; @@ -410,12 +422,13 @@ void tile(Monitor *m) { // wlr_log(WLR_ERROR, "stack_inner_per: %f", c->stack_inner_per); - resize(c, - (struct wlr_box){.x = m->w.x + mw + cur_gappoh, - .y = m->w.y + ty, - .width = m->w.width - mw - 2 * cur_gappoh, - .height = h}, - 0); + client_tile_resize( + c, + (struct wlr_box){.x = m->w.x + mw + cur_gappoh, + .y = m->w.y + ty, + .width = m->w.width - mw - 2 * cur_gappoh, + .height = h}, + 0); ty += c->geom.height + cur_gappiv * ie; } i++; @@ -430,7 +443,7 @@ void right_tile(Monitor *m) { int32_t master_num = 0; int32_t stack_num = 0; - n = m->visible_tiling_clients; + n = m->visible_fake_tiling_clients; master_num = m->pertag->nmasters[m->pertag->curtag]; master_num = n > master_num ? master_num : n; stack_num = n - master_num; @@ -443,18 +456,22 @@ void right_tile(Monitor *m) { int32_t cur_gappov = enablegaps ? m->gappov : 0; int32_t cur_gappoh = enablegaps ? m->gappoh : 0; - cur_gappiv = - config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappiv; - cur_gappih = - config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappih; - cur_gappov = - config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; - cur_gappoh = - config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; + cur_gappiv = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappiv; + cur_gappih = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappih; + cur_gappov = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappov; + cur_gappoh = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappoh; wl_list_for_each(fc, &clients, link) { - if (VISIBLEON(fc, m) && ISTILED(fc)) + if (VISIBLEON(fc, m) && ISFAKETILED(fc)) break; } @@ -479,7 +496,7 @@ void right_tile(Monitor *m) { float slave_surplus_ratio = 1.0; wl_list_for_each(c, &clients, link) { - if (!VISIBLEON(c, m) || !ISTILED(c)) + if (!VISIBLEON(c, m) || !ISFAKETILED(c)) continue; if (i < m->pertag->nmasters[m->pertag->curtag]) { r = MIN(n, m->pertag->nmasters[m->pertag->curtag]) - i; @@ -498,13 +515,14 @@ void right_tile(Monitor *m) { cur_gappiv * ie * (r - 1)); c->master_mfact_per = mfact; } - resize(c, - (struct wlr_box){.x = m->w.x + m->w.width - mw - cur_gappoh + - cur_gappih * ie, - .y = m->w.y + my, - .width = mw - cur_gappih * ie, - .height = h}, - 0); + client_tile_resize(c, + (struct wlr_box){.x = m->w.x + m->w.width - mw - + cur_gappoh + + cur_gappih * ie, + .y = m->w.y + my, + .width = mw - cur_gappih * ie, + .height = h}, + 0); my += c->geom.height + cur_gappiv * ie; } else { r = n - i; @@ -525,12 +543,13 @@ void right_tile(Monitor *m) { // wlr_log(WLR_ERROR, "stack_inner_per: %f", c->stack_inner_per); - resize(c, - (struct wlr_box){.x = m->w.x + cur_gappoh, - .y = m->w.y + ty, - .width = m->w.width - mw - 2 * cur_gappoh, - .height = h}, - 0); + client_tile_resize( + c, + (struct wlr_box){.x = m->w.x + cur_gappoh, + .y = m->w.y + ty, + .width = m->w.width - mw - 2 * cur_gappoh, + .height = h}, + 0); ty += c->geom.height + cur_gappiv * ie; } i++; @@ -545,19 +564,21 @@ monocle(Monitor *m) { int32_t cur_gappov = enablegaps ? m->gappov : 0; int32_t cur_gappoh = enablegaps ? m->gappoh : 0; - cur_gappoh = - config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; - cur_gappov = - config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; + cur_gappoh = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappoh; + cur_gappov = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappov; wl_list_for_each(c, &clients, link) { - if (!VISIBLEON(c, m) || !ISTILED(c)) + if (!VISIBLEON(c, m) || !ISFAKETILED(c)) continue; geom.x = m->w.x + cur_gappoh; geom.y = m->w.y + cur_gappov; geom.width = m->w.width - 2 * cur_gappoh; geom.height = m->w.height - 2 * cur_gappov; - resize(c, geom, 0); + client_tile_resize(c, geom, 0); } if ((c = focustop(m))) wlr_scene_node_raise_to_top(&c->scene->node); @@ -570,14 +591,12 @@ void grid(Monitor *m) { int32_t cols, rows, overcols; Client *c = NULL; n = 0; - int32_t target_gappo = - enablegaps ? m->isoverview ? config.overviewgappo : config.gappoh : 0; - int32_t target_gappi = - enablegaps ? m->isoverview ? config.overviewgappi : config.gappih : 0; - float single_width_ratio = m->isoverview ? 0.7 : 0.9; - float single_height_ratio = m->isoverview ? 0.8 : 0.9; + int32_t target_gappo = enablegaps ? config.gappoh : 0; + int32_t target_gappi = enablegaps ? config.gappih : 0; + float single_width_ratio = 0.9; + float single_height_ratio = 0.9; - n = m->isoverview ? m->visible_clients : m->visible_tiling_clients; + n = m->visible_fake_tiling_clients; if (n == 0) return; @@ -587,14 +606,15 @@ void grid(Monitor *m) { if (c->mon != m) continue; if (VISIBLEON(c, m) && !c->isunglobal && - ((m->isoverview && !client_is_x11_popup(c)) || ISTILED(c))) { + ((m->isoverview && !client_is_x11_popup(c)) || + ISFAKETILED(c))) { cw = (m->w.width - 2 * target_gappo) * single_width_ratio; ch = (m->w.height - 2 * target_gappo) * single_height_ratio; c->geom.x = m->w.x + (m->w.width - cw) / 2; c->geom.y = m->w.y + (m->w.height - ch) / 2; c->geom.width = cw; c->geom.height = ch; - resize(c, c->geom, 0); + client_tile_resize(c, c->geom, 0); return; } } @@ -608,7 +628,8 @@ void grid(Monitor *m) { if (c->mon != m) continue; if (VISIBLEON(c, m) && !c->isunglobal && - ((m->isoverview && !client_is_x11_popup(c)) || ISTILED(c))) { + ((m->isoverview && !client_is_x11_popup(c)) || + ISFAKETILED(c))) { if (i < 2) col_pers[i] = (c->grid_col_per > 0.0f) ? c->grid_col_per : 1.0f; @@ -626,7 +647,8 @@ void grid(Monitor *m) { if (c->mon != m) continue; if (VISIBLEON(c, m) && !c->isunglobal && - ((m->isoverview && !client_is_x11_popup(c)) || ISTILED(c))) { + ((m->isoverview && !client_is_x11_popup(c)) || + ISFAKETILED(c))) { c->grid_col_idx = i; c->grid_row_idx = 0; c->grid_col_per = col_pers[i]; @@ -645,7 +667,7 @@ void grid(Monitor *m) { c->geom.y = m->w.y + (m->w.height - ch) / 2 + target_gappo; c->geom.width = cw; c->geom.height = ch; - resize(c, c->geom, 0); + client_tile_resize(c, c->geom, 0); i++; } } @@ -673,7 +695,7 @@ void grid(Monitor *m) { if (c->mon != m) continue; if (VISIBLEON(c, m) && !c->isunglobal && - ((m->isoverview && !client_is_x11_popup(c)) || ISTILED(c))) { + ((m->isoverview && !client_is_x11_popup(c)) || ISFAKETILED(c))) { int32_t c_idx = i % cols; int32_t r_idx = i / cols; if (r_idx == 0) @@ -701,7 +723,7 @@ void grid(Monitor *m) { if (c->mon != m) continue; if (VISIBLEON(c, m) && !c->isunglobal && - ((m->isoverview && !client_is_x11_popup(c)) || ISTILED(c))) { + ((m->isoverview && !client_is_x11_popup(c)) || ISFAKETILED(c))) { int32_t c_idx = i % cols; int32_t r_idx = i / cols; @@ -746,7 +768,7 @@ void grid(Monitor *m) { c->geom.y = (int32_t)fl_cy; c->geom.width = (int32_t)fl_cw; c->geom.height = (int32_t)fl_ch; - resize(c, c->geom, 0); + client_tile_resize(c, c->geom, 0); i++; } } @@ -756,7 +778,7 @@ void fair(Monitor *m) { int32_t i, n = 0; Client *c = NULL; - n = m->visible_tiling_clients; + n = m->visible_fake_tiling_clients; if (n == 0) return; @@ -787,7 +809,7 @@ void fair(Monitor *m) { Client *arr[n]; int32_t arr_idx = 0; wl_list_for_each(c, &clients, link) { - if (VISIBLEON(c, m) && ISTILED(c)) { + if (VISIBLEON(c, m) && ISFAKETILED(c)) { arr[arr_idx++] = c; if (arr_idx >= n) break; // 安全边界 @@ -917,11 +939,11 @@ void fair(Monitor *m) { fl_cx = col_x[col_idx]; fl_cw = col_w[col_idx]; - resize(c, - (struct wlr_box){.x = (int32_t)fl_cx, - .y = (int32_t)fl_cy, - .width = (int32_t)fl_cw, - .height = (int32_t)fl_ch}, - 0); + client_tile_resize(c, + (struct wlr_box){.x = (int32_t)fl_cx, + .y = (int32_t)fl_cy, + .width = (int32_t)fl_cw, + .height = (int32_t)fl_ch}, + 0); } } \ No newline at end of file diff --git a/src/layout/vertical.h b/src/layout/vertical.h index 733ee364..fa54ae2d 100644 --- a/src/layout/vertical.h +++ b/src/layout/vertical.h @@ -6,7 +6,7 @@ void vertical_tile(Monitor *m) { int32_t master_num = 0; int32_t stack_num = 0; - n = m->visible_tiling_clients; + n = m->visible_fake_tiling_clients; master_num = m->pertag->nmasters[m->pertag->curtag]; master_num = n > master_num ? master_num : n; stack_num = n - master_num; @@ -20,16 +20,16 @@ void vertical_tile(Monitor *m) { int32_t cur_gapov = enablegaps ? m->gappov : 0; cur_gapih = - config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gapih; + config.smartgaps && m->visible_fake_tiling_clients == 1 ? 0 : cur_gapih; cur_gapiv = - config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gapiv; + config.smartgaps && m->visible_fake_tiling_clients == 1 ? 0 : cur_gapiv; cur_gapoh = - config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gapoh; + config.smartgaps && m->visible_fake_tiling_clients == 1 ? 0 : cur_gapoh; cur_gapov = - config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gapov; + config.smartgaps && m->visible_fake_tiling_clients == 1 ? 0 : cur_gapov; wl_list_for_each(fc, &clients, link) { - if (VISIBLEON(fc, m) && ISTILED(fc)) + if (VISIBLEON(fc, m) && ISFAKETILED(fc)) break; } @@ -55,7 +55,7 @@ void vertical_tile(Monitor *m) { float slave_surplus_ratio = 1.0; wl_list_for_each(c, &clients, link) { - if (!VISIBLEON(c, m) || !ISTILED(c)) + if (!VISIBLEON(c, m) || !ISFAKETILED(c)) continue; if (i < m->pertag->nmasters[m->pertag->curtag]) { r = MIN(n, m->pertag->nmasters[m->pertag->curtag]) - i; @@ -73,12 +73,12 @@ void vertical_tile(Monitor *m) { cur_gapih * ie * (r - 1)); c->master_mfact_per = mfact; } - resize(c, - (struct wlr_box){.x = m->w.x + mx, - .y = m->w.y + cur_gapov, - .width = w, - .height = mh - cur_gapiv * ie}, - 0); + client_tile_resize(c, + (struct wlr_box){.x = m->w.x + mx, + .y = m->w.y + cur_gapov, + .width = w, + .height = mh - cur_gapiv * ie}, + 0); mx += c->geom.width + cur_gapih * ie; } else { r = n - i; @@ -96,12 +96,13 @@ void vertical_tile(Monitor *m) { c->master_mfact_per = mfact; } - resize(c, - (struct wlr_box){.x = m->w.x + tx, - .y = m->w.y + mh + cur_gapov, - .width = w, - .height = m->w.height - mh - 2 * cur_gapov}, - 0); + client_tile_resize( + c, + (struct wlr_box){.x = m->w.x + tx, + .y = m->w.y + mh + cur_gapov, + .width = w, + .height = m->w.height - mh - 2 * cur_gapov}, + 0); tx += c->geom.width + cur_gapih * ie; } i++; @@ -120,21 +121,24 @@ void vertical_deck(Monitor *m) { int32_t cur_gappoh = enablegaps ? m->gappoh : 0; int32_t cur_gappov = enablegaps ? m->gappov : 0; - cur_gappiv = - config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappiv; - cur_gappoh = - config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; - cur_gappov = - config.smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; + cur_gappiv = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappiv; + cur_gappoh = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappoh; + cur_gappov = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappov; - n = m->visible_tiling_clients; + n = m->visible_fake_tiling_clients; if (n == 0) return; wl_list_for_each(fc, &clients, link) { - if (VISIBLEON(fc, m) && ISTILED(fc)) + if (VISIBLEON(fc, m) && ISFAKETILED(fc)) break; } @@ -149,10 +153,10 @@ void vertical_deck(Monitor *m) { i = mx = 0; wl_list_for_each(c, &clients, link) { - if (!VISIBLEON(c, m) || !ISTILED(c)) + if (!VISIBLEON(c, m) || !ISFAKETILED(c)) continue; if (i < nmasters) { - resize( + client_tile_resize( c, (struct wlr_box){.x = m->w.x + cur_gappoh + mx, .y = m->w.y + cur_gappov, @@ -162,13 +166,14 @@ void vertical_deck(Monitor *m) { 0); mx += c->geom.width; } else { - resize(c, - (struct wlr_box){.x = m->w.x + cur_gappoh, - .y = m->w.y + mh + cur_gappov + cur_gappiv, - .width = m->w.width - 2 * cur_gappoh, - .height = m->w.height - mh - - 2 * cur_gappov - cur_gappiv}, - 0); + client_tile_resize( + c, + (struct wlr_box){.x = m->w.x + cur_gappoh, + .y = m->w.y + mh + cur_gappov + cur_gappiv, + .width = m->w.width - 2 * cur_gappoh, + .height = m->w.height - mh - 2 * cur_gappov - + cur_gappiv}, + 0); if (c == focustop(m)) wlr_scene_node_raise_to_top(&c->scene->node); } @@ -188,7 +193,7 @@ void vertical_grid(Monitor *m) { float single_width_ratio = m->isoverview ? 0.7 : 0.9; float single_height_ratio = m->isoverview ? 0.8 : 0.9; - n = m->isoverview ? m->visible_clients : m->visible_tiling_clients; + n = m->isoverview ? m->visible_clients : m->visible_fake_tiling_clients; if (n == 0) return; @@ -197,14 +202,15 @@ void vertical_grid(Monitor *m) { if (c->mon != m) continue; if (VISIBLEON(c, m) && !c->isunglobal && - ((m->isoverview && !client_is_x11_popup(c)) || ISTILED(c))) { + ((m->isoverview && !client_is_x11_popup(c)) || + ISFAKETILED(c))) { ch = (m->w.height - 2 * target_gappo) * single_height_ratio; cw = (m->w.width - 2 * target_gappo) * single_width_ratio; c->geom.x = m->w.x + (m->w.width - cw) / 2; c->geom.y = m->w.y + (m->w.height - ch) / 2; c->geom.width = cw; c->geom.height = ch; - resize(c, c->geom, 0); + client_tile_resize(c, c->geom, 0); return; } } @@ -218,7 +224,8 @@ void vertical_grid(Monitor *m) { if (c->mon != m) continue; if (VISIBLEON(c, m) && !c->isunglobal && - ((m->isoverview && !client_is_x11_popup(c)) || ISTILED(c))) { + ((m->isoverview && !client_is_x11_popup(c)) || + ISFAKETILED(c))) { if (i < 2) row_pers[i] = (c->grid_row_per > 0.0f) ? c->grid_row_per : 1.0f; @@ -235,7 +242,8 @@ void vertical_grid(Monitor *m) { if (c->mon != m) continue; if (VISIBLEON(c, m) && !c->isunglobal && - ((m->isoverview && !client_is_x11_popup(c)) || ISTILED(c))) { + ((m->isoverview && !client_is_x11_popup(c)) || + ISFAKETILED(c))) { c->grid_col_idx = 0; c->grid_row_idx = i; c->grid_col_per = 1.0f; @@ -254,7 +262,7 @@ void vertical_grid(Monitor *m) { } c->geom.width = cw; c->geom.height = ch; - resize(c, c->geom, 0); + client_tile_resize(c, c->geom, 0); i++; } } @@ -279,7 +287,7 @@ void vertical_grid(Monitor *m) { if (c->mon != m) continue; if (VISIBLEON(c, m) && !c->isunglobal && - ((m->isoverview && !client_is_x11_popup(c)) || ISTILED(c))) { + ((m->isoverview && !client_is_x11_popup(c)) || ISFAKETILED(c))) { int32_t c_idx = i / rows; int32_t r_idx = i % rows; if (r_idx == 0) @@ -306,7 +314,7 @@ void vertical_grid(Monitor *m) { if (c->mon != m) continue; if (VISIBLEON(c, m) && !c->isunglobal && - ((m->isoverview && !client_is_x11_popup(c)) || ISTILED(c))) { + ((m->isoverview && !client_is_x11_popup(c)) || ISFAKETILED(c))) { int32_t c_idx = i / rows; int32_t r_idx = i % rows; @@ -348,7 +356,7 @@ void vertical_grid(Monitor *m) { c->geom.y = (int32_t)fl_cy; c->geom.width = (int32_t)fl_cw; c->geom.height = (int32_t)fl_ch; - resize(c, c->geom, 0); + client_tile_resize(c, c->geom, 0); i++; } } @@ -358,7 +366,7 @@ void vertical_fair(Monitor *m) { int32_t i, n = 0; Client *c = NULL; - n = m->visible_tiling_clients; + n = m->visible_fake_tiling_clients; if (n == 0) return; @@ -386,7 +394,7 @@ void vertical_fair(Monitor *m) { Client *arr[n]; int32_t arr_idx = 0; wl_list_for_each(c, &clients, link) { - if (VISIBLEON(c, m) && ISTILED(c)) { + if (VISIBLEON(c, m) && ISFAKETILED(c)) { arr[arr_idx++] = c; if (arr_idx >= n) break; @@ -511,11 +519,11 @@ void vertical_fair(Monitor *m) { fl_cy = row_y[row_idx]; fl_ch = row_h[row_idx]; - resize(c, - (struct wlr_box){.x = (int32_t)fl_cx, - .y = (int32_t)fl_cy, - .width = (int32_t)fl_cw, - .height = (int32_t)fl_ch}, - 0); + client_tile_resize(c, + (struct wlr_box){.x = (int32_t)fl_cx, + .y = (int32_t)fl_cy, + .width = (int32_t)fl_cw, + .height = (int32_t)fl_ch}, + 0); } } \ No newline at end of file diff --git a/src/mango.c b/src/mango.c index dae2c593..43eecf6d 100644 --- a/src/mango.c +++ b/src/mango.c @@ -116,6 +116,9 @@ #define ISSCROLLTILED(A) \ (A && !(A)->isfloating && !(A)->isminimized && !(A)->iskilling && \ !(A)->isunglobal) +#define ISFAKETILED(A) \ + (A && !(A)->isfloating && !(A)->isminimized && !(A)->iskilling && \ + !(A)->isunglobal) #define VISIBLEON(C, M) \ ((C) && (M) && (C)->mon == (M) && \ (((C)->tags & (M)->tagset[(M)->seltags] || (C)->isglobal || \ @@ -552,6 +555,7 @@ struct Monitor { uint32_t visible_clients; uint32_t visible_tiling_clients; uint32_t visible_scroll_tiling_clients; + uint32_t visible_fake_tiling_clients; struct wlr_scene_optimized_blur *blur; char last_surface_ws_name[256]; struct wlr_ext_workspace_group_handle_v1 *ext_group; @@ -5373,15 +5377,6 @@ 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)) { - restore_size_per(c->mon, c); - } - - if (c->isfloating && !old_floating_state) { - save_old_size_per(c->mon); - } - if (!c->force_fakemaximize) client_set_maximized(c, false); @@ -5434,7 +5429,6 @@ void setmaximizescreen(Client *c, int32_t maximizescreen) { if (c->mon->isoverview) return; - int32_t old_maximizescreen_state = c->ismaximizescreen; client_pending_maximized_state(c, maximizescreen); if (maximizescreen) { @@ -5461,13 +5455,6 @@ void setmaximizescreen(Client *c, int32_t maximizescreen) { wlr_scene_node_reparent(&c->scene->node, layers[c->isfloating ? LyrTop : LyrTile]); - if (!c->ismaximizescreen && old_maximizescreen_state) { - restore_size_per(c->mon, c); - } - - if (c->ismaximizescreen && !old_maximizescreen_state) { - save_old_size_per(c->mon); - } if (!c->force_fakemaximize && !c->ismaximizescreen) { client_set_maximized(c, false); @@ -5498,7 +5485,6 @@ void setfullscreen(Client *c, int32_t fullscreen) // 用自定义全屏代理自 if (c->mon->isoverview) return; - int32_t old_fullscreen_state = c->isfullscreen; c->isfullscreen = fullscreen; client_set_fullscreen(c, fullscreen); @@ -5535,14 +5521,6 @@ void setfullscreen(Client *c, int32_t fullscreen) // 用自定义全屏代理自 layers[fullscreen || c->isfloating ? LyrTop : LyrTile]); } - if (!c->isfullscreen && old_fullscreen_state) { - restore_size_per(c->mon, c); - } - - if (c->isfullscreen && !old_fullscreen_state) { - save_old_size_per(c->mon); - } - arrange(c->mon, false, false); } From 7da47c9d94355f381f677f9df6046e3ce2e605fc Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 24 May 2026 12:29:20 +0800 Subject: [PATCH 238/328] opt: optimize the calculation method of stack layout coordinates --- src/layout/horizontal.h | 515 ++++++++++++++++++++-------------------- src/layout/vertical.h | 4 +- 2 files changed, 256 insertions(+), 263 deletions(-) diff --git a/src/layout/horizontal.h b/src/layout/horizontal.h index 01f0e8a6..92980731 100644 --- a/src/layout/horizontal.h +++ b/src/layout/horizontal.h @@ -1,75 +1,234 @@ -void deck(Monitor *m) { - int32_t mw, my; - int32_t i, n = 0; +void tile(Monitor *m) { + int32_t i, n = 0, h, r, ie = enablegaps, mw, my, ty; Client *c = NULL; Client *fc = NULL; - float mfact; - uint32_t nmasters = m->pertag->nmasters[m->pertag->curtag]; - - int32_t cur_gappih = enablegaps ? m->gappih : 0; - int32_t cur_gappoh = enablegaps ? m->gappoh : 0; - int32_t cur_gappov = enablegaps ? m->gappov : 0; - - cur_gappih = config.smartgaps && m->visible_fake_tiling_clients == 1 - ? 0 - : cur_gappih; - cur_gappoh = config.smartgaps && m->visible_fake_tiling_clients == 1 - ? 0 - : cur_gappoh; - cur_gappov = config.smartgaps && m->visible_fake_tiling_clients == 1 - ? 0 - : cur_gappov; + double mfact = 0; + int32_t master_num = 0; + int32_t stack_num = 0; n = m->visible_fake_tiling_clients; + master_num = m->pertag->nmasters[m->pertag->curtag]; + master_num = n > master_num ? master_num : n; + stack_num = n - master_num; if (n == 0) return; - wl_list_for_each(fc, &clients, link) { + int32_t cur_gappiv = enablegaps ? m->gappiv : 0; + int32_t cur_gappih = enablegaps ? m->gappih : 0; + int32_t cur_gappov = enablegaps ? m->gappov : 0; + int32_t cur_gappoh = enablegaps ? m->gappoh : 0; + cur_gappiv = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappiv; + cur_gappih = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappih; + cur_gappov = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappov; + cur_gappoh = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappoh; + + wl_list_for_each(fc, &clients, link) { if (VISIBLEON(fc, m) && ISFAKETILED(fc)) break; } - // Calculate master width using mfact from pertag mfact = fc->master_mfact_per > 0.0f ? fc->master_mfact_per : m->pertag->mfacts[m->pertag->curtag]; - // Calculate master width including outer gaps - if (n > nmasters) - mw = nmasters ? round((m->w.width - 2 * cur_gappoh) * mfact) : 0; + if (n > m->pertag->nmasters[m->pertag->curtag]) + mw = m->pertag->nmasters[m->pertag->curtag] + ? (m->w.width + cur_gappih * ie) * mfact + : 0; else - mw = m->w.width - 2 * cur_gappoh; + mw = m->w.width - 2 * cur_gappoh + cur_gappih * ie; + + i = 0; + my = ty = cur_gappov; + + int32_t master_surplus_height = + (m->w.height - 2 * cur_gappov - cur_gappiv * ie * (master_num - 1)); + float master_surplus_ratio = 1.0; + + int32_t slave_surplus_height = + (m->w.height - 2 * cur_gappov - cur_gappiv * ie * (stack_num - 1)); + float slave_surplus_ratio = 1.0; - i = my = 0; wl_list_for_each(c, &clients, link) { if (!VISIBLEON(c, m) || !ISFAKETILED(c)) continue; - if (i < nmasters) { - c->master_mfact_per = mfact; - // Master area clients + if (i < m->pertag->nmasters[m->pertag->curtag]) { + r = MIN(n, m->pertag->nmasters[m->pertag->curtag]) - i; + if (c->master_inner_per > 0.0f) { + h = master_surplus_height * c->master_inner_per / + master_surplus_ratio; + master_surplus_height = master_surplus_height - h; + master_surplus_ratio = + master_surplus_ratio - c->master_inner_per; + c->master_mfact_per = mfact; + } else { + h = (m->w.height - my - cur_gappov - + cur_gappiv * ie * (r - 1)) / + r; + c->master_inner_per = h / (m->w.height - my - cur_gappov - + cur_gappiv * ie * (r - 1)); + c->master_mfact_per = mfact; + } + client_tile_resize(c, + (struct wlr_box){.x = m->w.x + cur_gappoh, + .y = m->w.y + my, + .width = mw - cur_gappih * ie, + .height = h}, + 0); + my += h + cur_gappiv * ie; // 使用理论高度累加 + } else { + r = n - i; + if (c->stack_inner_per > 0.0f) { + h = slave_surplus_height * c->stack_inner_per / + slave_surplus_ratio; + slave_surplus_height = slave_surplus_height - h; + slave_surplus_ratio = slave_surplus_ratio - c->stack_inner_per; + c->master_mfact_per = mfact; + } else { + h = (m->w.height - ty - cur_gappov - + cur_gappiv * ie * (r - 1)) / + r; + c->stack_inner_per = h / (m->w.height - ty - cur_gappov - + cur_gappiv * ie * (r - 1)); + c->master_mfact_per = mfact; + } + + client_tile_resize( + c, + (struct wlr_box){.x = m->w.x + mw + cur_gappoh, + .y = m->w.y + ty, + .width = m->w.width - mw - 2 * cur_gappoh, + .height = h}, + 0); + ty += h + cur_gappiv * ie; // 使用理论高度累加 + } + i++; + } +} + +void right_tile(Monitor *m) { + int32_t i, n = 0, h, r, ie = enablegaps, mw, my, ty; + Client *c = NULL; + Client *fc = NULL; + double mfact = 0; + int32_t master_num = 0; + int32_t stack_num = 0; + + n = m->visible_fake_tiling_clients; + master_num = m->pertag->nmasters[m->pertag->curtag]; + master_num = n > master_num ? master_num : n; + stack_num = n - master_num; + + if (n == 0) + return; + + int32_t cur_gappiv = enablegaps ? m->gappiv : 0; + int32_t cur_gappih = enablegaps ? m->gappih : 0; + int32_t cur_gappov = enablegaps ? m->gappov : 0; + int32_t cur_gappoh = enablegaps ? m->gappoh : 0; + + cur_gappiv = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappiv; + cur_gappih = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappih; + cur_gappov = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappov; + cur_gappoh = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappoh; + + wl_list_for_each(fc, &clients, link) { + if (VISIBLEON(fc, m) && ISFAKETILED(fc)) + break; + } + + mfact = fc->master_mfact_per > 0.0f ? fc->master_mfact_per + : m->pertag->mfacts[m->pertag->curtag]; + + if (n > m->pertag->nmasters[m->pertag->curtag]) + mw = m->pertag->nmasters[m->pertag->curtag] + ? (m->w.width + cur_gappih * ie) * mfact + : 0; + else + mw = m->w.width - 2 * cur_gappoh + cur_gappih * ie; + + i = 0; + my = ty = cur_gappov; + + int32_t master_surplus_height = + (m->w.height - 2 * cur_gappov - cur_gappiv * ie * (master_num - 1)); + float master_surplus_ratio = 1.0; + + int32_t slave_surplus_height = + (m->w.height - 2 * cur_gappov - cur_gappiv * ie * (stack_num - 1)); + float slave_surplus_ratio = 1.0; + + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || !ISFAKETILED(c)) + continue; + if (i < m->pertag->nmasters[m->pertag->curtag]) { + r = MIN(n, m->pertag->nmasters[m->pertag->curtag]) - i; + if (c->master_inner_per > 0.0f) { + h = master_surplus_height * c->master_inner_per / + master_surplus_ratio; + master_surplus_height = master_surplus_height - h; + master_surplus_ratio = + master_surplus_ratio - c->master_inner_per; + c->master_mfact_per = mfact; + } else { + h = (m->w.height - my - cur_gappov - + cur_gappiv * ie * (r - 1)) / + r; + c->master_inner_per = h / (m->w.height - my - cur_gappov - + cur_gappiv * ie * (r - 1)); + c->master_mfact_per = mfact; + } + client_tile_resize(c, + (struct wlr_box){.x = m->w.x + m->w.width - mw - + cur_gappoh + + cur_gappih * ie, + .y = m->w.y + my, + .width = mw - cur_gappih * ie, + .height = h}, + 0); + my += h + cur_gappiv * ie; // 使用理论高度累加 + } else { + r = n - i; + if (c->stack_inner_per > 0.0f) { + h = slave_surplus_height * c->stack_inner_per / + slave_surplus_ratio; + slave_surplus_height = slave_surplus_height - h; + slave_surplus_ratio = slave_surplus_ratio - c->stack_inner_per; + c->master_mfact_per = mfact; + } else { + h = (m->w.height - ty - cur_gappov - + cur_gappiv * ie * (r - 1)) / + r; + c->stack_inner_per = h / (m->w.height - ty - cur_gappov - + cur_gappiv * ie * (r - 1)); + c->master_mfact_per = mfact; + } + client_tile_resize( c, (struct wlr_box){.x = m->w.x + cur_gappoh, - .y = m->w.y + cur_gappov + my, - .width = mw, - .height = (m->w.height - 2 * cur_gappov - my) / - (MIN(n, nmasters) - i)}, + .y = m->w.y + ty, + .width = m->w.width - mw - 2 * cur_gappoh, + .height = h}, 0); - my += c->geom.height; - } else { - // Stack area clients - c->master_mfact_per = mfact; - client_tile_resize( - c, - (struct wlr_box){.x = m->w.x + mw + cur_gappoh + cur_gappih, - .y = m->w.y + cur_gappov, - .width = m->w.width - mw - 2 * cur_gappoh - - cur_gappih, - .height = m->w.height - 2 * cur_gappov}, - 0); - if (c == focustop(m)) - wlr_scene_node_raise_to_top(&c->scene->node); + ty += h + cur_gappiv * ie; // 使用理论高度累加 } i++; } @@ -86,7 +245,6 @@ void center_tile(Monitor *m) { n = m->visible_fake_tiling_clients; master_num = m->pertag->nmasters[m->pertag->curtag]; master_num = n > master_num ? master_num : n; - stack_num = n - master_num; if (n == 0) @@ -159,11 +317,11 @@ void center_tile(Monitor *m) { if (config.center_when_single_stack) { // stack在右边,master居中,左边空着 tw = (m->w.width - mw) / 2 - cur_gappoh - cur_gappih * ie; - mx = cur_gappoh + tw + cur_gappih * ie; // master居中 + mx = cur_gappoh + tw + cur_gappih * ie; } else { // stack在右边,master在左边 tw = m->w.width - mw - 2 * cur_gappoh - cur_gappih * ie; - mx = cur_gappoh; // master在左边 + mx = cur_gappoh; } } else { // 只有主区域窗口:居中显示 @@ -174,7 +332,7 @@ void center_tile(Monitor *m) { // 主区域铺满模式(只有主区域窗口时) mw = m->w.width - 2 * cur_gappoh; mx = cur_gappoh; - tw = 0; // 堆叠区域宽度为0 + tw = 0; } oty = cur_gappov; @@ -210,7 +368,7 @@ void center_tile(Monitor *m) { .width = mw, .height = h}, 0); - my += c->geom.height + cur_gappiv * ie; + my += h + cur_gappiv * ie; // 使用理论高度累加 } else { // 堆叠区域窗口 int32_t stack_index = i - nmasters; @@ -234,10 +392,8 @@ void center_tile(Monitor *m) { int32_t stack_x; if (config.center_when_single_stack) { - // 放在右侧(master居中时,stack在右边) stack_x = m->w.x + mx + mw + cur_gappih * ie; } else { - // 放在右侧(master在左边时,stack在右边) stack_x = m->w.x + mx + mw + cur_gappih * ie; } @@ -247,7 +403,7 @@ void center_tile(Monitor *m) { .width = tw, .height = h}, 0); - ety += c->geom.height + cur_gappiv * ie; + ety += h + cur_gappiv * ie; // 使用理论高度累加 } else { // 多个堆叠窗口:交替放在左右两侧 r = (n - i + 1) / 2; @@ -280,7 +436,7 @@ void center_tile(Monitor *m) { .width = tw, .height = h}, 0); - ety += c->geom.height + cur_gappiv * ie; + ety += h + cur_gappiv * ie; // 使用理论高度累加 } else { // 左侧堆叠窗口 if (c->stack_inner_per > 0.0f) { @@ -308,7 +464,7 @@ void center_tile(Monitor *m) { .width = tw, .height = h}, 0); - oty += c->geom.height + cur_gappiv * ie; + oty += h + cur_gappiv * ie; // 使用理论高度累加 } } } @@ -316,39 +472,32 @@ void center_tile(Monitor *m) { } } -void tile(Monitor *m) { - int32_t i, n = 0, h, r, ie = enablegaps, mw, my, ty; +void deck(Monitor *m) { + int32_t mw, my; + int32_t i, n = 0; Client *c = NULL; Client *fc = NULL; - double mfact = 0; - int32_t master_num = 0; - int32_t stack_num = 0; + float mfact; + uint32_t nmasters = m->pertag->nmasters[m->pertag->curtag]; - n = m->visible_fake_tiling_clients; - master_num = m->pertag->nmasters[m->pertag->curtag]; - master_num = n > master_num ? master_num : n; - stack_num = n - master_num; - - if (n == 0) - return; - - int32_t cur_gappiv = enablegaps ? m->gappiv : 0; int32_t cur_gappih = enablegaps ? m->gappih : 0; - int32_t cur_gappov = enablegaps ? m->gappov : 0; int32_t cur_gappoh = enablegaps ? m->gappoh : 0; + int32_t cur_gappov = enablegaps ? m->gappov : 0; - cur_gappiv = config.smartgaps && m->visible_fake_tiling_clients == 1 - ? 0 - : cur_gappiv; cur_gappih = config.smartgaps && m->visible_fake_tiling_clients == 1 ? 0 : cur_gappih; - cur_gappov = config.smartgaps && m->visible_fake_tiling_clients == 1 - ? 0 - : cur_gappov; cur_gappoh = config.smartgaps && m->visible_fake_tiling_clients == 1 ? 0 : cur_gappoh; + cur_gappov = config.smartgaps && m->visible_fake_tiling_clients == 1 + ? 0 + : cur_gappov; + + n = m->visible_fake_tiling_clients; + + if (n == 0) + return; wl_list_for_each(fc, &clients, link) { @@ -356,201 +505,45 @@ void tile(Monitor *m) { break; } + // Calculate master width using mfact from pertag mfact = fc->master_mfact_per > 0.0f ? fc->master_mfact_per : m->pertag->mfacts[m->pertag->curtag]; - if (n > m->pertag->nmasters[m->pertag->curtag]) - mw = m->pertag->nmasters[m->pertag->curtag] - ? (m->w.width + cur_gappih * ie) * mfact - : 0; + // Calculate master width including outer gaps + if (n > nmasters) + mw = nmasters ? round((m->w.width - 2 * cur_gappoh) * mfact) : 0; else - mw = m->w.width - 2 * cur_gappoh + cur_gappih * ie; - i = 0; - my = ty = cur_gappov; - - int32_t master_surplus_height = - (m->w.height - 2 * cur_gappov - cur_gappiv * ie * (master_num - 1)); - float master_surplus_ratio = 1.0; - - int32_t slave_surplus_height = - (m->w.height - 2 * cur_gappov - cur_gappiv * ie * (stack_num - 1)); - float slave_surplus_ratio = 1.0; + mw = m->w.width - 2 * cur_gappoh; + i = my = 0; wl_list_for_each(c, &clients, link) { if (!VISIBLEON(c, m) || !ISFAKETILED(c)) continue; - if (i < m->pertag->nmasters[m->pertag->curtag]) { - r = MIN(n, m->pertag->nmasters[m->pertag->curtag]) - i; - if (c->master_inner_per > 0.0f) { - h = master_surplus_height * c->master_inner_per / - master_surplus_ratio; - master_surplus_height = master_surplus_height - h; - master_surplus_ratio = - master_surplus_ratio - c->master_inner_per; - c->master_mfact_per = mfact; - } else { - h = (m->w.height - my - cur_gappov - - cur_gappiv * ie * (r - 1)) / - r; - c->master_inner_per = h / (m->w.height - my - cur_gappov - - cur_gappiv * ie * (r - 1)); - c->master_mfact_per = mfact; - } - client_tile_resize(c, - (struct wlr_box){.x = m->w.x + cur_gappoh, - .y = m->w.y + my, - .width = mw - cur_gappih * ie, - .height = h}, - 0); - my += c->geom.height + cur_gappiv * ie; - } else { - r = n - i; - if (c->stack_inner_per > 0.0f) { - h = slave_surplus_height * c->stack_inner_per / - slave_surplus_ratio; - slave_surplus_height = slave_surplus_height - h; - slave_surplus_ratio = slave_surplus_ratio - c->stack_inner_per; - c->master_mfact_per = mfact; - } else { - h = (m->w.height - ty - cur_gappov - - cur_gappiv * ie * (r - 1)) / - r; - c->stack_inner_per = h / (m->w.height - ty - cur_gappov - - cur_gappiv * ie * (r - 1)); - c->master_mfact_per = mfact; - } - - // wlr_log(WLR_ERROR, "stack_inner_per: %f", c->stack_inner_per); - - client_tile_resize( - c, - (struct wlr_box){.x = m->w.x + mw + cur_gappoh, - .y = m->w.y + ty, - .width = m->w.width - mw - 2 * cur_gappoh, - .height = h}, - 0); - ty += c->geom.height + cur_gappiv * ie; - } - i++; - } -} - -void right_tile(Monitor *m) { - int32_t i, n = 0, h, r, ie = enablegaps, mw, my, ty; - Client *c = NULL; - Client *fc = NULL; - double mfact = 0; - int32_t master_num = 0; - int32_t stack_num = 0; - - n = m->visible_fake_tiling_clients; - master_num = m->pertag->nmasters[m->pertag->curtag]; - master_num = n > master_num ? master_num : n; - stack_num = n - master_num; - - if (n == 0) - return; - - int32_t cur_gappiv = enablegaps ? m->gappiv : 0; - int32_t cur_gappih = enablegaps ? m->gappih : 0; - int32_t cur_gappov = enablegaps ? m->gappov : 0; - int32_t cur_gappoh = enablegaps ? m->gappoh : 0; - - cur_gappiv = config.smartgaps && m->visible_fake_tiling_clients == 1 - ? 0 - : cur_gappiv; - cur_gappih = config.smartgaps && m->visible_fake_tiling_clients == 1 - ? 0 - : cur_gappih; - cur_gappov = config.smartgaps && m->visible_fake_tiling_clients == 1 - ? 0 - : cur_gappov; - cur_gappoh = config.smartgaps && m->visible_fake_tiling_clients == 1 - ? 0 - : cur_gappoh; - - wl_list_for_each(fc, &clients, link) { - - if (VISIBLEON(fc, m) && ISFAKETILED(fc)) - break; - } - - mfact = fc->master_mfact_per > 0.0f ? fc->master_mfact_per - : m->pertag->mfacts[m->pertag->curtag]; - - if (n > m->pertag->nmasters[m->pertag->curtag]) - mw = m->pertag->nmasters[m->pertag->curtag] - ? (m->w.width + cur_gappih * ie) * mfact - : 0; - else - mw = m->w.width - 2 * cur_gappoh + cur_gappih * ie; - i = 0; - my = ty = cur_gappov; - - int32_t master_surplus_height = - (m->w.height - 2 * cur_gappov - cur_gappiv * ie * (master_num - 1)); - float master_surplus_ratio = 1.0; - - int32_t slave_surplus_height = - (m->w.height - 2 * cur_gappov - cur_gappiv * ie * (stack_num - 1)); - float slave_surplus_ratio = 1.0; - - wl_list_for_each(c, &clients, link) { - if (!VISIBLEON(c, m) || !ISFAKETILED(c)) - continue; - if (i < m->pertag->nmasters[m->pertag->curtag]) { - r = MIN(n, m->pertag->nmasters[m->pertag->curtag]) - i; - if (c->master_inner_per > 0.0f) { - h = master_surplus_height * c->master_inner_per / - master_surplus_ratio; - master_surplus_height = master_surplus_height - h; - master_surplus_ratio = - master_surplus_ratio - c->master_inner_per; - c->master_mfact_per = mfact; - } else { - h = (m->w.height - my - cur_gappov - - cur_gappiv * ie * (r - 1)) / - r; - c->master_inner_per = h / (m->w.height - my - cur_gappov - - cur_gappiv * ie * (r - 1)); - c->master_mfact_per = mfact; - } - client_tile_resize(c, - (struct wlr_box){.x = m->w.x + m->w.width - mw - - cur_gappoh + - cur_gappih * ie, - .y = m->w.y + my, - .width = mw - cur_gappih * ie, - .height = h}, - 0); - my += c->geom.height + cur_gappiv * ie; - } else { - r = n - i; - if (c->stack_inner_per > 0.0f) { - h = slave_surplus_height * c->stack_inner_per / - slave_surplus_ratio; - slave_surplus_height = slave_surplus_height - h; - slave_surplus_ratio = slave_surplus_ratio - c->stack_inner_per; - c->master_mfact_per = mfact; - } else { - h = (m->w.height - ty - cur_gappov - - cur_gappiv * ie * (r - 1)) / - r; - c->stack_inner_per = h / (m->w.height - ty - cur_gappov - - cur_gappiv * ie * (r - 1)); - c->master_mfact_per = mfact; - } - - // wlr_log(WLR_ERROR, "stack_inner_per: %f", c->stack_inner_per); - + if (i < nmasters) { + c->master_mfact_per = mfact; + // Master area clients client_tile_resize( c, (struct wlr_box){.x = m->w.x + cur_gappoh, - .y = m->w.y + ty, - .width = m->w.width - mw - 2 * cur_gappoh, - .height = h}, + .y = m->w.y + cur_gappov + my, + .width = mw, + .height = (m->w.height - 2 * cur_gappov - my) / + (MIN(n, nmasters) - i)}, 0); - ty += c->geom.height + cur_gappiv * ie; + my += c->geom.height; + } else { + // Stack area clients + c->master_mfact_per = mfact; + client_tile_resize( + c, + (struct wlr_box){.x = m->w.x + mw + cur_gappoh + cur_gappih, + .y = m->w.y + cur_gappov, + .width = m->w.width - mw - 2 * cur_gappoh - + cur_gappih, + .height = m->w.height - 2 * cur_gappov}, + 0); + if (c == focustop(m)) + wlr_scene_node_raise_to_top(&c->scene->node); } i++; } diff --git a/src/layout/vertical.h b/src/layout/vertical.h index fa54ae2d..523467f9 100644 --- a/src/layout/vertical.h +++ b/src/layout/vertical.h @@ -79,7 +79,7 @@ void vertical_tile(Monitor *m) { .width = w, .height = mh - cur_gapiv * ie}, 0); - mx += c->geom.width + cur_gapih * ie; + mx += w + cur_gapih * ie; // 使用理论宽度累加 } else { r = n - i; if (c->stack_inner_per > 0.0f) { @@ -103,7 +103,7 @@ void vertical_tile(Monitor *m) { .width = w, .height = m->w.height - mh - 2 * cur_gapov}, 0); - tx += c->geom.width + cur_gapih * ie; + tx += w + cur_gapih * ie; // 使用理论宽度累加 } i++; } From 8df29e49220c4739e98cc252df75df4a4498ceaf Mon Sep 17 00:00:00 2001 From: ernestoCruz05 <a2023144672@isec.pt> Date: Fri, 10 Apr 2026 21:22:57 +0100 Subject: [PATCH 239/328] fix: add support for _have_client movement --- src/dispatch/bind_define.h | 54 +++++++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index ed91bb40..dfc64fb9 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1566,16 +1566,13 @@ int32_t viewtoleft_have_client(const Arg *arg) { if (!selmon) return 0; + if (selmon->isoverview) + return 0; + uint32_t n; uint32_t current = get_tags_first_tag_num(selmon->tagset[selmon->seltags]); bool found = false; - - if (selmon->isoverview) { - return 0; - } - - if (current <= 1) - return 0; + bool wrapped = false; for (n = current - 1; n >= 1; n--) { if (get_tag_status(n, selmon)) { @@ -1584,8 +1581,22 @@ int32_t viewtoleft_have_client(const Arg *arg) { } } - if (found) + if (!found && config.tag_carousel) { + for (n = LENGTH(tags); n > current; n--) { + if (get_tag_status(n, selmon)) { + found = true; + wrapped = true; + break; + } + } + } + + if (found) { + if (wrapped) + selmon->carousel_anim_dir = -1; view(&(Arg){.ui = (1 << (n - 1)) & TAGMASK, .i = arg->i}, true); + selmon->carousel_anim_dir = 0; + } return 0; } @@ -1593,16 +1604,13 @@ int32_t viewtoright_have_client(const Arg *arg) { if (!selmon) return 0; + if (selmon->isoverview) + return 0; + uint32_t n; uint32_t current = get_tags_first_tag_num(selmon->tagset[selmon->seltags]); bool found = false; - - if (selmon->isoverview) { - return 0; - } - - if (current >= LENGTH(tags)) - return 0; + bool wrapped = false; for (n = current + 1; n <= LENGTH(tags); n++) { if (get_tag_status(n, selmon)) { @@ -1611,8 +1619,22 @@ int32_t viewtoright_have_client(const Arg *arg) { } } - if (found) + if (!found && config.tag_carousel) { + for (n = 1; n < current; n++) { + if (get_tag_status(n, selmon)) { + found = true; + wrapped = true; + break; + } + } + } + + if (found) { + if (wrapped) + selmon->carousel_anim_dir = 1; view(&(Arg){.ui = (1 << (n - 1)) & TAGMASK, .i = arg->i}, true); + selmon->carousel_anim_dir = 0; + } return 0; } From 9d1dbdf92f5900b8f06734728bbb2b4a68270ac3 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 24 May 2026 13:31:31 +0800 Subject: [PATCH 240/328] feat: new ipc impl --- format.sh | 2 +- meson.build | 2 + mmsg/arg.h | 65 --- mmsg/dynarr.h | 30 -- mmsg/mmsg.c | 804 +++--------------------------- src/action/client.h | 5 +- src/config/parse_config.h | 6 + src/dispatch/bind_declare.h | 3 +- src/dispatch/bind_define.h | 263 ++++------ src/ext-protocol/dwl-ipc.h | 2 +- src/fetch/client.h | 8 +- src/ipc/ipc.h | 970 ++++++++++++++++++++++++++++++++++++ src/mango.c | 63 +-- 13 files changed, 1206 insertions(+), 1017 deletions(-) delete mode 100644 mmsg/arg.h delete mode 100644 mmsg/dynarr.h create mode 100644 src/ipc/ipc.h diff --git a/format.sh b/format.sh index 1291ff8f..6b204b00 100644 --- a/format.sh +++ b/format.sh @@ -1,3 +1,3 @@ #!/usr/bin/bash -clang-format -i src/*/*.h -i src/*/*.c -i src/mango.c -i mmsg/mmsg.c -i mmsg/arg.h -i mmsg/dynarr.h +clang-format -i src/*/*.h -i src/*/*.c -i src/mango.c -i mmsg/mmsg.c diff --git a/meson.build b/meson.build index 899439c8..270c4688 100644 --- a/meson.build +++ b/meson.build @@ -40,6 +40,7 @@ libwayland_client_dep = dependency('wayland-client') pcre2_dep = dependency('libpcre2-8') libscenefx_dep = dependency('scenefx-0.4',version: '>=0.4.1') pixman_dep = dependency('pixman-1') +cjson_dep = dependency('libcjson') # 获取版本信息 @@ -112,6 +113,7 @@ executable('mango', libwayland_client_dep, pcre2_dep, pixman_dep, + cjson_dep, ], install : true, c_args : c_args, diff --git a/mmsg/arg.h b/mmsg/arg.h deleted file mode 100644 index c3b0d7b9..00000000 --- a/mmsg/arg.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copy me if you can. - * by 20h - */ - -#ifndef ARG_H__ -#define ARG_H__ - -extern char *argv0; - -/* use main(int32_t argc, char *argv[]) */ -#define ARGBEGIN \ - for (argv0 = *argv, argv++, argc--; \ - argv[0] && argv[0][0] == '-' && argv[0][1]; argc--, argv++) { \ - char argc_; \ - char **argv_; \ - int32_t brk_; \ - if (argv[0][1] == '-' && argv[0][2] == '\0') { \ - argv++; \ - argc--; \ - break; \ - } \ - for (brk_ = 0, argv[0]++, argv_ = argv; argv[0][0] && !brk_; \ - argv[0]++) { \ - if (argv_ != argv) \ - break; \ - argc_ = argv[0][0]; \ - switch (argc_) - -/* Handles obsolete -NUM syntax */ -#define ARGNUM \ - case '0': \ - case '1': \ - case '2': \ - case '3': \ - case '4': \ - case '5': \ - case '6': \ - case '7': \ - case '8': \ - case '9' - -#define ARGEND \ - } \ - } - -#define ARGC() argc_ - -#define ARGNUMF() (brk_ = 1, estrtonum(argv[0], 0, INT_MAX)) - -#define EARGF(x) \ - ((argv[0][1] == '\0' && argv[1] == NULL) \ - ? ((x), abort(), (char *)0) \ - : (brk_ = 1, \ - (argv[0][1] != '\0') ? (&argv[0][1]) : (argc--, argv++, argv[0]))) - -#define ARGF() \ - ((argv[0][1] == '\0' && argv[1] == NULL) \ - ? (char *)0 \ - : (brk_ = 1, \ - (argv[0][1] != '\0') ? (&argv[0][1]) : (argc--, argv++, argv[0]))) - -#define LNGARG() &argv[0][0] - -#endif diff --git a/mmsg/dynarr.h b/mmsg/dynarr.h deleted file mode 100644 index 45cd356a..00000000 --- a/mmsg/dynarr.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef DYNARR_H__ -#define DYNARR_H__ - -#define DYNARR_DEF(t) \ - struct { \ - t *arr; \ - size_t len, cap, size; \ - } - -#define DYNARR_INIT(p) \ - ((p)->arr = reallocarray((p)->arr, ((p)->cap = 1), \ - ((p)->size = sizeof(((p)->arr[0]))))) - -#define DYNARR_FINI(p) free((p)->arr) - -#define DYNARR_PUSH(p, v) \ - do { \ - if ((p)->len >= (p)->cap) { \ - while ((p)->len >= ((p)->cap *= 2)) \ - ; \ - (p)->arr = reallocarray((p)->arr, (p)->cap, (p)->size); \ - } \ - (p)->arr[(p)->len++] = (v); \ - } while (0) - -#define DYNARR_POP(p) ((p)->arr[(p)->len--]) - -#define DYNARR_CLR(p) ((p)->len = 0) - -#endif diff --git a/mmsg/mmsg.c b/mmsg/mmsg.c index 69f1d1d0..10b24afc 100644 --- a/mmsg/mmsg.c +++ b/mmsg/mmsg.c @@ -1,754 +1,96 @@ -#include "arg.h" -#include "dwl-ipc-unstable-v2-protocol.h" -#include "dynarr.h" -#include <ctype.h> -#include <poll.h> +#define _GNU_SOURCE +#include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sys/socket.h> +#include <sys/un.h> #include <unistd.h> -#include <wayland-client.h> -#include <wayland-util.h> -#define die(fmt, ...) \ - do { \ - fprintf(stderr, fmt "\n", ##__VA_ARGS__); \ - exit(EXIT_FAILURE); \ - } while (0) - -char *argv0; - -static enum { - NONE = 0, - SET = 1 << 0, - GET = 1 << 1, - WATCH = 1 << 2 | GET, -} mode = NONE; - -static int32_t Oflag; -static int32_t Tflag; -static int32_t Lflag; -static int32_t oflag; -static int32_t tflag; -static int32_t lflag; -static int32_t cflag; -static int32_t vflag; -static int32_t mflag; -static int32_t fflag; -static int32_t qflag; -static int32_t dflag; -static int32_t xflag; -static int32_t eflag; -static int32_t kflag; -static int32_t bflag; -static int32_t Aflag; - -static uint32_t occ, seltags, total_clients, urg; - -static char *output_name; -static int32_t tagcount; -static char *tagset; -static char *layout_name; -static int32_t layoutcount, layout_idx; -static char *client_tags; -static char *dispatch_cmd; -static char *dispatch_arg1; -static char *dispatch_arg2; -static char *dispatch_arg3; -static char *dispatch_arg4; -static char *dispatch_arg5; - -struct output { - char *output_name; - uint32_t name; -}; -static DYNARR_DEF(struct output) outputs; - -static struct wl_display *display; -static struct zdwl_ipc_manager_v2 *dwl_ipc_manager; - -// 为每个回调定义专用的空函数 -static void noop_geometry(void *data, struct wl_output *wl_output, int32_t x, - int32_t y, int32_t physical_width, - int32_t physical_height, int32_t subpixel, - const char *make, const char *model, - int32_t transform) {} - -static void noop_mode(void *data, struct wl_output *wl_output, uint32_t flags, - int32_t width, int32_t height, int32_t refresh) {} - -static void noop_done(void *data, struct wl_output *wl_output) {} - -static void noop_scale(void *data, struct wl_output *wl_output, - int32_t factor) {} - -static void noop_description(void *data, struct wl_output *wl_output, - const char *description) {} - -// 将 n 转换为 9 位二进制字符串,结果存入 buf(至少长度 10) -void bin_str_9bits(char *buf, uint32_t n) { - for (int32_t i = 8; i >= 0; i--) { - *buf++ = ((n >> i) & 1) ? '1' : '0'; +int main(int argc, char *argv[]) { + if (argc < 2) { + fprintf(stderr, "Usage: mmsg <command> [args...]\n"); + fprintf(stderr, " get <type> ... one-shot request\n"); + fprintf(stderr, " watch <type> ... persistent stream\n"); + return EXIT_FAILURE; } - *buf = '\0'; // 字符串结尾 -} -static void dwl_ipc_tags(void *data, - struct zdwl_ipc_manager_v2 *dwl_ipc_manager, - uint32_t count) { - tagcount = count; - if (Tflag && mode & GET) - printf("%d\n", tagcount); -} - -static void dwl_ipc_layout(void *data, - struct zdwl_ipc_manager_v2 *dwl_ipc_manager, - const char *name) { - if (lflag && mode & SET && strcmp(layout_name, name) == 0) - layout_idx = layoutcount; - if (Lflag && mode & GET) - printf("%s\n", name); - layoutcount++; -} - -static const struct zdwl_ipc_manager_v2_listener dwl_ipc_listener = { - .tags = dwl_ipc_tags, .layout = dwl_ipc_layout}; - -static void -dwl_ipc_output_toggle_visibility(void *data, - struct zdwl_ipc_output_v2 *dwl_ipc_output) { - if (!vflag) - return; - char *output_name = data; - if (output_name) - printf("%s ", output_name); - printf("toggle\n"); -} - -static void dwl_ipc_output_active(void *data, - struct zdwl_ipc_output_v2 *dwl_ipc_output, - uint32_t active) { - if (!oflag) { - if (mode & SET && !output_name && active) - output_name = strdup(data); - return; + const char *socket_path = getenv("MANGO_INSTANCE_SIGNATURE"); + if (!socket_path) { + fprintf(stderr, "Error: MANGO_INSTANCE_SIGNATURE is not set. Did you " + "run 'mmsg' in mango?\n"); + return EXIT_FAILURE; } - char *output_name = data; - if (output_name) - printf("%s ", output_name); - printf("selmon %u\n", active ? 1 : 0); -} -static void dwl_ipc_output_tag(void *data, - struct zdwl_ipc_output_v2 *dwl_ipc_output, - uint32_t tag, uint32_t state, uint32_t clients, - uint32_t focused) { - if (!tflag) - return; - if (state == ZDWL_IPC_OUTPUT_V2_TAG_STATE_ACTIVE) - seltags |= 1 << tag; - if (state == ZDWL_IPC_OUTPUT_V2_TAG_STATE_URGENT) - urg |= 1 << tag; - if (clients > 0) - occ |= 1 << tag; - - // 累计所有 tag 的 clients 总数 - total_clients += clients; - - if (!(mode & GET)) - return; - char *output_name = data; - if (output_name) - printf("%s ", output_name); - printf("tag %u %u %u %u\n", tag + 1, state, clients, focused); -} - -static void dwl_ipc_output_layout(void *data, - struct zdwl_ipc_output_v2 *dwl_ipc_output, - uint32_t layout) {} - -static void dwl_ipc_output_layout_symbol( - void *data, struct zdwl_ipc_output_v2 *dwl_ipc_output, const char *layout) { - if (!(lflag && mode & GET)) - return; - char *output_name = data; - if (output_name) - printf("%s ", output_name); - printf("layout %s\n", layout); -} - -static void dwl_ipc_output_title(void *data, - struct zdwl_ipc_output_v2 *dwl_ipc_output, - const char *title) { - if (!(cflag && mode & GET)) - return; - char *output_name = data; - if (output_name) - printf("%s ", output_name); - printf("title %s\n", title); -} - -static void dwl_ipc_output_appid(void *data, - struct zdwl_ipc_output_v2 *dwl_ipc_output, - const char *appid) { - if (!(cflag && mode & GET)) - return; - char *output_name = data; - if (output_name) - printf("%s ", output_name); - printf("appid %s\n", appid); -} - -static void dwl_ipc_output_x(void *data, - struct zdwl_ipc_output_v2 *dwl_ipc_output, - int32_t x) { - if (!xflag) - return; - char *output_name = data; - if (output_name) - printf("%s ", output_name); - printf("x %d\n", x); -} - -static void dwl_ipc_output_y(void *data, - struct zdwl_ipc_output_v2 *dwl_ipc_output, - int32_t y) { - if (!xflag) - return; - char *output_name = data; - if (output_name) - printf("%s ", output_name); - printf("y %d\n", y); -} - -static void dwl_ipc_output_width(void *data, - struct zdwl_ipc_output_v2 *dwl_ipc_output, - int32_t width) { - if (!xflag) - return; - char *output_name = data; - if (output_name) - printf("%s ", output_name); - printf("width %d\n", width); -} - -static void dwl_ipc_output_height(void *data, - struct zdwl_ipc_output_v2 *dwl_ipc_output, - int32_t height) { - if (!xflag) - return; - char *output_name = data; - if (output_name) - printf("%s ", output_name); - printf("height %d\n", height); -} - -static void dwl_ipc_output_last_layer(void *data, - struct zdwl_ipc_output_v2 *dwl_ipc_output, - const char *last_layer) { - if (!eflag) - return; - char *output_name = data; - if (output_name) - printf("%s ", output_name); - printf("last_layer %s\n", last_layer); -} - -static void dwl_ipc_output_kb_layout(void *data, - struct zdwl_ipc_output_v2 *dwl_ipc_output, - const char *kb_layout) { - if (!kflag) - return; - char *output_name = data; - if (output_name) - printf("%s ", output_name); - printf("kb_layout %s\n", kb_layout); -} - -static void -dwl_ipc_output_scalefactor(void *data, - struct zdwl_ipc_output_v2 *dwl_ipc_output, - const uint32_t scalefactor) { - if (!Aflag) - return; - char *output_name = data; - if (output_name) - printf("%s ", output_name); - printf("scale_factor %f\n", scalefactor / 100.0f); -} - -static void dwl_ipc_output_keymode(void *data, - struct zdwl_ipc_output_v2 *dwl_ipc_output, - const char *keymode) { - if (!bflag) - return; - char *output_name = data; - if (output_name) - printf("%s ", output_name); - printf("keymode %s\n", keymode); -} - -static void dwl_ipc_output_fullscreen(void *data, - struct zdwl_ipc_output_v2 *dwl_ipc_output, - uint32_t is_fullscreen) { - if (!mflag) - return; - char *output_name = data; - if (output_name) - printf("%s ", output_name); - printf("fullscreen %u\n", is_fullscreen); -} - -static void dwl_ipc_output_floating(void *data, - struct zdwl_ipc_output_v2 *dwl_ipc_output, - uint32_t is_floating) { - if (!fflag) - return; - char *output_name = data; - if (output_name) - printf("%s ", output_name); - printf("floating %u\n", is_floating); -} - -static void dwl_ipc_output_frame(void *data, - struct zdwl_ipc_output_v2 *dwl_ipc_output) { - if (mode & SET) { - if (data && (!output_name || strcmp(output_name, (char *)data))) - return; - if (qflag) { - zdwl_ipc_output_v2_quit(dwl_ipc_output); - } - if (lflag) { - zdwl_ipc_output_v2_set_layout(dwl_ipc_output, layout_idx); - } - if (tflag) { - uint32_t mask = seltags; - char *t = tagset; - int32_t i = 0; - - for (; *t && *t >= '0' && *t <= '9'; t++) - i = *t - '0' + i * 10; - - if (!*t) - mask = 1 << (i - 1); - - for (; *t; t++, i++) { - switch (*t) { - case '-': - mask &= ~(1 << (i - 1)); - break; - case '+': - mask |= 1 << (i - 1); - break; - case '^': - mask ^= 1 << (i - 1); - break; - } - } - - if ((i - 1) > tagcount) - die("bad tagset %s", tagset); - - zdwl_ipc_output_v2_set_tags(dwl_ipc_output, mask, 0); - } - if (cflag) { - uint32_t and = ~0, xor = 0; - char *t = client_tags; - int32_t i = 0; - - for (; *t && *t >= '0' && *t <= '9'; t++) - i = *t - '0' + i * 10; - - if (!*t) - t = "+"; - - for (; *t; t++, i++) { - switch (*t) { - case '-': - and &= ~(1 << (i - 1)); - break; - case '+': - and &= ~(1 << (i - 1)); - xor |= 1 << (i - 1); - break; - case '^': - xor |= 1 << (i - 1); - break; - } - } - if ((i - 1) > tagcount) - die("bad client tagset %s", client_tags); - - zdwl_ipc_output_v2_set_client_tags(dwl_ipc_output, and, xor); - } - if (dflag) { - zdwl_ipc_output_v2_dispatch( - dwl_ipc_output, dispatch_cmd, dispatch_arg1, dispatch_arg2, - dispatch_arg3, dispatch_arg4, dispatch_arg5); - } - wl_display_flush(display); - usleep(1000); - exit(0); - } else { - if (tflag) { - char *output_name = data; - - printf("%s clients %u\n", output_name, total_clients); - - char occ_str[10], seltags_str[10], urg_str[10]; - - bin_str_9bits(occ_str, occ); - bin_str_9bits(seltags_str, seltags); - bin_str_9bits(urg_str, urg); - printf("%s tags %u %u %u\n", output_name, occ, seltags, urg); - printf("%s tags %s %s %s\n", output_name, occ_str, seltags_str, - urg_str); - occ = seltags = total_clients = urg = 0; - } + int sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock < 0) { + perror("socket"); + return EXIT_FAILURE; } - fflush(stdout); -} -static const struct zdwl_ipc_output_v2_listener dwl_ipc_output_listener = { - .toggle_visibility = dwl_ipc_output_toggle_visibility, - .active = dwl_ipc_output_active, - .tag = dwl_ipc_output_tag, - .layout = dwl_ipc_output_layout, - .title = dwl_ipc_output_title, - .appid = dwl_ipc_output_appid, - .layout_symbol = dwl_ipc_output_layout_symbol, - .fullscreen = dwl_ipc_output_fullscreen, - .floating = dwl_ipc_output_floating, - .x = dwl_ipc_output_x, - .y = dwl_ipc_output_y, - .width = dwl_ipc_output_width, - .height = dwl_ipc_output_height, - .last_layer = dwl_ipc_output_last_layer, - .kb_layout = dwl_ipc_output_kb_layout, - .keymode = dwl_ipc_output_keymode, - .scalefactor = dwl_ipc_output_scalefactor, - .frame = dwl_ipc_output_frame, -}; + struct sockaddr_un addr = {.sun_family = AF_UNIX}; + strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1); -static void wl_output_name(void *data, struct wl_output *output, - const char *name) { - if (outputs.arr) { - struct output *o = (struct output *)data; - o->output_name = strdup(name); - printf("+ "); + if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + perror("connect"); + close(sock); + return EXIT_FAILURE; } - if (Oflag) - printf("%s\n", name); - if (output_name && strcmp(output_name, name) != 0) { - wl_output_release(output); - return; + + // 拼接命令,缓冲区大小 4096 以容纳较长参数 + char cmd[4096] = {0}; + int offset = 0; + for (int i = 1; i < argc; i++) { + int n = snprintf(cmd + offset, sizeof(cmd) - offset, "%s%s", argv[i], + (i == argc - 1) ? "" : " "); + if (n < 0 || n >= (int)(sizeof(cmd) - offset)) { + fprintf(stderr, "Error: command too long.\n"); + close(sock); + return EXIT_FAILURE; + } + offset += n; } - struct zdwl_ipc_output_v2 *dwl_ipc_output = - zdwl_ipc_manager_v2_get_output(dwl_ipc_manager, output); - zdwl_ipc_output_v2_add_listener(dwl_ipc_output, &dwl_ipc_output_listener, - output_name ? NULL : strdup(name)); -} -static const struct wl_output_listener output_listener = { - .geometry = noop_geometry, - .mode = noop_mode, - .done = noop_done, - .scale = noop_scale, - .name = wl_output_name, - .description = noop_description, -}; - -static void global_add(void *data, struct wl_registry *wl_registry, - uint32_t name, const char *interface, uint32_t version) { - if (strcmp(interface, wl_output_interface.name) == 0) { - struct wl_output *o = - wl_registry_bind(wl_registry, name, &wl_output_interface, - WL_OUTPUT_NAME_SINCE_VERSION); - if (!outputs.arr) { - wl_output_add_listener(o, &output_listener, NULL); - } else { - DYNARR_PUSH(&outputs, (struct output){.name = name}); - wl_output_add_listener(o, &output_listener, - &outputs.arr[outputs.len - 1]); - } - } else if (strcmp(interface, zdwl_ipc_manager_v2_interface.name) == 0) { - dwl_ipc_manager = wl_registry_bind(wl_registry, name, - &zdwl_ipc_manager_v2_interface, 2); - zdwl_ipc_manager_v2_add_listener(dwl_ipc_manager, &dwl_ipc_listener, - NULL); + // 添加换行符 + int n = snprintf(cmd + offset, sizeof(cmd) - offset, "\n"); + if (n < 0 || n >= (int)(sizeof(cmd) - offset)) { + fprintf(stderr, "Error: command too long to append newline.\n"); + close(sock); + return EXIT_FAILURE; } -} -static void global_remove(void *data, struct wl_registry *wl_registry, - uint32_t name) { - if (!outputs.arr) - return; - struct output *o = outputs.arr; - for (size_t i = 0; i < outputs.len; i++, o++) { - if (o->name == name) { - printf("- %s\n", o->output_name); - free(o->output_name); - *o = DYNARR_POP(&outputs); - } + // 发送命令,使用 MSG_NOSIGNAL 避免 SIGPIPE + if (send(sock, cmd, strlen(cmd), MSG_NOSIGNAL) < 0) { + perror("send"); + close(sock); + return EXIT_FAILURE; } -} -static const struct wl_registry_listener registry_listener = { - .global = global_add, - .global_remove = global_remove, -}; - -static void usage(void) { - fprintf(stderr, - "mmsg - MangoWM IPC\n" - "\n" - "SYNOPSIS:\n" - "\tmmsg [-OTLq]\n" - "\tmmsg [-o <output>] -s [-t <tags>] [-l <layout>] [-c <tags>] [-d " - "<cmd>,<arg1>,<arg2>,<arg3>,<arg4>,<arg5>]\n" - "\tmmsg [-o <output>] (-g | -w) [-OotlcvmfxekbA]\n" - "\n" - "OPERATION MODES:\n" - "\t-g Get values (tags, layout, focused client)\n" - "\t-s Set values (switch tags, layouts)\n" - "\t-w Watch mode (stream events)\n" - "\n" - "GENERAL OPTIONS:\n" - "\t-O Get all output (monitor) information\n" - "\t-T Get number of tags\n" - "\t-L Get all available layouts\n" - "\t-q Quit mango\n" - "\t-o <output> Select output (monitor)\n" - "\n" - "GET OPTIONS (used with -g or -w):\n" - "\t-O Get output name\n" - "\t-o Get output (monitor) focus information\n" - "\t-t Get selected tags\n" - "\t-l Get current layout\n" - "\t-c Get title and appid of focused clients\n" - "\t-v Get visibility of statusbar\n" - "\t-m Get fullscreen status\n" - "\t-f Get floating status\n" - "\t-x Get focused client geometry\n" - "\t-e Get name of last focused layer\n" - "\t-k Get current keyboard layout\n" - "\t-b Get current keybind mode\n" - "\t-A Get scale factor of monitor\n" - "\n" - "SET OPTIONS (used with -s):\n" - "\t-o <output> Select output (monitor)\n" - "\t-t <tags> Set selected tags (can be used with [+-^.] " - "modifiers)\n" - "\t-l <layout> Set current layout\n" - "\t-c <tags> Get title and appid of focused client\n" - "\t-d <cmd>,<args...> Dispatch internal command (max 5 args)\n"); - exit(2); -} - -int32_t main(int32_t argc, char *argv[]) { - ARGBEGIN { - case 'q': - qflag = 1; - if (!(mode & GET)) { - mode = SET; - } - break; - case 's': - if (mode != NONE) - usage(); - mode = SET; - break; - case 'g': - if (mode != NONE) - usage(); - mode = GET; - break; - case 'w': - if (mode != NONE) - usage(); - mode = WATCH; - break; - case 'o': - if (mode == GET || mode == WATCH) - oflag = 1; - else if (mode == SET) - output_name = EARGF(usage()); - else - output_name = ARGF(); - break; - case 't': - tflag = 1; - if (!(mode & GET)) { - mode = SET; - tagset = EARGF(usage()); - } - break; - case 'l': - lflag = 1; - if (!(mode & GET)) { - mode = SET; - layout_name = EARGF(usage()); - } - break; - case 'c': - cflag = 1; - if (!(mode & GET)) { - mode = SET; - client_tags = EARGF(usage()); - } - break; - case 'd': - dflag = 1; - if (!(mode & GET)) { - mode = SET; - char *arg = EARGF(usage()); - - dispatch_cmd = dispatch_arg1 = dispatch_arg2 = dispatch_arg3 = - dispatch_arg4 = dispatch_arg5 = ""; - - char *tokens[6] = {0}; - int count = 0; - - while (arg && count < 6) { - char *comma = (count < 5) ? strchr(arg, ',') : NULL; - if (comma) { - *comma = '\0'; - tokens[count++] = arg; - arg = comma + 1; - } else { - tokens[count++] = arg; - break; - } - } - - for (int i = 0; i < count; i++) { - char *str = tokens[i]; - while (isspace((unsigned char)*str)) - str++; - if (*str) { - char *end = str + strlen(str) - 1; - while (end > str && isspace((unsigned char)*end)) - end--; - *(end + 1) = '\0'; - } - tokens[i] = str; - } - - if (count > 0) - dispatch_cmd = tokens[0]; - if (count > 1) - dispatch_arg1 = tokens[1]; - if (count > 2) - dispatch_arg2 = tokens[2]; - if (count > 3) - dispatch_arg3 = tokens[3]; - if (count > 4) - dispatch_arg4 = tokens[4]; - if (count > 5) - dispatch_arg5 = tokens[5]; - } - break; - case 'O': - Oflag = 1; - if (mode && !(mode & GET)) - usage(); - if (mode & WATCH) - DYNARR_INIT(&outputs); - else - mode = GET; - break; - case 'T': - Tflag = 1; - if (mode && mode != GET) - usage(); - mode = GET; - break; - case 'L': - Lflag = 1; - if (mode && mode != GET) - usage(); - mode = GET; - break; - case 'v': - vflag = 1; - if (mode == SET) - usage(); - mode |= GET; - break; - case 'm': - mflag = 1; - if (mode == SET) - usage(); - mode |= GET; - break; - case 'f': - fflag = 1; - if (mode == SET) - usage(); - mode |= GET; - break; - case 'x': - xflag = 1; - if (mode == SET) - usage(); - mode |= GET; - break; - case 'e': - eflag = 1; - if (mode == SET) - usage(); - mode |= GET; - break; - case 'k': - kflag = 1; - if (mode == SET) - usage(); - mode |= GET; - break; - case 'b': - bflag = 1; - if (mode == SET) - usage(); - mode |= GET; - break; - case 'A': - Aflag = 1; - if (mode == SET) - usage(); - mode |= GET; - break; - default: - fprintf(stderr, "bad option %c\n", ARGC()); - usage(); + // 将 socket 封装为行缓冲文件流,自动处理 TCP 拆包,按完整行读取 + FILE *stream = fdopen(sock, "r"); + if (!stream) { + perror("fdopen"); + close(sock); + return EXIT_FAILURE; } - ARGEND - if (mode == NONE) - usage(); - if (mode & GET && !output_name && - !(oflag || tflag || lflag || Oflag || Tflag || Lflag || cflag || - vflag || mflag || fflag || xflag || eflag || kflag || bflag || - Aflag || dflag)) - oflag = tflag = lflag = cflag = vflag = mflag = fflag = xflag = eflag = - kflag = bflag = Aflag = 1; - display = wl_display_connect(NULL); - if (!display) - die("bad display"); + // 按行读取并输出,直到连接关闭(get 模式服务端主动 close)或出错 + char *line = NULL; + size_t len = 0; + while (getline(&line, &len, stream) != -1) { + printf("%s", line); + fflush(stdout); + } - struct wl_registry *registry = wl_display_get_registry(display); - wl_registry_add_listener(registry, ®istry_listener, NULL); + // 检查是否因读取错误退出(而非正常 EOF) + if (ferror(stream)) { + perror("recv"); + free(line); + fclose(stream); // 关闭 stream 同时关闭 socket + return EXIT_FAILURE; + } - wl_display_dispatch(display); - wl_display_roundtrip(display); - - if (!dwl_ipc_manager) - die("bad dwl-ipc protocol"); - - wl_display_roundtrip(display); - - if (mode == WATCH) - while (wl_display_dispatch(display) != -1) - ; - - return 0; -} + free(line); + fclose(stream); + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/src/action/client.h b/src/action/client.h index ce71a5c6..43eb2c7b 100644 --- a/src/action/client.h +++ b/src/action/client.h @@ -57,4 +57,7 @@ void client_tile_resize(Client *c, struct wlr_box geo, int32_t interact) { if (!c->isfullscreen && !c->ismaximizescreen) { resize(c, geo, interact); } -} \ No newline at end of file +} + +static uint32_t next_client_id = 0; +uint32_t generate_client_id(void) { return ++next_client_id; } \ No newline at end of file diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 49dcea74..4f3dd708 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -967,6 +967,8 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value, } else if (strcmp(func_name, "focusdir") == 0) { func = focusdir; (*arg).i = parse_direction(arg_value); + } else if (strcmp(func_name, "focusid") == 0) { + func = focusid; } else if (strcmp(func_name, "incnmaster") == 0) { func = incnmaster; (*arg).i = atoi(arg_value); @@ -2443,6 +2445,7 @@ bool parse_option(Config *config, char *key, char *value) { binding->arg.v = NULL; binding->arg.v2 = NULL; binding->arg.v3 = NULL; + binding->arg.tc = NULL; binding->func = parse_func_name(func_name, &binding->arg, arg_value, arg_value2, arg_value3, arg_value4, arg_value5); @@ -2524,6 +2527,7 @@ bool parse_option(Config *config, char *key, char *value) { binding->arg.v = NULL; binding->arg.v2 = NULL; binding->arg.v3 = NULL; + binding->arg.tc = NULL; // TODO: remove this in next version if (binding->mod == 0 && @@ -2609,6 +2613,7 @@ bool parse_option(Config *config, char *key, char *value) { binding->arg.v = NULL; binding->arg.v2 = NULL; binding->arg.v3 = NULL; + binding->arg.tc = NULL; binding->func = parse_func_name(func_name, &binding->arg, arg_value, arg_value2, arg_value3, arg_value4, arg_value5); @@ -2759,6 +2764,7 @@ bool parse_option(Config *config, char *key, char *value) { binding->arg.v = NULL; binding->arg.v2 = NULL; binding->arg.v3 = NULL; + binding->arg.tc = NULL; binding->func = parse_func_name(func_name, &binding->arg, arg_value, arg_value2, arg_value3, arg_value4, arg_value5); diff --git a/src/dispatch/bind_declare.h b/src/dispatch/bind_declare.h index b79ad689..a04ba0c1 100644 --- a/src/dispatch/bind_declare.h +++ b/src/dispatch/bind_declare.h @@ -73,4 +73,5 @@ int32_t scroller_stack(const Arg *arg); int32_t toggle_all_floating(const Arg *arg); int32_t dwindle_toggle_split_direction(const Arg *arg); int32_t dwindle_split_horizontal(const Arg *arg); -int32_t dwindle_split_vertical(const Arg *arg); \ No newline at end of file +int32_t dwindle_split_vertical(const Arg *arg); +int32_t focusid(const Arg *arg); \ No newline at end of file diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index ed91bb40..d2919d3a 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -34,10 +34,8 @@ int32_t bind_to_view(const Arg *arg) { int32_t chvt(const Arg *arg) { struct timespec ts; - // prevent the animation to rquest the new frame allow_frame_scheduling = false; - // backup current tag and monitor name if (selmon) { chvt_backup_tag = selmon->pertag->curtag; strncpy(chvt_backup_selmon, selmon->wlr_output->name, @@ -46,19 +44,15 @@ int32_t chvt(const Arg *arg) { wlr_session_change_vt(session, arg->ui); - // wait for DRM device to stabilize and ensure the session state is inactive ts.tv_sec = 0; - ts.tv_nsec = 100000000; // 200ms + ts.tv_nsec = 100000000; nanosleep(&ts, NULL); - // allow frame scheduling, - // because session state is now inactive, rendermon will not enter allow_frame_scheduling = true; return 1; } int32_t create_virtual_output(const Arg *arg) { - if (!wlr_backend_is_multi(backend)) { wlr_log(WLR_ERROR, "Expected a multi backend"); return 0; @@ -77,7 +71,6 @@ int32_t create_virtual_output(const Arg *arg) { } int32_t destroy_all_virtual_output(const Arg *arg) { - if (!wlr_backend_is_multi(backend)) { wlr_log(WLR_ERROR, "Expected a multi backend"); return 0; @@ -86,8 +79,6 @@ int32_t destroy_all_virtual_output(const Arg *arg) { Monitor *m, *tmp; wl_list_for_each_safe(m, tmp, &mons, link) { if (wlr_output_is_headless(m->wlr_output)) { - // if(selmon == m) - // selmon = NULL; wlr_output_destroy(m->wlr_output); wlr_log(WLR_INFO, "Virtual output destroyed"); } @@ -103,7 +94,7 @@ int32_t defaultgaps(const Arg *arg) { int32_t exchange_client(const Arg *arg) { if (!selmon) return 0; - Client *c = selmon->sel; + Client *c = arg->tc ? arg->tc : selmon->sel; if (!c || c->isfloating) return 0; @@ -111,7 +102,7 @@ int32_t exchange_client(const Arg *arg) { return 0; Client *tc = direction_select(arg); - tc = get_focused_stack_client(tc); + tc = get_focused_stack_client(tc, arg->tc); if (!tc) return 0; @@ -124,7 +115,7 @@ int32_t exchange_stack_client(const Arg *arg) { if (!selmon) return 0; - Client *c = selmon->sel; + Client *c = arg->tc ? arg->tc : selmon->sel; Client *tc = NULL; if (!c || c->isfloating || c->isfullscreen || c->ismaximizescreen) return 0; @@ -141,7 +132,7 @@ int32_t exchange_stack_client(const Arg *arg) { int32_t focusdir(const Arg *arg) { Client *c = NULL; c = direction_select(arg); - c = get_focused_stack_client(c); + c = get_focused_stack_client(c, arg->tc); if (c) { focusclient(c, 1); if (config.warpcursor) @@ -160,7 +151,6 @@ int32_t focusdir(const Arg *arg) { } int32_t focuslast(const Arg *arg) { - Client *c = NULL; Client *tc = NULL; bool begin = false; @@ -232,7 +222,7 @@ int32_t focusmon(const Arg *arg) { if (config.warpcursor) { warp_cursor_to_selmon(selmon); } - c = focustop(selmon); + c = arg->tc ? arg->tc : focustop(selmon); if (!c) { selmon->sel = NULL; wlr_seat_pointer_notify_clear_focus(seat); @@ -245,8 +235,7 @@ int32_t focusmon(const Arg *arg) { } int32_t focusstack(const Arg *arg) { - /* Focus the next or previous client (in tiling order) on selmon */ - Client *sel = focustop(selmon); + Client *sel = arg->tc ? arg->tc : focustop(selmon); Client *tc = NULL; if (!sel) @@ -256,7 +245,6 @@ int32_t focusstack(const Arg *arg) { } else { tc = get_next_stack_client(sel, true); } - /* If only one client is visible on selmon, then c == sel */ if (!tc) return 0; @@ -355,10 +343,7 @@ int32_t setmfact(const Arg *arg) { } int32_t killclient(const Arg *arg) { - Client *c = NULL; - if (!selmon) - return 0; - c = selmon->sel; + Client *c = arg->tc ? arg->tc : (selmon ? selmon->sel : NULL); if (c) { pending_kill_client(c); } @@ -377,7 +362,6 @@ int32_t moveresize(const Arg *arg) { grabc = NULL; return 0; } - /* 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); @@ -398,20 +382,16 @@ int32_t moveresize(const Arg *arg) { switch (cursor_mode = arg->ui) { case CurMove: - grabcx = cursor->x - grabc->geom.x; grabcy = cursor->y - grabc->geom.y; wlr_cursor_set_xcursor(cursor, cursor_mgr, "grab"); break; case CurResize: - /* Doesn't work for X11 output - the next absolute motion event - * returns the cursor to where it started */ if (grabc->isfloating) { rzcorner = config.drag_corner; grabcx = (int)round(cursor->x); grabcy = (int)round(cursor->y); if (rzcorner == 4) - /* identify the closest corner index */ rzcorner = (grabcx - grabc->geom.x < grabc->geom.x + grabc->geom.width - grabcx ? 0 @@ -439,14 +419,11 @@ int32_t moveresize(const Arg *arg) { } int32_t movewin(const Arg *arg) { - Client *c = NULL; - if (!selmon) - return 0; - c = selmon->sel; + Client *c = arg->tc ? arg->tc : (selmon ? selmon->sel : NULL); if (!c || c->isfullscreen) return 0; if (!c->isfloating) - togglefloating(NULL); + setfloating(c, 1); switch (arg->ui) { case NUM_TYPE_MINUS: @@ -484,10 +461,7 @@ int32_t quit(const Arg *arg) { } int32_t resizewin(const Arg *arg) { - Client *c = NULL; - if (!selmon) - return 0; - c = selmon->sel; + Client *c = arg->tc ? arg->tc : (selmon ? selmon->sel : NULL); int32_t offsetx = 0, offsety = 0; if (!c || c->isfullscreen || c->ismaximizescreen) @@ -558,18 +532,17 @@ int32_t resizewin(const Arg *arg) { } int32_t restore_minimized(const Arg *arg) { - Client *c = NULL; + Client *c = arg->tc ? arg->tc : (selmon ? selmon->sel : NULL); if (selmon && selmon->isoverview) return 0; - if (selmon && selmon->sel && selmon->sel->is_in_scratchpad && - selmon->sel->is_scratchpad_show) { - client_pending_minimized_state(selmon->sel, 0); - selmon->sel->is_scratchpad_show = 0; - selmon->sel->is_in_scratchpad = 0; - selmon->sel->isnamedscratchpad = 0; - setborder_color(selmon->sel); + if (c && c->is_in_scratchpad && c->is_scratchpad_show) { + client_pending_minimized_state(c, 0); + c->is_scratchpad_show = 0; + c->is_in_scratchpad = 0; + c->isnamedscratchpad = 0; + setborder_color(c); return 0; } @@ -628,11 +601,10 @@ int32_t set_proportion(const Arg *arg) { !config.scroller_ignore_proportion_single) return 0; - Client *tc = selmon->sel; + Client *tc = arg->tc ? arg->tc : selmon->sel; if (!tc) return 0; - /* 获取堆叠头部客户端 */ tc = scroll_get_stack_head_client(tc); if (!tc) return 0; @@ -645,12 +617,10 @@ int32_t set_proportion(const Arg *arg) { if (st) node = find_scroller_node(st, tc); - /* 同时更新节点和客户端字段 */ if (node) node->scroller_proportion = arg->f; tc->scroller_proportion = arg->f; - /* 可选的即时几何更新,arrange 时会重新计算 */ uint32_t max_client_width = m->w.width - 2 * config.scroller_structs - config.gappih; tc->geom.width = max_client_width * arg->f; @@ -674,7 +644,7 @@ int32_t switch_proportion_preset(const Arg *arg) { !config.scroller_ignore_proportion_single) return 0; - Client *tc = selmon->sel; + Client *tc = arg->tc ? arg->tc : selmon->sel; if (!tc) return 0; @@ -690,11 +660,9 @@ int32_t switch_proportion_preset(const Arg *arg) { if (st) node = find_scroller_node(st, tc); - /* 优先从节点读取当前比例,以确保切换基于正确的值 */ float current_proportion = node ? node->scroller_proportion : tc->scroller_proportion; - /* 查找预设目标 */ for (int32_t i = 0; i < config.scroller_proportion_preset_count; i++) { if (config.scroller_proportion_preset[i] == current_proportion) { if (arg->i == NEXT) { @@ -719,7 +687,6 @@ int32_t switch_proportion_preset(const Arg *arg) { if (target_proportion == 0.0f) target_proportion = config.scroller_proportion_preset[0]; - /* 更新节点和客户端 */ if (node) node->scroller_proportion = target_proportion; tc->scroller_proportion = target_proportion; @@ -738,11 +705,11 @@ int32_t smartmovewin(const Arg *arg) { int32_t buttom, top, left, right, tar; if (!selmon) return 0; - c = selmon->sel; + c = arg->tc ? arg->tc : selmon->sel; if (!c || c->isfullscreen) return 0; if (!c->isfloating) - setfloating(selmon->sel, true); + setfloating(c, true); nx = c->geom.x; ny = c->geom.y; @@ -841,7 +808,7 @@ int32_t smartresizewin(const Arg *arg) { int32_t buttom, top, left, right, tar; if (!selmon) return 0; - c = selmon->sel; + c = arg->tc ? arg->tc : selmon->sel; if (!c || c->isfullscreen) return 0; if (!c->isfloating) @@ -908,10 +875,7 @@ int32_t smartresizewin(const Arg *arg) { } int32_t centerwin(const Arg *arg) { - Client *c = NULL; - if (!selmon) - return 0; - c = selmon->sel; + Client *c = arg->tc ? arg->tc : (selmon ? selmon->sel : NULL); if (!c || c->isfullscreen || c->ismaximizescreen) return 0; @@ -944,7 +908,6 @@ int32_t spawn_shell(const Arg *arg) { return 0; if (fork() == 0) { - // 1. 忽略可能导致 coredump 的信号 signal(SIGSEGV, SIG_IGN); signal(SIGABRT, SIG_IGN); signal(SIGILL, SIG_IGN); @@ -953,11 +916,8 @@ int32_t spawn_shell(const Arg *arg) { setsid(); execlp("sh", "sh", "-c", arg->v, (char *)NULL); - - // fallback to bash execlp("bash", "bash", "-c", arg->v, (char *)NULL); - // if execlp fails, we should not reach here wlr_log(WLR_DEBUG, "mango: failed to execute command '%s' with shell: %s\n", arg->v, strerror(errno)); @@ -971,7 +931,6 @@ int32_t spawn(const Arg *arg) { return 0; if (fork() == 0) { - // 1. 忽略可能导致 coredump 的信号 signal(SIGSEGV, SIG_IGN); signal(SIGABRT, SIG_IGN); signal(SIGILL, SIG_IGN); @@ -979,20 +938,17 @@ int32_t spawn(const Arg *arg) { dup2(STDERR_FILENO, STDOUT_FILENO); setsid(); - // 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); } - // 3. 执行命令(p.we_wordv 已经是 argv 数组) execvp(p.we_wordv[0], p.we_wordv); - // 4. execvp 失败时:打印错误,释放 wordexp 资源,然后退出 wlr_log(WLR_DEBUG, "mango: execvp '%s' failed: %s\n", p.we_wordv[0], strerror(errno)); - wordfree(&p); // 释放 wordexp 分配的内存 + wordfree(&p); _exit(EXIT_FAILURE); } return 0; @@ -1030,7 +986,6 @@ int32_t switch_keyboard_layout(const Arg *arg) { return 0; } - // 1. 获取当前布局和计算下一个布局 xkb_layout_index_t current = xkb_state_serialize_layout( keyboard->xkb_state, XKB_STATE_LAYOUT_EFFECTIVE); const int32_t num_layouts = xkb_keymap_num_layouts(keyboard->keymap); @@ -1046,14 +1001,12 @@ int32_t switch_keyboard_layout(const Arg *arg) { next = (current + 1) % num_layouts; } - // 6. 应用新 keymap uint32_t depressed = keyboard->modifiers.depressed; uint32_t latched = keyboard->modifiers.latched; uint32_t locked = keyboard->modifiers.locked; wlr_keyboard_notify_modifiers(keyboard, depressed, latched, locked, next); - // 7. 更新 seat wlr_seat_set_keyboard(seat, keyboard); wlr_seat_keyboard_notify_modifiers(seat, &keyboard->modifiers); @@ -1066,7 +1019,6 @@ int32_t switch_keyboard_layout(const Arg *arg) { struct wlr_keyboard *tkb = (struct wlr_keyboard *)id->device_data; wlr_keyboard_notify_modifiers(tkb, depressed, latched, locked, next); - // 7. 更新 seat wlr_seat_set_keyboard(seat, tkb); wlr_seat_keyboard_notify_modifiers(seat, &tkb->modifiers); } @@ -1076,7 +1028,6 @@ int32_t switch_keyboard_layout(const Arg *arg) { } int32_t switch_layout(const Arg *arg) { - int32_t jk, ji; char *target_layout_name = NULL; uint32_t len; @@ -1086,7 +1037,6 @@ int32_t switch_layout(const Arg *arg) { if (config.circle_layout_count != 0) { for (jk = 0; jk < config.circle_layout_count; jk++) { - len = MAX( strlen(config.circle_layout[jk]), strlen(selmon->pertag->ltidxs[selmon->pertag->curtag]->name)); @@ -1109,7 +1059,6 @@ int32_t switch_layout(const Arg *arg) { len = MAX(strlen(layouts[ji].name), strlen(target_layout_name)); if (strncmp(layouts[ji].name, target_layout_name, len) == 0) { selmon->pertag->ltidxs[selmon->pertag->curtag] = &layouts[ji]; - break; } } @@ -1136,7 +1085,7 @@ int32_t switch_layout(const Arg *arg) { int32_t tag(const Arg *arg) { if (!selmon) return 0; - Client *target_client = selmon->sel; + Client *target_client = arg->tc ? arg->tc : selmon->sel; tag_client(arg, target_client); return 0; } @@ -1145,7 +1094,7 @@ int32_t tagmon(const Arg *arg) { Monitor *m = NULL, *cm = NULL, *oldmon = NULL; if (!selmon) return 0; - Client *c = focustop(selmon); + Client *c = arg->tc ? arg->tc : focustop(selmon); if (!c) return 0; @@ -1179,8 +1128,8 @@ int32_t tagmon(const Arg *arg) { return 0; } - if (c == selmon->sel) { - selmon->sel = NULL; + if (c == oldmon->sel) { + oldmon->sel = NULL; } setmon(c, m, newtags, true); @@ -1195,8 +1144,6 @@ int32_t tagmon(const Arg *arg) { selmon = c->mon; c->float_geom = setclient_coordinate_center(c, c->mon, c->float_geom, 0, 0); - // 重新计算居中的坐标 - // 重新计算居中的坐标 if (c->isfloating) { c->geom = c->float_geom; target = get_tags_first_tag(c->tags); @@ -1218,12 +1165,11 @@ int32_t tagmon(const Arg *arg) { int32_t tagsilent(const Arg *arg) { Client *fc = NULL; - Client *target_client = NULL; + Client *target_client = arg->tc ? arg->tc : (selmon ? selmon->sel : NULL); - if (!selmon || !selmon->sel) + if (!target_client) return 0; - target_client = selmon->sel; target_client->tags = arg->ui & TAGMASK; wl_list_for_each(fc, &clients, link) { if (fc && fc != target_client && target_client->tags & fc->tags && @@ -1240,7 +1186,8 @@ int32_t tagtoleft(const Arg *arg) { if (!selmon) return 0; - if (selmon->sel != NULL && + Client *sel = arg->tc ? arg->tc : selmon->sel; + if (sel != NULL && __builtin_popcount(selmon->tagset[selmon->seltags] & TAGMASK) == 1) { uint32_t target = selmon->tagset[selmon->seltags] >> 1; @@ -1251,7 +1198,8 @@ int32_t tagtoleft(const Arg *arg) { selmon->carousel_anim_dir = -1; } - tag(&(Arg){.ui = target & TAGMASK, .i = arg->i}); + Arg a = {.ui = target & TAGMASK, .i = arg->i, .tc = sel}; + tag(&a); selmon->carousel_anim_dir = 0; } return 0; @@ -1261,7 +1209,8 @@ int32_t tagtoright(const Arg *arg) { if (!selmon) return 0; - if (selmon->sel != NULL && + Client *sel = arg->tc ? arg->tc : selmon->sel; + if (sel != NULL && __builtin_popcount(selmon->tagset[selmon->seltags] & TAGMASK) == 1) { uint32_t target = selmon->tagset[selmon->seltags] << 1; @@ -1272,7 +1221,8 @@ int32_t tagtoright(const Arg *arg) { selmon->carousel_anim_dir = 1; } - tag(&(Arg){.ui = target & TAGMASK, .i = arg->i}); + Arg a = {.ui = target & TAGMASK, .i = arg->i, .tc = sel}; + tag(&a); selmon->carousel_anim_dir = 0; } return 0; @@ -1295,7 +1245,6 @@ int32_t toggle_named_scratchpad(const Arg *arg) { } target_client->isnamedscratchpad = 1; - apply_named_scratchpad(target_client); return 0; } @@ -1341,7 +1290,7 @@ int32_t toggle_scratchpad(const Arg *arg) { int32_t togglefakefullscreen(const Arg *arg) { if (!selmon) return 0; - Client *sel = focustop(selmon); + Client *sel = arg->tc ? arg->tc : focustop(selmon); if (sel) setfakefullscreen(sel, !sel->isfakefullscreen); return 0; @@ -1351,7 +1300,7 @@ int32_t togglefloating(const Arg *arg) { if (!selmon) return 0; - Client *sel = focustop(selmon); + Client *sel = arg->tc ? arg->tc : focustop(selmon); if (selmon && selmon->isoverview) return 0; @@ -1375,7 +1324,7 @@ int32_t togglefullscreen(const Arg *arg) { if (!selmon) return 0; - Client *sel = focustop(selmon); + Client *sel = arg->tc ? arg->tc : focustop(selmon); if (!sel) return 0; @@ -1394,15 +1343,17 @@ int32_t toggleglobal(const Arg *arg) { if (!selmon) return 0; - if (!selmon->sel) + Client *c = arg->tc ? arg->tc : selmon->sel; + if (!c) return 0; - if (selmon->sel->is_in_scratchpad) { - selmon->sel->is_in_scratchpad = 0; - selmon->sel->is_scratchpad_show = 0; - selmon->sel->isnamedscratchpad = 0; + + if (c->is_in_scratchpad) { + c->is_in_scratchpad = 0; + c->is_scratchpad_show = 0; + c->isnamedscratchpad = 0; } - selmon->sel->isglobal ^= 1; - setborder_color(selmon->sel); + c->isglobal ^= 1; + setborder_color(c); return 0; } @@ -1419,7 +1370,7 @@ int32_t togglemaximizescreen(const Arg *arg) { if (!selmon) return 0; - Client *sel = focustop(selmon); + Client *sel = arg->tc ? arg->tc : focustop(selmon); if (!sel) return 0; @@ -1440,23 +1391,23 @@ int32_t toggleoverlay(const Arg *arg) { if (!selmon) return 0; - if (!selmon->sel || !selmon->sel->mon || selmon->sel->isfullscreen) { + Client *c = arg->tc ? arg->tc : selmon->sel; + if (!c || !c->mon || c->isfullscreen) { return 0; } - selmon->sel->isoverlay ^= 1; + c->isoverlay ^= 1; - if (selmon->sel->isoverlay) { - wlr_scene_node_reparent(&selmon->sel->scene->node, layers[LyrOverlay]); - wlr_scene_node_raise_to_top(&selmon->sel->scene->node); - } else if (client_should_overtop(selmon->sel) && selmon->sel->isfloating) { - wlr_scene_node_reparent(&selmon->sel->scene->node, layers[LyrTop]); + if (c->isoverlay) { + wlr_scene_node_reparent(&c->scene->node, layers[LyrOverlay]); + wlr_scene_node_raise_to_top(&c->scene->node); + } else if (client_should_overtop(c) && c->isfloating) { + wlr_scene_node_reparent(&c->scene->node, layers[LyrTop]); } else { - wlr_scene_node_reparent( - &selmon->sel->scene->node, - layers[selmon->sel->isfloating ? LyrTop : LyrTile]); + wlr_scene_node_reparent(&c->scene->node, + layers[c->isfloating ? LyrTop : LyrTile]); } - setborder_color(selmon->sel); + setborder_color(c); return 0; } @@ -1465,7 +1416,7 @@ int32_t toggletag(const Arg *arg) { return 0; uint32_t newtags; - Client *sel = focustop(selmon); + Client *sel = arg->tc ? arg->tc : focustop(selmon); if (!sel) return 0; @@ -1626,15 +1577,20 @@ int32_t viewcrossmon(const Arg *arg) { } int32_t tagcrossmon(const Arg *arg) { - if (!selmon || !selmon->sel) + if (!selmon) + return 0; + + Client *c = arg->tc ? arg->tc : selmon->sel; + if (!c) return 0; if (match_monitor_spec(arg->v, selmon)) { - tag_client(arg, selmon->sel); + tag_client(arg, c); return 0; } - tagmon(&(Arg){.ui = arg->ui, .i = UNDIR, .v = arg->v}); + Arg a = {.ui = arg->ui, .i = UNDIR, .v = arg->v, .tc = c}; + tagmon(&a); return 0; } @@ -1658,15 +1614,13 @@ int32_t comboview(const Arg *arg) { } int32_t zoom(const Arg *arg) { - Client *c = NULL, *sel = focustop(selmon); + Client *c = NULL, *sel = arg->tc ? arg->tc : focustop(selmon); if (!sel || !selmon || !selmon->pertag->ltidxs[selmon->pertag->curtag]->arrange || sel->isfloating) return 0; - /* Search for the first tiled window that is not sel, marking sel as - * NULL if we pass it along the way */ wl_list_for_each(c, &clients, link) if (VISIBLEON(c, selmon) && !c->isfloating) { if (c != sel) @@ -1674,12 +1628,9 @@ int32_t zoom(const Arg *arg) { sel = NULL; } - /* Return if no other tiled window was found */ if (&c->link == &clients) return 0; - /* If we passed sel, move c to the front; otherwise, move sel to the - * front */ if (!sel) sel = c; wl_list_remove(&sel->link); @@ -1704,8 +1655,9 @@ int32_t minimized(const Arg *arg) { if (selmon && selmon->isoverview) return 0; - if (selmon->sel && !selmon->sel->isminimized) { - set_minimized(selmon->sel); + Client *c = arg->tc ? arg->tc : selmon->sel; + if (c && !c->isminimized) { + set_minimized(c); } return 0; } @@ -1725,8 +1677,9 @@ int32_t toggleoverview(const Arg *arg) { if (!selmon) return 0; - if (selmon->isoverview && config.ov_tab_mode && arg->i != 1 && - selmon->sel) { + Client *sel = arg->tc ? arg->tc : selmon->sel; + + if (selmon->isoverview && config.ov_tab_mode && arg->i != 1 && sel) { focusstack(&(Arg){.i = 1}); return 0; } @@ -1751,9 +1704,9 @@ int32_t toggleoverview(const Arg *arg) { selmon->isoverview ^= 1; return 0; } - } else if (!selmon->isoverview && selmon->sel) { - target = get_tags_first_tag(selmon->sel->tags); - } else if (!selmon->isoverview && !selmon->sel) { + } else if (!selmon->isoverview && sel) { + target = get_tags_first_tag(sel->tags); + } else if (!selmon->isoverview && !sel) { target = (1 << (selmon->pertag->prevtag - 1)); view(&(Arg){.ui = target}, false); fix_mon_tagset_from_overview(selmon); @@ -1761,11 +1714,7 @@ int32_t toggleoverview(const Arg *arg) { return 0; } - // 正常视图到overview,退出所有窗口的浮动和全屏状态参与平铺, - // overview到正常视图,还原之前退出的浮动和全屏窗口状态 if (selmon->isoverview) { - - // 让游戏窗口无法强制约束鼠标 wlr_seat_pointer_clear_focus(seat); wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); @@ -1856,7 +1805,6 @@ int32_t scroller_apply_stack(Client *c, Client *target_client, struct TagScrollerState *st = ensure_scroller_state(m, tag); - /* 获取当前节点 */ struct ScrollerStackNode *cnode = find_scroller_node(st, c); if (!cnode) @@ -1865,20 +1813,16 @@ int32_t scroller_apply_stack(Client *c, Client *target_client, struct ScrollerStackNode *tnode = target_client ? find_scroller_node(st, target_client) : NULL; - /* 若方向为 UNDIR 且有目标,直接插入到目标尾部 */ if (direction == UNDIR && target_client && target_client->mon == c->mon) { scroller_insert_stack(c, target_client, false); return 0; } - /* 处理从堆叠中移出的情况(方向 LEFT/UP 或 RIGHT/DOWN) */ if (cnode->prev_in_stack || cnode->next_in_stack) { struct ScrollerStackNode *move_out_refer_node = cnode->prev_in_stack ? cnode->prev_in_stack : cnode->next_in_stack; scroller_node_remove(st, cnode); - // 必须先更新,不然里面节点还存着的是cnode的信息, - // 会造成stach_head/stack_tail指向的客户端不对 update_scroller_state(c->mon); Client *stack_head = @@ -1905,12 +1849,10 @@ int32_t scroller_apply_stack(Client *c, Client *target_client, if (!tnode || target_client->mon != c->mon) return 0; - /* 找到目标堆叠的尾部节点 */ struct ScrollerStackNode *tail = tnode; while (tail->next_in_stack) tail = tail->next_in_stack; - /* 通过封装好的插入函数实现(尾部插入) */ scroller_insert_stack(c, tail->client, false); if (c != tail->client) { @@ -1923,7 +1865,7 @@ int32_t scroller_apply_stack(Client *c, Client *target_client, int32_t scroller_stack(const Arg *arg) { if (!selmon) return 0; - Client *c = selmon->sel; + Client *c = arg->tc ? arg->tc : selmon->sel; if (!c || !c->mon || c->isfloating || !is_scroller_layout(selmon)) return 0; @@ -1933,15 +1875,18 @@ int32_t scroller_stack(const Arg *arg) { } int32_t toggle_all_floating(const Arg *arg) { - if (!selmon || !selmon->sel) + if (!selmon) return 0; - Client *c = NULL; - bool should_floating = !selmon->sel->isfloating; + Client *ref = arg->tc ? arg->tc : selmon->sel; + if (!ref) + return 0; + bool should_floating = !ref->isfloating; + + Client *c; 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; @@ -1957,7 +1902,6 @@ int32_t toggle_all_floating(const Arg *arg) { } int32_t dwindle_set_split_direction(Client *c, bool istoggle, bool horizontal) { - const Layout *layout = c->mon->pertag->ltidxs[c->mon->pertag->curtag]; if (layout->id != DWINDLE) @@ -1982,31 +1926,40 @@ int32_t dwindle_set_split_direction(Client *c, bool istoggle, bool horizontal) { } int32_t dwindle_toggle_split_direction(const Arg *arg) { - if (!selmon || !selmon->sel) + if (!selmon) return 0; - Client *c = selmon->sel; + Client *c = arg->tc ? arg->tc : selmon->sel; if (!c || !c->mon || c->isfloating) return 0; - return dwindle_set_split_direction(selmon->sel, true, false); + return dwindle_set_split_direction(c, true, false); } int32_t dwindle_split_horizontal(const Arg *arg) { - if (!selmon || !selmon->sel) + if (!selmon) return 0; - Client *c = selmon->sel; + Client *c = arg->tc ? arg->tc : selmon->sel; if (!c || !c->mon || c->isfloating) return 0; - return dwindle_set_split_direction(selmon->sel, false, true); + return dwindle_set_split_direction(c, false, true); } int32_t dwindle_split_vertical(const Arg *arg) { - if (!selmon || !selmon->sel) + if (!selmon) return 0; - Client *c = selmon->sel; + Client *c = arg->tc ? arg->tc : selmon->sel; if (!c || !c->mon || c->isfloating) return 0; - return dwindle_set_split_direction(selmon->sel, false, false); + return dwindle_set_split_direction(c, false, false); +} + +int32_t focusid(const Arg *arg) { + if (!selmon || !arg->tc) + return 0; + + Client *c = arg->tc; + focusclient(c, 1); + return 0; } \ No newline at end of file diff --git a/src/ext-protocol/dwl-ipc.h b/src/ext-protocol/dwl-ipc.h index ab0bdb8d..1d57a20e 100644 --- a/src/ext-protocol/dwl-ipc.h +++ b/src/ext-protocol/dwl-ipc.h @@ -192,7 +192,7 @@ void dwl_ipc_output_printstatus_to(DwlIpcOutput *ipc_output) { if (wl_resource_get_version(ipc_output->resource) >= ZDWL_IPC_OUTPUT_V2_LAST_LAYER_SINCE_VERSION) { zdwl_ipc_output_v2_send_last_layer(ipc_output->resource, - monitor->last_surface_ws_name); + monitor->last_open_surface); } if (wl_resource_get_version(ipc_output->resource) >= diff --git a/src/fetch/client.h b/src/fetch/client.h index e39e39b3..85b296a3 100644 --- a/src/fetch/client.h +++ b/src/fetch/client.h @@ -452,7 +452,7 @@ Client *find_client_by_direction(Client *tc, const Arg *arg, bool findfloating, Client *direction_select(const Arg *arg) { - Client *tc = selmon->sel; + Client *tc = arg->tc ? arg->tc : selmon->sel; if (!tc) return NULL; @@ -593,12 +593,12 @@ bool client_is_in_same_stack(Client *sc, Client *tc, Client *fc) { return false; } -Client *get_focused_stack_client(Client *sc) { +Client *get_focused_stack_client(Client *sc, Client *custom_focus_client) { if (!sc || sc->isfloating) return sc; Client *tc = NULL; - Client *fc = focustop(sc->mon); + Client *fc = custom_focus_client ? custom_focus_client : focustop(sc->mon); if (fc->isfloating || sc->isfloating) return sc; @@ -616,4 +616,4 @@ Client *get_focused_stack_client(Client *sc) { } } return sc; -} +} \ No newline at end of file diff --git a/src/ipc/ipc.h b/src/ipc/ipc.h new file mode 100644 index 00000000..21ead90d --- /dev/null +++ b/src/ipc/ipc.h @@ -0,0 +1,970 @@ +#include <cjson/cJSON.h> +#include <errno.h> +#include <fcntl.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> + +enum ipc_watch_type { + IPC_WATCH_NONE, + IPC_WATCH_MONITOR, + IPC_WATCH_CLIENT, + IPC_WATCH_TAGS, + IPC_WATCH_ALL_MONITORS, + IPC_WATCH_ALL_TAGS, + IPC_WATCH_ALL_CLIENTS, + IPC_WATCH_KEYMODE, + IPC_WATCH_KB_LAYOUT, + IPC_WATCH_LAST_OPEN_SURFACE, +}; + +struct ipc_watch_client { + struct wl_list link; + int fd; + struct wl_event_source *source; + enum ipc_watch_type type; + union { + struct { + char name[64]; + } monitor; + struct { + uint32_t id; + } client; + struct { + char mon_name[64]; + } tags; + } target; +}; + +static struct wl_list watch_clients; + +struct ipc_client_state { + int fd; + struct wl_event_source *source; + struct wl_event_loop *loop; + char *buf; + size_t buf_len; + size_t buf_cap; +}; + +static void ipc_remove_watch_client(struct ipc_watch_client *wc); +static void ipc_notify_json_to_fd(int fd, cJSON *json); + +/* ---------- 工具函数 ---------- */ + +static Monitor *monitor_by_name(const char *name) { + Monitor *m; + wl_list_for_each(m, &mons, link) { + if (strcmp(m->wlr_output->name, name) == 0) + return m; + } + return NULL; +} + +static Client *client_by_id(uint32_t id) { + Client *c; + wl_list_for_each(c, &clients, link) { + if (c->id == id) + return c; + } + return NULL; +} + +static const char *ipc_get_layout_str(void) { + struct wlr_keyboard *keyboard = &kb_group->wlr_group->keyboard; + xkb_layout_index_t current = xkb_state_serialize_layout( + keyboard->xkb_state, XKB_STATE_LAYOUT_EFFECTIVE); + static char layout[32]; + get_layout_abbr(layout, + xkb_keymap_layout_get_name(keyboard->keymap, current)); + return layout; +} + +static cJSON *tags_mask_to_array(uint32_t tagmask) { + cJSON *arr = cJSON_CreateArray(); + for (int i = 0; i < LENGTH(tags); i++) + if (tagmask & (1 << i)) + cJSON_AddItemToArray(arr, cJSON_CreateNumber(i + 1)); + return arr; +} + +static cJSON *build_tags_json(Monitor *m) { + cJSON *tags_array = cJSON_CreateArray(); + Client *c = NULL; + for (int tag = 1; tag <= LENGTH(tags); tag++) { + int numclients = 0; + bool is_active = false, is_urgent = false; + uint32_t tagmask = 1 << (tag - 1); + if (tagmask & m->tagset[m->seltags]) + is_active = true; + wl_list_for_each(c, &clients, link) { + if (c->mon != m) + continue; + if (!(c->tags & tagmask & TAGMASK)) + continue; + if (c->isurgent) + is_urgent = true; + numclients++; + } + cJSON *tag_obj = cJSON_CreateObject(); + cJSON_AddNumberToObject(tag_obj, "index", tag); + cJSON_AddBoolToObject(tag_obj, "is_active", is_active); + cJSON_AddBoolToObject(tag_obj, "is_urgent", is_urgent); + cJSON_AddStringToObject(tag_obj, "layout", + m->pertag->ltidxs[tag]->symbol); + cJSON_AddNumberToObject(tag_obj, "client_count", numclients); + cJSON_AddItemToArray(tags_array, tag_obj); + } + return tags_array; +} + +static cJSON *monitor_active_client(Monitor *m) { + cJSON *obj = cJSON_CreateObject(); + if (!m->sel) { + cJSON_AddNullToObject(obj, "id"); + cJSON_AddNullToObject(obj, "title"); + cJSON_AddNullToObject(obj, "appid"); + return obj; + } + Client *c = m->sel; + cJSON_AddNumberToObject(obj, "id", c->id); + cJSON_AddStringToObject(obj, "title", client_get_title(c)); + cJSON_AddStringToObject(obj, "appid", client_get_appid(c)); + return obj; +} + +static cJSON *monitor_active_tags(Monitor *m) { + cJSON *arr = cJSON_CreateArray(); + uint32_t tagset; + if (m->isoverview) { + cJSON_AddItemToArray(arr, cJSON_CreateNumber(0)); + return arr; + } + tagset = m->tagset[m->seltags]; + for (int i = 0; i < LENGTH(tags); i++) + if (tagset & (1 << i)) + cJSON_AddItemToArray(arr, cJSON_CreateNumber(i + 1)); + return arr; +} + +static cJSON *build_client_json(Client *c) { + cJSON *obj = cJSON_CreateObject(); + cJSON_AddNumberToObject(obj, "id", c->id); + cJSON_AddNumberToObject(obj, "pid", c->pid); + cJSON_AddStringToObject(obj, "title", client_get_title(c)); + cJSON_AddStringToObject(obj, "appid", client_get_appid(c)); + cJSON_AddStringToObject(obj, "monitor", c->mon->wlr_output->name); + cJSON_AddItemToObject(obj, "tags", tags_mask_to_array(c->tags)); + cJSON_AddBoolToObject(obj, "is_focused", c == focustop(c->mon)); + cJSON_AddBoolToObject(obj, "is_fullscreen", c->isfullscreen); + cJSON_AddBoolToObject(obj, "is_floating", c->isfloating); + cJSON_AddBoolToObject(obj, "is_maximized", c->ismaximizescreen); + cJSON_AddBoolToObject(obj, "is_global", c->isglobal); + cJSON_AddBoolToObject(obj, "is_unglobal", c->isunglobal); + cJSON_AddBoolToObject(obj, "is_overlay", c->isoverlay); + cJSON_AddBoolToObject(obj, "is_fakefullscreen", c->isfakefullscreen); + cJSON_AddBoolToObject(obj, "is_minimized", c->isminimized); + cJSON_AddBoolToObject(obj, "is_urgent", c->isurgent); + cJSON_AddBoolToObject(obj, "is_scratchpad", c->is_in_scratchpad); + cJSON_AddBoolToObject(obj, "is_namedscratchpad", c->isnamedscratchpad); + cJSON_AddNumberToObject(obj, "x", c->geom.x); + cJSON_AddNumberToObject(obj, "y", c->geom.y); + cJSON_AddNumberToObject(obj, "width", c->geom.width); + cJSON_AddNumberToObject(obj, "height", c->geom.height); + return obj; +} + +static cJSON *build_monitor_json(Monitor *m) { + cJSON *resp = cJSON_CreateObject(); + cJSON_AddStringToObject(resp, "name", m->wlr_output->name); + cJSON_AddBoolToObject(resp, "active", m == selmon); + cJSON_AddNumberToObject(resp, "x", m->m.x); + cJSON_AddNumberToObject(resp, "y", m->m.y); + cJSON_AddNumberToObject(resp, "width", m->m.width); + cJSON_AddNumberToObject(resp, "height", m->m.height); + cJSON_AddNumberToObject(resp, "scale", m->wlr_output->scale); + cJSON_AddNumberToObject(resp, "layout_index", + m->pertag->ltidxs[m->pertag->curtag] - layouts); + cJSON_AddStringToObject(resp, "layout_symbol", + m->pertag->ltidxs[m->pertag->curtag]->symbol); + cJSON_AddStringToObject(resp, "last_open_surface", m->last_open_surface); + cJSON_AddItemToObject(resp, "tags", build_tags_json(m)); + cJSON_AddItemToObject(resp, "active_tags", monitor_active_tags(m)); + cJSON_AddItemToObject(resp, "active_client", monitor_active_client(m)); + cJSON_AddItemToObject(resp, "keymode", cJSON_CreateString(keymode.mode)); + cJSON_AddItemToObject(resp, "keyboardlayout", + cJSON_CreateString(ipc_get_layout_str())); + return resp; +} + +static cJSON *build_all_tags_entry(Monitor *m) { + cJSON *entry = cJSON_CreateObject(); + cJSON_AddStringToObject(entry, "monitor", m->wlr_output->name); + cJSON_AddItemToObject(entry, "tags", build_tags_json(m)); + return entry; +} + +static cJSON *build_all_tags_response(void) { + cJSON *arr = cJSON_CreateArray(); + Monitor *m; + wl_list_for_each(m, &mons, link) + cJSON_AddItemToArray(arr, build_all_tags_entry(m)); + cJSON *resp = cJSON_CreateObject(); + cJSON_AddItemToObject(resp, "all_tags", arr); + return resp; +} + +static cJSON *build_monitor_tags_response(Monitor *m) { + cJSON *resp = cJSON_CreateObject(); + cJSON_AddStringToObject(resp, "monitor", m->wlr_output->name); + cJSON_AddItemToObject(resp, "tags", build_tags_json(m)); + cJSON_AddItemToObject(resp, "active_tags", monitor_active_tags(m)); + return resp; +} + +static void send_static_json(int fd, const char *json_str) { + size_t len = strlen(json_str); + send(fd, json_str, len, MSG_NOSIGNAL); +} + +/* ---------- 一次性命令处理 ---------- */ +static void handle_command(int client_fd, const char *cmd_raw) { + cJSON *resp = NULL; + char *json_str = NULL; + char cmd[1024]; + + strncpy(cmd, cmd_raw, sizeof(cmd) - 1); + cmd[sizeof(cmd) - 1] = '\0'; + for (char *p = cmd; *p; p++) + if (*p == ',') + *p = ' '; + + if (strcmp(cmd, "get version") == 0) { + resp = cJSON_CreateObject(); + cJSON_AddStringToObject(resp, "version", VERSION); + } else if (strcmp(cmd, "get keymode") == 0) { + resp = cJSON_CreateObject(); + cJSON_AddStringToObject(resp, "keymode", keymode.mode); + } else if (strcmp(cmd, "get keyboardlayout") == 0) { + resp = cJSON_CreateObject(); + cJSON_AddStringToObject(resp, "layout", ipc_get_layout_str()); + } else if (strncmp(cmd, "get last_open_surface ", 25) == 0) { + const char *name = cmd + 25; + Monitor *m = monitor_by_name(name); + if (!m) { + send_static_json(client_fd, "{\"error\":\"monitor not found\"}\n"); + return; + } + resp = cJSON_CreateObject(); + cJSON_AddStringToObject(resp, "monitor", m->wlr_output->name); + cJSON_AddStringToObject(resp, "last_open_surface", + m->last_open_surface); + } else if (strncmp(cmd, "get monitor ", 12) == 0) { + Monitor *m = monitor_by_name(cmd + 12); + if (!m) { + send_static_json(client_fd, "{\"error\":\"monitor not found\"}\n"); + return; + } + resp = build_monitor_json(m); + } else if (strncmp(cmd, "get client ", 11) == 0) { + Client *c = client_by_id((uint32_t)atoi(cmd + 11)); + if (!c) { + send_static_json(client_fd, "{\"error\":\"client not found\"}\n"); + return; + } + resp = build_client_json(c); + } else if (strncmp(cmd, "get tag ", 8) == 0) { + char mon_name[64]; + int ext_tag_idx; + if (sscanf(cmd + 8, "%63s %d", mon_name, &ext_tag_idx) != 2) { + send_static_json( + client_fd, + "{\"error\":\"usage: get tag <monitor> <index>\"}\n"); + return; + } + int tag_idx = ext_tag_idx - 1; + Monitor *m = monitor_by_name(mon_name); + if (!m || tag_idx < 0 || tag_idx >= LENGTH(tags)) { + send_static_json(client_fd, + "{\"error\":\"invalid monitor or tag index\"}\n"); + return; + } + uint32_t tagmask = 1 << tag_idx; + int numclients = 0, focused_client = 0; + bool is_active = false, is_urgent = false; + if (tagmask & m->tagset[m->seltags]) + is_active = true; + + Client *c, *focused = focustop(m); + wl_list_for_each(c, &clients, link) { + if (c->mon != m || !(c->tags & tagmask)) + continue; + if (c == focused) + focused_client = 1; + if (c->isurgent) + is_urgent = true; + numclients++; + } + resp = cJSON_CreateObject(); + cJSON_AddStringToObject(resp, "monitor", m->wlr_output->name); + cJSON_AddNumberToObject(resp, "tag_index", ext_tag_idx); + cJSON_AddBoolToObject(resp, "is_active", is_active); + cJSON_AddBoolToObject(resp, "is_urgent", is_urgent); + cJSON_AddNumberToObject(resp, "client_count", numclients); + cJSON_AddBoolToObject(resp, "focused_client", focused_client); + } else if (strcmp(cmd, "get all-clients") == 0) { + cJSON *arr = cJSON_CreateArray(); + Client *c; + wl_list_for_each(c, &clients, link) + cJSON_AddItemToArray(arr, build_client_json(c)); + resp = cJSON_CreateObject(); + cJSON_AddItemToObject(resp, "clients", arr); + } else if (strcmp(cmd, "get all-monitors") == 0) { + cJSON *arr = cJSON_CreateArray(); + Monitor *m; + wl_list_for_each(m, &mons, link) + cJSON_AddItemToArray(arr, build_monitor_json(m)); + resp = cJSON_CreateObject(); + cJSON_AddItemToObject(resp, "monitors", arr); + } else if (strcmp(cmd, "get all-tags") == 0) { + resp = build_all_tags_response(); + } else if (strncmp(cmd, "get tags ", 9) == 0) { + Monitor *m = monitor_by_name(cmd + 9); + if (!m) { + send_static_json(client_fd, "{\"error\":\"monitor not found\"}\n"); + return; + } + resp = build_monitor_tags_response(m); + } else if (strncmp(cmd, "dispatch ", 9) == 0) { + char *dispatch_copy = strdup(cmd_raw + 9); + char *out = dispatch_copy, *ptr = dispatch_copy; + int client_id = -1; + while (*ptr) { + while (*ptr == ' ' || *ptr == '\t') + *out++ = *ptr++; + if (strncmp(ptr, "client,", 7) == 0) { + char *end; + long id = strtol(ptr + 7, &end, 10); + if (id > 0 && end > ptr + 7 && (*end == '\0' || *end == ',')) { + client_id = (int)id; + ptr = end; + if (*ptr == ',') + ptr++; + continue; + } + } + *out++ = *ptr++; + } + *out = '\0'; + + char *tokens[6] = {NULL}; + int token_count = 0; + char *saveptr; + char *token = strtok_r(dispatch_copy, ",", &saveptr); + while (token && token_count < 6) { + while (*token == ' ' || *token == '\t') + token++; + char *end = token + strlen(token) - 1; + while (end >= token && (*end == ' ' || *end == '\t')) + *end-- = '\0'; + tokens[token_count++] = token; + token = strtok_r(NULL, ",", &saveptr); + } + + Arg arg = {0}; + int32_t (*func)(const Arg *) = + parse_func_name(token_count > 0 ? tokens[0] : "", &arg, + token_count > 1 ? tokens[1] : NULL, + token_count > 2 ? tokens[2] : NULL, + token_count > 3 ? tokens[3] : NULL, + token_count > 4 ? tokens[4] : NULL, + token_count > 5 ? tokens[5] : NULL); + + if (func && client_id > 0) + arg.tc = client_by_id((uint32_t)client_id); + + if (func) { + func(&arg); + send_static_json(client_fd, "{\"success\":true}\n"); + } else { + send_static_json(client_fd, "{\"error\":\"unknown function\"}\n"); + } + + if (arg.v) + free(arg.v); + if (arg.v2) + free(arg.v2); + if (arg.v3) + free(arg.v3); + free(dispatch_copy); + return; // Fast path exit + } else { + send_static_json(client_fd, "{\"error\":\"unknown command\"}\n"); + return; + } + + if (resp) { + json_str = cJSON_PrintUnformatted(resp); + if (json_str) { + size_t len = strlen(json_str); + char *msg = malloc(len + 2); + if (msg) { + snprintf(msg, len + 2, "%s\n", json_str); + send(client_fd, msg, len + 1, MSG_NOSIGNAL); + free(msg); + } + free(json_str); + } + cJSON_Delete(resp); + } +} + +/* ---------- Watch 模式支持 ---------- */ +static void ipc_notify_json_to_fd(int fd, cJSON *json) { + char *str = cJSON_PrintUnformatted(json); + if (!str) + return; + size_t len = strlen(str); + char *msg = malloc(len + 2); + if (!msg) { + free(str); + return; + } + snprintf(msg, len + 2, "%s\n", str); + if (send(fd, msg, len + 1, MSG_NOSIGNAL) < 0) { + struct ipc_watch_client *wc, *tmp; + wl_list_for_each_safe(wc, tmp, &watch_clients, link) { + if (wc->fd == fd) { + ipc_remove_watch_client(wc); + break; + } + } + } + free(msg); + free(str); +} + +static void ipc_remove_watch_client(struct ipc_watch_client *wc) { + wl_list_remove(&wc->link); + wl_event_source_remove(wc->source); + close(wc->fd); + free(wc); +} + +static int ipc_watch_data_handler(int fd, uint32_t mask, void *data) { + struct ipc_watch_client *wc = data; + if (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR)) { + ipc_remove_watch_client(wc); + return 0; + } + if (mask & WL_EVENT_READABLE) { + char buf[64]; + ssize_t n = recv(fd, buf, sizeof(buf), MSG_DONTWAIT); + if (n == 0 || (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK)) { + ipc_remove_watch_client(wc); + } + } + return 0; +} + +static bool handle_watch_command(int fd, const char *cmd, + struct ipc_client_state *client) { + enum ipc_watch_type type = IPC_WATCH_NONE; + const char *arg = NULL; + uint32_t client_id = 0; + + if (strncmp(cmd, "watch monitor ", 14) == 0) { + type = IPC_WATCH_MONITOR; + arg = cmd + 14; + } else if (strncmp(cmd, "watch client ", 13) == 0) { + type = IPC_WATCH_CLIENT; + client_id = (uint32_t)atoi(cmd + 13); + } else if (strncmp(cmd, "watch tags ", 11) == 0) { + type = IPC_WATCH_TAGS; + arg = cmd + 11; + } else if (strcmp(cmd, "watch all-monitors") == 0) { + type = IPC_WATCH_ALL_MONITORS; + } else if (strcmp(cmd, "watch all-tags") == 0) { + type = IPC_WATCH_ALL_TAGS; + } else if (strcmp(cmd, "watch all-clients") == 0) { + type = IPC_WATCH_ALL_CLIENTS; + } else if (strcmp(cmd, "watch keymode") == 0) { + type = IPC_WATCH_KEYMODE; + } else if (strcmp(cmd, "watch keyboardlayout") == 0) { + type = IPC_WATCH_KB_LAYOUT; + } else if (strncmp(cmd, "watch last_open_surface ", 27) == 0) { + type = IPC_WATCH_LAST_OPEN_SURFACE; + arg = cmd + 27; + } + + if (type == IPC_WATCH_NONE) + return false; + + struct ipc_watch_client *wc = calloc(1, sizeof(*wc)); + wc->fd = fd; + wc->type = type; + + if ((type == IPC_WATCH_MONITOR || type == IPC_WATCH_LAST_OPEN_SURFACE) && + arg) + snprintf(wc->target.monitor.name, sizeof(wc->target.monitor.name), "%s", + arg); + else if (type == IPC_WATCH_TAGS && arg) + snprintf(wc->target.tags.mon_name, sizeof(wc->target.tags.mon_name), + "%s", arg); + else if (type == IPC_WATCH_CLIENT) + wc->target.client.id = client_id; + + wl_event_source_remove(client->source); + wc->source = wl_event_loop_add_fd( + client->loop, fd, WL_EVENT_READABLE | WL_EVENT_HANGUP | WL_EVENT_ERROR, + ipc_watch_data_handler, wc); + wl_list_insert(&watch_clients, &wc->link); + + /* 推送初始状态 */ + cJSON *json = NULL; + switch (type) { + case IPC_WATCH_MONITOR: { + Monitor *m = monitor_by_name(arg); + if (m) + json = build_monitor_json(m); + break; + } + case IPC_WATCH_LAST_OPEN_SURFACE: { + Monitor *m = monitor_by_name(arg); + if (m) { + json = cJSON_CreateObject(); + cJSON_AddStringToObject(json, "monitor", m->wlr_output->name); + cJSON_AddStringToObject(json, "last_open_surface", + m->last_open_surface); + } + break; + } + case IPC_WATCH_CLIENT: { + Client *c = client_by_id(client_id); + if (c) + json = build_client_json(c); + break; + } + case IPC_WATCH_TAGS: { + Monitor *m = monitor_by_name(arg); + if (m) + json = build_monitor_tags_response(m); + break; + } + case IPC_WATCH_ALL_MONITORS: { + cJSON *arr = cJSON_CreateArray(); + Monitor *m; + wl_list_for_each(m, &mons, link) + cJSON_AddItemToArray(arr, build_monitor_json(m)); + json = cJSON_CreateObject(); + cJSON_AddItemToObject(json, "monitors", arr); + break; + } + case IPC_WATCH_ALL_TAGS: { + json = build_all_tags_response(); + break; + } + case IPC_WATCH_ALL_CLIENTS: { + cJSON *arr = cJSON_CreateArray(); + Client *c; + wl_list_for_each(c, &clients, link) + cJSON_AddItemToArray(arr, build_client_json(c)); + json = cJSON_CreateObject(); + cJSON_AddItemToObject(json, "clients", arr); + break; + } + case IPC_WATCH_KEYMODE: { + json = cJSON_CreateObject(); + cJSON_AddStringToObject(json, "keymode", keymode.mode); + break; + } + case IPC_WATCH_KB_LAYOUT: { + json = cJSON_CreateObject(); + cJSON_AddStringToObject(json, "layout", ipc_get_layout_str()); + break; + } + default: + break; + } + + if (json) { + ipc_notify_json_to_fd(fd, json); + cJSON_Delete(json); + } + + free(client->buf); + free(client); + return true; +} + +/* ---------- Socket 事件处理 ---------- */ +static int ipc_handle_client_data(int fd, uint32_t mask, void *data) { + struct ipc_client_state *client = data; + if (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR)) + goto cleanup; + + if (mask & WL_EVENT_READABLE) { + size_t available = client->buf_cap - client->buf_len; + if (available < 4096) { + size_t new_cap = client->buf_cap ? client->buf_cap * 2 : 8192; + char *new_buf = realloc(client->buf, new_cap); + if (!new_buf) { + wlr_log(WLR_ERROR, "IPC: out of memory"); + goto cleanup; + } + client->buf = new_buf; + client->buf_cap = new_cap; + available = client->buf_cap - client->buf_len; + } + + // 直接读取到 client->buf 尾部,跳过临时数组 + ssize_t n = recv(fd, client->buf + client->buf_len, available - 1, + MSG_DONTWAIT); + if (n <= 0) + goto cleanup; + + client->buf_len += n; + client->buf[client->buf_len] = '\0'; + + char *nl = memchr(client->buf, '\n', client->buf_len); + if (!nl) { + if (client->buf_len > 1024 * 1024) + goto cleanup; // 防御过长命令 + return 0; + } + *nl = '\0'; + char *cmd = client->buf; + + bool is_watch = handle_watch_command(fd, cmd, client); + if (is_watch) + return 0; + + handle_command(fd, cmd); + goto cleanup; + } + return 0; + +cleanup: + close(client->fd); + wl_event_source_remove(client->source); + free(client->buf); + free(client); + return 0; +} + +static int ipc_handle_connection(int fd, uint32_t mask, void *data) { + struct wl_event_loop *loop = data; + int client_fd = accept(fd, NULL, NULL); + if (client_fd < 0) + return 0; + + int flags = fcntl(client_fd, F_GETFL, 0); + fcntl(client_fd, F_SETFL, flags | O_NONBLOCK); + + struct ipc_client_state *client = calloc(1, sizeof(*client)); + client->fd = client_fd; + client->loop = loop; + client->source = wl_event_loop_add_fd( + loop, client_fd, WL_EVENT_READABLE | WL_EVENT_HANGUP | WL_EVENT_ERROR, + ipc_handle_client_data, client); + return 0; +} + +/* ---------- 外部通知接口 ---------- */ + +void ipc_notify_monitor(Monitor *m) { + char *json_str = NULL; + size_t len = 0; + struct ipc_watch_client *wc, *tmp; + wl_list_for_each_safe(wc, tmp, &watch_clients, link) { + if (wc->type == IPC_WATCH_MONITOR && + strcmp(m->wlr_output->name, wc->target.monitor.name) == 0) { + if (!json_str) { + cJSON *json = build_monitor_json(m); + char *raw = cJSON_PrintUnformatted(json); + cJSON_Delete(json); + if (!raw) + return; + len = strlen(raw); + json_str = malloc(len + 2); + snprintf(json_str, len + 2, "%s\n", raw); + free(raw); + } + if (send(wc->fd, json_str, len + 1, MSG_NOSIGNAL) < 0) + ipc_remove_watch_client(wc); + } + } + if (json_str) + free(json_str); +} + +void ipc_notify_last_surface_ws_name(Monitor *m) { + char *json_str = NULL; + size_t len = 0; + struct ipc_watch_client *wc, *tmp; + wl_list_for_each_safe(wc, tmp, &watch_clients, link) { + if (wc->type == IPC_WATCH_LAST_OPEN_SURFACE && + strcmp(m->wlr_output->name, wc->target.monitor.name) == 0) { + if (!json_str) { + cJSON *json = cJSON_CreateObject(); + cJSON_AddStringToObject(json, "monitor", m->wlr_output->name); + cJSON_AddStringToObject(json, "last_open_surface", + m->last_open_surface); + char *raw = cJSON_PrintUnformatted(json); + cJSON_Delete(json); + if (!raw) + return; + len = strlen(raw); + json_str = malloc(len + 2); + snprintf(json_str, len + 2, "%s\n", raw); + free(raw); + } + if (send(wc->fd, json_str, len + 1, MSG_NOSIGNAL) < 0) + ipc_remove_watch_client(wc); + } + } + if (json_str) + free(json_str); +} + +void ipc_notify_client(Client *c) { + char *json_str = NULL; + size_t len = 0; + struct ipc_watch_client *wc, *tmp; + wl_list_for_each_safe(wc, tmp, &watch_clients, link) { + if (wc->type == IPC_WATCH_CLIENT && c->id == wc->target.client.id) { + if (!json_str) { + cJSON *json = build_client_json(c); + char *raw = cJSON_PrintUnformatted(json); + cJSON_Delete(json); + if (!raw) + return; + len = strlen(raw); + json_str = malloc(len + 2); + snprintf(json_str, len + 2, "%s\n", raw); + free(raw); + } + if (send(wc->fd, json_str, len + 1, MSG_NOSIGNAL) < 0) + ipc_remove_watch_client(wc); + } + } + if (json_str) + free(json_str); +} + +void ipc_notify_tags(Monitor *m) { + char *json_str = NULL; + size_t len = 0; + struct ipc_watch_client *wc, *tmp; + wl_list_for_each_safe(wc, tmp, &watch_clients, link) { + if (wc->type == IPC_WATCH_TAGS && + strcmp(m->wlr_output->name, wc->target.tags.mon_name) == 0) { + if (!json_str) { + cJSON *json = build_monitor_tags_response(m); + char *raw = cJSON_PrintUnformatted(json); + cJSON_Delete(json); + if (!raw) + return; + len = strlen(raw); + json_str = malloc(len + 2); + snprintf(json_str, len + 2, "%s\n", raw); + free(raw); + } + if (send(wc->fd, json_str, len + 1, MSG_NOSIGNAL) < 0) + ipc_remove_watch_client(wc); + } + } + if (json_str) + free(json_str); +} + +void ipc_notify_all_monitors(void) { + char *json_str = NULL; + size_t len = 0; + struct ipc_watch_client *wc, *tmp; + wl_list_for_each_safe(wc, tmp, &watch_clients, link) { + if (wc->type == IPC_WATCH_ALL_MONITORS) { + if (!json_str) { + cJSON *arr = cJSON_CreateArray(); + Monitor *m; + wl_list_for_each(m, &mons, link) + cJSON_AddItemToArray(arr, build_monitor_json(m)); + cJSON *json = cJSON_CreateObject(); + cJSON_AddItemToObject(json, "monitors", arr); + char *raw = cJSON_PrintUnformatted(json); + cJSON_Delete(json); + if (!raw) + return; + len = strlen(raw); + json_str = malloc(len + 2); + snprintf(json_str, len + 2, "%s\n", raw); + free(raw); + } + if (send(wc->fd, json_str, len + 1, MSG_NOSIGNAL) < 0) + ipc_remove_watch_client(wc); + } + } + if (json_str) + free(json_str); +} + +void ipc_notify_all_clients(void) { + char *json_str = NULL; + size_t len = 0; + struct ipc_watch_client *wc, *tmp; + wl_list_for_each_safe(wc, tmp, &watch_clients, link) { + if (wc->type == IPC_WATCH_ALL_CLIENTS) { + if (!json_str) { + cJSON *arr = cJSON_CreateArray(); + Client *c; + wl_list_for_each(c, &clients, link) + cJSON_AddItemToArray(arr, build_client_json(c)); + cJSON *json = cJSON_CreateObject(); + cJSON_AddItemToObject(json, "clients", arr); + char *raw = cJSON_PrintUnformatted(json); + cJSON_Delete(json); + if (!raw) + return; + len = strlen(raw); + json_str = malloc(len + 2); + snprintf(json_str, len + 2, "%s\n", raw); + free(raw); + } + if (send(wc->fd, json_str, len + 1, MSG_NOSIGNAL) < 0) + ipc_remove_watch_client(wc); + } + } + if (json_str) + free(json_str); +} + +void ipc_notify_all_tags(void) { + char *json_str = NULL; + size_t len = 0; + struct ipc_watch_client *wc, *tmp; + wl_list_for_each_safe(wc, tmp, &watch_clients, link) { + if (wc->type == IPC_WATCH_ALL_TAGS) { + if (!json_str) { + cJSON *json = build_all_tags_response(); + char *raw = cJSON_PrintUnformatted(json); + cJSON_Delete(json); + if (!raw) + return; + len = strlen(raw); + json_str = malloc(len + 2); + snprintf(json_str, len + 2, "%s\n", raw); + free(raw); + } + if (send(wc->fd, json_str, len + 1, MSG_NOSIGNAL) < 0) + ipc_remove_watch_client(wc); + } + } + if (json_str) + free(json_str); +} + +void ipc_notify_keymode(void) { + char *json_str = NULL; + size_t len = 0; + struct ipc_watch_client *wc, *tmp; + wl_list_for_each_safe(wc, tmp, &watch_clients, link) { + if (wc->type == IPC_WATCH_KEYMODE) { + if (!json_str) { + cJSON *json = cJSON_CreateObject(); + cJSON_AddStringToObject(json, "keymode", keymode.mode); + char *raw = cJSON_PrintUnformatted(json); + cJSON_Delete(json); + if (!raw) + return; + len = strlen(raw); + json_str = malloc(len + 2); + snprintf(json_str, len + 2, "%s\n", raw); + free(raw); + } + if (send(wc->fd, json_str, len + 1, MSG_NOSIGNAL) < 0) + ipc_remove_watch_client(wc); + } + } + if (json_str) + free(json_str); +} + +void ipc_notify_kb_layout(void) { + char *json_str = NULL; + size_t len = 0; + struct ipc_watch_client *wc, *tmp; + wl_list_for_each_safe(wc, tmp, &watch_clients, link) { + if (wc->type == IPC_WATCH_KB_LAYOUT) { + if (!json_str) { + cJSON *json = cJSON_CreateObject(); + cJSON_AddStringToObject(json, "layout", ipc_get_layout_str()); + char *raw = cJSON_PrintUnformatted(json); + cJSON_Delete(json); + if (!raw) + return; + len = strlen(raw); + json_str = malloc(len + 2); + snprintf(json_str, len + 2, "%s\n", raw); + free(raw); + } + if (send(wc->fd, json_str, len + 1, MSG_NOSIGNAL) < 0) + ipc_remove_watch_client(wc); + } + } + if (json_str) + free(json_str); +} + +/* ---------- 初始化与清理 ---------- */ +static int ipc_sock_fd = -1; +static struct wl_event_source *ipc_event_source = NULL; +static char ipc_socket_path[256]; + +void ipc_init(struct wl_event_loop *event_loop) { + wl_list_init(&watch_clients); + + const char *xdg_runtime = getenv("XDG_RUNTIME_DIR"); + if (!xdg_runtime) + return; + + snprintf(ipc_socket_path, sizeof(ipc_socket_path), "%s/mango-%d.sock", + xdg_runtime, getpid()); + + ipc_sock_fd = + socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); + if (ipc_sock_fd < 0) + return; + + struct sockaddr_un addr = {.sun_family = AF_UNIX}; + strncpy(addr.sun_path, ipc_socket_path, sizeof(addr.sun_path) - 1); + + unlink(ipc_socket_path); + if (bind(ipc_sock_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + close(ipc_sock_fd); + return; + } + listen(ipc_sock_fd, 16); + + setenv("MANGO_INSTANCE_SIGNATURE", ipc_socket_path, 1); + + ipc_event_source = + wl_event_loop_add_fd(event_loop, ipc_sock_fd, WL_EVENT_READABLE, + ipc_handle_connection, event_loop); +} + +void ipc_cleanup(void) { + if (ipc_event_source) + wl_event_source_remove(ipc_event_source); + if (ipc_sock_fd >= 0) + close(ipc_sock_fd); + unlink(ipc_socket_path); + unsetenv("MANGO_INSTANCE_SIGNATURE"); + + struct ipc_watch_client *wc, *tmp; + wl_list_for_each_safe(wc, tmp, &watch_clients, link) + ipc_remove_watch_client(wc); +} \ No newline at end of file diff --git a/src/mango.c b/src/mango.c index 43eecf6d..ffc0d35f 100644 --- a/src/mango.c +++ b/src/mango.c @@ -197,28 +197,6 @@ enum seat_config_shortcuts_inhibit { SHORTCUTS_INHIBIT_ENABLE, }; -// 事件掩码枚举 -enum print_event_type { - PRINT_ACTIVE = 1 << 0, - PRINT_TAG = 1 << 1, - PRINT_LAYOUT = 1 << 2, - PRINT_TITLE = 1 << 3, - PRINT_APPID = 1 << 4, - PRINT_LAYOUT_SYMBOL = 1 << 5, - PRINT_FULLSCREEN = 1 << 6, - PRINT_FLOATING = 1 << 7, - PRINT_X = 1 << 8, - PRINT_Y = 1 << 9, - PRINT_WIDTH = 1 << 10, - PRINT_HEIGHT = 1 << 11, - PRINT_LAST_LAYER = 1 << 12, - PRINT_KB_LAYOUT = 1 << 13, - PRINT_KEYMODE = 1 << 14, - PRINT_SCALEFACTOR = 1 << 15, - PRINT_FRAME = 1 << 16, - PRINT_ALL = (1 << 17) - 1 // 所有位都设为1 -}; - typedef struct Pertag Pertag; typedef struct Monitor Monitor; typedef struct Client Client; @@ -241,6 +219,7 @@ typedef struct { char *v3; uint32_t ui; uint32_t ui2; + Client *tc; } Arg; typedef struct { @@ -441,6 +420,7 @@ struct Client { float old_grid_row_per; int32_t grid_col_idx; int32_t grid_row_idx; + uint32_t id; }; typedef struct { @@ -557,7 +537,7 @@ struct Monitor { uint32_t visible_scroll_tiling_clients; uint32_t visible_fake_tiling_clients; struct wlr_scene_optimized_blur *blur; - char last_surface_ws_name[256]; + char last_open_surface[256]; struct wlr_ext_workspace_group_handle_v1 *ext_group; bool iscleanuping; int8_t carousel_anim_dir; @@ -857,7 +837,8 @@ static Client *find_client_by_direction(Client *tc, const Arg *arg, static void exit_scroller_stack(Client *c); static Client *scroll_get_stack_head_client(Client *c); static bool client_only_in_one_tag(Client *c); -static Client *get_focused_stack_client(Client *sc); +static Client *get_focused_stack_client(Client *sc, + Client *custom_focus_client); static bool client_is_in_same_stack(Client *sc, Client *tc, Client *fc); static void monitor_stop_skip_frame_timer(Monitor *m); static int monitor_skip_frame_timeout_callback(void *data); @@ -1005,6 +986,7 @@ static char *env_vars[] = {"DISPLAY", "XDG_SESSION_TYPE", "XCURSOR_THEME", "XCURSOR_SIZE", + "MANGO_INSTANCE_SIGNATURE", NULL}; static struct { enum wp_cursor_shape_device_v1_shape shape; @@ -1094,6 +1076,7 @@ static struct wl_event_source *sync_keymap; #include "dispatch/bind_define.h" #include "ext-protocol/all.h" #include "fetch/fetch.h" +#include "ipc/ipc.h" #include "layout/arrange.h" #include "layout/dwindle.h" #include "layout/horizontal.h" @@ -2511,6 +2494,7 @@ void cleanuplisteners(void) { } void cleanup(void) { + ipc_cleanup(); cleanuplisteners(); #ifdef XWAYLAND wlr_xwayland_destroy(xwayland); @@ -2668,9 +2652,9 @@ void maplayersurfacenotify(struct wl_listener *listener, void *data) { if (!l->mon) return; - strncpy(l->mon->last_surface_ws_name, layer_surface->namespace, - sizeof(l->mon->last_surface_ws_name) - 1); // 最多拷贝255个字符 - l->mon->last_surface_ws_name[sizeof(l->mon->last_surface_ws_name) - 1] = + strncpy(l->mon->last_open_surface, layer_surface->namespace, + sizeof(l->mon->last_open_surface) - 1); // 最多拷贝255个字符 + l->mon->last_open_surface[sizeof(l->mon->last_open_surface) - 1] = '\0'; // 确保字符串以null结尾 // 初始化几何位置 @@ -4418,6 +4402,9 @@ mapnotify(struct wl_listener *listener, void *data) { Client *at_client = NULL; Client *c = wl_container_of(listener, c, map); int32_t i = 0; + + c->id = generate_client_id(); + /* Create scene tree for this client and its border */ c->scene = client_surface(c)->data = wlr_scene_tree_create(layers[LyrTile]); wlr_scene_node_set_enabled(&c->scene->node, c->type != XDGShell); @@ -5721,13 +5708,30 @@ void create_output(struct wlr_backend *backend, void *data) { // 修改信号处理函数,接收掩码参数 void handle_print_status(struct wl_listener *listener, void *data) { + ipc_notify_keymode(); + ipc_notify_kb_layout(); + ipc_notify_all_tags(); + ipc_notify_all_clients(); + ipc_notify_all_monitors(); + + Client *c = NULL; + wl_list_for_each(c, &clients, link) { + if (c->iskilling) + continue; + ipc_notify_client(c); + } + Monitor *m = NULL; wl_list_for_each(m, &mons, link) { if (!m->wlr_output->enabled) { continue; } - dwl_ext_workspace_printstatus(m); + ipc_notify_monitor(m); + ipc_notify_tags(m); + ipc_notify_last_surface_ws_name(m); + + dwl_ext_workspace_printstatus(m); dwl_ipc_output_printstatus(m); } } @@ -5756,6 +5760,9 @@ void setup(void) { * clients from the Unix socket, manging Wayland globals, and so on. */ dpy = wl_display_create(); event_loop = wl_display_get_event_loop(dpy); + + ipc_init(event_loop); + tablet_mgr = wlr_tablet_v2_create(dpy); /* The backend is a wlroots feature which abstracts the underlying input * and output hardware. The autocreate option will choose the most From 29d085cfbab0df0c065d9a8ba7ae0f2d25d369be Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 25 May 2026 11:37:12 +0800 Subject: [PATCH 241/328] fix: fix switch_layout dispatch --- src/dispatch/bind_define.h | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index ac3ba397..4e952408 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1031,19 +1031,20 @@ int32_t switch_layout(const Arg *arg) { int32_t jk, ji; char *target_layout_name = NULL; uint32_t len; + uint32_t target_tag = selmon->pertag->curtag ? selmon->pertag->curtag + : selmon->pertag->prevtag; if (!selmon) return 0; if (config.circle_layout_count != 0) { for (jk = 0; jk < config.circle_layout_count; jk++) { - len = MAX( - strlen(config.circle_layout[jk]), - strlen(selmon->pertag->ltidxs[selmon->pertag->curtag]->name)); + + len = MAX(strlen(config.circle_layout[jk]), + strlen(selmon->pertag->ltidxs[target_tag]->name)); if (strncmp(config.circle_layout[jk], - selmon->pertag->ltidxs[selmon->pertag->curtag]->name, - len) == 0) { + selmon->pertag->ltidxs[target_tag]->name, len) == 0) { target_layout_name = jk == config.circle_layout_count - 1 ? config.circle_layout[0] : config.circle_layout[jk + 1]; @@ -1058,20 +1059,16 @@ int32_t switch_layout(const Arg *arg) { for (ji = 0; ji < LENGTH(layouts); ji++) { len = MAX(strlen(layouts[ji].name), strlen(target_layout_name)); if (strncmp(layouts[ji].name, target_layout_name, len) == 0) { - selmon->pertag->ltidxs[selmon->pertag->curtag] = &layouts[ji]; - break; + selmon->pertag->ltidxs[target_tag] = &layouts[ji]; } } - clear_fullscreen_and_maximized_state(selmon); - arrange(selmon, false, false); - printstatus(); return 0; } for (jk = 0; jk < LENGTH(layouts); jk++) { if (strcmp(layouts[jk].name, - selmon->pertag->ltidxs[selmon->pertag->curtag]->name) == 0) { - selmon->pertag->ltidxs[selmon->pertag->curtag] = + selmon->pertag->ltidxs[target_tag]->name) == 0) { + selmon->pertag->ltidxs[target_tag] = jk == LENGTH(layouts) - 1 ? &layouts[0] : &layouts[jk + 1]; clear_fullscreen_and_maximized_state(selmon); arrange(selmon, false, false); From e30ce071f5450fcd2d51ce6dcc59d14cc8d1019d Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 25 May 2026 11:48:40 +0800 Subject: [PATCH 242/328] update docs --- docs/ipc.md | 185 +++++++++++++++------------------------------------- 1 file changed, 53 insertions(+), 132 deletions(-) diff --git a/docs/ipc.md b/docs/ipc.md index e3ec4a32..18618da8 100644 --- a/docs/ipc.md +++ b/docs/ipc.md @@ -3,152 +3,73 @@ title: IPC description: Control mangowm programmatically using mmsg. --- -## Introduction +# mmsg(1) - User Manual -mangowm includes a powerful IPC (Inter-Process Communication) tool called `mmsg`. This allows you to query the window manager's state, watch for events, and execute commands from external scripts. +`mmsg` is the command-line interface for the Mango compositor's Inter-Process Communication (IPC) system. It allows users and scripts to query the state of the compositor or subscribe to real-time events. -## Basic Usage +## SYNOPSIS +`mmsg <command> [arguments...]` -The general syntax for `mmsg` is: +## DESCRIPTION +`mmsg` acts as a client that connects to the Mango compositor via a Unix domain socket defined by the `MANGO_INSTANCE_SIGNATURE` environment variable. It supports two primary modes of operation: +1. **One-shot Request (`get`)**: Sends a query to the compositor, receives a single JSON response, and terminates. +2. **Persistent Stream (`watch`)**: Subscribes to a specific state, receiving continuous JSON updates whenever that state changes. -```bash -mmsg [-OTLq] -mmsg [-o <output>] -s [-t <tags>] [-l <layout>] [-c <tags>] [-d <cmd>,<arg1>,<arg2>,<arg3>,<arg4>,<arg5>] -mmsg [-o <output>] (-g | -w) [-OotlcvmfxekbA] -``` +## ENVIRONMENT VARIABLES +* **`MANGO_INSTANCE_SIGNATURE`**: Must be set to the path of the Unix socket created by the running Mango instance. This is typically handled automatically when running `mmsg` from within a terminal spawned by the compositor. -### Options +## COMMANDS -| Flag | Description | +### GET (One-Shot Queries) +| Command | Description | | :--- | :--- | -| `-q` | Quit mangowm. | -| `-g` | **Get** values (tags, layout, focused client). | -| `-s` | **Set** values (switch tags, layouts). | -| `-w` | **Watch** mode (streams events). | -| `-O` | Get all output (monitor) information. | -| `-T` | Get number of tags. | -| `-L` | Get all available layouts. | -| `-o` | Select output (monitor). | -| `-t` | Get/set selected tags (set with `[+-^.]`). | -| `-l` | Get/set current layout. | -| `-c` | Get title and appid of focused client. | -| `-v` | Get visibility of statusbar. | -| `-m` | Get fullscreen status. | -| `-f` | Get floating status. | -| `-d` | **Dispatch** an internal command. | -| `-x` | Get focused client geometry. | -| `-e` | Get the name of the last focused layer. | -| `-k` | Get current keyboard layout. | -| `-b` | Get current keybind mode. | -| `-A` | Get scale factor of monitor. | - -## Examples - -### Tag Management - -You can perform arithmetic on tags using the `-t` flag with `-s` (set). +| `get version` | Returns the current version of the compositor. | +| `get keymode` | Returns the current active keyboard mode (e.g., normal, insert). | +| `get keyboardlayout` | Returns the active XKB layout (abbreviated). | +| `get monitor <name>` | Returns full JSON details for a specific monitor. | +| `get client <id>` | Returns full JSON details for a client with the given ID. | +| `get tag <mon> <idx>` | Queries status of a specific tag on a monitor. | +| `get all-clients` | Returns a JSON array of all active clients. | +| `get all-monitors` | Returns a JSON array of all connected monitors. | +| `get all-tags` | Returns a JSON object containing the status of all tags. | +| `get last_open_surface <mon>` | Returns the last focused surface name for a monitor. | +*Example:* ```bash -# Switch to Tag 1 -mmsg -t 1 - -# Add Tag 2 to current view (Multiview) -mmsg -s -t 2+ - -# Remove Tag 2 from current view -mmsg -s -t 2- - -# Toggle Tag 2 -mmsg -s -t 2^ +mmsg get monitor eDP-1 +mmsg get all-clients +mmsg get all-monitors ``` -### Layouts +### WATCH (Event Subscription) +Subscribes the client to real-time updates. When the state changes, the server pushes a new JSON object to the output stream. -Switch layouts programmatically. Layout codes: `S` (Scroller), `T` (Tile), `G` (Grid), `M` (Monocle), `K` (Deck), `CT` (Center Tile), `RT` (Right Tile), `VS` (Vertical Scroller), `VT` (Vertical Tile), `VG` (Vertical Grid), `VK` (Vertical Deck), `DW` (Dwindle), `F` (Fair), `VF` (Vertical Fair). +* `watch monitor <name>` +* `watch client <id>` +* `watch tags <mon_name>` +* `watch all-monitors` +* `watch all-tags` +* `watch all-clients` +* `watch keymode` +* `watch keyboardlayout` +* `watch last_open_surface <mon_name>` +*Example:* ```bash -# Switch to Scroller -mmsg -l "S" - -# Switch to Tile -mmsg -l "T" +# watch all monitors +mmsg watch all-monitors +# watch all tags +mmsg watch all-tags ``` -### Dispatching Commands +### DISPATCH +Allows sending commands to the compositor to alter its state. +* `dispatch <func_name>,[args...] [client,<id>]` -Any command available in `config.conf` keybindings can be run via IPC. - -```bash -# Close the focused window -mmsg -d killclient - -# Resize window by +10 width -mmsg -d resizewin,+10,0 - -# Toggle fullscreen -mmsg -d togglefullscreen - -# Disable a monitor power -mmsg -d disable_monitor,eDP-1 -``` - -### Monitoring & Status - -Use `-g` or `-w` to build custom status bars or automation scripts. - -```bash -# Watch for all message changes -mmsg -w - -# Get all messages without watch -mmsg -g - -# Watch focused client appid and title -mmsg -w -c - -# Get all available outputs -mmsg -O - -# Get all tags message -mmsg -g -t - -# Get current focused client message -mmsg -g -c - -# Get current keyboard layout -mmsg -g -k - -# Get current keybind mode -mmsg -g -b - -# Get scale factor of current monitor -mmsg -g -A -``` - -#### Tag Message Format - -- State: 0 → none, 1 → active, 2 → urgent - -Example output: - -| Monitor | Tag Number | Tag State | Clients in Tag | Focused Client | -|---------|------------|-----------|----------------|----------------| -| eDP-1 | tag 2 | 0 | 1 | 0 | - -| Monitor | occupied tags mask | active tags mask | urgent tags mask | -|---------|--------------------|------------------|------------------| -| eDP-1 | 14 | 6 | 0 | - -## Virtual Monitors - -You can create headless outputs for screen mirroring or remote desktop access (e.g., Sunshine/Moonlight). - -```bash -# Create a virtual output -mmsg -d create_virtual_output - -# Configure it (set resolution) -wlr-randr --output HEADLESS-1 --pos 1920,0 --mode 1920x1080@60Hz - -# Destroy all virtual outputs -mmsg -d destroy_all_virtual_output \ No newline at end of file +*Example:* +```bash +# operate specific client by id +mmsg dispatch exchange_client,left client,375 +# operate current client +mmsg dispatch exchange_client,left +```` From 063cef31973573001aad8ace230a2acec04e52b0 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 25 May 2026 13:02:57 +0800 Subject: [PATCH 243/328] update cjson dep in doc --- README.md | 6 ++++++ docs/installation.md | 1 + mangowm.scm | 2 ++ nix/default.nix | 2 ++ 4 files changed, 11 insertions(+) diff --git a/README.md b/README.md index 531383ee..d96ab5a7 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,12 @@ Thanks to everyone who has sponsored this project: <table> <tr> <!-- add new sponsors here: copy the <td>...</td> block below --> + <td align="center"> + <a href="https://github.com/dl09r"> + <img src="https://unavatar.io/github/dl09r" width="48" style="border-radius:50%"/><br/> + <sub>dl09r</sub> + </a> + </td> <td align="center"> <a href="https://github.com/tonybanters"> <img src="https://unavatar.io/github/tonybanters" width="48" style="border-radius:50%"/><br/> diff --git a/docs/installation.md b/docs/installation.md index ba51a109..48c667c5 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -273,6 +273,7 @@ If your distribution isn't listed above, or you want the latest unreleased chang > - `hwdata` > - `seatd` > - `pcre2` +> - `cjson` > - `pixman` > - `xorg-xwayland` > - `libxcb` diff --git a/mangowm.scm b/mangowm.scm index 83bcf168..c7cd32e9 100644 --- a/mangowm.scm +++ b/mangowm.scm @@ -10,6 +10,7 @@ #:use-module (gnu packages pciutils) #:use-module (gnu packages admin) #:use-module (gnu packages pcre) + #:use-module (gnu packages cjson) #:use-module (gnu packages xorg) #:use-module (gnu packages build-tools) #:use-module (gnu packages ninja) @@ -53,6 +54,7 @@ hwdata seatd pcre2 + cjson libxcb pixman xcb-util-wm diff --git a/nix/default.nix b/nix/default.nix index cb6497b9..d7bcab16 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -5,6 +5,7 @@ libxcb, libxkbcommon, pcre2, + cjson, pixman, pkg-config, stdenv, @@ -48,6 +49,7 @@ stdenv.mkDerivation { libxcb libxkbcommon pcre2 + cjson pixman wayland wayland-protocols From 1856bff3480cbb86a5136fc000a81bb5287a8d8b Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 25 May 2026 19:13:49 +0800 Subject: [PATCH 244/328] fix: error init arg to null in ipc dispatch --- src/ipc/ipc.h | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/ipc/ipc.h b/src/ipc/ipc.h index 21ead90d..d6fbea6f 100644 --- a/src/ipc/ipc.h +++ b/src/ipc/ipc.h @@ -376,13 +376,11 @@ static void handle_command(int client_fd, const char *cmd_raw) { } Arg arg = {0}; - int32_t (*func)(const Arg *) = - parse_func_name(token_count > 0 ? tokens[0] : "", &arg, - token_count > 1 ? tokens[1] : NULL, - token_count > 2 ? tokens[2] : NULL, - token_count > 3 ? tokens[3] : NULL, - token_count > 4 ? tokens[4] : NULL, - token_count > 5 ? tokens[5] : NULL); + int32_t (*func)(const Arg *) = parse_func_name( + token_count > 0 ? tokens[0] : "", &arg, + token_count > 1 ? tokens[1] : "", token_count > 2 ? tokens[2] : "", + token_count > 3 ? tokens[3] : "", token_count > 4 ? tokens[4] : "", + token_count > 5 ? tokens[5] : ""); if (func && client_id > 0) arg.tc = client_by_id((uint32_t)client_id); From c3bd6dda643a9fd7f6010969a19ecf89e84d39fe Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 25 May 2026 23:07:56 +0800 Subject: [PATCH 245/328] fix: fix switch_layout not apply instantly --- src/dispatch/bind_define.h | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 4e952408..61a1a2a5 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1028,11 +1028,10 @@ int32_t switch_keyboard_layout(const Arg *arg) { } int32_t switch_layout(const Arg *arg) { + int32_t jk, ji; char *target_layout_name = NULL; uint32_t len; - uint32_t target_tag = selmon->pertag->curtag ? selmon->pertag->curtag - : selmon->pertag->prevtag; if (!selmon) return 0; @@ -1040,11 +1039,13 @@ int32_t switch_layout(const Arg *arg) { if (config.circle_layout_count != 0) { for (jk = 0; jk < config.circle_layout_count; jk++) { - len = MAX(strlen(config.circle_layout[jk]), - strlen(selmon->pertag->ltidxs[target_tag]->name)); + len = MAX( + strlen(config.circle_layout[jk]), + strlen(selmon->pertag->ltidxs[selmon->pertag->curtag]->name)); if (strncmp(config.circle_layout[jk], - selmon->pertag->ltidxs[target_tag]->name, len) == 0) { + selmon->pertag->ltidxs[selmon->pertag->curtag]->name, + len) == 0) { target_layout_name = jk == config.circle_layout_count - 1 ? config.circle_layout[0] : config.circle_layout[jk + 1]; @@ -1059,16 +1060,21 @@ int32_t switch_layout(const Arg *arg) { for (ji = 0; ji < LENGTH(layouts); ji++) { len = MAX(strlen(layouts[ji].name), strlen(target_layout_name)); if (strncmp(layouts[ji].name, target_layout_name, len) == 0) { - selmon->pertag->ltidxs[target_tag] = &layouts[ji]; + selmon->pertag->ltidxs[selmon->pertag->curtag] = &layouts[ji]; + + break; } } + clear_fullscreen_and_maximized_state(selmon); + arrange(selmon, false, false); + printstatus(); return 0; } for (jk = 0; jk < LENGTH(layouts); jk++) { if (strcmp(layouts[jk].name, - selmon->pertag->ltidxs[target_tag]->name) == 0) { - selmon->pertag->ltidxs[target_tag] = + 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, false); From c67e4d5038e5861749c63a68716351153b36174d Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 25 May 2026 23:29:55 +0800 Subject: [PATCH 246/328] fix: fix last_open_surface command not match mmsg --- src/ipc/ipc.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ipc/ipc.h b/src/ipc/ipc.h index d6fbea6f..3794f7fa 100644 --- a/src/ipc/ipc.h +++ b/src/ipc/ipc.h @@ -252,8 +252,8 @@ static void handle_command(int client_fd, const char *cmd_raw) { } else if (strcmp(cmd, "get keyboardlayout") == 0) { resp = cJSON_CreateObject(); cJSON_AddStringToObject(resp, "layout", ipc_get_layout_str()); - } else if (strncmp(cmd, "get last_open_surface ", 25) == 0) { - const char *name = cmd + 25; + } else if (strncmp(cmd, "get last_open_surface ", 22) == 0) { + const char *name = cmd + 22; Monitor *m = monitor_by_name(name); if (!m) { send_static_json(client_fd, "{\"error\":\"monitor not found\"}\n"); From e502793ea193839ee765fc605b20dbc39616e2a3 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 26 May 2026 09:30:32 +0800 Subject: [PATCH 247/328] fix: fix focused of client in mmsg --- src/ipc/ipc.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ipc/ipc.h b/src/ipc/ipc.h index 3794f7fa..e3f0120f 100644 --- a/src/ipc/ipc.h +++ b/src/ipc/ipc.h @@ -159,7 +159,7 @@ static cJSON *build_client_json(Client *c) { cJSON_AddStringToObject(obj, "appid", client_get_appid(c)); cJSON_AddStringToObject(obj, "monitor", c->mon->wlr_output->name); cJSON_AddItemToObject(obj, "tags", tags_mask_to_array(c->tags)); - cJSON_AddBoolToObject(obj, "is_focused", c == focustop(c->mon)); + cJSON_AddBoolToObject(obj, "is_focused", c->isfocusing); cJSON_AddBoolToObject(obj, "is_fullscreen", c->isfullscreen); cJSON_AddBoolToObject(obj, "is_floating", c->isfloating); cJSON_AddBoolToObject(obj, "is_maximized", c->ismaximizescreen); From 900b49526ec949f12235203c5d45b761355e7076 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 26 May 2026 10:24:16 +0800 Subject: [PATCH 248/328] feat: add focusing-client watch to mmsg --- docs/ipc.md | 2 ++ src/ipc/ipc.h | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/mango.c | 1 + 3 files changed, 56 insertions(+) diff --git a/docs/ipc.md b/docs/ipc.md index 18618da8..b4372c39 100644 --- a/docs/ipc.md +++ b/docs/ipc.md @@ -27,6 +27,7 @@ description: Control mangowm programmatically using mmsg. | `get keymode` | Returns the current active keyboard mode (e.g., normal, insert). | | `get keyboardlayout` | Returns the active XKB layout (abbreviated). | | `get monitor <name>` | Returns full JSON details for a specific monitor. | +| `get focusing-client` | Returns full JSON details for the client currently in focus. | | `get client <id>` | Returns full JSON details for a client with the given ID. | | `get tag <mon> <idx>` | Queries status of a specific tag on a monitor. | | `get all-clients` | Returns a JSON array of all active clients. | @@ -45,6 +46,7 @@ mmsg get all-monitors Subscribes the client to real-time updates. When the state changes, the server pushes a new JSON object to the output stream. * `watch monitor <name>` +* `watch focusing-client` * `watch client <id>` * `watch tags <mon_name>` * `watch all-monitors` diff --git a/src/ipc/ipc.h b/src/ipc/ipc.h index e3f0120f..7d3c0660 100644 --- a/src/ipc/ipc.h +++ b/src/ipc/ipc.h @@ -20,6 +20,7 @@ enum ipc_watch_type { IPC_WATCH_KEYMODE, IPC_WATCH_KB_LAYOUT, IPC_WATCH_LAST_OPEN_SURFACE, + IPC_WATCH_FOCUSING_CLIENT }; struct ipc_watch_client { @@ -270,6 +271,13 @@ static void handle_command(int client_fd, const char *cmd_raw) { return; } resp = build_monitor_json(m); + } else if (strcmp(cmd, "get focusing-client") == 0) { + if (selmon && selmon->sel) { + resp = build_client_json(selmon->sel); + } else { + send_static_json(client_fd, "{\"error\":\"no focused client\"}\n"); + return; + } } else if (strncmp(cmd, "get client ", 11) == 0) { Client *c = client_by_id((uint32_t)atoi(cmd + 11)); if (!c) { @@ -478,6 +486,8 @@ static bool handle_watch_command(int fd, const char *cmd, if (strncmp(cmd, "watch monitor ", 14) == 0) { type = IPC_WATCH_MONITOR; arg = cmd + 14; + } else if (strcmp(cmd, "watch focusing-client") == 0) { + type = IPC_WATCH_FOCUSING_CLIENT; } else if (strncmp(cmd, "watch client ", 13) == 0) { type = IPC_WATCH_CLIENT; client_id = (uint32_t)atoi(cmd + 13); @@ -541,6 +551,17 @@ static bool handle_watch_command(int fd, const char *cmd, } break; } + case IPC_WATCH_FOCUSING_CLIENT: { + if (selmon && selmon->sel) { + json = build_client_json(selmon->sel); + } else { + json = cJSON_CreateObject(); + cJSON_AddNullToObject(json, "id"); + cJSON_AddNullToObject(json, "title"); + cJSON_AddNullToObject(json, "appid"); + } + break; + } case IPC_WATCH_CLIENT: { Client *c = client_by_id(client_id); if (c) @@ -729,6 +750,38 @@ void ipc_notify_last_surface_ws_name(Monitor *m) { free(json_str); } +void ipc_notify_focusing_client(void) { + char *json_str = NULL; + size_t len = 0; + struct ipc_watch_client *wc, *tmp; + wl_list_for_each_safe(wc, tmp, &watch_clients, link) { + if (wc->type == IPC_WATCH_FOCUSING_CLIENT) { + if (!json_str) { + cJSON *json = NULL; + if (selmon && selmon->sel) { + json = build_client_json(selmon->sel); + } else { + json = cJSON_CreateObject(); + cJSON_AddNullToObject(json, "id"); + cJSON_AddNullToObject(json, "title"); + cJSON_AddNullToObject(json, "appid"); + } + char *raw = cJSON_PrintUnformatted(json); + cJSON_Delete(json); + if (!raw) + return; + len = strlen(raw); + json_str = malloc(len + 2); + snprintf(json_str, len + 2, "%s\n", raw); + free(raw); + } + if (send(wc->fd, json_str, len + 1, MSG_NOSIGNAL) < 0) + ipc_remove_watch_client(wc); + } + } + free(json_str); +} + void ipc_notify_client(Client *c) { char *json_str = NULL; size_t len = 0; diff --git a/src/mango.c b/src/mango.c index ffc0d35f..a6d01f2d 100644 --- a/src/mango.c +++ b/src/mango.c @@ -5710,6 +5710,7 @@ void handle_print_status(struct wl_listener *listener, void *data) { ipc_notify_keymode(); ipc_notify_kb_layout(); + ipc_notify_focusing_client(); ipc_notify_all_tags(); ipc_notify_all_clients(); ipc_notify_all_monitors(); From 1ce45fac94df72376b191b04fad021141bc12d0d Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 26 May 2026 11:51:15 +0800 Subject: [PATCH 249/328] feat: support parse button code in config --- src/config/parse_config.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 4f3dd708..74b36ba4 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -878,6 +878,24 @@ uint32_t parse_button(const char *str) { } lowerStr[i] = '\0'; // 确保字符串正确终止 + // 解析 "code:数字" 格式 + if (strncmp(lowerStr, "code:", 5) == 0) { + const char *numStart = lowerStr + 5; // 跳过 "code:" + char *endptr; + unsigned long val = strtoul(numStart, &endptr, 10); + + // 检查是否成功转换且无多余字符,且值未溢出(在 uint32_t 范围内) + if (endptr != numStart && *endptr == '\0' && val <= UINT32_MAX) { + return (uint32_t)val; + } else { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid code format: " + "\033[1m\033[31m%s\n", + str); + return UINT32_MAX; + } + } + // 根据转换后的小写字符串返回对应的按钮编号 if (strcmp(lowerStr, "btn_left") == 0) { return BTN_LEFT; From e7e6115aa4ff7cd9be96c01f36321a6d0a25117a Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 26 May 2026 13:23:37 +0800 Subject: [PATCH 250/328] update docs --- docs/bindings/mouse-gestures.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/bindings/mouse-gestures.md b/docs/bindings/mouse-gestures.md index c4a36889..e96b6dbd 100644 --- a/docs/bindings/mouse-gestures.md +++ b/docs/bindings/mouse-gestures.md @@ -13,11 +13,14 @@ Assign actions to mouse button presses with optional modifier keys. mousebind=MODIFIERS,BUTTON,COMMAND,PARAMETERS ``` -- **Modifiers**: `SUPER`, `CTRL`, `ALT`, `SHIFT`, `NONE`. Combine with `+` (e.g., `SUPER+CTRL`) -- **Buttons**: `btn_left`, `btn_right`, `btn_middle`, `btn_side`, `btn_extra`, `btn_forward`, `btn_back`, `btn_task` +- **Modifiers**: `SUPER`, `CTRL`, `ALT`, `SHIFT`, `NONE`. Combine with `+` (e.g., `SUPER+CTRL`). +- **Buttons**: Can be specified in one of the following ways: + - **Standard Names**: `btn_left`, `btn_right`, `btn_middle`, `btn_side`, `btn_extra`, `btn_forward`, `btn_back`, `btn_task` + - **Hardware Codes**: `code:NUMBER` (e.g., `code:272`, `code:273`, useful for binding non-standard or extra mouse buttons) > **Warning:** When modifiers are set to `NONE`, only `btn_middle` works in normal mode. `btn_left` and `btn_right` only work in overview mode. + ### Examples ```ini @@ -26,10 +29,7 @@ mousebind=SUPER,btn_left,moveresize,curmove mousebind=SUPER,btn_right,moveresize,curresize mousebind=SUPER+CTRL,btn_right,killclient -# Overview mode (requires NONE modifier) -mousebind=NONE,btn_left,toggleoverview,-1 -mousebind=NONE,btn_right,killclient,0 -mousebind=NONE,btn_middle,togglemaximizescreen,0 +mousebind=NONE,code:273,togglemaximizescreen,0 ``` --- From 3f11ddda762baa72adaff102c737907a31155318 Mon Sep 17 00:00:00 2001 From: xtheeq <atheeq.rhxn@gmail.com> Date: Tue, 26 May 2026 11:10:16 +0530 Subject: [PATCH 251/328] docs: add versioned docs --- docs/{ => (git)}/bindings/index.mdx | 0 docs/{ => (git)}/bindings/keys.md | 0 docs/{ => (git)}/bindings/meta.json | 0 docs/{ => (git)}/bindings/mouse-gestures.md | 0 docs/{ => (git)}/configuration/basics.md | 0 docs/{ => (git)}/configuration/index.mdx | 0 docs/{ => (git)}/configuration/input.md | 0 docs/{ => (git)}/configuration/meta.json | 0 .../configuration/miscellaneous.md | 0 docs/{ => (git)}/configuration/monitors.md | 0 docs/{ => (git)}/configuration/xdg-portals.md | 0 docs/{ => (git)}/faq.md | 0 docs/{ => (git)}/index.md | 0 docs/{ => (git)}/installation.md | 0 docs/{ => (git)}/ipc.md | 0 docs/(git)/meta.json | 22 + docs/{ => (git)}/nix-options.md | 0 docs/{ => (git)}/quick-start.md | 0 docs/{ => (git)}/screenshot.md | 0 docs/{ => (git)}/visuals/animations.md | 0 docs/{ => (git)}/visuals/effects.md | 0 docs/{ => (git)}/visuals/index.mdx | 0 docs/{ => (git)}/visuals/meta.json | 0 docs/{ => (git)}/visuals/status-bar.md | 0 docs/{ => (git)}/visuals/theming.md | 0 docs/{ => (git)}/window-management/index.mdx | 0 docs/{ => (git)}/window-management/layouts.md | 0 docs/{ => (git)}/window-management/meta.json | 0 .../{ => (git)}/window-management/overview.md | 0 docs/{ => (git)}/window-management/rules.md | 0 .../window-management/scratchpad.md | 0 docs/meta.json | 19 +- docs/v0.13.0/bindings/index.mdx | 15 + docs/v0.13.0/bindings/keys.md | 216 ++++++++ docs/v0.13.0/bindings/meta.json | 4 + docs/v0.13.0/bindings/mouse-gestures.md | 116 ++++ docs/v0.13.0/configuration/basics.md | 87 +++ docs/v0.13.0/configuration/index.mdx | 21 + docs/v0.13.0/configuration/input.md | 161 ++++++ docs/v0.13.0/configuration/meta.json | 4 + docs/v0.13.0/configuration/miscellaneous.md | 50 ++ docs/v0.13.0/configuration/monitors.md | 276 ++++++++++ docs/v0.13.0/configuration/xdg-portals.md | 76 +++ docs/v0.13.0/faq.md | 100 ++++ docs/v0.13.0/index.md | 42 ++ docs/v0.13.0/installation.md | 308 +++++++++++ docs/v0.13.0/ipc.md | 154 ++++++ docs/v0.13.0/meta.json | 22 + docs/v0.13.0/nix-options.md | 519 ++++++++++++++++++ docs/v0.13.0/quick-start.md | 88 +++ docs/v0.13.0/screenshot.md | 213 +++++++ docs/v0.13.0/visuals/animations.md | 108 ++++ docs/v0.13.0/visuals/effects.md | 82 +++ docs/v0.13.0/visuals/index.mdx | 19 + docs/v0.13.0/visuals/meta.json | 4 + docs/v0.13.0/visuals/status-bar.md | 141 +++++ docs/v0.13.0/visuals/theming.md | 62 +++ docs/v0.13.0/window-management/index.mdx | 19 + docs/v0.13.0/window-management/layouts.md | 133 +++++ docs/v0.13.0/window-management/meta.json | 4 + docs/v0.13.0/window-management/overview.md | 29 + docs/v0.13.0/window-management/rules.md | 250 +++++++++ docs/v0.13.0/window-management/scratchpad.md | 73 +++ docs/v0.13.1/bindings/index.mdx | 15 + docs/v0.13.1/bindings/keys.md | 216 ++++++++ docs/v0.13.1/bindings/meta.json | 4 + docs/v0.13.1/bindings/mouse-gestures.md | 116 ++++ docs/v0.13.1/configuration/basics.md | 87 +++ docs/v0.13.1/configuration/index.mdx | 21 + docs/v0.13.1/configuration/input.md | 161 ++++++ docs/v0.13.1/configuration/meta.json | 4 + docs/v0.13.1/configuration/miscellaneous.md | 50 ++ docs/v0.13.1/configuration/monitors.md | 276 ++++++++++ docs/v0.13.1/configuration/xdg-portals.md | 76 +++ docs/v0.13.1/faq.md | 100 ++++ docs/v0.13.1/index.md | 42 ++ docs/v0.13.1/installation.md | 308 +++++++++++ docs/v0.13.1/ipc.md | 154 ++++++ docs/v0.13.1/meta.json | 22 + docs/v0.13.1/nix-options.md | 519 ++++++++++++++++++ docs/v0.13.1/quick-start.md | 88 +++ docs/v0.13.1/screenshot.md | 213 +++++++ docs/v0.13.1/visuals/animations.md | 108 ++++ docs/v0.13.1/visuals/effects.md | 82 +++ docs/v0.13.1/visuals/index.mdx | 19 + docs/v0.13.1/visuals/meta.json | 4 + docs/v0.13.1/visuals/status-bar.md | 141 +++++ docs/v0.13.1/visuals/theming.md | 62 +++ docs/v0.13.1/window-management/index.mdx | 19 + docs/v0.13.1/window-management/layouts.md | 135 +++++ docs/v0.13.1/window-management/meta.json | 4 + docs/v0.13.1/window-management/overview.md | 29 + docs/v0.13.1/window-management/rules.md | 250 +++++++++ docs/v0.13.1/window-management/scratchpad.md | 73 +++ 94 files changed, 6817 insertions(+), 18 deletions(-) rename docs/{ => (git)}/bindings/index.mdx (100%) rename docs/{ => (git)}/bindings/keys.md (100%) rename docs/{ => (git)}/bindings/meta.json (100%) rename docs/{ => (git)}/bindings/mouse-gestures.md (100%) rename docs/{ => (git)}/configuration/basics.md (100%) rename docs/{ => (git)}/configuration/index.mdx (100%) rename docs/{ => (git)}/configuration/input.md (100%) rename docs/{ => (git)}/configuration/meta.json (100%) rename docs/{ => (git)}/configuration/miscellaneous.md (100%) rename docs/{ => (git)}/configuration/monitors.md (100%) rename docs/{ => (git)}/configuration/xdg-portals.md (100%) rename docs/{ => (git)}/faq.md (100%) rename docs/{ => (git)}/index.md (100%) rename docs/{ => (git)}/installation.md (100%) rename docs/{ => (git)}/ipc.md (100%) create mode 100644 docs/(git)/meta.json rename docs/{ => (git)}/nix-options.md (100%) rename docs/{ => (git)}/quick-start.md (100%) rename docs/{ => (git)}/screenshot.md (100%) rename docs/{ => (git)}/visuals/animations.md (100%) rename docs/{ => (git)}/visuals/effects.md (100%) rename docs/{ => (git)}/visuals/index.mdx (100%) rename docs/{ => (git)}/visuals/meta.json (100%) rename docs/{ => (git)}/visuals/status-bar.md (100%) rename docs/{ => (git)}/visuals/theming.md (100%) rename docs/{ => (git)}/window-management/index.mdx (100%) rename docs/{ => (git)}/window-management/layouts.md (100%) rename docs/{ => (git)}/window-management/meta.json (100%) rename docs/{ => (git)}/window-management/overview.md (100%) rename docs/{ => (git)}/window-management/rules.md (100%) rename docs/{ => (git)}/window-management/scratchpad.md (100%) create mode 100644 docs/v0.13.0/bindings/index.mdx create mode 100644 docs/v0.13.0/bindings/keys.md create mode 100644 docs/v0.13.0/bindings/meta.json create mode 100644 docs/v0.13.0/bindings/mouse-gestures.md create mode 100644 docs/v0.13.0/configuration/basics.md create mode 100644 docs/v0.13.0/configuration/index.mdx create mode 100644 docs/v0.13.0/configuration/input.md create mode 100644 docs/v0.13.0/configuration/meta.json create mode 100644 docs/v0.13.0/configuration/miscellaneous.md create mode 100644 docs/v0.13.0/configuration/monitors.md create mode 100644 docs/v0.13.0/configuration/xdg-portals.md create mode 100644 docs/v0.13.0/faq.md create mode 100644 docs/v0.13.0/index.md create mode 100644 docs/v0.13.0/installation.md create mode 100644 docs/v0.13.0/ipc.md create mode 100644 docs/v0.13.0/meta.json create mode 100644 docs/v0.13.0/nix-options.md create mode 100644 docs/v0.13.0/quick-start.md create mode 100644 docs/v0.13.0/screenshot.md create mode 100644 docs/v0.13.0/visuals/animations.md create mode 100644 docs/v0.13.0/visuals/effects.md create mode 100644 docs/v0.13.0/visuals/index.mdx create mode 100644 docs/v0.13.0/visuals/meta.json create mode 100644 docs/v0.13.0/visuals/status-bar.md create mode 100644 docs/v0.13.0/visuals/theming.md create mode 100644 docs/v0.13.0/window-management/index.mdx create mode 100644 docs/v0.13.0/window-management/layouts.md create mode 100644 docs/v0.13.0/window-management/meta.json create mode 100644 docs/v0.13.0/window-management/overview.md create mode 100644 docs/v0.13.0/window-management/rules.md create mode 100644 docs/v0.13.0/window-management/scratchpad.md create mode 100644 docs/v0.13.1/bindings/index.mdx create mode 100644 docs/v0.13.1/bindings/keys.md create mode 100644 docs/v0.13.1/bindings/meta.json create mode 100644 docs/v0.13.1/bindings/mouse-gestures.md create mode 100644 docs/v0.13.1/configuration/basics.md create mode 100644 docs/v0.13.1/configuration/index.mdx create mode 100644 docs/v0.13.1/configuration/input.md create mode 100644 docs/v0.13.1/configuration/meta.json create mode 100644 docs/v0.13.1/configuration/miscellaneous.md create mode 100644 docs/v0.13.1/configuration/monitors.md create mode 100644 docs/v0.13.1/configuration/xdg-portals.md create mode 100644 docs/v0.13.1/faq.md create mode 100644 docs/v0.13.1/index.md create mode 100644 docs/v0.13.1/installation.md create mode 100644 docs/v0.13.1/ipc.md create mode 100644 docs/v0.13.1/meta.json create mode 100644 docs/v0.13.1/nix-options.md create mode 100644 docs/v0.13.1/quick-start.md create mode 100644 docs/v0.13.1/screenshot.md create mode 100644 docs/v0.13.1/visuals/animations.md create mode 100644 docs/v0.13.1/visuals/effects.md create mode 100644 docs/v0.13.1/visuals/index.mdx create mode 100644 docs/v0.13.1/visuals/meta.json create mode 100644 docs/v0.13.1/visuals/status-bar.md create mode 100644 docs/v0.13.1/visuals/theming.md create mode 100644 docs/v0.13.1/window-management/index.mdx create mode 100644 docs/v0.13.1/window-management/layouts.md create mode 100644 docs/v0.13.1/window-management/meta.json create mode 100644 docs/v0.13.1/window-management/overview.md create mode 100644 docs/v0.13.1/window-management/rules.md create mode 100644 docs/v0.13.1/window-management/scratchpad.md diff --git a/docs/bindings/index.mdx b/docs/(git)/bindings/index.mdx similarity index 100% rename from docs/bindings/index.mdx rename to docs/(git)/bindings/index.mdx diff --git a/docs/bindings/keys.md b/docs/(git)/bindings/keys.md similarity index 100% rename from docs/bindings/keys.md rename to docs/(git)/bindings/keys.md diff --git a/docs/bindings/meta.json b/docs/(git)/bindings/meta.json similarity index 100% rename from docs/bindings/meta.json rename to docs/(git)/bindings/meta.json diff --git a/docs/bindings/mouse-gestures.md b/docs/(git)/bindings/mouse-gestures.md similarity index 100% rename from docs/bindings/mouse-gestures.md rename to docs/(git)/bindings/mouse-gestures.md diff --git a/docs/configuration/basics.md b/docs/(git)/configuration/basics.md similarity index 100% rename from docs/configuration/basics.md rename to docs/(git)/configuration/basics.md diff --git a/docs/configuration/index.mdx b/docs/(git)/configuration/index.mdx similarity index 100% rename from docs/configuration/index.mdx rename to docs/(git)/configuration/index.mdx diff --git a/docs/configuration/input.md b/docs/(git)/configuration/input.md similarity index 100% rename from docs/configuration/input.md rename to docs/(git)/configuration/input.md diff --git a/docs/configuration/meta.json b/docs/(git)/configuration/meta.json similarity index 100% rename from docs/configuration/meta.json rename to docs/(git)/configuration/meta.json diff --git a/docs/configuration/miscellaneous.md b/docs/(git)/configuration/miscellaneous.md similarity index 100% rename from docs/configuration/miscellaneous.md rename to docs/(git)/configuration/miscellaneous.md diff --git a/docs/configuration/monitors.md b/docs/(git)/configuration/monitors.md similarity index 100% rename from docs/configuration/monitors.md rename to docs/(git)/configuration/monitors.md diff --git a/docs/configuration/xdg-portals.md b/docs/(git)/configuration/xdg-portals.md similarity index 100% rename from docs/configuration/xdg-portals.md rename to docs/(git)/configuration/xdg-portals.md diff --git a/docs/faq.md b/docs/(git)/faq.md similarity index 100% rename from docs/faq.md rename to docs/(git)/faq.md diff --git a/docs/index.md b/docs/(git)/index.md similarity index 100% rename from docs/index.md rename to docs/(git)/index.md diff --git a/docs/installation.md b/docs/(git)/installation.md similarity index 100% rename from docs/installation.md rename to docs/(git)/installation.md diff --git a/docs/ipc.md b/docs/(git)/ipc.md similarity index 100% rename from docs/ipc.md rename to docs/(git)/ipc.md diff --git a/docs/(git)/meta.json b/docs/(git)/meta.json new file mode 100644 index 00000000..40a214d5 --- /dev/null +++ b/docs/(git)/meta.json @@ -0,0 +1,22 @@ +{ + "title": "git (latest)", + "description": "Latest development", + "root": true, + "pages": [ + "---Getting Started---", + "index", + "installation", + "quick-start", + "---Configuration---", + "configuration", + "visuals", + "window-management", + "bindings", + "---Examples---", + "screenshot", + "---Reference---", + "nix-options", + "ipc", + "faq" + ] +} diff --git a/docs/nix-options.md b/docs/(git)/nix-options.md similarity index 100% rename from docs/nix-options.md rename to docs/(git)/nix-options.md diff --git a/docs/quick-start.md b/docs/(git)/quick-start.md similarity index 100% rename from docs/quick-start.md rename to docs/(git)/quick-start.md diff --git a/docs/screenshot.md b/docs/(git)/screenshot.md similarity index 100% rename from docs/screenshot.md rename to docs/(git)/screenshot.md diff --git a/docs/visuals/animations.md b/docs/(git)/visuals/animations.md similarity index 100% rename from docs/visuals/animations.md rename to docs/(git)/visuals/animations.md diff --git a/docs/visuals/effects.md b/docs/(git)/visuals/effects.md similarity index 100% rename from docs/visuals/effects.md rename to docs/(git)/visuals/effects.md diff --git a/docs/visuals/index.mdx b/docs/(git)/visuals/index.mdx similarity index 100% rename from docs/visuals/index.mdx rename to docs/(git)/visuals/index.mdx diff --git a/docs/visuals/meta.json b/docs/(git)/visuals/meta.json similarity index 100% rename from docs/visuals/meta.json rename to docs/(git)/visuals/meta.json diff --git a/docs/visuals/status-bar.md b/docs/(git)/visuals/status-bar.md similarity index 100% rename from docs/visuals/status-bar.md rename to docs/(git)/visuals/status-bar.md diff --git a/docs/visuals/theming.md b/docs/(git)/visuals/theming.md similarity index 100% rename from docs/visuals/theming.md rename to docs/(git)/visuals/theming.md diff --git a/docs/window-management/index.mdx b/docs/(git)/window-management/index.mdx similarity index 100% rename from docs/window-management/index.mdx rename to docs/(git)/window-management/index.mdx diff --git a/docs/window-management/layouts.md b/docs/(git)/window-management/layouts.md similarity index 100% rename from docs/window-management/layouts.md rename to docs/(git)/window-management/layouts.md diff --git a/docs/window-management/meta.json b/docs/(git)/window-management/meta.json similarity index 100% rename from docs/window-management/meta.json rename to docs/(git)/window-management/meta.json diff --git a/docs/window-management/overview.md b/docs/(git)/window-management/overview.md similarity index 100% rename from docs/window-management/overview.md rename to docs/(git)/window-management/overview.md diff --git a/docs/window-management/rules.md b/docs/(git)/window-management/rules.md similarity index 100% rename from docs/window-management/rules.md rename to docs/(git)/window-management/rules.md diff --git a/docs/window-management/scratchpad.md b/docs/(git)/window-management/scratchpad.md similarity index 100% rename from docs/window-management/scratchpad.md rename to docs/(git)/window-management/scratchpad.md diff --git a/docs/meta.json b/docs/meta.json index 89c34444..e6110ed2 100644 --- a/docs/meta.json +++ b/docs/meta.json @@ -1,20 +1,3 @@ { - "title": "mangowm", - "pages": [ - "---Getting Started---", - "index", - "installation", - "quick-start", - "---Configuration---", - "configuration", - "visuals", - "window-management", - "bindings", - "---Examples---", - "screenshot", - "---Reference---", - "nix-options", - "ipc", - "faq" - ] + "pages": ["(git)", "v0.13.1", "v0.13.0"] } diff --git a/docs/v0.13.0/bindings/index.mdx b/docs/v0.13.0/bindings/index.mdx new file mode 100644 index 00000000..4c3a5bda --- /dev/null +++ b/docs/v0.13.0/bindings/index.mdx @@ -0,0 +1,15 @@ +--- +title: Bindings & Input +description: Keybindings, mouse gestures, and input devices. +icon: Keyboard +--- + +Configure how you interact with mangowm using flexible keybindings and input options. + +<Cards> + +<Card href="/docs/bindings/keys" title="Key Bindings" description="Keyboard shortcuts and modes" /> + +<Card href="/docs/bindings/mouse-gestures" title="Mouse Gestures" description="Touchpad and mouse gestures" /> + +</Cards> diff --git a/docs/v0.13.0/bindings/keys.md b/docs/v0.13.0/bindings/keys.md new file mode 100644 index 00000000..002c9564 --- /dev/null +++ b/docs/v0.13.0/bindings/keys.md @@ -0,0 +1,216 @@ +--- +title: Key Bindings +description: Define keyboard shortcuts and modes. +--- + +## Syntax + +Key bindings follow this format: + +```ini +bind[flags]=MODIFIERS,KEY,COMMAND,PARAMETERS +``` + +- **Modifiers**: `SUPER`, `CTRL`, `ALT`, `SHIFT`, `NONE` (combine with `+`, e.g. `SUPER+CTRL+ALT`). +- **Key**: Key name (from `xev` or `wev`) or keycode (e.g., `code:24` for `q`). + +> **Info:** `bind` automatically converts keysym to keycode for comparison. This makes it compatible with all keyboard layouts, but the matching may not always be precise. If a key combination doesn't work on your keyboard layout, use a keycode instead (e.g., `code:24` instead of `q`). + +### Flags + +- `l`: Works even when screen is locked. +- `s`: Uses keysym instead of keycode to bind. +- `r`: Triggers on key release instead of press. +- `p`: Pass key event to client. + +**Examples:** + +```ini +bind=SUPER,Q,killclient +bindl=SUPER,L,spawn,swaylock + +# Using keycode instead of key name +bind=ALT,code:24,killclient + +# Combining keycodes for modifiers and keys +bind=code:64,code:24,killclient +bind=code:64+code:133,code:24,killclient + +# Bind with no modifier +bind=NONE,XF86MonBrightnessUp,spawn,brightnessctl set +5% + +# Bind a modifier key itself as the trigger key +bind=alt,shift_l,switch_keyboard_layout +``` + +## Key Modes (Submaps) + +You can divide key bindings into named modes. Rules: + +1. Set `keymode=<name>` before a group of `bind` lines — those binds only apply in that mode. +2. If no `keymode` is set before a bind, it belongs to the `default` mode. +3. The special `common` keymode applies its binds **across all modes**. + +Use `setkeymode` to switch modes, and `mmsg -b` to query the current mode. + +```ini +# Binds in 'common' apply in every mode +keymode=common +bind=SUPER,r,reload_config + +# Default mode bindings +keymode=default +bind=ALT,Return,spawn,foot +bind=SUPER,F,setkeymode,resize + +# 'resize' mode bindings +keymode=resize +bind=NONE,Left,resizewin,-10,0 +bind=NONE,Right,resizewin,+10,0 +bind=NONE,Escape,setkeymode,default +``` + +### Single Modifier Key Binding + +When binding a modifier key itself, use `NONE` for press and the modifier name for release: + +```ini +# Trigger on press of Super key +bind=none,Super_L,spawn,rofi -show run + +# Trigger on release of Super key +bindr=Super,Super_L,spawn,rofi -show run +``` + +## Dispatchers List + +### Window Management + +| Command | Param | Description | +| :--- | :--- | :--- | +| `killclient` | - | Close the focused window. | +| `togglefloating` | - | Toggle floating state. | +| `toggle_all_floating` | - | Toggle all visible clients floating state. | +| `togglefullscreen` | - | Toggle fullscreen. | +| `togglefakefullscreen` | - | Toggle "fake" fullscreen (remains constrained). | +| `togglemaximizescreen` | - | Maximize window (keep decoration/bar). | +| `toggleglobal` | - | Pin window to all tags. | +| `toggle_render_border` | - | Toggle border rendering. | +| `centerwin` | - | Center the floating window. | +| `minimized` | - | Minimize window to scratchpad. | +| `restore_minimized` | - | Restore window from scratchpad. | +| `toggle_scratchpad` | - | Toggle scratchpad. | +| `toggle_named_scratchpad` | `appid,title,cmd` | Toggle named scratchpad. Launches app if not running, otherwise shows/hides it. | + +### Focus & Movement + +| Command | Param | Description | +| :--- | :--- | :--- | +| `focusdir` | `left/right/up/down` | Focus window in direction. | +| `focusstack` | `next/prev` | Cycle focus within the stack. | +| `focuslast` | - | Focus the previously active window. | +| `exchange_client` | `left/right/up/down` | Swap window with neighbor in direction. | +| `exchange_stack_client` | `next/prev` | Exchange window position in stack. | +| `zoom` | - | Swap focused window with Master. | + +### Tags & Monitors + +| Command | Param | Description | +| :--- | :--- | :--- | +| `view` | `-1/0/1-9` or `mask [,synctag]` | View tag. `-1` = previous tagset, `0` = all tags, `1-9` = specific tag, mask e.g. `1\|3\|5`. Optional `synctag` (0/1) syncs the action to all monitors. | +| `viewtoleft` | `[synctag]` | View previous tag. Optional `synctag` (0/1) syncs to all monitors. | +| `viewtoright` | `[synctag]` | View next tag. Optional `synctag` (0/1) syncs to all monitors. | +| `viewtoleft_have_client` | `[synctag]` | View left tag and focus client if present. Optional `synctag` (0/1). | +| `viewtoright_have_client` | `[synctag]` | View right tag and focus client if present. Optional `synctag` (0/1). | +| `viewcrossmon` | `tag,monitor_spec` | View specified tag on specified monitor. | +| `tag` | `1-9 [,synctag]` | Move window to tag. Optional `synctag` (0/1) syncs to all monitors. | +| `tagsilent` | `1-9` | Move window to tag without focusing it. | +| `tagtoleft` | `[synctag]` | Move window to left tag. Optional `synctag` (0/1). | +| `tagtoright` | `[synctag]` | Move window to right tag. Optional `synctag` (0/1). | +| `tagcrossmon` | `tag,monitor_spec` | Move window to specified tag on specified monitor. | +| `toggletag` | `0-9` | Toggle tag on window (0 means all tags). | +| `toggleview` | `1-9` | Toggle tag view. | +| `comboview` | `1-9` | View multi tags pressed simultaneously. | +| `focusmon` | `left/right/up/down/monitor_spec` | Focus monitor by direction or [monitor spec](/docs/configuration/monitors#monitor-spec-format). | +| `tagmon` | `left/right/up/down/monitor_spec,[keeptag]` | Move window to monitor by direction or [monitor spec](/docs/configuration/monitors#monitor-spec-format). `keeptag` is 0 or 1. | + +### Layouts + +| Command | Param | Description | +| :--- | :--- | :--- | +| `setlayout` | `name` | Switch to layout (e.g., `scroller`, `tile`). | +| `switch_layout` | - | Cycle through available layouts. | +| `incnmaster` | `+1/-1` | Increase/Decrease number of master windows. | +| `setmfact` | `+0.05` | Increase/Decrease master area size. | +| `set_proportion` | `float` | Set scroller window proportion (0.0–1.0). | +| `switch_proportion_preset` | - | Cycle proportion presets of scroller window. | +| `scroller_stack` | `left/right/up/down` | Move window inside/outside scroller stack by direction. | +| `incgaps` | `+/-value` | Adjust gap size. | +| `togglegaps` | - | Toggle gaps. | +| `dwindle_toggle_split_direction` | - | Toggle split direction in dwindle layout. | + +### System + +| Command | Param | Description | +| :--- | :--- | :--- | +| `spawn` | `cmd` | Execute a command. | +| `spawn_shell` | `cmd` | Execute shell command (supports pipes `\|`). | +| `spawn_on_empty` | `cmd,tagnumber` | Open command on empty tag. | +| `reload_config` | - | Hot-reload configuration. | +| `quit` | - | Exit mangowm. | +| `toggleoverview` | - | Toggle overview mode. | +| `create_virtual_output` | - | Create a headless monitor (for VNC/Sunshine). | +| `destroy_all_virtual_output` | - | Destroy all virtual monitors. | +| `toggleoverlay` | - | Toggle overlay state for the focused window. | +| `toggle_trackpad_enable` | - | Toggle trackpad enable. | +| `setkeymode` | `mode` | Set keymode. | +| `switch_keyboard_layout` | `[index]` | Switch keyboard layout. Optional index (0, 1, 2...) to switch to specific layout. | +| `setoption` | `key,value` | Set config option temporarily. | +| `disable_monitor` | `monitor_spec` | Shutdown monitor. Accepts a [monitor spec](/docs/configuration/monitors#monitor-spec-format). | +| `enable_monitor` | `monitor_spec` | Power on monitor. Accepts a [monitor spec](/docs/configuration/monitors#monitor-spec-format). | +| `toggle_monitor` | `monitor_spec` | Toggle monitor power. Accepts a [monitor spec](/docs/configuration/monitors#monitor-spec-format). | + +### Media Controls + +> **Warning:** Some keyboards don't send standard media keys. Run `wev` and press your key to check the exact key name. + +#### Brightness + +Requires: `brightnessctl` + +```ini +bind=NONE,XF86MonBrightnessUp,spawn,brightnessctl s +2% +bind=SHIFT,XF86MonBrightnessUp,spawn,brightnessctl s 100% +bind=NONE,XF86MonBrightnessDown,spawn,brightnessctl s 2%- +bind=SHIFT,XF86MonBrightnessDown,spawn,brightnessctl s 1% +``` + +#### Volume + +Requires: `wpctl` (WirePlumber) + +```ini +bind=NONE,XF86AudioRaiseVolume,spawn,wpctl set-volume @DEFAULT_SINK@ 5%+ +bind=NONE,XF86AudioLowerVolume,spawn,wpctl set-volume @DEFAULT_SINK@ 5%- +bind=NONE,XF86AudioMute,spawn,wpctl set-mute @DEFAULT_SINK@ toggle +bind=SHIFT,XF86AudioMute,spawn,wpctl set-mute @DEFAULT_SOURCE@ toggle +``` + +#### 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 | +| :--- | :--- | :--- | +| `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. | diff --git a/docs/v0.13.0/bindings/meta.json b/docs/v0.13.0/bindings/meta.json new file mode 100644 index 00000000..f1b629b6 --- /dev/null +++ b/docs/v0.13.0/bindings/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Bindings & Input", + "pages": ["keys", "mouse-gestures"] +} diff --git a/docs/v0.13.0/bindings/mouse-gestures.md b/docs/v0.13.0/bindings/mouse-gestures.md new file mode 100644 index 00000000..c4a36889 --- /dev/null +++ b/docs/v0.13.0/bindings/mouse-gestures.md @@ -0,0 +1,116 @@ +--- +title: Mouse & Gestures +description: Configure mouse buttons, scrolling, gestures, and lid switches. +--- + +## Mouse Bindings + +Assign actions to mouse button presses with optional modifier keys. + +### Syntax + +```ini +mousebind=MODIFIERS,BUTTON,COMMAND,PARAMETERS +``` + +- **Modifiers**: `SUPER`, `CTRL`, `ALT`, `SHIFT`, `NONE`. Combine with `+` (e.g., `SUPER+CTRL`) +- **Buttons**: `btn_left`, `btn_right`, `btn_middle`, `btn_side`, `btn_extra`, `btn_forward`, `btn_back`, `btn_task` + +> **Warning:** When modifiers are set to `NONE`, only `btn_middle` works in normal mode. `btn_left` and `btn_right` only work in overview mode. + +### Examples + +```ini +# Window manipulation +mousebind=SUPER,btn_left,moveresize,curmove +mousebind=SUPER,btn_right,moveresize,curresize +mousebind=SUPER+CTRL,btn_right,killclient + +# Overview mode (requires NONE modifier) +mousebind=NONE,btn_left,toggleoverview,-1 +mousebind=NONE,btn_right,killclient,0 +mousebind=NONE,btn_middle,togglemaximizescreen,0 +``` + +--- + +## Axis Bindings + +Map scroll wheel movements to actions for workspace and window navigation. + +### Syntax + +```ini +axisbind=MODIFIERS,DIRECTION,COMMAND,PARAMETERS +``` + +- **Direction**: `UP`, `DOWN`, `LEFT`, `RIGHT` + +### Examples + +```ini +axisbind=SUPER,UP,viewtoleft_have_client +axisbind=SUPER,DOWN,viewtoright_have_client +``` + +--- + +## Gesture Bindings + +Enable touchpad swipe gestures for navigation and window management. + +### Syntax + +```ini +gesturebind=MODIFIERS,DIRECTION,FINGERS,COMMAND,PARAMETERS +``` + +- **Direction**: `up`, `down`, `left`, `right` +- **Fingers**: `3` or `4` + +> **Info:** Gestures require proper touchpad configuration. See [Input Devices](/docs/configuration/input) for touchpad settings like `tap_to_click` and `disable_while_typing`. + +### Examples + +```ini +# 3-finger: Window focus +gesturebind=none,left,3,focusdir,left +gesturebind=none,right,3,focusdir,right +gesturebind=none,up,3,focusdir,up +gesturebind=none,down,3,focusdir,down + +# 4-finger: Workspace navigation +gesturebind=none,left,4,viewtoleft_have_client +gesturebind=none,right,4,viewtoright_have_client +gesturebind=none,up,4,toggleoverview +gesturebind=none,down,4,toggleoverview +``` + +--- + +## Switch Bindings + +Trigger actions on hardware events like laptop lid open/close. + +### Syntax + +```ini +switchbind=FOLD_STATE,COMMAND,PARAMETERS +``` + +- **Fold State**: `fold` (lid closed), `unfold` (lid opened) + +> **Warning:** Disable system lid handling in `/etc/systemd/logind.conf`: +> +> ```ini +> HandleLidSwitch=ignore +> HandleLidSwitchExternalPower=ignore +> HandleLidSwitchDocked=ignore +> ``` + +### Examples + +```ini +switchbind=fold,spawn,swaylock -f -c 000000 +switchbind=unfold,spawn,wlr-dpms on +``` diff --git a/docs/v0.13.0/configuration/basics.md b/docs/v0.13.0/configuration/basics.md new file mode 100644 index 00000000..7afa343b --- /dev/null +++ b/docs/v0.13.0/configuration/basics.md @@ -0,0 +1,87 @@ +--- +title: Basic Configuration +description: Learn how to configure mangowm files, environment variables, and autostart scripts. +--- + +## Configuration File + +mangowm uses a simple configuration file format. By default, it looks for a configuration file in `~/.config/mango/`. + +1. **Locate Default Config** + + A fallback configuration is provided at `/etc/mango/config.conf`. You can use this as a reference. + +2. **Create User Config** + + Copy the default config to your local config directory to start customizing. + + ```bash + mkdir -p ~/.config/mango + cp /etc/mango/config.conf ~/.config/mango/config.conf + ``` + +3. **Launch with Custom Config (Optional)** + + If you prefer to keep your config elsewhere, you can launch mango with the `-c` flag. + + ```bash + mango -c /path/to/your_config.conf + ``` + +### Sub-Configuration + +To keep your configuration organized, you can split it into multiple files and include them using the `source` keyword. + +```ini +# Import keybindings from a separate file +source=~/.config/mango/bind.conf + +# Relative paths work too +source=./theme.conf + +# Optional: ignore if file doesn't exist (useful for shared configs) +source-optional=~/.config/mango/optional.conf +``` + +### Validate Configuration + +You can check your configuration for errors without starting mangowm: + +```bash +mango -c /path/to/config.conf -p +``` + +Use with `source-optional` for shared configs across different setups. + +## Environment Variables + +You can define environment variables directly within your config file. These are set before the window manager fully initializes. + +> **Warning:** Environment variables defined here will be **reset** every time you reload the configuration. + +```ini +env=QT_IM_MODULES,wayland;fcitx +env=XMODIFIERS,@im=fcitx +``` + +## Autostart + +mangowm can automatically run commands or scripts upon startup. There are two modes for execution: + +| Command | Behavior | Usage Case | +| :--- | :--- | :--- | +| `exec-once` | Runs **only once** when mangowm starts. | Status bars, Wallpapers, Notification daemons | +| `exec` | Runs **every time** the config is reloaded. | Scripts that need to refresh settings | + +### Example Setup + +```ini +# Start the status bar once +exec-once=waybar + +# Set wallpaper +exec-once=swaybg -i ~/.config/mango/wallpaper/room.png + +# Reload a custom script on config change +exec=bash ~/.config/mango/reload-settings.sh +``` diff --git a/docs/v0.13.0/configuration/index.mdx b/docs/v0.13.0/configuration/index.mdx new file mode 100644 index 00000000..2bcd3a7e --- /dev/null +++ b/docs/v0.13.0/configuration/index.mdx @@ -0,0 +1,21 @@ +--- +title: Configuration +description: Configure mangowm with config files, environment variables, and autostart. +icon: Settings +--- + +Configure mangowm through config files, environment variables, and autostart. + +<Cards> + +<Card href="/docs/configuration/basics" title="Basics" description="Config files, env vars, exec-once, exec" /> + +<Card href="/docs/configuration/monitors" title="Monitors" description="Monitor settings and resolution" /> + +<Card href="/docs/configuration/input" title="Input" description="Keyboard, mouse, and touchpad" /> + +<Card href="/docs/configuration/xdg-portals" title="XDG Portals" description="File pickers and notifications" /> + +<Card href="/docs/configuration/miscellaneous" title="Miscellaneous" description="Additional options" /> + +</Cards> \ No newline at end of file diff --git a/docs/v0.13.0/configuration/input.md b/docs/v0.13.0/configuration/input.md new file mode 100644 index 00000000..ee12906a --- /dev/null +++ b/docs/v0.13.0/configuration/input.md @@ -0,0 +1,161 @@ +--- +title: Input Devices +description: Configure keyboard layouts, mouse sensitivity, and touchpad gestures. +--- + +## Device Configuration + +mangowm provides granular control over different input devices. + +### Keyboard Settings + +Control key repeat rates and layout rules. + +| Setting | Type | Default | Description | +| :--- | :--- | :--- | :--- | +| `repeat_rate` | `int` | `25` | How many times a key repeats per second. | +| `repeat_delay` | `int` | `600` | Delay (ms) before a held key starts repeating. | +| `numlockon` | `0` or `1` | `0` | Enable NumLock on startup. | +| `xkb_rules_rules` | `string` | - | XKB rules file (e.g., `evdev`, `base`). Usually auto-detected. | +| `xkb_rules_model` | `string` | - | Keyboard model (e.g., `pc104`, `macbook`). | +| `xkb_rules_layout` | `string` | - | Keyboard layout code (e.g., `us`, `de`, `us,de`). | +| `xkb_rules_variant` | `string` | - | Layout variant (e.g., `dvorak`, `colemak`, `intl`). | +| `xkb_rules_options` | `string` | - | XKB options (e.g., `caps:escape`, `ctrl:nocaps`). | + +**Example:** + +```ini +repeat_rate=40 +repeat_delay=300 +numlockon=1 +xkb_rules_layout=us,de +xkb_rules_variant=dvorak +xkb_rules_options=caps:escape,ctrl:nocaps +``` + +--- + +### Mouse Settings + +Configuration for external mice. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `mouse_natural_scrolling` | `0` | Invert scrolling direction. | +| `mouse_accel_profile` | `2` | `0` (None), `1` (Flat), `2` (Adaptive). | +| `mouse_accel_speed` | `0.0` | Speed adjustment (-1.0 to 1.0). | +| `left_handed` | `0` | Swap left and right buttons. | +| `axis_scroll_factor` | `1.0` | Scroll factor for axis scroll speed (0.1–10.0). | +--- + +### Trackpad Settings + +Specific settings for laptop touchpads. Some settings may require a relogin to take effect. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `disable_trackpad` | `0` | Set to `1` to disable the trackpad entirely. | +| `tap_to_click` | `1` | Tap to trigger a left click. | +| `tap_and_drag` | `1` | Tap and hold to drag items. | +| `trackpad_natural_scrolling` | `0` | Invert scrolling direction (natural scrolling). | +| `trackpad_accel_profile` | `2` | `0` (None), `1` (Flat), `2` (Adaptive). | +| `trackpad_accel_speed` | `0.0` | Speed adjustment (-1.0 to 1.0). | +| `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). | +| `send_events_mode` | `0` | `0` (Enabled), `1` (Disabled), `2` (Disabled on external mouse). | +| `drag_lock` | `1` | Lock dragging after tapping. | +| `disable_while_typing` | `1` | Disable trackpad while typing. | +| `left_handed` | `0` | Swap left/right buttons. | +| `middle_button_emulation` | `0` | Emulate middle button. | +| `swipe_min_threshold` | `1` | Minimum swipe threshold when use gesture. | +| `button_map` | `0` | `0` (Left/right/middle), `1` (Left/middle/right). | +| `trackpad_scroll_factor` | `1.0` | Scroll factor for trackpad scroll speed (0.1–10.0). | +--- + +**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. + - `2` — Edge scrolling: send scroll events when a finger moves along the bottom or right edge. + - `4` — Button scrolling: send scroll events when a button is held and the device moves along a scroll axis. + +- `click_method` values: + - `0` — No software click emulation. + - `1` — Button areas: use software-defined areas on the touchpad to generate button events. + - `2` — Clickfinger: the number of fingers determines which button is pressed. + +- `mouse_accel_profile` or `trackpad_scroll_profile` values: + - `0` — No acceleration. + - `1` — Flat: no dynamic acceleration. Pointer speed = original input speed × (1 + `mouse_accel_speed`). + - `2` — Adaptive: slow movement results in less acceleration, fast movement results in more. + +- `button_map` values: + - `0` — 1/2/3 finger tap maps to left / right / middle. + - `1` — 1/2/3 finger tap maps to left / middle / right. + +- `send_events_mode` values: + - `0` — Send events from this device normally. + - `1` — Do not send events from this device. + - `2` — Disable this device when an external pointer device is plugged in. + +--- +--- + +## Keyboard Layout Switching + +To bind multiple layouts and toggle between them, define the layouts in `xkb_rules_layout` and use `xkb_rules_options` to set a toggle key combination. Then bind `switch_keyboard_layout` to trigger a switch. + +```ini +# Define two layouts: US QWERTY and US Dvorak +xkb_rules_layout=us,us +xkb_rules_variant=,dvorak +xkb_rules_options=grp:lalt_lshift_toggle +``` + +Or bind it manually to a key: + +```ini +# Bind Alt+Shift_L to cycle keyboard layout +bind=alt,shift_l,switch_keyboard_layout +``` + +Use `mmsg -g -k` to query the current keyboard layout at any time. + +--- + +## Input Method Editor (IME) + +To use Fcitx5 or IBus, set these environment variables in your config file. + +> **Info:** These settings require a restart of the window manager to take effect. + +**For Fcitx5:** + +```ini +env=GTK_IM_MODULE,fcitx +env=QT_IM_MODULE,fcitx +env=QT_IM_MODULES,wayland;fcitx +env=SDL_IM_MODULE,fcitx +env=XMODIFIERS,@im=fcitx +env=GLFW_IM_MODULE,ibus +``` + +**For IBus:** + +```ini +env=GTK_IM_MODULE,ibus +env=QT_IM_MODULE,ibus +env=XMODIFIERS,@im=ibus +``` diff --git a/docs/v0.13.0/configuration/meta.json b/docs/v0.13.0/configuration/meta.json new file mode 100644 index 00000000..bc209b4e --- /dev/null +++ b/docs/v0.13.0/configuration/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Configuration", + "pages": ["basics", "monitors", "input", "xdg-portals", "miscellaneous"] +} diff --git a/docs/v0.13.0/configuration/miscellaneous.md b/docs/v0.13.0/configuration/miscellaneous.md new file mode 100644 index 00000000..e1be2907 --- /dev/null +++ b/docs/v0.13.0/configuration/miscellaneous.md @@ -0,0 +1,50 @@ +--- +title: Miscellaneous +description: Advanced settings for XWayland, focus behavior, and system integration. +--- + +## System & Hardware + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `xwayland_persistence` | `1` | Keep XWayland running even when no X11 apps are open (reduces startup lag). | +| `syncobj_enable` | `0` | Enable `drm_syncobj` timeline support (helps with gaming stutter/lag). **Requires restart.** | +| `allow_lock_transparent` | `0` | Allow the lock screen to be transparent. | +| `allow_shortcuts_inhibit` | `1` | Allow shortcuts to be inhibited by clients. | +| `vrr` | - | Set via [monitor rule](/docs/configuration/monitors#monitor-rules). | + +## Focus & Input + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `focus_on_activate` | `1` | Automatically focus windows when they request activation. | +| `sloppyfocus` | `1` | Focus follows the mouse cursor. | +| `warpcursor` | `1` | Warp the cursor to the center of the window when focus changes via keyboard. | +| `cursor_hide_timeout` | `0` | Hide the cursor after `N` seconds of inactivity (`0` to disable). | +| `drag_tile_to_tile` | `0` | Allow dragging a tiled window onto another to swap their positions. | +| `drag_tile_small` | `1` | Allow dragging a tiled window temporarily to small size.| +| `drag_corner` | `3` | Corner for drag-to-tile detection (0: none, 1–3: corners, 4: auto-detect). | +| `drag_warp_cursor` | `1` | Warp cursor when dragging windows to tile. | +| `axis_bind_apply_timeout` | `100` | Timeout (ms) for detecting consecutive scroll events for axis bindings. | + +## Multi-Monitor & Tags + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `focus_cross_monitor` | `0` | Allow directional focus to cross monitor boundaries. | +| `exchange_cross_monitor` | `0` | Allow exchanging clients across monitor boundaries. | +| `focus_cross_tag` | `0` | Allow directional focus to cross into other tags. | +| `view_current_to_back` | `0` | Toggling the current tag switches back to the previously viewed tag. | +| `scratchpad_cross_monitor` | `0` | Share the scratchpad pool across all monitors. | +| `single_scratchpad` | `1` | Only allow one scratchpad (named or standard) to be visible at a time. | + +## Window Behavior + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `enable_floating_snap` | `0` | Snap floating windows to edges or other windows. | +| `snap_distance` | `30` | Max distance (pixels) to trigger floating snap. | +| `no_border_when_single` | `0` | Remove window borders when only one window is visible on the tag. | +| `idleinhibit_ignore_visible` | `0` | Allow invisible clients (e.g., background audio players) to inhibit idle. | +| `drag_tile_refresh_interval` | `8.0` | Interval (1.0–16.0) to refresh tiled window resize during drag. Too small may cause application lag. | +| `drag_floating_refresh_interval` | `8.0` | Interval (1.0–16.0) to refresh floating window resize during drag. Too small may cause application lag. | \ No newline at end of file diff --git a/docs/v0.13.0/configuration/monitors.md b/docs/v0.13.0/configuration/monitors.md new file mode 100644 index 00000000..28ef240b --- /dev/null +++ b/docs/v0.13.0/configuration/monitors.md @@ -0,0 +1,276 @@ +--- +title: Monitors +description: Manage display outputs, resolution, scaling, and tearing. +--- + +## Monitor Rules + +You can configure each display output individually using the `monitorrule` keyword. + +**Syntax:** + +```ini +monitorrule=name:Values,Parameter:Values,Parameter:Values +``` + +> **Info:** If any of the matching fields (`name`, `make`, `model`, `serial`) are set, **all** of the set ones must match to be considered a match. Use `wlr-randr` to get your monitor's name, make, model, and serial. + +### Parameters + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `name` | string | Any | Match by monitor name (supports regex) | +| `make` | string | Any | Match by monitor manufacturer | +| `model` | string | Any | Match by monitor model | +| `serial` | string | Any | Match by monitor serial number | +| `width` | integer | 0-9999 | Monitor width | +| `height` | integer | 0-9999 | Monitor height | +| `refresh` | float | 0.001-9999.0 | Monitor refresh rate | +| `x` | integer | 0-99999 | X position | +| `y` | integer | 0-99999 | Y position | +| `scale` | float | 0.01-100.0 | Monitor scale | +| `vrr` | integer | 0, 1 | Enable variable refresh rate | +| `rr` | integer | 0-7 | Monitor transform | +| `custom` | integer | 0, 1 | Enable custom mode (not supported on all displays — may cause black screen) | + +### Transform Values + +| Value | Rotation | +| :--- | :--- | +| `0` | No transform | +| `1` | 90° counter-clockwise | +| `2` | 180° counter-clockwise | +| `3` | 270° counter-clockwise | +| `4` | 180° vertical flip | +| `5` | Flip + 90° counter-clockwise | +| `6` | Flip + 180° counter-clockwise | +| `7` | Flip + 270° counter-clockwise | + +> **Critical:** If you use XWayland applications, **never use negative coordinates** for your monitor positions. This is a known XWayland bug that causes click events to malfunction. Always arrange your monitors starting from `0,0` and extend into positive coordinates. + +> **Note:** that "name" is a regular expression. If you want an exact match, you need to add `^` and `$` to the beginning and end of the expression, for example, `^eDP-1$` matches exactly the string `eDP-1`. + +### Examples + +```ini +# Laptop display: 1080p, 60Hz, positioned at origin +monitorrule=name:^eDP-1$,width:1920,height:1080,refresh:60,x:0,y:10 + +# Match by make and model instead of name +monitorrule=make:Chimei Innolux Corporation,model:0x15F5,width:1920,height:1080,refresh:60,x:0,y:0 + +# Virtual monitor with pattern matching +monitorrule=name:HEADLESS-.*,width:1920,height:1080,refresh:60,x:1926,y:0,scale:1,rr:0,vrr:0 +``` + +--- + +## Monitor Spec Format + +Several commands (`focusmon`, `tagmon`, `disable_monitor`, `enable_monitor`, `toggle_monitor`, `viewcrossmon`, `tagcrossmon`) accept a **monitor_spec** string to identify a monitor. + +**Format:** + +```text +name:xxx&&make:xxx&&model:xxx&&serial:xxx +``` + +- Any field can be omitted and there is no order requirement. +- If all fields are omitted, the string is treated as the monitor name directly (e.g., `eDP-1`). +- Use `wlr-randr` to find your monitor's name, make, model, and serial. + +**Examples:** + +```bash +# By name (shorthand) +mmsg -d toggle_monitor,eDP-1 + +# By make and model +mmsg -d toggle_monitor,make:Chimei Innolux Corporation&&model:0x15F5 + +# By serial +mmsg -d toggle_monitor,serial:12345678 +``` + +--- + +## Tearing (Game Mode) + +Tearing allows games to bypass the compositor's VSync for lower latency. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `allow_tearing` | `0` | Global tearing control: `0` (Disable), `1` (Enable), `2` (Fullscreen only). | + +### Configuration + +**Enable Globally:** + +```ini +allow_tearing=1 +``` + +**Enable per Window:** + +Use a window rule to force tearing for specific games. + +```ini +windowrule=force_tearing:1,title:vkcube +``` + +### Tearing Behavior Matrix + +| `force_tearing` \ `allow_tearing` | DISABLED (0) | ENABLED (1) | FULLSCREEN_ONLY (2) | +| :--- | :--- | :--- | :--- | +| **UNSPECIFIED** (0) | Not Allowed | Follows tearing_hint | Only fullscreen follows tearing_hint | +| **ENABLED** (1) | Not Allowed | Allowed | Only fullscreen allowed | +| **DISABLED** (2) | Not Allowed | Not Allowed | Not Allowed | + +### Graphics Card Compatibility + +> **Warning:** Some graphics cards require setting the `WLR_DRM_NO_ATOMIC` environment variable before mango starts to successfully enable tearing. + +Add this to `/etc/environment` and reboot: + +```bash +WLR_DRM_NO_ATOMIC=1 +``` + +Or run mango with the environment variable: + +```bash +WLR_DRM_NO_ATOMIC=1 mango +``` + +--- + +## GPU Compatibility + +If mango cannot display correctly or shows a black screen, try selecting a specific GPU: + +```bash +# Use a single GPU +WLR_DRM_DEVICES=/dev/dri/card1 mango + +# Use multiple GPUs +WLR_DRM_DEVICES=/dev/dri/card0:/dev/dri/card1 mango +``` + +Some GPUs have compatibility issues with `syncobj_enable=1` — it may crash apps like `kitty` that use syncobj. Set `WLR_DRM_NO_ATOMIC=1` in `/etc/environment` and reboot to resolve this. + +--- + +## Power Management + +You can control monitor power using the `mmsg` IPC tool. + +```bash +# Turn off +mmsg -d disable_monitor,eDP-1 + +# Turn on +mmsg -d enable_monitor,eDP-1 + +# Toggle +mmsg -d toggle_monitor,eDP-1 +``` + +You can also use `wlr-randr` for monitor management: + +```bash +# Turn off monitor +wlr-randr --output eDP-1 --off + +# Turn on monitor +wlr-randr --output eDP-1 --on + +# Show all monitors +wlr-randr +``` + +--- + +## Screen Scale + +### Without Global Scale (Recommended) + +- If you do not use XWayland apps, you can use monitor rules or `wlr-randr` to set a global monitor scale. +- If you are using XWayland apps, it is not recommended to set a global monitor scale. + +You can set scale like this, for example with a 1.4 factor. + +**Dependencies:** + +```bash +yay -S xorg-xrdb +yay -S xwayland-satellite +``` + +**In config file:** + +```ini +env=QT_AUTO_SCREEN_SCALE_FACTOR,1 +env=QT_WAYLAND_FORCE_DPI,140 +``` + +**In autostart:** + +```bash +echo "Xft.dpi: 140" | xrdb -merge +gsettings set org.gnome.desktop.interface text-scaling-factor 1.4 +``` + +**Edit autostart for XWayland:** + +```bash +# Start xwayland +/usr/sbin/xwayland-satellite :11 & +# Apply scale 1.4 for xwayland +sleep 0.5s && echo "Xft.dpi: 140" | xrdb -merge +``` + +### Using xwayland-satellite to Prevent Blurry XWayland Apps + +If you use fractional scaling, you can use `xwayland-satellite` to automatically scale XWayland apps to prevent blurriness, for example with a scale of 1.4. + +**Dependencies:** + +```bash +yay -S xwayland-satellite +``` + +**In config file:** + +```ini +env=DISPLAY,:2 +exec-once=xwayland-satellite :2 +monitorrule=name:eDP-1,width:1920,height:1080,refresh:60,x:0,y:0,scale:1.4,vrr:0,rr:0 +``` + +> **Warning:** Use a `DISPLAY` value other than `:1` to avoid conflicting with mangowm. + +--- + +## Virtual Monitors + +You can create and manage virtual displays through IPC commands: + +```bash +# Create virtual output +mmsg -d create_virtual_output + +# Destroy all virtual outputs +mmsg -d destroy_all_virtual_output +``` + +You can configure virtual monitors using `wlr-randr`: + +```bash +# Show all monitors +wlr-randr + +# Configure virtual monitor +wlr-randr --output HEADLESS-1 --pos 1921,0 --scale 1 --custom-mode 1920x1080@60Hz +``` + +Virtual monitors can be used for screen sharing with tools like [Sunshine](https://github.com/LizardByte/Sunshine) and [Moonlight](https://github.com/moonlight-stream/moonlight-android), allowing other devices to act as extended monitors. \ No newline at end of file diff --git a/docs/v0.13.0/configuration/xdg-portals.md b/docs/v0.13.0/configuration/xdg-portals.md new file mode 100644 index 00000000..27819ad8 --- /dev/null +++ b/docs/v0.13.0/configuration/xdg-portals.md @@ -0,0 +1,76 @@ +--- +title: XDG Portals +description: Set up screen sharing, clipboard, keyring, and file pickers using XDG portals. +--- + +## Portal Configuration + +You can customize portal settings via the following paths: + +- **User Configuration (Priority):** `~/.config/xdg-desktop-portal/mango-portals.conf` +- **System Fallback:** `/usr/share/xdg-desktop-portal/mango-portals.conf` + +> **Warning:** If you previously added `dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP=wlroots` to your config, remove it. Mango now handles this automatically. + +## Screen Sharing + +To enable screen sharing (OBS, Discord, WebRTC), you need `xdg-desktop-portal-wlr`. + +1. **Install Dependencies** + + `pipewire`, `pipewire-pulse`, `xdg-desktop-portal-wlr` + +2. **Optional: Add to autostart** + + In some situations the portal may not start automatically. You can add this to your autostart script to ensure it launches: + + ```bash + /usr/lib/xdg-desktop-portal-wlr & + ``` + +3. **Restart your computer** to apply changes. + +### Known Issues + +- **Window screen sharing:** Some applications may have issues sharing individual windows. See [#184](https://github.com/mangowm/mango/pull/184) for workarounds. + +- **Screen recording lag:** If you experience stuttering during screen recording, see [xdg-desktop-portal-wlr#351](https://github.com/emersion/xdg-desktop-portal-wlr/issues/351). + +## Clipboard Manager + +Use `cliphist` to manage clipboard history. + +**Dependencies:** `wl-clipboard`, `cliphist`, `wl-clip-persist` + +**Autostart Config:** + +```bash +# Keep clipboard content after app closes +wl-clip-persist --clipboard regular --reconnect-tries 0 & + +# Watch clipboard and store history +wl-paste --type text --watch cliphist store & +``` + +## GNOME Keyring + +If you need to store passwords or secrets (e.g., for VS Code or Minecraft launchers), install `gnome-keyring`. + +**Configuration:** + +Add the following to `~/.config/xdg-desktop-portal/mango-portals.conf`: + +```ini +[preferred] +default=gtk +org.freedesktop.impl.portal.ScreenCast=wlr +org.freedesktop.impl.portal.Screenshot=wlr +org.freedesktop.impl.portal.Secret=gnome-keyring +org.freedesktop.impl.portal.Inhibit=none +``` + +## File Picker (File Selector) + +**Dependencies:** `xdg-desktop-portal`, `xdg-desktop-portal-gtk` + +Reboot your computer once to apply. \ No newline at end of file diff --git a/docs/v0.13.0/faq.md b/docs/v0.13.0/faq.md new file mode 100644 index 00000000..9c9288de --- /dev/null +++ b/docs/v0.13.0/faq.md @@ -0,0 +1,100 @@ +--- +title: FAQ +description: Frequently asked questions and troubleshooting. +--- + +### How do I arrange tiled windows with my mouse? + +You can enable the `drag_tile_to_tile` option in your config. This allows you to drag a tiled window onto another to swap them. + +```ini +drag_tile_to_tile=1 +``` + +--- + +### Why is my background blurry or why does blur look wrong? + +Blur applies to the transparent areas of windows. To disable it entirely, set `blur=0`. + +If you are experiencing **performance issues with blur**, make sure `blur_optimized=1` (the default). This caches the wallpaper as the blur background, which is much cheaper on the GPU: + +```ini +blur_optimized=1 +``` + +--- + +### Blur shows my wallpaper instead of the real background content + +This is expected behavior when `blur_optimized=1` (the default). The optimizer caches the wallpaper to reduce GPU load — windows will blur against the wallpaper rather than the actual content stacked beneath them. + +If you want blur to composite against the true background (i.e., show whatever is actually behind the window), set: + +```ini +blur_optimized=0 +``` + +> **Warning:** Disabling `blur_optimized` significantly increases GPU consumption and may cause rendering lag, especially on lower-end hardware. + +--- + +### My games are lagging or stuttering + +Try enabling **SyncObj** timeline support. + +```ini +syncobj_enable=1 +``` + +--- + +### My games have high input latency + +You can enable **Tearing** (similar to VSync off). + +First, enable it globally: + +```ini +allow_tearing=1 +``` + +Then force it for your specific game: + +```ini +windowrule=force_tearing:1,title:Counter-Strike 2 +``` + +> **Warning:** Some graphics cards require setting `WLR_DRM_NO_ATOMIC=1` before mango starts for tearing to work. Add it to `/etc/environment` and reboot, or launch mango with `WLR_DRM_NO_ATOMIC=1 mango`. See [Monitors — Tearing](/docs/configuration/monitors#tearing-game-mode) for details. + +--- + +### How do I use pipes `|` in spawn commands? + +The standard `spawn` command does not support shell pipes directly. You must use `spawn_shell` instead. + +```ini +bind=SUPER,P,spawn_shell,echo "hello" | rofi -dmenu +``` + +--- + +### Certain key combinations do not work on my keyboard layout. + +`bind` automatically converts keysym to keycode, which is compatible with most layouts but can sometimes be imprecise. If a key combination is not triggering, use the **keycode** directly instead of the key name. + +Run `wev` and press the key to find its keycode, then use it in your bind: + +```ini +# Instead of: +bind=ALT,q,killclient + +# Use the keycode (e.g., code:24 = q on most layouts): +bind=ALT,code:24,killclient +``` + +You can also use `binds` (the `s` flag) to match by keysym instead of keycode: + +```ini +binds=ALT,q,killclient +``` diff --git a/docs/v0.13.0/index.md b/docs/v0.13.0/index.md new file mode 100644 index 00000000..d308370d --- /dev/null +++ b/docs/v0.13.0/index.md @@ -0,0 +1,42 @@ +--- +title: Introduction +description: A lightweight and feature-rich Wayland compositor based on dwl. +--- + + +**mango** is a Wayland compositor based on [dwl](https://codeberg.org/dwl/dwl/). It aims to be as lightweight as `dwl` and can be built completely within a few seconds, without compromising on functionality. + +> **Philosophy:** **Lightweight & Fast**: mango is designed to be minimal yet functional. It compiles in seconds and offers a robust set of features out of the box. + +## Feature Highlights + +Beyond basic window management, mangowm provides a rich set of features designed for a modern Wayland experience. + +- **[Animations](/docs/visuals/animations)** — Smooth, customizable animations for opening, moving, closing windows and tag switching. +- **[Layouts](/docs/window-management/layouts)** — Supports Scroller, Master-Stack, Monocle, Grid, Deck, and more, with per-tag layouts. +- **[Visual Effects](/docs/visuals/effects)** — Built-in blur, shadows, corner radius, and opacity effects powered by scenefx. +- **[IPC & Scripting](/docs/ipc)** — Control the compositor externally with robust IPC support for custom scripts and widgets. + +## Additional Features + +- **XWayland Support** — Excellent compatibility for legacy X11 applications. +- **Tag System** — Uses tags instead of workspaces, allowing separate window layouts for each tag. +- **Input Methods** — Great support for text input v2/v3 (Fcitx5, IBus). +- **Window States** — Rich states including swallow, minimize, maximize, fullscreen, and overlay. +- **Hot-Reload Config** — Simple external configuration that supports hot-reloading without restarting. +- **Scratchpads** — Support for both Sway-like and named scratchpads. + +## Community + +- **[Join the mangowm Discord](https://discord.gg/CPjbDxesh5)** — Chat with the community, get support, share your setup, and stay updated with the latest mangowm news. +- **[Join the GitHub Discussions](https://github.com/mangowm/mango/discussions)** — Ask questions, request features, report issues, or share ideas directly with contributors and other users. + +## Acknowledgements + +This project is built upon the hard work of several open-source projects: + +- **[wlroots](https://gitlab.freedesktop.org/wlroots/wlroots)** — Implementation of the Wayland protocol. +- **[mwc](https://github.com/nikoloc/mwc)** — Basal window animation reference. +- **[dwl](https://codeberg.org/dwl/dwl)** — Basal dwl features. +- **[sway](https://github.com/swaywm/sway)** — Sample implementation of the Wayland protocol. +- **[scenefx](https://github.com/wlrfx/scenefx)** — Library to simplify adding window effects. diff --git a/docs/v0.13.0/installation.md b/docs/v0.13.0/installation.md new file mode 100644 index 00000000..c5d4936c --- /dev/null +++ b/docs/v0.13.0/installation.md @@ -0,0 +1,308 @@ +--- +title: Installation +description: Install mangowm on AerynOS, Arch, Fedora, Gentoo, Guix System, NixOS, PikaOS, or build from source. +--- + +## Package Installation + +mangowm is available as a pre-built package on several distributions. Choose your distribution below. + +--- + +### AerynOS + +mangowm is available in the **AerynOS package repository**. + +You can install it using the `moss` package manager: + +```bash +sudo moss install mangowm +``` + +--- + +### Arch Linux + +mangowm is available in the **Arch User Repository (AUR)**. + +You can install it using an AUR helper like `yay` or `paru`: + +```bash +yay -S mangowm-git +``` + +> **Tip:** This package pulls the latest git version, ensuring you have the newest features and fixes. + +--- + +### Fedora + +The package is in the third-party **Terra repository**. First, add the Terra Repository. + +> **Warning:** Both commands require root privileges. Use `sudo` if needed. + +```bash +dnf install --nogpgcheck --repofrompath 'terra,https://repos.fyralabs.com/terra$releasever' terra-release +``` + +Then, install the package: + +```bash +dnf install mangowm +``` + +--- + +### Gentoo + +The package is hosted in the community-maintained **GURU** repository. + +1. **Add the GURU repository** + + ```bash + emerge --ask --verbose eselect-repository + eselect repository enable guru + emerge --sync guru + ``` + +2. **Unmask packages** + Add the required packages to your `package.accept_keywords` file: + - `gui-libs/scenefx` + - `gui-wm/mangowm` + +3. **Install mango** + ```bash + emerge --ask --verbose gui-wm/mangowm + ``` + +--- + +### Guix System + +The package definition is described in the source repository. + +1. **Add mango channel** + Add to `$HOME/.config/guix/channels.scm`: + + ```scheme + (cons (channel + (name 'mangowm) + (url "https://github.com/mangowm/mango.git") + (branch "main")) + %default-channels) + ``` + +2. **Install** + After running `guix pull`, you can install mangowm: + + ```bash + guix install mangowm + ``` + + Or add it to your system configuration using the mangowm module: + + ```scheme + (use-modules (mangowm)) + + (packages (cons* + mangowm-git + ... ;; Other packages + %base-packages)) + ``` + +> **Tip:** For more information, see the [Guix System documentation](https://guix.gnu.org/manual/devel/en/html_node/Channels.html). + +--- + +### NixOS + +The repository provides a Flake with a NixOS module. + +1. **Add flake input** + + ```nix + # flake.nix + { + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + mangowm = { + url = "github:mangowm/mango"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + # other inputs ... + }; + } + ``` + +2. **Import the NixOS module** + + **Option A — Import in `configuration.nix`:** + + ```nix + # configuration.nix (or any other file that you import) + {inputs, ...}: { + imports = [ + inputs.mangowm.nixosModules.mango + # .. other imports ... + ]; + + # ... + } + ``` + + **Option B — Import directly in flake:** + + ```nix + # flake.nix + { + # ... + + outputs = { self, nixpkgs, mangowm, ...}@inputs: let + inherit (nixpkgs) lib; + # ... + in { + nixosConfigurations.YourHostName = lib.nixosSystem { + modules = [ + mangowm.nixosModules.mango # or inputs.mangowm.nixosModules.mango + # other imports ... + ]; + }; + } + } + ``` + +3. **Enable the module** + + ```nix + # configuration.nix (or any other file that you import) + { + programs.mango.enable = true; + } + ``` + +4. **Start mango on login** + + The following are common examples. Refer to the official NixOS documentation for full configuration options. + + **Option A — greetd:** Autologin on first start; login screen after logout. + + ```nix + services.greetd = { + enable = true; + settings = { + initial_session = { + command = "mango"; + user = "your-username"; # auto-login on first start, no password required + }; + default_session = { + command = "${pkgs.greetd.tuigreet}/bin/tuigreet --cmd mango"; + user = "greeter"; + }; + }; + }; + ``` + + **Option B — Display manager autologin:** Autologin via an existing display manager (e.g. SDDM, GDM). [`addLoginEntry`](/docs/nix-options#addloginentry) (default: `true`) automatically registers mango as a session. + + ```nix + services.displayManager = { + defaultSession = "mango"; # derived from mango.desktop filename + autoLogin = { + enable = true; + user = "your-username"; + }; + }; + ``` + + **Option C — getty autologin:** No login screen, boots directly into mango on TTY1. + + For bash/zsh: + + ```nix + services.getty.autologinUser = "your-username"; + + environment.loginShellInit = '' + [ "$(tty)" = /dev/tty1 ] && exec mango + ''; + ``` + + For fish: + + ```nix + services.getty.autologinUser = "your-username"; + + programs.fish.loginShellInit = '' + if test (tty) = /dev/tty1 + exec mango + end + ''; + ``` + +5. **All available options** + + See [Nix Module Options](/docs/nix-options) for the full list of NixOS and Home Manager options. + +--- + +### PikaOS + +mangowm is available in the **PikaOS package repository**. + +You can install it using the `pikman` package manager: + +```bash +pikman install mangowm +``` + +--- + +## Building from Source + +If your distribution isn't listed above, or you want the latest unreleased changes, you can build mangowm from source. + +> **Info:** Ensure the following dependencies are installed before proceeding: +> +> - `wayland` +> - `wayland-protocols` +> - `libinput` +> - `libdrm` +> - `libxkbcommon` +> - `pixman` +> - `libdisplay-info` +> - `libliftoff` +> - `hwdata` +> - `seatd` +> - `pcre2` +> - `xorg-xwayland` +> - `libxcb` + +You will need to build `wlroots` and `scenefx` manually as well. + +1. **Build wlroots** + Clone and install the specific version required (check README for latest version). + + ```bash + git clone -b 0.19.3 https://gitlab.freedesktop.org/wlroots/wlroots.git + cd wlroots + meson build -Dprefix=/usr + sudo ninja -C build install + ``` + +2. **Build scenefx** + This library handles the visual effects. + + ```bash + git clone -b 0.4.1 https://github.com/wlrfx/scenefx.git + cd scenefx + meson build -Dprefix=/usr + sudo ninja -C build install + ``` + +3. **Build mangowm** + Finally, compile the compositor itself. + ```bash + git clone https://github.com/mangowm/mango.git + cd mango + meson build -Dprefix=/usr + sudo ninja -C build install + ``` diff --git a/docs/v0.13.0/ipc.md b/docs/v0.13.0/ipc.md new file mode 100644 index 00000000..8bb0f5c1 --- /dev/null +++ b/docs/v0.13.0/ipc.md @@ -0,0 +1,154 @@ +--- +title: IPC +description: Control mangowm programmatically using mmsg. +--- + +## Introduction + +mangowm includes a powerful IPC (Inter-Process Communication) tool called `mmsg`. This allows you to query the window manager's state, watch for events, and execute commands from external scripts. + +## Basic Usage + +The general syntax for `mmsg` is: + +```bash +mmsg [-OTLq] +mmsg [-o <output>] -s [-t <tags>] [-l <layout>] [-c <tags>] [-d <cmd>,<arg1>,<arg2>,<arg3>,<arg4>,<arg5>] +mmsg [-o <output>] (-g | -w) [-OotlcvmfxekbA] +``` + +### Options + +| Flag | Description | +| :--- | :--- | +| `-q` | Quit mangowm. | +| `-g` | **Get** values (tags, layout, focused client). | +| `-s` | **Set** values (switch tags, layouts). | +| `-w` | **Watch** mode (streams events). | +| `-O` | Get all output (monitor) information. | +| `-T` | Get number of tags. | +| `-L` | Get all available layouts. | +| `-o` | Select output (monitor). | +| `-t` | Get/set selected tags (set with `[+-^.]`). | +| `-l` | Get/set current layout. | +| `-c` | Get title and appid of focused client. | +| `-v` | Get visibility of statusbar. | +| `-m` | Get fullscreen status. | +| `-f` | Get floating status. | +| `-d` | **Dispatch** an internal command. | +| `-x` | Get focused client geometry. | +| `-e` | Get the name of the last focused layer. | +| `-k` | Get current keyboard layout. | +| `-b` | Get current keybind mode. | +| `-A` | Get scale factor of monitor. | + +## Examples + +### Tag Management + +You can perform arithmetic on tags using the `-t` flag with `-s` (set). + +```bash +# Switch to Tag 1 +mmsg -t 1 + +# Add Tag 2 to current view (Multiview) +mmsg -s -t 2+ + +# Remove Tag 2 from current view +mmsg -s -t 2- + +# Toggle Tag 2 +mmsg -s -t 2^ +``` + +### Layouts + +Switch layouts programmatically. Layout codes: `S` (Scroller), `T` (Tile), `G` (Grid), `M` (Monocle), `K` (Deck), `CT` (Center Tile), `RT` (Right Tile), `VS` (Vertical Scroller), `VT` (Vertical Tile), `VG` (Vertical Grid), `VK` (Vertical Deck), `DW` (Dwindle). + +```bash +# Switch to Scroller +mmsg -l "S" + +# Switch to Tile +mmsg -l "T" +``` + +### Dispatching Commands + +Any command available in `config.conf` keybindings can be run via IPC. + +```bash +# Close the focused window +mmsg -d killclient + +# Resize window by +10 width +mmsg -d resizewin,+10,0 + +# Toggle fullscreen +mmsg -d togglefullscreen + +# Disable a monitor power +mmsg -d disable_monitor,eDP-1 +``` + +### Monitoring & Status + +Use `-g` or `-w` to build custom status bars or automation scripts. + +```bash +# Watch for all message changes +mmsg -w + +# Get all messages without watch +mmsg -g + +# Watch focused client appid and title +mmsg -w -c + +# Get all available outputs +mmsg -O + +# Get all tags message +mmsg -g -t + +# Get current focused client message +mmsg -g -c + +# Get current keyboard layout +mmsg -g -k + +# Get current keybind mode +mmsg -g -b + +# Get scale factor of current monitor +mmsg -g -A +``` + +#### Tag Message Format + +- State: 0 → none, 1 → active, 2 → urgent + +Example output: + +| Monitor | Tag Number | Tag State | Clients in Tag | Focused Client | +|---------|------------|-----------|----------------|----------------| +| eDP-1 | tag 2 | 0 | 1 | 0 | + +| Monitor | occupied tags mask | active tags mask | urgent tags mask | +|---------|--------------------|------------------|------------------| +| eDP-1 | 14 | 6 | 0 | + +## Virtual Monitors + +You can create headless outputs for screen mirroring or remote desktop access (e.g., Sunshine/Moonlight). + +```bash +# Create a virtual output +mmsg -d create_virtual_output + +# Configure it (set resolution) +wlr-randr --output HEADLESS-1 --pos 1920,0 --mode 1920x1080@60Hz + +# Destroy all virtual outputs +mmsg -d destroy_all_virtual_output \ No newline at end of file diff --git a/docs/v0.13.0/meta.json b/docs/v0.13.0/meta.json new file mode 100644 index 00000000..95507bff --- /dev/null +++ b/docs/v0.13.0/meta.json @@ -0,0 +1,22 @@ +{ + "title": "v0.13.0", + "description": "v0.13.0 release", + "root": true, + "pages": [ + "---Getting Started---", + "index", + "installation", + "quick-start", + "---Configuration---", + "configuration", + "visuals", + "window-management", + "bindings", + "---Examples---", + "screenshot", + "---Reference---", + "nix-options", + "ipc", + "faq" + ] +} diff --git a/docs/v0.13.0/nix-options.md b/docs/v0.13.0/nix-options.md new file mode 100644 index 00000000..2537d9d8 --- /dev/null +++ b/docs/v0.13.0/nix-options.md @@ -0,0 +1,519 @@ +--- +title: Nix Module Options +description: NixOS and Home Manager configuration options for mangowm. +--- + +> **Note:** This document is automatically generated from the Nix module source code. + +## NixOS + +**System-level options via `programs.mango`.** + +### enable + + + +Whether to enable mango, a wayland compositor based on dwl\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +false +``` + + + +*Example:* + +```nix +true +``` + +*Declared by:* + - [\<mango/nix/nixos-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/nixos-modules.nix) + + + +### package + + + +The mango package to use + + + +*Type:* +package + + + +*Default:* + +```nix +<derivation mango-nightly> +``` + +*Declared by:* + - [\<mango/nix/nixos-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/nixos-modules.nix) + + + +### addLoginEntry + + + +Whether to add a login entry to the display manager for mango\. Only has effect if a display manager is configured (e\.g\. SDDM, GDM via ` services.displayManager `)\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +true +``` + +*Declared by:* + - [\<mango/nix/nixos-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/nixos-modules.nix) + +## Home Manager + +**Configure mangowm declaratively via `wayland.windowManager.mango`.** + +### enable + + + +Whether to enable mangowm, a Wayland compositor based on dwl\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +false +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### package + + + +The mango package to use + + + +*Type:* +package + + + +*Default:* + +```nix +<derivation mango-nightly> +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### autostart_sh + + + +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:* +strings concatenated with “\\n” + + + +*Default:* + +```nix +"" +``` + + + +*Example:* + +```nix +'' + waybar & + dunst & +'' +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### bottomPrefixes + + + +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\. + + + +*Type:* +list of string + + + +*Default:* + +```nix +[ ] +``` + + + +*Example:* + +```nix +[ + "source" +] +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### extraConfig + + + +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\. + + + +*Type:* +strings concatenated with “\\n” + + + +*Default:* + +```nix +"" +``` + + + +*Example:* + +```nix +'' + # Advanced config that doesn't fit structured format + special_option = 1 +'' +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### settings + + + +Mango configuration written in Nix\. Entries with the same key +should be written as lists\. Variables and colors names should be +quoted\. See [https://mangowm\.github\.io/docs](https://mangowm\.github\.io/docs) 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 ` + +Keymodes (submaps) are supported via the special ` keymode ` attribute\. Each keymode +is a nested attribute set under ` keymode ` that contains its own bindings\. + + + +*Type:* +Mango configuration value + + + +*Default:* + +```nix +{ } +``` + + + +*Example:* + +```nix +{ + # 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" + "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" + ]; + }; + }; +} + +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### systemd\.enable + + + +Whether to enable ` mango-session.target ` on +mango startup\. This links to +` graphical-session.target `\. +Some important environment variables will be imported to systemd +and dbus user environment before reaching the target, including + + - ` DISPLAY ` + - ` WAYLAND_DISPLAY ` + - ` XDG_CURRENT_DESKTOP ` + - ` XDG_SESSION_TYPE ` + - ` NIXOS_OZONE_WL ` + You can extend this list using the ` systemd.variables ` option\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +true +``` + + + +*Example:* + +```nix +false +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### systemd\.extraCommands + + + +Extra commands to run after D-Bus activation\. + + + +*Type:* +list of string + + + +*Default:* + +```nix +[ + "systemctl --user reset-failed" + "systemctl --user start mango-session.target" +] +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### systemd\.variables + + + +Environment variables imported into the systemd and D-Bus user environment\. + + + +*Type:* +list of string + + + +*Default:* + +```nix +[ + "DISPLAY" + "WAYLAND_DISPLAY" + "XDG_CURRENT_DESKTOP" + "XDG_SESSION_TYPE" + "NIXOS_OZONE_WL" + "XCURSOR_THEME" + "XCURSOR_SIZE" +] +``` + + + +*Example:* + +```nix +[ + "--all" +] +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### systemd\.xdgAutostart + + + +Whether to enable autostart of applications using +` systemd-xdg-autostart-generator(8) ` +\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +false +``` + + + +*Example:* + +```nix +true +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### topPrefixes + + + +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\. + + + +*Type:* +list of string + + + +*Default:* + +```nix +[ ] +``` + + + +*Example:* + +```nix +[ + "source" +] +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + diff --git a/docs/v0.13.0/quick-start.md b/docs/v0.13.0/quick-start.md new file mode 100644 index 00000000..bc192474 --- /dev/null +++ b/docs/v0.13.0/quick-start.md @@ -0,0 +1,88 @@ +--- +title: Quick Start +description: Basic configuration and first steps with mangowm. +--- + +Now that you have mangowm installed, let's get your environment set up. + +## Initial Setup + +1. **Create Configuration Directory** + + mangowm looks for configuration files in `~/.config/mango/`. + + ```bash + mkdir -p ~/.config/mango + ``` + +2. **Copy Default Config** + + A default configuration file is provided at `/etc/mango/config.conf`. Copy it to your local directory to start customizing. + + ```bash + cp /etc/mango/config.conf ~/.config/mango/config.conf + ``` + +3. **Launch mangowm** + + You can now start the compositor from your TTY. + + ```bash + mango + ``` + + Optional: To specify a custom config file path: + + ```bash + mango -c /path/to/your/config.conf + ``` + +## Essential Keybindings + +mangowm uses the following keybinds by default: + +| Key Combination | Action | +| :--- | :--- | +| `Alt` + `Return` | Open Terminal (defaults to `foot`) | +| `Alt` + `Space` | Open Launcher (defaults to `rofi`) | +| `Alt` + `Q` | Close (Kill) the active window | +| `Super` + `M` | Quit mangowm | +| `Super` + `F` | Toggle Fullscreen | +| `Alt` + `Arrow Keys` | Move focus (Left, Right, Up, Down) | +| `Ctrl` + `1-9` | Switch to Tag 1-9 | +| `Alt` + `1-9` | Move window to Tag 1-9 | + +> **Warning:** Some default bindings rely on specific tools like `foot` (terminal) and `rofi` (launcher). Ensure you have them installed or update your `config.conf` to use your preferred alternatives. + +## Recommended Tools + +To get a fully functional desktop experience, we recommend installing the following components: + +| Category | Recommended Tools | +| :--- | :--- | +| Application Launcher | rofi, bemenu, wmenu, fuzzel | +| Terminal Emulator | foot, wezterm, alacritty, kitty, ghostty | +| Status Bar | waybar, eww, quickshell, ags | +| Desktop Shell | Noctalia, DankMaterialShell | +| Wallpaper Setup | awww(swww), swaybg | +| Notification Daemon | swaync, dunst, mako | +| Desktop Portal | xdg-desktop-portal, xdg-desktop-portal-wlr, xdg-desktop-portal-gtk | +| Clipboard | wl-clipboard, wl-clip-persist, cliphist | +| Gamma Control / Night Light | wlsunset, gammastep | +| Miscellaneous | xfce-polkit, wlogout | + +## Example Configuration + +Check out the [example configuration](https://github.com/DreamMaoMao/mango-config) by the creator of mangowm, including complete setups for mangowm, Waybar, Rofi, and more. + +```bash +git clone https://github.com/DreamMaoMao/mango-config.git ~/.config/mango +``` + +## Next Steps + +Now that you are up and running, dive deeper into customizing mangowm: + +- [Configure Monitors](/docs/configuration/monitors) — Set up resolution, scaling, and multi-monitor layouts. +- [Window Rules](/docs/window-management/rules#window-rules) — Define how specific applications should behave. +- [Appearance](/docs/visuals/theming) — Customize colors, borders, gaps, and effects. diff --git a/docs/v0.13.0/screenshot.md b/docs/v0.13.0/screenshot.md new file mode 100644 index 00000000..f07cdf0c --- /dev/null +++ b/docs/v0.13.0/screenshot.md @@ -0,0 +1,213 @@ +--- + +title: Screenshots +description: Example screenshot keybindings and capture workflows for mangowm. + +--- + +mangowm does not include a built-in screenshot tool. This keeps the compositor lean. +Instead, compose your own workflow from small Wayland utilities and bind them to keys; + +| Tool | Purpose | +| :--- | :--- | +| [`grim`](https://github.com/emersion/grim) | Capture the screen or a region to a file | +| [`slurp`](https://github.com/emersion/slurp) | Interactively select a region for `grim` | +| [`wl-copy`](https://github.com/bugaevc/wl-clipboard) | Copy screenshots directly to the clipboard | +| [`satty`](https://github.com/gabm/Satty) | Annotate screenshots before saving | +| [`wayfreeze`](https://github.com/nicbk/wayfreeze) | Freeze the screen before capture | + +Install the required with your package manager or from source. + +`grim` writes to the file path you give it, but **will not create missing directories**. Create one first: + +```bash +mkdir -p ~/Pictures/Screenshots +``` + +Any directory works. `~/Pictures/Screenshots/` is just a convention. + +## Quick Binds + +Short, single-step commands can be placed directly in `config.conf` with `spawn_shell`. +No script file needed. + +### Fullscreen + +Captures the entire display. + +```ini +bind=NONE,Print,spawn_shell,grim $HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png +``` + +### Region + +Select an area with `slurp` before capturing. + +```ini +bind=SHIFT,Print,spawn_shell,g=$(slurp -d) && [ -n "$g" ] && grim -g "$g" $HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png +``` + +### Pointer + +Captures the full screen including the cursor. + +```ini +bind=ALT,Print,spawn_shell,grim -c $HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png +``` + +### Clipboard + +Captures to a temporary file and copies the image to the clipboard; no file is saved. + +```ini +bind=CTRL,Print,spawn_shell,f=$(mktemp -t screenshot-XXXXXX.png) && grim "$f" && wl-copy < "$f" && rm -f "$f" +``` + +### Annotate + +Captures and opens `satty` for drawing before saving. + +```ini +bind=SUPER,Print,spawn_shell,f=$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png && grim "$f" && satty --filename "$f" --output-filename "$f" --actions-on-enter save-to-file --early-exit +``` + +## Script Binds + +When a command involves multi-step logic, geometry parsing, FIFOs, or screen freezing, +move it into a script and invoke it with `spawn` instead of `spawn_shell`. + +Create the scripts directory first: + +```bash +mkdir -p ~/.config/mango/scripts/screenshot +``` + +### Window + +Uses `mmsg` (ships with mango) to capture the focused window. + +`~/.config/mango/scripts/screenshot/window.sh`: + +```bash +#!/usr/bin/env bash +geometry=$(mmsg -x | awk '/x / {x=$3} /y / {y=$3} /width / {w=$3} /height / {h=$3} END {print x","y" "w"x"h}') +[ -z "$geometry" ] && exit 1 +grim -g "$geometry" "$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png" +``` + +```ini +bind=CTRL+SHIFT,Print,spawn,$HOME/.config/mango/scripts/screenshot/window.sh +``` + +### Freeze + +Freezes the screen with `wayfreeze` before capturing. + +`~/.config/mango/scripts/screenshot/freeze.sh`: + +```bash +#!/usr/bin/env bash +pipe=$(mktemp -u).fifo +mkfifo "$pipe" +wayfreeze --after-freeze-timeout 100 --after-freeze-cmd "echo > $pipe" & +wayfreeze_pid=$! +read -r < "$pipe" +grim "$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png" +kill "$wayfreeze_pid" 2>/dev/null +rm -f "$pipe" +``` + +```ini +bind=CTRL+SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/freeze.sh +``` + +### Freeze + Region + +Freeze, then select a region with `slurp`. Cleans up on cancel. + +`~/.config/mango/scripts/screenshot/freeze-region.sh`: + +```bash +#!/usr/bin/env bash +pipe=$(mktemp -u).fifo +mkfifo "$pipe" +wayfreeze --after-freeze-timeout 100 --after-freeze-cmd "echo > $pipe" & +wayfreeze_pid=$! +read -r < "$pipe" +geometry=$(slurp -d) +if [[ -z "$geometry" ]]; then + kill "$wayfreeze_pid" 2>/dev/null + rm -f "$pipe" + exit 1 +fi +grim -g "$geometry" "$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png" +kill "$wayfreeze_pid" 2>/dev/null +rm -f "$pipe" +``` + +```ini +bind=SHIFT+SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/freeze-region.sh +``` + +Make all three scripts executable: + +```bash +chmod +x ~/.config/mango/scripts/screenshot/*.sh +``` + +## All-in-One Script + +Prefer fewer files? A single script with subcommands covers every mode above. +Place it in the same directory and use it in place of the individual scripts. + +`~/.config/mango/scripts/screenshot/screenshot.sh`: + +```bash +#!/usr/bin/env bash +set -euo pipefail +mkdir -p "$HOME/Pictures/Screenshots" +filepath="$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png" + +case "${1:-fullscreen}" in + region) + g=$(slurp -d); [ -z "$g" ] && exit 1 + grim -g "$g" "$filepath" ;; + window) + g=$(mmsg -x | awk '/x / {x=$3} /y / {y=$3} /width / {w=$3} /height / {h=$3} END {print x","y" "w"x"h}') + [ -z "$g" ] && exit 1 + grim -g "$g" "$filepath" ;; + freeze) + p=$(mktemp -u).fifo; mkfifo "$p" + wayfreeze --after-freeze-timeout 100 --after-freeze-cmd "echo > $p" & wp=$! + read -r < "$p"; grim "$filepath" + kill "$wp" 2>/dev/null; rm -f "$p" ;; + freeze-region) + p=$(mktemp -u).fifo; mkfifo "$p" + wayfreeze --after-freeze-timeout 100 --after-freeze-cmd "echo > $p" & wp=$! + read -r < "$p"; g=$(slurp -d) + if [ -z "$g" ]; then kill "$wp" 2>/dev/null; rm -f "$p"; exit 1; fi + grim -g "$g" "$filepath" + kill "$wp" 2>/dev/null; rm -f "$p" ;; + annotate) + grim "$filepath"; satty --filename "$filepath" --output-filename "$filepath" --actions-on-enter save-to-file --early-exit ;; + *) grim "$filepath" ;; +esac +``` + +Make the script executable: + + +```bash +chmod +x ~/.config/mango/scripts/screenshot/screenshot.sh +``` + +Then add the binds to `config.conf`: + +```ini +bind=NONE,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh fullscreen +bind=SHIFT,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh region +bind=CTRL+SHIFT,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh window +bind=CTRL+SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh freeze +bind=SHIFT+SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh freeze-region +bind=SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh annotate +``` diff --git a/docs/v0.13.0/visuals/animations.md b/docs/v0.13.0/visuals/animations.md new file mode 100644 index 00000000..76477e05 --- /dev/null +++ b/docs/v0.13.0/visuals/animations.md @@ -0,0 +1,108 @@ +--- +title: Animations +description: Configure smooth transitions for windows and layers. +--- + +## Enabling Animations + +mangowm supports animations for both standard windows and layer shell surfaces (like bars and notifications). + +```ini +animations=1 +layer_animations=1 +``` + +## Animation Types + +You can define different animation styles for opening and closing windows and layer surfaces. + +Available types: `slide`, `zoom`, `fade`, `none`. + +```ini +animation_type_open=zoom +animation_type_close=slide +layer_animation_type_open=slide +layer_animation_type_close=slide +``` + +## Fade Settings + +Control the fade-in and fade-out effects for animations. + +```ini +animation_fade_in=1 +animation_fade_out=1 +fadein_begin_opacity=0.5 +fadeout_begin_opacity=0.5 +``` + +- `animation_fade_in` — Enable fade-in effect (0: disable, 1: enable) +- `animation_fade_out` — Enable fade-out effect (0: disable, 1: enable) +- `fadein_begin_opacity` — Starting opacity for fade-in animations (0.0–1.0) +- `fadeout_begin_opacity` — Starting opacity for fade-out animations (0.0–1.0) + +## Zoom Settings + +Adjust the zoom ratios for zoom animations. + +```ini +zoom_initial_ratio=0.4 +zoom_end_ratio=0.8 +``` + +- `zoom_initial_ratio` — Initial zoom ratio +- `zoom_end_ratio` — End zoom ratio + +## Durations + +Control the speed of animations (in milliseconds). + +| Setting | Type | Default | Description | +| :--- | :--- | :--- | :--- | +| `animation_duration_move` | integer | `500` | Move animation duration (ms) | +| `animation_duration_open` | integer | `400` | Open animation duration (ms) | +| `animation_duration_tag` | integer | `300` | Tag animation duration (ms) | +| `animation_duration_close` | integer | `300` | Close animation duration (ms) | +| `animation_duration_focus` | integer | `0` | Focus change (opacity transition) animation duration (ms) | + +```ini +animation_duration_move=500 +animation_duration_open=400 +animation_duration_tag=300 +animation_duration_close=300 +animation_duration_focus=0 +``` + +## Custom Bezier Curves + +Bezier curves determine the "feel" of an animation (e.g., linear vs. bouncy). The format is `x1,y1,x2,y2`. + +You can visualize and generate curve values using online tools like [cssportal.com](https://www.cssportal.com/css-cubic-bezier-generator/) or [easings.net](https://easings.net). + +| Setting | Type | Default | Description | +| :--- | :--- | :--- | :--- | +| `animation_curve_open` | string | `0.46,1.0,0.29,0.99` | Open animation bezier curve | +| `animation_curve_move` | string | `0.46,1.0,0.29,0.99` | Move animation bezier curve | +| `animation_curve_tag` | string | `0.46,1.0,0.29,0.99` | Tag animation bezier curve | +| `animation_curve_close` | string | `0.46,1.0,0.29,0.99` | Close animation bezier curve | +| `animation_curve_focus` | string | `0.46,1.0,0.29,0.99` | Focus change (opacity transition) animation bezier curve | +| `animation_curve_opafadein` | string | `0.46,1.0,0.29,0.99` | Open opacity animation bezier curve | +| `animation_curve_opafadeout` | string | `0.5,0.5,0.5,0.5` | Close opacity animation bezier curve | + +```ini +animation_curve_open=0.46,1.0,0.29,0.99 +animation_curve_move=0.46,1.0,0.29,0.99 +animation_curve_tag=0.46,1.0,0.29,0.99 +animation_curve_close=0.46,1.0,0.29,0.99 +animation_curve_focus=0.46,1.0,0.29,0.99 +animation_curve_opafadein=0.46,1.0,0.29,0.99 +animation_curve_opafadeout=0.5,0.5,0.5,0.5 +``` + +## Tag Animation Direction + +Control the direction of tag switch animations. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `tag_animation_direction` | `1` | Tag animation direction (1: horizontal, 0: vertical) | \ No newline at end of file diff --git a/docs/v0.13.0/visuals/effects.md b/docs/v0.13.0/visuals/effects.md new file mode 100644 index 00000000..23c1f206 --- /dev/null +++ b/docs/v0.13.0/visuals/effects.md @@ -0,0 +1,82 @@ +--- +title: Window Effects +description: Add visual polish with blur, shadows, and opacity. +--- + +## Blur + +Blur creates a frosted glass effect for transparent windows. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `blur` | `0` | Enable blur for windows. | +| `blur_layer` | `0` | Enable blur for layer surfaces (like bars/docks). | +| `blur_optimized` | `1` | Caches the wallpaper and blur background, significantly reducing GPU usage. Disabling it will significantly increase GPU consumption and may cause rendering lag. **Highly recommended.** | +| `blur_params_radius` | `5` | The strength (radius) of the blur. | +| `blur_params_num_passes` | `1` | Number of passes. Higher = smoother but more expensive. | +| `blur_params_noise` | `0.02` | Blur noise level. | +| `blur_params_brightness` | `0.9` | Blur brightness adjustment. | +| `blur_params_contrast` | `0.9` | Blur contrast adjustment. | +| `blur_params_saturation` | `1.2` | Blur saturation adjustment. | + +> **Warning:** Blur has a relatively high impact on performance. If your hardware is limited, it is not recommended to enable it. If you experience lag with blur on, ensure `blur_optimized=1` — disabling it will significantly increase GPU consumption and may cause rendering lag. To disable blur entirely, set `blur=0`. + +--- + +## Shadows + +Drop shadows help distinguish floating windows from the background. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `shadows` | `0` | Enable shadows. | +| `layer_shadows` | `0` | Enable shadows for layer surfaces. | +| `shadow_only_floating` | `1` | Only draw shadows for floating windows (saves performance). | +| `shadows_size` | `10` | Size of the shadow. | +| `shadows_blur` | `15` | Shadow blur amount. | +| `shadows_position_x` | `0` | Shadow X offset. | +| `shadows_position_y` | `0` | Shadow Y offset. | +| `shadowscolor` | `0x000000ff` | Color of the shadow. | + +```ini +# Example shadows configuration +shadows=1 +layer_shadows=1 +shadow_only_floating=1 +shadows_size=12 +shadows_blur=15 +shadows_position_x=0 +shadows_position_y=0 +shadowscolor=0x000000ff +``` + +--- + +## Opacity & Corner Radius + +Control the transparency and roundness of your windows. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `border_radius` | `0` | Window corner radius in pixels. | +| `border_radius_location_default` | `0` | Corner radius location: `0` (all), `1` (top-left), `2` (top-right), `3` (bottom-left), `4` (bottom-right), `5` (closest corner). | +| `no_radius_when_single` | `0` | Disable radius if only one window is visible. | +| `focused_opacity` | `1.0` | Opacity for the active window (0.0 - 1.0). | +| `unfocused_opacity` | `1.0` | Opacity for inactive windows (0.0 - 1.0). | + +```ini +# Window corner radius in pixels +border_radius=0 + +# Corner radius location (0=all, 1=top-left, 2=top-right, 3=bottom-left, 4=bottom-right) +border_radius_location_default=0 + +# Disable radius if only one window is visible +no_radius_when_single=0 + +# Opacity for the active window (0.0 - 1.0) +focused_opacity=1.0 + +# Opacity for inactive windows +unfocused_opacity=1.0 +``` diff --git a/docs/v0.13.0/visuals/index.mdx b/docs/v0.13.0/visuals/index.mdx new file mode 100644 index 00000000..f71ae2f8 --- /dev/null +++ b/docs/v0.13.0/visuals/index.mdx @@ -0,0 +1,19 @@ +--- +title: Visuals +description: Customize borders, colors, effects, and animations. +icon: Palette +--- + +Customize the look of your desktop. + +<Cards> + +<Card href="/docs/visuals/theming" title="Theming" description="Borders, colors, and cursor" /> + +<Card href="/docs/visuals/status-bar" title="Status Bar" description="Built-in status bar" /> + +<Card href="/docs/visuals/effects" title="Effects" description="Blur, shadows, rounded corners" /> + +<Card href="/docs/visuals/animations" title="Animations" description="Window and tag animations" /> + +</Cards> \ No newline at end of file diff --git a/docs/v0.13.0/visuals/meta.json b/docs/v0.13.0/visuals/meta.json new file mode 100644 index 00000000..58723c4e --- /dev/null +++ b/docs/v0.13.0/visuals/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Visuals", + "pages": ["theming", "status-bar", "effects", "animations"] +} diff --git a/docs/v0.13.0/visuals/status-bar.md b/docs/v0.13.0/visuals/status-bar.md new file mode 100644 index 00000000..f2924e83 --- /dev/null +++ b/docs/v0.13.0/visuals/status-bar.md @@ -0,0 +1,141 @@ +--- +title: Status Bar +description: Configure Waybar for mangowm. +--- + +## Module Configuration + +mangowm is compatible with Waybar's `ext/workspaces` module (Wayland standard) or the `dwl/tags` module. We recommend `ext/workspaces` for the best experience. + +> **Tip:** You can also use the `dwl/tags` module, but `ext/workspaces` provides better integration with mangowm's features. The `ext/workspaces` module requires **Waybar > 0.14.0**. + +### `config.jsonc` + +Add the following to your Waybar configuration: + +```jsonc +{ + "modules-left": [ + "ext/workspaces", + "dwl/window" + ], + "ext/workspaces": { + "format": "{icon}", + "ignore-hidden": true, + "on-click": "activate", + "on-click-right": "deactivate", + "sort-by-id": true + }, + "dwl/window": { + "format": "[{layout}] {title}" + } +} +``` + +## Styling + +You can style the tags using standard CSS in `style.css`. + +### `style.css` + +```css +#workspaces { + border-radius: 4px; + border-width: 2px; + border-style: solid; + border-color: #c9b890; + margin-left: 4px; + padding-left: 10px; + padding-right: 6px; + background: rgba(40, 40, 40, 0.76); +} + +#workspaces button { + border: none; + background: none; + box-shadow: inherit; + text-shadow: inherit; + color: #ddca9e; + padding: 1px; + padding-left: 1px; + padding-right: 1px; + margin-right: 2px; + margin-left: 2px; +} + +#workspaces button.hidden { + color: #9e906f; + background-color: transparent; +} + +#workspaces button.visible { + color: #ddca9e; +} + +#workspaces button:hover { + color: #d79921; +} + +#workspaces button.active { + background-color: #ddca9e; + color: #282828; + margin-top: 5px; + margin-bottom: 5px; + padding-top: 1px; + padding-bottom: 0px; + border-radius: 3px; +} + +#workspaces button.urgent { + background-color: #ef5e5e; + color: #282828; + margin-top: 5px; + margin-bottom: 5px; + padding-top: 1px; + padding-bottom: 0px; + border-radius: 3px; +} + +#tags { + background-color: transparent; +} + +#tags button { + background-color: #fff; + color: #a585cd; +} + +#tags button:not(.occupied):not(.focused) { + font-size: 0; + min-width: 0; + min-height: 0; + margin: -17px; + padding: 0; + color: transparent; + background-color: transparent; +} + +#tags button.occupied { + background-color: #fff; + color: #cdc885; +} + +#tags button.focused { + background-color: rgb(186, 142, 213); + color: #fff; +} + +#tags button.urgent { + background: rgb(171, 101, 101); + color: #fff; +} + +#window { + background-color: rgb(237, 196, 147); + color: rgb(63, 37, 5); +} +``` + +## Complete Configuration Example + +> **Tip:** You can find a complete Waybar configuration for mangowm at [waybar-config](https://github.com/DreamMaoMao/waybar-config). \ No newline at end of file diff --git a/docs/v0.13.0/visuals/theming.md b/docs/v0.13.0/visuals/theming.md new file mode 100644 index 00000000..676c575b --- /dev/null +++ b/docs/v0.13.0/visuals/theming.md @@ -0,0 +1,62 @@ +--- +title: Theming +description: Customize the visual appearance of borders, colors, and the cursor. +--- + +## Dimensions + +Control the sizing of window borders and gaps. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `borderpx` | `4` | Border width in pixels. | +| `gappih` | `5` | Horizontal inner gap (between windows). | +| `gappiv` | `5` | Vertical inner gap. | +| `gappoh` | `10` | Horizontal outer gap (between windows and screen edges). | +| `gappov` | `10` | Vertical outer gap. | + +## Colors + +Colors are defined in `0xRRGGBBAA` hex format. + +```ini +# Background color of the root window +rootcolor=0x323232ff + +# Inactive window border +bordercolor=0x444444ff + +# Drop shadow when dragging windows +dropcolor=0x8FBA7C55 + +# Split window border color in manual dwindle layout +splitcolor=0xEB441EFF + +# Active window border +focuscolor=0xc66b25ff + +# Urgent window border (alerts) +urgentcolor=0xad401fff +``` + +### State-Specific Colors + +You can also color-code windows based on their state: + +| State | Config Key | Default Color | +| :--- | :--- | :--- | +| Maximized | `maximizescreencolor` | `0x89aa61ff` | +| Scratchpad | `scratchpadcolor` | `0x516c93ff` | +| Global | `globalcolor` | `0xb153a7ff` | +| Overlay | `overlaycolor` | `0x14a57cff` | + +> **Tip:** For scratchpad window sizing, see [Scratchpad](/docs/window-management/scratchpad) configuration. + +## Cursor Theme + +Set the size and theme of your mouse cursor. + +```ini +cursor_size=24 +cursor_theme=Adwaita +``` diff --git a/docs/v0.13.0/window-management/index.mdx b/docs/v0.13.0/window-management/index.mdx new file mode 100644 index 00000000..b96c5891 --- /dev/null +++ b/docs/v0.13.0/window-management/index.mdx @@ -0,0 +1,19 @@ +--- +title: Window Management +description: Layouts, rules, and window behavior. +icon: LayoutGrid +--- + +Window management with layouts, rules, and scratchpad support. + +<Cards> + +<Card href="/docs/window-management/layouts" title="Layouts" description="Tile, scroller, monocle, grid, deck" /> + +<Card href="/docs/window-management/rules" title="Rules" description="Window rules and conditions" /> + +<Card href="/docs/window-management/overview" title="Overview" description="Window states and properties" /> + +<Card href="/docs/window-management/scratchpad" title="Scratchpad" description="Quick access to applications" /> + +</Cards> \ No newline at end of file diff --git a/docs/v0.13.0/window-management/layouts.md b/docs/v0.13.0/window-management/layouts.md new file mode 100644 index 00000000..bf5283d7 --- /dev/null +++ b/docs/v0.13.0/window-management/layouts.md @@ -0,0 +1,133 @@ +--- +title: Layouts +description: Configure and switch between different window layouts. +--- + +## Supported Layouts + +mangowm supports a variety of layouts that can be assigned per tag. + +- `tile` +- `scroller` +- `monocle` +- `grid` +- `deck` +- `center_tile` +- `vertical_tile` +- `right_tile` +- `vertical_scroller` +- `vertical_grid` +- `vertical_deck` +- `dwindle` + +--- + +## Scroller Layout + +The Scroller layout positions windows in a scrollable strip, similar to PaperWM. + +### Configuration + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `scroller_structs` | `20` | Width reserved on sides when window ratio is 1. | +| `scroller_default_proportion` | `0.9` | Default width proportion for new windows. | +| `scroller_focus_center` | `0` | Always center the focused window (1 = enable). | +| `scroller_prefer_center` | `0` | Center focused window only if it was outside the view. | +| `scroller_prefer_overspread` | `1` | Allow windows to overspread when there's extra space. | +| `edge_scroller_pointer_focus` | `1` | Focus windows even if partially off-screen. | +| `scroller_proportion_preset` | `0.5,0.8,1.0` | Presets for cycling window widths. | +| `scroller_ignore_proportion_single` | `1` | Ignore proportion adjustments for single windows. | +| `scroller_default_proportion_single` | `1.0` | Default proportion for single windows in scroller. **Requires `scroller_ignore_proportion_single=0` to take effect.** | + +> **Warning:** `scroller_prefer_overspread`, `scroller_focus_center`, and `scroller_prefer_center` interact with each other. Their priority order is: +> +> **scroller_prefer_overspread > scroller_focus_center > scroller_prefer_center** +> +> To ensure a lower-priority setting takes effect, you must set all higher-priority options to `0`. + +```ini +# Example scroller configuration +scroller_structs=20 +scroller_default_proportion=0.9 +scroller_focus_center=0 +scroller_prefer_center=0 +scroller_prefer_overspread=1 +edge_scroller_pointer_focus=1 +scroller_default_proportion_single=1.0 +scroller_proportion_preset=0.5,0.8,1.0 +``` + +--- + +## Master-Stack Layouts + +These settings apply to layouts like `tile` and `center_tile`. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `new_is_master` | `1` | New windows become the master window. | +| `default_mfact` | `0.55` | The split ratio between master and stack areas. | +| `default_nmaster` | `1` | Number of allowed master windows. | +| `smartgaps` | `0` | Disable gaps when only one window is present. | +| `center_master_overspread` | `0` | (Center Tile) Master spreads across screen if no stack exists. | +| `center_when_single_stack` | `1` | (Center Tile) Center master when only one stack window exists. | + +```ini +# Example master-stack configuration +new_is_master=1 +smartgaps=0 +default_mfact=0.55 +default_nmaster=1 +``` + +--- + +## Dwindle Layout + +The Dwindle layout arranges windows as a binary tree of recursive splits. Each new window splits the focused window's container, producing a spiral-like tiling. + +### Configuration + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `dwindle_split_ratio` | `0.5` | Ratio used for new splits (`0.05`–`0.95`). | +| `dwindle_smart_split` | `0` | Pick the split axis from the cursor's position inside the focused window. The new window appears on the cursor's side. | +| `dwindle_hsplit` | `1` | Side-by-side splits: where the new window goes. `0` = follow cursor, `1` = right, `2` = left. | +| `dwindle_vsplit` | `1` | Top/bottom splits: where the new window goes. `0` = follow cursor, `1` = below, `2` = above. | +| `dwindle_preserve_split` | `0` | Keep the sibling's split orientation when a window is closed. | +| `dwindle_smart_resize` | `0` | When dragging to resize, move the split toward the cursor regardless of which side was grabbed. | +| `dwindle_drop_simple_split` | `1` | Drag-to-tile drop preview. `1` = 2-zone preview matching `dwindle_split_ratio`, `0` = 4-quadrant preview. | +| `dwindle_manual_split` | `0` | Manually split windows mode. | + +```ini +# Example dwindle configuration +dwindle_split_ratio=0.5 +dwindle_smart_split=0 +dwindle_hsplit=0 +dwindle_vsplit=0 +dwindle_preserve_split=0 +dwindle_smart_resize=0 +dwindle_drop_simple_split=1 +``` + +--- + +## Switching Layouts +| Setting | Default | Description | +| :--- | :--- | :--- | +| `circle_layout` | - | A comma-separated list of layouts `switch_layout` cycles through,the value sample:`tile,scroller`. | + +You can switch layouts dynamically or set a default for specific tags using [Tag Rules](/docs/window-management/rules#tag-rules). + +**Keybinding Examples:** + +```ini +# Cycle through layouts +circle_layout=grid,scroller,tile +bind=SUPER,n,switch_layout + +# Set specific layout +bind=SUPER,t,setlayout,tile +bind=SUPER,s,setlayout,scroller +``` diff --git a/docs/v0.13.0/window-management/meta.json b/docs/v0.13.0/window-management/meta.json new file mode 100644 index 00000000..e0937d14 --- /dev/null +++ b/docs/v0.13.0/window-management/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Window Management", + "pages": ["layouts", "rules", "overview", "scratchpad"] +} diff --git a/docs/v0.13.0/window-management/overview.md b/docs/v0.13.0/window-management/overview.md new file mode 100644 index 00000000..7da6e690 --- /dev/null +++ b/docs/v0.13.0/window-management/overview.md @@ -0,0 +1,29 @@ +--- +title: Overview +description: Configure the overview mode for window navigation. +--- + +## Overview Settings + +| Setting | Type | Default | Description | +| :--- | :--- | :--- | :--- | +| `hotarea_size` | integer | `10` | Hot area size in pixels. | +| `enable_hotarea` | integer | `1` | Enable hot areas (0: disable, 1: enable). | +| `hotarea_corner` | integer | `2` | Hot area corner (0: top-left, 1: top-right, 2: bottom-left, 3: bottom-right). | +| `ov_tab_mode` | integer | `0` | Overview tab mode (0: disable, 1: enable). | +| `overviewgappi` | integer | `5` | Inner gap in overview mode. | +| `overviewgappo` | integer | `30` | Outer gap in overview mode. | + +### Setting Descriptions + +- `enable_hotarea` — Toggles overview when the cursor enters the configured corner. +- `hotarea_size` — Size of the hot area trigger zone in pixels. +- `hotarea_corner` — Corner that triggers the hot area (0: top-left, 1: top-right, 2: bottom-left, 3: bottom-right). +- `ov_tab_mode` — Circles focus through windows in overview; releasing the mod key exits overview. + +### Mouse Interaction in Overview + +When in overview mode: + +- **Left mouse button** — Jump to (focus) a window. +- **Right mouse button** — Close a window. \ No newline at end of file diff --git a/docs/v0.13.0/window-management/rules.md b/docs/v0.13.0/window-management/rules.md new file mode 100644 index 00000000..4a295157 --- /dev/null +++ b/docs/v0.13.0/window-management/rules.md @@ -0,0 +1,250 @@ +--- +title: Rules +description: Define behavior for specific windows, tags, and layers. +--- + +## Window Rules + +Window rules allow you to set specific properties (floating, opacity, size, animations, etc.) for applications based on their `appid` or `title`. You can set all parameters in one line, and if you both set appid and title, the window will only follow the rules when appid and title both match. + +**Format:** + +```ini +windowrule=Parameter:Values,title:Values +windowrule=Parameter:Values,Parameter:Values,appid:Values,title:Values +``` + +### State & Behavior Parameters + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `appid` | string | Any | Match by application ID, supports regex | +| `title` | string | Any | Match by window title, supports regex | +| `isfloating` | integer | `0` / `1` | Force floating state | +| `isfullscreen` | integer | `0` / `1` | Force fullscreen state | +| `isfakefullscreen` | integer | `0` / `1` | Force fake-fullscreen state (window stays constrained) | +| `isglobal` | integer | `0` / `1` | Open as global window (sticky across tags) | +| `isoverlay` | integer | `0` / `1` | Make it always in top layer | +| `isopensilent` | integer | `0` / `1` | Open without focus | +| `istagsilent` | integer | `0` / `1` | Don't focus if client is not in current view tag | +| `force_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 | +| `noopenmaximized` | integer | `0` / `1` | Window does not open as maximized mode | +| `single_scratchpad` | integer | `0` / `1` (default 1) | Only show one out of named scratchpads or the normal scratchpad | +| `allow_shortcuts_inhibit` | integer | `0` / `1` (default 1) | Allow shortcuts to be inhibited by clients | +| `indleinhibit_when_focus` | integer | `0` / `1` (default 0) | Automatically keep idle inhibit active when this window is focused | + +### Geometry & Position + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `width` | float | 0-9999 | Window width when it becomes a floating window,if the value below 1, it will be the percentage of the screen width,otherwise it will be the pixel value | +| `height` | float | 0-9999 | Window height when it becomes a floating window,if the value below 1, it will be the percentage of the screen height,otherwise it will be the pixel value | +| `offsetx` | integer | -999-999 | X offset from center (%), 100 is the edge of screen with outer gap | +| `offsety` | integer | -999-999 | Y offset from center (%), 100 is the edge of screen with outer gap | +| `monitor` | string | Any | Assign to monitor by [monitor spec](/docs/configuration/monitors#monitor-spec-format) (name, make, model, or serial) | +| `tags` | integer | 1-9 | Assign to specific tag | +| `no_force_center` | integer | `0` / `1` | Window does not force center | +| `isnosizehint` | integer | `0` / `1` | Don't use min size and max size for size hints | + +### Visuals & Decoration + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `noblur` | integer | `0` / `1` | Window does not have blur effect | +| `isnoborder` | integer | `0` / `1` | Remove window border | +| `isnoshadow` | integer | `0` / `1` | Not apply shadow | +| `isnoradius` | integer | `0` / `1` | Not apply corner radius | +| `isnoanimation` | integer | `0` / `1` | Not apply animation | +| `focused_opacity` | integer | `0` / `1` | Window focused opacity | +| `unfocused_opacity` | integer | `0` / `1` | Window unfocused opacity | +| `allow_csd` | integer | `0` / `1` | Allow client side decoration | + +> **Tip:** For detailed visual effects configuration, see the [Window Effects](/docs/visuals/effects) page for blur, shadows, and opacity settings. + +### Layout & Scroller + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `scroller_proportion` | float | 0.1-1.0 | Set scroller proportion | +| `scroller_proportion_single` | float | 0.1-1.0 | Set scroller auto adjust proportion when it is single window | + +> **Tip:** For comprehensive layout configuration, see the [Layouts](/docs/window-management/layouts) page for all layout options and detailed settings. + +### Animation + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `animation_type_open` | string | zoom, slide, fade, none | Set open animation | +| `animation_type_close` | string | zoom, slide, fade, none | Set close animation | +| `nofadein` | integer | `0` / `1` | Window ignores fade-in animation | +| `nofadeout` | integer | `0` / `1` | Window ignores fade-out animation | + +> **Tip:** For detailed animation configuration, see the [Animations](/docs/visuals/animations) page for available types and settings. + +### Terminal & Swallowing + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `isterm` | integer | `0` / `1` | A new GUI window will replace the isterm window when it is opened | +| `noswallow` | integer | `0` / `1` | The window will not replace the isterm window | + +### Global & Special Windows + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `globalkeybinding` | string | `[mod combination][-][key]` | Global keybinding (only works for Wayland apps) | +| `isunglobal` | integer | `0` / `1` | Open as unmanaged global window (for desktop pets or camera windows) | +| `isnamedscratchpad` | integer | `0` / `1` | 0: disable, 1: named scratchpad | + +> **Tip:** For scratchpad usage, see the [Scratchpad](/docs/window-management/scratchpad) page for detailed configuration examples. + +### Performance & Tearing + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `force_tearing` | integer | `0` / `1` | Set window to tearing state, refer to [Tearing](/docs/configuration/monitors#tearing-game-mode) | + +### Examples + +```ini +# Set specific window size and position +windowrule=width:1000,height:900,appid:yesplaymusic,title:Demons + +# Global keybindings for OBS Studio +windowrule=globalkeybinding:ctrl+alt-o,appid:com.obsproject.Studio +windowrule=globalkeybinding:ctrl+alt+n,appid:com.obsproject.Studio +windowrule=isopensilent:1,appid:com.obsproject.Studio + +# Force tearing for games +windowrule=force_tearing:1,title:vkcube +windowrule=force_tearing:1,title:Counter-Strike 2 + +# Named scratchpad for file manager +windowrule=isnamedscratchpad:1,width:1280,height:800,appid:st-yazi + +# Custom opacity for specific apps +windowrule=focused_opacity:0.8,appid:firefox +windowrule=unfocused_opacity:0.6,appid:foot + +# Disable blur for selection tools +windowrule=noblur:1,appid:slurp + +# Position windows relative to screen center +windowrule=offsetx:20,offsety:-30,width:800,height:600,appid:alacritty + +# Send to specific tag and monitor +windowrule=tags:9,monitor:HDMI-A-1,appid:discord + +# Terminal swallowing setup +windowrule=isterm:1,appid:st +windowrule=noswallow:1,appid:foot + +# Disable client-side decorations +windowrule=allow_csd:1,appid:firefox + +# Unmanaged global window (desktop pets, camera) +windowrule=isunglobal:1,appid:cheese + +# Named scratchpad toggle +bind=alt,h,toggle_named_scratchpad,st-yazi,none,st -c st-yazi -e yazi +``` + +--- + +## Tag Rules + +You can set all parameters in one line. If only `id` is set, the rule is followed when the id matches. If any of `monitor_name`, `monitor_make`, `monitor_model`, or `monitor_serial` are set, the rule is followed only if **all** of the set monitor fields match. + +> **Warning:** Layouts set in tag rules have a higher priority than monitor rule layouts. + +**Format:** + +```ini +tagrule=id:Values,Parameter:Values,Parameter:Values +tagrule=id:Values,monitor_name:eDP-1,Parameter:Values,Parameter:Values +tagrule=id:Values,monitor_make:xxx,monitor_model:xxx,Parameter:Values +``` + +> **Tip:** See [Layouts](/docs/window-management/layouts#supported-layouts) for detailed descriptions of each layout type. + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `id` | integer | 0-9 | Match by tag id, 0 means the ~0 tag | +| `monitor_name` | string | monitor name | Match by monitor name | +| `monitor_make` | string | monitor make | Match by monitor manufacturer | +| `monitor_model` | string | monitor model | Match by monitor model | +| `monitor_serial` | string | monitor serial | Match by monitor serial number | +| `layout_name` | string | layout name | Layout name to set | +| `no_render_border` | integer | `0` / `1` | Disable render border | +| `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 | + +### Examples + +```ini +# Set layout for specific tags +tagrule=id:1,layout_name:scroller +tagrule=id:2,layout_name:scroller + +# Limit to specific monitor +tagrule=id:1,monitor_name:eDP-1,layout_name:scroller +tagrule=id:2,monitor_name:eDP-1,layout_name:scroller + +# Persistent tags (1-4) with layout assignment +tagrule=id:1,no_hide:1,layout_name:scroller +tagrule=id:2,no_hide:1,layout_name:scroller +tagrule=id:3,monitor_name:eDP-1,no_hide:1,layout_name:scroller +tagrule=id:4,monitor_name:eDP-1,no_hide:1,layout_name:scroller + +# Advanced tag configuration with master layout settings +tagrule=id:5,layout_name:tile,nmaster:2,mfact:0.6 +tagrule=id:6,monitor_name:HDMI-A-1,layout_name:monocle,no_render_border:1 +``` + +> **Tip:** For Waybar configuration with persistent tags, see [Status Bar](/docs/visuals/status-bar) documentation. + +--- + +## Layer Rules + +You can set all parameters in one line. Target "layer shell" surfaces like status bars (`waybar`), launchers (`rofi`), or notification daemons. + +**Format:** + +```ini +layerrule=layer_name:Values,Parameter:Values,Parameter:Values +``` + +> **Tip:** You can use `mmsg -e` to get the last open layer name for debugging. + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `layer_name` | string | layer name | Match name of layer, supports regex | +| `animation_type_open` | string | slide, zoom, fade, none | Set open animation | +| `animation_type_close` | string | slide, zoom, fade, none | Set close animation | +| `noblur` | integer | `0` / `1` | Disable blur | +| `noanim` | integer | `0` / `1` | Disable layer animation | +| `noshadow` | integer | `0` / `1` | Disable layer shadow | + +> **Tip:** For animation types, see [Animations](/docs/visuals/animations#animation-types). For visual effects, see [Window Effects](/docs/visuals/effects). + +### Examples + +```ini +# No blur or animation for slurp selection layer (avoids occlusion and ghosting in screenshots) +layerrule=noanim:1,noblur:1,layer_name:selection + +# Zoom animation for Rofi with multiple parameters +layerrule=animation_type_open:zoom,noanim:0,layer_name:rofi + +# Disable animations and shadows for notification daemon +layerrule=noanim:1,noshadow:1,layer_name:swaync + +# Multiple effects for launcher +layerrule=animation_type_open:slide,animation_type_close:fade,noblur:1,layer_name:wofi +``` diff --git a/docs/v0.13.0/window-management/scratchpad.md b/docs/v0.13.0/window-management/scratchpad.md new file mode 100644 index 00000000..398182f9 --- /dev/null +++ b/docs/v0.13.0/window-management/scratchpad.md @@ -0,0 +1,73 @@ +--- +title: Scratchpad +description: Manage hidden "scratchpad" windows for quick access. +--- + +mangowm supports two types of scratchpads: the standard pool (Sway-like) and named scratchpads. + +## Standard Scratchpad + +Any window can be sent to the "scratchpad" pile, which hides it. You can then cycle through them. + +**Keybindings:** + +```ini +# Send current window to scratchpad +bind=SUPER,i,minimized + +# Toggle (show/hide) the scratchpad +bind=ALT,z,toggle_scratchpad + +# Retrieve window from scratchpad (restore) +bind=SUPER+SHIFT,i,restore_minimized +``` + +--- + +## Named Scratchpad + +Named scratchpads are bound to specific keys and applications. When triggered, mangowm will either launch the app (if not running) or toggle its visibility. + +**1. Define the Window Rule** + +You must identify the app using a unique `appid` or `title` and mark it as a named scratchpad. The application must support setting a custom appid or title at launch. Common examples: + +- `st -c my-appid` — sets the appid +- `kitty -T my-title` — sets the window title +- `foot --app-id my-appid` — sets the appid + +Use `none` as a placeholder when you only want to match by one field. + +```ini +# Match by appid +windowrule=isnamedscratchpad:1,width:1280,height:800,appid:st-yazi + +# Match by title +windowrule=isnamedscratchpad:1,width:1000,height:700,title:kitty-scratch +``` + +**2. Bind the Toggle Key** + +Format: `bind=MOD,KEY,toggle_named_scratchpad,appid,title,command` + +Use `none` for whichever field you are not matching on. + +```ini +# Match by appid: launch 'st' with class 'st-yazi' running 'yazi' +bind=alt,h,toggle_named_scratchpad,st-yazi,none,st -c st-yazi -e yazi + +# Match by title: launch 'kitty' with window title 'kitty-scratch' +bind=alt,k,toggle_named_scratchpad,none,kitty-scratch,kitty -T kitty-scratch +``` + +--- + +## Appearance + +You can customize the size of scratchpad windows relative to the screen. + +```ini +scratchpad_width_ratio=0.8 +scratchpad_height_ratio=0.9 +scratchpadcolor=0x516c93ff +``` diff --git a/docs/v0.13.1/bindings/index.mdx b/docs/v0.13.1/bindings/index.mdx new file mode 100644 index 00000000..4c3a5bda --- /dev/null +++ b/docs/v0.13.1/bindings/index.mdx @@ -0,0 +1,15 @@ +--- +title: Bindings & Input +description: Keybindings, mouse gestures, and input devices. +icon: Keyboard +--- + +Configure how you interact with mangowm using flexible keybindings and input options. + +<Cards> + +<Card href="/docs/bindings/keys" title="Key Bindings" description="Keyboard shortcuts and modes" /> + +<Card href="/docs/bindings/mouse-gestures" title="Mouse Gestures" description="Touchpad and mouse gestures" /> + +</Cards> diff --git a/docs/v0.13.1/bindings/keys.md b/docs/v0.13.1/bindings/keys.md new file mode 100644 index 00000000..002c9564 --- /dev/null +++ b/docs/v0.13.1/bindings/keys.md @@ -0,0 +1,216 @@ +--- +title: Key Bindings +description: Define keyboard shortcuts and modes. +--- + +## Syntax + +Key bindings follow this format: + +```ini +bind[flags]=MODIFIERS,KEY,COMMAND,PARAMETERS +``` + +- **Modifiers**: `SUPER`, `CTRL`, `ALT`, `SHIFT`, `NONE` (combine with `+`, e.g. `SUPER+CTRL+ALT`). +- **Key**: Key name (from `xev` or `wev`) or keycode (e.g., `code:24` for `q`). + +> **Info:** `bind` automatically converts keysym to keycode for comparison. This makes it compatible with all keyboard layouts, but the matching may not always be precise. If a key combination doesn't work on your keyboard layout, use a keycode instead (e.g., `code:24` instead of `q`). + +### Flags + +- `l`: Works even when screen is locked. +- `s`: Uses keysym instead of keycode to bind. +- `r`: Triggers on key release instead of press. +- `p`: Pass key event to client. + +**Examples:** + +```ini +bind=SUPER,Q,killclient +bindl=SUPER,L,spawn,swaylock + +# Using keycode instead of key name +bind=ALT,code:24,killclient + +# Combining keycodes for modifiers and keys +bind=code:64,code:24,killclient +bind=code:64+code:133,code:24,killclient + +# Bind with no modifier +bind=NONE,XF86MonBrightnessUp,spawn,brightnessctl set +5% + +# Bind a modifier key itself as the trigger key +bind=alt,shift_l,switch_keyboard_layout +``` + +## Key Modes (Submaps) + +You can divide key bindings into named modes. Rules: + +1. Set `keymode=<name>` before a group of `bind` lines — those binds only apply in that mode. +2. If no `keymode` is set before a bind, it belongs to the `default` mode. +3. The special `common` keymode applies its binds **across all modes**. + +Use `setkeymode` to switch modes, and `mmsg -b` to query the current mode. + +```ini +# Binds in 'common' apply in every mode +keymode=common +bind=SUPER,r,reload_config + +# Default mode bindings +keymode=default +bind=ALT,Return,spawn,foot +bind=SUPER,F,setkeymode,resize + +# 'resize' mode bindings +keymode=resize +bind=NONE,Left,resizewin,-10,0 +bind=NONE,Right,resizewin,+10,0 +bind=NONE,Escape,setkeymode,default +``` + +### Single Modifier Key Binding + +When binding a modifier key itself, use `NONE` for press and the modifier name for release: + +```ini +# Trigger on press of Super key +bind=none,Super_L,spawn,rofi -show run + +# Trigger on release of Super key +bindr=Super,Super_L,spawn,rofi -show run +``` + +## Dispatchers List + +### Window Management + +| Command | Param | Description | +| :--- | :--- | :--- | +| `killclient` | - | Close the focused window. | +| `togglefloating` | - | Toggle floating state. | +| `toggle_all_floating` | - | Toggle all visible clients floating state. | +| `togglefullscreen` | - | Toggle fullscreen. | +| `togglefakefullscreen` | - | Toggle "fake" fullscreen (remains constrained). | +| `togglemaximizescreen` | - | Maximize window (keep decoration/bar). | +| `toggleglobal` | - | Pin window to all tags. | +| `toggle_render_border` | - | Toggle border rendering. | +| `centerwin` | - | Center the floating window. | +| `minimized` | - | Minimize window to scratchpad. | +| `restore_minimized` | - | Restore window from scratchpad. | +| `toggle_scratchpad` | - | Toggle scratchpad. | +| `toggle_named_scratchpad` | `appid,title,cmd` | Toggle named scratchpad. Launches app if not running, otherwise shows/hides it. | + +### Focus & Movement + +| Command | Param | Description | +| :--- | :--- | :--- | +| `focusdir` | `left/right/up/down` | Focus window in direction. | +| `focusstack` | `next/prev` | Cycle focus within the stack. | +| `focuslast` | - | Focus the previously active window. | +| `exchange_client` | `left/right/up/down` | Swap window with neighbor in direction. | +| `exchange_stack_client` | `next/prev` | Exchange window position in stack. | +| `zoom` | - | Swap focused window with Master. | + +### Tags & Monitors + +| Command | Param | Description | +| :--- | :--- | :--- | +| `view` | `-1/0/1-9` or `mask [,synctag]` | View tag. `-1` = previous tagset, `0` = all tags, `1-9` = specific tag, mask e.g. `1\|3\|5`. Optional `synctag` (0/1) syncs the action to all monitors. | +| `viewtoleft` | `[synctag]` | View previous tag. Optional `synctag` (0/1) syncs to all monitors. | +| `viewtoright` | `[synctag]` | View next tag. Optional `synctag` (0/1) syncs to all monitors. | +| `viewtoleft_have_client` | `[synctag]` | View left tag and focus client if present. Optional `synctag` (0/1). | +| `viewtoright_have_client` | `[synctag]` | View right tag and focus client if present. Optional `synctag` (0/1). | +| `viewcrossmon` | `tag,monitor_spec` | View specified tag on specified monitor. | +| `tag` | `1-9 [,synctag]` | Move window to tag. Optional `synctag` (0/1) syncs to all monitors. | +| `tagsilent` | `1-9` | Move window to tag without focusing it. | +| `tagtoleft` | `[synctag]` | Move window to left tag. Optional `synctag` (0/1). | +| `tagtoright` | `[synctag]` | Move window to right tag. Optional `synctag` (0/1). | +| `tagcrossmon` | `tag,monitor_spec` | Move window to specified tag on specified monitor. | +| `toggletag` | `0-9` | Toggle tag on window (0 means all tags). | +| `toggleview` | `1-9` | Toggle tag view. | +| `comboview` | `1-9` | View multi tags pressed simultaneously. | +| `focusmon` | `left/right/up/down/monitor_spec` | Focus monitor by direction or [monitor spec](/docs/configuration/monitors#monitor-spec-format). | +| `tagmon` | `left/right/up/down/monitor_spec,[keeptag]` | Move window to monitor by direction or [monitor spec](/docs/configuration/monitors#monitor-spec-format). `keeptag` is 0 or 1. | + +### Layouts + +| Command | Param | Description | +| :--- | :--- | :--- | +| `setlayout` | `name` | Switch to layout (e.g., `scroller`, `tile`). | +| `switch_layout` | - | Cycle through available layouts. | +| `incnmaster` | `+1/-1` | Increase/Decrease number of master windows. | +| `setmfact` | `+0.05` | Increase/Decrease master area size. | +| `set_proportion` | `float` | Set scroller window proportion (0.0–1.0). | +| `switch_proportion_preset` | - | Cycle proportion presets of scroller window. | +| `scroller_stack` | `left/right/up/down` | Move window inside/outside scroller stack by direction. | +| `incgaps` | `+/-value` | Adjust gap size. | +| `togglegaps` | - | Toggle gaps. | +| `dwindle_toggle_split_direction` | - | Toggle split direction in dwindle layout. | + +### System + +| Command | Param | Description | +| :--- | :--- | :--- | +| `spawn` | `cmd` | Execute a command. | +| `spawn_shell` | `cmd` | Execute shell command (supports pipes `\|`). | +| `spawn_on_empty` | `cmd,tagnumber` | Open command on empty tag. | +| `reload_config` | - | Hot-reload configuration. | +| `quit` | - | Exit mangowm. | +| `toggleoverview` | - | Toggle overview mode. | +| `create_virtual_output` | - | Create a headless monitor (for VNC/Sunshine). | +| `destroy_all_virtual_output` | - | Destroy all virtual monitors. | +| `toggleoverlay` | - | Toggle overlay state for the focused window. | +| `toggle_trackpad_enable` | - | Toggle trackpad enable. | +| `setkeymode` | `mode` | Set keymode. | +| `switch_keyboard_layout` | `[index]` | Switch keyboard layout. Optional index (0, 1, 2...) to switch to specific layout. | +| `setoption` | `key,value` | Set config option temporarily. | +| `disable_monitor` | `monitor_spec` | Shutdown monitor. Accepts a [monitor spec](/docs/configuration/monitors#monitor-spec-format). | +| `enable_monitor` | `monitor_spec` | Power on monitor. Accepts a [monitor spec](/docs/configuration/monitors#monitor-spec-format). | +| `toggle_monitor` | `monitor_spec` | Toggle monitor power. Accepts a [monitor spec](/docs/configuration/monitors#monitor-spec-format). | + +### Media Controls + +> **Warning:** Some keyboards don't send standard media keys. Run `wev` and press your key to check the exact key name. + +#### Brightness + +Requires: `brightnessctl` + +```ini +bind=NONE,XF86MonBrightnessUp,spawn,brightnessctl s +2% +bind=SHIFT,XF86MonBrightnessUp,spawn,brightnessctl s 100% +bind=NONE,XF86MonBrightnessDown,spawn,brightnessctl s 2%- +bind=SHIFT,XF86MonBrightnessDown,spawn,brightnessctl s 1% +``` + +#### Volume + +Requires: `wpctl` (WirePlumber) + +```ini +bind=NONE,XF86AudioRaiseVolume,spawn,wpctl set-volume @DEFAULT_SINK@ 5%+ +bind=NONE,XF86AudioLowerVolume,spawn,wpctl set-volume @DEFAULT_SINK@ 5%- +bind=NONE,XF86AudioMute,spawn,wpctl set-mute @DEFAULT_SINK@ toggle +bind=SHIFT,XF86AudioMute,spawn,wpctl set-mute @DEFAULT_SOURCE@ toggle +``` + +#### 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 | +| :--- | :--- | :--- | +| `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. | diff --git a/docs/v0.13.1/bindings/meta.json b/docs/v0.13.1/bindings/meta.json new file mode 100644 index 00000000..f1b629b6 --- /dev/null +++ b/docs/v0.13.1/bindings/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Bindings & Input", + "pages": ["keys", "mouse-gestures"] +} diff --git a/docs/v0.13.1/bindings/mouse-gestures.md b/docs/v0.13.1/bindings/mouse-gestures.md new file mode 100644 index 00000000..c4a36889 --- /dev/null +++ b/docs/v0.13.1/bindings/mouse-gestures.md @@ -0,0 +1,116 @@ +--- +title: Mouse & Gestures +description: Configure mouse buttons, scrolling, gestures, and lid switches. +--- + +## Mouse Bindings + +Assign actions to mouse button presses with optional modifier keys. + +### Syntax + +```ini +mousebind=MODIFIERS,BUTTON,COMMAND,PARAMETERS +``` + +- **Modifiers**: `SUPER`, `CTRL`, `ALT`, `SHIFT`, `NONE`. Combine with `+` (e.g., `SUPER+CTRL`) +- **Buttons**: `btn_left`, `btn_right`, `btn_middle`, `btn_side`, `btn_extra`, `btn_forward`, `btn_back`, `btn_task` + +> **Warning:** When modifiers are set to `NONE`, only `btn_middle` works in normal mode. `btn_left` and `btn_right` only work in overview mode. + +### Examples + +```ini +# Window manipulation +mousebind=SUPER,btn_left,moveresize,curmove +mousebind=SUPER,btn_right,moveresize,curresize +mousebind=SUPER+CTRL,btn_right,killclient + +# Overview mode (requires NONE modifier) +mousebind=NONE,btn_left,toggleoverview,-1 +mousebind=NONE,btn_right,killclient,0 +mousebind=NONE,btn_middle,togglemaximizescreen,0 +``` + +--- + +## Axis Bindings + +Map scroll wheel movements to actions for workspace and window navigation. + +### Syntax + +```ini +axisbind=MODIFIERS,DIRECTION,COMMAND,PARAMETERS +``` + +- **Direction**: `UP`, `DOWN`, `LEFT`, `RIGHT` + +### Examples + +```ini +axisbind=SUPER,UP,viewtoleft_have_client +axisbind=SUPER,DOWN,viewtoright_have_client +``` + +--- + +## Gesture Bindings + +Enable touchpad swipe gestures for navigation and window management. + +### Syntax + +```ini +gesturebind=MODIFIERS,DIRECTION,FINGERS,COMMAND,PARAMETERS +``` + +- **Direction**: `up`, `down`, `left`, `right` +- **Fingers**: `3` or `4` + +> **Info:** Gestures require proper touchpad configuration. See [Input Devices](/docs/configuration/input) for touchpad settings like `tap_to_click` and `disable_while_typing`. + +### Examples + +```ini +# 3-finger: Window focus +gesturebind=none,left,3,focusdir,left +gesturebind=none,right,3,focusdir,right +gesturebind=none,up,3,focusdir,up +gesturebind=none,down,3,focusdir,down + +# 4-finger: Workspace navigation +gesturebind=none,left,4,viewtoleft_have_client +gesturebind=none,right,4,viewtoright_have_client +gesturebind=none,up,4,toggleoverview +gesturebind=none,down,4,toggleoverview +``` + +--- + +## Switch Bindings + +Trigger actions on hardware events like laptop lid open/close. + +### Syntax + +```ini +switchbind=FOLD_STATE,COMMAND,PARAMETERS +``` + +- **Fold State**: `fold` (lid closed), `unfold` (lid opened) + +> **Warning:** Disable system lid handling in `/etc/systemd/logind.conf`: +> +> ```ini +> HandleLidSwitch=ignore +> HandleLidSwitchExternalPower=ignore +> HandleLidSwitchDocked=ignore +> ``` + +### Examples + +```ini +switchbind=fold,spawn,swaylock -f -c 000000 +switchbind=unfold,spawn,wlr-dpms on +``` diff --git a/docs/v0.13.1/configuration/basics.md b/docs/v0.13.1/configuration/basics.md new file mode 100644 index 00000000..7afa343b --- /dev/null +++ b/docs/v0.13.1/configuration/basics.md @@ -0,0 +1,87 @@ +--- +title: Basic Configuration +description: Learn how to configure mangowm files, environment variables, and autostart scripts. +--- + +## Configuration File + +mangowm uses a simple configuration file format. By default, it looks for a configuration file in `~/.config/mango/`. + +1. **Locate Default Config** + + A fallback configuration is provided at `/etc/mango/config.conf`. You can use this as a reference. + +2. **Create User Config** + + Copy the default config to your local config directory to start customizing. + + ```bash + mkdir -p ~/.config/mango + cp /etc/mango/config.conf ~/.config/mango/config.conf + ``` + +3. **Launch with Custom Config (Optional)** + + If you prefer to keep your config elsewhere, you can launch mango with the `-c` flag. + + ```bash + mango -c /path/to/your_config.conf + ``` + +### Sub-Configuration + +To keep your configuration organized, you can split it into multiple files and include them using the `source` keyword. + +```ini +# Import keybindings from a separate file +source=~/.config/mango/bind.conf + +# Relative paths work too +source=./theme.conf + +# Optional: ignore if file doesn't exist (useful for shared configs) +source-optional=~/.config/mango/optional.conf +``` + +### Validate Configuration + +You can check your configuration for errors without starting mangowm: + +```bash +mango -c /path/to/config.conf -p +``` + +Use with `source-optional` for shared configs across different setups. + +## Environment Variables + +You can define environment variables directly within your config file. These are set before the window manager fully initializes. + +> **Warning:** Environment variables defined here will be **reset** every time you reload the configuration. + +```ini +env=QT_IM_MODULES,wayland;fcitx +env=XMODIFIERS,@im=fcitx +``` + +## Autostart + +mangowm can automatically run commands or scripts upon startup. There are two modes for execution: + +| Command | Behavior | Usage Case | +| :--- | :--- | :--- | +| `exec-once` | Runs **only once** when mangowm starts. | Status bars, Wallpapers, Notification daemons | +| `exec` | Runs **every time** the config is reloaded. | Scripts that need to refresh settings | + +### Example Setup + +```ini +# Start the status bar once +exec-once=waybar + +# Set wallpaper +exec-once=swaybg -i ~/.config/mango/wallpaper/room.png + +# Reload a custom script on config change +exec=bash ~/.config/mango/reload-settings.sh +``` diff --git a/docs/v0.13.1/configuration/index.mdx b/docs/v0.13.1/configuration/index.mdx new file mode 100644 index 00000000..2bcd3a7e --- /dev/null +++ b/docs/v0.13.1/configuration/index.mdx @@ -0,0 +1,21 @@ +--- +title: Configuration +description: Configure mangowm with config files, environment variables, and autostart. +icon: Settings +--- + +Configure mangowm through config files, environment variables, and autostart. + +<Cards> + +<Card href="/docs/configuration/basics" title="Basics" description="Config files, env vars, exec-once, exec" /> + +<Card href="/docs/configuration/monitors" title="Monitors" description="Monitor settings and resolution" /> + +<Card href="/docs/configuration/input" title="Input" description="Keyboard, mouse, and touchpad" /> + +<Card href="/docs/configuration/xdg-portals" title="XDG Portals" description="File pickers and notifications" /> + +<Card href="/docs/configuration/miscellaneous" title="Miscellaneous" description="Additional options" /> + +</Cards> \ No newline at end of file diff --git a/docs/v0.13.1/configuration/input.md b/docs/v0.13.1/configuration/input.md new file mode 100644 index 00000000..ee12906a --- /dev/null +++ b/docs/v0.13.1/configuration/input.md @@ -0,0 +1,161 @@ +--- +title: Input Devices +description: Configure keyboard layouts, mouse sensitivity, and touchpad gestures. +--- + +## Device Configuration + +mangowm provides granular control over different input devices. + +### Keyboard Settings + +Control key repeat rates and layout rules. + +| Setting | Type | Default | Description | +| :--- | :--- | :--- | :--- | +| `repeat_rate` | `int` | `25` | How many times a key repeats per second. | +| `repeat_delay` | `int` | `600` | Delay (ms) before a held key starts repeating. | +| `numlockon` | `0` or `1` | `0` | Enable NumLock on startup. | +| `xkb_rules_rules` | `string` | - | XKB rules file (e.g., `evdev`, `base`). Usually auto-detected. | +| `xkb_rules_model` | `string` | - | Keyboard model (e.g., `pc104`, `macbook`). | +| `xkb_rules_layout` | `string` | - | Keyboard layout code (e.g., `us`, `de`, `us,de`). | +| `xkb_rules_variant` | `string` | - | Layout variant (e.g., `dvorak`, `colemak`, `intl`). | +| `xkb_rules_options` | `string` | - | XKB options (e.g., `caps:escape`, `ctrl:nocaps`). | + +**Example:** + +```ini +repeat_rate=40 +repeat_delay=300 +numlockon=1 +xkb_rules_layout=us,de +xkb_rules_variant=dvorak +xkb_rules_options=caps:escape,ctrl:nocaps +``` + +--- + +### Mouse Settings + +Configuration for external mice. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `mouse_natural_scrolling` | `0` | Invert scrolling direction. | +| `mouse_accel_profile` | `2` | `0` (None), `1` (Flat), `2` (Adaptive). | +| `mouse_accel_speed` | `0.0` | Speed adjustment (-1.0 to 1.0). | +| `left_handed` | `0` | Swap left and right buttons. | +| `axis_scroll_factor` | `1.0` | Scroll factor for axis scroll speed (0.1–10.0). | +--- + +### Trackpad Settings + +Specific settings for laptop touchpads. Some settings may require a relogin to take effect. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `disable_trackpad` | `0` | Set to `1` to disable the trackpad entirely. | +| `tap_to_click` | `1` | Tap to trigger a left click. | +| `tap_and_drag` | `1` | Tap and hold to drag items. | +| `trackpad_natural_scrolling` | `0` | Invert scrolling direction (natural scrolling). | +| `trackpad_accel_profile` | `2` | `0` (None), `1` (Flat), `2` (Adaptive). | +| `trackpad_accel_speed` | `0.0` | Speed adjustment (-1.0 to 1.0). | +| `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). | +| `send_events_mode` | `0` | `0` (Enabled), `1` (Disabled), `2` (Disabled on external mouse). | +| `drag_lock` | `1` | Lock dragging after tapping. | +| `disable_while_typing` | `1` | Disable trackpad while typing. | +| `left_handed` | `0` | Swap left/right buttons. | +| `middle_button_emulation` | `0` | Emulate middle button. | +| `swipe_min_threshold` | `1` | Minimum swipe threshold when use gesture. | +| `button_map` | `0` | `0` (Left/right/middle), `1` (Left/middle/right). | +| `trackpad_scroll_factor` | `1.0` | Scroll factor for trackpad scroll speed (0.1–10.0). | +--- + +**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. + - `2` — Edge scrolling: send scroll events when a finger moves along the bottom or right edge. + - `4` — Button scrolling: send scroll events when a button is held and the device moves along a scroll axis. + +- `click_method` values: + - `0` — No software click emulation. + - `1` — Button areas: use software-defined areas on the touchpad to generate button events. + - `2` — Clickfinger: the number of fingers determines which button is pressed. + +- `mouse_accel_profile` or `trackpad_scroll_profile` values: + - `0` — No acceleration. + - `1` — Flat: no dynamic acceleration. Pointer speed = original input speed × (1 + `mouse_accel_speed`). + - `2` — Adaptive: slow movement results in less acceleration, fast movement results in more. + +- `button_map` values: + - `0` — 1/2/3 finger tap maps to left / right / middle. + - `1` — 1/2/3 finger tap maps to left / middle / right. + +- `send_events_mode` values: + - `0` — Send events from this device normally. + - `1` — Do not send events from this device. + - `2` — Disable this device when an external pointer device is plugged in. + +--- +--- + +## Keyboard Layout Switching + +To bind multiple layouts and toggle between them, define the layouts in `xkb_rules_layout` and use `xkb_rules_options` to set a toggle key combination. Then bind `switch_keyboard_layout` to trigger a switch. + +```ini +# Define two layouts: US QWERTY and US Dvorak +xkb_rules_layout=us,us +xkb_rules_variant=,dvorak +xkb_rules_options=grp:lalt_lshift_toggle +``` + +Or bind it manually to a key: + +```ini +# Bind Alt+Shift_L to cycle keyboard layout +bind=alt,shift_l,switch_keyboard_layout +``` + +Use `mmsg -g -k` to query the current keyboard layout at any time. + +--- + +## Input Method Editor (IME) + +To use Fcitx5 or IBus, set these environment variables in your config file. + +> **Info:** These settings require a restart of the window manager to take effect. + +**For Fcitx5:** + +```ini +env=GTK_IM_MODULE,fcitx +env=QT_IM_MODULE,fcitx +env=QT_IM_MODULES,wayland;fcitx +env=SDL_IM_MODULE,fcitx +env=XMODIFIERS,@im=fcitx +env=GLFW_IM_MODULE,ibus +``` + +**For IBus:** + +```ini +env=GTK_IM_MODULE,ibus +env=QT_IM_MODULE,ibus +env=XMODIFIERS,@im=ibus +``` diff --git a/docs/v0.13.1/configuration/meta.json b/docs/v0.13.1/configuration/meta.json new file mode 100644 index 00000000..bc209b4e --- /dev/null +++ b/docs/v0.13.1/configuration/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Configuration", + "pages": ["basics", "monitors", "input", "xdg-portals", "miscellaneous"] +} diff --git a/docs/v0.13.1/configuration/miscellaneous.md b/docs/v0.13.1/configuration/miscellaneous.md new file mode 100644 index 00000000..e1be2907 --- /dev/null +++ b/docs/v0.13.1/configuration/miscellaneous.md @@ -0,0 +1,50 @@ +--- +title: Miscellaneous +description: Advanced settings for XWayland, focus behavior, and system integration. +--- + +## System & Hardware + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `xwayland_persistence` | `1` | Keep XWayland running even when no X11 apps are open (reduces startup lag). | +| `syncobj_enable` | `0` | Enable `drm_syncobj` timeline support (helps with gaming stutter/lag). **Requires restart.** | +| `allow_lock_transparent` | `0` | Allow the lock screen to be transparent. | +| `allow_shortcuts_inhibit` | `1` | Allow shortcuts to be inhibited by clients. | +| `vrr` | - | Set via [monitor rule](/docs/configuration/monitors#monitor-rules). | + +## Focus & Input + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `focus_on_activate` | `1` | Automatically focus windows when they request activation. | +| `sloppyfocus` | `1` | Focus follows the mouse cursor. | +| `warpcursor` | `1` | Warp the cursor to the center of the window when focus changes via keyboard. | +| `cursor_hide_timeout` | `0` | Hide the cursor after `N` seconds of inactivity (`0` to disable). | +| `drag_tile_to_tile` | `0` | Allow dragging a tiled window onto another to swap their positions. | +| `drag_tile_small` | `1` | Allow dragging a tiled window temporarily to small size.| +| `drag_corner` | `3` | Corner for drag-to-tile detection (0: none, 1–3: corners, 4: auto-detect). | +| `drag_warp_cursor` | `1` | Warp cursor when dragging windows to tile. | +| `axis_bind_apply_timeout` | `100` | Timeout (ms) for detecting consecutive scroll events for axis bindings. | + +## Multi-Monitor & Tags + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `focus_cross_monitor` | `0` | Allow directional focus to cross monitor boundaries. | +| `exchange_cross_monitor` | `0` | Allow exchanging clients across monitor boundaries. | +| `focus_cross_tag` | `0` | Allow directional focus to cross into other tags. | +| `view_current_to_back` | `0` | Toggling the current tag switches back to the previously viewed tag. | +| `scratchpad_cross_monitor` | `0` | Share the scratchpad pool across all monitors. | +| `single_scratchpad` | `1` | Only allow one scratchpad (named or standard) to be visible at a time. | + +## Window Behavior + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `enable_floating_snap` | `0` | Snap floating windows to edges or other windows. | +| `snap_distance` | `30` | Max distance (pixels) to trigger floating snap. | +| `no_border_when_single` | `0` | Remove window borders when only one window is visible on the tag. | +| `idleinhibit_ignore_visible` | `0` | Allow invisible clients (e.g., background audio players) to inhibit idle. | +| `drag_tile_refresh_interval` | `8.0` | Interval (1.0–16.0) to refresh tiled window resize during drag. Too small may cause application lag. | +| `drag_floating_refresh_interval` | `8.0` | Interval (1.0–16.0) to refresh floating window resize during drag. Too small may cause application lag. | \ No newline at end of file diff --git a/docs/v0.13.1/configuration/monitors.md b/docs/v0.13.1/configuration/monitors.md new file mode 100644 index 00000000..28ef240b --- /dev/null +++ b/docs/v0.13.1/configuration/monitors.md @@ -0,0 +1,276 @@ +--- +title: Monitors +description: Manage display outputs, resolution, scaling, and tearing. +--- + +## Monitor Rules + +You can configure each display output individually using the `monitorrule` keyword. + +**Syntax:** + +```ini +monitorrule=name:Values,Parameter:Values,Parameter:Values +``` + +> **Info:** If any of the matching fields (`name`, `make`, `model`, `serial`) are set, **all** of the set ones must match to be considered a match. Use `wlr-randr` to get your monitor's name, make, model, and serial. + +### Parameters + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `name` | string | Any | Match by monitor name (supports regex) | +| `make` | string | Any | Match by monitor manufacturer | +| `model` | string | Any | Match by monitor model | +| `serial` | string | Any | Match by monitor serial number | +| `width` | integer | 0-9999 | Monitor width | +| `height` | integer | 0-9999 | Monitor height | +| `refresh` | float | 0.001-9999.0 | Monitor refresh rate | +| `x` | integer | 0-99999 | X position | +| `y` | integer | 0-99999 | Y position | +| `scale` | float | 0.01-100.0 | Monitor scale | +| `vrr` | integer | 0, 1 | Enable variable refresh rate | +| `rr` | integer | 0-7 | Monitor transform | +| `custom` | integer | 0, 1 | Enable custom mode (not supported on all displays — may cause black screen) | + +### Transform Values + +| Value | Rotation | +| :--- | :--- | +| `0` | No transform | +| `1` | 90° counter-clockwise | +| `2` | 180° counter-clockwise | +| `3` | 270° counter-clockwise | +| `4` | 180° vertical flip | +| `5` | Flip + 90° counter-clockwise | +| `6` | Flip + 180° counter-clockwise | +| `7` | Flip + 270° counter-clockwise | + +> **Critical:** If you use XWayland applications, **never use negative coordinates** for your monitor positions. This is a known XWayland bug that causes click events to malfunction. Always arrange your monitors starting from `0,0` and extend into positive coordinates. + +> **Note:** that "name" is a regular expression. If you want an exact match, you need to add `^` and `$` to the beginning and end of the expression, for example, `^eDP-1$` matches exactly the string `eDP-1`. + +### Examples + +```ini +# Laptop display: 1080p, 60Hz, positioned at origin +monitorrule=name:^eDP-1$,width:1920,height:1080,refresh:60,x:0,y:10 + +# Match by make and model instead of name +monitorrule=make:Chimei Innolux Corporation,model:0x15F5,width:1920,height:1080,refresh:60,x:0,y:0 + +# Virtual monitor with pattern matching +monitorrule=name:HEADLESS-.*,width:1920,height:1080,refresh:60,x:1926,y:0,scale:1,rr:0,vrr:0 +``` + +--- + +## Monitor Spec Format + +Several commands (`focusmon`, `tagmon`, `disable_monitor`, `enable_monitor`, `toggle_monitor`, `viewcrossmon`, `tagcrossmon`) accept a **monitor_spec** string to identify a monitor. + +**Format:** + +```text +name:xxx&&make:xxx&&model:xxx&&serial:xxx +``` + +- Any field can be omitted and there is no order requirement. +- If all fields are omitted, the string is treated as the monitor name directly (e.g., `eDP-1`). +- Use `wlr-randr` to find your monitor's name, make, model, and serial. + +**Examples:** + +```bash +# By name (shorthand) +mmsg -d toggle_monitor,eDP-1 + +# By make and model +mmsg -d toggle_monitor,make:Chimei Innolux Corporation&&model:0x15F5 + +# By serial +mmsg -d toggle_monitor,serial:12345678 +``` + +--- + +## Tearing (Game Mode) + +Tearing allows games to bypass the compositor's VSync for lower latency. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `allow_tearing` | `0` | Global tearing control: `0` (Disable), `1` (Enable), `2` (Fullscreen only). | + +### Configuration + +**Enable Globally:** + +```ini +allow_tearing=1 +``` + +**Enable per Window:** + +Use a window rule to force tearing for specific games. + +```ini +windowrule=force_tearing:1,title:vkcube +``` + +### Tearing Behavior Matrix + +| `force_tearing` \ `allow_tearing` | DISABLED (0) | ENABLED (1) | FULLSCREEN_ONLY (2) | +| :--- | :--- | :--- | :--- | +| **UNSPECIFIED** (0) | Not Allowed | Follows tearing_hint | Only fullscreen follows tearing_hint | +| **ENABLED** (1) | Not Allowed | Allowed | Only fullscreen allowed | +| **DISABLED** (2) | Not Allowed | Not Allowed | Not Allowed | + +### Graphics Card Compatibility + +> **Warning:** Some graphics cards require setting the `WLR_DRM_NO_ATOMIC` environment variable before mango starts to successfully enable tearing. + +Add this to `/etc/environment` and reboot: + +```bash +WLR_DRM_NO_ATOMIC=1 +``` + +Or run mango with the environment variable: + +```bash +WLR_DRM_NO_ATOMIC=1 mango +``` + +--- + +## GPU Compatibility + +If mango cannot display correctly or shows a black screen, try selecting a specific GPU: + +```bash +# Use a single GPU +WLR_DRM_DEVICES=/dev/dri/card1 mango + +# Use multiple GPUs +WLR_DRM_DEVICES=/dev/dri/card0:/dev/dri/card1 mango +``` + +Some GPUs have compatibility issues with `syncobj_enable=1` — it may crash apps like `kitty` that use syncobj. Set `WLR_DRM_NO_ATOMIC=1` in `/etc/environment` and reboot to resolve this. + +--- + +## Power Management + +You can control monitor power using the `mmsg` IPC tool. + +```bash +# Turn off +mmsg -d disable_monitor,eDP-1 + +# Turn on +mmsg -d enable_monitor,eDP-1 + +# Toggle +mmsg -d toggle_monitor,eDP-1 +``` + +You can also use `wlr-randr` for monitor management: + +```bash +# Turn off monitor +wlr-randr --output eDP-1 --off + +# Turn on monitor +wlr-randr --output eDP-1 --on + +# Show all monitors +wlr-randr +``` + +--- + +## Screen Scale + +### Without Global Scale (Recommended) + +- If you do not use XWayland apps, you can use monitor rules or `wlr-randr` to set a global monitor scale. +- If you are using XWayland apps, it is not recommended to set a global monitor scale. + +You can set scale like this, for example with a 1.4 factor. + +**Dependencies:** + +```bash +yay -S xorg-xrdb +yay -S xwayland-satellite +``` + +**In config file:** + +```ini +env=QT_AUTO_SCREEN_SCALE_FACTOR,1 +env=QT_WAYLAND_FORCE_DPI,140 +``` + +**In autostart:** + +```bash +echo "Xft.dpi: 140" | xrdb -merge +gsettings set org.gnome.desktop.interface text-scaling-factor 1.4 +``` + +**Edit autostart for XWayland:** + +```bash +# Start xwayland +/usr/sbin/xwayland-satellite :11 & +# Apply scale 1.4 for xwayland +sleep 0.5s && echo "Xft.dpi: 140" | xrdb -merge +``` + +### Using xwayland-satellite to Prevent Blurry XWayland Apps + +If you use fractional scaling, you can use `xwayland-satellite` to automatically scale XWayland apps to prevent blurriness, for example with a scale of 1.4. + +**Dependencies:** + +```bash +yay -S xwayland-satellite +``` + +**In config file:** + +```ini +env=DISPLAY,:2 +exec-once=xwayland-satellite :2 +monitorrule=name:eDP-1,width:1920,height:1080,refresh:60,x:0,y:0,scale:1.4,vrr:0,rr:0 +``` + +> **Warning:** Use a `DISPLAY` value other than `:1` to avoid conflicting with mangowm. + +--- + +## Virtual Monitors + +You can create and manage virtual displays through IPC commands: + +```bash +# Create virtual output +mmsg -d create_virtual_output + +# Destroy all virtual outputs +mmsg -d destroy_all_virtual_output +``` + +You can configure virtual monitors using `wlr-randr`: + +```bash +# Show all monitors +wlr-randr + +# Configure virtual monitor +wlr-randr --output HEADLESS-1 --pos 1921,0 --scale 1 --custom-mode 1920x1080@60Hz +``` + +Virtual monitors can be used for screen sharing with tools like [Sunshine](https://github.com/LizardByte/Sunshine) and [Moonlight](https://github.com/moonlight-stream/moonlight-android), allowing other devices to act as extended monitors. \ No newline at end of file diff --git a/docs/v0.13.1/configuration/xdg-portals.md b/docs/v0.13.1/configuration/xdg-portals.md new file mode 100644 index 00000000..27819ad8 --- /dev/null +++ b/docs/v0.13.1/configuration/xdg-portals.md @@ -0,0 +1,76 @@ +--- +title: XDG Portals +description: Set up screen sharing, clipboard, keyring, and file pickers using XDG portals. +--- + +## Portal Configuration + +You can customize portal settings via the following paths: + +- **User Configuration (Priority):** `~/.config/xdg-desktop-portal/mango-portals.conf` +- **System Fallback:** `/usr/share/xdg-desktop-portal/mango-portals.conf` + +> **Warning:** If you previously added `dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP=wlroots` to your config, remove it. Mango now handles this automatically. + +## Screen Sharing + +To enable screen sharing (OBS, Discord, WebRTC), you need `xdg-desktop-portal-wlr`. + +1. **Install Dependencies** + + `pipewire`, `pipewire-pulse`, `xdg-desktop-portal-wlr` + +2. **Optional: Add to autostart** + + In some situations the portal may not start automatically. You can add this to your autostart script to ensure it launches: + + ```bash + /usr/lib/xdg-desktop-portal-wlr & + ``` + +3. **Restart your computer** to apply changes. + +### Known Issues + +- **Window screen sharing:** Some applications may have issues sharing individual windows. See [#184](https://github.com/mangowm/mango/pull/184) for workarounds. + +- **Screen recording lag:** If you experience stuttering during screen recording, see [xdg-desktop-portal-wlr#351](https://github.com/emersion/xdg-desktop-portal-wlr/issues/351). + +## Clipboard Manager + +Use `cliphist` to manage clipboard history. + +**Dependencies:** `wl-clipboard`, `cliphist`, `wl-clip-persist` + +**Autostart Config:** + +```bash +# Keep clipboard content after app closes +wl-clip-persist --clipboard regular --reconnect-tries 0 & + +# Watch clipboard and store history +wl-paste --type text --watch cliphist store & +``` + +## GNOME Keyring + +If you need to store passwords or secrets (e.g., for VS Code or Minecraft launchers), install `gnome-keyring`. + +**Configuration:** + +Add the following to `~/.config/xdg-desktop-portal/mango-portals.conf`: + +```ini +[preferred] +default=gtk +org.freedesktop.impl.portal.ScreenCast=wlr +org.freedesktop.impl.portal.Screenshot=wlr +org.freedesktop.impl.portal.Secret=gnome-keyring +org.freedesktop.impl.portal.Inhibit=none +``` + +## File Picker (File Selector) + +**Dependencies:** `xdg-desktop-portal`, `xdg-desktop-portal-gtk` + +Reboot your computer once to apply. \ No newline at end of file diff --git a/docs/v0.13.1/faq.md b/docs/v0.13.1/faq.md new file mode 100644 index 00000000..9c9288de --- /dev/null +++ b/docs/v0.13.1/faq.md @@ -0,0 +1,100 @@ +--- +title: FAQ +description: Frequently asked questions and troubleshooting. +--- + +### How do I arrange tiled windows with my mouse? + +You can enable the `drag_tile_to_tile` option in your config. This allows you to drag a tiled window onto another to swap them. + +```ini +drag_tile_to_tile=1 +``` + +--- + +### Why is my background blurry or why does blur look wrong? + +Blur applies to the transparent areas of windows. To disable it entirely, set `blur=0`. + +If you are experiencing **performance issues with blur**, make sure `blur_optimized=1` (the default). This caches the wallpaper as the blur background, which is much cheaper on the GPU: + +```ini +blur_optimized=1 +``` + +--- + +### Blur shows my wallpaper instead of the real background content + +This is expected behavior when `blur_optimized=1` (the default). The optimizer caches the wallpaper to reduce GPU load — windows will blur against the wallpaper rather than the actual content stacked beneath them. + +If you want blur to composite against the true background (i.e., show whatever is actually behind the window), set: + +```ini +blur_optimized=0 +``` + +> **Warning:** Disabling `blur_optimized` significantly increases GPU consumption and may cause rendering lag, especially on lower-end hardware. + +--- + +### My games are lagging or stuttering + +Try enabling **SyncObj** timeline support. + +```ini +syncobj_enable=1 +``` + +--- + +### My games have high input latency + +You can enable **Tearing** (similar to VSync off). + +First, enable it globally: + +```ini +allow_tearing=1 +``` + +Then force it for your specific game: + +```ini +windowrule=force_tearing:1,title:Counter-Strike 2 +``` + +> **Warning:** Some graphics cards require setting `WLR_DRM_NO_ATOMIC=1` before mango starts for tearing to work. Add it to `/etc/environment` and reboot, or launch mango with `WLR_DRM_NO_ATOMIC=1 mango`. See [Monitors — Tearing](/docs/configuration/monitors#tearing-game-mode) for details. + +--- + +### How do I use pipes `|` in spawn commands? + +The standard `spawn` command does not support shell pipes directly. You must use `spawn_shell` instead. + +```ini +bind=SUPER,P,spawn_shell,echo "hello" | rofi -dmenu +``` + +--- + +### Certain key combinations do not work on my keyboard layout. + +`bind` automatically converts keysym to keycode, which is compatible with most layouts but can sometimes be imprecise. If a key combination is not triggering, use the **keycode** directly instead of the key name. + +Run `wev` and press the key to find its keycode, then use it in your bind: + +```ini +# Instead of: +bind=ALT,q,killclient + +# Use the keycode (e.g., code:24 = q on most layouts): +bind=ALT,code:24,killclient +``` + +You can also use `binds` (the `s` flag) to match by keysym instead of keycode: + +```ini +binds=ALT,q,killclient +``` diff --git a/docs/v0.13.1/index.md b/docs/v0.13.1/index.md new file mode 100644 index 00000000..d308370d --- /dev/null +++ b/docs/v0.13.1/index.md @@ -0,0 +1,42 @@ +--- +title: Introduction +description: A lightweight and feature-rich Wayland compositor based on dwl. +--- + + +**mango** is a Wayland compositor based on [dwl](https://codeberg.org/dwl/dwl/). It aims to be as lightweight as `dwl` and can be built completely within a few seconds, without compromising on functionality. + +> **Philosophy:** **Lightweight & Fast**: mango is designed to be minimal yet functional. It compiles in seconds and offers a robust set of features out of the box. + +## Feature Highlights + +Beyond basic window management, mangowm provides a rich set of features designed for a modern Wayland experience. + +- **[Animations](/docs/visuals/animations)** — Smooth, customizable animations for opening, moving, closing windows and tag switching. +- **[Layouts](/docs/window-management/layouts)** — Supports Scroller, Master-Stack, Monocle, Grid, Deck, and more, with per-tag layouts. +- **[Visual Effects](/docs/visuals/effects)** — Built-in blur, shadows, corner radius, and opacity effects powered by scenefx. +- **[IPC & Scripting](/docs/ipc)** — Control the compositor externally with robust IPC support for custom scripts and widgets. + +## Additional Features + +- **XWayland Support** — Excellent compatibility for legacy X11 applications. +- **Tag System** — Uses tags instead of workspaces, allowing separate window layouts for each tag. +- **Input Methods** — Great support for text input v2/v3 (Fcitx5, IBus). +- **Window States** — Rich states including swallow, minimize, maximize, fullscreen, and overlay. +- **Hot-Reload Config** — Simple external configuration that supports hot-reloading without restarting. +- **Scratchpads** — Support for both Sway-like and named scratchpads. + +## Community + +- **[Join the mangowm Discord](https://discord.gg/CPjbDxesh5)** — Chat with the community, get support, share your setup, and stay updated with the latest mangowm news. +- **[Join the GitHub Discussions](https://github.com/mangowm/mango/discussions)** — Ask questions, request features, report issues, or share ideas directly with contributors and other users. + +## Acknowledgements + +This project is built upon the hard work of several open-source projects: + +- **[wlroots](https://gitlab.freedesktop.org/wlroots/wlroots)** — Implementation of the Wayland protocol. +- **[mwc](https://github.com/nikoloc/mwc)** — Basal window animation reference. +- **[dwl](https://codeberg.org/dwl/dwl)** — Basal dwl features. +- **[sway](https://github.com/swaywm/sway)** — Sample implementation of the Wayland protocol. +- **[scenefx](https://github.com/wlrfx/scenefx)** — Library to simplify adding window effects. diff --git a/docs/v0.13.1/installation.md b/docs/v0.13.1/installation.md new file mode 100644 index 00000000..c5d4936c --- /dev/null +++ b/docs/v0.13.1/installation.md @@ -0,0 +1,308 @@ +--- +title: Installation +description: Install mangowm on AerynOS, Arch, Fedora, Gentoo, Guix System, NixOS, PikaOS, or build from source. +--- + +## Package Installation + +mangowm is available as a pre-built package on several distributions. Choose your distribution below. + +--- + +### AerynOS + +mangowm is available in the **AerynOS package repository**. + +You can install it using the `moss` package manager: + +```bash +sudo moss install mangowm +``` + +--- + +### Arch Linux + +mangowm is available in the **Arch User Repository (AUR)**. + +You can install it using an AUR helper like `yay` or `paru`: + +```bash +yay -S mangowm-git +``` + +> **Tip:** This package pulls the latest git version, ensuring you have the newest features and fixes. + +--- + +### Fedora + +The package is in the third-party **Terra repository**. First, add the Terra Repository. + +> **Warning:** Both commands require root privileges. Use `sudo` if needed. + +```bash +dnf install --nogpgcheck --repofrompath 'terra,https://repos.fyralabs.com/terra$releasever' terra-release +``` + +Then, install the package: + +```bash +dnf install mangowm +``` + +--- + +### Gentoo + +The package is hosted in the community-maintained **GURU** repository. + +1. **Add the GURU repository** + + ```bash + emerge --ask --verbose eselect-repository + eselect repository enable guru + emerge --sync guru + ``` + +2. **Unmask packages** + Add the required packages to your `package.accept_keywords` file: + - `gui-libs/scenefx` + - `gui-wm/mangowm` + +3. **Install mango** + ```bash + emerge --ask --verbose gui-wm/mangowm + ``` + +--- + +### Guix System + +The package definition is described in the source repository. + +1. **Add mango channel** + Add to `$HOME/.config/guix/channels.scm`: + + ```scheme + (cons (channel + (name 'mangowm) + (url "https://github.com/mangowm/mango.git") + (branch "main")) + %default-channels) + ``` + +2. **Install** + After running `guix pull`, you can install mangowm: + + ```bash + guix install mangowm + ``` + + Or add it to your system configuration using the mangowm module: + + ```scheme + (use-modules (mangowm)) + + (packages (cons* + mangowm-git + ... ;; Other packages + %base-packages)) + ``` + +> **Tip:** For more information, see the [Guix System documentation](https://guix.gnu.org/manual/devel/en/html_node/Channels.html). + +--- + +### NixOS + +The repository provides a Flake with a NixOS module. + +1. **Add flake input** + + ```nix + # flake.nix + { + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + mangowm = { + url = "github:mangowm/mango"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + # other inputs ... + }; + } + ``` + +2. **Import the NixOS module** + + **Option A — Import in `configuration.nix`:** + + ```nix + # configuration.nix (or any other file that you import) + {inputs, ...}: { + imports = [ + inputs.mangowm.nixosModules.mango + # .. other imports ... + ]; + + # ... + } + ``` + + **Option B — Import directly in flake:** + + ```nix + # flake.nix + { + # ... + + outputs = { self, nixpkgs, mangowm, ...}@inputs: let + inherit (nixpkgs) lib; + # ... + in { + nixosConfigurations.YourHostName = lib.nixosSystem { + modules = [ + mangowm.nixosModules.mango # or inputs.mangowm.nixosModules.mango + # other imports ... + ]; + }; + } + } + ``` + +3. **Enable the module** + + ```nix + # configuration.nix (or any other file that you import) + { + programs.mango.enable = true; + } + ``` + +4. **Start mango on login** + + The following are common examples. Refer to the official NixOS documentation for full configuration options. + + **Option A — greetd:** Autologin on first start; login screen after logout. + + ```nix + services.greetd = { + enable = true; + settings = { + initial_session = { + command = "mango"; + user = "your-username"; # auto-login on first start, no password required + }; + default_session = { + command = "${pkgs.greetd.tuigreet}/bin/tuigreet --cmd mango"; + user = "greeter"; + }; + }; + }; + ``` + + **Option B — Display manager autologin:** Autologin via an existing display manager (e.g. SDDM, GDM). [`addLoginEntry`](/docs/nix-options#addloginentry) (default: `true`) automatically registers mango as a session. + + ```nix + services.displayManager = { + defaultSession = "mango"; # derived from mango.desktop filename + autoLogin = { + enable = true; + user = "your-username"; + }; + }; + ``` + + **Option C — getty autologin:** No login screen, boots directly into mango on TTY1. + + For bash/zsh: + + ```nix + services.getty.autologinUser = "your-username"; + + environment.loginShellInit = '' + [ "$(tty)" = /dev/tty1 ] && exec mango + ''; + ``` + + For fish: + + ```nix + services.getty.autologinUser = "your-username"; + + programs.fish.loginShellInit = '' + if test (tty) = /dev/tty1 + exec mango + end + ''; + ``` + +5. **All available options** + + See [Nix Module Options](/docs/nix-options) for the full list of NixOS and Home Manager options. + +--- + +### PikaOS + +mangowm is available in the **PikaOS package repository**. + +You can install it using the `pikman` package manager: + +```bash +pikman install mangowm +``` + +--- + +## Building from Source + +If your distribution isn't listed above, or you want the latest unreleased changes, you can build mangowm from source. + +> **Info:** Ensure the following dependencies are installed before proceeding: +> +> - `wayland` +> - `wayland-protocols` +> - `libinput` +> - `libdrm` +> - `libxkbcommon` +> - `pixman` +> - `libdisplay-info` +> - `libliftoff` +> - `hwdata` +> - `seatd` +> - `pcre2` +> - `xorg-xwayland` +> - `libxcb` + +You will need to build `wlroots` and `scenefx` manually as well. + +1. **Build wlroots** + Clone and install the specific version required (check README for latest version). + + ```bash + git clone -b 0.19.3 https://gitlab.freedesktop.org/wlroots/wlroots.git + cd wlroots + meson build -Dprefix=/usr + sudo ninja -C build install + ``` + +2. **Build scenefx** + This library handles the visual effects. + + ```bash + git clone -b 0.4.1 https://github.com/wlrfx/scenefx.git + cd scenefx + meson build -Dprefix=/usr + sudo ninja -C build install + ``` + +3. **Build mangowm** + Finally, compile the compositor itself. + ```bash + git clone https://github.com/mangowm/mango.git + cd mango + meson build -Dprefix=/usr + sudo ninja -C build install + ``` diff --git a/docs/v0.13.1/ipc.md b/docs/v0.13.1/ipc.md new file mode 100644 index 00000000..e3ec4a32 --- /dev/null +++ b/docs/v0.13.1/ipc.md @@ -0,0 +1,154 @@ +--- +title: IPC +description: Control mangowm programmatically using mmsg. +--- + +## Introduction + +mangowm includes a powerful IPC (Inter-Process Communication) tool called `mmsg`. This allows you to query the window manager's state, watch for events, and execute commands from external scripts. + +## Basic Usage + +The general syntax for `mmsg` is: + +```bash +mmsg [-OTLq] +mmsg [-o <output>] -s [-t <tags>] [-l <layout>] [-c <tags>] [-d <cmd>,<arg1>,<arg2>,<arg3>,<arg4>,<arg5>] +mmsg [-o <output>] (-g | -w) [-OotlcvmfxekbA] +``` + +### Options + +| Flag | Description | +| :--- | :--- | +| `-q` | Quit mangowm. | +| `-g` | **Get** values (tags, layout, focused client). | +| `-s` | **Set** values (switch tags, layouts). | +| `-w` | **Watch** mode (streams events). | +| `-O` | Get all output (monitor) information. | +| `-T` | Get number of tags. | +| `-L` | Get all available layouts. | +| `-o` | Select output (monitor). | +| `-t` | Get/set selected tags (set with `[+-^.]`). | +| `-l` | Get/set current layout. | +| `-c` | Get title and appid of focused client. | +| `-v` | Get visibility of statusbar. | +| `-m` | Get fullscreen status. | +| `-f` | Get floating status. | +| `-d` | **Dispatch** an internal command. | +| `-x` | Get focused client geometry. | +| `-e` | Get the name of the last focused layer. | +| `-k` | Get current keyboard layout. | +| `-b` | Get current keybind mode. | +| `-A` | Get scale factor of monitor. | + +## Examples + +### Tag Management + +You can perform arithmetic on tags using the `-t` flag with `-s` (set). + +```bash +# Switch to Tag 1 +mmsg -t 1 + +# Add Tag 2 to current view (Multiview) +mmsg -s -t 2+ + +# Remove Tag 2 from current view +mmsg -s -t 2- + +# Toggle Tag 2 +mmsg -s -t 2^ +``` + +### Layouts + +Switch layouts programmatically. Layout codes: `S` (Scroller), `T` (Tile), `G` (Grid), `M` (Monocle), `K` (Deck), `CT` (Center Tile), `RT` (Right Tile), `VS` (Vertical Scroller), `VT` (Vertical Tile), `VG` (Vertical Grid), `VK` (Vertical Deck), `DW` (Dwindle), `F` (Fair), `VF` (Vertical Fair). + +```bash +# Switch to Scroller +mmsg -l "S" + +# Switch to Tile +mmsg -l "T" +``` + +### Dispatching Commands + +Any command available in `config.conf` keybindings can be run via IPC. + +```bash +# Close the focused window +mmsg -d killclient + +# Resize window by +10 width +mmsg -d resizewin,+10,0 + +# Toggle fullscreen +mmsg -d togglefullscreen + +# Disable a monitor power +mmsg -d disable_monitor,eDP-1 +``` + +### Monitoring & Status + +Use `-g` or `-w` to build custom status bars or automation scripts. + +```bash +# Watch for all message changes +mmsg -w + +# Get all messages without watch +mmsg -g + +# Watch focused client appid and title +mmsg -w -c + +# Get all available outputs +mmsg -O + +# Get all tags message +mmsg -g -t + +# Get current focused client message +mmsg -g -c + +# Get current keyboard layout +mmsg -g -k + +# Get current keybind mode +mmsg -g -b + +# Get scale factor of current monitor +mmsg -g -A +``` + +#### Tag Message Format + +- State: 0 → none, 1 → active, 2 → urgent + +Example output: + +| Monitor | Tag Number | Tag State | Clients in Tag | Focused Client | +|---------|------------|-----------|----------------|----------------| +| eDP-1 | tag 2 | 0 | 1 | 0 | + +| Monitor | occupied tags mask | active tags mask | urgent tags mask | +|---------|--------------------|------------------|------------------| +| eDP-1 | 14 | 6 | 0 | + +## Virtual Monitors + +You can create headless outputs for screen mirroring or remote desktop access (e.g., Sunshine/Moonlight). + +```bash +# Create a virtual output +mmsg -d create_virtual_output + +# Configure it (set resolution) +wlr-randr --output HEADLESS-1 --pos 1920,0 --mode 1920x1080@60Hz + +# Destroy all virtual outputs +mmsg -d destroy_all_virtual_output \ No newline at end of file diff --git a/docs/v0.13.1/meta.json b/docs/v0.13.1/meta.json new file mode 100644 index 00000000..a1f41d47 --- /dev/null +++ b/docs/v0.13.1/meta.json @@ -0,0 +1,22 @@ +{ + "title": "v0.13.1", + "description": "v0.13.1 release", + "root": true, + "pages": [ + "---Getting Started---", + "index", + "installation", + "quick-start", + "---Configuration---", + "configuration", + "visuals", + "window-management", + "bindings", + "---Examples---", + "screenshot", + "---Reference---", + "nix-options", + "ipc", + "faq" + ] +} diff --git a/docs/v0.13.1/nix-options.md b/docs/v0.13.1/nix-options.md new file mode 100644 index 00000000..2537d9d8 --- /dev/null +++ b/docs/v0.13.1/nix-options.md @@ -0,0 +1,519 @@ +--- +title: Nix Module Options +description: NixOS and Home Manager configuration options for mangowm. +--- + +> **Note:** This document is automatically generated from the Nix module source code. + +## NixOS + +**System-level options via `programs.mango`.** + +### enable + + + +Whether to enable mango, a wayland compositor based on dwl\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +false +``` + + + +*Example:* + +```nix +true +``` + +*Declared by:* + - [\<mango/nix/nixos-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/nixos-modules.nix) + + + +### package + + + +The mango package to use + + + +*Type:* +package + + + +*Default:* + +```nix +<derivation mango-nightly> +``` + +*Declared by:* + - [\<mango/nix/nixos-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/nixos-modules.nix) + + + +### addLoginEntry + + + +Whether to add a login entry to the display manager for mango\. Only has effect if a display manager is configured (e\.g\. SDDM, GDM via ` services.displayManager `)\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +true +``` + +*Declared by:* + - [\<mango/nix/nixos-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/nixos-modules.nix) + +## Home Manager + +**Configure mangowm declaratively via `wayland.windowManager.mango`.** + +### enable + + + +Whether to enable mangowm, a Wayland compositor based on dwl\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +false +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### package + + + +The mango package to use + + + +*Type:* +package + + + +*Default:* + +```nix +<derivation mango-nightly> +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### autostart_sh + + + +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:* +strings concatenated with “\\n” + + + +*Default:* + +```nix +"" +``` + + + +*Example:* + +```nix +'' + waybar & + dunst & +'' +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### bottomPrefixes + + + +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\. + + + +*Type:* +list of string + + + +*Default:* + +```nix +[ ] +``` + + + +*Example:* + +```nix +[ + "source" +] +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### extraConfig + + + +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\. + + + +*Type:* +strings concatenated with “\\n” + + + +*Default:* + +```nix +"" +``` + + + +*Example:* + +```nix +'' + # Advanced config that doesn't fit structured format + special_option = 1 +'' +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### settings + + + +Mango configuration written in Nix\. Entries with the same key +should be written as lists\. Variables and colors names should be +quoted\. See [https://mangowm\.github\.io/docs](https://mangowm\.github\.io/docs) 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 ` + +Keymodes (submaps) are supported via the special ` keymode ` attribute\. Each keymode +is a nested attribute set under ` keymode ` that contains its own bindings\. + + + +*Type:* +Mango configuration value + + + +*Default:* + +```nix +{ } +``` + + + +*Example:* + +```nix +{ + # 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" + "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" + ]; + }; + }; +} + +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### systemd\.enable + + + +Whether to enable ` mango-session.target ` on +mango startup\. This links to +` graphical-session.target `\. +Some important environment variables will be imported to systemd +and dbus user environment before reaching the target, including + + - ` DISPLAY ` + - ` WAYLAND_DISPLAY ` + - ` XDG_CURRENT_DESKTOP ` + - ` XDG_SESSION_TYPE ` + - ` NIXOS_OZONE_WL ` + You can extend this list using the ` systemd.variables ` option\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +true +``` + + + +*Example:* + +```nix +false +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### systemd\.extraCommands + + + +Extra commands to run after D-Bus activation\. + + + +*Type:* +list of string + + + +*Default:* + +```nix +[ + "systemctl --user reset-failed" + "systemctl --user start mango-session.target" +] +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### systemd\.variables + + + +Environment variables imported into the systemd and D-Bus user environment\. + + + +*Type:* +list of string + + + +*Default:* + +```nix +[ + "DISPLAY" + "WAYLAND_DISPLAY" + "XDG_CURRENT_DESKTOP" + "XDG_SESSION_TYPE" + "NIXOS_OZONE_WL" + "XCURSOR_THEME" + "XCURSOR_SIZE" +] +``` + + + +*Example:* + +```nix +[ + "--all" +] +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### systemd\.xdgAutostart + + + +Whether to enable autostart of applications using +` systemd-xdg-autostart-generator(8) ` +\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +false +``` + + + +*Example:* + +```nix +true +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### topPrefixes + + + +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\. + + + +*Type:* +list of string + + + +*Default:* + +```nix +[ ] +``` + + + +*Example:* + +```nix +[ + "source" +] +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + diff --git a/docs/v0.13.1/quick-start.md b/docs/v0.13.1/quick-start.md new file mode 100644 index 00000000..bc192474 --- /dev/null +++ b/docs/v0.13.1/quick-start.md @@ -0,0 +1,88 @@ +--- +title: Quick Start +description: Basic configuration and first steps with mangowm. +--- + +Now that you have mangowm installed, let's get your environment set up. + +## Initial Setup + +1. **Create Configuration Directory** + + mangowm looks for configuration files in `~/.config/mango/`. + + ```bash + mkdir -p ~/.config/mango + ``` + +2. **Copy Default Config** + + A default configuration file is provided at `/etc/mango/config.conf`. Copy it to your local directory to start customizing. + + ```bash + cp /etc/mango/config.conf ~/.config/mango/config.conf + ``` + +3. **Launch mangowm** + + You can now start the compositor from your TTY. + + ```bash + mango + ``` + + Optional: To specify a custom config file path: + + ```bash + mango -c /path/to/your/config.conf + ``` + +## Essential Keybindings + +mangowm uses the following keybinds by default: + +| Key Combination | Action | +| :--- | :--- | +| `Alt` + `Return` | Open Terminal (defaults to `foot`) | +| `Alt` + `Space` | Open Launcher (defaults to `rofi`) | +| `Alt` + `Q` | Close (Kill) the active window | +| `Super` + `M` | Quit mangowm | +| `Super` + `F` | Toggle Fullscreen | +| `Alt` + `Arrow Keys` | Move focus (Left, Right, Up, Down) | +| `Ctrl` + `1-9` | Switch to Tag 1-9 | +| `Alt` + `1-9` | Move window to Tag 1-9 | + +> **Warning:** Some default bindings rely on specific tools like `foot` (terminal) and `rofi` (launcher). Ensure you have them installed or update your `config.conf` to use your preferred alternatives. + +## Recommended Tools + +To get a fully functional desktop experience, we recommend installing the following components: + +| Category | Recommended Tools | +| :--- | :--- | +| Application Launcher | rofi, bemenu, wmenu, fuzzel | +| Terminal Emulator | foot, wezterm, alacritty, kitty, ghostty | +| Status Bar | waybar, eww, quickshell, ags | +| Desktop Shell | Noctalia, DankMaterialShell | +| Wallpaper Setup | awww(swww), swaybg | +| Notification Daemon | swaync, dunst, mako | +| Desktop Portal | xdg-desktop-portal, xdg-desktop-portal-wlr, xdg-desktop-portal-gtk | +| Clipboard | wl-clipboard, wl-clip-persist, cliphist | +| Gamma Control / Night Light | wlsunset, gammastep | +| Miscellaneous | xfce-polkit, wlogout | + +## Example Configuration + +Check out the [example configuration](https://github.com/DreamMaoMao/mango-config) by the creator of mangowm, including complete setups for mangowm, Waybar, Rofi, and more. + +```bash +git clone https://github.com/DreamMaoMao/mango-config.git ~/.config/mango +``` + +## Next Steps + +Now that you are up and running, dive deeper into customizing mangowm: + +- [Configure Monitors](/docs/configuration/monitors) — Set up resolution, scaling, and multi-monitor layouts. +- [Window Rules](/docs/window-management/rules#window-rules) — Define how specific applications should behave. +- [Appearance](/docs/visuals/theming) — Customize colors, borders, gaps, and effects. diff --git a/docs/v0.13.1/screenshot.md b/docs/v0.13.1/screenshot.md new file mode 100644 index 00000000..f07cdf0c --- /dev/null +++ b/docs/v0.13.1/screenshot.md @@ -0,0 +1,213 @@ +--- + +title: Screenshots +description: Example screenshot keybindings and capture workflows for mangowm. + +--- + +mangowm does not include a built-in screenshot tool. This keeps the compositor lean. +Instead, compose your own workflow from small Wayland utilities and bind them to keys; + +| Tool | Purpose | +| :--- | :--- | +| [`grim`](https://github.com/emersion/grim) | Capture the screen or a region to a file | +| [`slurp`](https://github.com/emersion/slurp) | Interactively select a region for `grim` | +| [`wl-copy`](https://github.com/bugaevc/wl-clipboard) | Copy screenshots directly to the clipboard | +| [`satty`](https://github.com/gabm/Satty) | Annotate screenshots before saving | +| [`wayfreeze`](https://github.com/nicbk/wayfreeze) | Freeze the screen before capture | + +Install the required with your package manager or from source. + +`grim` writes to the file path you give it, but **will not create missing directories**. Create one first: + +```bash +mkdir -p ~/Pictures/Screenshots +``` + +Any directory works. `~/Pictures/Screenshots/` is just a convention. + +## Quick Binds + +Short, single-step commands can be placed directly in `config.conf` with `spawn_shell`. +No script file needed. + +### Fullscreen + +Captures the entire display. + +```ini +bind=NONE,Print,spawn_shell,grim $HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png +``` + +### Region + +Select an area with `slurp` before capturing. + +```ini +bind=SHIFT,Print,spawn_shell,g=$(slurp -d) && [ -n "$g" ] && grim -g "$g" $HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png +``` + +### Pointer + +Captures the full screen including the cursor. + +```ini +bind=ALT,Print,spawn_shell,grim -c $HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png +``` + +### Clipboard + +Captures to a temporary file and copies the image to the clipboard; no file is saved. + +```ini +bind=CTRL,Print,spawn_shell,f=$(mktemp -t screenshot-XXXXXX.png) && grim "$f" && wl-copy < "$f" && rm -f "$f" +``` + +### Annotate + +Captures and opens `satty` for drawing before saving. + +```ini +bind=SUPER,Print,spawn_shell,f=$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png && grim "$f" && satty --filename "$f" --output-filename "$f" --actions-on-enter save-to-file --early-exit +``` + +## Script Binds + +When a command involves multi-step logic, geometry parsing, FIFOs, or screen freezing, +move it into a script and invoke it with `spawn` instead of `spawn_shell`. + +Create the scripts directory first: + +```bash +mkdir -p ~/.config/mango/scripts/screenshot +``` + +### Window + +Uses `mmsg` (ships with mango) to capture the focused window. + +`~/.config/mango/scripts/screenshot/window.sh`: + +```bash +#!/usr/bin/env bash +geometry=$(mmsg -x | awk '/x / {x=$3} /y / {y=$3} /width / {w=$3} /height / {h=$3} END {print x","y" "w"x"h}') +[ -z "$geometry" ] && exit 1 +grim -g "$geometry" "$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png" +``` + +```ini +bind=CTRL+SHIFT,Print,spawn,$HOME/.config/mango/scripts/screenshot/window.sh +``` + +### Freeze + +Freezes the screen with `wayfreeze` before capturing. + +`~/.config/mango/scripts/screenshot/freeze.sh`: + +```bash +#!/usr/bin/env bash +pipe=$(mktemp -u).fifo +mkfifo "$pipe" +wayfreeze --after-freeze-timeout 100 --after-freeze-cmd "echo > $pipe" & +wayfreeze_pid=$! +read -r < "$pipe" +grim "$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png" +kill "$wayfreeze_pid" 2>/dev/null +rm -f "$pipe" +``` + +```ini +bind=CTRL+SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/freeze.sh +``` + +### Freeze + Region + +Freeze, then select a region with `slurp`. Cleans up on cancel. + +`~/.config/mango/scripts/screenshot/freeze-region.sh`: + +```bash +#!/usr/bin/env bash +pipe=$(mktemp -u).fifo +mkfifo "$pipe" +wayfreeze --after-freeze-timeout 100 --after-freeze-cmd "echo > $pipe" & +wayfreeze_pid=$! +read -r < "$pipe" +geometry=$(slurp -d) +if [[ -z "$geometry" ]]; then + kill "$wayfreeze_pid" 2>/dev/null + rm -f "$pipe" + exit 1 +fi +grim -g "$geometry" "$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png" +kill "$wayfreeze_pid" 2>/dev/null +rm -f "$pipe" +``` + +```ini +bind=SHIFT+SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/freeze-region.sh +``` + +Make all three scripts executable: + +```bash +chmod +x ~/.config/mango/scripts/screenshot/*.sh +``` + +## All-in-One Script + +Prefer fewer files? A single script with subcommands covers every mode above. +Place it in the same directory and use it in place of the individual scripts. + +`~/.config/mango/scripts/screenshot/screenshot.sh`: + +```bash +#!/usr/bin/env bash +set -euo pipefail +mkdir -p "$HOME/Pictures/Screenshots" +filepath="$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png" + +case "${1:-fullscreen}" in + region) + g=$(slurp -d); [ -z "$g" ] && exit 1 + grim -g "$g" "$filepath" ;; + window) + g=$(mmsg -x | awk '/x / {x=$3} /y / {y=$3} /width / {w=$3} /height / {h=$3} END {print x","y" "w"x"h}') + [ -z "$g" ] && exit 1 + grim -g "$g" "$filepath" ;; + freeze) + p=$(mktemp -u).fifo; mkfifo "$p" + wayfreeze --after-freeze-timeout 100 --after-freeze-cmd "echo > $p" & wp=$! + read -r < "$p"; grim "$filepath" + kill "$wp" 2>/dev/null; rm -f "$p" ;; + freeze-region) + p=$(mktemp -u).fifo; mkfifo "$p" + wayfreeze --after-freeze-timeout 100 --after-freeze-cmd "echo > $p" & wp=$! + read -r < "$p"; g=$(slurp -d) + if [ -z "$g" ]; then kill "$wp" 2>/dev/null; rm -f "$p"; exit 1; fi + grim -g "$g" "$filepath" + kill "$wp" 2>/dev/null; rm -f "$p" ;; + annotate) + grim "$filepath"; satty --filename "$filepath" --output-filename "$filepath" --actions-on-enter save-to-file --early-exit ;; + *) grim "$filepath" ;; +esac +``` + +Make the script executable: + + +```bash +chmod +x ~/.config/mango/scripts/screenshot/screenshot.sh +``` + +Then add the binds to `config.conf`: + +```ini +bind=NONE,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh fullscreen +bind=SHIFT,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh region +bind=CTRL+SHIFT,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh window +bind=CTRL+SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh freeze +bind=SHIFT+SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh freeze-region +bind=SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh annotate +``` diff --git a/docs/v0.13.1/visuals/animations.md b/docs/v0.13.1/visuals/animations.md new file mode 100644 index 00000000..76477e05 --- /dev/null +++ b/docs/v0.13.1/visuals/animations.md @@ -0,0 +1,108 @@ +--- +title: Animations +description: Configure smooth transitions for windows and layers. +--- + +## Enabling Animations + +mangowm supports animations for both standard windows and layer shell surfaces (like bars and notifications). + +```ini +animations=1 +layer_animations=1 +``` + +## Animation Types + +You can define different animation styles for opening and closing windows and layer surfaces. + +Available types: `slide`, `zoom`, `fade`, `none`. + +```ini +animation_type_open=zoom +animation_type_close=slide +layer_animation_type_open=slide +layer_animation_type_close=slide +``` + +## Fade Settings + +Control the fade-in and fade-out effects for animations. + +```ini +animation_fade_in=1 +animation_fade_out=1 +fadein_begin_opacity=0.5 +fadeout_begin_opacity=0.5 +``` + +- `animation_fade_in` — Enable fade-in effect (0: disable, 1: enable) +- `animation_fade_out` — Enable fade-out effect (0: disable, 1: enable) +- `fadein_begin_opacity` — Starting opacity for fade-in animations (0.0–1.0) +- `fadeout_begin_opacity` — Starting opacity for fade-out animations (0.0–1.0) + +## Zoom Settings + +Adjust the zoom ratios for zoom animations. + +```ini +zoom_initial_ratio=0.4 +zoom_end_ratio=0.8 +``` + +- `zoom_initial_ratio` — Initial zoom ratio +- `zoom_end_ratio` — End zoom ratio + +## Durations + +Control the speed of animations (in milliseconds). + +| Setting | Type | Default | Description | +| :--- | :--- | :--- | :--- | +| `animation_duration_move` | integer | `500` | Move animation duration (ms) | +| `animation_duration_open` | integer | `400` | Open animation duration (ms) | +| `animation_duration_tag` | integer | `300` | Tag animation duration (ms) | +| `animation_duration_close` | integer | `300` | Close animation duration (ms) | +| `animation_duration_focus` | integer | `0` | Focus change (opacity transition) animation duration (ms) | + +```ini +animation_duration_move=500 +animation_duration_open=400 +animation_duration_tag=300 +animation_duration_close=300 +animation_duration_focus=0 +``` + +## Custom Bezier Curves + +Bezier curves determine the "feel" of an animation (e.g., linear vs. bouncy). The format is `x1,y1,x2,y2`. + +You can visualize and generate curve values using online tools like [cssportal.com](https://www.cssportal.com/css-cubic-bezier-generator/) or [easings.net](https://easings.net). + +| Setting | Type | Default | Description | +| :--- | :--- | :--- | :--- | +| `animation_curve_open` | string | `0.46,1.0,0.29,0.99` | Open animation bezier curve | +| `animation_curve_move` | string | `0.46,1.0,0.29,0.99` | Move animation bezier curve | +| `animation_curve_tag` | string | `0.46,1.0,0.29,0.99` | Tag animation bezier curve | +| `animation_curve_close` | string | `0.46,1.0,0.29,0.99` | Close animation bezier curve | +| `animation_curve_focus` | string | `0.46,1.0,0.29,0.99` | Focus change (opacity transition) animation bezier curve | +| `animation_curve_opafadein` | string | `0.46,1.0,0.29,0.99` | Open opacity animation bezier curve | +| `animation_curve_opafadeout` | string | `0.5,0.5,0.5,0.5` | Close opacity animation bezier curve | + +```ini +animation_curve_open=0.46,1.0,0.29,0.99 +animation_curve_move=0.46,1.0,0.29,0.99 +animation_curve_tag=0.46,1.0,0.29,0.99 +animation_curve_close=0.46,1.0,0.29,0.99 +animation_curve_focus=0.46,1.0,0.29,0.99 +animation_curve_opafadein=0.46,1.0,0.29,0.99 +animation_curve_opafadeout=0.5,0.5,0.5,0.5 +``` + +## Tag Animation Direction + +Control the direction of tag switch animations. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `tag_animation_direction` | `1` | Tag animation direction (1: horizontal, 0: vertical) | \ No newline at end of file diff --git a/docs/v0.13.1/visuals/effects.md b/docs/v0.13.1/visuals/effects.md new file mode 100644 index 00000000..23c1f206 --- /dev/null +++ b/docs/v0.13.1/visuals/effects.md @@ -0,0 +1,82 @@ +--- +title: Window Effects +description: Add visual polish with blur, shadows, and opacity. +--- + +## Blur + +Blur creates a frosted glass effect for transparent windows. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `blur` | `0` | Enable blur for windows. | +| `blur_layer` | `0` | Enable blur for layer surfaces (like bars/docks). | +| `blur_optimized` | `1` | Caches the wallpaper and blur background, significantly reducing GPU usage. Disabling it will significantly increase GPU consumption and may cause rendering lag. **Highly recommended.** | +| `blur_params_radius` | `5` | The strength (radius) of the blur. | +| `blur_params_num_passes` | `1` | Number of passes. Higher = smoother but more expensive. | +| `blur_params_noise` | `0.02` | Blur noise level. | +| `blur_params_brightness` | `0.9` | Blur brightness adjustment. | +| `blur_params_contrast` | `0.9` | Blur contrast adjustment. | +| `blur_params_saturation` | `1.2` | Blur saturation adjustment. | + +> **Warning:** Blur has a relatively high impact on performance. If your hardware is limited, it is not recommended to enable it. If you experience lag with blur on, ensure `blur_optimized=1` — disabling it will significantly increase GPU consumption and may cause rendering lag. To disable blur entirely, set `blur=0`. + +--- + +## Shadows + +Drop shadows help distinguish floating windows from the background. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `shadows` | `0` | Enable shadows. | +| `layer_shadows` | `0` | Enable shadows for layer surfaces. | +| `shadow_only_floating` | `1` | Only draw shadows for floating windows (saves performance). | +| `shadows_size` | `10` | Size of the shadow. | +| `shadows_blur` | `15` | Shadow blur amount. | +| `shadows_position_x` | `0` | Shadow X offset. | +| `shadows_position_y` | `0` | Shadow Y offset. | +| `shadowscolor` | `0x000000ff` | Color of the shadow. | + +```ini +# Example shadows configuration +shadows=1 +layer_shadows=1 +shadow_only_floating=1 +shadows_size=12 +shadows_blur=15 +shadows_position_x=0 +shadows_position_y=0 +shadowscolor=0x000000ff +``` + +--- + +## Opacity & Corner Radius + +Control the transparency and roundness of your windows. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `border_radius` | `0` | Window corner radius in pixels. | +| `border_radius_location_default` | `0` | Corner radius location: `0` (all), `1` (top-left), `2` (top-right), `3` (bottom-left), `4` (bottom-right), `5` (closest corner). | +| `no_radius_when_single` | `0` | Disable radius if only one window is visible. | +| `focused_opacity` | `1.0` | Opacity for the active window (0.0 - 1.0). | +| `unfocused_opacity` | `1.0` | Opacity for inactive windows (0.0 - 1.0). | + +```ini +# Window corner radius in pixels +border_radius=0 + +# Corner radius location (0=all, 1=top-left, 2=top-right, 3=bottom-left, 4=bottom-right) +border_radius_location_default=0 + +# Disable radius if only one window is visible +no_radius_when_single=0 + +# Opacity for the active window (0.0 - 1.0) +focused_opacity=1.0 + +# Opacity for inactive windows +unfocused_opacity=1.0 +``` diff --git a/docs/v0.13.1/visuals/index.mdx b/docs/v0.13.1/visuals/index.mdx new file mode 100644 index 00000000..f71ae2f8 --- /dev/null +++ b/docs/v0.13.1/visuals/index.mdx @@ -0,0 +1,19 @@ +--- +title: Visuals +description: Customize borders, colors, effects, and animations. +icon: Palette +--- + +Customize the look of your desktop. + +<Cards> + +<Card href="/docs/visuals/theming" title="Theming" description="Borders, colors, and cursor" /> + +<Card href="/docs/visuals/status-bar" title="Status Bar" description="Built-in status bar" /> + +<Card href="/docs/visuals/effects" title="Effects" description="Blur, shadows, rounded corners" /> + +<Card href="/docs/visuals/animations" title="Animations" description="Window and tag animations" /> + +</Cards> \ No newline at end of file diff --git a/docs/v0.13.1/visuals/meta.json b/docs/v0.13.1/visuals/meta.json new file mode 100644 index 00000000..58723c4e --- /dev/null +++ b/docs/v0.13.1/visuals/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Visuals", + "pages": ["theming", "status-bar", "effects", "animations"] +} diff --git a/docs/v0.13.1/visuals/status-bar.md b/docs/v0.13.1/visuals/status-bar.md new file mode 100644 index 00000000..f2924e83 --- /dev/null +++ b/docs/v0.13.1/visuals/status-bar.md @@ -0,0 +1,141 @@ +--- +title: Status Bar +description: Configure Waybar for mangowm. +--- + +## Module Configuration + +mangowm is compatible with Waybar's `ext/workspaces` module (Wayland standard) or the `dwl/tags` module. We recommend `ext/workspaces` for the best experience. + +> **Tip:** You can also use the `dwl/tags` module, but `ext/workspaces` provides better integration with mangowm's features. The `ext/workspaces` module requires **Waybar > 0.14.0**. + +### `config.jsonc` + +Add the following to your Waybar configuration: + +```jsonc +{ + "modules-left": [ + "ext/workspaces", + "dwl/window" + ], + "ext/workspaces": { + "format": "{icon}", + "ignore-hidden": true, + "on-click": "activate", + "on-click-right": "deactivate", + "sort-by-id": true + }, + "dwl/window": { + "format": "[{layout}] {title}" + } +} +``` + +## Styling + +You can style the tags using standard CSS in `style.css`. + +### `style.css` + +```css +#workspaces { + border-radius: 4px; + border-width: 2px; + border-style: solid; + border-color: #c9b890; + margin-left: 4px; + padding-left: 10px; + padding-right: 6px; + background: rgba(40, 40, 40, 0.76); +} + +#workspaces button { + border: none; + background: none; + box-shadow: inherit; + text-shadow: inherit; + color: #ddca9e; + padding: 1px; + padding-left: 1px; + padding-right: 1px; + margin-right: 2px; + margin-left: 2px; +} + +#workspaces button.hidden { + color: #9e906f; + background-color: transparent; +} + +#workspaces button.visible { + color: #ddca9e; +} + +#workspaces button:hover { + color: #d79921; +} + +#workspaces button.active { + background-color: #ddca9e; + color: #282828; + margin-top: 5px; + margin-bottom: 5px; + padding-top: 1px; + padding-bottom: 0px; + border-radius: 3px; +} + +#workspaces button.urgent { + background-color: #ef5e5e; + color: #282828; + margin-top: 5px; + margin-bottom: 5px; + padding-top: 1px; + padding-bottom: 0px; + border-radius: 3px; +} + +#tags { + background-color: transparent; +} + +#tags button { + background-color: #fff; + color: #a585cd; +} + +#tags button:not(.occupied):not(.focused) { + font-size: 0; + min-width: 0; + min-height: 0; + margin: -17px; + padding: 0; + color: transparent; + background-color: transparent; +} + +#tags button.occupied { + background-color: #fff; + color: #cdc885; +} + +#tags button.focused { + background-color: rgb(186, 142, 213); + color: #fff; +} + +#tags button.urgent { + background: rgb(171, 101, 101); + color: #fff; +} + +#window { + background-color: rgb(237, 196, 147); + color: rgb(63, 37, 5); +} +``` + +## Complete Configuration Example + +> **Tip:** You can find a complete Waybar configuration for mangowm at [waybar-config](https://github.com/DreamMaoMao/waybar-config). \ No newline at end of file diff --git a/docs/v0.13.1/visuals/theming.md b/docs/v0.13.1/visuals/theming.md new file mode 100644 index 00000000..676c575b --- /dev/null +++ b/docs/v0.13.1/visuals/theming.md @@ -0,0 +1,62 @@ +--- +title: Theming +description: Customize the visual appearance of borders, colors, and the cursor. +--- + +## Dimensions + +Control the sizing of window borders and gaps. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `borderpx` | `4` | Border width in pixels. | +| `gappih` | `5` | Horizontal inner gap (between windows). | +| `gappiv` | `5` | Vertical inner gap. | +| `gappoh` | `10` | Horizontal outer gap (between windows and screen edges). | +| `gappov` | `10` | Vertical outer gap. | + +## Colors + +Colors are defined in `0xRRGGBBAA` hex format. + +```ini +# Background color of the root window +rootcolor=0x323232ff + +# Inactive window border +bordercolor=0x444444ff + +# Drop shadow when dragging windows +dropcolor=0x8FBA7C55 + +# Split window border color in manual dwindle layout +splitcolor=0xEB441EFF + +# Active window border +focuscolor=0xc66b25ff + +# Urgent window border (alerts) +urgentcolor=0xad401fff +``` + +### State-Specific Colors + +You can also color-code windows based on their state: + +| State | Config Key | Default Color | +| :--- | :--- | :--- | +| Maximized | `maximizescreencolor` | `0x89aa61ff` | +| Scratchpad | `scratchpadcolor` | `0x516c93ff` | +| Global | `globalcolor` | `0xb153a7ff` | +| Overlay | `overlaycolor` | `0x14a57cff` | + +> **Tip:** For scratchpad window sizing, see [Scratchpad](/docs/window-management/scratchpad) configuration. + +## Cursor Theme + +Set the size and theme of your mouse cursor. + +```ini +cursor_size=24 +cursor_theme=Adwaita +``` diff --git a/docs/v0.13.1/window-management/index.mdx b/docs/v0.13.1/window-management/index.mdx new file mode 100644 index 00000000..b96c5891 --- /dev/null +++ b/docs/v0.13.1/window-management/index.mdx @@ -0,0 +1,19 @@ +--- +title: Window Management +description: Layouts, rules, and window behavior. +icon: LayoutGrid +--- + +Window management with layouts, rules, and scratchpad support. + +<Cards> + +<Card href="/docs/window-management/layouts" title="Layouts" description="Tile, scroller, monocle, grid, deck" /> + +<Card href="/docs/window-management/rules" title="Rules" description="Window rules and conditions" /> + +<Card href="/docs/window-management/overview" title="Overview" description="Window states and properties" /> + +<Card href="/docs/window-management/scratchpad" title="Scratchpad" description="Quick access to applications" /> + +</Cards> \ No newline at end of file diff --git a/docs/v0.13.1/window-management/layouts.md b/docs/v0.13.1/window-management/layouts.md new file mode 100644 index 00000000..45b08ec2 --- /dev/null +++ b/docs/v0.13.1/window-management/layouts.md @@ -0,0 +1,135 @@ +--- +title: Layouts +description: Configure and switch between different window layouts. +--- + +## Supported Layouts + +mangowm supports a variety of layouts that can be assigned per tag. + +- `tile` +- `scroller` +- `monocle` +- `grid` +- `deck` +- `center_tile` +- `vertical_tile` +- `right_tile` +- `vertical_scroller` +- `vertical_grid` +- `vertical_deck` +- `dwindle` +- `fair` +- `vertical_fair` + +--- + +## Scroller Layout + +The Scroller layout positions windows in a scrollable strip, similar to PaperWM. + +### Configuration + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `scroller_structs` | `20` | Width reserved on sides when window ratio is 1. | +| `scroller_default_proportion` | `0.9` | Default width proportion for new windows. | +| `scroller_focus_center` | `0` | Always center the focused window (1 = enable). | +| `scroller_prefer_center` | `0` | Center focused window only if it was outside the view. | +| `scroller_prefer_overspread` | `1` | Allow windows to overspread when there's extra space. | +| `edge_scroller_pointer_focus` | `1` | Focus windows even if partially off-screen. | +| `scroller_proportion_preset` | `0.5,0.8,1.0` | Presets for cycling window widths. | +| `scroller_ignore_proportion_single` | `1` | Ignore proportion adjustments for single windows. | +| `scroller_default_proportion_single` | `1.0` | Default proportion for single windows in scroller. **Requires `scroller_ignore_proportion_single=0` to take effect.** | + +> **Warning:** `scroller_prefer_overspread`, `scroller_focus_center`, and `scroller_prefer_center` interact with each other. Their priority order is: +> +> **scroller_prefer_overspread > scroller_focus_center > scroller_prefer_center** +> +> To ensure a lower-priority setting takes effect, you must set all higher-priority options to `0`. + +```ini +# Example scroller configuration +scroller_structs=20 +scroller_default_proportion=0.9 +scroller_focus_center=0 +scroller_prefer_center=0 +scroller_prefer_overspread=1 +edge_scroller_pointer_focus=1 +scroller_default_proportion_single=1.0 +scroller_proportion_preset=0.5,0.8,1.0 +``` + +--- + +## Master-Stack Layouts + +These settings apply to layouts like `tile` and `center_tile`. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `new_is_master` | `1` | New windows become the master window. | +| `default_mfact` | `0.55` | The split ratio between master and stack areas. | +| `default_nmaster` | `1` | Number of allowed master windows. | +| `smartgaps` | `0` | Disable gaps when only one window is present. | +| `center_master_overspread` | `0` | (Center Tile) Master spreads across screen if no stack exists. | +| `center_when_single_stack` | `1` | (Center Tile) Center master when only one stack window exists. | + +```ini +# Example master-stack configuration +new_is_master=1 +smartgaps=0 +default_mfact=0.55 +default_nmaster=1 +``` + +--- + +## Dwindle Layout + +The Dwindle layout arranges windows as a binary tree of recursive splits. Each new window splits the focused window's container, producing a spiral-like tiling. + +### Configuration + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `dwindle_split_ratio` | `0.5` | Ratio used for new splits (`0.05`–`0.95`). | +| `dwindle_smart_split` | `0` | Pick the split axis from the cursor's position inside the focused window. The new window appears on the cursor's side. | +| `dwindle_hsplit` | `1` | Side-by-side splits: where the new window goes. `0` = follow cursor, `1` = right, `2` = left. | +| `dwindle_vsplit` | `1` | Top/bottom splits: where the new window goes. `0` = follow cursor, `1` = below, `2` = above. | +| `dwindle_preserve_split` | `0` | Keep the sibling's split orientation when a window is closed. | +| `dwindle_smart_resize` | `0` | When dragging to resize, move the split toward the cursor regardless of which side was grabbed. | +| `dwindle_drop_simple_split` | `1` | Drag-to-tile drop preview. `1` = 2-zone preview matching `dwindle_split_ratio`, `0` = 4-quadrant preview. | +| `dwindle_manual_split` | `0` | Manually split windows mode. | + +```ini +# Example dwindle configuration +dwindle_split_ratio=0.5 +dwindle_smart_split=0 +dwindle_hsplit=0 +dwindle_vsplit=0 +dwindle_preserve_split=0 +dwindle_smart_resize=0 +dwindle_drop_simple_split=1 +``` + +--- + +## Switching Layouts +| Setting | Default | Description | +| :--- | :--- | :--- | +| `circle_layout` | - | A comma-separated list of layouts `switch_layout` cycles through,the value sample:`tile,scroller`. | + +You can switch layouts dynamically or set a default for specific tags using [Tag Rules](/docs/window-management/rules#tag-rules). + +**Keybinding Examples:** + +```ini +# Cycle through layouts +circle_layout=grid,scroller,tile +bind=SUPER,n,switch_layout + +# Set specific layout +bind=SUPER,t,setlayout,tile +bind=SUPER,s,setlayout,scroller +``` diff --git a/docs/v0.13.1/window-management/meta.json b/docs/v0.13.1/window-management/meta.json new file mode 100644 index 00000000..e0937d14 --- /dev/null +++ b/docs/v0.13.1/window-management/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Window Management", + "pages": ["layouts", "rules", "overview", "scratchpad"] +} diff --git a/docs/v0.13.1/window-management/overview.md b/docs/v0.13.1/window-management/overview.md new file mode 100644 index 00000000..7da6e690 --- /dev/null +++ b/docs/v0.13.1/window-management/overview.md @@ -0,0 +1,29 @@ +--- +title: Overview +description: Configure the overview mode for window navigation. +--- + +## Overview Settings + +| Setting | Type | Default | Description | +| :--- | :--- | :--- | :--- | +| `hotarea_size` | integer | `10` | Hot area size in pixels. | +| `enable_hotarea` | integer | `1` | Enable hot areas (0: disable, 1: enable). | +| `hotarea_corner` | integer | `2` | Hot area corner (0: top-left, 1: top-right, 2: bottom-left, 3: bottom-right). | +| `ov_tab_mode` | integer | `0` | Overview tab mode (0: disable, 1: enable). | +| `overviewgappi` | integer | `5` | Inner gap in overview mode. | +| `overviewgappo` | integer | `30` | Outer gap in overview mode. | + +### Setting Descriptions + +- `enable_hotarea` — Toggles overview when the cursor enters the configured corner. +- `hotarea_size` — Size of the hot area trigger zone in pixels. +- `hotarea_corner` — Corner that triggers the hot area (0: top-left, 1: top-right, 2: bottom-left, 3: bottom-right). +- `ov_tab_mode` — Circles focus through windows in overview; releasing the mod key exits overview. + +### Mouse Interaction in Overview + +When in overview mode: + +- **Left mouse button** — Jump to (focus) a window. +- **Right mouse button** — Close a window. \ No newline at end of file diff --git a/docs/v0.13.1/window-management/rules.md b/docs/v0.13.1/window-management/rules.md new file mode 100644 index 00000000..4a295157 --- /dev/null +++ b/docs/v0.13.1/window-management/rules.md @@ -0,0 +1,250 @@ +--- +title: Rules +description: Define behavior for specific windows, tags, and layers. +--- + +## Window Rules + +Window rules allow you to set specific properties (floating, opacity, size, animations, etc.) for applications based on their `appid` or `title`. You can set all parameters in one line, and if you both set appid and title, the window will only follow the rules when appid and title both match. + +**Format:** + +```ini +windowrule=Parameter:Values,title:Values +windowrule=Parameter:Values,Parameter:Values,appid:Values,title:Values +``` + +### State & Behavior Parameters + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `appid` | string | Any | Match by application ID, supports regex | +| `title` | string | Any | Match by window title, supports regex | +| `isfloating` | integer | `0` / `1` | Force floating state | +| `isfullscreen` | integer | `0` / `1` | Force fullscreen state | +| `isfakefullscreen` | integer | `0` / `1` | Force fake-fullscreen state (window stays constrained) | +| `isglobal` | integer | `0` / `1` | Open as global window (sticky across tags) | +| `isoverlay` | integer | `0` / `1` | Make it always in top layer | +| `isopensilent` | integer | `0` / `1` | Open without focus | +| `istagsilent` | integer | `0` / `1` | Don't focus if client is not in current view tag | +| `force_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 | +| `noopenmaximized` | integer | `0` / `1` | Window does not open as maximized mode | +| `single_scratchpad` | integer | `0` / `1` (default 1) | Only show one out of named scratchpads or the normal scratchpad | +| `allow_shortcuts_inhibit` | integer | `0` / `1` (default 1) | Allow shortcuts to be inhibited by clients | +| `indleinhibit_when_focus` | integer | `0` / `1` (default 0) | Automatically keep idle inhibit active when this window is focused | + +### Geometry & Position + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `width` | float | 0-9999 | Window width when it becomes a floating window,if the value below 1, it will be the percentage of the screen width,otherwise it will be the pixel value | +| `height` | float | 0-9999 | Window height when it becomes a floating window,if the value below 1, it will be the percentage of the screen height,otherwise it will be the pixel value | +| `offsetx` | integer | -999-999 | X offset from center (%), 100 is the edge of screen with outer gap | +| `offsety` | integer | -999-999 | Y offset from center (%), 100 is the edge of screen with outer gap | +| `monitor` | string | Any | Assign to monitor by [monitor spec](/docs/configuration/monitors#monitor-spec-format) (name, make, model, or serial) | +| `tags` | integer | 1-9 | Assign to specific tag | +| `no_force_center` | integer | `0` / `1` | Window does not force center | +| `isnosizehint` | integer | `0` / `1` | Don't use min size and max size for size hints | + +### Visuals & Decoration + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `noblur` | integer | `0` / `1` | Window does not have blur effect | +| `isnoborder` | integer | `0` / `1` | Remove window border | +| `isnoshadow` | integer | `0` / `1` | Not apply shadow | +| `isnoradius` | integer | `0` / `1` | Not apply corner radius | +| `isnoanimation` | integer | `0` / `1` | Not apply animation | +| `focused_opacity` | integer | `0` / `1` | Window focused opacity | +| `unfocused_opacity` | integer | `0` / `1` | Window unfocused opacity | +| `allow_csd` | integer | `0` / `1` | Allow client side decoration | + +> **Tip:** For detailed visual effects configuration, see the [Window Effects](/docs/visuals/effects) page for blur, shadows, and opacity settings. + +### Layout & Scroller + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `scroller_proportion` | float | 0.1-1.0 | Set scroller proportion | +| `scroller_proportion_single` | float | 0.1-1.0 | Set scroller auto adjust proportion when it is single window | + +> **Tip:** For comprehensive layout configuration, see the [Layouts](/docs/window-management/layouts) page for all layout options and detailed settings. + +### Animation + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `animation_type_open` | string | zoom, slide, fade, none | Set open animation | +| `animation_type_close` | string | zoom, slide, fade, none | Set close animation | +| `nofadein` | integer | `0` / `1` | Window ignores fade-in animation | +| `nofadeout` | integer | `0` / `1` | Window ignores fade-out animation | + +> **Tip:** For detailed animation configuration, see the [Animations](/docs/visuals/animations) page for available types and settings. + +### Terminal & Swallowing + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `isterm` | integer | `0` / `1` | A new GUI window will replace the isterm window when it is opened | +| `noswallow` | integer | `0` / `1` | The window will not replace the isterm window | + +### Global & Special Windows + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `globalkeybinding` | string | `[mod combination][-][key]` | Global keybinding (only works for Wayland apps) | +| `isunglobal` | integer | `0` / `1` | Open as unmanaged global window (for desktop pets or camera windows) | +| `isnamedscratchpad` | integer | `0` / `1` | 0: disable, 1: named scratchpad | + +> **Tip:** For scratchpad usage, see the [Scratchpad](/docs/window-management/scratchpad) page for detailed configuration examples. + +### Performance & Tearing + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `force_tearing` | integer | `0` / `1` | Set window to tearing state, refer to [Tearing](/docs/configuration/monitors#tearing-game-mode) | + +### Examples + +```ini +# Set specific window size and position +windowrule=width:1000,height:900,appid:yesplaymusic,title:Demons + +# Global keybindings for OBS Studio +windowrule=globalkeybinding:ctrl+alt-o,appid:com.obsproject.Studio +windowrule=globalkeybinding:ctrl+alt+n,appid:com.obsproject.Studio +windowrule=isopensilent:1,appid:com.obsproject.Studio + +# Force tearing for games +windowrule=force_tearing:1,title:vkcube +windowrule=force_tearing:1,title:Counter-Strike 2 + +# Named scratchpad for file manager +windowrule=isnamedscratchpad:1,width:1280,height:800,appid:st-yazi + +# Custom opacity for specific apps +windowrule=focused_opacity:0.8,appid:firefox +windowrule=unfocused_opacity:0.6,appid:foot + +# Disable blur for selection tools +windowrule=noblur:1,appid:slurp + +# Position windows relative to screen center +windowrule=offsetx:20,offsety:-30,width:800,height:600,appid:alacritty + +# Send to specific tag and monitor +windowrule=tags:9,monitor:HDMI-A-1,appid:discord + +# Terminal swallowing setup +windowrule=isterm:1,appid:st +windowrule=noswallow:1,appid:foot + +# Disable client-side decorations +windowrule=allow_csd:1,appid:firefox + +# Unmanaged global window (desktop pets, camera) +windowrule=isunglobal:1,appid:cheese + +# Named scratchpad toggle +bind=alt,h,toggle_named_scratchpad,st-yazi,none,st -c st-yazi -e yazi +``` + +--- + +## Tag Rules + +You can set all parameters in one line. If only `id` is set, the rule is followed when the id matches. If any of `monitor_name`, `monitor_make`, `monitor_model`, or `monitor_serial` are set, the rule is followed only if **all** of the set monitor fields match. + +> **Warning:** Layouts set in tag rules have a higher priority than monitor rule layouts. + +**Format:** + +```ini +tagrule=id:Values,Parameter:Values,Parameter:Values +tagrule=id:Values,monitor_name:eDP-1,Parameter:Values,Parameter:Values +tagrule=id:Values,monitor_make:xxx,monitor_model:xxx,Parameter:Values +``` + +> **Tip:** See [Layouts](/docs/window-management/layouts#supported-layouts) for detailed descriptions of each layout type. + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `id` | integer | 0-9 | Match by tag id, 0 means the ~0 tag | +| `monitor_name` | string | monitor name | Match by monitor name | +| `monitor_make` | string | monitor make | Match by monitor manufacturer | +| `monitor_model` | string | monitor model | Match by monitor model | +| `monitor_serial` | string | monitor serial | Match by monitor serial number | +| `layout_name` | string | layout name | Layout name to set | +| `no_render_border` | integer | `0` / `1` | Disable render border | +| `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 | + +### Examples + +```ini +# Set layout for specific tags +tagrule=id:1,layout_name:scroller +tagrule=id:2,layout_name:scroller + +# Limit to specific monitor +tagrule=id:1,monitor_name:eDP-1,layout_name:scroller +tagrule=id:2,monitor_name:eDP-1,layout_name:scroller + +# Persistent tags (1-4) with layout assignment +tagrule=id:1,no_hide:1,layout_name:scroller +tagrule=id:2,no_hide:1,layout_name:scroller +tagrule=id:3,monitor_name:eDP-1,no_hide:1,layout_name:scroller +tagrule=id:4,monitor_name:eDP-1,no_hide:1,layout_name:scroller + +# Advanced tag configuration with master layout settings +tagrule=id:5,layout_name:tile,nmaster:2,mfact:0.6 +tagrule=id:6,monitor_name:HDMI-A-1,layout_name:monocle,no_render_border:1 +``` + +> **Tip:** For Waybar configuration with persistent tags, see [Status Bar](/docs/visuals/status-bar) documentation. + +--- + +## Layer Rules + +You can set all parameters in one line. Target "layer shell" surfaces like status bars (`waybar`), launchers (`rofi`), or notification daemons. + +**Format:** + +```ini +layerrule=layer_name:Values,Parameter:Values,Parameter:Values +``` + +> **Tip:** You can use `mmsg -e` to get the last open layer name for debugging. + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `layer_name` | string | layer name | Match name of layer, supports regex | +| `animation_type_open` | string | slide, zoom, fade, none | Set open animation | +| `animation_type_close` | string | slide, zoom, fade, none | Set close animation | +| `noblur` | integer | `0` / `1` | Disable blur | +| `noanim` | integer | `0` / `1` | Disable layer animation | +| `noshadow` | integer | `0` / `1` | Disable layer shadow | + +> **Tip:** For animation types, see [Animations](/docs/visuals/animations#animation-types). For visual effects, see [Window Effects](/docs/visuals/effects). + +### Examples + +```ini +# No blur or animation for slurp selection layer (avoids occlusion and ghosting in screenshots) +layerrule=noanim:1,noblur:1,layer_name:selection + +# Zoom animation for Rofi with multiple parameters +layerrule=animation_type_open:zoom,noanim:0,layer_name:rofi + +# Disable animations and shadows for notification daemon +layerrule=noanim:1,noshadow:1,layer_name:swaync + +# Multiple effects for launcher +layerrule=animation_type_open:slide,animation_type_close:fade,noblur:1,layer_name:wofi +``` diff --git a/docs/v0.13.1/window-management/scratchpad.md b/docs/v0.13.1/window-management/scratchpad.md new file mode 100644 index 00000000..398182f9 --- /dev/null +++ b/docs/v0.13.1/window-management/scratchpad.md @@ -0,0 +1,73 @@ +--- +title: Scratchpad +description: Manage hidden "scratchpad" windows for quick access. +--- + +mangowm supports two types of scratchpads: the standard pool (Sway-like) and named scratchpads. + +## Standard Scratchpad + +Any window can be sent to the "scratchpad" pile, which hides it. You can then cycle through them. + +**Keybindings:** + +```ini +# Send current window to scratchpad +bind=SUPER,i,minimized + +# Toggle (show/hide) the scratchpad +bind=ALT,z,toggle_scratchpad + +# Retrieve window from scratchpad (restore) +bind=SUPER+SHIFT,i,restore_minimized +``` + +--- + +## Named Scratchpad + +Named scratchpads are bound to specific keys and applications. When triggered, mangowm will either launch the app (if not running) or toggle its visibility. + +**1. Define the Window Rule** + +You must identify the app using a unique `appid` or `title` and mark it as a named scratchpad. The application must support setting a custom appid or title at launch. Common examples: + +- `st -c my-appid` — sets the appid +- `kitty -T my-title` — sets the window title +- `foot --app-id my-appid` — sets the appid + +Use `none` as a placeholder when you only want to match by one field. + +```ini +# Match by appid +windowrule=isnamedscratchpad:1,width:1280,height:800,appid:st-yazi + +# Match by title +windowrule=isnamedscratchpad:1,width:1000,height:700,title:kitty-scratch +``` + +**2. Bind the Toggle Key** + +Format: `bind=MOD,KEY,toggle_named_scratchpad,appid,title,command` + +Use `none` for whichever field you are not matching on. + +```ini +# Match by appid: launch 'st' with class 'st-yazi' running 'yazi' +bind=alt,h,toggle_named_scratchpad,st-yazi,none,st -c st-yazi -e yazi + +# Match by title: launch 'kitty' with window title 'kitty-scratch' +bind=alt,k,toggle_named_scratchpad,none,kitty-scratch,kitty -T kitty-scratch +``` + +--- + +## Appearance + +You can customize the size of scratchpad windows relative to the screen. + +```ini +scratchpad_width_ratio=0.8 +scratchpad_height_ratio=0.9 +scratchpadcolor=0x516c93ff +``` From 1b68ffc89424f16d6f835ebb064c69a370914aeb Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 26 May 2026 14:09:47 +0800 Subject: [PATCH 252/328] opt: more simple get last_open_surface in mmsg --- src/ipc/ipc.h | 83 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 55 insertions(+), 28 deletions(-) diff --git a/src/ipc/ipc.h b/src/ipc/ipc.h index 7d3c0660..666a5387 100644 --- a/src/ipc/ipc.h +++ b/src/ipc/ipc.h @@ -253,9 +253,14 @@ static void handle_command(int client_fd, const char *cmd_raw) { } else if (strcmp(cmd, "get keyboardlayout") == 0) { resp = cJSON_CreateObject(); cJSON_AddStringToObject(resp, "layout", ipc_get_layout_str()); - } else if (strncmp(cmd, "get last_open_surface ", 22) == 0) { - const char *name = cmd + 22; - Monitor *m = monitor_by_name(name); + } else if (strcmp(cmd, "get last_open_surface") == 0 || + strncmp(cmd, "get last_open_surface ", 22) == 0) { + Monitor *m; + if (cmd[21] == '\0') { // exactly "get last_open_surface" + m = selmon; + } else { + m = monitor_by_name(cmd + 22); + } if (!m) { send_static_json(client_fd, "{\"error\":\"monitor not found\"}\n"); return; @@ -504,9 +509,14 @@ static bool handle_watch_command(int fd, const char *cmd, type = IPC_WATCH_KEYMODE; } else if (strcmp(cmd, "watch keyboardlayout") == 0) { type = IPC_WATCH_KB_LAYOUT; - } else if (strncmp(cmd, "watch last_open_surface ", 27) == 0) { + } else if (strcmp(cmd, "watch last_open_surface") == 0 || + strncmp(cmd, "watch last_open_surface ", 24) == 0) { type = IPC_WATCH_LAST_OPEN_SURFACE; - arg = cmd + 27; + if (cmd[24] != '\0') { // has argument after the space + arg = cmd + 24; + } else { + arg = NULL; // default to selmon + } } if (type == IPC_WATCH_NONE) @@ -542,7 +552,12 @@ static bool handle_watch_command(int fd, const char *cmd, break; } case IPC_WATCH_LAST_OPEN_SURFACE: { - Monitor *m = monitor_by_name(arg); + Monitor *m = NULL; + if (arg) { + m = monitor_by_name(arg); + } else { + m = selmon; + } if (m) { json = cJSON_CreateObject(); cJSON_AddStringToObject(json, "monitor", m->wlr_output->name); @@ -640,7 +655,6 @@ static int ipc_handle_client_data(int fd, uint32_t mask, void *data) { available = client->buf_cap - client->buf_len; } - // 直接读取到 client->buf 尾部,跳过临时数组 ssize_t n = recv(fd, client->buf + client->buf_len, available - 1, MSG_DONTWAIT); if (n <= 0) @@ -652,7 +666,7 @@ static int ipc_handle_client_data(int fd, uint32_t mask, void *data) { char *nl = memchr(client->buf, '\n', client->buf_len); if (!nl) { if (client->buf_len > 1024 * 1024) - goto cleanup; // 防御过长命令 + goto cleanup; return 0; } *nl = '\0'; @@ -726,28 +740,41 @@ void ipc_notify_last_surface_ws_name(Monitor *m) { size_t len = 0; struct ipc_watch_client *wc, *tmp; wl_list_for_each_safe(wc, tmp, &watch_clients, link) { - if (wc->type == IPC_WATCH_LAST_OPEN_SURFACE && - strcmp(m->wlr_output->name, wc->target.monitor.name) == 0) { - if (!json_str) { - cJSON *json = cJSON_CreateObject(); - cJSON_AddStringToObject(json, "monitor", m->wlr_output->name); - cJSON_AddStringToObject(json, "last_open_surface", - m->last_open_surface); - char *raw = cJSON_PrintUnformatted(json); - cJSON_Delete(json); - if (!raw) - return; - len = strlen(raw); - json_str = malloc(len + 2); - snprintf(json_str, len + 2, "%s\n", raw); - free(raw); - } - if (send(wc->fd, json_str, len + 1, MSG_NOSIGNAL) < 0) - ipc_remove_watch_client(wc); + if (wc->type != IPC_WATCH_LAST_OPEN_SURFACE) + continue; + + /* 匹配具体 monitor 名称,或空名称表示默认 selmon */ + bool match = false; + if (wc->target.monitor.name[0] == '\0') { + /* 订阅的是 selmon */ + if (m == selmon) + match = true; + } else { + if (strcmp(m->wlr_output->name, wc->target.monitor.name) == 0) + match = true; } + + if (!match) + continue; + + if (!json_str) { + cJSON *json = cJSON_CreateObject(); + cJSON_AddStringToObject(json, "monitor", m->wlr_output->name); + cJSON_AddStringToObject(json, "last_open_surface", + m->last_open_surface); + char *raw = cJSON_PrintUnformatted(json); + cJSON_Delete(json); + if (!raw) + return; + len = strlen(raw); + json_str = malloc(len + 2); + snprintf(json_str, len + 2, "%s\n", raw); + free(raw); + } + if (send(wc->fd, json_str, len + 1, MSG_NOSIGNAL) < 0) + ipc_remove_watch_client(wc); } - if (json_str) - free(json_str); + free(json_str); } void ipc_notify_focusing_client(void) { From 9a54f2fa7c20bf14d0634c7a50790bddd321e630 Mon Sep 17 00:00:00 2001 From: xtheeq <atheeq.rhxn@gmail.com> Date: Tue, 26 May 2026 11:48:10 +0530 Subject: [PATCH 253/328] fix(ci): update workflows and scripts for versioned docs layout --- .github/scripts/sync-wiki.py | 7 ++++--- .github/workflows/generate-nix-options-docs.yml | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/scripts/sync-wiki.py b/.github/scripts/sync-wiki.py index ebf543c7..51f3e404 100644 --- a/.github/scripts/sync-wiki.py +++ b/.github/scripts/sync-wiki.py @@ -5,6 +5,7 @@ from pathlib import Path DOCS_DIR = Path("docs") WIKI_DIR = Path("wiki-temp") +LATEST_DIR = DOCS_DIR / "(git)" FRONTMATTER_RE = re.compile(r"\A---\s*\n.*?^---\s*\n", re.DOTALL | re.MULTILINE) DOCS_LINK_RE = re.compile(r"\[([^\]]+)\]\(/docs/(?:[^/)]+/)*([^/)#]+)(#[^)]+)?\)") @@ -20,10 +21,10 @@ def collect_all_files() -> list[tuple[Path, str]]: return [directory / f"{p}.md" for p in data.get("pages", []) if (directory / f"{p}.md").exists()] return sorted(directory.glob("*.md")) - for src in from_dir(DOCS_DIR): + for src in from_dir(LATEST_DIR): files.append((src, "Home" if src.stem == "index" else src.stem)) - for subdir in sorted(DOCS_DIR.iterdir()): + for subdir in sorted(LATEST_DIR.iterdir()): if subdir.is_dir(): for src in from_dir(subdir): files.append((src, src.stem)) @@ -44,7 +45,7 @@ def main() -> None: lines: list[str] = [] current_section = None for src, dest_name in files: - section = "General" if src.parent == DOCS_DIR else src.parent.name.replace("-", " ").title() + section = "General" if src.parent == LATEST_DIR else src.parent.name.replace("-", " ").title() if section != current_section: if current_section is not None: lines.append("") diff --git a/.github/workflows/generate-nix-options-docs.yml b/.github/workflows/generate-nix-options-docs.yml index 7249ef46..ff6006c1 100644 --- a/.github/workflows/generate-nix-options-docs.yml +++ b/.github/workflows/generate-nix-options-docs.yml @@ -53,11 +53,11 @@ jobs: python3 ./.github/scripts/generate-nix-options-docs.py \ /tmp/nixos-raw.md \ /tmp/hm-raw.md \ - docs/nix-options.md + 'docs/(git)/nix-options.md' - name: Auto-commit changes uses: stefanzweifel/git-auto-commit-action@v5 with: commit_message: "docs: auto-generate Nix module options" - file_pattern: 'docs/*-options.md' + file_pattern: 'docs/**/nix-options.md' branch: ${{ github.head_ref }} From 564903254e6983881cdffe1defa2339f100a8601 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 26 May 2026 14:25:51 +0800 Subject: [PATCH 254/328] update mmsg usecase in docs --- docs/bindings/keys.md | 2 +- docs/configuration/input.md | 2 +- docs/configuration/monitors.md | 16 ++++++++-------- docs/ipc.md | 5 +++-- docs/screenshot.md | 2 +- docs/window-management/rules.md | 2 +- 6 files changed, 15 insertions(+), 14 deletions(-) diff --git a/docs/bindings/keys.md b/docs/bindings/keys.md index 616ea305..5abc716b 100644 --- a/docs/bindings/keys.md +++ b/docs/bindings/keys.md @@ -51,7 +51,7 @@ You can divide key bindings into named modes. Rules: 2. If no `keymode` is set before a bind, it belongs to the `default` mode. 3. The special `common` keymode applies its binds **across all modes**. -Use `setkeymode` to switch modes, and `mmsg -b` to query the current mode. +Use `setkeymode` to switch modes, and `mmsg get keymode` to query the current mode. ```ini # Binds in 'common' apply in every mode diff --git a/docs/configuration/input.md b/docs/configuration/input.md index ee12906a..b821bf29 100644 --- a/docs/configuration/input.md +++ b/docs/configuration/input.md @@ -131,7 +131,7 @@ Or bind it manually to a key: bind=alt,shift_l,switch_keyboard_layout ``` -Use `mmsg -g -k` to query the current keyboard layout at any time. +Use `mmsg get keyboardlayout` to query the current layout. --- diff --git a/docs/configuration/monitors.md b/docs/configuration/monitors.md index 28ef240b..084db60c 100644 --- a/docs/configuration/monitors.md +++ b/docs/configuration/monitors.md @@ -83,13 +83,13 @@ name:xxx&&make:xxx&&model:xxx&&serial:xxx ```bash # By name (shorthand) -mmsg -d toggle_monitor,eDP-1 +mmsg dispatch toggle_monitor,eDP-1 # By make and model -mmsg -d toggle_monitor,make:Chimei Innolux Corporation&&model:0x15F5 +mmsg dispatch toggle_monitor,make:Chimei Innolux Corporation&&model:0x15F5 # By serial -mmsg -d toggle_monitor,serial:12345678 +mmsg dispatch toggle_monitor,serial:12345678 ``` --- @@ -166,13 +166,13 @@ You can control monitor power using the `mmsg` IPC tool. ```bash # Turn off -mmsg -d disable_monitor,eDP-1 +mmsg dispatch disable_monitor,eDP-1 # Turn on -mmsg -d enable_monitor,eDP-1 +mmsg dispatch enable_monitor,eDP-1 # Toggle -mmsg -d toggle_monitor,eDP-1 +mmsg dispatch toggle_monitor,eDP-1 ``` You can also use `wlr-randr` for monitor management: @@ -257,10 +257,10 @@ You can create and manage virtual displays through IPC commands: ```bash # Create virtual output -mmsg -d create_virtual_output +mmsg dispatch create_virtual_output # Destroy all virtual outputs -mmsg -d destroy_all_virtual_output +mmsg dispatch destroy_all_virtual_output ``` You can configure virtual monitors using `wlr-randr`: diff --git a/docs/ipc.md b/docs/ipc.md index b4372c39..489293fa 100644 --- a/docs/ipc.md +++ b/docs/ipc.md @@ -30,10 +30,11 @@ description: Control mangowm programmatically using mmsg. | `get focusing-client` | Returns full JSON details for the client currently in focus. | | `get client <id>` | Returns full JSON details for a client with the given ID. | | `get tag <mon> <idx>` | Queries status of a specific tag on a monitor. | +| `get tags <mon>` | Returns a JSON object containing the status of all tags on a monitor. | | `get all-clients` | Returns a JSON array of all active clients. | | `get all-monitors` | Returns a JSON array of all connected monitors. | | `get all-tags` | Returns a JSON object containing the status of all tags. | -| `get last_open_surface <mon>` | Returns the last focused surface name for a monitor. | +| `get last_open_surface [<mon>]` | Returns the last focused surface name for a monitor,if the mon not set, it will get current monitor. | *Example:* ```bash @@ -54,7 +55,7 @@ Subscribes the client to real-time updates. When the state changes, the server p * `watch all-clients` * `watch keymode` * `watch keyboardlayout` -* `watch last_open_surface <mon_name>` +* `watch last_open_surface [<mon_name>]` *Example:* ```bash diff --git a/docs/screenshot.md b/docs/screenshot.md index f07cdf0c..4ae84c3d 100644 --- a/docs/screenshot.md +++ b/docs/screenshot.md @@ -90,7 +90,7 @@ Uses `mmsg` (ships with mango) to capture the focused window. ```bash #!/usr/bin/env bash -geometry=$(mmsg -x | awk '/x / {x=$3} /y / {y=$3} /width / {w=$3} /height / {h=$3} END {print x","y" "w"x"h}') +geometry=$(mmsg get focusing-client | jq -r '"\(.x),\(.y) \(.width)x\(.height)"') [ -z "$geometry" ] && exit 1 grim -g "$geometry" "$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png" ``` diff --git a/docs/window-management/rules.md b/docs/window-management/rules.md index acfd5a79..41b4cc53 100644 --- a/docs/window-management/rules.md +++ b/docs/window-management/rules.md @@ -220,7 +220,7 @@ You can set all parameters in one line. Target "layer shell" surfaces like statu layerrule=layer_name:Values,Parameter:Values,Parameter:Values ``` -> **Tip:** You can use `mmsg -e` to get the last open layer name for debugging. +> **Tip:** You can use `mmsg get last_open_surface` to get the last open layer name for debugging. | Parameter | Type | Values | Description | | :--- | :--- | :--- | :--- | From 20124282968c95f48b608294d949d0bd99a726f0 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 26 May 2026 14:32:49 +0800 Subject: [PATCH 255/328] docs: update git version --- docs/(git)/bindings/keys.md | 2 +- docs/(git)/configuration/input.md | 2 +- docs/(git)/configuration/monitors.md | 16 ++++++++-------- docs/(git)/ipc.md | 5 +++-- docs/(git)/meta.json | 4 +--- docs/(git)/screenshot.md | 2 +- docs/(git)/window-management/rules.md | 2 +- 7 files changed, 16 insertions(+), 17 deletions(-) diff --git a/docs/(git)/bindings/keys.md b/docs/(git)/bindings/keys.md index 616ea305..5abc716b 100644 --- a/docs/(git)/bindings/keys.md +++ b/docs/(git)/bindings/keys.md @@ -51,7 +51,7 @@ You can divide key bindings into named modes. Rules: 2. If no `keymode` is set before a bind, it belongs to the `default` mode. 3. The special `common` keymode applies its binds **across all modes**. -Use `setkeymode` to switch modes, and `mmsg -b` to query the current mode. +Use `setkeymode` to switch modes, and `mmsg get keymode` to query the current mode. ```ini # Binds in 'common' apply in every mode diff --git a/docs/(git)/configuration/input.md b/docs/(git)/configuration/input.md index ee12906a..b821bf29 100644 --- a/docs/(git)/configuration/input.md +++ b/docs/(git)/configuration/input.md @@ -131,7 +131,7 @@ Or bind it manually to a key: bind=alt,shift_l,switch_keyboard_layout ``` -Use `mmsg -g -k` to query the current keyboard layout at any time. +Use `mmsg get keyboardlayout` to query the current layout. --- diff --git a/docs/(git)/configuration/monitors.md b/docs/(git)/configuration/monitors.md index 28ef240b..084db60c 100644 --- a/docs/(git)/configuration/monitors.md +++ b/docs/(git)/configuration/monitors.md @@ -83,13 +83,13 @@ name:xxx&&make:xxx&&model:xxx&&serial:xxx ```bash # By name (shorthand) -mmsg -d toggle_monitor,eDP-1 +mmsg dispatch toggle_monitor,eDP-1 # By make and model -mmsg -d toggle_monitor,make:Chimei Innolux Corporation&&model:0x15F5 +mmsg dispatch toggle_monitor,make:Chimei Innolux Corporation&&model:0x15F5 # By serial -mmsg -d toggle_monitor,serial:12345678 +mmsg dispatch toggle_monitor,serial:12345678 ``` --- @@ -166,13 +166,13 @@ You can control monitor power using the `mmsg` IPC tool. ```bash # Turn off -mmsg -d disable_monitor,eDP-1 +mmsg dispatch disable_monitor,eDP-1 # Turn on -mmsg -d enable_monitor,eDP-1 +mmsg dispatch enable_monitor,eDP-1 # Toggle -mmsg -d toggle_monitor,eDP-1 +mmsg dispatch toggle_monitor,eDP-1 ``` You can also use `wlr-randr` for monitor management: @@ -257,10 +257,10 @@ You can create and manage virtual displays through IPC commands: ```bash # Create virtual output -mmsg -d create_virtual_output +mmsg dispatch create_virtual_output # Destroy all virtual outputs -mmsg -d destroy_all_virtual_output +mmsg dispatch destroy_all_virtual_output ``` You can configure virtual monitors using `wlr-randr`: diff --git a/docs/(git)/ipc.md b/docs/(git)/ipc.md index b4372c39..489293fa 100644 --- a/docs/(git)/ipc.md +++ b/docs/(git)/ipc.md @@ -30,10 +30,11 @@ description: Control mangowm programmatically using mmsg. | `get focusing-client` | Returns full JSON details for the client currently in focus. | | `get client <id>` | Returns full JSON details for a client with the given ID. | | `get tag <mon> <idx>` | Queries status of a specific tag on a monitor. | +| `get tags <mon>` | Returns a JSON object containing the status of all tags on a monitor. | | `get all-clients` | Returns a JSON array of all active clients. | | `get all-monitors` | Returns a JSON array of all connected monitors. | | `get all-tags` | Returns a JSON object containing the status of all tags. | -| `get last_open_surface <mon>` | Returns the last focused surface name for a monitor. | +| `get last_open_surface [<mon>]` | Returns the last focused surface name for a monitor,if the mon not set, it will get current monitor. | *Example:* ```bash @@ -54,7 +55,7 @@ Subscribes the client to real-time updates. When the state changes, the server p * `watch all-clients` * `watch keymode` * `watch keyboardlayout` -* `watch last_open_surface <mon_name>` +* `watch last_open_surface [<mon_name>]` *Example:* ```bash diff --git a/docs/(git)/meta.json b/docs/(git)/meta.json index 40a214d5..89c34444 100644 --- a/docs/(git)/meta.json +++ b/docs/(git)/meta.json @@ -1,7 +1,5 @@ { - "title": "git (latest)", - "description": "Latest development", - "root": true, + "title": "mangowm", "pages": [ "---Getting Started---", "index", diff --git a/docs/(git)/screenshot.md b/docs/(git)/screenshot.md index f07cdf0c..4ae84c3d 100644 --- a/docs/(git)/screenshot.md +++ b/docs/(git)/screenshot.md @@ -90,7 +90,7 @@ Uses `mmsg` (ships with mango) to capture the focused window. ```bash #!/usr/bin/env bash -geometry=$(mmsg -x | awk '/x / {x=$3} /y / {y=$3} /width / {w=$3} /height / {h=$3} END {print x","y" "w"x"h}') +geometry=$(mmsg get focusing-client | jq -r '"\(.x),\(.y) \(.width)x\(.height)"') [ -z "$geometry" ] && exit 1 grim -g "$geometry" "$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png" ``` diff --git a/docs/(git)/window-management/rules.md b/docs/(git)/window-management/rules.md index acfd5a79..41b4cc53 100644 --- a/docs/(git)/window-management/rules.md +++ b/docs/(git)/window-management/rules.md @@ -220,7 +220,7 @@ You can set all parameters in one line. Target "layer shell" surfaces like statu layerrule=layer_name:Values,Parameter:Values,Parameter:Values ``` -> **Tip:** You can use `mmsg -e` to get the last open layer name for debugging. +> **Tip:** You can use `mmsg get last_open_surface` to get the last open layer name for debugging. | Parameter | Type | Values | Description | | :--- | :--- | :--- | :--- | From 6e6ac32e5ddfd7d37d4940028a984f3a0a3bc48a Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 26 May 2026 14:33:30 +0800 Subject: [PATCH 256/328] feat: support docs version --- docs/{ => (git)}/bindings/index.mdx | 0 docs/{ => (git)}/bindings/keys.md | 0 docs/{ => (git)}/bindings/meta.json | 0 docs/{ => (git)}/bindings/mouse-gestures.md | 0 docs/{ => (git)}/configuration/basics.md | 0 docs/{ => (git)}/configuration/index.mdx | 0 docs/{ => (git)}/configuration/input.md | 0 docs/{ => (git)}/configuration/meta.json | 0 .../configuration/miscellaneous.md | 0 docs/{ => (git)}/configuration/monitors.md | 0 docs/{ => (git)}/configuration/xdg-portals.md | 0 docs/{ => (git)}/faq.md | 0 docs/{ => (git)}/index.md | 0 docs/{ => (git)}/installation.md | 0 docs/{ => (git)}/ipc.md | 0 docs/(git)/meta.json | 20 + docs/{ => (git)}/nix-options.md | 0 docs/{ => (git)}/quick-start.md | 0 docs/{ => (git)}/screenshot.md | 0 docs/{ => (git)}/visuals/animations.md | 0 docs/{ => (git)}/visuals/effects.md | 0 docs/{ => (git)}/visuals/index.mdx | 0 docs/{ => (git)}/visuals/meta.json | 0 docs/{ => (git)}/visuals/status-bar.md | 0 docs/{ => (git)}/visuals/theming.md | 0 docs/{ => (git)}/window-management/index.mdx | 0 docs/{ => (git)}/window-management/layouts.md | 0 docs/{ => (git)}/window-management/meta.json | 0 .../{ => (git)}/window-management/overview.md | 0 docs/{ => (git)}/window-management/rules.md | 0 .../window-management/scratchpad.md | 0 docs/meta.json | 19 +- docs/v0.13.0/bindings/index.mdx | 15 + docs/v0.13.0/bindings/keys.md | 216 ++++++++ docs/v0.13.0/bindings/meta.json | 4 + docs/v0.13.0/bindings/mouse-gestures.md | 116 ++++ docs/v0.13.0/configuration/basics.md | 87 +++ docs/v0.13.0/configuration/index.mdx | 21 + docs/v0.13.0/configuration/input.md | 161 ++++++ docs/v0.13.0/configuration/meta.json | 4 + docs/v0.13.0/configuration/miscellaneous.md | 50 ++ docs/v0.13.0/configuration/monitors.md | 276 ++++++++++ docs/v0.13.0/configuration/xdg-portals.md | 76 +++ docs/v0.13.0/faq.md | 100 ++++ docs/v0.13.0/index.md | 42 ++ docs/v0.13.0/installation.md | 308 +++++++++++ docs/v0.13.0/ipc.md | 154 ++++++ docs/v0.13.0/meta.json | 22 + docs/v0.13.0/nix-options.md | 519 ++++++++++++++++++ docs/v0.13.0/quick-start.md | 88 +++ docs/v0.13.0/screenshot.md | 213 +++++++ docs/v0.13.0/visuals/animations.md | 108 ++++ docs/v0.13.0/visuals/effects.md | 82 +++ docs/v0.13.0/visuals/index.mdx | 19 + docs/v0.13.0/visuals/meta.json | 4 + docs/v0.13.0/visuals/status-bar.md | 141 +++++ docs/v0.13.0/visuals/theming.md | 62 +++ docs/v0.13.0/window-management/index.mdx | 19 + docs/v0.13.0/window-management/layouts.md | 133 +++++ docs/v0.13.0/window-management/meta.json | 4 + docs/v0.13.0/window-management/overview.md | 29 + docs/v0.13.0/window-management/rules.md | 250 +++++++++ docs/v0.13.0/window-management/scratchpad.md | 73 +++ docs/v0.13.1/bindings/index.mdx | 15 + docs/v0.13.1/bindings/keys.md | 216 ++++++++ docs/v0.13.1/bindings/meta.json | 4 + docs/v0.13.1/bindings/mouse-gestures.md | 116 ++++ docs/v0.13.1/configuration/basics.md | 87 +++ docs/v0.13.1/configuration/index.mdx | 21 + docs/v0.13.1/configuration/input.md | 161 ++++++ docs/v0.13.1/configuration/meta.json | 4 + docs/v0.13.1/configuration/miscellaneous.md | 50 ++ docs/v0.13.1/configuration/monitors.md | 276 ++++++++++ docs/v0.13.1/configuration/xdg-portals.md | 76 +++ docs/v0.13.1/faq.md | 100 ++++ docs/v0.13.1/index.md | 42 ++ docs/v0.13.1/installation.md | 308 +++++++++++ docs/v0.13.1/ipc.md | 154 ++++++ docs/v0.13.1/meta.json | 22 + docs/v0.13.1/nix-options.md | 519 ++++++++++++++++++ docs/v0.13.1/quick-start.md | 88 +++ docs/v0.13.1/screenshot.md | 213 +++++++ docs/v0.13.1/visuals/animations.md | 108 ++++ docs/v0.13.1/visuals/effects.md | 82 +++ docs/v0.13.1/visuals/index.mdx | 19 + docs/v0.13.1/visuals/meta.json | 4 + docs/v0.13.1/visuals/status-bar.md | 141 +++++ docs/v0.13.1/visuals/theming.md | 62 +++ docs/v0.13.1/window-management/index.mdx | 19 + docs/v0.13.1/window-management/layouts.md | 135 +++++ docs/v0.13.1/window-management/meta.json | 4 + docs/v0.13.1/window-management/overview.md | 29 + docs/v0.13.1/window-management/rules.md | 250 +++++++++ docs/v0.13.1/window-management/scratchpad.md | 73 +++ 94 files changed, 6815 insertions(+), 18 deletions(-) rename docs/{ => (git)}/bindings/index.mdx (100%) rename docs/{ => (git)}/bindings/keys.md (100%) rename docs/{ => (git)}/bindings/meta.json (100%) rename docs/{ => (git)}/bindings/mouse-gestures.md (100%) rename docs/{ => (git)}/configuration/basics.md (100%) rename docs/{ => (git)}/configuration/index.mdx (100%) rename docs/{ => (git)}/configuration/input.md (100%) rename docs/{ => (git)}/configuration/meta.json (100%) rename docs/{ => (git)}/configuration/miscellaneous.md (100%) rename docs/{ => (git)}/configuration/monitors.md (100%) rename docs/{ => (git)}/configuration/xdg-portals.md (100%) rename docs/{ => (git)}/faq.md (100%) rename docs/{ => (git)}/index.md (100%) rename docs/{ => (git)}/installation.md (100%) rename docs/{ => (git)}/ipc.md (100%) create mode 100644 docs/(git)/meta.json rename docs/{ => (git)}/nix-options.md (100%) rename docs/{ => (git)}/quick-start.md (100%) rename docs/{ => (git)}/screenshot.md (100%) rename docs/{ => (git)}/visuals/animations.md (100%) rename docs/{ => (git)}/visuals/effects.md (100%) rename docs/{ => (git)}/visuals/index.mdx (100%) rename docs/{ => (git)}/visuals/meta.json (100%) rename docs/{ => (git)}/visuals/status-bar.md (100%) rename docs/{ => (git)}/visuals/theming.md (100%) rename docs/{ => (git)}/window-management/index.mdx (100%) rename docs/{ => (git)}/window-management/layouts.md (100%) rename docs/{ => (git)}/window-management/meta.json (100%) rename docs/{ => (git)}/window-management/overview.md (100%) rename docs/{ => (git)}/window-management/rules.md (100%) rename docs/{ => (git)}/window-management/scratchpad.md (100%) create mode 100644 docs/v0.13.0/bindings/index.mdx create mode 100644 docs/v0.13.0/bindings/keys.md create mode 100644 docs/v0.13.0/bindings/meta.json create mode 100644 docs/v0.13.0/bindings/mouse-gestures.md create mode 100644 docs/v0.13.0/configuration/basics.md create mode 100644 docs/v0.13.0/configuration/index.mdx create mode 100644 docs/v0.13.0/configuration/input.md create mode 100644 docs/v0.13.0/configuration/meta.json create mode 100644 docs/v0.13.0/configuration/miscellaneous.md create mode 100644 docs/v0.13.0/configuration/monitors.md create mode 100644 docs/v0.13.0/configuration/xdg-portals.md create mode 100644 docs/v0.13.0/faq.md create mode 100644 docs/v0.13.0/index.md create mode 100644 docs/v0.13.0/installation.md create mode 100644 docs/v0.13.0/ipc.md create mode 100644 docs/v0.13.0/meta.json create mode 100644 docs/v0.13.0/nix-options.md create mode 100644 docs/v0.13.0/quick-start.md create mode 100644 docs/v0.13.0/screenshot.md create mode 100644 docs/v0.13.0/visuals/animations.md create mode 100644 docs/v0.13.0/visuals/effects.md create mode 100644 docs/v0.13.0/visuals/index.mdx create mode 100644 docs/v0.13.0/visuals/meta.json create mode 100644 docs/v0.13.0/visuals/status-bar.md create mode 100644 docs/v0.13.0/visuals/theming.md create mode 100644 docs/v0.13.0/window-management/index.mdx create mode 100644 docs/v0.13.0/window-management/layouts.md create mode 100644 docs/v0.13.0/window-management/meta.json create mode 100644 docs/v0.13.0/window-management/overview.md create mode 100644 docs/v0.13.0/window-management/rules.md create mode 100644 docs/v0.13.0/window-management/scratchpad.md create mode 100644 docs/v0.13.1/bindings/index.mdx create mode 100644 docs/v0.13.1/bindings/keys.md create mode 100644 docs/v0.13.1/bindings/meta.json create mode 100644 docs/v0.13.1/bindings/mouse-gestures.md create mode 100644 docs/v0.13.1/configuration/basics.md create mode 100644 docs/v0.13.1/configuration/index.mdx create mode 100644 docs/v0.13.1/configuration/input.md create mode 100644 docs/v0.13.1/configuration/meta.json create mode 100644 docs/v0.13.1/configuration/miscellaneous.md create mode 100644 docs/v0.13.1/configuration/monitors.md create mode 100644 docs/v0.13.1/configuration/xdg-portals.md create mode 100644 docs/v0.13.1/faq.md create mode 100644 docs/v0.13.1/index.md create mode 100644 docs/v0.13.1/installation.md create mode 100644 docs/v0.13.1/ipc.md create mode 100644 docs/v0.13.1/meta.json create mode 100644 docs/v0.13.1/nix-options.md create mode 100644 docs/v0.13.1/quick-start.md create mode 100644 docs/v0.13.1/screenshot.md create mode 100644 docs/v0.13.1/visuals/animations.md create mode 100644 docs/v0.13.1/visuals/effects.md create mode 100644 docs/v0.13.1/visuals/index.mdx create mode 100644 docs/v0.13.1/visuals/meta.json create mode 100644 docs/v0.13.1/visuals/status-bar.md create mode 100644 docs/v0.13.1/visuals/theming.md create mode 100644 docs/v0.13.1/window-management/index.mdx create mode 100644 docs/v0.13.1/window-management/layouts.md create mode 100644 docs/v0.13.1/window-management/meta.json create mode 100644 docs/v0.13.1/window-management/overview.md create mode 100644 docs/v0.13.1/window-management/rules.md create mode 100644 docs/v0.13.1/window-management/scratchpad.md diff --git a/docs/bindings/index.mdx b/docs/(git)/bindings/index.mdx similarity index 100% rename from docs/bindings/index.mdx rename to docs/(git)/bindings/index.mdx diff --git a/docs/bindings/keys.md b/docs/(git)/bindings/keys.md similarity index 100% rename from docs/bindings/keys.md rename to docs/(git)/bindings/keys.md diff --git a/docs/bindings/meta.json b/docs/(git)/bindings/meta.json similarity index 100% rename from docs/bindings/meta.json rename to docs/(git)/bindings/meta.json diff --git a/docs/bindings/mouse-gestures.md b/docs/(git)/bindings/mouse-gestures.md similarity index 100% rename from docs/bindings/mouse-gestures.md rename to docs/(git)/bindings/mouse-gestures.md diff --git a/docs/configuration/basics.md b/docs/(git)/configuration/basics.md similarity index 100% rename from docs/configuration/basics.md rename to docs/(git)/configuration/basics.md diff --git a/docs/configuration/index.mdx b/docs/(git)/configuration/index.mdx similarity index 100% rename from docs/configuration/index.mdx rename to docs/(git)/configuration/index.mdx diff --git a/docs/configuration/input.md b/docs/(git)/configuration/input.md similarity index 100% rename from docs/configuration/input.md rename to docs/(git)/configuration/input.md diff --git a/docs/configuration/meta.json b/docs/(git)/configuration/meta.json similarity index 100% rename from docs/configuration/meta.json rename to docs/(git)/configuration/meta.json diff --git a/docs/configuration/miscellaneous.md b/docs/(git)/configuration/miscellaneous.md similarity index 100% rename from docs/configuration/miscellaneous.md rename to docs/(git)/configuration/miscellaneous.md diff --git a/docs/configuration/monitors.md b/docs/(git)/configuration/monitors.md similarity index 100% rename from docs/configuration/monitors.md rename to docs/(git)/configuration/monitors.md diff --git a/docs/configuration/xdg-portals.md b/docs/(git)/configuration/xdg-portals.md similarity index 100% rename from docs/configuration/xdg-portals.md rename to docs/(git)/configuration/xdg-portals.md diff --git a/docs/faq.md b/docs/(git)/faq.md similarity index 100% rename from docs/faq.md rename to docs/(git)/faq.md diff --git a/docs/index.md b/docs/(git)/index.md similarity index 100% rename from docs/index.md rename to docs/(git)/index.md diff --git a/docs/installation.md b/docs/(git)/installation.md similarity index 100% rename from docs/installation.md rename to docs/(git)/installation.md diff --git a/docs/ipc.md b/docs/(git)/ipc.md similarity index 100% rename from docs/ipc.md rename to docs/(git)/ipc.md diff --git a/docs/(git)/meta.json b/docs/(git)/meta.json new file mode 100644 index 00000000..89c34444 --- /dev/null +++ b/docs/(git)/meta.json @@ -0,0 +1,20 @@ +{ + "title": "mangowm", + "pages": [ + "---Getting Started---", + "index", + "installation", + "quick-start", + "---Configuration---", + "configuration", + "visuals", + "window-management", + "bindings", + "---Examples---", + "screenshot", + "---Reference---", + "nix-options", + "ipc", + "faq" + ] +} diff --git a/docs/nix-options.md b/docs/(git)/nix-options.md similarity index 100% rename from docs/nix-options.md rename to docs/(git)/nix-options.md diff --git a/docs/quick-start.md b/docs/(git)/quick-start.md similarity index 100% rename from docs/quick-start.md rename to docs/(git)/quick-start.md diff --git a/docs/screenshot.md b/docs/(git)/screenshot.md similarity index 100% rename from docs/screenshot.md rename to docs/(git)/screenshot.md diff --git a/docs/visuals/animations.md b/docs/(git)/visuals/animations.md similarity index 100% rename from docs/visuals/animations.md rename to docs/(git)/visuals/animations.md diff --git a/docs/visuals/effects.md b/docs/(git)/visuals/effects.md similarity index 100% rename from docs/visuals/effects.md rename to docs/(git)/visuals/effects.md diff --git a/docs/visuals/index.mdx b/docs/(git)/visuals/index.mdx similarity index 100% rename from docs/visuals/index.mdx rename to docs/(git)/visuals/index.mdx diff --git a/docs/visuals/meta.json b/docs/(git)/visuals/meta.json similarity index 100% rename from docs/visuals/meta.json rename to docs/(git)/visuals/meta.json diff --git a/docs/visuals/status-bar.md b/docs/(git)/visuals/status-bar.md similarity index 100% rename from docs/visuals/status-bar.md rename to docs/(git)/visuals/status-bar.md diff --git a/docs/visuals/theming.md b/docs/(git)/visuals/theming.md similarity index 100% rename from docs/visuals/theming.md rename to docs/(git)/visuals/theming.md diff --git a/docs/window-management/index.mdx b/docs/(git)/window-management/index.mdx similarity index 100% rename from docs/window-management/index.mdx rename to docs/(git)/window-management/index.mdx diff --git a/docs/window-management/layouts.md b/docs/(git)/window-management/layouts.md similarity index 100% rename from docs/window-management/layouts.md rename to docs/(git)/window-management/layouts.md diff --git a/docs/window-management/meta.json b/docs/(git)/window-management/meta.json similarity index 100% rename from docs/window-management/meta.json rename to docs/(git)/window-management/meta.json diff --git a/docs/window-management/overview.md b/docs/(git)/window-management/overview.md similarity index 100% rename from docs/window-management/overview.md rename to docs/(git)/window-management/overview.md diff --git a/docs/window-management/rules.md b/docs/(git)/window-management/rules.md similarity index 100% rename from docs/window-management/rules.md rename to docs/(git)/window-management/rules.md diff --git a/docs/window-management/scratchpad.md b/docs/(git)/window-management/scratchpad.md similarity index 100% rename from docs/window-management/scratchpad.md rename to docs/(git)/window-management/scratchpad.md diff --git a/docs/meta.json b/docs/meta.json index 89c34444..e6110ed2 100644 --- a/docs/meta.json +++ b/docs/meta.json @@ -1,20 +1,3 @@ { - "title": "mangowm", - "pages": [ - "---Getting Started---", - "index", - "installation", - "quick-start", - "---Configuration---", - "configuration", - "visuals", - "window-management", - "bindings", - "---Examples---", - "screenshot", - "---Reference---", - "nix-options", - "ipc", - "faq" - ] + "pages": ["(git)", "v0.13.1", "v0.13.0"] } diff --git a/docs/v0.13.0/bindings/index.mdx b/docs/v0.13.0/bindings/index.mdx new file mode 100644 index 00000000..4c3a5bda --- /dev/null +++ b/docs/v0.13.0/bindings/index.mdx @@ -0,0 +1,15 @@ +--- +title: Bindings & Input +description: Keybindings, mouse gestures, and input devices. +icon: Keyboard +--- + +Configure how you interact with mangowm using flexible keybindings and input options. + +<Cards> + +<Card href="/docs/bindings/keys" title="Key Bindings" description="Keyboard shortcuts and modes" /> + +<Card href="/docs/bindings/mouse-gestures" title="Mouse Gestures" description="Touchpad and mouse gestures" /> + +</Cards> diff --git a/docs/v0.13.0/bindings/keys.md b/docs/v0.13.0/bindings/keys.md new file mode 100644 index 00000000..002c9564 --- /dev/null +++ b/docs/v0.13.0/bindings/keys.md @@ -0,0 +1,216 @@ +--- +title: Key Bindings +description: Define keyboard shortcuts and modes. +--- + +## Syntax + +Key bindings follow this format: + +```ini +bind[flags]=MODIFIERS,KEY,COMMAND,PARAMETERS +``` + +- **Modifiers**: `SUPER`, `CTRL`, `ALT`, `SHIFT`, `NONE` (combine with `+`, e.g. `SUPER+CTRL+ALT`). +- **Key**: Key name (from `xev` or `wev`) or keycode (e.g., `code:24` for `q`). + +> **Info:** `bind` automatically converts keysym to keycode for comparison. This makes it compatible with all keyboard layouts, but the matching may not always be precise. If a key combination doesn't work on your keyboard layout, use a keycode instead (e.g., `code:24` instead of `q`). + +### Flags + +- `l`: Works even when screen is locked. +- `s`: Uses keysym instead of keycode to bind. +- `r`: Triggers on key release instead of press. +- `p`: Pass key event to client. + +**Examples:** + +```ini +bind=SUPER,Q,killclient +bindl=SUPER,L,spawn,swaylock + +# Using keycode instead of key name +bind=ALT,code:24,killclient + +# Combining keycodes for modifiers and keys +bind=code:64,code:24,killclient +bind=code:64+code:133,code:24,killclient + +# Bind with no modifier +bind=NONE,XF86MonBrightnessUp,spawn,brightnessctl set +5% + +# Bind a modifier key itself as the trigger key +bind=alt,shift_l,switch_keyboard_layout +``` + +## Key Modes (Submaps) + +You can divide key bindings into named modes. Rules: + +1. Set `keymode=<name>` before a group of `bind` lines — those binds only apply in that mode. +2. If no `keymode` is set before a bind, it belongs to the `default` mode. +3. The special `common` keymode applies its binds **across all modes**. + +Use `setkeymode` to switch modes, and `mmsg -b` to query the current mode. + +```ini +# Binds in 'common' apply in every mode +keymode=common +bind=SUPER,r,reload_config + +# Default mode bindings +keymode=default +bind=ALT,Return,spawn,foot +bind=SUPER,F,setkeymode,resize + +# 'resize' mode bindings +keymode=resize +bind=NONE,Left,resizewin,-10,0 +bind=NONE,Right,resizewin,+10,0 +bind=NONE,Escape,setkeymode,default +``` + +### Single Modifier Key Binding + +When binding a modifier key itself, use `NONE` for press and the modifier name for release: + +```ini +# Trigger on press of Super key +bind=none,Super_L,spawn,rofi -show run + +# Trigger on release of Super key +bindr=Super,Super_L,spawn,rofi -show run +``` + +## Dispatchers List + +### Window Management + +| Command | Param | Description | +| :--- | :--- | :--- | +| `killclient` | - | Close the focused window. | +| `togglefloating` | - | Toggle floating state. | +| `toggle_all_floating` | - | Toggle all visible clients floating state. | +| `togglefullscreen` | - | Toggle fullscreen. | +| `togglefakefullscreen` | - | Toggle "fake" fullscreen (remains constrained). | +| `togglemaximizescreen` | - | Maximize window (keep decoration/bar). | +| `toggleglobal` | - | Pin window to all tags. | +| `toggle_render_border` | - | Toggle border rendering. | +| `centerwin` | - | Center the floating window. | +| `minimized` | - | Minimize window to scratchpad. | +| `restore_minimized` | - | Restore window from scratchpad. | +| `toggle_scratchpad` | - | Toggle scratchpad. | +| `toggle_named_scratchpad` | `appid,title,cmd` | Toggle named scratchpad. Launches app if not running, otherwise shows/hides it. | + +### Focus & Movement + +| Command | Param | Description | +| :--- | :--- | :--- | +| `focusdir` | `left/right/up/down` | Focus window in direction. | +| `focusstack` | `next/prev` | Cycle focus within the stack. | +| `focuslast` | - | Focus the previously active window. | +| `exchange_client` | `left/right/up/down` | Swap window with neighbor in direction. | +| `exchange_stack_client` | `next/prev` | Exchange window position in stack. | +| `zoom` | - | Swap focused window with Master. | + +### Tags & Monitors + +| Command | Param | Description | +| :--- | :--- | :--- | +| `view` | `-1/0/1-9` or `mask [,synctag]` | View tag. `-1` = previous tagset, `0` = all tags, `1-9` = specific tag, mask e.g. `1\|3\|5`. Optional `synctag` (0/1) syncs the action to all monitors. | +| `viewtoleft` | `[synctag]` | View previous tag. Optional `synctag` (0/1) syncs to all monitors. | +| `viewtoright` | `[synctag]` | View next tag. Optional `synctag` (0/1) syncs to all monitors. | +| `viewtoleft_have_client` | `[synctag]` | View left tag and focus client if present. Optional `synctag` (0/1). | +| `viewtoright_have_client` | `[synctag]` | View right tag and focus client if present. Optional `synctag` (0/1). | +| `viewcrossmon` | `tag,monitor_spec` | View specified tag on specified monitor. | +| `tag` | `1-9 [,synctag]` | Move window to tag. Optional `synctag` (0/1) syncs to all monitors. | +| `tagsilent` | `1-9` | Move window to tag without focusing it. | +| `tagtoleft` | `[synctag]` | Move window to left tag. Optional `synctag` (0/1). | +| `tagtoright` | `[synctag]` | Move window to right tag. Optional `synctag` (0/1). | +| `tagcrossmon` | `tag,monitor_spec` | Move window to specified tag on specified monitor. | +| `toggletag` | `0-9` | Toggle tag on window (0 means all tags). | +| `toggleview` | `1-9` | Toggle tag view. | +| `comboview` | `1-9` | View multi tags pressed simultaneously. | +| `focusmon` | `left/right/up/down/monitor_spec` | Focus monitor by direction or [monitor spec](/docs/configuration/monitors#monitor-spec-format). | +| `tagmon` | `left/right/up/down/monitor_spec,[keeptag]` | Move window to monitor by direction or [monitor spec](/docs/configuration/monitors#monitor-spec-format). `keeptag` is 0 or 1. | + +### Layouts + +| Command | Param | Description | +| :--- | :--- | :--- | +| `setlayout` | `name` | Switch to layout (e.g., `scroller`, `tile`). | +| `switch_layout` | - | Cycle through available layouts. | +| `incnmaster` | `+1/-1` | Increase/Decrease number of master windows. | +| `setmfact` | `+0.05` | Increase/Decrease master area size. | +| `set_proportion` | `float` | Set scroller window proportion (0.0–1.0). | +| `switch_proportion_preset` | - | Cycle proportion presets of scroller window. | +| `scroller_stack` | `left/right/up/down` | Move window inside/outside scroller stack by direction. | +| `incgaps` | `+/-value` | Adjust gap size. | +| `togglegaps` | - | Toggle gaps. | +| `dwindle_toggle_split_direction` | - | Toggle split direction in dwindle layout. | + +### System + +| Command | Param | Description | +| :--- | :--- | :--- | +| `spawn` | `cmd` | Execute a command. | +| `spawn_shell` | `cmd` | Execute shell command (supports pipes `\|`). | +| `spawn_on_empty` | `cmd,tagnumber` | Open command on empty tag. | +| `reload_config` | - | Hot-reload configuration. | +| `quit` | - | Exit mangowm. | +| `toggleoverview` | - | Toggle overview mode. | +| `create_virtual_output` | - | Create a headless monitor (for VNC/Sunshine). | +| `destroy_all_virtual_output` | - | Destroy all virtual monitors. | +| `toggleoverlay` | - | Toggle overlay state for the focused window. | +| `toggle_trackpad_enable` | - | Toggle trackpad enable. | +| `setkeymode` | `mode` | Set keymode. | +| `switch_keyboard_layout` | `[index]` | Switch keyboard layout. Optional index (0, 1, 2...) to switch to specific layout. | +| `setoption` | `key,value` | Set config option temporarily. | +| `disable_monitor` | `monitor_spec` | Shutdown monitor. Accepts a [monitor spec](/docs/configuration/monitors#monitor-spec-format). | +| `enable_monitor` | `monitor_spec` | Power on monitor. Accepts a [monitor spec](/docs/configuration/monitors#monitor-spec-format). | +| `toggle_monitor` | `monitor_spec` | Toggle monitor power. Accepts a [monitor spec](/docs/configuration/monitors#monitor-spec-format). | + +### Media Controls + +> **Warning:** Some keyboards don't send standard media keys. Run `wev` and press your key to check the exact key name. + +#### Brightness + +Requires: `brightnessctl` + +```ini +bind=NONE,XF86MonBrightnessUp,spawn,brightnessctl s +2% +bind=SHIFT,XF86MonBrightnessUp,spawn,brightnessctl s 100% +bind=NONE,XF86MonBrightnessDown,spawn,brightnessctl s 2%- +bind=SHIFT,XF86MonBrightnessDown,spawn,brightnessctl s 1% +``` + +#### Volume + +Requires: `wpctl` (WirePlumber) + +```ini +bind=NONE,XF86AudioRaiseVolume,spawn,wpctl set-volume @DEFAULT_SINK@ 5%+ +bind=NONE,XF86AudioLowerVolume,spawn,wpctl set-volume @DEFAULT_SINK@ 5%- +bind=NONE,XF86AudioMute,spawn,wpctl set-mute @DEFAULT_SINK@ toggle +bind=SHIFT,XF86AudioMute,spawn,wpctl set-mute @DEFAULT_SOURCE@ toggle +``` + +#### 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 | +| :--- | :--- | :--- | +| `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. | diff --git a/docs/v0.13.0/bindings/meta.json b/docs/v0.13.0/bindings/meta.json new file mode 100644 index 00000000..f1b629b6 --- /dev/null +++ b/docs/v0.13.0/bindings/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Bindings & Input", + "pages": ["keys", "mouse-gestures"] +} diff --git a/docs/v0.13.0/bindings/mouse-gestures.md b/docs/v0.13.0/bindings/mouse-gestures.md new file mode 100644 index 00000000..c4a36889 --- /dev/null +++ b/docs/v0.13.0/bindings/mouse-gestures.md @@ -0,0 +1,116 @@ +--- +title: Mouse & Gestures +description: Configure mouse buttons, scrolling, gestures, and lid switches. +--- + +## Mouse Bindings + +Assign actions to mouse button presses with optional modifier keys. + +### Syntax + +```ini +mousebind=MODIFIERS,BUTTON,COMMAND,PARAMETERS +``` + +- **Modifiers**: `SUPER`, `CTRL`, `ALT`, `SHIFT`, `NONE`. Combine with `+` (e.g., `SUPER+CTRL`) +- **Buttons**: `btn_left`, `btn_right`, `btn_middle`, `btn_side`, `btn_extra`, `btn_forward`, `btn_back`, `btn_task` + +> **Warning:** When modifiers are set to `NONE`, only `btn_middle` works in normal mode. `btn_left` and `btn_right` only work in overview mode. + +### Examples + +```ini +# Window manipulation +mousebind=SUPER,btn_left,moveresize,curmove +mousebind=SUPER,btn_right,moveresize,curresize +mousebind=SUPER+CTRL,btn_right,killclient + +# Overview mode (requires NONE modifier) +mousebind=NONE,btn_left,toggleoverview,-1 +mousebind=NONE,btn_right,killclient,0 +mousebind=NONE,btn_middle,togglemaximizescreen,0 +``` + +--- + +## Axis Bindings + +Map scroll wheel movements to actions for workspace and window navigation. + +### Syntax + +```ini +axisbind=MODIFIERS,DIRECTION,COMMAND,PARAMETERS +``` + +- **Direction**: `UP`, `DOWN`, `LEFT`, `RIGHT` + +### Examples + +```ini +axisbind=SUPER,UP,viewtoleft_have_client +axisbind=SUPER,DOWN,viewtoright_have_client +``` + +--- + +## Gesture Bindings + +Enable touchpad swipe gestures for navigation and window management. + +### Syntax + +```ini +gesturebind=MODIFIERS,DIRECTION,FINGERS,COMMAND,PARAMETERS +``` + +- **Direction**: `up`, `down`, `left`, `right` +- **Fingers**: `3` or `4` + +> **Info:** Gestures require proper touchpad configuration. See [Input Devices](/docs/configuration/input) for touchpad settings like `tap_to_click` and `disable_while_typing`. + +### Examples + +```ini +# 3-finger: Window focus +gesturebind=none,left,3,focusdir,left +gesturebind=none,right,3,focusdir,right +gesturebind=none,up,3,focusdir,up +gesturebind=none,down,3,focusdir,down + +# 4-finger: Workspace navigation +gesturebind=none,left,4,viewtoleft_have_client +gesturebind=none,right,4,viewtoright_have_client +gesturebind=none,up,4,toggleoverview +gesturebind=none,down,4,toggleoverview +``` + +--- + +## Switch Bindings + +Trigger actions on hardware events like laptop lid open/close. + +### Syntax + +```ini +switchbind=FOLD_STATE,COMMAND,PARAMETERS +``` + +- **Fold State**: `fold` (lid closed), `unfold` (lid opened) + +> **Warning:** Disable system lid handling in `/etc/systemd/logind.conf`: +> +> ```ini +> HandleLidSwitch=ignore +> HandleLidSwitchExternalPower=ignore +> HandleLidSwitchDocked=ignore +> ``` + +### Examples + +```ini +switchbind=fold,spawn,swaylock -f -c 000000 +switchbind=unfold,spawn,wlr-dpms on +``` diff --git a/docs/v0.13.0/configuration/basics.md b/docs/v0.13.0/configuration/basics.md new file mode 100644 index 00000000..7afa343b --- /dev/null +++ b/docs/v0.13.0/configuration/basics.md @@ -0,0 +1,87 @@ +--- +title: Basic Configuration +description: Learn how to configure mangowm files, environment variables, and autostart scripts. +--- + +## Configuration File + +mangowm uses a simple configuration file format. By default, it looks for a configuration file in `~/.config/mango/`. + +1. **Locate Default Config** + + A fallback configuration is provided at `/etc/mango/config.conf`. You can use this as a reference. + +2. **Create User Config** + + Copy the default config to your local config directory to start customizing. + + ```bash + mkdir -p ~/.config/mango + cp /etc/mango/config.conf ~/.config/mango/config.conf + ``` + +3. **Launch with Custom Config (Optional)** + + If you prefer to keep your config elsewhere, you can launch mango with the `-c` flag. + + ```bash + mango -c /path/to/your_config.conf + ``` + +### Sub-Configuration + +To keep your configuration organized, you can split it into multiple files and include them using the `source` keyword. + +```ini +# Import keybindings from a separate file +source=~/.config/mango/bind.conf + +# Relative paths work too +source=./theme.conf + +# Optional: ignore if file doesn't exist (useful for shared configs) +source-optional=~/.config/mango/optional.conf +``` + +### Validate Configuration + +You can check your configuration for errors without starting mangowm: + +```bash +mango -c /path/to/config.conf -p +``` + +Use with `source-optional` for shared configs across different setups. + +## Environment Variables + +You can define environment variables directly within your config file. These are set before the window manager fully initializes. + +> **Warning:** Environment variables defined here will be **reset** every time you reload the configuration. + +```ini +env=QT_IM_MODULES,wayland;fcitx +env=XMODIFIERS,@im=fcitx +``` + +## Autostart + +mangowm can automatically run commands or scripts upon startup. There are two modes for execution: + +| Command | Behavior | Usage Case | +| :--- | :--- | :--- | +| `exec-once` | Runs **only once** when mangowm starts. | Status bars, Wallpapers, Notification daemons | +| `exec` | Runs **every time** the config is reloaded. | Scripts that need to refresh settings | + +### Example Setup + +```ini +# Start the status bar once +exec-once=waybar + +# Set wallpaper +exec-once=swaybg -i ~/.config/mango/wallpaper/room.png + +# Reload a custom script on config change +exec=bash ~/.config/mango/reload-settings.sh +``` diff --git a/docs/v0.13.0/configuration/index.mdx b/docs/v0.13.0/configuration/index.mdx new file mode 100644 index 00000000..2bcd3a7e --- /dev/null +++ b/docs/v0.13.0/configuration/index.mdx @@ -0,0 +1,21 @@ +--- +title: Configuration +description: Configure mangowm with config files, environment variables, and autostart. +icon: Settings +--- + +Configure mangowm through config files, environment variables, and autostart. + +<Cards> + +<Card href="/docs/configuration/basics" title="Basics" description="Config files, env vars, exec-once, exec" /> + +<Card href="/docs/configuration/monitors" title="Monitors" description="Monitor settings and resolution" /> + +<Card href="/docs/configuration/input" title="Input" description="Keyboard, mouse, and touchpad" /> + +<Card href="/docs/configuration/xdg-portals" title="XDG Portals" description="File pickers and notifications" /> + +<Card href="/docs/configuration/miscellaneous" title="Miscellaneous" description="Additional options" /> + +</Cards> \ No newline at end of file diff --git a/docs/v0.13.0/configuration/input.md b/docs/v0.13.0/configuration/input.md new file mode 100644 index 00000000..ee12906a --- /dev/null +++ b/docs/v0.13.0/configuration/input.md @@ -0,0 +1,161 @@ +--- +title: Input Devices +description: Configure keyboard layouts, mouse sensitivity, and touchpad gestures. +--- + +## Device Configuration + +mangowm provides granular control over different input devices. + +### Keyboard Settings + +Control key repeat rates and layout rules. + +| Setting | Type | Default | Description | +| :--- | :--- | :--- | :--- | +| `repeat_rate` | `int` | `25` | How many times a key repeats per second. | +| `repeat_delay` | `int` | `600` | Delay (ms) before a held key starts repeating. | +| `numlockon` | `0` or `1` | `0` | Enable NumLock on startup. | +| `xkb_rules_rules` | `string` | - | XKB rules file (e.g., `evdev`, `base`). Usually auto-detected. | +| `xkb_rules_model` | `string` | - | Keyboard model (e.g., `pc104`, `macbook`). | +| `xkb_rules_layout` | `string` | - | Keyboard layout code (e.g., `us`, `de`, `us,de`). | +| `xkb_rules_variant` | `string` | - | Layout variant (e.g., `dvorak`, `colemak`, `intl`). | +| `xkb_rules_options` | `string` | - | XKB options (e.g., `caps:escape`, `ctrl:nocaps`). | + +**Example:** + +```ini +repeat_rate=40 +repeat_delay=300 +numlockon=1 +xkb_rules_layout=us,de +xkb_rules_variant=dvorak +xkb_rules_options=caps:escape,ctrl:nocaps +``` + +--- + +### Mouse Settings + +Configuration for external mice. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `mouse_natural_scrolling` | `0` | Invert scrolling direction. | +| `mouse_accel_profile` | `2` | `0` (None), `1` (Flat), `2` (Adaptive). | +| `mouse_accel_speed` | `0.0` | Speed adjustment (-1.0 to 1.0). | +| `left_handed` | `0` | Swap left and right buttons. | +| `axis_scroll_factor` | `1.0` | Scroll factor for axis scroll speed (0.1–10.0). | +--- + +### Trackpad Settings + +Specific settings for laptop touchpads. Some settings may require a relogin to take effect. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `disable_trackpad` | `0` | Set to `1` to disable the trackpad entirely. | +| `tap_to_click` | `1` | Tap to trigger a left click. | +| `tap_and_drag` | `1` | Tap and hold to drag items. | +| `trackpad_natural_scrolling` | `0` | Invert scrolling direction (natural scrolling). | +| `trackpad_accel_profile` | `2` | `0` (None), `1` (Flat), `2` (Adaptive). | +| `trackpad_accel_speed` | `0.0` | Speed adjustment (-1.0 to 1.0). | +| `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). | +| `send_events_mode` | `0` | `0` (Enabled), `1` (Disabled), `2` (Disabled on external mouse). | +| `drag_lock` | `1` | Lock dragging after tapping. | +| `disable_while_typing` | `1` | Disable trackpad while typing. | +| `left_handed` | `0` | Swap left/right buttons. | +| `middle_button_emulation` | `0` | Emulate middle button. | +| `swipe_min_threshold` | `1` | Minimum swipe threshold when use gesture. | +| `button_map` | `0` | `0` (Left/right/middle), `1` (Left/middle/right). | +| `trackpad_scroll_factor` | `1.0` | Scroll factor for trackpad scroll speed (0.1–10.0). | +--- + +**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. + - `2` — Edge scrolling: send scroll events when a finger moves along the bottom or right edge. + - `4` — Button scrolling: send scroll events when a button is held and the device moves along a scroll axis. + +- `click_method` values: + - `0` — No software click emulation. + - `1` — Button areas: use software-defined areas on the touchpad to generate button events. + - `2` — Clickfinger: the number of fingers determines which button is pressed. + +- `mouse_accel_profile` or `trackpad_scroll_profile` values: + - `0` — No acceleration. + - `1` — Flat: no dynamic acceleration. Pointer speed = original input speed × (1 + `mouse_accel_speed`). + - `2` — Adaptive: slow movement results in less acceleration, fast movement results in more. + +- `button_map` values: + - `0` — 1/2/3 finger tap maps to left / right / middle. + - `1` — 1/2/3 finger tap maps to left / middle / right. + +- `send_events_mode` values: + - `0` — Send events from this device normally. + - `1` — Do not send events from this device. + - `2` — Disable this device when an external pointer device is plugged in. + +--- +--- + +## Keyboard Layout Switching + +To bind multiple layouts and toggle between them, define the layouts in `xkb_rules_layout` and use `xkb_rules_options` to set a toggle key combination. Then bind `switch_keyboard_layout` to trigger a switch. + +```ini +# Define two layouts: US QWERTY and US Dvorak +xkb_rules_layout=us,us +xkb_rules_variant=,dvorak +xkb_rules_options=grp:lalt_lshift_toggle +``` + +Or bind it manually to a key: + +```ini +# Bind Alt+Shift_L to cycle keyboard layout +bind=alt,shift_l,switch_keyboard_layout +``` + +Use `mmsg -g -k` to query the current keyboard layout at any time. + +--- + +## Input Method Editor (IME) + +To use Fcitx5 or IBus, set these environment variables in your config file. + +> **Info:** These settings require a restart of the window manager to take effect. + +**For Fcitx5:** + +```ini +env=GTK_IM_MODULE,fcitx +env=QT_IM_MODULE,fcitx +env=QT_IM_MODULES,wayland;fcitx +env=SDL_IM_MODULE,fcitx +env=XMODIFIERS,@im=fcitx +env=GLFW_IM_MODULE,ibus +``` + +**For IBus:** + +```ini +env=GTK_IM_MODULE,ibus +env=QT_IM_MODULE,ibus +env=XMODIFIERS,@im=ibus +``` diff --git a/docs/v0.13.0/configuration/meta.json b/docs/v0.13.0/configuration/meta.json new file mode 100644 index 00000000..bc209b4e --- /dev/null +++ b/docs/v0.13.0/configuration/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Configuration", + "pages": ["basics", "monitors", "input", "xdg-portals", "miscellaneous"] +} diff --git a/docs/v0.13.0/configuration/miscellaneous.md b/docs/v0.13.0/configuration/miscellaneous.md new file mode 100644 index 00000000..e1be2907 --- /dev/null +++ b/docs/v0.13.0/configuration/miscellaneous.md @@ -0,0 +1,50 @@ +--- +title: Miscellaneous +description: Advanced settings for XWayland, focus behavior, and system integration. +--- + +## System & Hardware + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `xwayland_persistence` | `1` | Keep XWayland running even when no X11 apps are open (reduces startup lag). | +| `syncobj_enable` | `0` | Enable `drm_syncobj` timeline support (helps with gaming stutter/lag). **Requires restart.** | +| `allow_lock_transparent` | `0` | Allow the lock screen to be transparent. | +| `allow_shortcuts_inhibit` | `1` | Allow shortcuts to be inhibited by clients. | +| `vrr` | - | Set via [monitor rule](/docs/configuration/monitors#monitor-rules). | + +## Focus & Input + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `focus_on_activate` | `1` | Automatically focus windows when they request activation. | +| `sloppyfocus` | `1` | Focus follows the mouse cursor. | +| `warpcursor` | `1` | Warp the cursor to the center of the window when focus changes via keyboard. | +| `cursor_hide_timeout` | `0` | Hide the cursor after `N` seconds of inactivity (`0` to disable). | +| `drag_tile_to_tile` | `0` | Allow dragging a tiled window onto another to swap their positions. | +| `drag_tile_small` | `1` | Allow dragging a tiled window temporarily to small size.| +| `drag_corner` | `3` | Corner for drag-to-tile detection (0: none, 1–3: corners, 4: auto-detect). | +| `drag_warp_cursor` | `1` | Warp cursor when dragging windows to tile. | +| `axis_bind_apply_timeout` | `100` | Timeout (ms) for detecting consecutive scroll events for axis bindings. | + +## Multi-Monitor & Tags + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `focus_cross_monitor` | `0` | Allow directional focus to cross monitor boundaries. | +| `exchange_cross_monitor` | `0` | Allow exchanging clients across monitor boundaries. | +| `focus_cross_tag` | `0` | Allow directional focus to cross into other tags. | +| `view_current_to_back` | `0` | Toggling the current tag switches back to the previously viewed tag. | +| `scratchpad_cross_monitor` | `0` | Share the scratchpad pool across all monitors. | +| `single_scratchpad` | `1` | Only allow one scratchpad (named or standard) to be visible at a time. | + +## Window Behavior + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `enable_floating_snap` | `0` | Snap floating windows to edges or other windows. | +| `snap_distance` | `30` | Max distance (pixels) to trigger floating snap. | +| `no_border_when_single` | `0` | Remove window borders when only one window is visible on the tag. | +| `idleinhibit_ignore_visible` | `0` | Allow invisible clients (e.g., background audio players) to inhibit idle. | +| `drag_tile_refresh_interval` | `8.0` | Interval (1.0–16.0) to refresh tiled window resize during drag. Too small may cause application lag. | +| `drag_floating_refresh_interval` | `8.0` | Interval (1.0–16.0) to refresh floating window resize during drag. Too small may cause application lag. | \ No newline at end of file diff --git a/docs/v0.13.0/configuration/monitors.md b/docs/v0.13.0/configuration/monitors.md new file mode 100644 index 00000000..28ef240b --- /dev/null +++ b/docs/v0.13.0/configuration/monitors.md @@ -0,0 +1,276 @@ +--- +title: Monitors +description: Manage display outputs, resolution, scaling, and tearing. +--- + +## Monitor Rules + +You can configure each display output individually using the `monitorrule` keyword. + +**Syntax:** + +```ini +monitorrule=name:Values,Parameter:Values,Parameter:Values +``` + +> **Info:** If any of the matching fields (`name`, `make`, `model`, `serial`) are set, **all** of the set ones must match to be considered a match. Use `wlr-randr` to get your monitor's name, make, model, and serial. + +### Parameters + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `name` | string | Any | Match by monitor name (supports regex) | +| `make` | string | Any | Match by monitor manufacturer | +| `model` | string | Any | Match by monitor model | +| `serial` | string | Any | Match by monitor serial number | +| `width` | integer | 0-9999 | Monitor width | +| `height` | integer | 0-9999 | Monitor height | +| `refresh` | float | 0.001-9999.0 | Monitor refresh rate | +| `x` | integer | 0-99999 | X position | +| `y` | integer | 0-99999 | Y position | +| `scale` | float | 0.01-100.0 | Monitor scale | +| `vrr` | integer | 0, 1 | Enable variable refresh rate | +| `rr` | integer | 0-7 | Monitor transform | +| `custom` | integer | 0, 1 | Enable custom mode (not supported on all displays — may cause black screen) | + +### Transform Values + +| Value | Rotation | +| :--- | :--- | +| `0` | No transform | +| `1` | 90° counter-clockwise | +| `2` | 180° counter-clockwise | +| `3` | 270° counter-clockwise | +| `4` | 180° vertical flip | +| `5` | Flip + 90° counter-clockwise | +| `6` | Flip + 180° counter-clockwise | +| `7` | Flip + 270° counter-clockwise | + +> **Critical:** If you use XWayland applications, **never use negative coordinates** for your monitor positions. This is a known XWayland bug that causes click events to malfunction. Always arrange your monitors starting from `0,0` and extend into positive coordinates. + +> **Note:** that "name" is a regular expression. If you want an exact match, you need to add `^` and `$` to the beginning and end of the expression, for example, `^eDP-1$` matches exactly the string `eDP-1`. + +### Examples + +```ini +# Laptop display: 1080p, 60Hz, positioned at origin +monitorrule=name:^eDP-1$,width:1920,height:1080,refresh:60,x:0,y:10 + +# Match by make and model instead of name +monitorrule=make:Chimei Innolux Corporation,model:0x15F5,width:1920,height:1080,refresh:60,x:0,y:0 + +# Virtual monitor with pattern matching +monitorrule=name:HEADLESS-.*,width:1920,height:1080,refresh:60,x:1926,y:0,scale:1,rr:0,vrr:0 +``` + +--- + +## Monitor Spec Format + +Several commands (`focusmon`, `tagmon`, `disable_monitor`, `enable_monitor`, `toggle_monitor`, `viewcrossmon`, `tagcrossmon`) accept a **monitor_spec** string to identify a monitor. + +**Format:** + +```text +name:xxx&&make:xxx&&model:xxx&&serial:xxx +``` + +- Any field can be omitted and there is no order requirement. +- If all fields are omitted, the string is treated as the monitor name directly (e.g., `eDP-1`). +- Use `wlr-randr` to find your monitor's name, make, model, and serial. + +**Examples:** + +```bash +# By name (shorthand) +mmsg -d toggle_monitor,eDP-1 + +# By make and model +mmsg -d toggle_monitor,make:Chimei Innolux Corporation&&model:0x15F5 + +# By serial +mmsg -d toggle_monitor,serial:12345678 +``` + +--- + +## Tearing (Game Mode) + +Tearing allows games to bypass the compositor's VSync for lower latency. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `allow_tearing` | `0` | Global tearing control: `0` (Disable), `1` (Enable), `2` (Fullscreen only). | + +### Configuration + +**Enable Globally:** + +```ini +allow_tearing=1 +``` + +**Enable per Window:** + +Use a window rule to force tearing for specific games. + +```ini +windowrule=force_tearing:1,title:vkcube +``` + +### Tearing Behavior Matrix + +| `force_tearing` \ `allow_tearing` | DISABLED (0) | ENABLED (1) | FULLSCREEN_ONLY (2) | +| :--- | :--- | :--- | :--- | +| **UNSPECIFIED** (0) | Not Allowed | Follows tearing_hint | Only fullscreen follows tearing_hint | +| **ENABLED** (1) | Not Allowed | Allowed | Only fullscreen allowed | +| **DISABLED** (2) | Not Allowed | Not Allowed | Not Allowed | + +### Graphics Card Compatibility + +> **Warning:** Some graphics cards require setting the `WLR_DRM_NO_ATOMIC` environment variable before mango starts to successfully enable tearing. + +Add this to `/etc/environment` and reboot: + +```bash +WLR_DRM_NO_ATOMIC=1 +``` + +Or run mango with the environment variable: + +```bash +WLR_DRM_NO_ATOMIC=1 mango +``` + +--- + +## GPU Compatibility + +If mango cannot display correctly or shows a black screen, try selecting a specific GPU: + +```bash +# Use a single GPU +WLR_DRM_DEVICES=/dev/dri/card1 mango + +# Use multiple GPUs +WLR_DRM_DEVICES=/dev/dri/card0:/dev/dri/card1 mango +``` + +Some GPUs have compatibility issues with `syncobj_enable=1` — it may crash apps like `kitty` that use syncobj. Set `WLR_DRM_NO_ATOMIC=1` in `/etc/environment` and reboot to resolve this. + +--- + +## Power Management + +You can control monitor power using the `mmsg` IPC tool. + +```bash +# Turn off +mmsg -d disable_monitor,eDP-1 + +# Turn on +mmsg -d enable_monitor,eDP-1 + +# Toggle +mmsg -d toggle_monitor,eDP-1 +``` + +You can also use `wlr-randr` for monitor management: + +```bash +# Turn off monitor +wlr-randr --output eDP-1 --off + +# Turn on monitor +wlr-randr --output eDP-1 --on + +# Show all monitors +wlr-randr +``` + +--- + +## Screen Scale + +### Without Global Scale (Recommended) + +- If you do not use XWayland apps, you can use monitor rules or `wlr-randr` to set a global monitor scale. +- If you are using XWayland apps, it is not recommended to set a global monitor scale. + +You can set scale like this, for example with a 1.4 factor. + +**Dependencies:** + +```bash +yay -S xorg-xrdb +yay -S xwayland-satellite +``` + +**In config file:** + +```ini +env=QT_AUTO_SCREEN_SCALE_FACTOR,1 +env=QT_WAYLAND_FORCE_DPI,140 +``` + +**In autostart:** + +```bash +echo "Xft.dpi: 140" | xrdb -merge +gsettings set org.gnome.desktop.interface text-scaling-factor 1.4 +``` + +**Edit autostart for XWayland:** + +```bash +# Start xwayland +/usr/sbin/xwayland-satellite :11 & +# Apply scale 1.4 for xwayland +sleep 0.5s && echo "Xft.dpi: 140" | xrdb -merge +``` + +### Using xwayland-satellite to Prevent Blurry XWayland Apps + +If you use fractional scaling, you can use `xwayland-satellite` to automatically scale XWayland apps to prevent blurriness, for example with a scale of 1.4. + +**Dependencies:** + +```bash +yay -S xwayland-satellite +``` + +**In config file:** + +```ini +env=DISPLAY,:2 +exec-once=xwayland-satellite :2 +monitorrule=name:eDP-1,width:1920,height:1080,refresh:60,x:0,y:0,scale:1.4,vrr:0,rr:0 +``` + +> **Warning:** Use a `DISPLAY` value other than `:1` to avoid conflicting with mangowm. + +--- + +## Virtual Monitors + +You can create and manage virtual displays through IPC commands: + +```bash +# Create virtual output +mmsg -d create_virtual_output + +# Destroy all virtual outputs +mmsg -d destroy_all_virtual_output +``` + +You can configure virtual monitors using `wlr-randr`: + +```bash +# Show all monitors +wlr-randr + +# Configure virtual monitor +wlr-randr --output HEADLESS-1 --pos 1921,0 --scale 1 --custom-mode 1920x1080@60Hz +``` + +Virtual monitors can be used for screen sharing with tools like [Sunshine](https://github.com/LizardByte/Sunshine) and [Moonlight](https://github.com/moonlight-stream/moonlight-android), allowing other devices to act as extended monitors. \ No newline at end of file diff --git a/docs/v0.13.0/configuration/xdg-portals.md b/docs/v0.13.0/configuration/xdg-portals.md new file mode 100644 index 00000000..27819ad8 --- /dev/null +++ b/docs/v0.13.0/configuration/xdg-portals.md @@ -0,0 +1,76 @@ +--- +title: XDG Portals +description: Set up screen sharing, clipboard, keyring, and file pickers using XDG portals. +--- + +## Portal Configuration + +You can customize portal settings via the following paths: + +- **User Configuration (Priority):** `~/.config/xdg-desktop-portal/mango-portals.conf` +- **System Fallback:** `/usr/share/xdg-desktop-portal/mango-portals.conf` + +> **Warning:** If you previously added `dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP=wlroots` to your config, remove it. Mango now handles this automatically. + +## Screen Sharing + +To enable screen sharing (OBS, Discord, WebRTC), you need `xdg-desktop-portal-wlr`. + +1. **Install Dependencies** + + `pipewire`, `pipewire-pulse`, `xdg-desktop-portal-wlr` + +2. **Optional: Add to autostart** + + In some situations the portal may not start automatically. You can add this to your autostart script to ensure it launches: + + ```bash + /usr/lib/xdg-desktop-portal-wlr & + ``` + +3. **Restart your computer** to apply changes. + +### Known Issues + +- **Window screen sharing:** Some applications may have issues sharing individual windows. See [#184](https://github.com/mangowm/mango/pull/184) for workarounds. + +- **Screen recording lag:** If you experience stuttering during screen recording, see [xdg-desktop-portal-wlr#351](https://github.com/emersion/xdg-desktop-portal-wlr/issues/351). + +## Clipboard Manager + +Use `cliphist` to manage clipboard history. + +**Dependencies:** `wl-clipboard`, `cliphist`, `wl-clip-persist` + +**Autostart Config:** + +```bash +# Keep clipboard content after app closes +wl-clip-persist --clipboard regular --reconnect-tries 0 & + +# Watch clipboard and store history +wl-paste --type text --watch cliphist store & +``` + +## GNOME Keyring + +If you need to store passwords or secrets (e.g., for VS Code or Minecraft launchers), install `gnome-keyring`. + +**Configuration:** + +Add the following to `~/.config/xdg-desktop-portal/mango-portals.conf`: + +```ini +[preferred] +default=gtk +org.freedesktop.impl.portal.ScreenCast=wlr +org.freedesktop.impl.portal.Screenshot=wlr +org.freedesktop.impl.portal.Secret=gnome-keyring +org.freedesktop.impl.portal.Inhibit=none +``` + +## File Picker (File Selector) + +**Dependencies:** `xdg-desktop-portal`, `xdg-desktop-portal-gtk` + +Reboot your computer once to apply. \ No newline at end of file diff --git a/docs/v0.13.0/faq.md b/docs/v0.13.0/faq.md new file mode 100644 index 00000000..9c9288de --- /dev/null +++ b/docs/v0.13.0/faq.md @@ -0,0 +1,100 @@ +--- +title: FAQ +description: Frequently asked questions and troubleshooting. +--- + +### How do I arrange tiled windows with my mouse? + +You can enable the `drag_tile_to_tile` option in your config. This allows you to drag a tiled window onto another to swap them. + +```ini +drag_tile_to_tile=1 +``` + +--- + +### Why is my background blurry or why does blur look wrong? + +Blur applies to the transparent areas of windows. To disable it entirely, set `blur=0`. + +If you are experiencing **performance issues with blur**, make sure `blur_optimized=1` (the default). This caches the wallpaper as the blur background, which is much cheaper on the GPU: + +```ini +blur_optimized=1 +``` + +--- + +### Blur shows my wallpaper instead of the real background content + +This is expected behavior when `blur_optimized=1` (the default). The optimizer caches the wallpaper to reduce GPU load — windows will blur against the wallpaper rather than the actual content stacked beneath them. + +If you want blur to composite against the true background (i.e., show whatever is actually behind the window), set: + +```ini +blur_optimized=0 +``` + +> **Warning:** Disabling `blur_optimized` significantly increases GPU consumption and may cause rendering lag, especially on lower-end hardware. + +--- + +### My games are lagging or stuttering + +Try enabling **SyncObj** timeline support. + +```ini +syncobj_enable=1 +``` + +--- + +### My games have high input latency + +You can enable **Tearing** (similar to VSync off). + +First, enable it globally: + +```ini +allow_tearing=1 +``` + +Then force it for your specific game: + +```ini +windowrule=force_tearing:1,title:Counter-Strike 2 +``` + +> **Warning:** Some graphics cards require setting `WLR_DRM_NO_ATOMIC=1` before mango starts for tearing to work. Add it to `/etc/environment` and reboot, or launch mango with `WLR_DRM_NO_ATOMIC=1 mango`. See [Monitors — Tearing](/docs/configuration/monitors#tearing-game-mode) for details. + +--- + +### How do I use pipes `|` in spawn commands? + +The standard `spawn` command does not support shell pipes directly. You must use `spawn_shell` instead. + +```ini +bind=SUPER,P,spawn_shell,echo "hello" | rofi -dmenu +``` + +--- + +### Certain key combinations do not work on my keyboard layout. + +`bind` automatically converts keysym to keycode, which is compatible with most layouts but can sometimes be imprecise. If a key combination is not triggering, use the **keycode** directly instead of the key name. + +Run `wev` and press the key to find its keycode, then use it in your bind: + +```ini +# Instead of: +bind=ALT,q,killclient + +# Use the keycode (e.g., code:24 = q on most layouts): +bind=ALT,code:24,killclient +``` + +You can also use `binds` (the `s` flag) to match by keysym instead of keycode: + +```ini +binds=ALT,q,killclient +``` diff --git a/docs/v0.13.0/index.md b/docs/v0.13.0/index.md new file mode 100644 index 00000000..d308370d --- /dev/null +++ b/docs/v0.13.0/index.md @@ -0,0 +1,42 @@ +--- +title: Introduction +description: A lightweight and feature-rich Wayland compositor based on dwl. +--- + + +**mango** is a Wayland compositor based on [dwl](https://codeberg.org/dwl/dwl/). It aims to be as lightweight as `dwl` and can be built completely within a few seconds, without compromising on functionality. + +> **Philosophy:** **Lightweight & Fast**: mango is designed to be minimal yet functional. It compiles in seconds and offers a robust set of features out of the box. + +## Feature Highlights + +Beyond basic window management, mangowm provides a rich set of features designed for a modern Wayland experience. + +- **[Animations](/docs/visuals/animations)** — Smooth, customizable animations for opening, moving, closing windows and tag switching. +- **[Layouts](/docs/window-management/layouts)** — Supports Scroller, Master-Stack, Monocle, Grid, Deck, and more, with per-tag layouts. +- **[Visual Effects](/docs/visuals/effects)** — Built-in blur, shadows, corner radius, and opacity effects powered by scenefx. +- **[IPC & Scripting](/docs/ipc)** — Control the compositor externally with robust IPC support for custom scripts and widgets. + +## Additional Features + +- **XWayland Support** — Excellent compatibility for legacy X11 applications. +- **Tag System** — Uses tags instead of workspaces, allowing separate window layouts for each tag. +- **Input Methods** — Great support for text input v2/v3 (Fcitx5, IBus). +- **Window States** — Rich states including swallow, minimize, maximize, fullscreen, and overlay. +- **Hot-Reload Config** — Simple external configuration that supports hot-reloading without restarting. +- **Scratchpads** — Support for both Sway-like and named scratchpads. + +## Community + +- **[Join the mangowm Discord](https://discord.gg/CPjbDxesh5)** — Chat with the community, get support, share your setup, and stay updated with the latest mangowm news. +- **[Join the GitHub Discussions](https://github.com/mangowm/mango/discussions)** — Ask questions, request features, report issues, or share ideas directly with contributors and other users. + +## Acknowledgements + +This project is built upon the hard work of several open-source projects: + +- **[wlroots](https://gitlab.freedesktop.org/wlroots/wlroots)** — Implementation of the Wayland protocol. +- **[mwc](https://github.com/nikoloc/mwc)** — Basal window animation reference. +- **[dwl](https://codeberg.org/dwl/dwl)** — Basal dwl features. +- **[sway](https://github.com/swaywm/sway)** — Sample implementation of the Wayland protocol. +- **[scenefx](https://github.com/wlrfx/scenefx)** — Library to simplify adding window effects. diff --git a/docs/v0.13.0/installation.md b/docs/v0.13.0/installation.md new file mode 100644 index 00000000..c5d4936c --- /dev/null +++ b/docs/v0.13.0/installation.md @@ -0,0 +1,308 @@ +--- +title: Installation +description: Install mangowm on AerynOS, Arch, Fedora, Gentoo, Guix System, NixOS, PikaOS, or build from source. +--- + +## Package Installation + +mangowm is available as a pre-built package on several distributions. Choose your distribution below. + +--- + +### AerynOS + +mangowm is available in the **AerynOS package repository**. + +You can install it using the `moss` package manager: + +```bash +sudo moss install mangowm +``` + +--- + +### Arch Linux + +mangowm is available in the **Arch User Repository (AUR)**. + +You can install it using an AUR helper like `yay` or `paru`: + +```bash +yay -S mangowm-git +``` + +> **Tip:** This package pulls the latest git version, ensuring you have the newest features and fixes. + +--- + +### Fedora + +The package is in the third-party **Terra repository**. First, add the Terra Repository. + +> **Warning:** Both commands require root privileges. Use `sudo` if needed. + +```bash +dnf install --nogpgcheck --repofrompath 'terra,https://repos.fyralabs.com/terra$releasever' terra-release +``` + +Then, install the package: + +```bash +dnf install mangowm +``` + +--- + +### Gentoo + +The package is hosted in the community-maintained **GURU** repository. + +1. **Add the GURU repository** + + ```bash + emerge --ask --verbose eselect-repository + eselect repository enable guru + emerge --sync guru + ``` + +2. **Unmask packages** + Add the required packages to your `package.accept_keywords` file: + - `gui-libs/scenefx` + - `gui-wm/mangowm` + +3. **Install mango** + ```bash + emerge --ask --verbose gui-wm/mangowm + ``` + +--- + +### Guix System + +The package definition is described in the source repository. + +1. **Add mango channel** + Add to `$HOME/.config/guix/channels.scm`: + + ```scheme + (cons (channel + (name 'mangowm) + (url "https://github.com/mangowm/mango.git") + (branch "main")) + %default-channels) + ``` + +2. **Install** + After running `guix pull`, you can install mangowm: + + ```bash + guix install mangowm + ``` + + Or add it to your system configuration using the mangowm module: + + ```scheme + (use-modules (mangowm)) + + (packages (cons* + mangowm-git + ... ;; Other packages + %base-packages)) + ``` + +> **Tip:** For more information, see the [Guix System documentation](https://guix.gnu.org/manual/devel/en/html_node/Channels.html). + +--- + +### NixOS + +The repository provides a Flake with a NixOS module. + +1. **Add flake input** + + ```nix + # flake.nix + { + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + mangowm = { + url = "github:mangowm/mango"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + # other inputs ... + }; + } + ``` + +2. **Import the NixOS module** + + **Option A — Import in `configuration.nix`:** + + ```nix + # configuration.nix (or any other file that you import) + {inputs, ...}: { + imports = [ + inputs.mangowm.nixosModules.mango + # .. other imports ... + ]; + + # ... + } + ``` + + **Option B — Import directly in flake:** + + ```nix + # flake.nix + { + # ... + + outputs = { self, nixpkgs, mangowm, ...}@inputs: let + inherit (nixpkgs) lib; + # ... + in { + nixosConfigurations.YourHostName = lib.nixosSystem { + modules = [ + mangowm.nixosModules.mango # or inputs.mangowm.nixosModules.mango + # other imports ... + ]; + }; + } + } + ``` + +3. **Enable the module** + + ```nix + # configuration.nix (or any other file that you import) + { + programs.mango.enable = true; + } + ``` + +4. **Start mango on login** + + The following are common examples. Refer to the official NixOS documentation for full configuration options. + + **Option A — greetd:** Autologin on first start; login screen after logout. + + ```nix + services.greetd = { + enable = true; + settings = { + initial_session = { + command = "mango"; + user = "your-username"; # auto-login on first start, no password required + }; + default_session = { + command = "${pkgs.greetd.tuigreet}/bin/tuigreet --cmd mango"; + user = "greeter"; + }; + }; + }; + ``` + + **Option B — Display manager autologin:** Autologin via an existing display manager (e.g. SDDM, GDM). [`addLoginEntry`](/docs/nix-options#addloginentry) (default: `true`) automatically registers mango as a session. + + ```nix + services.displayManager = { + defaultSession = "mango"; # derived from mango.desktop filename + autoLogin = { + enable = true; + user = "your-username"; + }; + }; + ``` + + **Option C — getty autologin:** No login screen, boots directly into mango on TTY1. + + For bash/zsh: + + ```nix + services.getty.autologinUser = "your-username"; + + environment.loginShellInit = '' + [ "$(tty)" = /dev/tty1 ] && exec mango + ''; + ``` + + For fish: + + ```nix + services.getty.autologinUser = "your-username"; + + programs.fish.loginShellInit = '' + if test (tty) = /dev/tty1 + exec mango + end + ''; + ``` + +5. **All available options** + + See [Nix Module Options](/docs/nix-options) for the full list of NixOS and Home Manager options. + +--- + +### PikaOS + +mangowm is available in the **PikaOS package repository**. + +You can install it using the `pikman` package manager: + +```bash +pikman install mangowm +``` + +--- + +## Building from Source + +If your distribution isn't listed above, or you want the latest unreleased changes, you can build mangowm from source. + +> **Info:** Ensure the following dependencies are installed before proceeding: +> +> - `wayland` +> - `wayland-protocols` +> - `libinput` +> - `libdrm` +> - `libxkbcommon` +> - `pixman` +> - `libdisplay-info` +> - `libliftoff` +> - `hwdata` +> - `seatd` +> - `pcre2` +> - `xorg-xwayland` +> - `libxcb` + +You will need to build `wlroots` and `scenefx` manually as well. + +1. **Build wlroots** + Clone and install the specific version required (check README for latest version). + + ```bash + git clone -b 0.19.3 https://gitlab.freedesktop.org/wlroots/wlroots.git + cd wlroots + meson build -Dprefix=/usr + sudo ninja -C build install + ``` + +2. **Build scenefx** + This library handles the visual effects. + + ```bash + git clone -b 0.4.1 https://github.com/wlrfx/scenefx.git + cd scenefx + meson build -Dprefix=/usr + sudo ninja -C build install + ``` + +3. **Build mangowm** + Finally, compile the compositor itself. + ```bash + git clone https://github.com/mangowm/mango.git + cd mango + meson build -Dprefix=/usr + sudo ninja -C build install + ``` diff --git a/docs/v0.13.0/ipc.md b/docs/v0.13.0/ipc.md new file mode 100644 index 00000000..8bb0f5c1 --- /dev/null +++ b/docs/v0.13.0/ipc.md @@ -0,0 +1,154 @@ +--- +title: IPC +description: Control mangowm programmatically using mmsg. +--- + +## Introduction + +mangowm includes a powerful IPC (Inter-Process Communication) tool called `mmsg`. This allows you to query the window manager's state, watch for events, and execute commands from external scripts. + +## Basic Usage + +The general syntax for `mmsg` is: + +```bash +mmsg [-OTLq] +mmsg [-o <output>] -s [-t <tags>] [-l <layout>] [-c <tags>] [-d <cmd>,<arg1>,<arg2>,<arg3>,<arg4>,<arg5>] +mmsg [-o <output>] (-g | -w) [-OotlcvmfxekbA] +``` + +### Options + +| Flag | Description | +| :--- | :--- | +| `-q` | Quit mangowm. | +| `-g` | **Get** values (tags, layout, focused client). | +| `-s` | **Set** values (switch tags, layouts). | +| `-w` | **Watch** mode (streams events). | +| `-O` | Get all output (monitor) information. | +| `-T` | Get number of tags. | +| `-L` | Get all available layouts. | +| `-o` | Select output (monitor). | +| `-t` | Get/set selected tags (set with `[+-^.]`). | +| `-l` | Get/set current layout. | +| `-c` | Get title and appid of focused client. | +| `-v` | Get visibility of statusbar. | +| `-m` | Get fullscreen status. | +| `-f` | Get floating status. | +| `-d` | **Dispatch** an internal command. | +| `-x` | Get focused client geometry. | +| `-e` | Get the name of the last focused layer. | +| `-k` | Get current keyboard layout. | +| `-b` | Get current keybind mode. | +| `-A` | Get scale factor of monitor. | + +## Examples + +### Tag Management + +You can perform arithmetic on tags using the `-t` flag with `-s` (set). + +```bash +# Switch to Tag 1 +mmsg -t 1 + +# Add Tag 2 to current view (Multiview) +mmsg -s -t 2+ + +# Remove Tag 2 from current view +mmsg -s -t 2- + +# Toggle Tag 2 +mmsg -s -t 2^ +``` + +### Layouts + +Switch layouts programmatically. Layout codes: `S` (Scroller), `T` (Tile), `G` (Grid), `M` (Monocle), `K` (Deck), `CT` (Center Tile), `RT` (Right Tile), `VS` (Vertical Scroller), `VT` (Vertical Tile), `VG` (Vertical Grid), `VK` (Vertical Deck), `DW` (Dwindle). + +```bash +# Switch to Scroller +mmsg -l "S" + +# Switch to Tile +mmsg -l "T" +``` + +### Dispatching Commands + +Any command available in `config.conf` keybindings can be run via IPC. + +```bash +# Close the focused window +mmsg -d killclient + +# Resize window by +10 width +mmsg -d resizewin,+10,0 + +# Toggle fullscreen +mmsg -d togglefullscreen + +# Disable a monitor power +mmsg -d disable_monitor,eDP-1 +``` + +### Monitoring & Status + +Use `-g` or `-w` to build custom status bars or automation scripts. + +```bash +# Watch for all message changes +mmsg -w + +# Get all messages without watch +mmsg -g + +# Watch focused client appid and title +mmsg -w -c + +# Get all available outputs +mmsg -O + +# Get all tags message +mmsg -g -t + +# Get current focused client message +mmsg -g -c + +# Get current keyboard layout +mmsg -g -k + +# Get current keybind mode +mmsg -g -b + +# Get scale factor of current monitor +mmsg -g -A +``` + +#### Tag Message Format + +- State: 0 → none, 1 → active, 2 → urgent + +Example output: + +| Monitor | Tag Number | Tag State | Clients in Tag | Focused Client | +|---------|------------|-----------|----------------|----------------| +| eDP-1 | tag 2 | 0 | 1 | 0 | + +| Monitor | occupied tags mask | active tags mask | urgent tags mask | +|---------|--------------------|------------------|------------------| +| eDP-1 | 14 | 6 | 0 | + +## Virtual Monitors + +You can create headless outputs for screen mirroring or remote desktop access (e.g., Sunshine/Moonlight). + +```bash +# Create a virtual output +mmsg -d create_virtual_output + +# Configure it (set resolution) +wlr-randr --output HEADLESS-1 --pos 1920,0 --mode 1920x1080@60Hz + +# Destroy all virtual outputs +mmsg -d destroy_all_virtual_output \ No newline at end of file diff --git a/docs/v0.13.0/meta.json b/docs/v0.13.0/meta.json new file mode 100644 index 00000000..95507bff --- /dev/null +++ b/docs/v0.13.0/meta.json @@ -0,0 +1,22 @@ +{ + "title": "v0.13.0", + "description": "v0.13.0 release", + "root": true, + "pages": [ + "---Getting Started---", + "index", + "installation", + "quick-start", + "---Configuration---", + "configuration", + "visuals", + "window-management", + "bindings", + "---Examples---", + "screenshot", + "---Reference---", + "nix-options", + "ipc", + "faq" + ] +} diff --git a/docs/v0.13.0/nix-options.md b/docs/v0.13.0/nix-options.md new file mode 100644 index 00000000..2537d9d8 --- /dev/null +++ b/docs/v0.13.0/nix-options.md @@ -0,0 +1,519 @@ +--- +title: Nix Module Options +description: NixOS and Home Manager configuration options for mangowm. +--- + +> **Note:** This document is automatically generated from the Nix module source code. + +## NixOS + +**System-level options via `programs.mango`.** + +### enable + + + +Whether to enable mango, a wayland compositor based on dwl\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +false +``` + + + +*Example:* + +```nix +true +``` + +*Declared by:* + - [\<mango/nix/nixos-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/nixos-modules.nix) + + + +### package + + + +The mango package to use + + + +*Type:* +package + + + +*Default:* + +```nix +<derivation mango-nightly> +``` + +*Declared by:* + - [\<mango/nix/nixos-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/nixos-modules.nix) + + + +### addLoginEntry + + + +Whether to add a login entry to the display manager for mango\. Only has effect if a display manager is configured (e\.g\. SDDM, GDM via ` services.displayManager `)\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +true +``` + +*Declared by:* + - [\<mango/nix/nixos-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/nixos-modules.nix) + +## Home Manager + +**Configure mangowm declaratively via `wayland.windowManager.mango`.** + +### enable + + + +Whether to enable mangowm, a Wayland compositor based on dwl\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +false +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### package + + + +The mango package to use + + + +*Type:* +package + + + +*Default:* + +```nix +<derivation mango-nightly> +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### autostart_sh + + + +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:* +strings concatenated with “\\n” + + + +*Default:* + +```nix +"" +``` + + + +*Example:* + +```nix +'' + waybar & + dunst & +'' +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### bottomPrefixes + + + +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\. + + + +*Type:* +list of string + + + +*Default:* + +```nix +[ ] +``` + + + +*Example:* + +```nix +[ + "source" +] +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### extraConfig + + + +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\. + + + +*Type:* +strings concatenated with “\\n” + + + +*Default:* + +```nix +"" +``` + + + +*Example:* + +```nix +'' + # Advanced config that doesn't fit structured format + special_option = 1 +'' +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### settings + + + +Mango configuration written in Nix\. Entries with the same key +should be written as lists\. Variables and colors names should be +quoted\. See [https://mangowm\.github\.io/docs](https://mangowm\.github\.io/docs) 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 ` + +Keymodes (submaps) are supported via the special ` keymode ` attribute\. Each keymode +is a nested attribute set under ` keymode ` that contains its own bindings\. + + + +*Type:* +Mango configuration value + + + +*Default:* + +```nix +{ } +``` + + + +*Example:* + +```nix +{ + # 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" + "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" + ]; + }; + }; +} + +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### systemd\.enable + + + +Whether to enable ` mango-session.target ` on +mango startup\. This links to +` graphical-session.target `\. +Some important environment variables will be imported to systemd +and dbus user environment before reaching the target, including + + - ` DISPLAY ` + - ` WAYLAND_DISPLAY ` + - ` XDG_CURRENT_DESKTOP ` + - ` XDG_SESSION_TYPE ` + - ` NIXOS_OZONE_WL ` + You can extend this list using the ` systemd.variables ` option\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +true +``` + + + +*Example:* + +```nix +false +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### systemd\.extraCommands + + + +Extra commands to run after D-Bus activation\. + + + +*Type:* +list of string + + + +*Default:* + +```nix +[ + "systemctl --user reset-failed" + "systemctl --user start mango-session.target" +] +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### systemd\.variables + + + +Environment variables imported into the systemd and D-Bus user environment\. + + + +*Type:* +list of string + + + +*Default:* + +```nix +[ + "DISPLAY" + "WAYLAND_DISPLAY" + "XDG_CURRENT_DESKTOP" + "XDG_SESSION_TYPE" + "NIXOS_OZONE_WL" + "XCURSOR_THEME" + "XCURSOR_SIZE" +] +``` + + + +*Example:* + +```nix +[ + "--all" +] +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### systemd\.xdgAutostart + + + +Whether to enable autostart of applications using +` systemd-xdg-autostart-generator(8) ` +\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +false +``` + + + +*Example:* + +```nix +true +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### topPrefixes + + + +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\. + + + +*Type:* +list of string + + + +*Default:* + +```nix +[ ] +``` + + + +*Example:* + +```nix +[ + "source" +] +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + diff --git a/docs/v0.13.0/quick-start.md b/docs/v0.13.0/quick-start.md new file mode 100644 index 00000000..bc192474 --- /dev/null +++ b/docs/v0.13.0/quick-start.md @@ -0,0 +1,88 @@ +--- +title: Quick Start +description: Basic configuration and first steps with mangowm. +--- + +Now that you have mangowm installed, let's get your environment set up. + +## Initial Setup + +1. **Create Configuration Directory** + + mangowm looks for configuration files in `~/.config/mango/`. + + ```bash + mkdir -p ~/.config/mango + ``` + +2. **Copy Default Config** + + A default configuration file is provided at `/etc/mango/config.conf`. Copy it to your local directory to start customizing. + + ```bash + cp /etc/mango/config.conf ~/.config/mango/config.conf + ``` + +3. **Launch mangowm** + + You can now start the compositor from your TTY. + + ```bash + mango + ``` + + Optional: To specify a custom config file path: + + ```bash + mango -c /path/to/your/config.conf + ``` + +## Essential Keybindings + +mangowm uses the following keybinds by default: + +| Key Combination | Action | +| :--- | :--- | +| `Alt` + `Return` | Open Terminal (defaults to `foot`) | +| `Alt` + `Space` | Open Launcher (defaults to `rofi`) | +| `Alt` + `Q` | Close (Kill) the active window | +| `Super` + `M` | Quit mangowm | +| `Super` + `F` | Toggle Fullscreen | +| `Alt` + `Arrow Keys` | Move focus (Left, Right, Up, Down) | +| `Ctrl` + `1-9` | Switch to Tag 1-9 | +| `Alt` + `1-9` | Move window to Tag 1-9 | + +> **Warning:** Some default bindings rely on specific tools like `foot` (terminal) and `rofi` (launcher). Ensure you have them installed or update your `config.conf` to use your preferred alternatives. + +## Recommended Tools + +To get a fully functional desktop experience, we recommend installing the following components: + +| Category | Recommended Tools | +| :--- | :--- | +| Application Launcher | rofi, bemenu, wmenu, fuzzel | +| Terminal Emulator | foot, wezterm, alacritty, kitty, ghostty | +| Status Bar | waybar, eww, quickshell, ags | +| Desktop Shell | Noctalia, DankMaterialShell | +| Wallpaper Setup | awww(swww), swaybg | +| Notification Daemon | swaync, dunst, mako | +| Desktop Portal | xdg-desktop-portal, xdg-desktop-portal-wlr, xdg-desktop-portal-gtk | +| Clipboard | wl-clipboard, wl-clip-persist, cliphist | +| Gamma Control / Night Light | wlsunset, gammastep | +| Miscellaneous | xfce-polkit, wlogout | + +## Example Configuration + +Check out the [example configuration](https://github.com/DreamMaoMao/mango-config) by the creator of mangowm, including complete setups for mangowm, Waybar, Rofi, and more. + +```bash +git clone https://github.com/DreamMaoMao/mango-config.git ~/.config/mango +``` + +## Next Steps + +Now that you are up and running, dive deeper into customizing mangowm: + +- [Configure Monitors](/docs/configuration/monitors) — Set up resolution, scaling, and multi-monitor layouts. +- [Window Rules](/docs/window-management/rules#window-rules) — Define how specific applications should behave. +- [Appearance](/docs/visuals/theming) — Customize colors, borders, gaps, and effects. diff --git a/docs/v0.13.0/screenshot.md b/docs/v0.13.0/screenshot.md new file mode 100644 index 00000000..f07cdf0c --- /dev/null +++ b/docs/v0.13.0/screenshot.md @@ -0,0 +1,213 @@ +--- + +title: Screenshots +description: Example screenshot keybindings and capture workflows for mangowm. + +--- + +mangowm does not include a built-in screenshot tool. This keeps the compositor lean. +Instead, compose your own workflow from small Wayland utilities and bind them to keys; + +| Tool | Purpose | +| :--- | :--- | +| [`grim`](https://github.com/emersion/grim) | Capture the screen or a region to a file | +| [`slurp`](https://github.com/emersion/slurp) | Interactively select a region for `grim` | +| [`wl-copy`](https://github.com/bugaevc/wl-clipboard) | Copy screenshots directly to the clipboard | +| [`satty`](https://github.com/gabm/Satty) | Annotate screenshots before saving | +| [`wayfreeze`](https://github.com/nicbk/wayfreeze) | Freeze the screen before capture | + +Install the required with your package manager or from source. + +`grim` writes to the file path you give it, but **will not create missing directories**. Create one first: + +```bash +mkdir -p ~/Pictures/Screenshots +``` + +Any directory works. `~/Pictures/Screenshots/` is just a convention. + +## Quick Binds + +Short, single-step commands can be placed directly in `config.conf` with `spawn_shell`. +No script file needed. + +### Fullscreen + +Captures the entire display. + +```ini +bind=NONE,Print,spawn_shell,grim $HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png +``` + +### Region + +Select an area with `slurp` before capturing. + +```ini +bind=SHIFT,Print,spawn_shell,g=$(slurp -d) && [ -n "$g" ] && grim -g "$g" $HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png +``` + +### Pointer + +Captures the full screen including the cursor. + +```ini +bind=ALT,Print,spawn_shell,grim -c $HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png +``` + +### Clipboard + +Captures to a temporary file and copies the image to the clipboard; no file is saved. + +```ini +bind=CTRL,Print,spawn_shell,f=$(mktemp -t screenshot-XXXXXX.png) && grim "$f" && wl-copy < "$f" && rm -f "$f" +``` + +### Annotate + +Captures and opens `satty` for drawing before saving. + +```ini +bind=SUPER,Print,spawn_shell,f=$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png && grim "$f" && satty --filename "$f" --output-filename "$f" --actions-on-enter save-to-file --early-exit +``` + +## Script Binds + +When a command involves multi-step logic, geometry parsing, FIFOs, or screen freezing, +move it into a script and invoke it with `spawn` instead of `spawn_shell`. + +Create the scripts directory first: + +```bash +mkdir -p ~/.config/mango/scripts/screenshot +``` + +### Window + +Uses `mmsg` (ships with mango) to capture the focused window. + +`~/.config/mango/scripts/screenshot/window.sh`: + +```bash +#!/usr/bin/env bash +geometry=$(mmsg -x | awk '/x / {x=$3} /y / {y=$3} /width / {w=$3} /height / {h=$3} END {print x","y" "w"x"h}') +[ -z "$geometry" ] && exit 1 +grim -g "$geometry" "$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png" +``` + +```ini +bind=CTRL+SHIFT,Print,spawn,$HOME/.config/mango/scripts/screenshot/window.sh +``` + +### Freeze + +Freezes the screen with `wayfreeze` before capturing. + +`~/.config/mango/scripts/screenshot/freeze.sh`: + +```bash +#!/usr/bin/env bash +pipe=$(mktemp -u).fifo +mkfifo "$pipe" +wayfreeze --after-freeze-timeout 100 --after-freeze-cmd "echo > $pipe" & +wayfreeze_pid=$! +read -r < "$pipe" +grim "$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png" +kill "$wayfreeze_pid" 2>/dev/null +rm -f "$pipe" +``` + +```ini +bind=CTRL+SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/freeze.sh +``` + +### Freeze + Region + +Freeze, then select a region with `slurp`. Cleans up on cancel. + +`~/.config/mango/scripts/screenshot/freeze-region.sh`: + +```bash +#!/usr/bin/env bash +pipe=$(mktemp -u).fifo +mkfifo "$pipe" +wayfreeze --after-freeze-timeout 100 --after-freeze-cmd "echo > $pipe" & +wayfreeze_pid=$! +read -r < "$pipe" +geometry=$(slurp -d) +if [[ -z "$geometry" ]]; then + kill "$wayfreeze_pid" 2>/dev/null + rm -f "$pipe" + exit 1 +fi +grim -g "$geometry" "$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png" +kill "$wayfreeze_pid" 2>/dev/null +rm -f "$pipe" +``` + +```ini +bind=SHIFT+SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/freeze-region.sh +``` + +Make all three scripts executable: + +```bash +chmod +x ~/.config/mango/scripts/screenshot/*.sh +``` + +## All-in-One Script + +Prefer fewer files? A single script with subcommands covers every mode above. +Place it in the same directory and use it in place of the individual scripts. + +`~/.config/mango/scripts/screenshot/screenshot.sh`: + +```bash +#!/usr/bin/env bash +set -euo pipefail +mkdir -p "$HOME/Pictures/Screenshots" +filepath="$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png" + +case "${1:-fullscreen}" in + region) + g=$(slurp -d); [ -z "$g" ] && exit 1 + grim -g "$g" "$filepath" ;; + window) + g=$(mmsg -x | awk '/x / {x=$3} /y / {y=$3} /width / {w=$3} /height / {h=$3} END {print x","y" "w"x"h}') + [ -z "$g" ] && exit 1 + grim -g "$g" "$filepath" ;; + freeze) + p=$(mktemp -u).fifo; mkfifo "$p" + wayfreeze --after-freeze-timeout 100 --after-freeze-cmd "echo > $p" & wp=$! + read -r < "$p"; grim "$filepath" + kill "$wp" 2>/dev/null; rm -f "$p" ;; + freeze-region) + p=$(mktemp -u).fifo; mkfifo "$p" + wayfreeze --after-freeze-timeout 100 --after-freeze-cmd "echo > $p" & wp=$! + read -r < "$p"; g=$(slurp -d) + if [ -z "$g" ]; then kill "$wp" 2>/dev/null; rm -f "$p"; exit 1; fi + grim -g "$g" "$filepath" + kill "$wp" 2>/dev/null; rm -f "$p" ;; + annotate) + grim "$filepath"; satty --filename "$filepath" --output-filename "$filepath" --actions-on-enter save-to-file --early-exit ;; + *) grim "$filepath" ;; +esac +``` + +Make the script executable: + + +```bash +chmod +x ~/.config/mango/scripts/screenshot/screenshot.sh +``` + +Then add the binds to `config.conf`: + +```ini +bind=NONE,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh fullscreen +bind=SHIFT,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh region +bind=CTRL+SHIFT,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh window +bind=CTRL+SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh freeze +bind=SHIFT+SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh freeze-region +bind=SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh annotate +``` diff --git a/docs/v0.13.0/visuals/animations.md b/docs/v0.13.0/visuals/animations.md new file mode 100644 index 00000000..76477e05 --- /dev/null +++ b/docs/v0.13.0/visuals/animations.md @@ -0,0 +1,108 @@ +--- +title: Animations +description: Configure smooth transitions for windows and layers. +--- + +## Enabling Animations + +mangowm supports animations for both standard windows and layer shell surfaces (like bars and notifications). + +```ini +animations=1 +layer_animations=1 +``` + +## Animation Types + +You can define different animation styles for opening and closing windows and layer surfaces. + +Available types: `slide`, `zoom`, `fade`, `none`. + +```ini +animation_type_open=zoom +animation_type_close=slide +layer_animation_type_open=slide +layer_animation_type_close=slide +``` + +## Fade Settings + +Control the fade-in and fade-out effects for animations. + +```ini +animation_fade_in=1 +animation_fade_out=1 +fadein_begin_opacity=0.5 +fadeout_begin_opacity=0.5 +``` + +- `animation_fade_in` — Enable fade-in effect (0: disable, 1: enable) +- `animation_fade_out` — Enable fade-out effect (0: disable, 1: enable) +- `fadein_begin_opacity` — Starting opacity for fade-in animations (0.0–1.0) +- `fadeout_begin_opacity` — Starting opacity for fade-out animations (0.0–1.0) + +## Zoom Settings + +Adjust the zoom ratios for zoom animations. + +```ini +zoom_initial_ratio=0.4 +zoom_end_ratio=0.8 +``` + +- `zoom_initial_ratio` — Initial zoom ratio +- `zoom_end_ratio` — End zoom ratio + +## Durations + +Control the speed of animations (in milliseconds). + +| Setting | Type | Default | Description | +| :--- | :--- | :--- | :--- | +| `animation_duration_move` | integer | `500` | Move animation duration (ms) | +| `animation_duration_open` | integer | `400` | Open animation duration (ms) | +| `animation_duration_tag` | integer | `300` | Tag animation duration (ms) | +| `animation_duration_close` | integer | `300` | Close animation duration (ms) | +| `animation_duration_focus` | integer | `0` | Focus change (opacity transition) animation duration (ms) | + +```ini +animation_duration_move=500 +animation_duration_open=400 +animation_duration_tag=300 +animation_duration_close=300 +animation_duration_focus=0 +``` + +## Custom Bezier Curves + +Bezier curves determine the "feel" of an animation (e.g., linear vs. bouncy). The format is `x1,y1,x2,y2`. + +You can visualize and generate curve values using online tools like [cssportal.com](https://www.cssportal.com/css-cubic-bezier-generator/) or [easings.net](https://easings.net). + +| Setting | Type | Default | Description | +| :--- | :--- | :--- | :--- | +| `animation_curve_open` | string | `0.46,1.0,0.29,0.99` | Open animation bezier curve | +| `animation_curve_move` | string | `0.46,1.0,0.29,0.99` | Move animation bezier curve | +| `animation_curve_tag` | string | `0.46,1.0,0.29,0.99` | Tag animation bezier curve | +| `animation_curve_close` | string | `0.46,1.0,0.29,0.99` | Close animation bezier curve | +| `animation_curve_focus` | string | `0.46,1.0,0.29,0.99` | Focus change (opacity transition) animation bezier curve | +| `animation_curve_opafadein` | string | `0.46,1.0,0.29,0.99` | Open opacity animation bezier curve | +| `animation_curve_opafadeout` | string | `0.5,0.5,0.5,0.5` | Close opacity animation bezier curve | + +```ini +animation_curve_open=0.46,1.0,0.29,0.99 +animation_curve_move=0.46,1.0,0.29,0.99 +animation_curve_tag=0.46,1.0,0.29,0.99 +animation_curve_close=0.46,1.0,0.29,0.99 +animation_curve_focus=0.46,1.0,0.29,0.99 +animation_curve_opafadein=0.46,1.0,0.29,0.99 +animation_curve_opafadeout=0.5,0.5,0.5,0.5 +``` + +## Tag Animation Direction + +Control the direction of tag switch animations. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `tag_animation_direction` | `1` | Tag animation direction (1: horizontal, 0: vertical) | \ No newline at end of file diff --git a/docs/v0.13.0/visuals/effects.md b/docs/v0.13.0/visuals/effects.md new file mode 100644 index 00000000..23c1f206 --- /dev/null +++ b/docs/v0.13.0/visuals/effects.md @@ -0,0 +1,82 @@ +--- +title: Window Effects +description: Add visual polish with blur, shadows, and opacity. +--- + +## Blur + +Blur creates a frosted glass effect for transparent windows. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `blur` | `0` | Enable blur for windows. | +| `blur_layer` | `0` | Enable blur for layer surfaces (like bars/docks). | +| `blur_optimized` | `1` | Caches the wallpaper and blur background, significantly reducing GPU usage. Disabling it will significantly increase GPU consumption and may cause rendering lag. **Highly recommended.** | +| `blur_params_radius` | `5` | The strength (radius) of the blur. | +| `blur_params_num_passes` | `1` | Number of passes. Higher = smoother but more expensive. | +| `blur_params_noise` | `0.02` | Blur noise level. | +| `blur_params_brightness` | `0.9` | Blur brightness adjustment. | +| `blur_params_contrast` | `0.9` | Blur contrast adjustment. | +| `blur_params_saturation` | `1.2` | Blur saturation adjustment. | + +> **Warning:** Blur has a relatively high impact on performance. If your hardware is limited, it is not recommended to enable it. If you experience lag with blur on, ensure `blur_optimized=1` — disabling it will significantly increase GPU consumption and may cause rendering lag. To disable blur entirely, set `blur=0`. + +--- + +## Shadows + +Drop shadows help distinguish floating windows from the background. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `shadows` | `0` | Enable shadows. | +| `layer_shadows` | `0` | Enable shadows for layer surfaces. | +| `shadow_only_floating` | `1` | Only draw shadows for floating windows (saves performance). | +| `shadows_size` | `10` | Size of the shadow. | +| `shadows_blur` | `15` | Shadow blur amount. | +| `shadows_position_x` | `0` | Shadow X offset. | +| `shadows_position_y` | `0` | Shadow Y offset. | +| `shadowscolor` | `0x000000ff` | Color of the shadow. | + +```ini +# Example shadows configuration +shadows=1 +layer_shadows=1 +shadow_only_floating=1 +shadows_size=12 +shadows_blur=15 +shadows_position_x=0 +shadows_position_y=0 +shadowscolor=0x000000ff +``` + +--- + +## Opacity & Corner Radius + +Control the transparency and roundness of your windows. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `border_radius` | `0` | Window corner radius in pixels. | +| `border_radius_location_default` | `0` | Corner radius location: `0` (all), `1` (top-left), `2` (top-right), `3` (bottom-left), `4` (bottom-right), `5` (closest corner). | +| `no_radius_when_single` | `0` | Disable radius if only one window is visible. | +| `focused_opacity` | `1.0` | Opacity for the active window (0.0 - 1.0). | +| `unfocused_opacity` | `1.0` | Opacity for inactive windows (0.0 - 1.0). | + +```ini +# Window corner radius in pixels +border_radius=0 + +# Corner radius location (0=all, 1=top-left, 2=top-right, 3=bottom-left, 4=bottom-right) +border_radius_location_default=0 + +# Disable radius if only one window is visible +no_radius_when_single=0 + +# Opacity for the active window (0.0 - 1.0) +focused_opacity=1.0 + +# Opacity for inactive windows +unfocused_opacity=1.0 +``` diff --git a/docs/v0.13.0/visuals/index.mdx b/docs/v0.13.0/visuals/index.mdx new file mode 100644 index 00000000..f71ae2f8 --- /dev/null +++ b/docs/v0.13.0/visuals/index.mdx @@ -0,0 +1,19 @@ +--- +title: Visuals +description: Customize borders, colors, effects, and animations. +icon: Palette +--- + +Customize the look of your desktop. + +<Cards> + +<Card href="/docs/visuals/theming" title="Theming" description="Borders, colors, and cursor" /> + +<Card href="/docs/visuals/status-bar" title="Status Bar" description="Built-in status bar" /> + +<Card href="/docs/visuals/effects" title="Effects" description="Blur, shadows, rounded corners" /> + +<Card href="/docs/visuals/animations" title="Animations" description="Window and tag animations" /> + +</Cards> \ No newline at end of file diff --git a/docs/v0.13.0/visuals/meta.json b/docs/v0.13.0/visuals/meta.json new file mode 100644 index 00000000..58723c4e --- /dev/null +++ b/docs/v0.13.0/visuals/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Visuals", + "pages": ["theming", "status-bar", "effects", "animations"] +} diff --git a/docs/v0.13.0/visuals/status-bar.md b/docs/v0.13.0/visuals/status-bar.md new file mode 100644 index 00000000..f2924e83 --- /dev/null +++ b/docs/v0.13.0/visuals/status-bar.md @@ -0,0 +1,141 @@ +--- +title: Status Bar +description: Configure Waybar for mangowm. +--- + +## Module Configuration + +mangowm is compatible with Waybar's `ext/workspaces` module (Wayland standard) or the `dwl/tags` module. We recommend `ext/workspaces` for the best experience. + +> **Tip:** You can also use the `dwl/tags` module, but `ext/workspaces` provides better integration with mangowm's features. The `ext/workspaces` module requires **Waybar > 0.14.0**. + +### `config.jsonc` + +Add the following to your Waybar configuration: + +```jsonc +{ + "modules-left": [ + "ext/workspaces", + "dwl/window" + ], + "ext/workspaces": { + "format": "{icon}", + "ignore-hidden": true, + "on-click": "activate", + "on-click-right": "deactivate", + "sort-by-id": true + }, + "dwl/window": { + "format": "[{layout}] {title}" + } +} +``` + +## Styling + +You can style the tags using standard CSS in `style.css`. + +### `style.css` + +```css +#workspaces { + border-radius: 4px; + border-width: 2px; + border-style: solid; + border-color: #c9b890; + margin-left: 4px; + padding-left: 10px; + padding-right: 6px; + background: rgba(40, 40, 40, 0.76); +} + +#workspaces button { + border: none; + background: none; + box-shadow: inherit; + text-shadow: inherit; + color: #ddca9e; + padding: 1px; + padding-left: 1px; + padding-right: 1px; + margin-right: 2px; + margin-left: 2px; +} + +#workspaces button.hidden { + color: #9e906f; + background-color: transparent; +} + +#workspaces button.visible { + color: #ddca9e; +} + +#workspaces button:hover { + color: #d79921; +} + +#workspaces button.active { + background-color: #ddca9e; + color: #282828; + margin-top: 5px; + margin-bottom: 5px; + padding-top: 1px; + padding-bottom: 0px; + border-radius: 3px; +} + +#workspaces button.urgent { + background-color: #ef5e5e; + color: #282828; + margin-top: 5px; + margin-bottom: 5px; + padding-top: 1px; + padding-bottom: 0px; + border-radius: 3px; +} + +#tags { + background-color: transparent; +} + +#tags button { + background-color: #fff; + color: #a585cd; +} + +#tags button:not(.occupied):not(.focused) { + font-size: 0; + min-width: 0; + min-height: 0; + margin: -17px; + padding: 0; + color: transparent; + background-color: transparent; +} + +#tags button.occupied { + background-color: #fff; + color: #cdc885; +} + +#tags button.focused { + background-color: rgb(186, 142, 213); + color: #fff; +} + +#tags button.urgent { + background: rgb(171, 101, 101); + color: #fff; +} + +#window { + background-color: rgb(237, 196, 147); + color: rgb(63, 37, 5); +} +``` + +## Complete Configuration Example + +> **Tip:** You can find a complete Waybar configuration for mangowm at [waybar-config](https://github.com/DreamMaoMao/waybar-config). \ No newline at end of file diff --git a/docs/v0.13.0/visuals/theming.md b/docs/v0.13.0/visuals/theming.md new file mode 100644 index 00000000..676c575b --- /dev/null +++ b/docs/v0.13.0/visuals/theming.md @@ -0,0 +1,62 @@ +--- +title: Theming +description: Customize the visual appearance of borders, colors, and the cursor. +--- + +## Dimensions + +Control the sizing of window borders and gaps. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `borderpx` | `4` | Border width in pixels. | +| `gappih` | `5` | Horizontal inner gap (between windows). | +| `gappiv` | `5` | Vertical inner gap. | +| `gappoh` | `10` | Horizontal outer gap (between windows and screen edges). | +| `gappov` | `10` | Vertical outer gap. | + +## Colors + +Colors are defined in `0xRRGGBBAA` hex format. + +```ini +# Background color of the root window +rootcolor=0x323232ff + +# Inactive window border +bordercolor=0x444444ff + +# Drop shadow when dragging windows +dropcolor=0x8FBA7C55 + +# Split window border color in manual dwindle layout +splitcolor=0xEB441EFF + +# Active window border +focuscolor=0xc66b25ff + +# Urgent window border (alerts) +urgentcolor=0xad401fff +``` + +### State-Specific Colors + +You can also color-code windows based on their state: + +| State | Config Key | Default Color | +| :--- | :--- | :--- | +| Maximized | `maximizescreencolor` | `0x89aa61ff` | +| Scratchpad | `scratchpadcolor` | `0x516c93ff` | +| Global | `globalcolor` | `0xb153a7ff` | +| Overlay | `overlaycolor` | `0x14a57cff` | + +> **Tip:** For scratchpad window sizing, see [Scratchpad](/docs/window-management/scratchpad) configuration. + +## Cursor Theme + +Set the size and theme of your mouse cursor. + +```ini +cursor_size=24 +cursor_theme=Adwaita +``` diff --git a/docs/v0.13.0/window-management/index.mdx b/docs/v0.13.0/window-management/index.mdx new file mode 100644 index 00000000..b96c5891 --- /dev/null +++ b/docs/v0.13.0/window-management/index.mdx @@ -0,0 +1,19 @@ +--- +title: Window Management +description: Layouts, rules, and window behavior. +icon: LayoutGrid +--- + +Window management with layouts, rules, and scratchpad support. + +<Cards> + +<Card href="/docs/window-management/layouts" title="Layouts" description="Tile, scroller, monocle, grid, deck" /> + +<Card href="/docs/window-management/rules" title="Rules" description="Window rules and conditions" /> + +<Card href="/docs/window-management/overview" title="Overview" description="Window states and properties" /> + +<Card href="/docs/window-management/scratchpad" title="Scratchpad" description="Quick access to applications" /> + +</Cards> \ No newline at end of file diff --git a/docs/v0.13.0/window-management/layouts.md b/docs/v0.13.0/window-management/layouts.md new file mode 100644 index 00000000..bf5283d7 --- /dev/null +++ b/docs/v0.13.0/window-management/layouts.md @@ -0,0 +1,133 @@ +--- +title: Layouts +description: Configure and switch between different window layouts. +--- + +## Supported Layouts + +mangowm supports a variety of layouts that can be assigned per tag. + +- `tile` +- `scroller` +- `monocle` +- `grid` +- `deck` +- `center_tile` +- `vertical_tile` +- `right_tile` +- `vertical_scroller` +- `vertical_grid` +- `vertical_deck` +- `dwindle` + +--- + +## Scroller Layout + +The Scroller layout positions windows in a scrollable strip, similar to PaperWM. + +### Configuration + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `scroller_structs` | `20` | Width reserved on sides when window ratio is 1. | +| `scroller_default_proportion` | `0.9` | Default width proportion for new windows. | +| `scroller_focus_center` | `0` | Always center the focused window (1 = enable). | +| `scroller_prefer_center` | `0` | Center focused window only if it was outside the view. | +| `scroller_prefer_overspread` | `1` | Allow windows to overspread when there's extra space. | +| `edge_scroller_pointer_focus` | `1` | Focus windows even if partially off-screen. | +| `scroller_proportion_preset` | `0.5,0.8,1.0` | Presets for cycling window widths. | +| `scroller_ignore_proportion_single` | `1` | Ignore proportion adjustments for single windows. | +| `scroller_default_proportion_single` | `1.0` | Default proportion for single windows in scroller. **Requires `scroller_ignore_proportion_single=0` to take effect.** | + +> **Warning:** `scroller_prefer_overspread`, `scroller_focus_center`, and `scroller_prefer_center` interact with each other. Their priority order is: +> +> **scroller_prefer_overspread > scroller_focus_center > scroller_prefer_center** +> +> To ensure a lower-priority setting takes effect, you must set all higher-priority options to `0`. + +```ini +# Example scroller configuration +scroller_structs=20 +scroller_default_proportion=0.9 +scroller_focus_center=0 +scroller_prefer_center=0 +scroller_prefer_overspread=1 +edge_scroller_pointer_focus=1 +scroller_default_proportion_single=1.0 +scroller_proportion_preset=0.5,0.8,1.0 +``` + +--- + +## Master-Stack Layouts + +These settings apply to layouts like `tile` and `center_tile`. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `new_is_master` | `1` | New windows become the master window. | +| `default_mfact` | `0.55` | The split ratio between master and stack areas. | +| `default_nmaster` | `1` | Number of allowed master windows. | +| `smartgaps` | `0` | Disable gaps when only one window is present. | +| `center_master_overspread` | `0` | (Center Tile) Master spreads across screen if no stack exists. | +| `center_when_single_stack` | `1` | (Center Tile) Center master when only one stack window exists. | + +```ini +# Example master-stack configuration +new_is_master=1 +smartgaps=0 +default_mfact=0.55 +default_nmaster=1 +``` + +--- + +## Dwindle Layout + +The Dwindle layout arranges windows as a binary tree of recursive splits. Each new window splits the focused window's container, producing a spiral-like tiling. + +### Configuration + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `dwindle_split_ratio` | `0.5` | Ratio used for new splits (`0.05`–`0.95`). | +| `dwindle_smart_split` | `0` | Pick the split axis from the cursor's position inside the focused window. The new window appears on the cursor's side. | +| `dwindle_hsplit` | `1` | Side-by-side splits: where the new window goes. `0` = follow cursor, `1` = right, `2` = left. | +| `dwindle_vsplit` | `1` | Top/bottom splits: where the new window goes. `0` = follow cursor, `1` = below, `2` = above. | +| `dwindle_preserve_split` | `0` | Keep the sibling's split orientation when a window is closed. | +| `dwindle_smart_resize` | `0` | When dragging to resize, move the split toward the cursor regardless of which side was grabbed. | +| `dwindle_drop_simple_split` | `1` | Drag-to-tile drop preview. `1` = 2-zone preview matching `dwindle_split_ratio`, `0` = 4-quadrant preview. | +| `dwindle_manual_split` | `0` | Manually split windows mode. | + +```ini +# Example dwindle configuration +dwindle_split_ratio=0.5 +dwindle_smart_split=0 +dwindle_hsplit=0 +dwindle_vsplit=0 +dwindle_preserve_split=0 +dwindle_smart_resize=0 +dwindle_drop_simple_split=1 +``` + +--- + +## Switching Layouts +| Setting | Default | Description | +| :--- | :--- | :--- | +| `circle_layout` | - | A comma-separated list of layouts `switch_layout` cycles through,the value sample:`tile,scroller`. | + +You can switch layouts dynamically or set a default for specific tags using [Tag Rules](/docs/window-management/rules#tag-rules). + +**Keybinding Examples:** + +```ini +# Cycle through layouts +circle_layout=grid,scroller,tile +bind=SUPER,n,switch_layout + +# Set specific layout +bind=SUPER,t,setlayout,tile +bind=SUPER,s,setlayout,scroller +``` diff --git a/docs/v0.13.0/window-management/meta.json b/docs/v0.13.0/window-management/meta.json new file mode 100644 index 00000000..e0937d14 --- /dev/null +++ b/docs/v0.13.0/window-management/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Window Management", + "pages": ["layouts", "rules", "overview", "scratchpad"] +} diff --git a/docs/v0.13.0/window-management/overview.md b/docs/v0.13.0/window-management/overview.md new file mode 100644 index 00000000..7da6e690 --- /dev/null +++ b/docs/v0.13.0/window-management/overview.md @@ -0,0 +1,29 @@ +--- +title: Overview +description: Configure the overview mode for window navigation. +--- + +## Overview Settings + +| Setting | Type | Default | Description | +| :--- | :--- | :--- | :--- | +| `hotarea_size` | integer | `10` | Hot area size in pixels. | +| `enable_hotarea` | integer | `1` | Enable hot areas (0: disable, 1: enable). | +| `hotarea_corner` | integer | `2` | Hot area corner (0: top-left, 1: top-right, 2: bottom-left, 3: bottom-right). | +| `ov_tab_mode` | integer | `0` | Overview tab mode (0: disable, 1: enable). | +| `overviewgappi` | integer | `5` | Inner gap in overview mode. | +| `overviewgappo` | integer | `30` | Outer gap in overview mode. | + +### Setting Descriptions + +- `enable_hotarea` — Toggles overview when the cursor enters the configured corner. +- `hotarea_size` — Size of the hot area trigger zone in pixels. +- `hotarea_corner` — Corner that triggers the hot area (0: top-left, 1: top-right, 2: bottom-left, 3: bottom-right). +- `ov_tab_mode` — Circles focus through windows in overview; releasing the mod key exits overview. + +### Mouse Interaction in Overview + +When in overview mode: + +- **Left mouse button** — Jump to (focus) a window. +- **Right mouse button** — Close a window. \ No newline at end of file diff --git a/docs/v0.13.0/window-management/rules.md b/docs/v0.13.0/window-management/rules.md new file mode 100644 index 00000000..4a295157 --- /dev/null +++ b/docs/v0.13.0/window-management/rules.md @@ -0,0 +1,250 @@ +--- +title: Rules +description: Define behavior for specific windows, tags, and layers. +--- + +## Window Rules + +Window rules allow you to set specific properties (floating, opacity, size, animations, etc.) for applications based on their `appid` or `title`. You can set all parameters in one line, and if you both set appid and title, the window will only follow the rules when appid and title both match. + +**Format:** + +```ini +windowrule=Parameter:Values,title:Values +windowrule=Parameter:Values,Parameter:Values,appid:Values,title:Values +``` + +### State & Behavior Parameters + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `appid` | string | Any | Match by application ID, supports regex | +| `title` | string | Any | Match by window title, supports regex | +| `isfloating` | integer | `0` / `1` | Force floating state | +| `isfullscreen` | integer | `0` / `1` | Force fullscreen state | +| `isfakefullscreen` | integer | `0` / `1` | Force fake-fullscreen state (window stays constrained) | +| `isglobal` | integer | `0` / `1` | Open as global window (sticky across tags) | +| `isoverlay` | integer | `0` / `1` | Make it always in top layer | +| `isopensilent` | integer | `0` / `1` | Open without focus | +| `istagsilent` | integer | `0` / `1` | Don't focus if client is not in current view tag | +| `force_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 | +| `noopenmaximized` | integer | `0` / `1` | Window does not open as maximized mode | +| `single_scratchpad` | integer | `0` / `1` (default 1) | Only show one out of named scratchpads or the normal scratchpad | +| `allow_shortcuts_inhibit` | integer | `0` / `1` (default 1) | Allow shortcuts to be inhibited by clients | +| `indleinhibit_when_focus` | integer | `0` / `1` (default 0) | Automatically keep idle inhibit active when this window is focused | + +### Geometry & Position + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `width` | float | 0-9999 | Window width when it becomes a floating window,if the value below 1, it will be the percentage of the screen width,otherwise it will be the pixel value | +| `height` | float | 0-9999 | Window height when it becomes a floating window,if the value below 1, it will be the percentage of the screen height,otherwise it will be the pixel value | +| `offsetx` | integer | -999-999 | X offset from center (%), 100 is the edge of screen with outer gap | +| `offsety` | integer | -999-999 | Y offset from center (%), 100 is the edge of screen with outer gap | +| `monitor` | string | Any | Assign to monitor by [monitor spec](/docs/configuration/monitors#monitor-spec-format) (name, make, model, or serial) | +| `tags` | integer | 1-9 | Assign to specific tag | +| `no_force_center` | integer | `0` / `1` | Window does not force center | +| `isnosizehint` | integer | `0` / `1` | Don't use min size and max size for size hints | + +### Visuals & Decoration + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `noblur` | integer | `0` / `1` | Window does not have blur effect | +| `isnoborder` | integer | `0` / `1` | Remove window border | +| `isnoshadow` | integer | `0` / `1` | Not apply shadow | +| `isnoradius` | integer | `0` / `1` | Not apply corner radius | +| `isnoanimation` | integer | `0` / `1` | Not apply animation | +| `focused_opacity` | integer | `0` / `1` | Window focused opacity | +| `unfocused_opacity` | integer | `0` / `1` | Window unfocused opacity | +| `allow_csd` | integer | `0` / `1` | Allow client side decoration | + +> **Tip:** For detailed visual effects configuration, see the [Window Effects](/docs/visuals/effects) page for blur, shadows, and opacity settings. + +### Layout & Scroller + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `scroller_proportion` | float | 0.1-1.0 | Set scroller proportion | +| `scroller_proportion_single` | float | 0.1-1.0 | Set scroller auto adjust proportion when it is single window | + +> **Tip:** For comprehensive layout configuration, see the [Layouts](/docs/window-management/layouts) page for all layout options and detailed settings. + +### Animation + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `animation_type_open` | string | zoom, slide, fade, none | Set open animation | +| `animation_type_close` | string | zoom, slide, fade, none | Set close animation | +| `nofadein` | integer | `0` / `1` | Window ignores fade-in animation | +| `nofadeout` | integer | `0` / `1` | Window ignores fade-out animation | + +> **Tip:** For detailed animation configuration, see the [Animations](/docs/visuals/animations) page for available types and settings. + +### Terminal & Swallowing + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `isterm` | integer | `0` / `1` | A new GUI window will replace the isterm window when it is opened | +| `noswallow` | integer | `0` / `1` | The window will not replace the isterm window | + +### Global & Special Windows + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `globalkeybinding` | string | `[mod combination][-][key]` | Global keybinding (only works for Wayland apps) | +| `isunglobal` | integer | `0` / `1` | Open as unmanaged global window (for desktop pets or camera windows) | +| `isnamedscratchpad` | integer | `0` / `1` | 0: disable, 1: named scratchpad | + +> **Tip:** For scratchpad usage, see the [Scratchpad](/docs/window-management/scratchpad) page for detailed configuration examples. + +### Performance & Tearing + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `force_tearing` | integer | `0` / `1` | Set window to tearing state, refer to [Tearing](/docs/configuration/monitors#tearing-game-mode) | + +### Examples + +```ini +# Set specific window size and position +windowrule=width:1000,height:900,appid:yesplaymusic,title:Demons + +# Global keybindings for OBS Studio +windowrule=globalkeybinding:ctrl+alt-o,appid:com.obsproject.Studio +windowrule=globalkeybinding:ctrl+alt+n,appid:com.obsproject.Studio +windowrule=isopensilent:1,appid:com.obsproject.Studio + +# Force tearing for games +windowrule=force_tearing:1,title:vkcube +windowrule=force_tearing:1,title:Counter-Strike 2 + +# Named scratchpad for file manager +windowrule=isnamedscratchpad:1,width:1280,height:800,appid:st-yazi + +# Custom opacity for specific apps +windowrule=focused_opacity:0.8,appid:firefox +windowrule=unfocused_opacity:0.6,appid:foot + +# Disable blur for selection tools +windowrule=noblur:1,appid:slurp + +# Position windows relative to screen center +windowrule=offsetx:20,offsety:-30,width:800,height:600,appid:alacritty + +# Send to specific tag and monitor +windowrule=tags:9,monitor:HDMI-A-1,appid:discord + +# Terminal swallowing setup +windowrule=isterm:1,appid:st +windowrule=noswallow:1,appid:foot + +# Disable client-side decorations +windowrule=allow_csd:1,appid:firefox + +# Unmanaged global window (desktop pets, camera) +windowrule=isunglobal:1,appid:cheese + +# Named scratchpad toggle +bind=alt,h,toggle_named_scratchpad,st-yazi,none,st -c st-yazi -e yazi +``` + +--- + +## Tag Rules + +You can set all parameters in one line. If only `id` is set, the rule is followed when the id matches. If any of `monitor_name`, `monitor_make`, `monitor_model`, or `monitor_serial` are set, the rule is followed only if **all** of the set monitor fields match. + +> **Warning:** Layouts set in tag rules have a higher priority than monitor rule layouts. + +**Format:** + +```ini +tagrule=id:Values,Parameter:Values,Parameter:Values +tagrule=id:Values,monitor_name:eDP-1,Parameter:Values,Parameter:Values +tagrule=id:Values,monitor_make:xxx,monitor_model:xxx,Parameter:Values +``` + +> **Tip:** See [Layouts](/docs/window-management/layouts#supported-layouts) for detailed descriptions of each layout type. + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `id` | integer | 0-9 | Match by tag id, 0 means the ~0 tag | +| `monitor_name` | string | monitor name | Match by monitor name | +| `monitor_make` | string | monitor make | Match by monitor manufacturer | +| `monitor_model` | string | monitor model | Match by monitor model | +| `monitor_serial` | string | monitor serial | Match by monitor serial number | +| `layout_name` | string | layout name | Layout name to set | +| `no_render_border` | integer | `0` / `1` | Disable render border | +| `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 | + +### Examples + +```ini +# Set layout for specific tags +tagrule=id:1,layout_name:scroller +tagrule=id:2,layout_name:scroller + +# Limit to specific monitor +tagrule=id:1,monitor_name:eDP-1,layout_name:scroller +tagrule=id:2,monitor_name:eDP-1,layout_name:scroller + +# Persistent tags (1-4) with layout assignment +tagrule=id:1,no_hide:1,layout_name:scroller +tagrule=id:2,no_hide:1,layout_name:scroller +tagrule=id:3,monitor_name:eDP-1,no_hide:1,layout_name:scroller +tagrule=id:4,monitor_name:eDP-1,no_hide:1,layout_name:scroller + +# Advanced tag configuration with master layout settings +tagrule=id:5,layout_name:tile,nmaster:2,mfact:0.6 +tagrule=id:6,monitor_name:HDMI-A-1,layout_name:monocle,no_render_border:1 +``` + +> **Tip:** For Waybar configuration with persistent tags, see [Status Bar](/docs/visuals/status-bar) documentation. + +--- + +## Layer Rules + +You can set all parameters in one line. Target "layer shell" surfaces like status bars (`waybar`), launchers (`rofi`), or notification daemons. + +**Format:** + +```ini +layerrule=layer_name:Values,Parameter:Values,Parameter:Values +``` + +> **Tip:** You can use `mmsg -e` to get the last open layer name for debugging. + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `layer_name` | string | layer name | Match name of layer, supports regex | +| `animation_type_open` | string | slide, zoom, fade, none | Set open animation | +| `animation_type_close` | string | slide, zoom, fade, none | Set close animation | +| `noblur` | integer | `0` / `1` | Disable blur | +| `noanim` | integer | `0` / `1` | Disable layer animation | +| `noshadow` | integer | `0` / `1` | Disable layer shadow | + +> **Tip:** For animation types, see [Animations](/docs/visuals/animations#animation-types). For visual effects, see [Window Effects](/docs/visuals/effects). + +### Examples + +```ini +# No blur or animation for slurp selection layer (avoids occlusion and ghosting in screenshots) +layerrule=noanim:1,noblur:1,layer_name:selection + +# Zoom animation for Rofi with multiple parameters +layerrule=animation_type_open:zoom,noanim:0,layer_name:rofi + +# Disable animations and shadows for notification daemon +layerrule=noanim:1,noshadow:1,layer_name:swaync + +# Multiple effects for launcher +layerrule=animation_type_open:slide,animation_type_close:fade,noblur:1,layer_name:wofi +``` diff --git a/docs/v0.13.0/window-management/scratchpad.md b/docs/v0.13.0/window-management/scratchpad.md new file mode 100644 index 00000000..398182f9 --- /dev/null +++ b/docs/v0.13.0/window-management/scratchpad.md @@ -0,0 +1,73 @@ +--- +title: Scratchpad +description: Manage hidden "scratchpad" windows for quick access. +--- + +mangowm supports two types of scratchpads: the standard pool (Sway-like) and named scratchpads. + +## Standard Scratchpad + +Any window can be sent to the "scratchpad" pile, which hides it. You can then cycle through them. + +**Keybindings:** + +```ini +# Send current window to scratchpad +bind=SUPER,i,minimized + +# Toggle (show/hide) the scratchpad +bind=ALT,z,toggle_scratchpad + +# Retrieve window from scratchpad (restore) +bind=SUPER+SHIFT,i,restore_minimized +``` + +--- + +## Named Scratchpad + +Named scratchpads are bound to specific keys and applications. When triggered, mangowm will either launch the app (if not running) or toggle its visibility. + +**1. Define the Window Rule** + +You must identify the app using a unique `appid` or `title` and mark it as a named scratchpad. The application must support setting a custom appid or title at launch. Common examples: + +- `st -c my-appid` — sets the appid +- `kitty -T my-title` — sets the window title +- `foot --app-id my-appid` — sets the appid + +Use `none` as a placeholder when you only want to match by one field. + +```ini +# Match by appid +windowrule=isnamedscratchpad:1,width:1280,height:800,appid:st-yazi + +# Match by title +windowrule=isnamedscratchpad:1,width:1000,height:700,title:kitty-scratch +``` + +**2. Bind the Toggle Key** + +Format: `bind=MOD,KEY,toggle_named_scratchpad,appid,title,command` + +Use `none` for whichever field you are not matching on. + +```ini +# Match by appid: launch 'st' with class 'st-yazi' running 'yazi' +bind=alt,h,toggle_named_scratchpad,st-yazi,none,st -c st-yazi -e yazi + +# Match by title: launch 'kitty' with window title 'kitty-scratch' +bind=alt,k,toggle_named_scratchpad,none,kitty-scratch,kitty -T kitty-scratch +``` + +--- + +## Appearance + +You can customize the size of scratchpad windows relative to the screen. + +```ini +scratchpad_width_ratio=0.8 +scratchpad_height_ratio=0.9 +scratchpadcolor=0x516c93ff +``` diff --git a/docs/v0.13.1/bindings/index.mdx b/docs/v0.13.1/bindings/index.mdx new file mode 100644 index 00000000..4c3a5bda --- /dev/null +++ b/docs/v0.13.1/bindings/index.mdx @@ -0,0 +1,15 @@ +--- +title: Bindings & Input +description: Keybindings, mouse gestures, and input devices. +icon: Keyboard +--- + +Configure how you interact with mangowm using flexible keybindings and input options. + +<Cards> + +<Card href="/docs/bindings/keys" title="Key Bindings" description="Keyboard shortcuts and modes" /> + +<Card href="/docs/bindings/mouse-gestures" title="Mouse Gestures" description="Touchpad and mouse gestures" /> + +</Cards> diff --git a/docs/v0.13.1/bindings/keys.md b/docs/v0.13.1/bindings/keys.md new file mode 100644 index 00000000..002c9564 --- /dev/null +++ b/docs/v0.13.1/bindings/keys.md @@ -0,0 +1,216 @@ +--- +title: Key Bindings +description: Define keyboard shortcuts and modes. +--- + +## Syntax + +Key bindings follow this format: + +```ini +bind[flags]=MODIFIERS,KEY,COMMAND,PARAMETERS +``` + +- **Modifiers**: `SUPER`, `CTRL`, `ALT`, `SHIFT`, `NONE` (combine with `+`, e.g. `SUPER+CTRL+ALT`). +- **Key**: Key name (from `xev` or `wev`) or keycode (e.g., `code:24` for `q`). + +> **Info:** `bind` automatically converts keysym to keycode for comparison. This makes it compatible with all keyboard layouts, but the matching may not always be precise. If a key combination doesn't work on your keyboard layout, use a keycode instead (e.g., `code:24` instead of `q`). + +### Flags + +- `l`: Works even when screen is locked. +- `s`: Uses keysym instead of keycode to bind. +- `r`: Triggers on key release instead of press. +- `p`: Pass key event to client. + +**Examples:** + +```ini +bind=SUPER,Q,killclient +bindl=SUPER,L,spawn,swaylock + +# Using keycode instead of key name +bind=ALT,code:24,killclient + +# Combining keycodes for modifiers and keys +bind=code:64,code:24,killclient +bind=code:64+code:133,code:24,killclient + +# Bind with no modifier +bind=NONE,XF86MonBrightnessUp,spawn,brightnessctl set +5% + +# Bind a modifier key itself as the trigger key +bind=alt,shift_l,switch_keyboard_layout +``` + +## Key Modes (Submaps) + +You can divide key bindings into named modes. Rules: + +1. Set `keymode=<name>` before a group of `bind` lines — those binds only apply in that mode. +2. If no `keymode` is set before a bind, it belongs to the `default` mode. +3. The special `common` keymode applies its binds **across all modes**. + +Use `setkeymode` to switch modes, and `mmsg -b` to query the current mode. + +```ini +# Binds in 'common' apply in every mode +keymode=common +bind=SUPER,r,reload_config + +# Default mode bindings +keymode=default +bind=ALT,Return,spawn,foot +bind=SUPER,F,setkeymode,resize + +# 'resize' mode bindings +keymode=resize +bind=NONE,Left,resizewin,-10,0 +bind=NONE,Right,resizewin,+10,0 +bind=NONE,Escape,setkeymode,default +``` + +### Single Modifier Key Binding + +When binding a modifier key itself, use `NONE` for press and the modifier name for release: + +```ini +# Trigger on press of Super key +bind=none,Super_L,spawn,rofi -show run + +# Trigger on release of Super key +bindr=Super,Super_L,spawn,rofi -show run +``` + +## Dispatchers List + +### Window Management + +| Command | Param | Description | +| :--- | :--- | :--- | +| `killclient` | - | Close the focused window. | +| `togglefloating` | - | Toggle floating state. | +| `toggle_all_floating` | - | Toggle all visible clients floating state. | +| `togglefullscreen` | - | Toggle fullscreen. | +| `togglefakefullscreen` | - | Toggle "fake" fullscreen (remains constrained). | +| `togglemaximizescreen` | - | Maximize window (keep decoration/bar). | +| `toggleglobal` | - | Pin window to all tags. | +| `toggle_render_border` | - | Toggle border rendering. | +| `centerwin` | - | Center the floating window. | +| `minimized` | - | Minimize window to scratchpad. | +| `restore_minimized` | - | Restore window from scratchpad. | +| `toggle_scratchpad` | - | Toggle scratchpad. | +| `toggle_named_scratchpad` | `appid,title,cmd` | Toggle named scratchpad. Launches app if not running, otherwise shows/hides it. | + +### Focus & Movement + +| Command | Param | Description | +| :--- | :--- | :--- | +| `focusdir` | `left/right/up/down` | Focus window in direction. | +| `focusstack` | `next/prev` | Cycle focus within the stack. | +| `focuslast` | - | Focus the previously active window. | +| `exchange_client` | `left/right/up/down` | Swap window with neighbor in direction. | +| `exchange_stack_client` | `next/prev` | Exchange window position in stack. | +| `zoom` | - | Swap focused window with Master. | + +### Tags & Monitors + +| Command | Param | Description | +| :--- | :--- | :--- | +| `view` | `-1/0/1-9` or `mask [,synctag]` | View tag. `-1` = previous tagset, `0` = all tags, `1-9` = specific tag, mask e.g. `1\|3\|5`. Optional `synctag` (0/1) syncs the action to all monitors. | +| `viewtoleft` | `[synctag]` | View previous tag. Optional `synctag` (0/1) syncs to all monitors. | +| `viewtoright` | `[synctag]` | View next tag. Optional `synctag` (0/1) syncs to all monitors. | +| `viewtoleft_have_client` | `[synctag]` | View left tag and focus client if present. Optional `synctag` (0/1). | +| `viewtoright_have_client` | `[synctag]` | View right tag and focus client if present. Optional `synctag` (0/1). | +| `viewcrossmon` | `tag,monitor_spec` | View specified tag on specified monitor. | +| `tag` | `1-9 [,synctag]` | Move window to tag. Optional `synctag` (0/1) syncs to all monitors. | +| `tagsilent` | `1-9` | Move window to tag without focusing it. | +| `tagtoleft` | `[synctag]` | Move window to left tag. Optional `synctag` (0/1). | +| `tagtoright` | `[synctag]` | Move window to right tag. Optional `synctag` (0/1). | +| `tagcrossmon` | `tag,monitor_spec` | Move window to specified tag on specified monitor. | +| `toggletag` | `0-9` | Toggle tag on window (0 means all tags). | +| `toggleview` | `1-9` | Toggle tag view. | +| `comboview` | `1-9` | View multi tags pressed simultaneously. | +| `focusmon` | `left/right/up/down/monitor_spec` | Focus monitor by direction or [monitor spec](/docs/configuration/monitors#monitor-spec-format). | +| `tagmon` | `left/right/up/down/monitor_spec,[keeptag]` | Move window to monitor by direction or [monitor spec](/docs/configuration/monitors#monitor-spec-format). `keeptag` is 0 or 1. | + +### Layouts + +| Command | Param | Description | +| :--- | :--- | :--- | +| `setlayout` | `name` | Switch to layout (e.g., `scroller`, `tile`). | +| `switch_layout` | - | Cycle through available layouts. | +| `incnmaster` | `+1/-1` | Increase/Decrease number of master windows. | +| `setmfact` | `+0.05` | Increase/Decrease master area size. | +| `set_proportion` | `float` | Set scroller window proportion (0.0–1.0). | +| `switch_proportion_preset` | - | Cycle proportion presets of scroller window. | +| `scroller_stack` | `left/right/up/down` | Move window inside/outside scroller stack by direction. | +| `incgaps` | `+/-value` | Adjust gap size. | +| `togglegaps` | - | Toggle gaps. | +| `dwindle_toggle_split_direction` | - | Toggle split direction in dwindle layout. | + +### System + +| Command | Param | Description | +| :--- | :--- | :--- | +| `spawn` | `cmd` | Execute a command. | +| `spawn_shell` | `cmd` | Execute shell command (supports pipes `\|`). | +| `spawn_on_empty` | `cmd,tagnumber` | Open command on empty tag. | +| `reload_config` | - | Hot-reload configuration. | +| `quit` | - | Exit mangowm. | +| `toggleoverview` | - | Toggle overview mode. | +| `create_virtual_output` | - | Create a headless monitor (for VNC/Sunshine). | +| `destroy_all_virtual_output` | - | Destroy all virtual monitors. | +| `toggleoverlay` | - | Toggle overlay state for the focused window. | +| `toggle_trackpad_enable` | - | Toggle trackpad enable. | +| `setkeymode` | `mode` | Set keymode. | +| `switch_keyboard_layout` | `[index]` | Switch keyboard layout. Optional index (0, 1, 2...) to switch to specific layout. | +| `setoption` | `key,value` | Set config option temporarily. | +| `disable_monitor` | `monitor_spec` | Shutdown monitor. Accepts a [monitor spec](/docs/configuration/monitors#monitor-spec-format). | +| `enable_monitor` | `monitor_spec` | Power on monitor. Accepts a [monitor spec](/docs/configuration/monitors#monitor-spec-format). | +| `toggle_monitor` | `monitor_spec` | Toggle monitor power. Accepts a [monitor spec](/docs/configuration/monitors#monitor-spec-format). | + +### Media Controls + +> **Warning:** Some keyboards don't send standard media keys. Run `wev` and press your key to check the exact key name. + +#### Brightness + +Requires: `brightnessctl` + +```ini +bind=NONE,XF86MonBrightnessUp,spawn,brightnessctl s +2% +bind=SHIFT,XF86MonBrightnessUp,spawn,brightnessctl s 100% +bind=NONE,XF86MonBrightnessDown,spawn,brightnessctl s 2%- +bind=SHIFT,XF86MonBrightnessDown,spawn,brightnessctl s 1% +``` + +#### Volume + +Requires: `wpctl` (WirePlumber) + +```ini +bind=NONE,XF86AudioRaiseVolume,spawn,wpctl set-volume @DEFAULT_SINK@ 5%+ +bind=NONE,XF86AudioLowerVolume,spawn,wpctl set-volume @DEFAULT_SINK@ 5%- +bind=NONE,XF86AudioMute,spawn,wpctl set-mute @DEFAULT_SINK@ toggle +bind=SHIFT,XF86AudioMute,spawn,wpctl set-mute @DEFAULT_SOURCE@ toggle +``` + +#### 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 | +| :--- | :--- | :--- | +| `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. | diff --git a/docs/v0.13.1/bindings/meta.json b/docs/v0.13.1/bindings/meta.json new file mode 100644 index 00000000..f1b629b6 --- /dev/null +++ b/docs/v0.13.1/bindings/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Bindings & Input", + "pages": ["keys", "mouse-gestures"] +} diff --git a/docs/v0.13.1/bindings/mouse-gestures.md b/docs/v0.13.1/bindings/mouse-gestures.md new file mode 100644 index 00000000..c4a36889 --- /dev/null +++ b/docs/v0.13.1/bindings/mouse-gestures.md @@ -0,0 +1,116 @@ +--- +title: Mouse & Gestures +description: Configure mouse buttons, scrolling, gestures, and lid switches. +--- + +## Mouse Bindings + +Assign actions to mouse button presses with optional modifier keys. + +### Syntax + +```ini +mousebind=MODIFIERS,BUTTON,COMMAND,PARAMETERS +``` + +- **Modifiers**: `SUPER`, `CTRL`, `ALT`, `SHIFT`, `NONE`. Combine with `+` (e.g., `SUPER+CTRL`) +- **Buttons**: `btn_left`, `btn_right`, `btn_middle`, `btn_side`, `btn_extra`, `btn_forward`, `btn_back`, `btn_task` + +> **Warning:** When modifiers are set to `NONE`, only `btn_middle` works in normal mode. `btn_left` and `btn_right` only work in overview mode. + +### Examples + +```ini +# Window manipulation +mousebind=SUPER,btn_left,moveresize,curmove +mousebind=SUPER,btn_right,moveresize,curresize +mousebind=SUPER+CTRL,btn_right,killclient + +# Overview mode (requires NONE modifier) +mousebind=NONE,btn_left,toggleoverview,-1 +mousebind=NONE,btn_right,killclient,0 +mousebind=NONE,btn_middle,togglemaximizescreen,0 +``` + +--- + +## Axis Bindings + +Map scroll wheel movements to actions for workspace and window navigation. + +### Syntax + +```ini +axisbind=MODIFIERS,DIRECTION,COMMAND,PARAMETERS +``` + +- **Direction**: `UP`, `DOWN`, `LEFT`, `RIGHT` + +### Examples + +```ini +axisbind=SUPER,UP,viewtoleft_have_client +axisbind=SUPER,DOWN,viewtoright_have_client +``` + +--- + +## Gesture Bindings + +Enable touchpad swipe gestures for navigation and window management. + +### Syntax + +```ini +gesturebind=MODIFIERS,DIRECTION,FINGERS,COMMAND,PARAMETERS +``` + +- **Direction**: `up`, `down`, `left`, `right` +- **Fingers**: `3` or `4` + +> **Info:** Gestures require proper touchpad configuration. See [Input Devices](/docs/configuration/input) for touchpad settings like `tap_to_click` and `disable_while_typing`. + +### Examples + +```ini +# 3-finger: Window focus +gesturebind=none,left,3,focusdir,left +gesturebind=none,right,3,focusdir,right +gesturebind=none,up,3,focusdir,up +gesturebind=none,down,3,focusdir,down + +# 4-finger: Workspace navigation +gesturebind=none,left,4,viewtoleft_have_client +gesturebind=none,right,4,viewtoright_have_client +gesturebind=none,up,4,toggleoverview +gesturebind=none,down,4,toggleoverview +``` + +--- + +## Switch Bindings + +Trigger actions on hardware events like laptop lid open/close. + +### Syntax + +```ini +switchbind=FOLD_STATE,COMMAND,PARAMETERS +``` + +- **Fold State**: `fold` (lid closed), `unfold` (lid opened) + +> **Warning:** Disable system lid handling in `/etc/systemd/logind.conf`: +> +> ```ini +> HandleLidSwitch=ignore +> HandleLidSwitchExternalPower=ignore +> HandleLidSwitchDocked=ignore +> ``` + +### Examples + +```ini +switchbind=fold,spawn,swaylock -f -c 000000 +switchbind=unfold,spawn,wlr-dpms on +``` diff --git a/docs/v0.13.1/configuration/basics.md b/docs/v0.13.1/configuration/basics.md new file mode 100644 index 00000000..7afa343b --- /dev/null +++ b/docs/v0.13.1/configuration/basics.md @@ -0,0 +1,87 @@ +--- +title: Basic Configuration +description: Learn how to configure mangowm files, environment variables, and autostart scripts. +--- + +## Configuration File + +mangowm uses a simple configuration file format. By default, it looks for a configuration file in `~/.config/mango/`. + +1. **Locate Default Config** + + A fallback configuration is provided at `/etc/mango/config.conf`. You can use this as a reference. + +2. **Create User Config** + + Copy the default config to your local config directory to start customizing. + + ```bash + mkdir -p ~/.config/mango + cp /etc/mango/config.conf ~/.config/mango/config.conf + ``` + +3. **Launch with Custom Config (Optional)** + + If you prefer to keep your config elsewhere, you can launch mango with the `-c` flag. + + ```bash + mango -c /path/to/your_config.conf + ``` + +### Sub-Configuration + +To keep your configuration organized, you can split it into multiple files and include them using the `source` keyword. + +```ini +# Import keybindings from a separate file +source=~/.config/mango/bind.conf + +# Relative paths work too +source=./theme.conf + +# Optional: ignore if file doesn't exist (useful for shared configs) +source-optional=~/.config/mango/optional.conf +``` + +### Validate Configuration + +You can check your configuration for errors without starting mangowm: + +```bash +mango -c /path/to/config.conf -p +``` + +Use with `source-optional` for shared configs across different setups. + +## Environment Variables + +You can define environment variables directly within your config file. These are set before the window manager fully initializes. + +> **Warning:** Environment variables defined here will be **reset** every time you reload the configuration. + +```ini +env=QT_IM_MODULES,wayland;fcitx +env=XMODIFIERS,@im=fcitx +``` + +## Autostart + +mangowm can automatically run commands or scripts upon startup. There are two modes for execution: + +| Command | Behavior | Usage Case | +| :--- | :--- | :--- | +| `exec-once` | Runs **only once** when mangowm starts. | Status bars, Wallpapers, Notification daemons | +| `exec` | Runs **every time** the config is reloaded. | Scripts that need to refresh settings | + +### Example Setup + +```ini +# Start the status bar once +exec-once=waybar + +# Set wallpaper +exec-once=swaybg -i ~/.config/mango/wallpaper/room.png + +# Reload a custom script on config change +exec=bash ~/.config/mango/reload-settings.sh +``` diff --git a/docs/v0.13.1/configuration/index.mdx b/docs/v0.13.1/configuration/index.mdx new file mode 100644 index 00000000..2bcd3a7e --- /dev/null +++ b/docs/v0.13.1/configuration/index.mdx @@ -0,0 +1,21 @@ +--- +title: Configuration +description: Configure mangowm with config files, environment variables, and autostart. +icon: Settings +--- + +Configure mangowm through config files, environment variables, and autostart. + +<Cards> + +<Card href="/docs/configuration/basics" title="Basics" description="Config files, env vars, exec-once, exec" /> + +<Card href="/docs/configuration/monitors" title="Monitors" description="Monitor settings and resolution" /> + +<Card href="/docs/configuration/input" title="Input" description="Keyboard, mouse, and touchpad" /> + +<Card href="/docs/configuration/xdg-portals" title="XDG Portals" description="File pickers and notifications" /> + +<Card href="/docs/configuration/miscellaneous" title="Miscellaneous" description="Additional options" /> + +</Cards> \ No newline at end of file diff --git a/docs/v0.13.1/configuration/input.md b/docs/v0.13.1/configuration/input.md new file mode 100644 index 00000000..ee12906a --- /dev/null +++ b/docs/v0.13.1/configuration/input.md @@ -0,0 +1,161 @@ +--- +title: Input Devices +description: Configure keyboard layouts, mouse sensitivity, and touchpad gestures. +--- + +## Device Configuration + +mangowm provides granular control over different input devices. + +### Keyboard Settings + +Control key repeat rates and layout rules. + +| Setting | Type | Default | Description | +| :--- | :--- | :--- | :--- | +| `repeat_rate` | `int` | `25` | How many times a key repeats per second. | +| `repeat_delay` | `int` | `600` | Delay (ms) before a held key starts repeating. | +| `numlockon` | `0` or `1` | `0` | Enable NumLock on startup. | +| `xkb_rules_rules` | `string` | - | XKB rules file (e.g., `evdev`, `base`). Usually auto-detected. | +| `xkb_rules_model` | `string` | - | Keyboard model (e.g., `pc104`, `macbook`). | +| `xkb_rules_layout` | `string` | - | Keyboard layout code (e.g., `us`, `de`, `us,de`). | +| `xkb_rules_variant` | `string` | - | Layout variant (e.g., `dvorak`, `colemak`, `intl`). | +| `xkb_rules_options` | `string` | - | XKB options (e.g., `caps:escape`, `ctrl:nocaps`). | + +**Example:** + +```ini +repeat_rate=40 +repeat_delay=300 +numlockon=1 +xkb_rules_layout=us,de +xkb_rules_variant=dvorak +xkb_rules_options=caps:escape,ctrl:nocaps +``` + +--- + +### Mouse Settings + +Configuration for external mice. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `mouse_natural_scrolling` | `0` | Invert scrolling direction. | +| `mouse_accel_profile` | `2` | `0` (None), `1` (Flat), `2` (Adaptive). | +| `mouse_accel_speed` | `0.0` | Speed adjustment (-1.0 to 1.0). | +| `left_handed` | `0` | Swap left and right buttons. | +| `axis_scroll_factor` | `1.0` | Scroll factor for axis scroll speed (0.1–10.0). | +--- + +### Trackpad Settings + +Specific settings for laptop touchpads. Some settings may require a relogin to take effect. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `disable_trackpad` | `0` | Set to `1` to disable the trackpad entirely. | +| `tap_to_click` | `1` | Tap to trigger a left click. | +| `tap_and_drag` | `1` | Tap and hold to drag items. | +| `trackpad_natural_scrolling` | `0` | Invert scrolling direction (natural scrolling). | +| `trackpad_accel_profile` | `2` | `0` (None), `1` (Flat), `2` (Adaptive). | +| `trackpad_accel_speed` | `0.0` | Speed adjustment (-1.0 to 1.0). | +| `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). | +| `send_events_mode` | `0` | `0` (Enabled), `1` (Disabled), `2` (Disabled on external mouse). | +| `drag_lock` | `1` | Lock dragging after tapping. | +| `disable_while_typing` | `1` | Disable trackpad while typing. | +| `left_handed` | `0` | Swap left/right buttons. | +| `middle_button_emulation` | `0` | Emulate middle button. | +| `swipe_min_threshold` | `1` | Minimum swipe threshold when use gesture. | +| `button_map` | `0` | `0` (Left/right/middle), `1` (Left/middle/right). | +| `trackpad_scroll_factor` | `1.0` | Scroll factor for trackpad scroll speed (0.1–10.0). | +--- + +**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. + - `2` — Edge scrolling: send scroll events when a finger moves along the bottom or right edge. + - `4` — Button scrolling: send scroll events when a button is held and the device moves along a scroll axis. + +- `click_method` values: + - `0` — No software click emulation. + - `1` — Button areas: use software-defined areas on the touchpad to generate button events. + - `2` — Clickfinger: the number of fingers determines which button is pressed. + +- `mouse_accel_profile` or `trackpad_scroll_profile` values: + - `0` — No acceleration. + - `1` — Flat: no dynamic acceleration. Pointer speed = original input speed × (1 + `mouse_accel_speed`). + - `2` — Adaptive: slow movement results in less acceleration, fast movement results in more. + +- `button_map` values: + - `0` — 1/2/3 finger tap maps to left / right / middle. + - `1` — 1/2/3 finger tap maps to left / middle / right. + +- `send_events_mode` values: + - `0` — Send events from this device normally. + - `1` — Do not send events from this device. + - `2` — Disable this device when an external pointer device is plugged in. + +--- +--- + +## Keyboard Layout Switching + +To bind multiple layouts and toggle between them, define the layouts in `xkb_rules_layout` and use `xkb_rules_options` to set a toggle key combination. Then bind `switch_keyboard_layout` to trigger a switch. + +```ini +# Define two layouts: US QWERTY and US Dvorak +xkb_rules_layout=us,us +xkb_rules_variant=,dvorak +xkb_rules_options=grp:lalt_lshift_toggle +``` + +Or bind it manually to a key: + +```ini +# Bind Alt+Shift_L to cycle keyboard layout +bind=alt,shift_l,switch_keyboard_layout +``` + +Use `mmsg -g -k` to query the current keyboard layout at any time. + +--- + +## Input Method Editor (IME) + +To use Fcitx5 or IBus, set these environment variables in your config file. + +> **Info:** These settings require a restart of the window manager to take effect. + +**For Fcitx5:** + +```ini +env=GTK_IM_MODULE,fcitx +env=QT_IM_MODULE,fcitx +env=QT_IM_MODULES,wayland;fcitx +env=SDL_IM_MODULE,fcitx +env=XMODIFIERS,@im=fcitx +env=GLFW_IM_MODULE,ibus +``` + +**For IBus:** + +```ini +env=GTK_IM_MODULE,ibus +env=QT_IM_MODULE,ibus +env=XMODIFIERS,@im=ibus +``` diff --git a/docs/v0.13.1/configuration/meta.json b/docs/v0.13.1/configuration/meta.json new file mode 100644 index 00000000..bc209b4e --- /dev/null +++ b/docs/v0.13.1/configuration/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Configuration", + "pages": ["basics", "monitors", "input", "xdg-portals", "miscellaneous"] +} diff --git a/docs/v0.13.1/configuration/miscellaneous.md b/docs/v0.13.1/configuration/miscellaneous.md new file mode 100644 index 00000000..e1be2907 --- /dev/null +++ b/docs/v0.13.1/configuration/miscellaneous.md @@ -0,0 +1,50 @@ +--- +title: Miscellaneous +description: Advanced settings for XWayland, focus behavior, and system integration. +--- + +## System & Hardware + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `xwayland_persistence` | `1` | Keep XWayland running even when no X11 apps are open (reduces startup lag). | +| `syncobj_enable` | `0` | Enable `drm_syncobj` timeline support (helps with gaming stutter/lag). **Requires restart.** | +| `allow_lock_transparent` | `0` | Allow the lock screen to be transparent. | +| `allow_shortcuts_inhibit` | `1` | Allow shortcuts to be inhibited by clients. | +| `vrr` | - | Set via [monitor rule](/docs/configuration/monitors#monitor-rules). | + +## Focus & Input + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `focus_on_activate` | `1` | Automatically focus windows when they request activation. | +| `sloppyfocus` | `1` | Focus follows the mouse cursor. | +| `warpcursor` | `1` | Warp the cursor to the center of the window when focus changes via keyboard. | +| `cursor_hide_timeout` | `0` | Hide the cursor after `N` seconds of inactivity (`0` to disable). | +| `drag_tile_to_tile` | `0` | Allow dragging a tiled window onto another to swap their positions. | +| `drag_tile_small` | `1` | Allow dragging a tiled window temporarily to small size.| +| `drag_corner` | `3` | Corner for drag-to-tile detection (0: none, 1–3: corners, 4: auto-detect). | +| `drag_warp_cursor` | `1` | Warp cursor when dragging windows to tile. | +| `axis_bind_apply_timeout` | `100` | Timeout (ms) for detecting consecutive scroll events for axis bindings. | + +## Multi-Monitor & Tags + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `focus_cross_monitor` | `0` | Allow directional focus to cross monitor boundaries. | +| `exchange_cross_monitor` | `0` | Allow exchanging clients across monitor boundaries. | +| `focus_cross_tag` | `0` | Allow directional focus to cross into other tags. | +| `view_current_to_back` | `0` | Toggling the current tag switches back to the previously viewed tag. | +| `scratchpad_cross_monitor` | `0` | Share the scratchpad pool across all monitors. | +| `single_scratchpad` | `1` | Only allow one scratchpad (named or standard) to be visible at a time. | + +## Window Behavior + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `enable_floating_snap` | `0` | Snap floating windows to edges or other windows. | +| `snap_distance` | `30` | Max distance (pixels) to trigger floating snap. | +| `no_border_when_single` | `0` | Remove window borders when only one window is visible on the tag. | +| `idleinhibit_ignore_visible` | `0` | Allow invisible clients (e.g., background audio players) to inhibit idle. | +| `drag_tile_refresh_interval` | `8.0` | Interval (1.0–16.0) to refresh tiled window resize during drag. Too small may cause application lag. | +| `drag_floating_refresh_interval` | `8.0` | Interval (1.0–16.0) to refresh floating window resize during drag. Too small may cause application lag. | \ No newline at end of file diff --git a/docs/v0.13.1/configuration/monitors.md b/docs/v0.13.1/configuration/monitors.md new file mode 100644 index 00000000..28ef240b --- /dev/null +++ b/docs/v0.13.1/configuration/monitors.md @@ -0,0 +1,276 @@ +--- +title: Monitors +description: Manage display outputs, resolution, scaling, and tearing. +--- + +## Monitor Rules + +You can configure each display output individually using the `monitorrule` keyword. + +**Syntax:** + +```ini +monitorrule=name:Values,Parameter:Values,Parameter:Values +``` + +> **Info:** If any of the matching fields (`name`, `make`, `model`, `serial`) are set, **all** of the set ones must match to be considered a match. Use `wlr-randr` to get your monitor's name, make, model, and serial. + +### Parameters + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `name` | string | Any | Match by monitor name (supports regex) | +| `make` | string | Any | Match by monitor manufacturer | +| `model` | string | Any | Match by monitor model | +| `serial` | string | Any | Match by monitor serial number | +| `width` | integer | 0-9999 | Monitor width | +| `height` | integer | 0-9999 | Monitor height | +| `refresh` | float | 0.001-9999.0 | Monitor refresh rate | +| `x` | integer | 0-99999 | X position | +| `y` | integer | 0-99999 | Y position | +| `scale` | float | 0.01-100.0 | Monitor scale | +| `vrr` | integer | 0, 1 | Enable variable refresh rate | +| `rr` | integer | 0-7 | Monitor transform | +| `custom` | integer | 0, 1 | Enable custom mode (not supported on all displays — may cause black screen) | + +### Transform Values + +| Value | Rotation | +| :--- | :--- | +| `0` | No transform | +| `1` | 90° counter-clockwise | +| `2` | 180° counter-clockwise | +| `3` | 270° counter-clockwise | +| `4` | 180° vertical flip | +| `5` | Flip + 90° counter-clockwise | +| `6` | Flip + 180° counter-clockwise | +| `7` | Flip + 270° counter-clockwise | + +> **Critical:** If you use XWayland applications, **never use negative coordinates** for your monitor positions. This is a known XWayland bug that causes click events to malfunction. Always arrange your monitors starting from `0,0` and extend into positive coordinates. + +> **Note:** that "name" is a regular expression. If you want an exact match, you need to add `^` and `$` to the beginning and end of the expression, for example, `^eDP-1$` matches exactly the string `eDP-1`. + +### Examples + +```ini +# Laptop display: 1080p, 60Hz, positioned at origin +monitorrule=name:^eDP-1$,width:1920,height:1080,refresh:60,x:0,y:10 + +# Match by make and model instead of name +monitorrule=make:Chimei Innolux Corporation,model:0x15F5,width:1920,height:1080,refresh:60,x:0,y:0 + +# Virtual monitor with pattern matching +monitorrule=name:HEADLESS-.*,width:1920,height:1080,refresh:60,x:1926,y:0,scale:1,rr:0,vrr:0 +``` + +--- + +## Monitor Spec Format + +Several commands (`focusmon`, `tagmon`, `disable_monitor`, `enable_monitor`, `toggle_monitor`, `viewcrossmon`, `tagcrossmon`) accept a **monitor_spec** string to identify a monitor. + +**Format:** + +```text +name:xxx&&make:xxx&&model:xxx&&serial:xxx +``` + +- Any field can be omitted and there is no order requirement. +- If all fields are omitted, the string is treated as the monitor name directly (e.g., `eDP-1`). +- Use `wlr-randr` to find your monitor's name, make, model, and serial. + +**Examples:** + +```bash +# By name (shorthand) +mmsg -d toggle_monitor,eDP-1 + +# By make and model +mmsg -d toggle_monitor,make:Chimei Innolux Corporation&&model:0x15F5 + +# By serial +mmsg -d toggle_monitor,serial:12345678 +``` + +--- + +## Tearing (Game Mode) + +Tearing allows games to bypass the compositor's VSync for lower latency. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `allow_tearing` | `0` | Global tearing control: `0` (Disable), `1` (Enable), `2` (Fullscreen only). | + +### Configuration + +**Enable Globally:** + +```ini +allow_tearing=1 +``` + +**Enable per Window:** + +Use a window rule to force tearing for specific games. + +```ini +windowrule=force_tearing:1,title:vkcube +``` + +### Tearing Behavior Matrix + +| `force_tearing` \ `allow_tearing` | DISABLED (0) | ENABLED (1) | FULLSCREEN_ONLY (2) | +| :--- | :--- | :--- | :--- | +| **UNSPECIFIED** (0) | Not Allowed | Follows tearing_hint | Only fullscreen follows tearing_hint | +| **ENABLED** (1) | Not Allowed | Allowed | Only fullscreen allowed | +| **DISABLED** (2) | Not Allowed | Not Allowed | Not Allowed | + +### Graphics Card Compatibility + +> **Warning:** Some graphics cards require setting the `WLR_DRM_NO_ATOMIC` environment variable before mango starts to successfully enable tearing. + +Add this to `/etc/environment` and reboot: + +```bash +WLR_DRM_NO_ATOMIC=1 +``` + +Or run mango with the environment variable: + +```bash +WLR_DRM_NO_ATOMIC=1 mango +``` + +--- + +## GPU Compatibility + +If mango cannot display correctly or shows a black screen, try selecting a specific GPU: + +```bash +# Use a single GPU +WLR_DRM_DEVICES=/dev/dri/card1 mango + +# Use multiple GPUs +WLR_DRM_DEVICES=/dev/dri/card0:/dev/dri/card1 mango +``` + +Some GPUs have compatibility issues with `syncobj_enable=1` — it may crash apps like `kitty` that use syncobj. Set `WLR_DRM_NO_ATOMIC=1` in `/etc/environment` and reboot to resolve this. + +--- + +## Power Management + +You can control monitor power using the `mmsg` IPC tool. + +```bash +# Turn off +mmsg -d disable_monitor,eDP-1 + +# Turn on +mmsg -d enable_monitor,eDP-1 + +# Toggle +mmsg -d toggle_monitor,eDP-1 +``` + +You can also use `wlr-randr` for monitor management: + +```bash +# Turn off monitor +wlr-randr --output eDP-1 --off + +# Turn on monitor +wlr-randr --output eDP-1 --on + +# Show all monitors +wlr-randr +``` + +--- + +## Screen Scale + +### Without Global Scale (Recommended) + +- If you do not use XWayland apps, you can use monitor rules or `wlr-randr` to set a global monitor scale. +- If you are using XWayland apps, it is not recommended to set a global monitor scale. + +You can set scale like this, for example with a 1.4 factor. + +**Dependencies:** + +```bash +yay -S xorg-xrdb +yay -S xwayland-satellite +``` + +**In config file:** + +```ini +env=QT_AUTO_SCREEN_SCALE_FACTOR,1 +env=QT_WAYLAND_FORCE_DPI,140 +``` + +**In autostart:** + +```bash +echo "Xft.dpi: 140" | xrdb -merge +gsettings set org.gnome.desktop.interface text-scaling-factor 1.4 +``` + +**Edit autostart for XWayland:** + +```bash +# Start xwayland +/usr/sbin/xwayland-satellite :11 & +# Apply scale 1.4 for xwayland +sleep 0.5s && echo "Xft.dpi: 140" | xrdb -merge +``` + +### Using xwayland-satellite to Prevent Blurry XWayland Apps + +If you use fractional scaling, you can use `xwayland-satellite` to automatically scale XWayland apps to prevent blurriness, for example with a scale of 1.4. + +**Dependencies:** + +```bash +yay -S xwayland-satellite +``` + +**In config file:** + +```ini +env=DISPLAY,:2 +exec-once=xwayland-satellite :2 +monitorrule=name:eDP-1,width:1920,height:1080,refresh:60,x:0,y:0,scale:1.4,vrr:0,rr:0 +``` + +> **Warning:** Use a `DISPLAY` value other than `:1` to avoid conflicting with mangowm. + +--- + +## Virtual Monitors + +You can create and manage virtual displays through IPC commands: + +```bash +# Create virtual output +mmsg -d create_virtual_output + +# Destroy all virtual outputs +mmsg -d destroy_all_virtual_output +``` + +You can configure virtual monitors using `wlr-randr`: + +```bash +# Show all monitors +wlr-randr + +# Configure virtual monitor +wlr-randr --output HEADLESS-1 --pos 1921,0 --scale 1 --custom-mode 1920x1080@60Hz +``` + +Virtual monitors can be used for screen sharing with tools like [Sunshine](https://github.com/LizardByte/Sunshine) and [Moonlight](https://github.com/moonlight-stream/moonlight-android), allowing other devices to act as extended monitors. \ No newline at end of file diff --git a/docs/v0.13.1/configuration/xdg-portals.md b/docs/v0.13.1/configuration/xdg-portals.md new file mode 100644 index 00000000..27819ad8 --- /dev/null +++ b/docs/v0.13.1/configuration/xdg-portals.md @@ -0,0 +1,76 @@ +--- +title: XDG Portals +description: Set up screen sharing, clipboard, keyring, and file pickers using XDG portals. +--- + +## Portal Configuration + +You can customize portal settings via the following paths: + +- **User Configuration (Priority):** `~/.config/xdg-desktop-portal/mango-portals.conf` +- **System Fallback:** `/usr/share/xdg-desktop-portal/mango-portals.conf` + +> **Warning:** If you previously added `dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP=wlroots` to your config, remove it. Mango now handles this automatically. + +## Screen Sharing + +To enable screen sharing (OBS, Discord, WebRTC), you need `xdg-desktop-portal-wlr`. + +1. **Install Dependencies** + + `pipewire`, `pipewire-pulse`, `xdg-desktop-portal-wlr` + +2. **Optional: Add to autostart** + + In some situations the portal may not start automatically. You can add this to your autostart script to ensure it launches: + + ```bash + /usr/lib/xdg-desktop-portal-wlr & + ``` + +3. **Restart your computer** to apply changes. + +### Known Issues + +- **Window screen sharing:** Some applications may have issues sharing individual windows. See [#184](https://github.com/mangowm/mango/pull/184) for workarounds. + +- **Screen recording lag:** If you experience stuttering during screen recording, see [xdg-desktop-portal-wlr#351](https://github.com/emersion/xdg-desktop-portal-wlr/issues/351). + +## Clipboard Manager + +Use `cliphist` to manage clipboard history. + +**Dependencies:** `wl-clipboard`, `cliphist`, `wl-clip-persist` + +**Autostart Config:** + +```bash +# Keep clipboard content after app closes +wl-clip-persist --clipboard regular --reconnect-tries 0 & + +# Watch clipboard and store history +wl-paste --type text --watch cliphist store & +``` + +## GNOME Keyring + +If you need to store passwords or secrets (e.g., for VS Code or Minecraft launchers), install `gnome-keyring`. + +**Configuration:** + +Add the following to `~/.config/xdg-desktop-portal/mango-portals.conf`: + +```ini +[preferred] +default=gtk +org.freedesktop.impl.portal.ScreenCast=wlr +org.freedesktop.impl.portal.Screenshot=wlr +org.freedesktop.impl.portal.Secret=gnome-keyring +org.freedesktop.impl.portal.Inhibit=none +``` + +## File Picker (File Selector) + +**Dependencies:** `xdg-desktop-portal`, `xdg-desktop-portal-gtk` + +Reboot your computer once to apply. \ No newline at end of file diff --git a/docs/v0.13.1/faq.md b/docs/v0.13.1/faq.md new file mode 100644 index 00000000..9c9288de --- /dev/null +++ b/docs/v0.13.1/faq.md @@ -0,0 +1,100 @@ +--- +title: FAQ +description: Frequently asked questions and troubleshooting. +--- + +### How do I arrange tiled windows with my mouse? + +You can enable the `drag_tile_to_tile` option in your config. This allows you to drag a tiled window onto another to swap them. + +```ini +drag_tile_to_tile=1 +``` + +--- + +### Why is my background blurry or why does blur look wrong? + +Blur applies to the transparent areas of windows. To disable it entirely, set `blur=0`. + +If you are experiencing **performance issues with blur**, make sure `blur_optimized=1` (the default). This caches the wallpaper as the blur background, which is much cheaper on the GPU: + +```ini +blur_optimized=1 +``` + +--- + +### Blur shows my wallpaper instead of the real background content + +This is expected behavior when `blur_optimized=1` (the default). The optimizer caches the wallpaper to reduce GPU load — windows will blur against the wallpaper rather than the actual content stacked beneath them. + +If you want blur to composite against the true background (i.e., show whatever is actually behind the window), set: + +```ini +blur_optimized=0 +``` + +> **Warning:** Disabling `blur_optimized` significantly increases GPU consumption and may cause rendering lag, especially on lower-end hardware. + +--- + +### My games are lagging or stuttering + +Try enabling **SyncObj** timeline support. + +```ini +syncobj_enable=1 +``` + +--- + +### My games have high input latency + +You can enable **Tearing** (similar to VSync off). + +First, enable it globally: + +```ini +allow_tearing=1 +``` + +Then force it for your specific game: + +```ini +windowrule=force_tearing:1,title:Counter-Strike 2 +``` + +> **Warning:** Some graphics cards require setting `WLR_DRM_NO_ATOMIC=1` before mango starts for tearing to work. Add it to `/etc/environment` and reboot, or launch mango with `WLR_DRM_NO_ATOMIC=1 mango`. See [Monitors — Tearing](/docs/configuration/monitors#tearing-game-mode) for details. + +--- + +### How do I use pipes `|` in spawn commands? + +The standard `spawn` command does not support shell pipes directly. You must use `spawn_shell` instead. + +```ini +bind=SUPER,P,spawn_shell,echo "hello" | rofi -dmenu +``` + +--- + +### Certain key combinations do not work on my keyboard layout. + +`bind` automatically converts keysym to keycode, which is compatible with most layouts but can sometimes be imprecise. If a key combination is not triggering, use the **keycode** directly instead of the key name. + +Run `wev` and press the key to find its keycode, then use it in your bind: + +```ini +# Instead of: +bind=ALT,q,killclient + +# Use the keycode (e.g., code:24 = q on most layouts): +bind=ALT,code:24,killclient +``` + +You can also use `binds` (the `s` flag) to match by keysym instead of keycode: + +```ini +binds=ALT,q,killclient +``` diff --git a/docs/v0.13.1/index.md b/docs/v0.13.1/index.md new file mode 100644 index 00000000..d308370d --- /dev/null +++ b/docs/v0.13.1/index.md @@ -0,0 +1,42 @@ +--- +title: Introduction +description: A lightweight and feature-rich Wayland compositor based on dwl. +--- + + +**mango** is a Wayland compositor based on [dwl](https://codeberg.org/dwl/dwl/). It aims to be as lightweight as `dwl` and can be built completely within a few seconds, without compromising on functionality. + +> **Philosophy:** **Lightweight & Fast**: mango is designed to be minimal yet functional. It compiles in seconds and offers a robust set of features out of the box. + +## Feature Highlights + +Beyond basic window management, mangowm provides a rich set of features designed for a modern Wayland experience. + +- **[Animations](/docs/visuals/animations)** — Smooth, customizable animations for opening, moving, closing windows and tag switching. +- **[Layouts](/docs/window-management/layouts)** — Supports Scroller, Master-Stack, Monocle, Grid, Deck, and more, with per-tag layouts. +- **[Visual Effects](/docs/visuals/effects)** — Built-in blur, shadows, corner radius, and opacity effects powered by scenefx. +- **[IPC & Scripting](/docs/ipc)** — Control the compositor externally with robust IPC support for custom scripts and widgets. + +## Additional Features + +- **XWayland Support** — Excellent compatibility for legacy X11 applications. +- **Tag System** — Uses tags instead of workspaces, allowing separate window layouts for each tag. +- **Input Methods** — Great support for text input v2/v3 (Fcitx5, IBus). +- **Window States** — Rich states including swallow, minimize, maximize, fullscreen, and overlay. +- **Hot-Reload Config** — Simple external configuration that supports hot-reloading without restarting. +- **Scratchpads** — Support for both Sway-like and named scratchpads. + +## Community + +- **[Join the mangowm Discord](https://discord.gg/CPjbDxesh5)** — Chat with the community, get support, share your setup, and stay updated with the latest mangowm news. +- **[Join the GitHub Discussions](https://github.com/mangowm/mango/discussions)** — Ask questions, request features, report issues, or share ideas directly with contributors and other users. + +## Acknowledgements + +This project is built upon the hard work of several open-source projects: + +- **[wlroots](https://gitlab.freedesktop.org/wlroots/wlroots)** — Implementation of the Wayland protocol. +- **[mwc](https://github.com/nikoloc/mwc)** — Basal window animation reference. +- **[dwl](https://codeberg.org/dwl/dwl)** — Basal dwl features. +- **[sway](https://github.com/swaywm/sway)** — Sample implementation of the Wayland protocol. +- **[scenefx](https://github.com/wlrfx/scenefx)** — Library to simplify adding window effects. diff --git a/docs/v0.13.1/installation.md b/docs/v0.13.1/installation.md new file mode 100644 index 00000000..c5d4936c --- /dev/null +++ b/docs/v0.13.1/installation.md @@ -0,0 +1,308 @@ +--- +title: Installation +description: Install mangowm on AerynOS, Arch, Fedora, Gentoo, Guix System, NixOS, PikaOS, or build from source. +--- + +## Package Installation + +mangowm is available as a pre-built package on several distributions. Choose your distribution below. + +--- + +### AerynOS + +mangowm is available in the **AerynOS package repository**. + +You can install it using the `moss` package manager: + +```bash +sudo moss install mangowm +``` + +--- + +### Arch Linux + +mangowm is available in the **Arch User Repository (AUR)**. + +You can install it using an AUR helper like `yay` or `paru`: + +```bash +yay -S mangowm-git +``` + +> **Tip:** This package pulls the latest git version, ensuring you have the newest features and fixes. + +--- + +### Fedora + +The package is in the third-party **Terra repository**. First, add the Terra Repository. + +> **Warning:** Both commands require root privileges. Use `sudo` if needed. + +```bash +dnf install --nogpgcheck --repofrompath 'terra,https://repos.fyralabs.com/terra$releasever' terra-release +``` + +Then, install the package: + +```bash +dnf install mangowm +``` + +--- + +### Gentoo + +The package is hosted in the community-maintained **GURU** repository. + +1. **Add the GURU repository** + + ```bash + emerge --ask --verbose eselect-repository + eselect repository enable guru + emerge --sync guru + ``` + +2. **Unmask packages** + Add the required packages to your `package.accept_keywords` file: + - `gui-libs/scenefx` + - `gui-wm/mangowm` + +3. **Install mango** + ```bash + emerge --ask --verbose gui-wm/mangowm + ``` + +--- + +### Guix System + +The package definition is described in the source repository. + +1. **Add mango channel** + Add to `$HOME/.config/guix/channels.scm`: + + ```scheme + (cons (channel + (name 'mangowm) + (url "https://github.com/mangowm/mango.git") + (branch "main")) + %default-channels) + ``` + +2. **Install** + After running `guix pull`, you can install mangowm: + + ```bash + guix install mangowm + ``` + + Or add it to your system configuration using the mangowm module: + + ```scheme + (use-modules (mangowm)) + + (packages (cons* + mangowm-git + ... ;; Other packages + %base-packages)) + ``` + +> **Tip:** For more information, see the [Guix System documentation](https://guix.gnu.org/manual/devel/en/html_node/Channels.html). + +--- + +### NixOS + +The repository provides a Flake with a NixOS module. + +1. **Add flake input** + + ```nix + # flake.nix + { + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + mangowm = { + url = "github:mangowm/mango"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + # other inputs ... + }; + } + ``` + +2. **Import the NixOS module** + + **Option A — Import in `configuration.nix`:** + + ```nix + # configuration.nix (or any other file that you import) + {inputs, ...}: { + imports = [ + inputs.mangowm.nixosModules.mango + # .. other imports ... + ]; + + # ... + } + ``` + + **Option B — Import directly in flake:** + + ```nix + # flake.nix + { + # ... + + outputs = { self, nixpkgs, mangowm, ...}@inputs: let + inherit (nixpkgs) lib; + # ... + in { + nixosConfigurations.YourHostName = lib.nixosSystem { + modules = [ + mangowm.nixosModules.mango # or inputs.mangowm.nixosModules.mango + # other imports ... + ]; + }; + } + } + ``` + +3. **Enable the module** + + ```nix + # configuration.nix (or any other file that you import) + { + programs.mango.enable = true; + } + ``` + +4. **Start mango on login** + + The following are common examples. Refer to the official NixOS documentation for full configuration options. + + **Option A — greetd:** Autologin on first start; login screen after logout. + + ```nix + services.greetd = { + enable = true; + settings = { + initial_session = { + command = "mango"; + user = "your-username"; # auto-login on first start, no password required + }; + default_session = { + command = "${pkgs.greetd.tuigreet}/bin/tuigreet --cmd mango"; + user = "greeter"; + }; + }; + }; + ``` + + **Option B — Display manager autologin:** Autologin via an existing display manager (e.g. SDDM, GDM). [`addLoginEntry`](/docs/nix-options#addloginentry) (default: `true`) automatically registers mango as a session. + + ```nix + services.displayManager = { + defaultSession = "mango"; # derived from mango.desktop filename + autoLogin = { + enable = true; + user = "your-username"; + }; + }; + ``` + + **Option C — getty autologin:** No login screen, boots directly into mango on TTY1. + + For bash/zsh: + + ```nix + services.getty.autologinUser = "your-username"; + + environment.loginShellInit = '' + [ "$(tty)" = /dev/tty1 ] && exec mango + ''; + ``` + + For fish: + + ```nix + services.getty.autologinUser = "your-username"; + + programs.fish.loginShellInit = '' + if test (tty) = /dev/tty1 + exec mango + end + ''; + ``` + +5. **All available options** + + See [Nix Module Options](/docs/nix-options) for the full list of NixOS and Home Manager options. + +--- + +### PikaOS + +mangowm is available in the **PikaOS package repository**. + +You can install it using the `pikman` package manager: + +```bash +pikman install mangowm +``` + +--- + +## Building from Source + +If your distribution isn't listed above, or you want the latest unreleased changes, you can build mangowm from source. + +> **Info:** Ensure the following dependencies are installed before proceeding: +> +> - `wayland` +> - `wayland-protocols` +> - `libinput` +> - `libdrm` +> - `libxkbcommon` +> - `pixman` +> - `libdisplay-info` +> - `libliftoff` +> - `hwdata` +> - `seatd` +> - `pcre2` +> - `xorg-xwayland` +> - `libxcb` + +You will need to build `wlroots` and `scenefx` manually as well. + +1. **Build wlroots** + Clone and install the specific version required (check README for latest version). + + ```bash + git clone -b 0.19.3 https://gitlab.freedesktop.org/wlroots/wlroots.git + cd wlroots + meson build -Dprefix=/usr + sudo ninja -C build install + ``` + +2. **Build scenefx** + This library handles the visual effects. + + ```bash + git clone -b 0.4.1 https://github.com/wlrfx/scenefx.git + cd scenefx + meson build -Dprefix=/usr + sudo ninja -C build install + ``` + +3. **Build mangowm** + Finally, compile the compositor itself. + ```bash + git clone https://github.com/mangowm/mango.git + cd mango + meson build -Dprefix=/usr + sudo ninja -C build install + ``` diff --git a/docs/v0.13.1/ipc.md b/docs/v0.13.1/ipc.md new file mode 100644 index 00000000..e3ec4a32 --- /dev/null +++ b/docs/v0.13.1/ipc.md @@ -0,0 +1,154 @@ +--- +title: IPC +description: Control mangowm programmatically using mmsg. +--- + +## Introduction + +mangowm includes a powerful IPC (Inter-Process Communication) tool called `mmsg`. This allows you to query the window manager's state, watch for events, and execute commands from external scripts. + +## Basic Usage + +The general syntax for `mmsg` is: + +```bash +mmsg [-OTLq] +mmsg [-o <output>] -s [-t <tags>] [-l <layout>] [-c <tags>] [-d <cmd>,<arg1>,<arg2>,<arg3>,<arg4>,<arg5>] +mmsg [-o <output>] (-g | -w) [-OotlcvmfxekbA] +``` + +### Options + +| Flag | Description | +| :--- | :--- | +| `-q` | Quit mangowm. | +| `-g` | **Get** values (tags, layout, focused client). | +| `-s` | **Set** values (switch tags, layouts). | +| `-w` | **Watch** mode (streams events). | +| `-O` | Get all output (monitor) information. | +| `-T` | Get number of tags. | +| `-L` | Get all available layouts. | +| `-o` | Select output (monitor). | +| `-t` | Get/set selected tags (set with `[+-^.]`). | +| `-l` | Get/set current layout. | +| `-c` | Get title and appid of focused client. | +| `-v` | Get visibility of statusbar. | +| `-m` | Get fullscreen status. | +| `-f` | Get floating status. | +| `-d` | **Dispatch** an internal command. | +| `-x` | Get focused client geometry. | +| `-e` | Get the name of the last focused layer. | +| `-k` | Get current keyboard layout. | +| `-b` | Get current keybind mode. | +| `-A` | Get scale factor of monitor. | + +## Examples + +### Tag Management + +You can perform arithmetic on tags using the `-t` flag with `-s` (set). + +```bash +# Switch to Tag 1 +mmsg -t 1 + +# Add Tag 2 to current view (Multiview) +mmsg -s -t 2+ + +# Remove Tag 2 from current view +mmsg -s -t 2- + +# Toggle Tag 2 +mmsg -s -t 2^ +``` + +### Layouts + +Switch layouts programmatically. Layout codes: `S` (Scroller), `T` (Tile), `G` (Grid), `M` (Monocle), `K` (Deck), `CT` (Center Tile), `RT` (Right Tile), `VS` (Vertical Scroller), `VT` (Vertical Tile), `VG` (Vertical Grid), `VK` (Vertical Deck), `DW` (Dwindle), `F` (Fair), `VF` (Vertical Fair). + +```bash +# Switch to Scroller +mmsg -l "S" + +# Switch to Tile +mmsg -l "T" +``` + +### Dispatching Commands + +Any command available in `config.conf` keybindings can be run via IPC. + +```bash +# Close the focused window +mmsg -d killclient + +# Resize window by +10 width +mmsg -d resizewin,+10,0 + +# Toggle fullscreen +mmsg -d togglefullscreen + +# Disable a monitor power +mmsg -d disable_monitor,eDP-1 +``` + +### Monitoring & Status + +Use `-g` or `-w` to build custom status bars or automation scripts. + +```bash +# Watch for all message changes +mmsg -w + +# Get all messages without watch +mmsg -g + +# Watch focused client appid and title +mmsg -w -c + +# Get all available outputs +mmsg -O + +# Get all tags message +mmsg -g -t + +# Get current focused client message +mmsg -g -c + +# Get current keyboard layout +mmsg -g -k + +# Get current keybind mode +mmsg -g -b + +# Get scale factor of current monitor +mmsg -g -A +``` + +#### Tag Message Format + +- State: 0 → none, 1 → active, 2 → urgent + +Example output: + +| Monitor | Tag Number | Tag State | Clients in Tag | Focused Client | +|---------|------------|-----------|----------------|----------------| +| eDP-1 | tag 2 | 0 | 1 | 0 | + +| Monitor | occupied tags mask | active tags mask | urgent tags mask | +|---------|--------------------|------------------|------------------| +| eDP-1 | 14 | 6 | 0 | + +## Virtual Monitors + +You can create headless outputs for screen mirroring or remote desktop access (e.g., Sunshine/Moonlight). + +```bash +# Create a virtual output +mmsg -d create_virtual_output + +# Configure it (set resolution) +wlr-randr --output HEADLESS-1 --pos 1920,0 --mode 1920x1080@60Hz + +# Destroy all virtual outputs +mmsg -d destroy_all_virtual_output \ No newline at end of file diff --git a/docs/v0.13.1/meta.json b/docs/v0.13.1/meta.json new file mode 100644 index 00000000..a1f41d47 --- /dev/null +++ b/docs/v0.13.1/meta.json @@ -0,0 +1,22 @@ +{ + "title": "v0.13.1", + "description": "v0.13.1 release", + "root": true, + "pages": [ + "---Getting Started---", + "index", + "installation", + "quick-start", + "---Configuration---", + "configuration", + "visuals", + "window-management", + "bindings", + "---Examples---", + "screenshot", + "---Reference---", + "nix-options", + "ipc", + "faq" + ] +} diff --git a/docs/v0.13.1/nix-options.md b/docs/v0.13.1/nix-options.md new file mode 100644 index 00000000..2537d9d8 --- /dev/null +++ b/docs/v0.13.1/nix-options.md @@ -0,0 +1,519 @@ +--- +title: Nix Module Options +description: NixOS and Home Manager configuration options for mangowm. +--- + +> **Note:** This document is automatically generated from the Nix module source code. + +## NixOS + +**System-level options via `programs.mango`.** + +### enable + + + +Whether to enable mango, a wayland compositor based on dwl\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +false +``` + + + +*Example:* + +```nix +true +``` + +*Declared by:* + - [\<mango/nix/nixos-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/nixos-modules.nix) + + + +### package + + + +The mango package to use + + + +*Type:* +package + + + +*Default:* + +```nix +<derivation mango-nightly> +``` + +*Declared by:* + - [\<mango/nix/nixos-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/nixos-modules.nix) + + + +### addLoginEntry + + + +Whether to add a login entry to the display manager for mango\. Only has effect if a display manager is configured (e\.g\. SDDM, GDM via ` services.displayManager `)\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +true +``` + +*Declared by:* + - [\<mango/nix/nixos-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/nixos-modules.nix) + +## Home Manager + +**Configure mangowm declaratively via `wayland.windowManager.mango`.** + +### enable + + + +Whether to enable mangowm, a Wayland compositor based on dwl\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +false +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### package + + + +The mango package to use + + + +*Type:* +package + + + +*Default:* + +```nix +<derivation mango-nightly> +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### autostart_sh + + + +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:* +strings concatenated with “\\n” + + + +*Default:* + +```nix +"" +``` + + + +*Example:* + +```nix +'' + waybar & + dunst & +'' +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### bottomPrefixes + + + +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\. + + + +*Type:* +list of string + + + +*Default:* + +```nix +[ ] +``` + + + +*Example:* + +```nix +[ + "source" +] +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### extraConfig + + + +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\. + + + +*Type:* +strings concatenated with “\\n” + + + +*Default:* + +```nix +"" +``` + + + +*Example:* + +```nix +'' + # Advanced config that doesn't fit structured format + special_option = 1 +'' +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### settings + + + +Mango configuration written in Nix\. Entries with the same key +should be written as lists\. Variables and colors names should be +quoted\. See [https://mangowm\.github\.io/docs](https://mangowm\.github\.io/docs) 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 ` + +Keymodes (submaps) are supported via the special ` keymode ` attribute\. Each keymode +is a nested attribute set under ` keymode ` that contains its own bindings\. + + + +*Type:* +Mango configuration value + + + +*Default:* + +```nix +{ } +``` + + + +*Example:* + +```nix +{ + # 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" + "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" + ]; + }; + }; +} + +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### systemd\.enable + + + +Whether to enable ` mango-session.target ` on +mango startup\. This links to +` graphical-session.target `\. +Some important environment variables will be imported to systemd +and dbus user environment before reaching the target, including + + - ` DISPLAY ` + - ` WAYLAND_DISPLAY ` + - ` XDG_CURRENT_DESKTOP ` + - ` XDG_SESSION_TYPE ` + - ` NIXOS_OZONE_WL ` + You can extend this list using the ` systemd.variables ` option\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +true +``` + + + +*Example:* + +```nix +false +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### systemd\.extraCommands + + + +Extra commands to run after D-Bus activation\. + + + +*Type:* +list of string + + + +*Default:* + +```nix +[ + "systemctl --user reset-failed" + "systemctl --user start mango-session.target" +] +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### systemd\.variables + + + +Environment variables imported into the systemd and D-Bus user environment\. + + + +*Type:* +list of string + + + +*Default:* + +```nix +[ + "DISPLAY" + "WAYLAND_DISPLAY" + "XDG_CURRENT_DESKTOP" + "XDG_SESSION_TYPE" + "NIXOS_OZONE_WL" + "XCURSOR_THEME" + "XCURSOR_SIZE" +] +``` + + + +*Example:* + +```nix +[ + "--all" +] +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### systemd\.xdgAutostart + + + +Whether to enable autostart of applications using +` systemd-xdg-autostart-generator(8) ` +\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +false +``` + + + +*Example:* + +```nix +true +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + + + +### topPrefixes + + + +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\. + + + +*Type:* +list of string + + + +*Default:* + +```nix +[ ] +``` + + + +*Example:* + +```nix +[ + "source" +] +``` + +*Declared by:* + - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) + diff --git a/docs/v0.13.1/quick-start.md b/docs/v0.13.1/quick-start.md new file mode 100644 index 00000000..bc192474 --- /dev/null +++ b/docs/v0.13.1/quick-start.md @@ -0,0 +1,88 @@ +--- +title: Quick Start +description: Basic configuration and first steps with mangowm. +--- + +Now that you have mangowm installed, let's get your environment set up. + +## Initial Setup + +1. **Create Configuration Directory** + + mangowm looks for configuration files in `~/.config/mango/`. + + ```bash + mkdir -p ~/.config/mango + ``` + +2. **Copy Default Config** + + A default configuration file is provided at `/etc/mango/config.conf`. Copy it to your local directory to start customizing. + + ```bash + cp /etc/mango/config.conf ~/.config/mango/config.conf + ``` + +3. **Launch mangowm** + + You can now start the compositor from your TTY. + + ```bash + mango + ``` + + Optional: To specify a custom config file path: + + ```bash + mango -c /path/to/your/config.conf + ``` + +## Essential Keybindings + +mangowm uses the following keybinds by default: + +| Key Combination | Action | +| :--- | :--- | +| `Alt` + `Return` | Open Terminal (defaults to `foot`) | +| `Alt` + `Space` | Open Launcher (defaults to `rofi`) | +| `Alt` + `Q` | Close (Kill) the active window | +| `Super` + `M` | Quit mangowm | +| `Super` + `F` | Toggle Fullscreen | +| `Alt` + `Arrow Keys` | Move focus (Left, Right, Up, Down) | +| `Ctrl` + `1-9` | Switch to Tag 1-9 | +| `Alt` + `1-9` | Move window to Tag 1-9 | + +> **Warning:** Some default bindings rely on specific tools like `foot` (terminal) and `rofi` (launcher). Ensure you have them installed or update your `config.conf` to use your preferred alternatives. + +## Recommended Tools + +To get a fully functional desktop experience, we recommend installing the following components: + +| Category | Recommended Tools | +| :--- | :--- | +| Application Launcher | rofi, bemenu, wmenu, fuzzel | +| Terminal Emulator | foot, wezterm, alacritty, kitty, ghostty | +| Status Bar | waybar, eww, quickshell, ags | +| Desktop Shell | Noctalia, DankMaterialShell | +| Wallpaper Setup | awww(swww), swaybg | +| Notification Daemon | swaync, dunst, mako | +| Desktop Portal | xdg-desktop-portal, xdg-desktop-portal-wlr, xdg-desktop-portal-gtk | +| Clipboard | wl-clipboard, wl-clip-persist, cliphist | +| Gamma Control / Night Light | wlsunset, gammastep | +| Miscellaneous | xfce-polkit, wlogout | + +## Example Configuration + +Check out the [example configuration](https://github.com/DreamMaoMao/mango-config) by the creator of mangowm, including complete setups for mangowm, Waybar, Rofi, and more. + +```bash +git clone https://github.com/DreamMaoMao/mango-config.git ~/.config/mango +``` + +## Next Steps + +Now that you are up and running, dive deeper into customizing mangowm: + +- [Configure Monitors](/docs/configuration/monitors) — Set up resolution, scaling, and multi-monitor layouts. +- [Window Rules](/docs/window-management/rules#window-rules) — Define how specific applications should behave. +- [Appearance](/docs/visuals/theming) — Customize colors, borders, gaps, and effects. diff --git a/docs/v0.13.1/screenshot.md b/docs/v0.13.1/screenshot.md new file mode 100644 index 00000000..f07cdf0c --- /dev/null +++ b/docs/v0.13.1/screenshot.md @@ -0,0 +1,213 @@ +--- + +title: Screenshots +description: Example screenshot keybindings and capture workflows for mangowm. + +--- + +mangowm does not include a built-in screenshot tool. This keeps the compositor lean. +Instead, compose your own workflow from small Wayland utilities and bind them to keys; + +| Tool | Purpose | +| :--- | :--- | +| [`grim`](https://github.com/emersion/grim) | Capture the screen or a region to a file | +| [`slurp`](https://github.com/emersion/slurp) | Interactively select a region for `grim` | +| [`wl-copy`](https://github.com/bugaevc/wl-clipboard) | Copy screenshots directly to the clipboard | +| [`satty`](https://github.com/gabm/Satty) | Annotate screenshots before saving | +| [`wayfreeze`](https://github.com/nicbk/wayfreeze) | Freeze the screen before capture | + +Install the required with your package manager or from source. + +`grim` writes to the file path you give it, but **will not create missing directories**. Create one first: + +```bash +mkdir -p ~/Pictures/Screenshots +``` + +Any directory works. `~/Pictures/Screenshots/` is just a convention. + +## Quick Binds + +Short, single-step commands can be placed directly in `config.conf` with `spawn_shell`. +No script file needed. + +### Fullscreen + +Captures the entire display. + +```ini +bind=NONE,Print,spawn_shell,grim $HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png +``` + +### Region + +Select an area with `slurp` before capturing. + +```ini +bind=SHIFT,Print,spawn_shell,g=$(slurp -d) && [ -n "$g" ] && grim -g "$g" $HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png +``` + +### Pointer + +Captures the full screen including the cursor. + +```ini +bind=ALT,Print,spawn_shell,grim -c $HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png +``` + +### Clipboard + +Captures to a temporary file and copies the image to the clipboard; no file is saved. + +```ini +bind=CTRL,Print,spawn_shell,f=$(mktemp -t screenshot-XXXXXX.png) && grim "$f" && wl-copy < "$f" && rm -f "$f" +``` + +### Annotate + +Captures and opens `satty` for drawing before saving. + +```ini +bind=SUPER,Print,spawn_shell,f=$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png && grim "$f" && satty --filename "$f" --output-filename "$f" --actions-on-enter save-to-file --early-exit +``` + +## Script Binds + +When a command involves multi-step logic, geometry parsing, FIFOs, or screen freezing, +move it into a script and invoke it with `spawn` instead of `spawn_shell`. + +Create the scripts directory first: + +```bash +mkdir -p ~/.config/mango/scripts/screenshot +``` + +### Window + +Uses `mmsg` (ships with mango) to capture the focused window. + +`~/.config/mango/scripts/screenshot/window.sh`: + +```bash +#!/usr/bin/env bash +geometry=$(mmsg -x | awk '/x / {x=$3} /y / {y=$3} /width / {w=$3} /height / {h=$3} END {print x","y" "w"x"h}') +[ -z "$geometry" ] && exit 1 +grim -g "$geometry" "$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png" +``` + +```ini +bind=CTRL+SHIFT,Print,spawn,$HOME/.config/mango/scripts/screenshot/window.sh +``` + +### Freeze + +Freezes the screen with `wayfreeze` before capturing. + +`~/.config/mango/scripts/screenshot/freeze.sh`: + +```bash +#!/usr/bin/env bash +pipe=$(mktemp -u).fifo +mkfifo "$pipe" +wayfreeze --after-freeze-timeout 100 --after-freeze-cmd "echo > $pipe" & +wayfreeze_pid=$! +read -r < "$pipe" +grim "$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png" +kill "$wayfreeze_pid" 2>/dev/null +rm -f "$pipe" +``` + +```ini +bind=CTRL+SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/freeze.sh +``` + +### Freeze + Region + +Freeze, then select a region with `slurp`. Cleans up on cancel. + +`~/.config/mango/scripts/screenshot/freeze-region.sh`: + +```bash +#!/usr/bin/env bash +pipe=$(mktemp -u).fifo +mkfifo "$pipe" +wayfreeze --after-freeze-timeout 100 --after-freeze-cmd "echo > $pipe" & +wayfreeze_pid=$! +read -r < "$pipe" +geometry=$(slurp -d) +if [[ -z "$geometry" ]]; then + kill "$wayfreeze_pid" 2>/dev/null + rm -f "$pipe" + exit 1 +fi +grim -g "$geometry" "$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png" +kill "$wayfreeze_pid" 2>/dev/null +rm -f "$pipe" +``` + +```ini +bind=SHIFT+SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/freeze-region.sh +``` + +Make all three scripts executable: + +```bash +chmod +x ~/.config/mango/scripts/screenshot/*.sh +``` + +## All-in-One Script + +Prefer fewer files? A single script with subcommands covers every mode above. +Place it in the same directory and use it in place of the individual scripts. + +`~/.config/mango/scripts/screenshot/screenshot.sh`: + +```bash +#!/usr/bin/env bash +set -euo pipefail +mkdir -p "$HOME/Pictures/Screenshots" +filepath="$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png" + +case "${1:-fullscreen}" in + region) + g=$(slurp -d); [ -z "$g" ] && exit 1 + grim -g "$g" "$filepath" ;; + window) + g=$(mmsg -x | awk '/x / {x=$3} /y / {y=$3} /width / {w=$3} /height / {h=$3} END {print x","y" "w"x"h}') + [ -z "$g" ] && exit 1 + grim -g "$g" "$filepath" ;; + freeze) + p=$(mktemp -u).fifo; mkfifo "$p" + wayfreeze --after-freeze-timeout 100 --after-freeze-cmd "echo > $p" & wp=$! + read -r < "$p"; grim "$filepath" + kill "$wp" 2>/dev/null; rm -f "$p" ;; + freeze-region) + p=$(mktemp -u).fifo; mkfifo "$p" + wayfreeze --after-freeze-timeout 100 --after-freeze-cmd "echo > $p" & wp=$! + read -r < "$p"; g=$(slurp -d) + if [ -z "$g" ]; then kill "$wp" 2>/dev/null; rm -f "$p"; exit 1; fi + grim -g "$g" "$filepath" + kill "$wp" 2>/dev/null; rm -f "$p" ;; + annotate) + grim "$filepath"; satty --filename "$filepath" --output-filename "$filepath" --actions-on-enter save-to-file --early-exit ;; + *) grim "$filepath" ;; +esac +``` + +Make the script executable: + + +```bash +chmod +x ~/.config/mango/scripts/screenshot/screenshot.sh +``` + +Then add the binds to `config.conf`: + +```ini +bind=NONE,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh fullscreen +bind=SHIFT,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh region +bind=CTRL+SHIFT,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh window +bind=CTRL+SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh freeze +bind=SHIFT+SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh freeze-region +bind=SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh annotate +``` diff --git a/docs/v0.13.1/visuals/animations.md b/docs/v0.13.1/visuals/animations.md new file mode 100644 index 00000000..76477e05 --- /dev/null +++ b/docs/v0.13.1/visuals/animations.md @@ -0,0 +1,108 @@ +--- +title: Animations +description: Configure smooth transitions for windows and layers. +--- + +## Enabling Animations + +mangowm supports animations for both standard windows and layer shell surfaces (like bars and notifications). + +```ini +animations=1 +layer_animations=1 +``` + +## Animation Types + +You can define different animation styles for opening and closing windows and layer surfaces. + +Available types: `slide`, `zoom`, `fade`, `none`. + +```ini +animation_type_open=zoom +animation_type_close=slide +layer_animation_type_open=slide +layer_animation_type_close=slide +``` + +## Fade Settings + +Control the fade-in and fade-out effects for animations. + +```ini +animation_fade_in=1 +animation_fade_out=1 +fadein_begin_opacity=0.5 +fadeout_begin_opacity=0.5 +``` + +- `animation_fade_in` — Enable fade-in effect (0: disable, 1: enable) +- `animation_fade_out` — Enable fade-out effect (0: disable, 1: enable) +- `fadein_begin_opacity` — Starting opacity for fade-in animations (0.0–1.0) +- `fadeout_begin_opacity` — Starting opacity for fade-out animations (0.0–1.0) + +## Zoom Settings + +Adjust the zoom ratios for zoom animations. + +```ini +zoom_initial_ratio=0.4 +zoom_end_ratio=0.8 +``` + +- `zoom_initial_ratio` — Initial zoom ratio +- `zoom_end_ratio` — End zoom ratio + +## Durations + +Control the speed of animations (in milliseconds). + +| Setting | Type | Default | Description | +| :--- | :--- | :--- | :--- | +| `animation_duration_move` | integer | `500` | Move animation duration (ms) | +| `animation_duration_open` | integer | `400` | Open animation duration (ms) | +| `animation_duration_tag` | integer | `300` | Tag animation duration (ms) | +| `animation_duration_close` | integer | `300` | Close animation duration (ms) | +| `animation_duration_focus` | integer | `0` | Focus change (opacity transition) animation duration (ms) | + +```ini +animation_duration_move=500 +animation_duration_open=400 +animation_duration_tag=300 +animation_duration_close=300 +animation_duration_focus=0 +``` + +## Custom Bezier Curves + +Bezier curves determine the "feel" of an animation (e.g., linear vs. bouncy). The format is `x1,y1,x2,y2`. + +You can visualize and generate curve values using online tools like [cssportal.com](https://www.cssportal.com/css-cubic-bezier-generator/) or [easings.net](https://easings.net). + +| Setting | Type | Default | Description | +| :--- | :--- | :--- | :--- | +| `animation_curve_open` | string | `0.46,1.0,0.29,0.99` | Open animation bezier curve | +| `animation_curve_move` | string | `0.46,1.0,0.29,0.99` | Move animation bezier curve | +| `animation_curve_tag` | string | `0.46,1.0,0.29,0.99` | Tag animation bezier curve | +| `animation_curve_close` | string | `0.46,1.0,0.29,0.99` | Close animation bezier curve | +| `animation_curve_focus` | string | `0.46,1.0,0.29,0.99` | Focus change (opacity transition) animation bezier curve | +| `animation_curve_opafadein` | string | `0.46,1.0,0.29,0.99` | Open opacity animation bezier curve | +| `animation_curve_opafadeout` | string | `0.5,0.5,0.5,0.5` | Close opacity animation bezier curve | + +```ini +animation_curve_open=0.46,1.0,0.29,0.99 +animation_curve_move=0.46,1.0,0.29,0.99 +animation_curve_tag=0.46,1.0,0.29,0.99 +animation_curve_close=0.46,1.0,0.29,0.99 +animation_curve_focus=0.46,1.0,0.29,0.99 +animation_curve_opafadein=0.46,1.0,0.29,0.99 +animation_curve_opafadeout=0.5,0.5,0.5,0.5 +``` + +## Tag Animation Direction + +Control the direction of tag switch animations. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `tag_animation_direction` | `1` | Tag animation direction (1: horizontal, 0: vertical) | \ No newline at end of file diff --git a/docs/v0.13.1/visuals/effects.md b/docs/v0.13.1/visuals/effects.md new file mode 100644 index 00000000..23c1f206 --- /dev/null +++ b/docs/v0.13.1/visuals/effects.md @@ -0,0 +1,82 @@ +--- +title: Window Effects +description: Add visual polish with blur, shadows, and opacity. +--- + +## Blur + +Blur creates a frosted glass effect for transparent windows. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `blur` | `0` | Enable blur for windows. | +| `blur_layer` | `0` | Enable blur for layer surfaces (like bars/docks). | +| `blur_optimized` | `1` | Caches the wallpaper and blur background, significantly reducing GPU usage. Disabling it will significantly increase GPU consumption and may cause rendering lag. **Highly recommended.** | +| `blur_params_radius` | `5` | The strength (radius) of the blur. | +| `blur_params_num_passes` | `1` | Number of passes. Higher = smoother but more expensive. | +| `blur_params_noise` | `0.02` | Blur noise level. | +| `blur_params_brightness` | `0.9` | Blur brightness adjustment. | +| `blur_params_contrast` | `0.9` | Blur contrast adjustment. | +| `blur_params_saturation` | `1.2` | Blur saturation adjustment. | + +> **Warning:** Blur has a relatively high impact on performance. If your hardware is limited, it is not recommended to enable it. If you experience lag with blur on, ensure `blur_optimized=1` — disabling it will significantly increase GPU consumption and may cause rendering lag. To disable blur entirely, set `blur=0`. + +--- + +## Shadows + +Drop shadows help distinguish floating windows from the background. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `shadows` | `0` | Enable shadows. | +| `layer_shadows` | `0` | Enable shadows for layer surfaces. | +| `shadow_only_floating` | `1` | Only draw shadows for floating windows (saves performance). | +| `shadows_size` | `10` | Size of the shadow. | +| `shadows_blur` | `15` | Shadow blur amount. | +| `shadows_position_x` | `0` | Shadow X offset. | +| `shadows_position_y` | `0` | Shadow Y offset. | +| `shadowscolor` | `0x000000ff` | Color of the shadow. | + +```ini +# Example shadows configuration +shadows=1 +layer_shadows=1 +shadow_only_floating=1 +shadows_size=12 +shadows_blur=15 +shadows_position_x=0 +shadows_position_y=0 +shadowscolor=0x000000ff +``` + +--- + +## Opacity & Corner Radius + +Control the transparency and roundness of your windows. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `border_radius` | `0` | Window corner radius in pixels. | +| `border_radius_location_default` | `0` | Corner radius location: `0` (all), `1` (top-left), `2` (top-right), `3` (bottom-left), `4` (bottom-right), `5` (closest corner). | +| `no_radius_when_single` | `0` | Disable radius if only one window is visible. | +| `focused_opacity` | `1.0` | Opacity for the active window (0.0 - 1.0). | +| `unfocused_opacity` | `1.0` | Opacity for inactive windows (0.0 - 1.0). | + +```ini +# Window corner radius in pixels +border_radius=0 + +# Corner radius location (0=all, 1=top-left, 2=top-right, 3=bottom-left, 4=bottom-right) +border_radius_location_default=0 + +# Disable radius if only one window is visible +no_radius_when_single=0 + +# Opacity for the active window (0.0 - 1.0) +focused_opacity=1.0 + +# Opacity for inactive windows +unfocused_opacity=1.0 +``` diff --git a/docs/v0.13.1/visuals/index.mdx b/docs/v0.13.1/visuals/index.mdx new file mode 100644 index 00000000..f71ae2f8 --- /dev/null +++ b/docs/v0.13.1/visuals/index.mdx @@ -0,0 +1,19 @@ +--- +title: Visuals +description: Customize borders, colors, effects, and animations. +icon: Palette +--- + +Customize the look of your desktop. + +<Cards> + +<Card href="/docs/visuals/theming" title="Theming" description="Borders, colors, and cursor" /> + +<Card href="/docs/visuals/status-bar" title="Status Bar" description="Built-in status bar" /> + +<Card href="/docs/visuals/effects" title="Effects" description="Blur, shadows, rounded corners" /> + +<Card href="/docs/visuals/animations" title="Animations" description="Window and tag animations" /> + +</Cards> \ No newline at end of file diff --git a/docs/v0.13.1/visuals/meta.json b/docs/v0.13.1/visuals/meta.json new file mode 100644 index 00000000..58723c4e --- /dev/null +++ b/docs/v0.13.1/visuals/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Visuals", + "pages": ["theming", "status-bar", "effects", "animations"] +} diff --git a/docs/v0.13.1/visuals/status-bar.md b/docs/v0.13.1/visuals/status-bar.md new file mode 100644 index 00000000..f2924e83 --- /dev/null +++ b/docs/v0.13.1/visuals/status-bar.md @@ -0,0 +1,141 @@ +--- +title: Status Bar +description: Configure Waybar for mangowm. +--- + +## Module Configuration + +mangowm is compatible with Waybar's `ext/workspaces` module (Wayland standard) or the `dwl/tags` module. We recommend `ext/workspaces` for the best experience. + +> **Tip:** You can also use the `dwl/tags` module, but `ext/workspaces` provides better integration with mangowm's features. The `ext/workspaces` module requires **Waybar > 0.14.0**. + +### `config.jsonc` + +Add the following to your Waybar configuration: + +```jsonc +{ + "modules-left": [ + "ext/workspaces", + "dwl/window" + ], + "ext/workspaces": { + "format": "{icon}", + "ignore-hidden": true, + "on-click": "activate", + "on-click-right": "deactivate", + "sort-by-id": true + }, + "dwl/window": { + "format": "[{layout}] {title}" + } +} +``` + +## Styling + +You can style the tags using standard CSS in `style.css`. + +### `style.css` + +```css +#workspaces { + border-radius: 4px; + border-width: 2px; + border-style: solid; + border-color: #c9b890; + margin-left: 4px; + padding-left: 10px; + padding-right: 6px; + background: rgba(40, 40, 40, 0.76); +} + +#workspaces button { + border: none; + background: none; + box-shadow: inherit; + text-shadow: inherit; + color: #ddca9e; + padding: 1px; + padding-left: 1px; + padding-right: 1px; + margin-right: 2px; + margin-left: 2px; +} + +#workspaces button.hidden { + color: #9e906f; + background-color: transparent; +} + +#workspaces button.visible { + color: #ddca9e; +} + +#workspaces button:hover { + color: #d79921; +} + +#workspaces button.active { + background-color: #ddca9e; + color: #282828; + margin-top: 5px; + margin-bottom: 5px; + padding-top: 1px; + padding-bottom: 0px; + border-radius: 3px; +} + +#workspaces button.urgent { + background-color: #ef5e5e; + color: #282828; + margin-top: 5px; + margin-bottom: 5px; + padding-top: 1px; + padding-bottom: 0px; + border-radius: 3px; +} + +#tags { + background-color: transparent; +} + +#tags button { + background-color: #fff; + color: #a585cd; +} + +#tags button:not(.occupied):not(.focused) { + font-size: 0; + min-width: 0; + min-height: 0; + margin: -17px; + padding: 0; + color: transparent; + background-color: transparent; +} + +#tags button.occupied { + background-color: #fff; + color: #cdc885; +} + +#tags button.focused { + background-color: rgb(186, 142, 213); + color: #fff; +} + +#tags button.urgent { + background: rgb(171, 101, 101); + color: #fff; +} + +#window { + background-color: rgb(237, 196, 147); + color: rgb(63, 37, 5); +} +``` + +## Complete Configuration Example + +> **Tip:** You can find a complete Waybar configuration for mangowm at [waybar-config](https://github.com/DreamMaoMao/waybar-config). \ No newline at end of file diff --git a/docs/v0.13.1/visuals/theming.md b/docs/v0.13.1/visuals/theming.md new file mode 100644 index 00000000..676c575b --- /dev/null +++ b/docs/v0.13.1/visuals/theming.md @@ -0,0 +1,62 @@ +--- +title: Theming +description: Customize the visual appearance of borders, colors, and the cursor. +--- + +## Dimensions + +Control the sizing of window borders and gaps. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `borderpx` | `4` | Border width in pixels. | +| `gappih` | `5` | Horizontal inner gap (between windows). | +| `gappiv` | `5` | Vertical inner gap. | +| `gappoh` | `10` | Horizontal outer gap (between windows and screen edges). | +| `gappov` | `10` | Vertical outer gap. | + +## Colors + +Colors are defined in `0xRRGGBBAA` hex format. + +```ini +# Background color of the root window +rootcolor=0x323232ff + +# Inactive window border +bordercolor=0x444444ff + +# Drop shadow when dragging windows +dropcolor=0x8FBA7C55 + +# Split window border color in manual dwindle layout +splitcolor=0xEB441EFF + +# Active window border +focuscolor=0xc66b25ff + +# Urgent window border (alerts) +urgentcolor=0xad401fff +``` + +### State-Specific Colors + +You can also color-code windows based on their state: + +| State | Config Key | Default Color | +| :--- | :--- | :--- | +| Maximized | `maximizescreencolor` | `0x89aa61ff` | +| Scratchpad | `scratchpadcolor` | `0x516c93ff` | +| Global | `globalcolor` | `0xb153a7ff` | +| Overlay | `overlaycolor` | `0x14a57cff` | + +> **Tip:** For scratchpad window sizing, see [Scratchpad](/docs/window-management/scratchpad) configuration. + +## Cursor Theme + +Set the size and theme of your mouse cursor. + +```ini +cursor_size=24 +cursor_theme=Adwaita +``` diff --git a/docs/v0.13.1/window-management/index.mdx b/docs/v0.13.1/window-management/index.mdx new file mode 100644 index 00000000..b96c5891 --- /dev/null +++ b/docs/v0.13.1/window-management/index.mdx @@ -0,0 +1,19 @@ +--- +title: Window Management +description: Layouts, rules, and window behavior. +icon: LayoutGrid +--- + +Window management with layouts, rules, and scratchpad support. + +<Cards> + +<Card href="/docs/window-management/layouts" title="Layouts" description="Tile, scroller, monocle, grid, deck" /> + +<Card href="/docs/window-management/rules" title="Rules" description="Window rules and conditions" /> + +<Card href="/docs/window-management/overview" title="Overview" description="Window states and properties" /> + +<Card href="/docs/window-management/scratchpad" title="Scratchpad" description="Quick access to applications" /> + +</Cards> \ No newline at end of file diff --git a/docs/v0.13.1/window-management/layouts.md b/docs/v0.13.1/window-management/layouts.md new file mode 100644 index 00000000..45b08ec2 --- /dev/null +++ b/docs/v0.13.1/window-management/layouts.md @@ -0,0 +1,135 @@ +--- +title: Layouts +description: Configure and switch between different window layouts. +--- + +## Supported Layouts + +mangowm supports a variety of layouts that can be assigned per tag. + +- `tile` +- `scroller` +- `monocle` +- `grid` +- `deck` +- `center_tile` +- `vertical_tile` +- `right_tile` +- `vertical_scroller` +- `vertical_grid` +- `vertical_deck` +- `dwindle` +- `fair` +- `vertical_fair` + +--- + +## Scroller Layout + +The Scroller layout positions windows in a scrollable strip, similar to PaperWM. + +### Configuration + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `scroller_structs` | `20` | Width reserved on sides when window ratio is 1. | +| `scroller_default_proportion` | `0.9` | Default width proportion for new windows. | +| `scroller_focus_center` | `0` | Always center the focused window (1 = enable). | +| `scroller_prefer_center` | `0` | Center focused window only if it was outside the view. | +| `scroller_prefer_overspread` | `1` | Allow windows to overspread when there's extra space. | +| `edge_scroller_pointer_focus` | `1` | Focus windows even if partially off-screen. | +| `scroller_proportion_preset` | `0.5,0.8,1.0` | Presets for cycling window widths. | +| `scroller_ignore_proportion_single` | `1` | Ignore proportion adjustments for single windows. | +| `scroller_default_proportion_single` | `1.0` | Default proportion for single windows in scroller. **Requires `scroller_ignore_proportion_single=0` to take effect.** | + +> **Warning:** `scroller_prefer_overspread`, `scroller_focus_center`, and `scroller_prefer_center` interact with each other. Their priority order is: +> +> **scroller_prefer_overspread > scroller_focus_center > scroller_prefer_center** +> +> To ensure a lower-priority setting takes effect, you must set all higher-priority options to `0`. + +```ini +# Example scroller configuration +scroller_structs=20 +scroller_default_proportion=0.9 +scroller_focus_center=0 +scroller_prefer_center=0 +scroller_prefer_overspread=1 +edge_scroller_pointer_focus=1 +scroller_default_proportion_single=1.0 +scroller_proportion_preset=0.5,0.8,1.0 +``` + +--- + +## Master-Stack Layouts + +These settings apply to layouts like `tile` and `center_tile`. + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `new_is_master` | `1` | New windows become the master window. | +| `default_mfact` | `0.55` | The split ratio between master and stack areas. | +| `default_nmaster` | `1` | Number of allowed master windows. | +| `smartgaps` | `0` | Disable gaps when only one window is present. | +| `center_master_overspread` | `0` | (Center Tile) Master spreads across screen if no stack exists. | +| `center_when_single_stack` | `1` | (Center Tile) Center master when only one stack window exists. | + +```ini +# Example master-stack configuration +new_is_master=1 +smartgaps=0 +default_mfact=0.55 +default_nmaster=1 +``` + +--- + +## Dwindle Layout + +The Dwindle layout arranges windows as a binary tree of recursive splits. Each new window splits the focused window's container, producing a spiral-like tiling. + +### Configuration + +| Setting | Default | Description | +| :--- | :--- | :--- | +| `dwindle_split_ratio` | `0.5` | Ratio used for new splits (`0.05`–`0.95`). | +| `dwindle_smart_split` | `0` | Pick the split axis from the cursor's position inside the focused window. The new window appears on the cursor's side. | +| `dwindle_hsplit` | `1` | Side-by-side splits: where the new window goes. `0` = follow cursor, `1` = right, `2` = left. | +| `dwindle_vsplit` | `1` | Top/bottom splits: where the new window goes. `0` = follow cursor, `1` = below, `2` = above. | +| `dwindle_preserve_split` | `0` | Keep the sibling's split orientation when a window is closed. | +| `dwindle_smart_resize` | `0` | When dragging to resize, move the split toward the cursor regardless of which side was grabbed. | +| `dwindle_drop_simple_split` | `1` | Drag-to-tile drop preview. `1` = 2-zone preview matching `dwindle_split_ratio`, `0` = 4-quadrant preview. | +| `dwindle_manual_split` | `0` | Manually split windows mode. | + +```ini +# Example dwindle configuration +dwindle_split_ratio=0.5 +dwindle_smart_split=0 +dwindle_hsplit=0 +dwindle_vsplit=0 +dwindle_preserve_split=0 +dwindle_smart_resize=0 +dwindle_drop_simple_split=1 +``` + +--- + +## Switching Layouts +| Setting | Default | Description | +| :--- | :--- | :--- | +| `circle_layout` | - | A comma-separated list of layouts `switch_layout` cycles through,the value sample:`tile,scroller`. | + +You can switch layouts dynamically or set a default for specific tags using [Tag Rules](/docs/window-management/rules#tag-rules). + +**Keybinding Examples:** + +```ini +# Cycle through layouts +circle_layout=grid,scroller,tile +bind=SUPER,n,switch_layout + +# Set specific layout +bind=SUPER,t,setlayout,tile +bind=SUPER,s,setlayout,scroller +``` diff --git a/docs/v0.13.1/window-management/meta.json b/docs/v0.13.1/window-management/meta.json new file mode 100644 index 00000000..e0937d14 --- /dev/null +++ b/docs/v0.13.1/window-management/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Window Management", + "pages": ["layouts", "rules", "overview", "scratchpad"] +} diff --git a/docs/v0.13.1/window-management/overview.md b/docs/v0.13.1/window-management/overview.md new file mode 100644 index 00000000..7da6e690 --- /dev/null +++ b/docs/v0.13.1/window-management/overview.md @@ -0,0 +1,29 @@ +--- +title: Overview +description: Configure the overview mode for window navigation. +--- + +## Overview Settings + +| Setting | Type | Default | Description | +| :--- | :--- | :--- | :--- | +| `hotarea_size` | integer | `10` | Hot area size in pixels. | +| `enable_hotarea` | integer | `1` | Enable hot areas (0: disable, 1: enable). | +| `hotarea_corner` | integer | `2` | Hot area corner (0: top-left, 1: top-right, 2: bottom-left, 3: bottom-right). | +| `ov_tab_mode` | integer | `0` | Overview tab mode (0: disable, 1: enable). | +| `overviewgappi` | integer | `5` | Inner gap in overview mode. | +| `overviewgappo` | integer | `30` | Outer gap in overview mode. | + +### Setting Descriptions + +- `enable_hotarea` — Toggles overview when the cursor enters the configured corner. +- `hotarea_size` — Size of the hot area trigger zone in pixels. +- `hotarea_corner` — Corner that triggers the hot area (0: top-left, 1: top-right, 2: bottom-left, 3: bottom-right). +- `ov_tab_mode` — Circles focus through windows in overview; releasing the mod key exits overview. + +### Mouse Interaction in Overview + +When in overview mode: + +- **Left mouse button** — Jump to (focus) a window. +- **Right mouse button** — Close a window. \ No newline at end of file diff --git a/docs/v0.13.1/window-management/rules.md b/docs/v0.13.1/window-management/rules.md new file mode 100644 index 00000000..4a295157 --- /dev/null +++ b/docs/v0.13.1/window-management/rules.md @@ -0,0 +1,250 @@ +--- +title: Rules +description: Define behavior for specific windows, tags, and layers. +--- + +## Window Rules + +Window rules allow you to set specific properties (floating, opacity, size, animations, etc.) for applications based on their `appid` or `title`. You can set all parameters in one line, and if you both set appid and title, the window will only follow the rules when appid and title both match. + +**Format:** + +```ini +windowrule=Parameter:Values,title:Values +windowrule=Parameter:Values,Parameter:Values,appid:Values,title:Values +``` + +### State & Behavior Parameters + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `appid` | string | Any | Match by application ID, supports regex | +| `title` | string | Any | Match by window title, supports regex | +| `isfloating` | integer | `0` / `1` | Force floating state | +| `isfullscreen` | integer | `0` / `1` | Force fullscreen state | +| `isfakefullscreen` | integer | `0` / `1` | Force fake-fullscreen state (window stays constrained) | +| `isglobal` | integer | `0` / `1` | Open as global window (sticky across tags) | +| `isoverlay` | integer | `0` / `1` | Make it always in top layer | +| `isopensilent` | integer | `0` / `1` | Open without focus | +| `istagsilent` | integer | `0` / `1` | Don't focus if client is not in current view tag | +| `force_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 | +| `noopenmaximized` | integer | `0` / `1` | Window does not open as maximized mode | +| `single_scratchpad` | integer | `0` / `1` (default 1) | Only show one out of named scratchpads or the normal scratchpad | +| `allow_shortcuts_inhibit` | integer | `0` / `1` (default 1) | Allow shortcuts to be inhibited by clients | +| `indleinhibit_when_focus` | integer | `0` / `1` (default 0) | Automatically keep idle inhibit active when this window is focused | + +### Geometry & Position + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `width` | float | 0-9999 | Window width when it becomes a floating window,if the value below 1, it will be the percentage of the screen width,otherwise it will be the pixel value | +| `height` | float | 0-9999 | Window height when it becomes a floating window,if the value below 1, it will be the percentage of the screen height,otherwise it will be the pixel value | +| `offsetx` | integer | -999-999 | X offset from center (%), 100 is the edge of screen with outer gap | +| `offsety` | integer | -999-999 | Y offset from center (%), 100 is the edge of screen with outer gap | +| `monitor` | string | Any | Assign to monitor by [monitor spec](/docs/configuration/monitors#monitor-spec-format) (name, make, model, or serial) | +| `tags` | integer | 1-9 | Assign to specific tag | +| `no_force_center` | integer | `0` / `1` | Window does not force center | +| `isnosizehint` | integer | `0` / `1` | Don't use min size and max size for size hints | + +### Visuals & Decoration + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `noblur` | integer | `0` / `1` | Window does not have blur effect | +| `isnoborder` | integer | `0` / `1` | Remove window border | +| `isnoshadow` | integer | `0` / `1` | Not apply shadow | +| `isnoradius` | integer | `0` / `1` | Not apply corner radius | +| `isnoanimation` | integer | `0` / `1` | Not apply animation | +| `focused_opacity` | integer | `0` / `1` | Window focused opacity | +| `unfocused_opacity` | integer | `0` / `1` | Window unfocused opacity | +| `allow_csd` | integer | `0` / `1` | Allow client side decoration | + +> **Tip:** For detailed visual effects configuration, see the [Window Effects](/docs/visuals/effects) page for blur, shadows, and opacity settings. + +### Layout & Scroller + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `scroller_proportion` | float | 0.1-1.0 | Set scroller proportion | +| `scroller_proportion_single` | float | 0.1-1.0 | Set scroller auto adjust proportion when it is single window | + +> **Tip:** For comprehensive layout configuration, see the [Layouts](/docs/window-management/layouts) page for all layout options and detailed settings. + +### Animation + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `animation_type_open` | string | zoom, slide, fade, none | Set open animation | +| `animation_type_close` | string | zoom, slide, fade, none | Set close animation | +| `nofadein` | integer | `0` / `1` | Window ignores fade-in animation | +| `nofadeout` | integer | `0` / `1` | Window ignores fade-out animation | + +> **Tip:** For detailed animation configuration, see the [Animations](/docs/visuals/animations) page for available types and settings. + +### Terminal & Swallowing + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `isterm` | integer | `0` / `1` | A new GUI window will replace the isterm window when it is opened | +| `noswallow` | integer | `0` / `1` | The window will not replace the isterm window | + +### Global & Special Windows + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `globalkeybinding` | string | `[mod combination][-][key]` | Global keybinding (only works for Wayland apps) | +| `isunglobal` | integer | `0` / `1` | Open as unmanaged global window (for desktop pets or camera windows) | +| `isnamedscratchpad` | integer | `0` / `1` | 0: disable, 1: named scratchpad | + +> **Tip:** For scratchpad usage, see the [Scratchpad](/docs/window-management/scratchpad) page for detailed configuration examples. + +### Performance & Tearing + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `force_tearing` | integer | `0` / `1` | Set window to tearing state, refer to [Tearing](/docs/configuration/monitors#tearing-game-mode) | + +### Examples + +```ini +# Set specific window size and position +windowrule=width:1000,height:900,appid:yesplaymusic,title:Demons + +# Global keybindings for OBS Studio +windowrule=globalkeybinding:ctrl+alt-o,appid:com.obsproject.Studio +windowrule=globalkeybinding:ctrl+alt+n,appid:com.obsproject.Studio +windowrule=isopensilent:1,appid:com.obsproject.Studio + +# Force tearing for games +windowrule=force_tearing:1,title:vkcube +windowrule=force_tearing:1,title:Counter-Strike 2 + +# Named scratchpad for file manager +windowrule=isnamedscratchpad:1,width:1280,height:800,appid:st-yazi + +# Custom opacity for specific apps +windowrule=focused_opacity:0.8,appid:firefox +windowrule=unfocused_opacity:0.6,appid:foot + +# Disable blur for selection tools +windowrule=noblur:1,appid:slurp + +# Position windows relative to screen center +windowrule=offsetx:20,offsety:-30,width:800,height:600,appid:alacritty + +# Send to specific tag and monitor +windowrule=tags:9,monitor:HDMI-A-1,appid:discord + +# Terminal swallowing setup +windowrule=isterm:1,appid:st +windowrule=noswallow:1,appid:foot + +# Disable client-side decorations +windowrule=allow_csd:1,appid:firefox + +# Unmanaged global window (desktop pets, camera) +windowrule=isunglobal:1,appid:cheese + +# Named scratchpad toggle +bind=alt,h,toggle_named_scratchpad,st-yazi,none,st -c st-yazi -e yazi +``` + +--- + +## Tag Rules + +You can set all parameters in one line. If only `id` is set, the rule is followed when the id matches. If any of `monitor_name`, `monitor_make`, `monitor_model`, or `monitor_serial` are set, the rule is followed only if **all** of the set monitor fields match. + +> **Warning:** Layouts set in tag rules have a higher priority than monitor rule layouts. + +**Format:** + +```ini +tagrule=id:Values,Parameter:Values,Parameter:Values +tagrule=id:Values,monitor_name:eDP-1,Parameter:Values,Parameter:Values +tagrule=id:Values,monitor_make:xxx,monitor_model:xxx,Parameter:Values +``` + +> **Tip:** See [Layouts](/docs/window-management/layouts#supported-layouts) for detailed descriptions of each layout type. + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `id` | integer | 0-9 | Match by tag id, 0 means the ~0 tag | +| `monitor_name` | string | monitor name | Match by monitor name | +| `monitor_make` | string | monitor make | Match by monitor manufacturer | +| `monitor_model` | string | monitor model | Match by monitor model | +| `monitor_serial` | string | monitor serial | Match by monitor serial number | +| `layout_name` | string | layout name | Layout name to set | +| `no_render_border` | integer | `0` / `1` | Disable render border | +| `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 | + +### Examples + +```ini +# Set layout for specific tags +tagrule=id:1,layout_name:scroller +tagrule=id:2,layout_name:scroller + +# Limit to specific monitor +tagrule=id:1,monitor_name:eDP-1,layout_name:scroller +tagrule=id:2,monitor_name:eDP-1,layout_name:scroller + +# Persistent tags (1-4) with layout assignment +tagrule=id:1,no_hide:1,layout_name:scroller +tagrule=id:2,no_hide:1,layout_name:scroller +tagrule=id:3,monitor_name:eDP-1,no_hide:1,layout_name:scroller +tagrule=id:4,monitor_name:eDP-1,no_hide:1,layout_name:scroller + +# Advanced tag configuration with master layout settings +tagrule=id:5,layout_name:tile,nmaster:2,mfact:0.6 +tagrule=id:6,monitor_name:HDMI-A-1,layout_name:monocle,no_render_border:1 +``` + +> **Tip:** For Waybar configuration with persistent tags, see [Status Bar](/docs/visuals/status-bar) documentation. + +--- + +## Layer Rules + +You can set all parameters in one line. Target "layer shell" surfaces like status bars (`waybar`), launchers (`rofi`), or notification daemons. + +**Format:** + +```ini +layerrule=layer_name:Values,Parameter:Values,Parameter:Values +``` + +> **Tip:** You can use `mmsg -e` to get the last open layer name for debugging. + +| Parameter | Type | Values | Description | +| :--- | :--- | :--- | :--- | +| `layer_name` | string | layer name | Match name of layer, supports regex | +| `animation_type_open` | string | slide, zoom, fade, none | Set open animation | +| `animation_type_close` | string | slide, zoom, fade, none | Set close animation | +| `noblur` | integer | `0` / `1` | Disable blur | +| `noanim` | integer | `0` / `1` | Disable layer animation | +| `noshadow` | integer | `0` / `1` | Disable layer shadow | + +> **Tip:** For animation types, see [Animations](/docs/visuals/animations#animation-types). For visual effects, see [Window Effects](/docs/visuals/effects). + +### Examples + +```ini +# No blur or animation for slurp selection layer (avoids occlusion and ghosting in screenshots) +layerrule=noanim:1,noblur:1,layer_name:selection + +# Zoom animation for Rofi with multiple parameters +layerrule=animation_type_open:zoom,noanim:0,layer_name:rofi + +# Disable animations and shadows for notification daemon +layerrule=noanim:1,noshadow:1,layer_name:swaync + +# Multiple effects for launcher +layerrule=animation_type_open:slide,animation_type_close:fade,noblur:1,layer_name:wofi +``` diff --git a/docs/v0.13.1/window-management/scratchpad.md b/docs/v0.13.1/window-management/scratchpad.md new file mode 100644 index 00000000..398182f9 --- /dev/null +++ b/docs/v0.13.1/window-management/scratchpad.md @@ -0,0 +1,73 @@ +--- +title: Scratchpad +description: Manage hidden "scratchpad" windows for quick access. +--- + +mangowm supports two types of scratchpads: the standard pool (Sway-like) and named scratchpads. + +## Standard Scratchpad + +Any window can be sent to the "scratchpad" pile, which hides it. You can then cycle through them. + +**Keybindings:** + +```ini +# Send current window to scratchpad +bind=SUPER,i,minimized + +# Toggle (show/hide) the scratchpad +bind=ALT,z,toggle_scratchpad + +# Retrieve window from scratchpad (restore) +bind=SUPER+SHIFT,i,restore_minimized +``` + +--- + +## Named Scratchpad + +Named scratchpads are bound to specific keys and applications. When triggered, mangowm will either launch the app (if not running) or toggle its visibility. + +**1. Define the Window Rule** + +You must identify the app using a unique `appid` or `title` and mark it as a named scratchpad. The application must support setting a custom appid or title at launch. Common examples: + +- `st -c my-appid` — sets the appid +- `kitty -T my-title` — sets the window title +- `foot --app-id my-appid` — sets the appid + +Use `none` as a placeholder when you only want to match by one field. + +```ini +# Match by appid +windowrule=isnamedscratchpad:1,width:1280,height:800,appid:st-yazi + +# Match by title +windowrule=isnamedscratchpad:1,width:1000,height:700,title:kitty-scratch +``` + +**2. Bind the Toggle Key** + +Format: `bind=MOD,KEY,toggle_named_scratchpad,appid,title,command` + +Use `none` for whichever field you are not matching on. + +```ini +# Match by appid: launch 'st' with class 'st-yazi' running 'yazi' +bind=alt,h,toggle_named_scratchpad,st-yazi,none,st -c st-yazi -e yazi + +# Match by title: launch 'kitty' with window title 'kitty-scratch' +bind=alt,k,toggle_named_scratchpad,none,kitty-scratch,kitty -T kitty-scratch +``` + +--- + +## Appearance + +You can customize the size of scratchpad windows relative to the screen. + +```ini +scratchpad_width_ratio=0.8 +scratchpad_height_ratio=0.9 +scratchpadcolor=0x516c93ff +``` From 742e5e5d71357ad5e840eda1c26d68b86e97663a Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 26 May 2026 14:58:26 +0800 Subject: [PATCH 257/328] update docs --- docs/(git)/visuals/theming.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/(git)/visuals/theming.md b/docs/(git)/visuals/theming.md index 676c575b..36c75c57 100644 --- a/docs/(git)/visuals/theming.md +++ b/docs/(git)/visuals/theming.md @@ -27,7 +27,7 @@ rootcolor=0x323232ff bordercolor=0x444444ff # Drop shadow when dragging windows -dropcolor=0x8FBA7C55 +dropcolor=0xBFBA7C55 # Split window border color in manual dwindle layout splitcolor=0xEB441EFF From 68398cc5284dba06dcddd68f2d3d095037706a7d Mon Sep 17 00:00:00 2001 From: xtheeq <atheeq.rhxn@gmail.com> Date: Tue, 26 May 2026 14:28:50 +0530 Subject: [PATCH 258/328] fix(docs): meta file in git --- docs/(git)/meta.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/(git)/meta.json b/docs/(git)/meta.json index 89c34444..40a214d5 100644 --- a/docs/(git)/meta.json +++ b/docs/(git)/meta.json @@ -1,5 +1,7 @@ { - "title": "mangowm", + "title": "git (latest)", + "description": "Latest development", + "root": true, "pages": [ "---Getting Started---", "index", From 037356b129951bb503052c635d9a7bac36377ca9 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 26 May 2026 18:45:14 +0800 Subject: [PATCH 259/328] opt: don't block cli when use mmsg spawn --- src/dispatch/bind_define.h | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 61a1a2a5..d6537d89 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -908,9 +908,15 @@ int32_t spawn_shell(const Arg *arg) { return 0; if (fork() == 0) { - signal(SIGSEGV, SIG_IGN); - signal(SIGABRT, SIG_IGN); - signal(SIGILL, SIG_IGN); + signal(SIGSEGV, SIG_DFL); + signal(SIGABRT, SIG_DFL); + signal(SIGILL, SIG_DFL); + signal(SIGCHLD, SIG_DFL); + + int fd_max = sysconf(_SC_OPEN_MAX); + for (int i = 3; i < fd_max; i++) { + close(i); + } dup2(STDERR_FILENO, STDOUT_FILENO); setsid(); @@ -920,7 +926,7 @@ int32_t spawn_shell(const Arg *arg) { wlr_log(WLR_DEBUG, "mango: failed to execute command '%s' with shell: %s\n", - arg->v, strerror(errno)); + (char *)arg->v, strerror(errno)); _exit(EXIT_FAILURE); } return 0; @@ -931,16 +937,25 @@ int32_t spawn(const Arg *arg) { return 0; if (fork() == 0) { - signal(SIGSEGV, SIG_IGN); - signal(SIGABRT, SIG_IGN); - signal(SIGILL, SIG_IGN); + signal(SIGSEGV, SIG_DFL); + signal(SIGABRT, SIG_DFL); + signal(SIGILL, SIG_DFL); + signal(SIGCHLD, SIG_DFL); + + // close all file descriptors inherited from the parent process to + // prevent IPC handle leakage that can block clients + int fd_max = sysconf(_SC_OPEN_MAX); + for (int i = 3; i < fd_max; i++) { + close(i); + } dup2(STDERR_FILENO, STDOUT_FILENO); setsid(); wordexp_t p; if (wordexp(arg->v, &p, 0) != 0) { - wlr_log(WLR_DEBUG, "mango: wordexp failed for '%s'\n", arg->v); + wlr_log(WLR_DEBUG, "mango: wordexp failed for '%s'\n", + (char *)arg->v); _exit(EXIT_FAILURE); } From d20d708a9545708423549a8a7aded58b6429d6ce Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 26 May 2026 19:55:35 +0800 Subject: [PATCH 260/328] opt: cleanup commit listener for ext-workspace --- src/ext-protocol/ext-workspace.h | 8 +++++--- src/mango.c | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/ext-protocol/ext-workspace.h b/src/ext-protocol/ext-workspace.h index d938ce51..19567859 100644 --- a/src/ext-protocol/ext-workspace.h +++ b/src/ext-protocol/ext-workspace.h @@ -17,6 +17,10 @@ struct workspace { struct wlr_ext_workspace_manager_v1 *ext_manager; struct wl_list workspaces; +static void handle_ext_commit(struct wl_listener *listener, void *data); +static struct wl_listener ext_manager_commit_listener = {.notify = + handle_ext_commit}; + void goto_workspace(struct workspace *target) { uint32_t tag; tag = 1 << (target->tag - 1); @@ -203,7 +207,5 @@ void workspaces_init() { wl_list_init(&workspaces); - static struct wl_listener commit_listener; - commit_listener.notify = handle_ext_commit; - wl_signal_add(&ext_manager->events.commit, &commit_listener); + wl_signal_add(&ext_manager->events.commit, &ext_manager_commit_listener); } \ No newline at end of file diff --git a/src/mango.c b/src/mango.c index a6d01f2d..61cf861a 100644 --- a/src/mango.c +++ b/src/mango.c @@ -2449,6 +2449,7 @@ void setcursorshape(struct wl_listener *listener, void *data) { } void cleanuplisteners(void) { + wl_list_remove(&ext_manager_commit_listener.link); // 0.7 wl_list_remove(&print_status_listener.link); wl_list_remove(&cursor_axis.link); wl_list_remove(&cursor_button.link); From 887a50a54e2d3b139ba0045916e053764ae1dc72 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 26 May 2026 20:12:55 +0800 Subject: [PATCH 261/328] fix: avoid use null mon when in mmsg --- src/ext-protocol/ext-workspace.h | 2 +- src/ipc/ipc.h | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ext-protocol/ext-workspace.h b/src/ext-protocol/ext-workspace.h index 19567859..480dd5cf 100644 --- a/src/ext-protocol/ext-workspace.h +++ b/src/ext-protocol/ext-workspace.h @@ -19,7 +19,7 @@ struct wl_list workspaces; static void handle_ext_commit(struct wl_listener *listener, void *data); static struct wl_listener ext_manager_commit_listener = {.notify = - handle_ext_commit}; + handle_ext_commit}; void goto_workspace(struct workspace *target) { uint32_t tag; diff --git a/src/ipc/ipc.h b/src/ipc/ipc.h index 666a5387..0a36da4a 100644 --- a/src/ipc/ipc.h +++ b/src/ipc/ipc.h @@ -154,11 +154,13 @@ static cJSON *monitor_active_tags(Monitor *m) { static cJSON *build_client_json(Client *c) { cJSON *obj = cJSON_CreateObject(); + cJSON_AddNumberToObject(obj, "id", c->id); cJSON_AddNumberToObject(obj, "pid", c->pid); cJSON_AddStringToObject(obj, "title", client_get_title(c)); cJSON_AddStringToObject(obj, "appid", client_get_appid(c)); - cJSON_AddStringToObject(obj, "monitor", c->mon->wlr_output->name); + cJSON_AddStringToObject(obj, "monitor", + c->mon ? c->mon->wlr_output->name : ""); cJSON_AddItemToObject(obj, "tags", tags_mask_to_array(c->tags)); cJSON_AddBoolToObject(obj, "is_focused", c->isfocusing); cJSON_AddBoolToObject(obj, "is_fullscreen", c->isfullscreen); From 8765f9b7c572998f67123f30fe2c5b07d73effc2 Mon Sep 17 00:00:00 2001 From: xtheeq <atheeq.rhxn@gmail.com> Date: Tue, 26 May 2026 18:25:50 +0530 Subject: [PATCH 262/328] feat(docs): use workflow for version docs generation --- .github/scripts/sync-wiki.py | 7 +- .../workflows/generate-nix-options-docs.yml | 4 +- .github/workflows/sync-website.yml | 31 +- docs/(git)/meta.json | 22 - docs/{(git) => }/bindings/index.mdx | 0 docs/{(git) => }/bindings/keys.md | 0 docs/{(git) => }/bindings/meta.json | 0 docs/{(git) => }/bindings/mouse-gestures.md | 0 docs/{(git) => }/configuration/basics.md | 0 docs/{(git) => }/configuration/index.mdx | 0 docs/{(git) => }/configuration/input.md | 0 docs/{(git) => }/configuration/meta.json | 0 .../configuration/miscellaneous.md | 0 docs/{(git) => }/configuration/monitors.md | 0 docs/{(git) => }/configuration/xdg-portals.md | 0 docs/{(git) => }/faq.md | 0 docs/{(git) => }/index.md | 0 docs/{(git) => }/installation.md | 0 docs/{(git) => }/ipc.md | 0 docs/meta.json | 21 +- docs/{(git) => }/nix-options.md | 0 docs/{(git) => }/quick-start.md | 0 docs/{(git) => }/screenshot.md | 0 docs/v0.13.0/bindings/index.mdx | 15 - docs/v0.13.0/bindings/keys.md | 216 -------- docs/v0.13.0/bindings/meta.json | 4 - docs/v0.13.0/bindings/mouse-gestures.md | 116 ---- docs/v0.13.0/configuration/basics.md | 87 --- docs/v0.13.0/configuration/index.mdx | 21 - docs/v0.13.0/configuration/input.md | 161 ------ docs/v0.13.0/configuration/meta.json | 4 - docs/v0.13.0/configuration/miscellaneous.md | 50 -- docs/v0.13.0/configuration/monitors.md | 276 ---------- docs/v0.13.0/configuration/xdg-portals.md | 76 --- docs/v0.13.0/faq.md | 100 ---- docs/v0.13.0/index.md | 42 -- docs/v0.13.0/installation.md | 308 ----------- docs/v0.13.0/ipc.md | 154 ------ docs/v0.13.0/meta.json | 22 - docs/v0.13.0/nix-options.md | 519 ------------------ docs/v0.13.0/quick-start.md | 88 --- docs/v0.13.0/screenshot.md | 213 ------- docs/v0.13.0/visuals/animations.md | 108 ---- docs/v0.13.0/visuals/effects.md | 82 --- docs/v0.13.0/visuals/index.mdx | 19 - docs/v0.13.0/visuals/meta.json | 4 - docs/v0.13.0/visuals/status-bar.md | 141 ----- docs/v0.13.0/visuals/theming.md | 62 --- docs/v0.13.0/window-management/index.mdx | 19 - docs/v0.13.0/window-management/layouts.md | 133 ----- docs/v0.13.0/window-management/meta.json | 4 - docs/v0.13.0/window-management/overview.md | 29 - docs/v0.13.0/window-management/rules.md | 250 --------- docs/v0.13.0/window-management/scratchpad.md | 73 --- docs/v0.13.1/bindings/index.mdx | 15 - docs/v0.13.1/bindings/keys.md | 216 -------- docs/v0.13.1/bindings/meta.json | 4 - docs/v0.13.1/bindings/mouse-gestures.md | 116 ---- docs/v0.13.1/configuration/basics.md | 87 --- docs/v0.13.1/configuration/index.mdx | 21 - docs/v0.13.1/configuration/input.md | 161 ------ docs/v0.13.1/configuration/meta.json | 4 - docs/v0.13.1/configuration/miscellaneous.md | 50 -- docs/v0.13.1/configuration/monitors.md | 276 ---------- docs/v0.13.1/configuration/xdg-portals.md | 76 --- docs/v0.13.1/faq.md | 100 ---- docs/v0.13.1/index.md | 42 -- docs/v0.13.1/installation.md | 308 ----------- docs/v0.13.1/ipc.md | 154 ------ docs/v0.13.1/meta.json | 22 - docs/v0.13.1/nix-options.md | 519 ------------------ docs/v0.13.1/quick-start.md | 88 --- docs/v0.13.1/screenshot.md | 213 ------- docs/v0.13.1/visuals/animations.md | 108 ---- docs/v0.13.1/visuals/effects.md | 82 --- docs/v0.13.1/visuals/index.mdx | 19 - docs/v0.13.1/visuals/meta.json | 4 - docs/v0.13.1/visuals/status-bar.md | 141 ----- docs/v0.13.1/visuals/theming.md | 62 --- docs/v0.13.1/window-management/index.mdx | 19 - docs/v0.13.1/window-management/layouts.md | 135 ----- docs/v0.13.1/window-management/meta.json | 4 - docs/v0.13.1/window-management/overview.md | 29 - docs/v0.13.1/window-management/rules.md | 250 --------- docs/v0.13.1/window-management/scratchpad.md | 73 --- docs/{(git) => }/visuals/animations.md | 0 docs/{(git) => }/visuals/effects.md | 0 docs/{(git) => }/visuals/index.mdx | 0 docs/{(git) => }/visuals/meta.json | 0 docs/{(git) => }/visuals/status-bar.md | 0 docs/{(git) => }/visuals/theming.md | 0 docs/{(git) => }/window-management/index.mdx | 0 docs/{(git) => }/window-management/layouts.md | 0 docs/{(git) => }/window-management/meta.json | 0 .../{(git) => }/window-management/overview.md | 0 docs/{(git) => }/window-management/rules.md | 0 .../window-management/scratchpad.md | 0 97 files changed, 53 insertions(+), 6826 deletions(-) delete mode 100644 docs/(git)/meta.json rename docs/{(git) => }/bindings/index.mdx (100%) rename docs/{(git) => }/bindings/keys.md (100%) rename docs/{(git) => }/bindings/meta.json (100%) rename docs/{(git) => }/bindings/mouse-gestures.md (100%) rename docs/{(git) => }/configuration/basics.md (100%) rename docs/{(git) => }/configuration/index.mdx (100%) rename docs/{(git) => }/configuration/input.md (100%) rename docs/{(git) => }/configuration/meta.json (100%) rename docs/{(git) => }/configuration/miscellaneous.md (100%) rename docs/{(git) => }/configuration/monitors.md (100%) rename docs/{(git) => }/configuration/xdg-portals.md (100%) rename docs/{(git) => }/faq.md (100%) rename docs/{(git) => }/index.md (100%) rename docs/{(git) => }/installation.md (100%) rename docs/{(git) => }/ipc.md (100%) rename docs/{(git) => }/nix-options.md (100%) rename docs/{(git) => }/quick-start.md (100%) rename docs/{(git) => }/screenshot.md (100%) delete mode 100644 docs/v0.13.0/bindings/index.mdx delete mode 100644 docs/v0.13.0/bindings/keys.md delete mode 100644 docs/v0.13.0/bindings/meta.json delete mode 100644 docs/v0.13.0/bindings/mouse-gestures.md delete mode 100644 docs/v0.13.0/configuration/basics.md delete mode 100644 docs/v0.13.0/configuration/index.mdx delete mode 100644 docs/v0.13.0/configuration/input.md delete mode 100644 docs/v0.13.0/configuration/meta.json delete mode 100644 docs/v0.13.0/configuration/miscellaneous.md delete mode 100644 docs/v0.13.0/configuration/monitors.md delete mode 100644 docs/v0.13.0/configuration/xdg-portals.md delete mode 100644 docs/v0.13.0/faq.md delete mode 100644 docs/v0.13.0/index.md delete mode 100644 docs/v0.13.0/installation.md delete mode 100644 docs/v0.13.0/ipc.md delete mode 100644 docs/v0.13.0/meta.json delete mode 100644 docs/v0.13.0/nix-options.md delete mode 100644 docs/v0.13.0/quick-start.md delete mode 100644 docs/v0.13.0/screenshot.md delete mode 100644 docs/v0.13.0/visuals/animations.md delete mode 100644 docs/v0.13.0/visuals/effects.md delete mode 100644 docs/v0.13.0/visuals/index.mdx delete mode 100644 docs/v0.13.0/visuals/meta.json delete mode 100644 docs/v0.13.0/visuals/status-bar.md delete mode 100644 docs/v0.13.0/visuals/theming.md delete mode 100644 docs/v0.13.0/window-management/index.mdx delete mode 100644 docs/v0.13.0/window-management/layouts.md delete mode 100644 docs/v0.13.0/window-management/meta.json delete mode 100644 docs/v0.13.0/window-management/overview.md delete mode 100644 docs/v0.13.0/window-management/rules.md delete mode 100644 docs/v0.13.0/window-management/scratchpad.md delete mode 100644 docs/v0.13.1/bindings/index.mdx delete mode 100644 docs/v0.13.1/bindings/keys.md delete mode 100644 docs/v0.13.1/bindings/meta.json delete mode 100644 docs/v0.13.1/bindings/mouse-gestures.md delete mode 100644 docs/v0.13.1/configuration/basics.md delete mode 100644 docs/v0.13.1/configuration/index.mdx delete mode 100644 docs/v0.13.1/configuration/input.md delete mode 100644 docs/v0.13.1/configuration/meta.json delete mode 100644 docs/v0.13.1/configuration/miscellaneous.md delete mode 100644 docs/v0.13.1/configuration/monitors.md delete mode 100644 docs/v0.13.1/configuration/xdg-portals.md delete mode 100644 docs/v0.13.1/faq.md delete mode 100644 docs/v0.13.1/index.md delete mode 100644 docs/v0.13.1/installation.md delete mode 100644 docs/v0.13.1/ipc.md delete mode 100644 docs/v0.13.1/meta.json delete mode 100644 docs/v0.13.1/nix-options.md delete mode 100644 docs/v0.13.1/quick-start.md delete mode 100644 docs/v0.13.1/screenshot.md delete mode 100644 docs/v0.13.1/visuals/animations.md delete mode 100644 docs/v0.13.1/visuals/effects.md delete mode 100644 docs/v0.13.1/visuals/index.mdx delete mode 100644 docs/v0.13.1/visuals/meta.json delete mode 100644 docs/v0.13.1/visuals/status-bar.md delete mode 100644 docs/v0.13.1/visuals/theming.md delete mode 100644 docs/v0.13.1/window-management/index.mdx delete mode 100644 docs/v0.13.1/window-management/layouts.md delete mode 100644 docs/v0.13.1/window-management/meta.json delete mode 100644 docs/v0.13.1/window-management/overview.md delete mode 100644 docs/v0.13.1/window-management/rules.md delete mode 100644 docs/v0.13.1/window-management/scratchpad.md rename docs/{(git) => }/visuals/animations.md (100%) rename docs/{(git) => }/visuals/effects.md (100%) rename docs/{(git) => }/visuals/index.mdx (100%) rename docs/{(git) => }/visuals/meta.json (100%) rename docs/{(git) => }/visuals/status-bar.md (100%) rename docs/{(git) => }/visuals/theming.md (100%) rename docs/{(git) => }/window-management/index.mdx (100%) rename docs/{(git) => }/window-management/layouts.md (100%) rename docs/{(git) => }/window-management/meta.json (100%) rename docs/{(git) => }/window-management/overview.md (100%) rename docs/{(git) => }/window-management/rules.md (100%) rename docs/{(git) => }/window-management/scratchpad.md (100%) diff --git a/.github/scripts/sync-wiki.py b/.github/scripts/sync-wiki.py index 51f3e404..ebf543c7 100644 --- a/.github/scripts/sync-wiki.py +++ b/.github/scripts/sync-wiki.py @@ -5,7 +5,6 @@ from pathlib import Path DOCS_DIR = Path("docs") WIKI_DIR = Path("wiki-temp") -LATEST_DIR = DOCS_DIR / "(git)" FRONTMATTER_RE = re.compile(r"\A---\s*\n.*?^---\s*\n", re.DOTALL | re.MULTILINE) DOCS_LINK_RE = re.compile(r"\[([^\]]+)\]\(/docs/(?:[^/)]+/)*([^/)#]+)(#[^)]+)?\)") @@ -21,10 +20,10 @@ def collect_all_files() -> list[tuple[Path, str]]: return [directory / f"{p}.md" for p in data.get("pages", []) if (directory / f"{p}.md").exists()] return sorted(directory.glob("*.md")) - for src in from_dir(LATEST_DIR): + for src in from_dir(DOCS_DIR): files.append((src, "Home" if src.stem == "index" else src.stem)) - for subdir in sorted(LATEST_DIR.iterdir()): + for subdir in sorted(DOCS_DIR.iterdir()): if subdir.is_dir(): for src in from_dir(subdir): files.append((src, src.stem)) @@ -45,7 +44,7 @@ def main() -> None: lines: list[str] = [] current_section = None for src, dest_name in files: - section = "General" if src.parent == LATEST_DIR else src.parent.name.replace("-", " ").title() + section = "General" if src.parent == DOCS_DIR else src.parent.name.replace("-", " ").title() if section != current_section: if current_section is not None: lines.append("") diff --git a/.github/workflows/generate-nix-options-docs.yml b/.github/workflows/generate-nix-options-docs.yml index ff6006c1..cee4054c 100644 --- a/.github/workflows/generate-nix-options-docs.yml +++ b/.github/workflows/generate-nix-options-docs.yml @@ -53,11 +53,11 @@ jobs: python3 ./.github/scripts/generate-nix-options-docs.py \ /tmp/nixos-raw.md \ /tmp/hm-raw.md \ - 'docs/(git)/nix-options.md' + 'docs/nix-options.md' - name: Auto-commit changes uses: stefanzweifel/git-auto-commit-action@v5 with: commit_message: "docs: auto-generate Nix module options" - file_pattern: 'docs/**/nix-options.md' + file_pattern: 'docs/nix-options.md' branch: ${{ github.head_ref }} diff --git a/.github/workflows/sync-website.yml b/.github/workflows/sync-website.yml index 844dc2cc..f1cb7115 100644 --- a/.github/workflows/sync-website.yml +++ b/.github/workflows/sync-website.yml @@ -17,9 +17,11 @@ jobs: steps: - uses: actions/checkout@v4 with: - fetch-depth: 1 token: ${{ github.token }} + - name: Fetch tags for versioned docs + run: git fetch origin --tags --depth=1 + - name: Checkout website uses: actions/checkout@v4 with: @@ -30,8 +32,31 @@ jobs: - name: Sync docs run: | - rm -rf website/apps/web/content/docs - cp -r docs website/apps/web/content/docs + TARGET=website/apps/web/content/docs + rm -rf "$TARGET" + mkdir -p "$TARGET" + + # Copy current docs as "(git)" + cp -r docs "$TARGET/(git)" + + # Generate versioned docs from last 2 semver tags + tags=$(git tag --sort=-v:refname | grep '^v\?[0-9]\+\.[0-9]\+\.[0-9]\+$' | head -2) + for tag in $tags; do + git worktree add /tmp/docs-"$tag" "$tag" || continue + if [ -d "/tmp/docs-$tag/docs" ]; then + name="v${tag#v}" + cp -r "/tmp/docs-$tag/docs" "$TARGET/$name" + fi + git worktree remove /tmp/docs-"$tag" + done + + # Generate root version index + { + echo "(git)" + for tag in $tags; do + echo "v${tag#v}" + done + } | jq -Rn '[inputs | select(length > 0)] | {pages: .}' > "$TARGET/meta.json" - name: Commit and push working-directory: website diff --git a/docs/(git)/meta.json b/docs/(git)/meta.json deleted file mode 100644 index 40a214d5..00000000 --- a/docs/(git)/meta.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "title": "git (latest)", - "description": "Latest development", - "root": true, - "pages": [ - "---Getting Started---", - "index", - "installation", - "quick-start", - "---Configuration---", - "configuration", - "visuals", - "window-management", - "bindings", - "---Examples---", - "screenshot", - "---Reference---", - "nix-options", - "ipc", - "faq" - ] -} diff --git a/docs/(git)/bindings/index.mdx b/docs/bindings/index.mdx similarity index 100% rename from docs/(git)/bindings/index.mdx rename to docs/bindings/index.mdx diff --git a/docs/(git)/bindings/keys.md b/docs/bindings/keys.md similarity index 100% rename from docs/(git)/bindings/keys.md rename to docs/bindings/keys.md diff --git a/docs/(git)/bindings/meta.json b/docs/bindings/meta.json similarity index 100% rename from docs/(git)/bindings/meta.json rename to docs/bindings/meta.json diff --git a/docs/(git)/bindings/mouse-gestures.md b/docs/bindings/mouse-gestures.md similarity index 100% rename from docs/(git)/bindings/mouse-gestures.md rename to docs/bindings/mouse-gestures.md diff --git a/docs/(git)/configuration/basics.md b/docs/configuration/basics.md similarity index 100% rename from docs/(git)/configuration/basics.md rename to docs/configuration/basics.md diff --git a/docs/(git)/configuration/index.mdx b/docs/configuration/index.mdx similarity index 100% rename from docs/(git)/configuration/index.mdx rename to docs/configuration/index.mdx diff --git a/docs/(git)/configuration/input.md b/docs/configuration/input.md similarity index 100% rename from docs/(git)/configuration/input.md rename to docs/configuration/input.md diff --git a/docs/(git)/configuration/meta.json b/docs/configuration/meta.json similarity index 100% rename from docs/(git)/configuration/meta.json rename to docs/configuration/meta.json diff --git a/docs/(git)/configuration/miscellaneous.md b/docs/configuration/miscellaneous.md similarity index 100% rename from docs/(git)/configuration/miscellaneous.md rename to docs/configuration/miscellaneous.md diff --git a/docs/(git)/configuration/monitors.md b/docs/configuration/monitors.md similarity index 100% rename from docs/(git)/configuration/monitors.md rename to docs/configuration/monitors.md diff --git a/docs/(git)/configuration/xdg-portals.md b/docs/configuration/xdg-portals.md similarity index 100% rename from docs/(git)/configuration/xdg-portals.md rename to docs/configuration/xdg-portals.md diff --git a/docs/(git)/faq.md b/docs/faq.md similarity index 100% rename from docs/(git)/faq.md rename to docs/faq.md diff --git a/docs/(git)/index.md b/docs/index.md similarity index 100% rename from docs/(git)/index.md rename to docs/index.md diff --git a/docs/(git)/installation.md b/docs/installation.md similarity index 100% rename from docs/(git)/installation.md rename to docs/installation.md diff --git a/docs/(git)/ipc.md b/docs/ipc.md similarity index 100% rename from docs/(git)/ipc.md rename to docs/ipc.md diff --git a/docs/meta.json b/docs/meta.json index e6110ed2..40a214d5 100644 --- a/docs/meta.json +++ b/docs/meta.json @@ -1,3 +1,22 @@ { - "pages": ["(git)", "v0.13.1", "v0.13.0"] + "title": "git (latest)", + "description": "Latest development", + "root": true, + "pages": [ + "---Getting Started---", + "index", + "installation", + "quick-start", + "---Configuration---", + "configuration", + "visuals", + "window-management", + "bindings", + "---Examples---", + "screenshot", + "---Reference---", + "nix-options", + "ipc", + "faq" + ] } diff --git a/docs/(git)/nix-options.md b/docs/nix-options.md similarity index 100% rename from docs/(git)/nix-options.md rename to docs/nix-options.md diff --git a/docs/(git)/quick-start.md b/docs/quick-start.md similarity index 100% rename from docs/(git)/quick-start.md rename to docs/quick-start.md diff --git a/docs/(git)/screenshot.md b/docs/screenshot.md similarity index 100% rename from docs/(git)/screenshot.md rename to docs/screenshot.md diff --git a/docs/v0.13.0/bindings/index.mdx b/docs/v0.13.0/bindings/index.mdx deleted file mode 100644 index 4c3a5bda..00000000 --- a/docs/v0.13.0/bindings/index.mdx +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: Bindings & Input -description: Keybindings, mouse gestures, and input devices. -icon: Keyboard ---- - -Configure how you interact with mangowm using flexible keybindings and input options. - -<Cards> - -<Card href="/docs/bindings/keys" title="Key Bindings" description="Keyboard shortcuts and modes" /> - -<Card href="/docs/bindings/mouse-gestures" title="Mouse Gestures" description="Touchpad and mouse gestures" /> - -</Cards> diff --git a/docs/v0.13.0/bindings/keys.md b/docs/v0.13.0/bindings/keys.md deleted file mode 100644 index 002c9564..00000000 --- a/docs/v0.13.0/bindings/keys.md +++ /dev/null @@ -1,216 +0,0 @@ ---- -title: Key Bindings -description: Define keyboard shortcuts and modes. ---- - -## Syntax - -Key bindings follow this format: - -```ini -bind[flags]=MODIFIERS,KEY,COMMAND,PARAMETERS -``` - -- **Modifiers**: `SUPER`, `CTRL`, `ALT`, `SHIFT`, `NONE` (combine with `+`, e.g. `SUPER+CTRL+ALT`). -- **Key**: Key name (from `xev` or `wev`) or keycode (e.g., `code:24` for `q`). - -> **Info:** `bind` automatically converts keysym to keycode for comparison. This makes it compatible with all keyboard layouts, but the matching may not always be precise. If a key combination doesn't work on your keyboard layout, use a keycode instead (e.g., `code:24` instead of `q`). - -### Flags - -- `l`: Works even when screen is locked. -- `s`: Uses keysym instead of keycode to bind. -- `r`: Triggers on key release instead of press. -- `p`: Pass key event to client. - -**Examples:** - -```ini -bind=SUPER,Q,killclient -bindl=SUPER,L,spawn,swaylock - -# Using keycode instead of key name -bind=ALT,code:24,killclient - -# Combining keycodes for modifiers and keys -bind=code:64,code:24,killclient -bind=code:64+code:133,code:24,killclient - -# Bind with no modifier -bind=NONE,XF86MonBrightnessUp,spawn,brightnessctl set +5% - -# Bind a modifier key itself as the trigger key -bind=alt,shift_l,switch_keyboard_layout -``` - -## Key Modes (Submaps) - -You can divide key bindings into named modes. Rules: - -1. Set `keymode=<name>` before a group of `bind` lines — those binds only apply in that mode. -2. If no `keymode` is set before a bind, it belongs to the `default` mode. -3. The special `common` keymode applies its binds **across all modes**. - -Use `setkeymode` to switch modes, and `mmsg -b` to query the current mode. - -```ini -# Binds in 'common' apply in every mode -keymode=common -bind=SUPER,r,reload_config - -# Default mode bindings -keymode=default -bind=ALT,Return,spawn,foot -bind=SUPER,F,setkeymode,resize - -# 'resize' mode bindings -keymode=resize -bind=NONE,Left,resizewin,-10,0 -bind=NONE,Right,resizewin,+10,0 -bind=NONE,Escape,setkeymode,default -``` - -### Single Modifier Key Binding - -When binding a modifier key itself, use `NONE` for press and the modifier name for release: - -```ini -# Trigger on press of Super key -bind=none,Super_L,spawn,rofi -show run - -# Trigger on release of Super key -bindr=Super,Super_L,spawn,rofi -show run -``` - -## Dispatchers List - -### Window Management - -| Command | Param | Description | -| :--- | :--- | :--- | -| `killclient` | - | Close the focused window. | -| `togglefloating` | - | Toggle floating state. | -| `toggle_all_floating` | - | Toggle all visible clients floating state. | -| `togglefullscreen` | - | Toggle fullscreen. | -| `togglefakefullscreen` | - | Toggle "fake" fullscreen (remains constrained). | -| `togglemaximizescreen` | - | Maximize window (keep decoration/bar). | -| `toggleglobal` | - | Pin window to all tags. | -| `toggle_render_border` | - | Toggle border rendering. | -| `centerwin` | - | Center the floating window. | -| `minimized` | - | Minimize window to scratchpad. | -| `restore_minimized` | - | Restore window from scratchpad. | -| `toggle_scratchpad` | - | Toggle scratchpad. | -| `toggle_named_scratchpad` | `appid,title,cmd` | Toggle named scratchpad. Launches app if not running, otherwise shows/hides it. | - -### Focus & Movement - -| Command | Param | Description | -| :--- | :--- | :--- | -| `focusdir` | `left/right/up/down` | Focus window in direction. | -| `focusstack` | `next/prev` | Cycle focus within the stack. | -| `focuslast` | - | Focus the previously active window. | -| `exchange_client` | `left/right/up/down` | Swap window with neighbor in direction. | -| `exchange_stack_client` | `next/prev` | Exchange window position in stack. | -| `zoom` | - | Swap focused window with Master. | - -### Tags & Monitors - -| Command | Param | Description | -| :--- | :--- | :--- | -| `view` | `-1/0/1-9` or `mask [,synctag]` | View tag. `-1` = previous tagset, `0` = all tags, `1-9` = specific tag, mask e.g. `1\|3\|5`. Optional `synctag` (0/1) syncs the action to all monitors. | -| `viewtoleft` | `[synctag]` | View previous tag. Optional `synctag` (0/1) syncs to all monitors. | -| `viewtoright` | `[synctag]` | View next tag. Optional `synctag` (0/1) syncs to all monitors. | -| `viewtoleft_have_client` | `[synctag]` | View left tag and focus client if present. Optional `synctag` (0/1). | -| `viewtoright_have_client` | `[synctag]` | View right tag and focus client if present. Optional `synctag` (0/1). | -| `viewcrossmon` | `tag,monitor_spec` | View specified tag on specified monitor. | -| `tag` | `1-9 [,synctag]` | Move window to tag. Optional `synctag` (0/1) syncs to all monitors. | -| `tagsilent` | `1-9` | Move window to tag without focusing it. | -| `tagtoleft` | `[synctag]` | Move window to left tag. Optional `synctag` (0/1). | -| `tagtoright` | `[synctag]` | Move window to right tag. Optional `synctag` (0/1). | -| `tagcrossmon` | `tag,monitor_spec` | Move window to specified tag on specified monitor. | -| `toggletag` | `0-9` | Toggle tag on window (0 means all tags). | -| `toggleview` | `1-9` | Toggle tag view. | -| `comboview` | `1-9` | View multi tags pressed simultaneously. | -| `focusmon` | `left/right/up/down/monitor_spec` | Focus monitor by direction or [monitor spec](/docs/configuration/monitors#monitor-spec-format). | -| `tagmon` | `left/right/up/down/monitor_spec,[keeptag]` | Move window to monitor by direction or [monitor spec](/docs/configuration/monitors#monitor-spec-format). `keeptag` is 0 or 1. | - -### Layouts - -| Command | Param | Description | -| :--- | :--- | :--- | -| `setlayout` | `name` | Switch to layout (e.g., `scroller`, `tile`). | -| `switch_layout` | - | Cycle through available layouts. | -| `incnmaster` | `+1/-1` | Increase/Decrease number of master windows. | -| `setmfact` | `+0.05` | Increase/Decrease master area size. | -| `set_proportion` | `float` | Set scroller window proportion (0.0–1.0). | -| `switch_proportion_preset` | - | Cycle proportion presets of scroller window. | -| `scroller_stack` | `left/right/up/down` | Move window inside/outside scroller stack by direction. | -| `incgaps` | `+/-value` | Adjust gap size. | -| `togglegaps` | - | Toggle gaps. | -| `dwindle_toggle_split_direction` | - | Toggle split direction in dwindle layout. | - -### System - -| Command | Param | Description | -| :--- | :--- | :--- | -| `spawn` | `cmd` | Execute a command. | -| `spawn_shell` | `cmd` | Execute shell command (supports pipes `\|`). | -| `spawn_on_empty` | `cmd,tagnumber` | Open command on empty tag. | -| `reload_config` | - | Hot-reload configuration. | -| `quit` | - | Exit mangowm. | -| `toggleoverview` | - | Toggle overview mode. | -| `create_virtual_output` | - | Create a headless monitor (for VNC/Sunshine). | -| `destroy_all_virtual_output` | - | Destroy all virtual monitors. | -| `toggleoverlay` | - | Toggle overlay state for the focused window. | -| `toggle_trackpad_enable` | - | Toggle trackpad enable. | -| `setkeymode` | `mode` | Set keymode. | -| `switch_keyboard_layout` | `[index]` | Switch keyboard layout. Optional index (0, 1, 2...) to switch to specific layout. | -| `setoption` | `key,value` | Set config option temporarily. | -| `disable_monitor` | `monitor_spec` | Shutdown monitor. Accepts a [monitor spec](/docs/configuration/monitors#monitor-spec-format). | -| `enable_monitor` | `monitor_spec` | Power on monitor. Accepts a [monitor spec](/docs/configuration/monitors#monitor-spec-format). | -| `toggle_monitor` | `monitor_spec` | Toggle monitor power. Accepts a [monitor spec](/docs/configuration/monitors#monitor-spec-format). | - -### Media Controls - -> **Warning:** Some keyboards don't send standard media keys. Run `wev` and press your key to check the exact key name. - -#### Brightness - -Requires: `brightnessctl` - -```ini -bind=NONE,XF86MonBrightnessUp,spawn,brightnessctl s +2% -bind=SHIFT,XF86MonBrightnessUp,spawn,brightnessctl s 100% -bind=NONE,XF86MonBrightnessDown,spawn,brightnessctl s 2%- -bind=SHIFT,XF86MonBrightnessDown,spawn,brightnessctl s 1% -``` - -#### Volume - -Requires: `wpctl` (WirePlumber) - -```ini -bind=NONE,XF86AudioRaiseVolume,spawn,wpctl set-volume @DEFAULT_SINK@ 5%+ -bind=NONE,XF86AudioLowerVolume,spawn,wpctl set-volume @DEFAULT_SINK@ 5%- -bind=NONE,XF86AudioMute,spawn,wpctl set-mute @DEFAULT_SINK@ toggle -bind=SHIFT,XF86AudioMute,spawn,wpctl set-mute @DEFAULT_SOURCE@ toggle -``` - -#### 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 | -| :--- | :--- | :--- | -| `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. | diff --git a/docs/v0.13.0/bindings/meta.json b/docs/v0.13.0/bindings/meta.json deleted file mode 100644 index f1b629b6..00000000 --- a/docs/v0.13.0/bindings/meta.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "title": "Bindings & Input", - "pages": ["keys", "mouse-gestures"] -} diff --git a/docs/v0.13.0/bindings/mouse-gestures.md b/docs/v0.13.0/bindings/mouse-gestures.md deleted file mode 100644 index c4a36889..00000000 --- a/docs/v0.13.0/bindings/mouse-gestures.md +++ /dev/null @@ -1,116 +0,0 @@ ---- -title: Mouse & Gestures -description: Configure mouse buttons, scrolling, gestures, and lid switches. ---- - -## Mouse Bindings - -Assign actions to mouse button presses with optional modifier keys. - -### Syntax - -```ini -mousebind=MODIFIERS,BUTTON,COMMAND,PARAMETERS -``` - -- **Modifiers**: `SUPER`, `CTRL`, `ALT`, `SHIFT`, `NONE`. Combine with `+` (e.g., `SUPER+CTRL`) -- **Buttons**: `btn_left`, `btn_right`, `btn_middle`, `btn_side`, `btn_extra`, `btn_forward`, `btn_back`, `btn_task` - -> **Warning:** When modifiers are set to `NONE`, only `btn_middle` works in normal mode. `btn_left` and `btn_right` only work in overview mode. - -### Examples - -```ini -# Window manipulation -mousebind=SUPER,btn_left,moveresize,curmove -mousebind=SUPER,btn_right,moveresize,curresize -mousebind=SUPER+CTRL,btn_right,killclient - -# Overview mode (requires NONE modifier) -mousebind=NONE,btn_left,toggleoverview,-1 -mousebind=NONE,btn_right,killclient,0 -mousebind=NONE,btn_middle,togglemaximizescreen,0 -``` - ---- - -## Axis Bindings - -Map scroll wheel movements to actions for workspace and window navigation. - -### Syntax - -```ini -axisbind=MODIFIERS,DIRECTION,COMMAND,PARAMETERS -``` - -- **Direction**: `UP`, `DOWN`, `LEFT`, `RIGHT` - -### Examples - -```ini -axisbind=SUPER,UP,viewtoleft_have_client -axisbind=SUPER,DOWN,viewtoright_have_client -``` - ---- - -## Gesture Bindings - -Enable touchpad swipe gestures for navigation and window management. - -### Syntax - -```ini -gesturebind=MODIFIERS,DIRECTION,FINGERS,COMMAND,PARAMETERS -``` - -- **Direction**: `up`, `down`, `left`, `right` -- **Fingers**: `3` or `4` - -> **Info:** Gestures require proper touchpad configuration. See [Input Devices](/docs/configuration/input) for touchpad settings like `tap_to_click` and `disable_while_typing`. - -### Examples - -```ini -# 3-finger: Window focus -gesturebind=none,left,3,focusdir,left -gesturebind=none,right,3,focusdir,right -gesturebind=none,up,3,focusdir,up -gesturebind=none,down,3,focusdir,down - -# 4-finger: Workspace navigation -gesturebind=none,left,4,viewtoleft_have_client -gesturebind=none,right,4,viewtoright_have_client -gesturebind=none,up,4,toggleoverview -gesturebind=none,down,4,toggleoverview -``` - ---- - -## Switch Bindings - -Trigger actions on hardware events like laptop lid open/close. - -### Syntax - -```ini -switchbind=FOLD_STATE,COMMAND,PARAMETERS -``` - -- **Fold State**: `fold` (lid closed), `unfold` (lid opened) - -> **Warning:** Disable system lid handling in `/etc/systemd/logind.conf`: -> -> ```ini -> HandleLidSwitch=ignore -> HandleLidSwitchExternalPower=ignore -> HandleLidSwitchDocked=ignore -> ``` - -### Examples - -```ini -switchbind=fold,spawn,swaylock -f -c 000000 -switchbind=unfold,spawn,wlr-dpms on -``` diff --git a/docs/v0.13.0/configuration/basics.md b/docs/v0.13.0/configuration/basics.md deleted file mode 100644 index 7afa343b..00000000 --- a/docs/v0.13.0/configuration/basics.md +++ /dev/null @@ -1,87 +0,0 @@ ---- -title: Basic Configuration -description: Learn how to configure mangowm files, environment variables, and autostart scripts. ---- - -## Configuration File - -mangowm uses a simple configuration file format. By default, it looks for a configuration file in `~/.config/mango/`. - -1. **Locate Default Config** - - A fallback configuration is provided at `/etc/mango/config.conf`. You can use this as a reference. - -2. **Create User Config** - - Copy the default config to your local config directory to start customizing. - - ```bash - mkdir -p ~/.config/mango - cp /etc/mango/config.conf ~/.config/mango/config.conf - ``` - -3. **Launch with Custom Config (Optional)** - - If you prefer to keep your config elsewhere, you can launch mango with the `-c` flag. - - ```bash - mango -c /path/to/your_config.conf - ``` - -### Sub-Configuration - -To keep your configuration organized, you can split it into multiple files and include them using the `source` keyword. - -```ini -# Import keybindings from a separate file -source=~/.config/mango/bind.conf - -# Relative paths work too -source=./theme.conf - -# Optional: ignore if file doesn't exist (useful for shared configs) -source-optional=~/.config/mango/optional.conf -``` - -### Validate Configuration - -You can check your configuration for errors without starting mangowm: - -```bash -mango -c /path/to/config.conf -p -``` - -Use with `source-optional` for shared configs across different setups. - -## Environment Variables - -You can define environment variables directly within your config file. These are set before the window manager fully initializes. - -> **Warning:** Environment variables defined here will be **reset** every time you reload the configuration. - -```ini -env=QT_IM_MODULES,wayland;fcitx -env=XMODIFIERS,@im=fcitx -``` - -## Autostart - -mangowm can automatically run commands or scripts upon startup. There are two modes for execution: - -| Command | Behavior | Usage Case | -| :--- | :--- | :--- | -| `exec-once` | Runs **only once** when mangowm starts. | Status bars, Wallpapers, Notification daemons | -| `exec` | Runs **every time** the config is reloaded. | Scripts that need to refresh settings | - -### Example Setup - -```ini -# Start the status bar once -exec-once=waybar - -# Set wallpaper -exec-once=swaybg -i ~/.config/mango/wallpaper/room.png - -# Reload a custom script on config change -exec=bash ~/.config/mango/reload-settings.sh -``` diff --git a/docs/v0.13.0/configuration/index.mdx b/docs/v0.13.0/configuration/index.mdx deleted file mode 100644 index 2bcd3a7e..00000000 --- a/docs/v0.13.0/configuration/index.mdx +++ /dev/null @@ -1,21 +0,0 @@ ---- -title: Configuration -description: Configure mangowm with config files, environment variables, and autostart. -icon: Settings ---- - -Configure mangowm through config files, environment variables, and autostart. - -<Cards> - -<Card href="/docs/configuration/basics" title="Basics" description="Config files, env vars, exec-once, exec" /> - -<Card href="/docs/configuration/monitors" title="Monitors" description="Monitor settings and resolution" /> - -<Card href="/docs/configuration/input" title="Input" description="Keyboard, mouse, and touchpad" /> - -<Card href="/docs/configuration/xdg-portals" title="XDG Portals" description="File pickers and notifications" /> - -<Card href="/docs/configuration/miscellaneous" title="Miscellaneous" description="Additional options" /> - -</Cards> \ No newline at end of file diff --git a/docs/v0.13.0/configuration/input.md b/docs/v0.13.0/configuration/input.md deleted file mode 100644 index ee12906a..00000000 --- a/docs/v0.13.0/configuration/input.md +++ /dev/null @@ -1,161 +0,0 @@ ---- -title: Input Devices -description: Configure keyboard layouts, mouse sensitivity, and touchpad gestures. ---- - -## Device Configuration - -mangowm provides granular control over different input devices. - -### Keyboard Settings - -Control key repeat rates and layout rules. - -| Setting | Type | Default | Description | -| :--- | :--- | :--- | :--- | -| `repeat_rate` | `int` | `25` | How many times a key repeats per second. | -| `repeat_delay` | `int` | `600` | Delay (ms) before a held key starts repeating. | -| `numlockon` | `0` or `1` | `0` | Enable NumLock on startup. | -| `xkb_rules_rules` | `string` | - | XKB rules file (e.g., `evdev`, `base`). Usually auto-detected. | -| `xkb_rules_model` | `string` | - | Keyboard model (e.g., `pc104`, `macbook`). | -| `xkb_rules_layout` | `string` | - | Keyboard layout code (e.g., `us`, `de`, `us,de`). | -| `xkb_rules_variant` | `string` | - | Layout variant (e.g., `dvorak`, `colemak`, `intl`). | -| `xkb_rules_options` | `string` | - | XKB options (e.g., `caps:escape`, `ctrl:nocaps`). | - -**Example:** - -```ini -repeat_rate=40 -repeat_delay=300 -numlockon=1 -xkb_rules_layout=us,de -xkb_rules_variant=dvorak -xkb_rules_options=caps:escape,ctrl:nocaps -``` - ---- - -### Mouse Settings - -Configuration for external mice. - -| Setting | Default | Description | -| :--- | :--- | :--- | -| `mouse_natural_scrolling` | `0` | Invert scrolling direction. | -| `mouse_accel_profile` | `2` | `0` (None), `1` (Flat), `2` (Adaptive). | -| `mouse_accel_speed` | `0.0` | Speed adjustment (-1.0 to 1.0). | -| `left_handed` | `0` | Swap left and right buttons. | -| `axis_scroll_factor` | `1.0` | Scroll factor for axis scroll speed (0.1–10.0). | ---- - -### Trackpad Settings - -Specific settings for laptop touchpads. Some settings may require a relogin to take effect. - -| Setting | Default | Description | -| :--- | :--- | :--- | -| `disable_trackpad` | `0` | Set to `1` to disable the trackpad entirely. | -| `tap_to_click` | `1` | Tap to trigger a left click. | -| `tap_and_drag` | `1` | Tap and hold to drag items. | -| `trackpad_natural_scrolling` | `0` | Invert scrolling direction (natural scrolling). | -| `trackpad_accel_profile` | `2` | `0` (None), `1` (Flat), `2` (Adaptive). | -| `trackpad_accel_speed` | `0.0` | Speed adjustment (-1.0 to 1.0). | -| `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). | -| `send_events_mode` | `0` | `0` (Enabled), `1` (Disabled), `2` (Disabled on external mouse). | -| `drag_lock` | `1` | Lock dragging after tapping. | -| `disable_while_typing` | `1` | Disable trackpad while typing. | -| `left_handed` | `0` | Swap left/right buttons. | -| `middle_button_emulation` | `0` | Emulate middle button. | -| `swipe_min_threshold` | `1` | Minimum swipe threshold when use gesture. | -| `button_map` | `0` | `0` (Left/right/middle), `1` (Left/middle/right). | -| `trackpad_scroll_factor` | `1.0` | Scroll factor for trackpad scroll speed (0.1–10.0). | ---- - -**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. - - `2` — Edge scrolling: send scroll events when a finger moves along the bottom or right edge. - - `4` — Button scrolling: send scroll events when a button is held and the device moves along a scroll axis. - -- `click_method` values: - - `0` — No software click emulation. - - `1` — Button areas: use software-defined areas on the touchpad to generate button events. - - `2` — Clickfinger: the number of fingers determines which button is pressed. - -- `mouse_accel_profile` or `trackpad_scroll_profile` values: - - `0` — No acceleration. - - `1` — Flat: no dynamic acceleration. Pointer speed = original input speed × (1 + `mouse_accel_speed`). - - `2` — Adaptive: slow movement results in less acceleration, fast movement results in more. - -- `button_map` values: - - `0` — 1/2/3 finger tap maps to left / right / middle. - - `1` — 1/2/3 finger tap maps to left / middle / right. - -- `send_events_mode` values: - - `0` — Send events from this device normally. - - `1` — Do not send events from this device. - - `2` — Disable this device when an external pointer device is plugged in. - ---- ---- - -## Keyboard Layout Switching - -To bind multiple layouts and toggle between them, define the layouts in `xkb_rules_layout` and use `xkb_rules_options` to set a toggle key combination. Then bind `switch_keyboard_layout` to trigger a switch. - -```ini -# Define two layouts: US QWERTY and US Dvorak -xkb_rules_layout=us,us -xkb_rules_variant=,dvorak -xkb_rules_options=grp:lalt_lshift_toggle -``` - -Or bind it manually to a key: - -```ini -# Bind Alt+Shift_L to cycle keyboard layout -bind=alt,shift_l,switch_keyboard_layout -``` - -Use `mmsg -g -k` to query the current keyboard layout at any time. - ---- - -## Input Method Editor (IME) - -To use Fcitx5 or IBus, set these environment variables in your config file. - -> **Info:** These settings require a restart of the window manager to take effect. - -**For Fcitx5:** - -```ini -env=GTK_IM_MODULE,fcitx -env=QT_IM_MODULE,fcitx -env=QT_IM_MODULES,wayland;fcitx -env=SDL_IM_MODULE,fcitx -env=XMODIFIERS,@im=fcitx -env=GLFW_IM_MODULE,ibus -``` - -**For IBus:** - -```ini -env=GTK_IM_MODULE,ibus -env=QT_IM_MODULE,ibus -env=XMODIFIERS,@im=ibus -``` diff --git a/docs/v0.13.0/configuration/meta.json b/docs/v0.13.0/configuration/meta.json deleted file mode 100644 index bc209b4e..00000000 --- a/docs/v0.13.0/configuration/meta.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "title": "Configuration", - "pages": ["basics", "monitors", "input", "xdg-portals", "miscellaneous"] -} diff --git a/docs/v0.13.0/configuration/miscellaneous.md b/docs/v0.13.0/configuration/miscellaneous.md deleted file mode 100644 index e1be2907..00000000 --- a/docs/v0.13.0/configuration/miscellaneous.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -title: Miscellaneous -description: Advanced settings for XWayland, focus behavior, and system integration. ---- - -## System & Hardware - -| Setting | Default | Description | -| :--- | :--- | :--- | -| `xwayland_persistence` | `1` | Keep XWayland running even when no X11 apps are open (reduces startup lag). | -| `syncobj_enable` | `0` | Enable `drm_syncobj` timeline support (helps with gaming stutter/lag). **Requires restart.** | -| `allow_lock_transparent` | `0` | Allow the lock screen to be transparent. | -| `allow_shortcuts_inhibit` | `1` | Allow shortcuts to be inhibited by clients. | -| `vrr` | - | Set via [monitor rule](/docs/configuration/monitors#monitor-rules). | - -## Focus & Input - -| Setting | Default | Description | -| :--- | :--- | :--- | -| `focus_on_activate` | `1` | Automatically focus windows when they request activation. | -| `sloppyfocus` | `1` | Focus follows the mouse cursor. | -| `warpcursor` | `1` | Warp the cursor to the center of the window when focus changes via keyboard. | -| `cursor_hide_timeout` | `0` | Hide the cursor after `N` seconds of inactivity (`0` to disable). | -| `drag_tile_to_tile` | `0` | Allow dragging a tiled window onto another to swap their positions. | -| `drag_tile_small` | `1` | Allow dragging a tiled window temporarily to small size.| -| `drag_corner` | `3` | Corner for drag-to-tile detection (0: none, 1–3: corners, 4: auto-detect). | -| `drag_warp_cursor` | `1` | Warp cursor when dragging windows to tile. | -| `axis_bind_apply_timeout` | `100` | Timeout (ms) for detecting consecutive scroll events for axis bindings. | - -## Multi-Monitor & Tags - -| Setting | Default | Description | -| :--- | :--- | :--- | -| `focus_cross_monitor` | `0` | Allow directional focus to cross monitor boundaries. | -| `exchange_cross_monitor` | `0` | Allow exchanging clients across monitor boundaries. | -| `focus_cross_tag` | `0` | Allow directional focus to cross into other tags. | -| `view_current_to_back` | `0` | Toggling the current tag switches back to the previously viewed tag. | -| `scratchpad_cross_monitor` | `0` | Share the scratchpad pool across all monitors. | -| `single_scratchpad` | `1` | Only allow one scratchpad (named or standard) to be visible at a time. | - -## Window Behavior - -| Setting | Default | Description | -| :--- | :--- | :--- | -| `enable_floating_snap` | `0` | Snap floating windows to edges or other windows. | -| `snap_distance` | `30` | Max distance (pixels) to trigger floating snap. | -| `no_border_when_single` | `0` | Remove window borders when only one window is visible on the tag. | -| `idleinhibit_ignore_visible` | `0` | Allow invisible clients (e.g., background audio players) to inhibit idle. | -| `drag_tile_refresh_interval` | `8.0` | Interval (1.0–16.0) to refresh tiled window resize during drag. Too small may cause application lag. | -| `drag_floating_refresh_interval` | `8.0` | Interval (1.0–16.0) to refresh floating window resize during drag. Too small may cause application lag. | \ No newline at end of file diff --git a/docs/v0.13.0/configuration/monitors.md b/docs/v0.13.0/configuration/monitors.md deleted file mode 100644 index 28ef240b..00000000 --- a/docs/v0.13.0/configuration/monitors.md +++ /dev/null @@ -1,276 +0,0 @@ ---- -title: Monitors -description: Manage display outputs, resolution, scaling, and tearing. ---- - -## Monitor Rules - -You can configure each display output individually using the `monitorrule` keyword. - -**Syntax:** - -```ini -monitorrule=name:Values,Parameter:Values,Parameter:Values -``` - -> **Info:** If any of the matching fields (`name`, `make`, `model`, `serial`) are set, **all** of the set ones must match to be considered a match. Use `wlr-randr` to get your monitor's name, make, model, and serial. - -### Parameters - -| Parameter | Type | Values | Description | -| :--- | :--- | :--- | :--- | -| `name` | string | Any | Match by monitor name (supports regex) | -| `make` | string | Any | Match by monitor manufacturer | -| `model` | string | Any | Match by monitor model | -| `serial` | string | Any | Match by monitor serial number | -| `width` | integer | 0-9999 | Monitor width | -| `height` | integer | 0-9999 | Monitor height | -| `refresh` | float | 0.001-9999.0 | Monitor refresh rate | -| `x` | integer | 0-99999 | X position | -| `y` | integer | 0-99999 | Y position | -| `scale` | float | 0.01-100.0 | Monitor scale | -| `vrr` | integer | 0, 1 | Enable variable refresh rate | -| `rr` | integer | 0-7 | Monitor transform | -| `custom` | integer | 0, 1 | Enable custom mode (not supported on all displays — may cause black screen) | - -### Transform Values - -| Value | Rotation | -| :--- | :--- | -| `0` | No transform | -| `1` | 90° counter-clockwise | -| `2` | 180° counter-clockwise | -| `3` | 270° counter-clockwise | -| `4` | 180° vertical flip | -| `5` | Flip + 90° counter-clockwise | -| `6` | Flip + 180° counter-clockwise | -| `7` | Flip + 270° counter-clockwise | - -> **Critical:** If you use XWayland applications, **never use negative coordinates** for your monitor positions. This is a known XWayland bug that causes click events to malfunction. Always arrange your monitors starting from `0,0` and extend into positive coordinates. - -> **Note:** that "name" is a regular expression. If you want an exact match, you need to add `^` and `$` to the beginning and end of the expression, for example, `^eDP-1$` matches exactly the string `eDP-1`. - -### Examples - -```ini -# Laptop display: 1080p, 60Hz, positioned at origin -monitorrule=name:^eDP-1$,width:1920,height:1080,refresh:60,x:0,y:10 - -# Match by make and model instead of name -monitorrule=make:Chimei Innolux Corporation,model:0x15F5,width:1920,height:1080,refresh:60,x:0,y:0 - -# Virtual monitor with pattern matching -monitorrule=name:HEADLESS-.*,width:1920,height:1080,refresh:60,x:1926,y:0,scale:1,rr:0,vrr:0 -``` - ---- - -## Monitor Spec Format - -Several commands (`focusmon`, `tagmon`, `disable_monitor`, `enable_monitor`, `toggle_monitor`, `viewcrossmon`, `tagcrossmon`) accept a **monitor_spec** string to identify a monitor. - -**Format:** - -```text -name:xxx&&make:xxx&&model:xxx&&serial:xxx -``` - -- Any field can be omitted and there is no order requirement. -- If all fields are omitted, the string is treated as the monitor name directly (e.g., `eDP-1`). -- Use `wlr-randr` to find your monitor's name, make, model, and serial. - -**Examples:** - -```bash -# By name (shorthand) -mmsg -d toggle_monitor,eDP-1 - -# By make and model -mmsg -d toggle_monitor,make:Chimei Innolux Corporation&&model:0x15F5 - -# By serial -mmsg -d toggle_monitor,serial:12345678 -``` - ---- - -## Tearing (Game Mode) - -Tearing allows games to bypass the compositor's VSync for lower latency. - -| Setting | Default | Description | -| :--- | :--- | :--- | -| `allow_tearing` | `0` | Global tearing control: `0` (Disable), `1` (Enable), `2` (Fullscreen only). | - -### Configuration - -**Enable Globally:** - -```ini -allow_tearing=1 -``` - -**Enable per Window:** - -Use a window rule to force tearing for specific games. - -```ini -windowrule=force_tearing:1,title:vkcube -``` - -### Tearing Behavior Matrix - -| `force_tearing` \ `allow_tearing` | DISABLED (0) | ENABLED (1) | FULLSCREEN_ONLY (2) | -| :--- | :--- | :--- | :--- | -| **UNSPECIFIED** (0) | Not Allowed | Follows tearing_hint | Only fullscreen follows tearing_hint | -| **ENABLED** (1) | Not Allowed | Allowed | Only fullscreen allowed | -| **DISABLED** (2) | Not Allowed | Not Allowed | Not Allowed | - -### Graphics Card Compatibility - -> **Warning:** Some graphics cards require setting the `WLR_DRM_NO_ATOMIC` environment variable before mango starts to successfully enable tearing. - -Add this to `/etc/environment` and reboot: - -```bash -WLR_DRM_NO_ATOMIC=1 -``` - -Or run mango with the environment variable: - -```bash -WLR_DRM_NO_ATOMIC=1 mango -``` - ---- - -## GPU Compatibility - -If mango cannot display correctly or shows a black screen, try selecting a specific GPU: - -```bash -# Use a single GPU -WLR_DRM_DEVICES=/dev/dri/card1 mango - -# Use multiple GPUs -WLR_DRM_DEVICES=/dev/dri/card0:/dev/dri/card1 mango -``` - -Some GPUs have compatibility issues with `syncobj_enable=1` — it may crash apps like `kitty` that use syncobj. Set `WLR_DRM_NO_ATOMIC=1` in `/etc/environment` and reboot to resolve this. - ---- - -## Power Management - -You can control monitor power using the `mmsg` IPC tool. - -```bash -# Turn off -mmsg -d disable_monitor,eDP-1 - -# Turn on -mmsg -d enable_monitor,eDP-1 - -# Toggle -mmsg -d toggle_monitor,eDP-1 -``` - -You can also use `wlr-randr` for monitor management: - -```bash -# Turn off monitor -wlr-randr --output eDP-1 --off - -# Turn on monitor -wlr-randr --output eDP-1 --on - -# Show all monitors -wlr-randr -``` - ---- - -## Screen Scale - -### Without Global Scale (Recommended) - -- If you do not use XWayland apps, you can use monitor rules or `wlr-randr` to set a global monitor scale. -- If you are using XWayland apps, it is not recommended to set a global monitor scale. - -You can set scale like this, for example with a 1.4 factor. - -**Dependencies:** - -```bash -yay -S xorg-xrdb -yay -S xwayland-satellite -``` - -**In config file:** - -```ini -env=QT_AUTO_SCREEN_SCALE_FACTOR,1 -env=QT_WAYLAND_FORCE_DPI,140 -``` - -**In autostart:** - -```bash -echo "Xft.dpi: 140" | xrdb -merge -gsettings set org.gnome.desktop.interface text-scaling-factor 1.4 -``` - -**Edit autostart for XWayland:** - -```bash -# Start xwayland -/usr/sbin/xwayland-satellite :11 & -# Apply scale 1.4 for xwayland -sleep 0.5s && echo "Xft.dpi: 140" | xrdb -merge -``` - -### Using xwayland-satellite to Prevent Blurry XWayland Apps - -If you use fractional scaling, you can use `xwayland-satellite` to automatically scale XWayland apps to prevent blurriness, for example with a scale of 1.4. - -**Dependencies:** - -```bash -yay -S xwayland-satellite -``` - -**In config file:** - -```ini -env=DISPLAY,:2 -exec-once=xwayland-satellite :2 -monitorrule=name:eDP-1,width:1920,height:1080,refresh:60,x:0,y:0,scale:1.4,vrr:0,rr:0 -``` - -> **Warning:** Use a `DISPLAY` value other than `:1` to avoid conflicting with mangowm. - ---- - -## Virtual Monitors - -You can create and manage virtual displays through IPC commands: - -```bash -# Create virtual output -mmsg -d create_virtual_output - -# Destroy all virtual outputs -mmsg -d destroy_all_virtual_output -``` - -You can configure virtual monitors using `wlr-randr`: - -```bash -# Show all monitors -wlr-randr - -# Configure virtual monitor -wlr-randr --output HEADLESS-1 --pos 1921,0 --scale 1 --custom-mode 1920x1080@60Hz -``` - -Virtual monitors can be used for screen sharing with tools like [Sunshine](https://github.com/LizardByte/Sunshine) and [Moonlight](https://github.com/moonlight-stream/moonlight-android), allowing other devices to act as extended monitors. \ No newline at end of file diff --git a/docs/v0.13.0/configuration/xdg-portals.md b/docs/v0.13.0/configuration/xdg-portals.md deleted file mode 100644 index 27819ad8..00000000 --- a/docs/v0.13.0/configuration/xdg-portals.md +++ /dev/null @@ -1,76 +0,0 @@ ---- -title: XDG Portals -description: Set up screen sharing, clipboard, keyring, and file pickers using XDG portals. ---- - -## Portal Configuration - -You can customize portal settings via the following paths: - -- **User Configuration (Priority):** `~/.config/xdg-desktop-portal/mango-portals.conf` -- **System Fallback:** `/usr/share/xdg-desktop-portal/mango-portals.conf` - -> **Warning:** If you previously added `dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP=wlroots` to your config, remove it. Mango now handles this automatically. - -## Screen Sharing - -To enable screen sharing (OBS, Discord, WebRTC), you need `xdg-desktop-portal-wlr`. - -1. **Install Dependencies** - - `pipewire`, `pipewire-pulse`, `xdg-desktop-portal-wlr` - -2. **Optional: Add to autostart** - - In some situations the portal may not start automatically. You can add this to your autostart script to ensure it launches: - - ```bash - /usr/lib/xdg-desktop-portal-wlr & - ``` - -3. **Restart your computer** to apply changes. - -### Known Issues - -- **Window screen sharing:** Some applications may have issues sharing individual windows. See [#184](https://github.com/mangowm/mango/pull/184) for workarounds. - -- **Screen recording lag:** If you experience stuttering during screen recording, see [xdg-desktop-portal-wlr#351](https://github.com/emersion/xdg-desktop-portal-wlr/issues/351). - -## Clipboard Manager - -Use `cliphist` to manage clipboard history. - -**Dependencies:** `wl-clipboard`, `cliphist`, `wl-clip-persist` - -**Autostart Config:** - -```bash -# Keep clipboard content after app closes -wl-clip-persist --clipboard regular --reconnect-tries 0 & - -# Watch clipboard and store history -wl-paste --type text --watch cliphist store & -``` - -## GNOME Keyring - -If you need to store passwords or secrets (e.g., for VS Code or Minecraft launchers), install `gnome-keyring`. - -**Configuration:** - -Add the following to `~/.config/xdg-desktop-portal/mango-portals.conf`: - -```ini -[preferred] -default=gtk -org.freedesktop.impl.portal.ScreenCast=wlr -org.freedesktop.impl.portal.Screenshot=wlr -org.freedesktop.impl.portal.Secret=gnome-keyring -org.freedesktop.impl.portal.Inhibit=none -``` - -## File Picker (File Selector) - -**Dependencies:** `xdg-desktop-portal`, `xdg-desktop-portal-gtk` - -Reboot your computer once to apply. \ No newline at end of file diff --git a/docs/v0.13.0/faq.md b/docs/v0.13.0/faq.md deleted file mode 100644 index 9c9288de..00000000 --- a/docs/v0.13.0/faq.md +++ /dev/null @@ -1,100 +0,0 @@ ---- -title: FAQ -description: Frequently asked questions and troubleshooting. ---- - -### How do I arrange tiled windows with my mouse? - -You can enable the `drag_tile_to_tile` option in your config. This allows you to drag a tiled window onto another to swap them. - -```ini -drag_tile_to_tile=1 -``` - ---- - -### Why is my background blurry or why does blur look wrong? - -Blur applies to the transparent areas of windows. To disable it entirely, set `blur=0`. - -If you are experiencing **performance issues with blur**, make sure `blur_optimized=1` (the default). This caches the wallpaper as the blur background, which is much cheaper on the GPU: - -```ini -blur_optimized=1 -``` - ---- - -### Blur shows my wallpaper instead of the real background content - -This is expected behavior when `blur_optimized=1` (the default). The optimizer caches the wallpaper to reduce GPU load — windows will blur against the wallpaper rather than the actual content stacked beneath them. - -If you want blur to composite against the true background (i.e., show whatever is actually behind the window), set: - -```ini -blur_optimized=0 -``` - -> **Warning:** Disabling `blur_optimized` significantly increases GPU consumption and may cause rendering lag, especially on lower-end hardware. - ---- - -### My games are lagging or stuttering - -Try enabling **SyncObj** timeline support. - -```ini -syncobj_enable=1 -``` - ---- - -### My games have high input latency - -You can enable **Tearing** (similar to VSync off). - -First, enable it globally: - -```ini -allow_tearing=1 -``` - -Then force it for your specific game: - -```ini -windowrule=force_tearing:1,title:Counter-Strike 2 -``` - -> **Warning:** Some graphics cards require setting `WLR_DRM_NO_ATOMIC=1` before mango starts for tearing to work. Add it to `/etc/environment` and reboot, or launch mango with `WLR_DRM_NO_ATOMIC=1 mango`. See [Monitors — Tearing](/docs/configuration/monitors#tearing-game-mode) for details. - ---- - -### How do I use pipes `|` in spawn commands? - -The standard `spawn` command does not support shell pipes directly. You must use `spawn_shell` instead. - -```ini -bind=SUPER,P,spawn_shell,echo "hello" | rofi -dmenu -``` - ---- - -### Certain key combinations do not work on my keyboard layout. - -`bind` automatically converts keysym to keycode, which is compatible with most layouts but can sometimes be imprecise. If a key combination is not triggering, use the **keycode** directly instead of the key name. - -Run `wev` and press the key to find its keycode, then use it in your bind: - -```ini -# Instead of: -bind=ALT,q,killclient - -# Use the keycode (e.g., code:24 = q on most layouts): -bind=ALT,code:24,killclient -``` - -You can also use `binds` (the `s` flag) to match by keysym instead of keycode: - -```ini -binds=ALT,q,killclient -``` diff --git a/docs/v0.13.0/index.md b/docs/v0.13.0/index.md deleted file mode 100644 index d308370d..00000000 --- a/docs/v0.13.0/index.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: Introduction -description: A lightweight and feature-rich Wayland compositor based on dwl. ---- - - -**mango** is a Wayland compositor based on [dwl](https://codeberg.org/dwl/dwl/). It aims to be as lightweight as `dwl` and can be built completely within a few seconds, without compromising on functionality. - -> **Philosophy:** **Lightweight & Fast**: mango is designed to be minimal yet functional. It compiles in seconds and offers a robust set of features out of the box. - -## Feature Highlights - -Beyond basic window management, mangowm provides a rich set of features designed for a modern Wayland experience. - -- **[Animations](/docs/visuals/animations)** — Smooth, customizable animations for opening, moving, closing windows and tag switching. -- **[Layouts](/docs/window-management/layouts)** — Supports Scroller, Master-Stack, Monocle, Grid, Deck, and more, with per-tag layouts. -- **[Visual Effects](/docs/visuals/effects)** — Built-in blur, shadows, corner radius, and opacity effects powered by scenefx. -- **[IPC & Scripting](/docs/ipc)** — Control the compositor externally with robust IPC support for custom scripts and widgets. - -## Additional Features - -- **XWayland Support** — Excellent compatibility for legacy X11 applications. -- **Tag System** — Uses tags instead of workspaces, allowing separate window layouts for each tag. -- **Input Methods** — Great support for text input v2/v3 (Fcitx5, IBus). -- **Window States** — Rich states including swallow, minimize, maximize, fullscreen, and overlay. -- **Hot-Reload Config** — Simple external configuration that supports hot-reloading without restarting. -- **Scratchpads** — Support for both Sway-like and named scratchpads. - -## Community - -- **[Join the mangowm Discord](https://discord.gg/CPjbDxesh5)** — Chat with the community, get support, share your setup, and stay updated with the latest mangowm news. -- **[Join the GitHub Discussions](https://github.com/mangowm/mango/discussions)** — Ask questions, request features, report issues, or share ideas directly with contributors and other users. - -## Acknowledgements - -This project is built upon the hard work of several open-source projects: - -- **[wlroots](https://gitlab.freedesktop.org/wlroots/wlroots)** — Implementation of the Wayland protocol. -- **[mwc](https://github.com/nikoloc/mwc)** — Basal window animation reference. -- **[dwl](https://codeberg.org/dwl/dwl)** — Basal dwl features. -- **[sway](https://github.com/swaywm/sway)** — Sample implementation of the Wayland protocol. -- **[scenefx](https://github.com/wlrfx/scenefx)** — Library to simplify adding window effects. diff --git a/docs/v0.13.0/installation.md b/docs/v0.13.0/installation.md deleted file mode 100644 index c5d4936c..00000000 --- a/docs/v0.13.0/installation.md +++ /dev/null @@ -1,308 +0,0 @@ ---- -title: Installation -description: Install mangowm on AerynOS, Arch, Fedora, Gentoo, Guix System, NixOS, PikaOS, or build from source. ---- - -## Package Installation - -mangowm is available as a pre-built package on several distributions. Choose your distribution below. - ---- - -### AerynOS - -mangowm is available in the **AerynOS package repository**. - -You can install it using the `moss` package manager: - -```bash -sudo moss install mangowm -``` - ---- - -### Arch Linux - -mangowm is available in the **Arch User Repository (AUR)**. - -You can install it using an AUR helper like `yay` or `paru`: - -```bash -yay -S mangowm-git -``` - -> **Tip:** This package pulls the latest git version, ensuring you have the newest features and fixes. - ---- - -### Fedora - -The package is in the third-party **Terra repository**. First, add the Terra Repository. - -> **Warning:** Both commands require root privileges. Use `sudo` if needed. - -```bash -dnf install --nogpgcheck --repofrompath 'terra,https://repos.fyralabs.com/terra$releasever' terra-release -``` - -Then, install the package: - -```bash -dnf install mangowm -``` - ---- - -### Gentoo - -The package is hosted in the community-maintained **GURU** repository. - -1. **Add the GURU repository** - - ```bash - emerge --ask --verbose eselect-repository - eselect repository enable guru - emerge --sync guru - ``` - -2. **Unmask packages** - Add the required packages to your `package.accept_keywords` file: - - `gui-libs/scenefx` - - `gui-wm/mangowm` - -3. **Install mango** - ```bash - emerge --ask --verbose gui-wm/mangowm - ``` - ---- - -### Guix System - -The package definition is described in the source repository. - -1. **Add mango channel** - Add to `$HOME/.config/guix/channels.scm`: - - ```scheme - (cons (channel - (name 'mangowm) - (url "https://github.com/mangowm/mango.git") - (branch "main")) - %default-channels) - ``` - -2. **Install** - After running `guix pull`, you can install mangowm: - - ```bash - guix install mangowm - ``` - - Or add it to your system configuration using the mangowm module: - - ```scheme - (use-modules (mangowm)) - - (packages (cons* - mangowm-git - ... ;; Other packages - %base-packages)) - ``` - -> **Tip:** For more information, see the [Guix System documentation](https://guix.gnu.org/manual/devel/en/html_node/Channels.html). - ---- - -### NixOS - -The repository provides a Flake with a NixOS module. - -1. **Add flake input** - - ```nix - # flake.nix - { - inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - mangowm = { - url = "github:mangowm/mango"; - inputs.nixpkgs.follows = "nixpkgs"; - }; - # other inputs ... - }; - } - ``` - -2. **Import the NixOS module** - - **Option A — Import in `configuration.nix`:** - - ```nix - # configuration.nix (or any other file that you import) - {inputs, ...}: { - imports = [ - inputs.mangowm.nixosModules.mango - # .. other imports ... - ]; - - # ... - } - ``` - - **Option B — Import directly in flake:** - - ```nix - # flake.nix - { - # ... - - outputs = { self, nixpkgs, mangowm, ...}@inputs: let - inherit (nixpkgs) lib; - # ... - in { - nixosConfigurations.YourHostName = lib.nixosSystem { - modules = [ - mangowm.nixosModules.mango # or inputs.mangowm.nixosModules.mango - # other imports ... - ]; - }; - } - } - ``` - -3. **Enable the module** - - ```nix - # configuration.nix (or any other file that you import) - { - programs.mango.enable = true; - } - ``` - -4. **Start mango on login** - - The following are common examples. Refer to the official NixOS documentation for full configuration options. - - **Option A — greetd:** Autologin on first start; login screen after logout. - - ```nix - services.greetd = { - enable = true; - settings = { - initial_session = { - command = "mango"; - user = "your-username"; # auto-login on first start, no password required - }; - default_session = { - command = "${pkgs.greetd.tuigreet}/bin/tuigreet --cmd mango"; - user = "greeter"; - }; - }; - }; - ``` - - **Option B — Display manager autologin:** Autologin via an existing display manager (e.g. SDDM, GDM). [`addLoginEntry`](/docs/nix-options#addloginentry) (default: `true`) automatically registers mango as a session. - - ```nix - services.displayManager = { - defaultSession = "mango"; # derived from mango.desktop filename - autoLogin = { - enable = true; - user = "your-username"; - }; - }; - ``` - - **Option C — getty autologin:** No login screen, boots directly into mango on TTY1. - - For bash/zsh: - - ```nix - services.getty.autologinUser = "your-username"; - - environment.loginShellInit = '' - [ "$(tty)" = /dev/tty1 ] && exec mango - ''; - ``` - - For fish: - - ```nix - services.getty.autologinUser = "your-username"; - - programs.fish.loginShellInit = '' - if test (tty) = /dev/tty1 - exec mango - end - ''; - ``` - -5. **All available options** - - See [Nix Module Options](/docs/nix-options) for the full list of NixOS and Home Manager options. - ---- - -### PikaOS - -mangowm is available in the **PikaOS package repository**. - -You can install it using the `pikman` package manager: - -```bash -pikman install mangowm -``` - ---- - -## Building from Source - -If your distribution isn't listed above, or you want the latest unreleased changes, you can build mangowm from source. - -> **Info:** Ensure the following dependencies are installed before proceeding: -> -> - `wayland` -> - `wayland-protocols` -> - `libinput` -> - `libdrm` -> - `libxkbcommon` -> - `pixman` -> - `libdisplay-info` -> - `libliftoff` -> - `hwdata` -> - `seatd` -> - `pcre2` -> - `xorg-xwayland` -> - `libxcb` - -You will need to build `wlroots` and `scenefx` manually as well. - -1. **Build wlroots** - Clone and install the specific version required (check README for latest version). - - ```bash - git clone -b 0.19.3 https://gitlab.freedesktop.org/wlroots/wlroots.git - cd wlroots - meson build -Dprefix=/usr - sudo ninja -C build install - ``` - -2. **Build scenefx** - This library handles the visual effects. - - ```bash - git clone -b 0.4.1 https://github.com/wlrfx/scenefx.git - cd scenefx - meson build -Dprefix=/usr - sudo ninja -C build install - ``` - -3. **Build mangowm** - Finally, compile the compositor itself. - ```bash - git clone https://github.com/mangowm/mango.git - cd mango - meson build -Dprefix=/usr - sudo ninja -C build install - ``` diff --git a/docs/v0.13.0/ipc.md b/docs/v0.13.0/ipc.md deleted file mode 100644 index 8bb0f5c1..00000000 --- a/docs/v0.13.0/ipc.md +++ /dev/null @@ -1,154 +0,0 @@ ---- -title: IPC -description: Control mangowm programmatically using mmsg. ---- - -## Introduction - -mangowm includes a powerful IPC (Inter-Process Communication) tool called `mmsg`. This allows you to query the window manager's state, watch for events, and execute commands from external scripts. - -## Basic Usage - -The general syntax for `mmsg` is: - -```bash -mmsg [-OTLq] -mmsg [-o <output>] -s [-t <tags>] [-l <layout>] [-c <tags>] [-d <cmd>,<arg1>,<arg2>,<arg3>,<arg4>,<arg5>] -mmsg [-o <output>] (-g | -w) [-OotlcvmfxekbA] -``` - -### Options - -| Flag | Description | -| :--- | :--- | -| `-q` | Quit mangowm. | -| `-g` | **Get** values (tags, layout, focused client). | -| `-s` | **Set** values (switch tags, layouts). | -| `-w` | **Watch** mode (streams events). | -| `-O` | Get all output (monitor) information. | -| `-T` | Get number of tags. | -| `-L` | Get all available layouts. | -| `-o` | Select output (monitor). | -| `-t` | Get/set selected tags (set with `[+-^.]`). | -| `-l` | Get/set current layout. | -| `-c` | Get title and appid of focused client. | -| `-v` | Get visibility of statusbar. | -| `-m` | Get fullscreen status. | -| `-f` | Get floating status. | -| `-d` | **Dispatch** an internal command. | -| `-x` | Get focused client geometry. | -| `-e` | Get the name of the last focused layer. | -| `-k` | Get current keyboard layout. | -| `-b` | Get current keybind mode. | -| `-A` | Get scale factor of monitor. | - -## Examples - -### Tag Management - -You can perform arithmetic on tags using the `-t` flag with `-s` (set). - -```bash -# Switch to Tag 1 -mmsg -t 1 - -# Add Tag 2 to current view (Multiview) -mmsg -s -t 2+ - -# Remove Tag 2 from current view -mmsg -s -t 2- - -# Toggle Tag 2 -mmsg -s -t 2^ -``` - -### Layouts - -Switch layouts programmatically. Layout codes: `S` (Scroller), `T` (Tile), `G` (Grid), `M` (Monocle), `K` (Deck), `CT` (Center Tile), `RT` (Right Tile), `VS` (Vertical Scroller), `VT` (Vertical Tile), `VG` (Vertical Grid), `VK` (Vertical Deck), `DW` (Dwindle). - -```bash -# Switch to Scroller -mmsg -l "S" - -# Switch to Tile -mmsg -l "T" -``` - -### Dispatching Commands - -Any command available in `config.conf` keybindings can be run via IPC. - -```bash -# Close the focused window -mmsg -d killclient - -# Resize window by +10 width -mmsg -d resizewin,+10,0 - -# Toggle fullscreen -mmsg -d togglefullscreen - -# Disable a monitor power -mmsg -d disable_monitor,eDP-1 -``` - -### Monitoring & Status - -Use `-g` or `-w` to build custom status bars or automation scripts. - -```bash -# Watch for all message changes -mmsg -w - -# Get all messages without watch -mmsg -g - -# Watch focused client appid and title -mmsg -w -c - -# Get all available outputs -mmsg -O - -# Get all tags message -mmsg -g -t - -# Get current focused client message -mmsg -g -c - -# Get current keyboard layout -mmsg -g -k - -# Get current keybind mode -mmsg -g -b - -# Get scale factor of current monitor -mmsg -g -A -``` - -#### Tag Message Format - -- State: 0 → none, 1 → active, 2 → urgent - -Example output: - -| Monitor | Tag Number | Tag State | Clients in Tag | Focused Client | -|---------|------------|-----------|----------------|----------------| -| eDP-1 | tag 2 | 0 | 1 | 0 | - -| Monitor | occupied tags mask | active tags mask | urgent tags mask | -|---------|--------------------|------------------|------------------| -| eDP-1 | 14 | 6 | 0 | - -## Virtual Monitors - -You can create headless outputs for screen mirroring or remote desktop access (e.g., Sunshine/Moonlight). - -```bash -# Create a virtual output -mmsg -d create_virtual_output - -# Configure it (set resolution) -wlr-randr --output HEADLESS-1 --pos 1920,0 --mode 1920x1080@60Hz - -# Destroy all virtual outputs -mmsg -d destroy_all_virtual_output \ No newline at end of file diff --git a/docs/v0.13.0/meta.json b/docs/v0.13.0/meta.json deleted file mode 100644 index 95507bff..00000000 --- a/docs/v0.13.0/meta.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "title": "v0.13.0", - "description": "v0.13.0 release", - "root": true, - "pages": [ - "---Getting Started---", - "index", - "installation", - "quick-start", - "---Configuration---", - "configuration", - "visuals", - "window-management", - "bindings", - "---Examples---", - "screenshot", - "---Reference---", - "nix-options", - "ipc", - "faq" - ] -} diff --git a/docs/v0.13.0/nix-options.md b/docs/v0.13.0/nix-options.md deleted file mode 100644 index 2537d9d8..00000000 --- a/docs/v0.13.0/nix-options.md +++ /dev/null @@ -1,519 +0,0 @@ ---- -title: Nix Module Options -description: NixOS and Home Manager configuration options for mangowm. ---- - -> **Note:** This document is automatically generated from the Nix module source code. - -## NixOS - -**System-level options via `programs.mango`.** - -### enable - - - -Whether to enable mango, a wayland compositor based on dwl\. - - - -*Type:* -boolean - - - -*Default:* - -```nix -false -``` - - - -*Example:* - -```nix -true -``` - -*Declared by:* - - [\<mango/nix/nixos-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/nixos-modules.nix) - - - -### package - - - -The mango package to use - - - -*Type:* -package - - - -*Default:* - -```nix -<derivation mango-nightly> -``` - -*Declared by:* - - [\<mango/nix/nixos-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/nixos-modules.nix) - - - -### addLoginEntry - - - -Whether to add a login entry to the display manager for mango\. Only has effect if a display manager is configured (e\.g\. SDDM, GDM via ` services.displayManager `)\. - - - -*Type:* -boolean - - - -*Default:* - -```nix -true -``` - -*Declared by:* - - [\<mango/nix/nixos-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/nixos-modules.nix) - -## Home Manager - -**Configure mangowm declaratively via `wayland.windowManager.mango`.** - -### enable - - - -Whether to enable mangowm, a Wayland compositor based on dwl\. - - - -*Type:* -boolean - - - -*Default:* - -```nix -false -``` - -*Declared by:* - - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) - - - -### package - - - -The mango package to use - - - -*Type:* -package - - - -*Default:* - -```nix -<derivation mango-nightly> -``` - -*Declared by:* - - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) - - - -### autostart_sh - - - -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:* -strings concatenated with “\\n” - - - -*Default:* - -```nix -"" -``` - - - -*Example:* - -```nix -'' - waybar & - dunst & -'' -``` - -*Declared by:* - - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) - - - -### bottomPrefixes - - - -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\. - - - -*Type:* -list of string - - - -*Default:* - -```nix -[ ] -``` - - - -*Example:* - -```nix -[ - "source" -] -``` - -*Declared by:* - - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) - - - -### extraConfig - - - -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\. - - - -*Type:* -strings concatenated with “\\n” - - - -*Default:* - -```nix -"" -``` - - - -*Example:* - -```nix -'' - # Advanced config that doesn't fit structured format - special_option = 1 -'' -``` - -*Declared by:* - - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) - - - -### settings - - - -Mango configuration written in Nix\. Entries with the same key -should be written as lists\. Variables and colors names should be -quoted\. See [https://mangowm\.github\.io/docs](https://mangowm\.github\.io/docs) 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 ` - -Keymodes (submaps) are supported via the special ` keymode ` attribute\. Each keymode -is a nested attribute set under ` keymode ` that contains its own bindings\. - - - -*Type:* -Mango configuration value - - - -*Default:* - -```nix -{ } -``` - - - -*Example:* - -```nix -{ - # 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" - "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" - ]; - }; - }; -} - -``` - -*Declared by:* - - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) - - - -### systemd\.enable - - - -Whether to enable ` mango-session.target ` on -mango startup\. This links to -` graphical-session.target `\. -Some important environment variables will be imported to systemd -and dbus user environment before reaching the target, including - - - ` DISPLAY ` - - ` WAYLAND_DISPLAY ` - - ` XDG_CURRENT_DESKTOP ` - - ` XDG_SESSION_TYPE ` - - ` NIXOS_OZONE_WL ` - You can extend this list using the ` systemd.variables ` option\. - - - -*Type:* -boolean - - - -*Default:* - -```nix -true -``` - - - -*Example:* - -```nix -false -``` - -*Declared by:* - - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) - - - -### systemd\.extraCommands - - - -Extra commands to run after D-Bus activation\. - - - -*Type:* -list of string - - - -*Default:* - -```nix -[ - "systemctl --user reset-failed" - "systemctl --user start mango-session.target" -] -``` - -*Declared by:* - - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) - - - -### systemd\.variables - - - -Environment variables imported into the systemd and D-Bus user environment\. - - - -*Type:* -list of string - - - -*Default:* - -```nix -[ - "DISPLAY" - "WAYLAND_DISPLAY" - "XDG_CURRENT_DESKTOP" - "XDG_SESSION_TYPE" - "NIXOS_OZONE_WL" - "XCURSOR_THEME" - "XCURSOR_SIZE" -] -``` - - - -*Example:* - -```nix -[ - "--all" -] -``` - -*Declared by:* - - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) - - - -### systemd\.xdgAutostart - - - -Whether to enable autostart of applications using -` systemd-xdg-autostart-generator(8) ` -\. - - - -*Type:* -boolean - - - -*Default:* - -```nix -false -``` - - - -*Example:* - -```nix -true -``` - -*Declared by:* - - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) - - - -### topPrefixes - - - -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\. - - - -*Type:* -list of string - - - -*Default:* - -```nix -[ ] -``` - - - -*Example:* - -```nix -[ - "source" -] -``` - -*Declared by:* - - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) - diff --git a/docs/v0.13.0/quick-start.md b/docs/v0.13.0/quick-start.md deleted file mode 100644 index bc192474..00000000 --- a/docs/v0.13.0/quick-start.md +++ /dev/null @@ -1,88 +0,0 @@ ---- -title: Quick Start -description: Basic configuration and first steps with mangowm. ---- - -Now that you have mangowm installed, let's get your environment set up. - -## Initial Setup - -1. **Create Configuration Directory** - - mangowm looks for configuration files in `~/.config/mango/`. - - ```bash - mkdir -p ~/.config/mango - ``` - -2. **Copy Default Config** - - A default configuration file is provided at `/etc/mango/config.conf`. Copy it to your local directory to start customizing. - - ```bash - cp /etc/mango/config.conf ~/.config/mango/config.conf - ``` - -3. **Launch mangowm** - - You can now start the compositor from your TTY. - - ```bash - mango - ``` - - Optional: To specify a custom config file path: - - ```bash - mango -c /path/to/your/config.conf - ``` - -## Essential Keybindings - -mangowm uses the following keybinds by default: - -| Key Combination | Action | -| :--- | :--- | -| `Alt` + `Return` | Open Terminal (defaults to `foot`) | -| `Alt` + `Space` | Open Launcher (defaults to `rofi`) | -| `Alt` + `Q` | Close (Kill) the active window | -| `Super` + `M` | Quit mangowm | -| `Super` + `F` | Toggle Fullscreen | -| `Alt` + `Arrow Keys` | Move focus (Left, Right, Up, Down) | -| `Ctrl` + `1-9` | Switch to Tag 1-9 | -| `Alt` + `1-9` | Move window to Tag 1-9 | - -> **Warning:** Some default bindings rely on specific tools like `foot` (terminal) and `rofi` (launcher). Ensure you have them installed or update your `config.conf` to use your preferred alternatives. - -## Recommended Tools - -To get a fully functional desktop experience, we recommend installing the following components: - -| Category | Recommended Tools | -| :--- | :--- | -| Application Launcher | rofi, bemenu, wmenu, fuzzel | -| Terminal Emulator | foot, wezterm, alacritty, kitty, ghostty | -| Status Bar | waybar, eww, quickshell, ags | -| Desktop Shell | Noctalia, DankMaterialShell | -| Wallpaper Setup | awww(swww), swaybg | -| Notification Daemon | swaync, dunst, mako | -| Desktop Portal | xdg-desktop-portal, xdg-desktop-portal-wlr, xdg-desktop-portal-gtk | -| Clipboard | wl-clipboard, wl-clip-persist, cliphist | -| Gamma Control / Night Light | wlsunset, gammastep | -| Miscellaneous | xfce-polkit, wlogout | - -## Example Configuration - -Check out the [example configuration](https://github.com/DreamMaoMao/mango-config) by the creator of mangowm, including complete setups for mangowm, Waybar, Rofi, and more. - -```bash -git clone https://github.com/DreamMaoMao/mango-config.git ~/.config/mango -``` - -## Next Steps - -Now that you are up and running, dive deeper into customizing mangowm: - -- [Configure Monitors](/docs/configuration/monitors) — Set up resolution, scaling, and multi-monitor layouts. -- [Window Rules](/docs/window-management/rules#window-rules) — Define how specific applications should behave. -- [Appearance](/docs/visuals/theming) — Customize colors, borders, gaps, and effects. diff --git a/docs/v0.13.0/screenshot.md b/docs/v0.13.0/screenshot.md deleted file mode 100644 index f07cdf0c..00000000 --- a/docs/v0.13.0/screenshot.md +++ /dev/null @@ -1,213 +0,0 @@ ---- - -title: Screenshots -description: Example screenshot keybindings and capture workflows for mangowm. - ---- - -mangowm does not include a built-in screenshot tool. This keeps the compositor lean. -Instead, compose your own workflow from small Wayland utilities and bind them to keys; - -| Tool | Purpose | -| :--- | :--- | -| [`grim`](https://github.com/emersion/grim) | Capture the screen or a region to a file | -| [`slurp`](https://github.com/emersion/slurp) | Interactively select a region for `grim` | -| [`wl-copy`](https://github.com/bugaevc/wl-clipboard) | Copy screenshots directly to the clipboard | -| [`satty`](https://github.com/gabm/Satty) | Annotate screenshots before saving | -| [`wayfreeze`](https://github.com/nicbk/wayfreeze) | Freeze the screen before capture | - -Install the required with your package manager or from source. - -`grim` writes to the file path you give it, but **will not create missing directories**. Create one first: - -```bash -mkdir -p ~/Pictures/Screenshots -``` - -Any directory works. `~/Pictures/Screenshots/` is just a convention. - -## Quick Binds - -Short, single-step commands can be placed directly in `config.conf` with `spawn_shell`. -No script file needed. - -### Fullscreen - -Captures the entire display. - -```ini -bind=NONE,Print,spawn_shell,grim $HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png -``` - -### Region - -Select an area with `slurp` before capturing. - -```ini -bind=SHIFT,Print,spawn_shell,g=$(slurp -d) && [ -n "$g" ] && grim -g "$g" $HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png -``` - -### Pointer - -Captures the full screen including the cursor. - -```ini -bind=ALT,Print,spawn_shell,grim -c $HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png -``` - -### Clipboard - -Captures to a temporary file and copies the image to the clipboard; no file is saved. - -```ini -bind=CTRL,Print,spawn_shell,f=$(mktemp -t screenshot-XXXXXX.png) && grim "$f" && wl-copy < "$f" && rm -f "$f" -``` - -### Annotate - -Captures and opens `satty` for drawing before saving. - -```ini -bind=SUPER,Print,spawn_shell,f=$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png && grim "$f" && satty --filename "$f" --output-filename "$f" --actions-on-enter save-to-file --early-exit -``` - -## Script Binds - -When a command involves multi-step logic, geometry parsing, FIFOs, or screen freezing, -move it into a script and invoke it with `spawn` instead of `spawn_shell`. - -Create the scripts directory first: - -```bash -mkdir -p ~/.config/mango/scripts/screenshot -``` - -### Window - -Uses `mmsg` (ships with mango) to capture the focused window. - -`~/.config/mango/scripts/screenshot/window.sh`: - -```bash -#!/usr/bin/env bash -geometry=$(mmsg -x | awk '/x / {x=$3} /y / {y=$3} /width / {w=$3} /height / {h=$3} END {print x","y" "w"x"h}') -[ -z "$geometry" ] && exit 1 -grim -g "$geometry" "$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png" -``` - -```ini -bind=CTRL+SHIFT,Print,spawn,$HOME/.config/mango/scripts/screenshot/window.sh -``` - -### Freeze - -Freezes the screen with `wayfreeze` before capturing. - -`~/.config/mango/scripts/screenshot/freeze.sh`: - -```bash -#!/usr/bin/env bash -pipe=$(mktemp -u).fifo -mkfifo "$pipe" -wayfreeze --after-freeze-timeout 100 --after-freeze-cmd "echo > $pipe" & -wayfreeze_pid=$! -read -r < "$pipe" -grim "$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png" -kill "$wayfreeze_pid" 2>/dev/null -rm -f "$pipe" -``` - -```ini -bind=CTRL+SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/freeze.sh -``` - -### Freeze + Region - -Freeze, then select a region with `slurp`. Cleans up on cancel. - -`~/.config/mango/scripts/screenshot/freeze-region.sh`: - -```bash -#!/usr/bin/env bash -pipe=$(mktemp -u).fifo -mkfifo "$pipe" -wayfreeze --after-freeze-timeout 100 --after-freeze-cmd "echo > $pipe" & -wayfreeze_pid=$! -read -r < "$pipe" -geometry=$(slurp -d) -if [[ -z "$geometry" ]]; then - kill "$wayfreeze_pid" 2>/dev/null - rm -f "$pipe" - exit 1 -fi -grim -g "$geometry" "$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png" -kill "$wayfreeze_pid" 2>/dev/null -rm -f "$pipe" -``` - -```ini -bind=SHIFT+SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/freeze-region.sh -``` - -Make all three scripts executable: - -```bash -chmod +x ~/.config/mango/scripts/screenshot/*.sh -``` - -## All-in-One Script - -Prefer fewer files? A single script with subcommands covers every mode above. -Place it in the same directory and use it in place of the individual scripts. - -`~/.config/mango/scripts/screenshot/screenshot.sh`: - -```bash -#!/usr/bin/env bash -set -euo pipefail -mkdir -p "$HOME/Pictures/Screenshots" -filepath="$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png" - -case "${1:-fullscreen}" in - region) - g=$(slurp -d); [ -z "$g" ] && exit 1 - grim -g "$g" "$filepath" ;; - window) - g=$(mmsg -x | awk '/x / {x=$3} /y / {y=$3} /width / {w=$3} /height / {h=$3} END {print x","y" "w"x"h}') - [ -z "$g" ] && exit 1 - grim -g "$g" "$filepath" ;; - freeze) - p=$(mktemp -u).fifo; mkfifo "$p" - wayfreeze --after-freeze-timeout 100 --after-freeze-cmd "echo > $p" & wp=$! - read -r < "$p"; grim "$filepath" - kill "$wp" 2>/dev/null; rm -f "$p" ;; - freeze-region) - p=$(mktemp -u).fifo; mkfifo "$p" - wayfreeze --after-freeze-timeout 100 --after-freeze-cmd "echo > $p" & wp=$! - read -r < "$p"; g=$(slurp -d) - if [ -z "$g" ]; then kill "$wp" 2>/dev/null; rm -f "$p"; exit 1; fi - grim -g "$g" "$filepath" - kill "$wp" 2>/dev/null; rm -f "$p" ;; - annotate) - grim "$filepath"; satty --filename "$filepath" --output-filename "$filepath" --actions-on-enter save-to-file --early-exit ;; - *) grim "$filepath" ;; -esac -``` - -Make the script executable: - - -```bash -chmod +x ~/.config/mango/scripts/screenshot/screenshot.sh -``` - -Then add the binds to `config.conf`: - -```ini -bind=NONE,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh fullscreen -bind=SHIFT,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh region -bind=CTRL+SHIFT,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh window -bind=CTRL+SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh freeze -bind=SHIFT+SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh freeze-region -bind=SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh annotate -``` diff --git a/docs/v0.13.0/visuals/animations.md b/docs/v0.13.0/visuals/animations.md deleted file mode 100644 index 76477e05..00000000 --- a/docs/v0.13.0/visuals/animations.md +++ /dev/null @@ -1,108 +0,0 @@ ---- -title: Animations -description: Configure smooth transitions for windows and layers. ---- - -## Enabling Animations - -mangowm supports animations for both standard windows and layer shell surfaces (like bars and notifications). - -```ini -animations=1 -layer_animations=1 -``` - -## Animation Types - -You can define different animation styles for opening and closing windows and layer surfaces. - -Available types: `slide`, `zoom`, `fade`, `none`. - -```ini -animation_type_open=zoom -animation_type_close=slide -layer_animation_type_open=slide -layer_animation_type_close=slide -``` - -## Fade Settings - -Control the fade-in and fade-out effects for animations. - -```ini -animation_fade_in=1 -animation_fade_out=1 -fadein_begin_opacity=0.5 -fadeout_begin_opacity=0.5 -``` - -- `animation_fade_in` — Enable fade-in effect (0: disable, 1: enable) -- `animation_fade_out` — Enable fade-out effect (0: disable, 1: enable) -- `fadein_begin_opacity` — Starting opacity for fade-in animations (0.0–1.0) -- `fadeout_begin_opacity` — Starting opacity for fade-out animations (0.0–1.0) - -## Zoom Settings - -Adjust the zoom ratios for zoom animations. - -```ini -zoom_initial_ratio=0.4 -zoom_end_ratio=0.8 -``` - -- `zoom_initial_ratio` — Initial zoom ratio -- `zoom_end_ratio` — End zoom ratio - -## Durations - -Control the speed of animations (in milliseconds). - -| Setting | Type | Default | Description | -| :--- | :--- | :--- | :--- | -| `animation_duration_move` | integer | `500` | Move animation duration (ms) | -| `animation_duration_open` | integer | `400` | Open animation duration (ms) | -| `animation_duration_tag` | integer | `300` | Tag animation duration (ms) | -| `animation_duration_close` | integer | `300` | Close animation duration (ms) | -| `animation_duration_focus` | integer | `0` | Focus change (opacity transition) animation duration (ms) | - -```ini -animation_duration_move=500 -animation_duration_open=400 -animation_duration_tag=300 -animation_duration_close=300 -animation_duration_focus=0 -``` - -## Custom Bezier Curves - -Bezier curves determine the "feel" of an animation (e.g., linear vs. bouncy). The format is `x1,y1,x2,y2`. - -You can visualize and generate curve values using online tools like [cssportal.com](https://www.cssportal.com/css-cubic-bezier-generator/) or [easings.net](https://easings.net). - -| Setting | Type | Default | Description | -| :--- | :--- | :--- | :--- | -| `animation_curve_open` | string | `0.46,1.0,0.29,0.99` | Open animation bezier curve | -| `animation_curve_move` | string | `0.46,1.0,0.29,0.99` | Move animation bezier curve | -| `animation_curve_tag` | string | `0.46,1.0,0.29,0.99` | Tag animation bezier curve | -| `animation_curve_close` | string | `0.46,1.0,0.29,0.99` | Close animation bezier curve | -| `animation_curve_focus` | string | `0.46,1.0,0.29,0.99` | Focus change (opacity transition) animation bezier curve | -| `animation_curve_opafadein` | string | `0.46,1.0,0.29,0.99` | Open opacity animation bezier curve | -| `animation_curve_opafadeout` | string | `0.5,0.5,0.5,0.5` | Close opacity animation bezier curve | - -```ini -animation_curve_open=0.46,1.0,0.29,0.99 -animation_curve_move=0.46,1.0,0.29,0.99 -animation_curve_tag=0.46,1.0,0.29,0.99 -animation_curve_close=0.46,1.0,0.29,0.99 -animation_curve_focus=0.46,1.0,0.29,0.99 -animation_curve_opafadein=0.46,1.0,0.29,0.99 -animation_curve_opafadeout=0.5,0.5,0.5,0.5 -``` - -## Tag Animation Direction - -Control the direction of tag switch animations. - -| Setting | Default | Description | -| :--- | :--- | :--- | -| `tag_animation_direction` | `1` | Tag animation direction (1: horizontal, 0: vertical) | \ No newline at end of file diff --git a/docs/v0.13.0/visuals/effects.md b/docs/v0.13.0/visuals/effects.md deleted file mode 100644 index 23c1f206..00000000 --- a/docs/v0.13.0/visuals/effects.md +++ /dev/null @@ -1,82 +0,0 @@ ---- -title: Window Effects -description: Add visual polish with blur, shadows, and opacity. ---- - -## Blur - -Blur creates a frosted glass effect for transparent windows. - -| Setting | Default | Description | -| :--- | :--- | :--- | -| `blur` | `0` | Enable blur for windows. | -| `blur_layer` | `0` | Enable blur for layer surfaces (like bars/docks). | -| `blur_optimized` | `1` | Caches the wallpaper and blur background, significantly reducing GPU usage. Disabling it will significantly increase GPU consumption and may cause rendering lag. **Highly recommended.** | -| `blur_params_radius` | `5` | The strength (radius) of the blur. | -| `blur_params_num_passes` | `1` | Number of passes. Higher = smoother but more expensive. | -| `blur_params_noise` | `0.02` | Blur noise level. | -| `blur_params_brightness` | `0.9` | Blur brightness adjustment. | -| `blur_params_contrast` | `0.9` | Blur contrast adjustment. | -| `blur_params_saturation` | `1.2` | Blur saturation adjustment. | - -> **Warning:** Blur has a relatively high impact on performance. If your hardware is limited, it is not recommended to enable it. If you experience lag with blur on, ensure `blur_optimized=1` — disabling it will significantly increase GPU consumption and may cause rendering lag. To disable blur entirely, set `blur=0`. - ---- - -## Shadows - -Drop shadows help distinguish floating windows from the background. - -| Setting | Default | Description | -| :--- | :--- | :--- | -| `shadows` | `0` | Enable shadows. | -| `layer_shadows` | `0` | Enable shadows for layer surfaces. | -| `shadow_only_floating` | `1` | Only draw shadows for floating windows (saves performance). | -| `shadows_size` | `10` | Size of the shadow. | -| `shadows_blur` | `15` | Shadow blur amount. | -| `shadows_position_x` | `0` | Shadow X offset. | -| `shadows_position_y` | `0` | Shadow Y offset. | -| `shadowscolor` | `0x000000ff` | Color of the shadow. | - -```ini -# Example shadows configuration -shadows=1 -layer_shadows=1 -shadow_only_floating=1 -shadows_size=12 -shadows_blur=15 -shadows_position_x=0 -shadows_position_y=0 -shadowscolor=0x000000ff -``` - ---- - -## Opacity & Corner Radius - -Control the transparency and roundness of your windows. - -| Setting | Default | Description | -| :--- | :--- | :--- | -| `border_radius` | `0` | Window corner radius in pixels. | -| `border_radius_location_default` | `0` | Corner radius location: `0` (all), `1` (top-left), `2` (top-right), `3` (bottom-left), `4` (bottom-right), `5` (closest corner). | -| `no_radius_when_single` | `0` | Disable radius if only one window is visible. | -| `focused_opacity` | `1.0` | Opacity for the active window (0.0 - 1.0). | -| `unfocused_opacity` | `1.0` | Opacity for inactive windows (0.0 - 1.0). | - -```ini -# Window corner radius in pixels -border_radius=0 - -# Corner radius location (0=all, 1=top-left, 2=top-right, 3=bottom-left, 4=bottom-right) -border_radius_location_default=0 - -# Disable radius if only one window is visible -no_radius_when_single=0 - -# Opacity for the active window (0.0 - 1.0) -focused_opacity=1.0 - -# Opacity for inactive windows -unfocused_opacity=1.0 -``` diff --git a/docs/v0.13.0/visuals/index.mdx b/docs/v0.13.0/visuals/index.mdx deleted file mode 100644 index f71ae2f8..00000000 --- a/docs/v0.13.0/visuals/index.mdx +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: Visuals -description: Customize borders, colors, effects, and animations. -icon: Palette ---- - -Customize the look of your desktop. - -<Cards> - -<Card href="/docs/visuals/theming" title="Theming" description="Borders, colors, and cursor" /> - -<Card href="/docs/visuals/status-bar" title="Status Bar" description="Built-in status bar" /> - -<Card href="/docs/visuals/effects" title="Effects" description="Blur, shadows, rounded corners" /> - -<Card href="/docs/visuals/animations" title="Animations" description="Window and tag animations" /> - -</Cards> \ No newline at end of file diff --git a/docs/v0.13.0/visuals/meta.json b/docs/v0.13.0/visuals/meta.json deleted file mode 100644 index 58723c4e..00000000 --- a/docs/v0.13.0/visuals/meta.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "title": "Visuals", - "pages": ["theming", "status-bar", "effects", "animations"] -} diff --git a/docs/v0.13.0/visuals/status-bar.md b/docs/v0.13.0/visuals/status-bar.md deleted file mode 100644 index f2924e83..00000000 --- a/docs/v0.13.0/visuals/status-bar.md +++ /dev/null @@ -1,141 +0,0 @@ ---- -title: Status Bar -description: Configure Waybar for mangowm. ---- - -## Module Configuration - -mangowm is compatible with Waybar's `ext/workspaces` module (Wayland standard) or the `dwl/tags` module. We recommend `ext/workspaces` for the best experience. - -> **Tip:** You can also use the `dwl/tags` module, but `ext/workspaces` provides better integration with mangowm's features. The `ext/workspaces` module requires **Waybar > 0.14.0**. - -### `config.jsonc` - -Add the following to your Waybar configuration: - -```jsonc -{ - "modules-left": [ - "ext/workspaces", - "dwl/window" - ], - "ext/workspaces": { - "format": "{icon}", - "ignore-hidden": true, - "on-click": "activate", - "on-click-right": "deactivate", - "sort-by-id": true - }, - "dwl/window": { - "format": "[{layout}] {title}" - } -} -``` - -## Styling - -You can style the tags using standard CSS in `style.css`. - -### `style.css` - -```css -#workspaces { - border-radius: 4px; - border-width: 2px; - border-style: solid; - border-color: #c9b890; - margin-left: 4px; - padding-left: 10px; - padding-right: 6px; - background: rgba(40, 40, 40, 0.76); -} - -#workspaces button { - border: none; - background: none; - box-shadow: inherit; - text-shadow: inherit; - color: #ddca9e; - padding: 1px; - padding-left: 1px; - padding-right: 1px; - margin-right: 2px; - margin-left: 2px; -} - -#workspaces button.hidden { - color: #9e906f; - background-color: transparent; -} - -#workspaces button.visible { - color: #ddca9e; -} - -#workspaces button:hover { - color: #d79921; -} - -#workspaces button.active { - background-color: #ddca9e; - color: #282828; - margin-top: 5px; - margin-bottom: 5px; - padding-top: 1px; - padding-bottom: 0px; - border-radius: 3px; -} - -#workspaces button.urgent { - background-color: #ef5e5e; - color: #282828; - margin-top: 5px; - margin-bottom: 5px; - padding-top: 1px; - padding-bottom: 0px; - border-radius: 3px; -} - -#tags { - background-color: transparent; -} - -#tags button { - background-color: #fff; - color: #a585cd; -} - -#tags button:not(.occupied):not(.focused) { - font-size: 0; - min-width: 0; - min-height: 0; - margin: -17px; - padding: 0; - color: transparent; - background-color: transparent; -} - -#tags button.occupied { - background-color: #fff; - color: #cdc885; -} - -#tags button.focused { - background-color: rgb(186, 142, 213); - color: #fff; -} - -#tags button.urgent { - background: rgb(171, 101, 101); - color: #fff; -} - -#window { - background-color: rgb(237, 196, 147); - color: rgb(63, 37, 5); -} -``` - -## Complete Configuration Example - -> **Tip:** You can find a complete Waybar configuration for mangowm at [waybar-config](https://github.com/DreamMaoMao/waybar-config). \ No newline at end of file diff --git a/docs/v0.13.0/visuals/theming.md b/docs/v0.13.0/visuals/theming.md deleted file mode 100644 index 676c575b..00000000 --- a/docs/v0.13.0/visuals/theming.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -title: Theming -description: Customize the visual appearance of borders, colors, and the cursor. ---- - -## Dimensions - -Control the sizing of window borders and gaps. - -| Setting | Default | Description | -| :--- | :--- | :--- | -| `borderpx` | `4` | Border width in pixels. | -| `gappih` | `5` | Horizontal inner gap (between windows). | -| `gappiv` | `5` | Vertical inner gap. | -| `gappoh` | `10` | Horizontal outer gap (between windows and screen edges). | -| `gappov` | `10` | Vertical outer gap. | - -## Colors - -Colors are defined in `0xRRGGBBAA` hex format. - -```ini -# Background color of the root window -rootcolor=0x323232ff - -# Inactive window border -bordercolor=0x444444ff - -# Drop shadow when dragging windows -dropcolor=0x8FBA7C55 - -# Split window border color in manual dwindle layout -splitcolor=0xEB441EFF - -# Active window border -focuscolor=0xc66b25ff - -# Urgent window border (alerts) -urgentcolor=0xad401fff -``` - -### State-Specific Colors - -You can also color-code windows based on their state: - -| State | Config Key | Default Color | -| :--- | :--- | :--- | -| Maximized | `maximizescreencolor` | `0x89aa61ff` | -| Scratchpad | `scratchpadcolor` | `0x516c93ff` | -| Global | `globalcolor` | `0xb153a7ff` | -| Overlay | `overlaycolor` | `0x14a57cff` | - -> **Tip:** For scratchpad window sizing, see [Scratchpad](/docs/window-management/scratchpad) configuration. - -## Cursor Theme - -Set the size and theme of your mouse cursor. - -```ini -cursor_size=24 -cursor_theme=Adwaita -``` diff --git a/docs/v0.13.0/window-management/index.mdx b/docs/v0.13.0/window-management/index.mdx deleted file mode 100644 index b96c5891..00000000 --- a/docs/v0.13.0/window-management/index.mdx +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: Window Management -description: Layouts, rules, and window behavior. -icon: LayoutGrid ---- - -Window management with layouts, rules, and scratchpad support. - -<Cards> - -<Card href="/docs/window-management/layouts" title="Layouts" description="Tile, scroller, monocle, grid, deck" /> - -<Card href="/docs/window-management/rules" title="Rules" description="Window rules and conditions" /> - -<Card href="/docs/window-management/overview" title="Overview" description="Window states and properties" /> - -<Card href="/docs/window-management/scratchpad" title="Scratchpad" description="Quick access to applications" /> - -</Cards> \ No newline at end of file diff --git a/docs/v0.13.0/window-management/layouts.md b/docs/v0.13.0/window-management/layouts.md deleted file mode 100644 index bf5283d7..00000000 --- a/docs/v0.13.0/window-management/layouts.md +++ /dev/null @@ -1,133 +0,0 @@ ---- -title: Layouts -description: Configure and switch between different window layouts. ---- - -## Supported Layouts - -mangowm supports a variety of layouts that can be assigned per tag. - -- `tile` -- `scroller` -- `monocle` -- `grid` -- `deck` -- `center_tile` -- `vertical_tile` -- `right_tile` -- `vertical_scroller` -- `vertical_grid` -- `vertical_deck` -- `dwindle` - ---- - -## Scroller Layout - -The Scroller layout positions windows in a scrollable strip, similar to PaperWM. - -### Configuration - -| Setting | Default | Description | -| :--- | :--- | :--- | -| `scroller_structs` | `20` | Width reserved on sides when window ratio is 1. | -| `scroller_default_proportion` | `0.9` | Default width proportion for new windows. | -| `scroller_focus_center` | `0` | Always center the focused window (1 = enable). | -| `scroller_prefer_center` | `0` | Center focused window only if it was outside the view. | -| `scroller_prefer_overspread` | `1` | Allow windows to overspread when there's extra space. | -| `edge_scroller_pointer_focus` | `1` | Focus windows even if partially off-screen. | -| `scroller_proportion_preset` | `0.5,0.8,1.0` | Presets for cycling window widths. | -| `scroller_ignore_proportion_single` | `1` | Ignore proportion adjustments for single windows. | -| `scroller_default_proportion_single` | `1.0` | Default proportion for single windows in scroller. **Requires `scroller_ignore_proportion_single=0` to take effect.** | - -> **Warning:** `scroller_prefer_overspread`, `scroller_focus_center`, and `scroller_prefer_center` interact with each other. Their priority order is: -> -> **scroller_prefer_overspread > scroller_focus_center > scroller_prefer_center** -> -> To ensure a lower-priority setting takes effect, you must set all higher-priority options to `0`. - -```ini -# Example scroller configuration -scroller_structs=20 -scroller_default_proportion=0.9 -scroller_focus_center=0 -scroller_prefer_center=0 -scroller_prefer_overspread=1 -edge_scroller_pointer_focus=1 -scroller_default_proportion_single=1.0 -scroller_proportion_preset=0.5,0.8,1.0 -``` - ---- - -## Master-Stack Layouts - -These settings apply to layouts like `tile` and `center_tile`. - -| Setting | Default | Description | -| :--- | :--- | :--- | -| `new_is_master` | `1` | New windows become the master window. | -| `default_mfact` | `0.55` | The split ratio between master and stack areas. | -| `default_nmaster` | `1` | Number of allowed master windows. | -| `smartgaps` | `0` | Disable gaps when only one window is present. | -| `center_master_overspread` | `0` | (Center Tile) Master spreads across screen if no stack exists. | -| `center_when_single_stack` | `1` | (Center Tile) Center master when only one stack window exists. | - -```ini -# Example master-stack configuration -new_is_master=1 -smartgaps=0 -default_mfact=0.55 -default_nmaster=1 -``` - ---- - -## Dwindle Layout - -The Dwindle layout arranges windows as a binary tree of recursive splits. Each new window splits the focused window's container, producing a spiral-like tiling. - -### Configuration - -| Setting | Default | Description | -| :--- | :--- | :--- | -| `dwindle_split_ratio` | `0.5` | Ratio used for new splits (`0.05`–`0.95`). | -| `dwindle_smart_split` | `0` | Pick the split axis from the cursor's position inside the focused window. The new window appears on the cursor's side. | -| `dwindle_hsplit` | `1` | Side-by-side splits: where the new window goes. `0` = follow cursor, `1` = right, `2` = left. | -| `dwindle_vsplit` | `1` | Top/bottom splits: where the new window goes. `0` = follow cursor, `1` = below, `2` = above. | -| `dwindle_preserve_split` | `0` | Keep the sibling's split orientation when a window is closed. | -| `dwindle_smart_resize` | `0` | When dragging to resize, move the split toward the cursor regardless of which side was grabbed. | -| `dwindle_drop_simple_split` | `1` | Drag-to-tile drop preview. `1` = 2-zone preview matching `dwindle_split_ratio`, `0` = 4-quadrant preview. | -| `dwindle_manual_split` | `0` | Manually split windows mode. | - -```ini -# Example dwindle configuration -dwindle_split_ratio=0.5 -dwindle_smart_split=0 -dwindle_hsplit=0 -dwindle_vsplit=0 -dwindle_preserve_split=0 -dwindle_smart_resize=0 -dwindle_drop_simple_split=1 -``` - ---- - -## Switching Layouts -| Setting | Default | Description | -| :--- | :--- | :--- | -| `circle_layout` | - | A comma-separated list of layouts `switch_layout` cycles through,the value sample:`tile,scroller`. | - -You can switch layouts dynamically or set a default for specific tags using [Tag Rules](/docs/window-management/rules#tag-rules). - -**Keybinding Examples:** - -```ini -# Cycle through layouts -circle_layout=grid,scroller,tile -bind=SUPER,n,switch_layout - -# Set specific layout -bind=SUPER,t,setlayout,tile -bind=SUPER,s,setlayout,scroller -``` diff --git a/docs/v0.13.0/window-management/meta.json b/docs/v0.13.0/window-management/meta.json deleted file mode 100644 index e0937d14..00000000 --- a/docs/v0.13.0/window-management/meta.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "title": "Window Management", - "pages": ["layouts", "rules", "overview", "scratchpad"] -} diff --git a/docs/v0.13.0/window-management/overview.md b/docs/v0.13.0/window-management/overview.md deleted file mode 100644 index 7da6e690..00000000 --- a/docs/v0.13.0/window-management/overview.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -title: Overview -description: Configure the overview mode for window navigation. ---- - -## Overview Settings - -| Setting | Type | Default | Description | -| :--- | :--- | :--- | :--- | -| `hotarea_size` | integer | `10` | Hot area size in pixels. | -| `enable_hotarea` | integer | `1` | Enable hot areas (0: disable, 1: enable). | -| `hotarea_corner` | integer | `2` | Hot area corner (0: top-left, 1: top-right, 2: bottom-left, 3: bottom-right). | -| `ov_tab_mode` | integer | `0` | Overview tab mode (0: disable, 1: enable). | -| `overviewgappi` | integer | `5` | Inner gap in overview mode. | -| `overviewgappo` | integer | `30` | Outer gap in overview mode. | - -### Setting Descriptions - -- `enable_hotarea` — Toggles overview when the cursor enters the configured corner. -- `hotarea_size` — Size of the hot area trigger zone in pixels. -- `hotarea_corner` — Corner that triggers the hot area (0: top-left, 1: top-right, 2: bottom-left, 3: bottom-right). -- `ov_tab_mode` — Circles focus through windows in overview; releasing the mod key exits overview. - -### Mouse Interaction in Overview - -When in overview mode: - -- **Left mouse button** — Jump to (focus) a window. -- **Right mouse button** — Close a window. \ No newline at end of file diff --git a/docs/v0.13.0/window-management/rules.md b/docs/v0.13.0/window-management/rules.md deleted file mode 100644 index 4a295157..00000000 --- a/docs/v0.13.0/window-management/rules.md +++ /dev/null @@ -1,250 +0,0 @@ ---- -title: Rules -description: Define behavior for specific windows, tags, and layers. ---- - -## Window Rules - -Window rules allow you to set specific properties (floating, opacity, size, animations, etc.) for applications based on their `appid` or `title`. You can set all parameters in one line, and if you both set appid and title, the window will only follow the rules when appid and title both match. - -**Format:** - -```ini -windowrule=Parameter:Values,title:Values -windowrule=Parameter:Values,Parameter:Values,appid:Values,title:Values -``` - -### State & Behavior Parameters - -| Parameter | Type | Values | Description | -| :--- | :--- | :--- | :--- | -| `appid` | string | Any | Match by application ID, supports regex | -| `title` | string | Any | Match by window title, supports regex | -| `isfloating` | integer | `0` / `1` | Force floating state | -| `isfullscreen` | integer | `0` / `1` | Force fullscreen state | -| `isfakefullscreen` | integer | `0` / `1` | Force fake-fullscreen state (window stays constrained) | -| `isglobal` | integer | `0` / `1` | Open as global window (sticky across tags) | -| `isoverlay` | integer | `0` / `1` | Make it always in top layer | -| `isopensilent` | integer | `0` / `1` | Open without focus | -| `istagsilent` | integer | `0` / `1` | Don't focus if client is not in current view tag | -| `force_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 | -| `noopenmaximized` | integer | `0` / `1` | Window does not open as maximized mode | -| `single_scratchpad` | integer | `0` / `1` (default 1) | Only show one out of named scratchpads or the normal scratchpad | -| `allow_shortcuts_inhibit` | integer | `0` / `1` (default 1) | Allow shortcuts to be inhibited by clients | -| `indleinhibit_when_focus` | integer | `0` / `1` (default 0) | Automatically keep idle inhibit active when this window is focused | - -### Geometry & Position - -| Parameter | Type | Values | Description | -| :--- | :--- | :--- | :--- | -| `width` | float | 0-9999 | Window width when it becomes a floating window,if the value below 1, it will be the percentage of the screen width,otherwise it will be the pixel value | -| `height` | float | 0-9999 | Window height when it becomes a floating window,if the value below 1, it will be the percentage of the screen height,otherwise it will be the pixel value | -| `offsetx` | integer | -999-999 | X offset from center (%), 100 is the edge of screen with outer gap | -| `offsety` | integer | -999-999 | Y offset from center (%), 100 is the edge of screen with outer gap | -| `monitor` | string | Any | Assign to monitor by [monitor spec](/docs/configuration/monitors#monitor-spec-format) (name, make, model, or serial) | -| `tags` | integer | 1-9 | Assign to specific tag | -| `no_force_center` | integer | `0` / `1` | Window does not force center | -| `isnosizehint` | integer | `0` / `1` | Don't use min size and max size for size hints | - -### Visuals & Decoration - -| Parameter | Type | Values | Description | -| :--- | :--- | :--- | :--- | -| `noblur` | integer | `0` / `1` | Window does not have blur effect | -| `isnoborder` | integer | `0` / `1` | Remove window border | -| `isnoshadow` | integer | `0` / `1` | Not apply shadow | -| `isnoradius` | integer | `0` / `1` | Not apply corner radius | -| `isnoanimation` | integer | `0` / `1` | Not apply animation | -| `focused_opacity` | integer | `0` / `1` | Window focused opacity | -| `unfocused_opacity` | integer | `0` / `1` | Window unfocused opacity | -| `allow_csd` | integer | `0` / `1` | Allow client side decoration | - -> **Tip:** For detailed visual effects configuration, see the [Window Effects](/docs/visuals/effects) page for blur, shadows, and opacity settings. - -### Layout & Scroller - -| Parameter | Type | Values | Description | -| :--- | :--- | :--- | :--- | -| `scroller_proportion` | float | 0.1-1.0 | Set scroller proportion | -| `scroller_proportion_single` | float | 0.1-1.0 | Set scroller auto adjust proportion when it is single window | - -> **Tip:** For comprehensive layout configuration, see the [Layouts](/docs/window-management/layouts) page for all layout options and detailed settings. - -### Animation - -| Parameter | Type | Values | Description | -| :--- | :--- | :--- | :--- | -| `animation_type_open` | string | zoom, slide, fade, none | Set open animation | -| `animation_type_close` | string | zoom, slide, fade, none | Set close animation | -| `nofadein` | integer | `0` / `1` | Window ignores fade-in animation | -| `nofadeout` | integer | `0` / `1` | Window ignores fade-out animation | - -> **Tip:** For detailed animation configuration, see the [Animations](/docs/visuals/animations) page for available types and settings. - -### Terminal & Swallowing - -| Parameter | Type | Values | Description | -| :--- | :--- | :--- | :--- | -| `isterm` | integer | `0` / `1` | A new GUI window will replace the isterm window when it is opened | -| `noswallow` | integer | `0` / `1` | The window will not replace the isterm window | - -### Global & Special Windows - -| Parameter | Type | Values | Description | -| :--- | :--- | :--- | :--- | -| `globalkeybinding` | string | `[mod combination][-][key]` | Global keybinding (only works for Wayland apps) | -| `isunglobal` | integer | `0` / `1` | Open as unmanaged global window (for desktop pets or camera windows) | -| `isnamedscratchpad` | integer | `0` / `1` | 0: disable, 1: named scratchpad | - -> **Tip:** For scratchpad usage, see the [Scratchpad](/docs/window-management/scratchpad) page for detailed configuration examples. - -### Performance & Tearing - -| Parameter | Type | Values | Description | -| :--- | :--- | :--- | :--- | -| `force_tearing` | integer | `0` / `1` | Set window to tearing state, refer to [Tearing](/docs/configuration/monitors#tearing-game-mode) | - -### Examples - -```ini -# Set specific window size and position -windowrule=width:1000,height:900,appid:yesplaymusic,title:Demons - -# Global keybindings for OBS Studio -windowrule=globalkeybinding:ctrl+alt-o,appid:com.obsproject.Studio -windowrule=globalkeybinding:ctrl+alt+n,appid:com.obsproject.Studio -windowrule=isopensilent:1,appid:com.obsproject.Studio - -# Force tearing for games -windowrule=force_tearing:1,title:vkcube -windowrule=force_tearing:1,title:Counter-Strike 2 - -# Named scratchpad for file manager -windowrule=isnamedscratchpad:1,width:1280,height:800,appid:st-yazi - -# Custom opacity for specific apps -windowrule=focused_opacity:0.8,appid:firefox -windowrule=unfocused_opacity:0.6,appid:foot - -# Disable blur for selection tools -windowrule=noblur:1,appid:slurp - -# Position windows relative to screen center -windowrule=offsetx:20,offsety:-30,width:800,height:600,appid:alacritty - -# Send to specific tag and monitor -windowrule=tags:9,monitor:HDMI-A-1,appid:discord - -# Terminal swallowing setup -windowrule=isterm:1,appid:st -windowrule=noswallow:1,appid:foot - -# Disable client-side decorations -windowrule=allow_csd:1,appid:firefox - -# Unmanaged global window (desktop pets, camera) -windowrule=isunglobal:1,appid:cheese - -# Named scratchpad toggle -bind=alt,h,toggle_named_scratchpad,st-yazi,none,st -c st-yazi -e yazi -``` - ---- - -## Tag Rules - -You can set all parameters in one line. If only `id` is set, the rule is followed when the id matches. If any of `monitor_name`, `monitor_make`, `monitor_model`, or `monitor_serial` are set, the rule is followed only if **all** of the set monitor fields match. - -> **Warning:** Layouts set in tag rules have a higher priority than monitor rule layouts. - -**Format:** - -```ini -tagrule=id:Values,Parameter:Values,Parameter:Values -tagrule=id:Values,monitor_name:eDP-1,Parameter:Values,Parameter:Values -tagrule=id:Values,monitor_make:xxx,monitor_model:xxx,Parameter:Values -``` - -> **Tip:** See [Layouts](/docs/window-management/layouts#supported-layouts) for detailed descriptions of each layout type. - -| Parameter | Type | Values | Description | -| :--- | :--- | :--- | :--- | -| `id` | integer | 0-9 | Match by tag id, 0 means the ~0 tag | -| `monitor_name` | string | monitor name | Match by monitor name | -| `monitor_make` | string | monitor make | Match by monitor manufacturer | -| `monitor_model` | string | monitor model | Match by monitor model | -| `monitor_serial` | string | monitor serial | Match by monitor serial number | -| `layout_name` | string | layout name | Layout name to set | -| `no_render_border` | integer | `0` / `1` | Disable render border | -| `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 | - -### Examples - -```ini -# Set layout for specific tags -tagrule=id:1,layout_name:scroller -tagrule=id:2,layout_name:scroller - -# Limit to specific monitor -tagrule=id:1,monitor_name:eDP-1,layout_name:scroller -tagrule=id:2,monitor_name:eDP-1,layout_name:scroller - -# Persistent tags (1-4) with layout assignment -tagrule=id:1,no_hide:1,layout_name:scroller -tagrule=id:2,no_hide:1,layout_name:scroller -tagrule=id:3,monitor_name:eDP-1,no_hide:1,layout_name:scroller -tagrule=id:4,monitor_name:eDP-1,no_hide:1,layout_name:scroller - -# Advanced tag configuration with master layout settings -tagrule=id:5,layout_name:tile,nmaster:2,mfact:0.6 -tagrule=id:6,monitor_name:HDMI-A-1,layout_name:monocle,no_render_border:1 -``` - -> **Tip:** For Waybar configuration with persistent tags, see [Status Bar](/docs/visuals/status-bar) documentation. - ---- - -## Layer Rules - -You can set all parameters in one line. Target "layer shell" surfaces like status bars (`waybar`), launchers (`rofi`), or notification daemons. - -**Format:** - -```ini -layerrule=layer_name:Values,Parameter:Values,Parameter:Values -``` - -> **Tip:** You can use `mmsg -e` to get the last open layer name for debugging. - -| Parameter | Type | Values | Description | -| :--- | :--- | :--- | :--- | -| `layer_name` | string | layer name | Match name of layer, supports regex | -| `animation_type_open` | string | slide, zoom, fade, none | Set open animation | -| `animation_type_close` | string | slide, zoom, fade, none | Set close animation | -| `noblur` | integer | `0` / `1` | Disable blur | -| `noanim` | integer | `0` / `1` | Disable layer animation | -| `noshadow` | integer | `0` / `1` | Disable layer shadow | - -> **Tip:** For animation types, see [Animations](/docs/visuals/animations#animation-types). For visual effects, see [Window Effects](/docs/visuals/effects). - -### Examples - -```ini -# No blur or animation for slurp selection layer (avoids occlusion and ghosting in screenshots) -layerrule=noanim:1,noblur:1,layer_name:selection - -# Zoom animation for Rofi with multiple parameters -layerrule=animation_type_open:zoom,noanim:0,layer_name:rofi - -# Disable animations and shadows for notification daemon -layerrule=noanim:1,noshadow:1,layer_name:swaync - -# Multiple effects for launcher -layerrule=animation_type_open:slide,animation_type_close:fade,noblur:1,layer_name:wofi -``` diff --git a/docs/v0.13.0/window-management/scratchpad.md b/docs/v0.13.0/window-management/scratchpad.md deleted file mode 100644 index 398182f9..00000000 --- a/docs/v0.13.0/window-management/scratchpad.md +++ /dev/null @@ -1,73 +0,0 @@ ---- -title: Scratchpad -description: Manage hidden "scratchpad" windows for quick access. ---- - -mangowm supports two types of scratchpads: the standard pool (Sway-like) and named scratchpads. - -## Standard Scratchpad - -Any window can be sent to the "scratchpad" pile, which hides it. You can then cycle through them. - -**Keybindings:** - -```ini -# Send current window to scratchpad -bind=SUPER,i,minimized - -# Toggle (show/hide) the scratchpad -bind=ALT,z,toggle_scratchpad - -# Retrieve window from scratchpad (restore) -bind=SUPER+SHIFT,i,restore_minimized -``` - ---- - -## Named Scratchpad - -Named scratchpads are bound to specific keys and applications. When triggered, mangowm will either launch the app (if not running) or toggle its visibility. - -**1. Define the Window Rule** - -You must identify the app using a unique `appid` or `title` and mark it as a named scratchpad. The application must support setting a custom appid or title at launch. Common examples: - -- `st -c my-appid` — sets the appid -- `kitty -T my-title` — sets the window title -- `foot --app-id my-appid` — sets the appid - -Use `none` as a placeholder when you only want to match by one field. - -```ini -# Match by appid -windowrule=isnamedscratchpad:1,width:1280,height:800,appid:st-yazi - -# Match by title -windowrule=isnamedscratchpad:1,width:1000,height:700,title:kitty-scratch -``` - -**2. Bind the Toggle Key** - -Format: `bind=MOD,KEY,toggle_named_scratchpad,appid,title,command` - -Use `none` for whichever field you are not matching on. - -```ini -# Match by appid: launch 'st' with class 'st-yazi' running 'yazi' -bind=alt,h,toggle_named_scratchpad,st-yazi,none,st -c st-yazi -e yazi - -# Match by title: launch 'kitty' with window title 'kitty-scratch' -bind=alt,k,toggle_named_scratchpad,none,kitty-scratch,kitty -T kitty-scratch -``` - ---- - -## Appearance - -You can customize the size of scratchpad windows relative to the screen. - -```ini -scratchpad_width_ratio=0.8 -scratchpad_height_ratio=0.9 -scratchpadcolor=0x516c93ff -``` diff --git a/docs/v0.13.1/bindings/index.mdx b/docs/v0.13.1/bindings/index.mdx deleted file mode 100644 index 4c3a5bda..00000000 --- a/docs/v0.13.1/bindings/index.mdx +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: Bindings & Input -description: Keybindings, mouse gestures, and input devices. -icon: Keyboard ---- - -Configure how you interact with mangowm using flexible keybindings and input options. - -<Cards> - -<Card href="/docs/bindings/keys" title="Key Bindings" description="Keyboard shortcuts and modes" /> - -<Card href="/docs/bindings/mouse-gestures" title="Mouse Gestures" description="Touchpad and mouse gestures" /> - -</Cards> diff --git a/docs/v0.13.1/bindings/keys.md b/docs/v0.13.1/bindings/keys.md deleted file mode 100644 index 002c9564..00000000 --- a/docs/v0.13.1/bindings/keys.md +++ /dev/null @@ -1,216 +0,0 @@ ---- -title: Key Bindings -description: Define keyboard shortcuts and modes. ---- - -## Syntax - -Key bindings follow this format: - -```ini -bind[flags]=MODIFIERS,KEY,COMMAND,PARAMETERS -``` - -- **Modifiers**: `SUPER`, `CTRL`, `ALT`, `SHIFT`, `NONE` (combine with `+`, e.g. `SUPER+CTRL+ALT`). -- **Key**: Key name (from `xev` or `wev`) or keycode (e.g., `code:24` for `q`). - -> **Info:** `bind` automatically converts keysym to keycode for comparison. This makes it compatible with all keyboard layouts, but the matching may not always be precise. If a key combination doesn't work on your keyboard layout, use a keycode instead (e.g., `code:24` instead of `q`). - -### Flags - -- `l`: Works even when screen is locked. -- `s`: Uses keysym instead of keycode to bind. -- `r`: Triggers on key release instead of press. -- `p`: Pass key event to client. - -**Examples:** - -```ini -bind=SUPER,Q,killclient -bindl=SUPER,L,spawn,swaylock - -# Using keycode instead of key name -bind=ALT,code:24,killclient - -# Combining keycodes for modifiers and keys -bind=code:64,code:24,killclient -bind=code:64+code:133,code:24,killclient - -# Bind with no modifier -bind=NONE,XF86MonBrightnessUp,spawn,brightnessctl set +5% - -# Bind a modifier key itself as the trigger key -bind=alt,shift_l,switch_keyboard_layout -``` - -## Key Modes (Submaps) - -You can divide key bindings into named modes. Rules: - -1. Set `keymode=<name>` before a group of `bind` lines — those binds only apply in that mode. -2. If no `keymode` is set before a bind, it belongs to the `default` mode. -3. The special `common` keymode applies its binds **across all modes**. - -Use `setkeymode` to switch modes, and `mmsg -b` to query the current mode. - -```ini -# Binds in 'common' apply in every mode -keymode=common -bind=SUPER,r,reload_config - -# Default mode bindings -keymode=default -bind=ALT,Return,spawn,foot -bind=SUPER,F,setkeymode,resize - -# 'resize' mode bindings -keymode=resize -bind=NONE,Left,resizewin,-10,0 -bind=NONE,Right,resizewin,+10,0 -bind=NONE,Escape,setkeymode,default -``` - -### Single Modifier Key Binding - -When binding a modifier key itself, use `NONE` for press and the modifier name for release: - -```ini -# Trigger on press of Super key -bind=none,Super_L,spawn,rofi -show run - -# Trigger on release of Super key -bindr=Super,Super_L,spawn,rofi -show run -``` - -## Dispatchers List - -### Window Management - -| Command | Param | Description | -| :--- | :--- | :--- | -| `killclient` | - | Close the focused window. | -| `togglefloating` | - | Toggle floating state. | -| `toggle_all_floating` | - | Toggle all visible clients floating state. | -| `togglefullscreen` | - | Toggle fullscreen. | -| `togglefakefullscreen` | - | Toggle "fake" fullscreen (remains constrained). | -| `togglemaximizescreen` | - | Maximize window (keep decoration/bar). | -| `toggleglobal` | - | Pin window to all tags. | -| `toggle_render_border` | - | Toggle border rendering. | -| `centerwin` | - | Center the floating window. | -| `minimized` | - | Minimize window to scratchpad. | -| `restore_minimized` | - | Restore window from scratchpad. | -| `toggle_scratchpad` | - | Toggle scratchpad. | -| `toggle_named_scratchpad` | `appid,title,cmd` | Toggle named scratchpad. Launches app if not running, otherwise shows/hides it. | - -### Focus & Movement - -| Command | Param | Description | -| :--- | :--- | :--- | -| `focusdir` | `left/right/up/down` | Focus window in direction. | -| `focusstack` | `next/prev` | Cycle focus within the stack. | -| `focuslast` | - | Focus the previously active window. | -| `exchange_client` | `left/right/up/down` | Swap window with neighbor in direction. | -| `exchange_stack_client` | `next/prev` | Exchange window position in stack. | -| `zoom` | - | Swap focused window with Master. | - -### Tags & Monitors - -| Command | Param | Description | -| :--- | :--- | :--- | -| `view` | `-1/0/1-9` or `mask [,synctag]` | View tag. `-1` = previous tagset, `0` = all tags, `1-9` = specific tag, mask e.g. `1\|3\|5`. Optional `synctag` (0/1) syncs the action to all monitors. | -| `viewtoleft` | `[synctag]` | View previous tag. Optional `synctag` (0/1) syncs to all monitors. | -| `viewtoright` | `[synctag]` | View next tag. Optional `synctag` (0/1) syncs to all monitors. | -| `viewtoleft_have_client` | `[synctag]` | View left tag and focus client if present. Optional `synctag` (0/1). | -| `viewtoright_have_client` | `[synctag]` | View right tag and focus client if present. Optional `synctag` (0/1). | -| `viewcrossmon` | `tag,monitor_spec` | View specified tag on specified monitor. | -| `tag` | `1-9 [,synctag]` | Move window to tag. Optional `synctag` (0/1) syncs to all monitors. | -| `tagsilent` | `1-9` | Move window to tag without focusing it. | -| `tagtoleft` | `[synctag]` | Move window to left tag. Optional `synctag` (0/1). | -| `tagtoright` | `[synctag]` | Move window to right tag. Optional `synctag` (0/1). | -| `tagcrossmon` | `tag,monitor_spec` | Move window to specified tag on specified monitor. | -| `toggletag` | `0-9` | Toggle tag on window (0 means all tags). | -| `toggleview` | `1-9` | Toggle tag view. | -| `comboview` | `1-9` | View multi tags pressed simultaneously. | -| `focusmon` | `left/right/up/down/monitor_spec` | Focus monitor by direction or [monitor spec](/docs/configuration/monitors#monitor-spec-format). | -| `tagmon` | `left/right/up/down/monitor_spec,[keeptag]` | Move window to monitor by direction or [monitor spec](/docs/configuration/monitors#monitor-spec-format). `keeptag` is 0 or 1. | - -### Layouts - -| Command | Param | Description | -| :--- | :--- | :--- | -| `setlayout` | `name` | Switch to layout (e.g., `scroller`, `tile`). | -| `switch_layout` | - | Cycle through available layouts. | -| `incnmaster` | `+1/-1` | Increase/Decrease number of master windows. | -| `setmfact` | `+0.05` | Increase/Decrease master area size. | -| `set_proportion` | `float` | Set scroller window proportion (0.0–1.0). | -| `switch_proportion_preset` | - | Cycle proportion presets of scroller window. | -| `scroller_stack` | `left/right/up/down` | Move window inside/outside scroller stack by direction. | -| `incgaps` | `+/-value` | Adjust gap size. | -| `togglegaps` | - | Toggle gaps. | -| `dwindle_toggle_split_direction` | - | Toggle split direction in dwindle layout. | - -### System - -| Command | Param | Description | -| :--- | :--- | :--- | -| `spawn` | `cmd` | Execute a command. | -| `spawn_shell` | `cmd` | Execute shell command (supports pipes `\|`). | -| `spawn_on_empty` | `cmd,tagnumber` | Open command on empty tag. | -| `reload_config` | - | Hot-reload configuration. | -| `quit` | - | Exit mangowm. | -| `toggleoverview` | - | Toggle overview mode. | -| `create_virtual_output` | - | Create a headless monitor (for VNC/Sunshine). | -| `destroy_all_virtual_output` | - | Destroy all virtual monitors. | -| `toggleoverlay` | - | Toggle overlay state for the focused window. | -| `toggle_trackpad_enable` | - | Toggle trackpad enable. | -| `setkeymode` | `mode` | Set keymode. | -| `switch_keyboard_layout` | `[index]` | Switch keyboard layout. Optional index (0, 1, 2...) to switch to specific layout. | -| `setoption` | `key,value` | Set config option temporarily. | -| `disable_monitor` | `monitor_spec` | Shutdown monitor. Accepts a [monitor spec](/docs/configuration/monitors#monitor-spec-format). | -| `enable_monitor` | `monitor_spec` | Power on monitor. Accepts a [monitor spec](/docs/configuration/monitors#monitor-spec-format). | -| `toggle_monitor` | `monitor_spec` | Toggle monitor power. Accepts a [monitor spec](/docs/configuration/monitors#monitor-spec-format). | - -### Media Controls - -> **Warning:** Some keyboards don't send standard media keys. Run `wev` and press your key to check the exact key name. - -#### Brightness - -Requires: `brightnessctl` - -```ini -bind=NONE,XF86MonBrightnessUp,spawn,brightnessctl s +2% -bind=SHIFT,XF86MonBrightnessUp,spawn,brightnessctl s 100% -bind=NONE,XF86MonBrightnessDown,spawn,brightnessctl s 2%- -bind=SHIFT,XF86MonBrightnessDown,spawn,brightnessctl s 1% -``` - -#### Volume - -Requires: `wpctl` (WirePlumber) - -```ini -bind=NONE,XF86AudioRaiseVolume,spawn,wpctl set-volume @DEFAULT_SINK@ 5%+ -bind=NONE,XF86AudioLowerVolume,spawn,wpctl set-volume @DEFAULT_SINK@ 5%- -bind=NONE,XF86AudioMute,spawn,wpctl set-mute @DEFAULT_SINK@ toggle -bind=SHIFT,XF86AudioMute,spawn,wpctl set-mute @DEFAULT_SOURCE@ toggle -``` - -#### 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 | -| :--- | :--- | :--- | -| `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. | diff --git a/docs/v0.13.1/bindings/meta.json b/docs/v0.13.1/bindings/meta.json deleted file mode 100644 index f1b629b6..00000000 --- a/docs/v0.13.1/bindings/meta.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "title": "Bindings & Input", - "pages": ["keys", "mouse-gestures"] -} diff --git a/docs/v0.13.1/bindings/mouse-gestures.md b/docs/v0.13.1/bindings/mouse-gestures.md deleted file mode 100644 index c4a36889..00000000 --- a/docs/v0.13.1/bindings/mouse-gestures.md +++ /dev/null @@ -1,116 +0,0 @@ ---- -title: Mouse & Gestures -description: Configure mouse buttons, scrolling, gestures, and lid switches. ---- - -## Mouse Bindings - -Assign actions to mouse button presses with optional modifier keys. - -### Syntax - -```ini -mousebind=MODIFIERS,BUTTON,COMMAND,PARAMETERS -``` - -- **Modifiers**: `SUPER`, `CTRL`, `ALT`, `SHIFT`, `NONE`. Combine with `+` (e.g., `SUPER+CTRL`) -- **Buttons**: `btn_left`, `btn_right`, `btn_middle`, `btn_side`, `btn_extra`, `btn_forward`, `btn_back`, `btn_task` - -> **Warning:** When modifiers are set to `NONE`, only `btn_middle` works in normal mode. `btn_left` and `btn_right` only work in overview mode. - -### Examples - -```ini -# Window manipulation -mousebind=SUPER,btn_left,moveresize,curmove -mousebind=SUPER,btn_right,moveresize,curresize -mousebind=SUPER+CTRL,btn_right,killclient - -# Overview mode (requires NONE modifier) -mousebind=NONE,btn_left,toggleoverview,-1 -mousebind=NONE,btn_right,killclient,0 -mousebind=NONE,btn_middle,togglemaximizescreen,0 -``` - ---- - -## Axis Bindings - -Map scroll wheel movements to actions for workspace and window navigation. - -### Syntax - -```ini -axisbind=MODIFIERS,DIRECTION,COMMAND,PARAMETERS -``` - -- **Direction**: `UP`, `DOWN`, `LEFT`, `RIGHT` - -### Examples - -```ini -axisbind=SUPER,UP,viewtoleft_have_client -axisbind=SUPER,DOWN,viewtoright_have_client -``` - ---- - -## Gesture Bindings - -Enable touchpad swipe gestures for navigation and window management. - -### Syntax - -```ini -gesturebind=MODIFIERS,DIRECTION,FINGERS,COMMAND,PARAMETERS -``` - -- **Direction**: `up`, `down`, `left`, `right` -- **Fingers**: `3` or `4` - -> **Info:** Gestures require proper touchpad configuration. See [Input Devices](/docs/configuration/input) for touchpad settings like `tap_to_click` and `disable_while_typing`. - -### Examples - -```ini -# 3-finger: Window focus -gesturebind=none,left,3,focusdir,left -gesturebind=none,right,3,focusdir,right -gesturebind=none,up,3,focusdir,up -gesturebind=none,down,3,focusdir,down - -# 4-finger: Workspace navigation -gesturebind=none,left,4,viewtoleft_have_client -gesturebind=none,right,4,viewtoright_have_client -gesturebind=none,up,4,toggleoverview -gesturebind=none,down,4,toggleoverview -``` - ---- - -## Switch Bindings - -Trigger actions on hardware events like laptop lid open/close. - -### Syntax - -```ini -switchbind=FOLD_STATE,COMMAND,PARAMETERS -``` - -- **Fold State**: `fold` (lid closed), `unfold` (lid opened) - -> **Warning:** Disable system lid handling in `/etc/systemd/logind.conf`: -> -> ```ini -> HandleLidSwitch=ignore -> HandleLidSwitchExternalPower=ignore -> HandleLidSwitchDocked=ignore -> ``` - -### Examples - -```ini -switchbind=fold,spawn,swaylock -f -c 000000 -switchbind=unfold,spawn,wlr-dpms on -``` diff --git a/docs/v0.13.1/configuration/basics.md b/docs/v0.13.1/configuration/basics.md deleted file mode 100644 index 7afa343b..00000000 --- a/docs/v0.13.1/configuration/basics.md +++ /dev/null @@ -1,87 +0,0 @@ ---- -title: Basic Configuration -description: Learn how to configure mangowm files, environment variables, and autostart scripts. ---- - -## Configuration File - -mangowm uses a simple configuration file format. By default, it looks for a configuration file in `~/.config/mango/`. - -1. **Locate Default Config** - - A fallback configuration is provided at `/etc/mango/config.conf`. You can use this as a reference. - -2. **Create User Config** - - Copy the default config to your local config directory to start customizing. - - ```bash - mkdir -p ~/.config/mango - cp /etc/mango/config.conf ~/.config/mango/config.conf - ``` - -3. **Launch with Custom Config (Optional)** - - If you prefer to keep your config elsewhere, you can launch mango with the `-c` flag. - - ```bash - mango -c /path/to/your_config.conf - ``` - -### Sub-Configuration - -To keep your configuration organized, you can split it into multiple files and include them using the `source` keyword. - -```ini -# Import keybindings from a separate file -source=~/.config/mango/bind.conf - -# Relative paths work too -source=./theme.conf - -# Optional: ignore if file doesn't exist (useful for shared configs) -source-optional=~/.config/mango/optional.conf -``` - -### Validate Configuration - -You can check your configuration for errors without starting mangowm: - -```bash -mango -c /path/to/config.conf -p -``` - -Use with `source-optional` for shared configs across different setups. - -## Environment Variables - -You can define environment variables directly within your config file. These are set before the window manager fully initializes. - -> **Warning:** Environment variables defined here will be **reset** every time you reload the configuration. - -```ini -env=QT_IM_MODULES,wayland;fcitx -env=XMODIFIERS,@im=fcitx -``` - -## Autostart - -mangowm can automatically run commands or scripts upon startup. There are two modes for execution: - -| Command | Behavior | Usage Case | -| :--- | :--- | :--- | -| `exec-once` | Runs **only once** when mangowm starts. | Status bars, Wallpapers, Notification daemons | -| `exec` | Runs **every time** the config is reloaded. | Scripts that need to refresh settings | - -### Example Setup - -```ini -# Start the status bar once -exec-once=waybar - -# Set wallpaper -exec-once=swaybg -i ~/.config/mango/wallpaper/room.png - -# Reload a custom script on config change -exec=bash ~/.config/mango/reload-settings.sh -``` diff --git a/docs/v0.13.1/configuration/index.mdx b/docs/v0.13.1/configuration/index.mdx deleted file mode 100644 index 2bcd3a7e..00000000 --- a/docs/v0.13.1/configuration/index.mdx +++ /dev/null @@ -1,21 +0,0 @@ ---- -title: Configuration -description: Configure mangowm with config files, environment variables, and autostart. -icon: Settings ---- - -Configure mangowm through config files, environment variables, and autostart. - -<Cards> - -<Card href="/docs/configuration/basics" title="Basics" description="Config files, env vars, exec-once, exec" /> - -<Card href="/docs/configuration/monitors" title="Monitors" description="Monitor settings and resolution" /> - -<Card href="/docs/configuration/input" title="Input" description="Keyboard, mouse, and touchpad" /> - -<Card href="/docs/configuration/xdg-portals" title="XDG Portals" description="File pickers and notifications" /> - -<Card href="/docs/configuration/miscellaneous" title="Miscellaneous" description="Additional options" /> - -</Cards> \ No newline at end of file diff --git a/docs/v0.13.1/configuration/input.md b/docs/v0.13.1/configuration/input.md deleted file mode 100644 index ee12906a..00000000 --- a/docs/v0.13.1/configuration/input.md +++ /dev/null @@ -1,161 +0,0 @@ ---- -title: Input Devices -description: Configure keyboard layouts, mouse sensitivity, and touchpad gestures. ---- - -## Device Configuration - -mangowm provides granular control over different input devices. - -### Keyboard Settings - -Control key repeat rates and layout rules. - -| Setting | Type | Default | Description | -| :--- | :--- | :--- | :--- | -| `repeat_rate` | `int` | `25` | How many times a key repeats per second. | -| `repeat_delay` | `int` | `600` | Delay (ms) before a held key starts repeating. | -| `numlockon` | `0` or `1` | `0` | Enable NumLock on startup. | -| `xkb_rules_rules` | `string` | - | XKB rules file (e.g., `evdev`, `base`). Usually auto-detected. | -| `xkb_rules_model` | `string` | - | Keyboard model (e.g., `pc104`, `macbook`). | -| `xkb_rules_layout` | `string` | - | Keyboard layout code (e.g., `us`, `de`, `us,de`). | -| `xkb_rules_variant` | `string` | - | Layout variant (e.g., `dvorak`, `colemak`, `intl`). | -| `xkb_rules_options` | `string` | - | XKB options (e.g., `caps:escape`, `ctrl:nocaps`). | - -**Example:** - -```ini -repeat_rate=40 -repeat_delay=300 -numlockon=1 -xkb_rules_layout=us,de -xkb_rules_variant=dvorak -xkb_rules_options=caps:escape,ctrl:nocaps -``` - ---- - -### Mouse Settings - -Configuration for external mice. - -| Setting | Default | Description | -| :--- | :--- | :--- | -| `mouse_natural_scrolling` | `0` | Invert scrolling direction. | -| `mouse_accel_profile` | `2` | `0` (None), `1` (Flat), `2` (Adaptive). | -| `mouse_accel_speed` | `0.0` | Speed adjustment (-1.0 to 1.0). | -| `left_handed` | `0` | Swap left and right buttons. | -| `axis_scroll_factor` | `1.0` | Scroll factor for axis scroll speed (0.1–10.0). | ---- - -### Trackpad Settings - -Specific settings for laptop touchpads. Some settings may require a relogin to take effect. - -| Setting | Default | Description | -| :--- | :--- | :--- | -| `disable_trackpad` | `0` | Set to `1` to disable the trackpad entirely. | -| `tap_to_click` | `1` | Tap to trigger a left click. | -| `tap_and_drag` | `1` | Tap and hold to drag items. | -| `trackpad_natural_scrolling` | `0` | Invert scrolling direction (natural scrolling). | -| `trackpad_accel_profile` | `2` | `0` (None), `1` (Flat), `2` (Adaptive). | -| `trackpad_accel_speed` | `0.0` | Speed adjustment (-1.0 to 1.0). | -| `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). | -| `send_events_mode` | `0` | `0` (Enabled), `1` (Disabled), `2` (Disabled on external mouse). | -| `drag_lock` | `1` | Lock dragging after tapping. | -| `disable_while_typing` | `1` | Disable trackpad while typing. | -| `left_handed` | `0` | Swap left/right buttons. | -| `middle_button_emulation` | `0` | Emulate middle button. | -| `swipe_min_threshold` | `1` | Minimum swipe threshold when use gesture. | -| `button_map` | `0` | `0` (Left/right/middle), `1` (Left/middle/right). | -| `trackpad_scroll_factor` | `1.0` | Scroll factor for trackpad scroll speed (0.1–10.0). | ---- - -**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. - - `2` — Edge scrolling: send scroll events when a finger moves along the bottom or right edge. - - `4` — Button scrolling: send scroll events when a button is held and the device moves along a scroll axis. - -- `click_method` values: - - `0` — No software click emulation. - - `1` — Button areas: use software-defined areas on the touchpad to generate button events. - - `2` — Clickfinger: the number of fingers determines which button is pressed. - -- `mouse_accel_profile` or `trackpad_scroll_profile` values: - - `0` — No acceleration. - - `1` — Flat: no dynamic acceleration. Pointer speed = original input speed × (1 + `mouse_accel_speed`). - - `2` — Adaptive: slow movement results in less acceleration, fast movement results in more. - -- `button_map` values: - - `0` — 1/2/3 finger tap maps to left / right / middle. - - `1` — 1/2/3 finger tap maps to left / middle / right. - -- `send_events_mode` values: - - `0` — Send events from this device normally. - - `1` — Do not send events from this device. - - `2` — Disable this device when an external pointer device is plugged in. - ---- ---- - -## Keyboard Layout Switching - -To bind multiple layouts and toggle between them, define the layouts in `xkb_rules_layout` and use `xkb_rules_options` to set a toggle key combination. Then bind `switch_keyboard_layout` to trigger a switch. - -```ini -# Define two layouts: US QWERTY and US Dvorak -xkb_rules_layout=us,us -xkb_rules_variant=,dvorak -xkb_rules_options=grp:lalt_lshift_toggle -``` - -Or bind it manually to a key: - -```ini -# Bind Alt+Shift_L to cycle keyboard layout -bind=alt,shift_l,switch_keyboard_layout -``` - -Use `mmsg -g -k` to query the current keyboard layout at any time. - ---- - -## Input Method Editor (IME) - -To use Fcitx5 or IBus, set these environment variables in your config file. - -> **Info:** These settings require a restart of the window manager to take effect. - -**For Fcitx5:** - -```ini -env=GTK_IM_MODULE,fcitx -env=QT_IM_MODULE,fcitx -env=QT_IM_MODULES,wayland;fcitx -env=SDL_IM_MODULE,fcitx -env=XMODIFIERS,@im=fcitx -env=GLFW_IM_MODULE,ibus -``` - -**For IBus:** - -```ini -env=GTK_IM_MODULE,ibus -env=QT_IM_MODULE,ibus -env=XMODIFIERS,@im=ibus -``` diff --git a/docs/v0.13.1/configuration/meta.json b/docs/v0.13.1/configuration/meta.json deleted file mode 100644 index bc209b4e..00000000 --- a/docs/v0.13.1/configuration/meta.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "title": "Configuration", - "pages": ["basics", "monitors", "input", "xdg-portals", "miscellaneous"] -} diff --git a/docs/v0.13.1/configuration/miscellaneous.md b/docs/v0.13.1/configuration/miscellaneous.md deleted file mode 100644 index e1be2907..00000000 --- a/docs/v0.13.1/configuration/miscellaneous.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -title: Miscellaneous -description: Advanced settings for XWayland, focus behavior, and system integration. ---- - -## System & Hardware - -| Setting | Default | Description | -| :--- | :--- | :--- | -| `xwayland_persistence` | `1` | Keep XWayland running even when no X11 apps are open (reduces startup lag). | -| `syncobj_enable` | `0` | Enable `drm_syncobj` timeline support (helps with gaming stutter/lag). **Requires restart.** | -| `allow_lock_transparent` | `0` | Allow the lock screen to be transparent. | -| `allow_shortcuts_inhibit` | `1` | Allow shortcuts to be inhibited by clients. | -| `vrr` | - | Set via [monitor rule](/docs/configuration/monitors#monitor-rules). | - -## Focus & Input - -| Setting | Default | Description | -| :--- | :--- | :--- | -| `focus_on_activate` | `1` | Automatically focus windows when they request activation. | -| `sloppyfocus` | `1` | Focus follows the mouse cursor. | -| `warpcursor` | `1` | Warp the cursor to the center of the window when focus changes via keyboard. | -| `cursor_hide_timeout` | `0` | Hide the cursor after `N` seconds of inactivity (`0` to disable). | -| `drag_tile_to_tile` | `0` | Allow dragging a tiled window onto another to swap their positions. | -| `drag_tile_small` | `1` | Allow dragging a tiled window temporarily to small size.| -| `drag_corner` | `3` | Corner for drag-to-tile detection (0: none, 1–3: corners, 4: auto-detect). | -| `drag_warp_cursor` | `1` | Warp cursor when dragging windows to tile. | -| `axis_bind_apply_timeout` | `100` | Timeout (ms) for detecting consecutive scroll events for axis bindings. | - -## Multi-Monitor & Tags - -| Setting | Default | Description | -| :--- | :--- | :--- | -| `focus_cross_monitor` | `0` | Allow directional focus to cross monitor boundaries. | -| `exchange_cross_monitor` | `0` | Allow exchanging clients across monitor boundaries. | -| `focus_cross_tag` | `0` | Allow directional focus to cross into other tags. | -| `view_current_to_back` | `0` | Toggling the current tag switches back to the previously viewed tag. | -| `scratchpad_cross_monitor` | `0` | Share the scratchpad pool across all monitors. | -| `single_scratchpad` | `1` | Only allow one scratchpad (named or standard) to be visible at a time. | - -## Window Behavior - -| Setting | Default | Description | -| :--- | :--- | :--- | -| `enable_floating_snap` | `0` | Snap floating windows to edges or other windows. | -| `snap_distance` | `30` | Max distance (pixels) to trigger floating snap. | -| `no_border_when_single` | `0` | Remove window borders when only one window is visible on the tag. | -| `idleinhibit_ignore_visible` | `0` | Allow invisible clients (e.g., background audio players) to inhibit idle. | -| `drag_tile_refresh_interval` | `8.0` | Interval (1.0–16.0) to refresh tiled window resize during drag. Too small may cause application lag. | -| `drag_floating_refresh_interval` | `8.0` | Interval (1.0–16.0) to refresh floating window resize during drag. Too small may cause application lag. | \ No newline at end of file diff --git a/docs/v0.13.1/configuration/monitors.md b/docs/v0.13.1/configuration/monitors.md deleted file mode 100644 index 28ef240b..00000000 --- a/docs/v0.13.1/configuration/monitors.md +++ /dev/null @@ -1,276 +0,0 @@ ---- -title: Monitors -description: Manage display outputs, resolution, scaling, and tearing. ---- - -## Monitor Rules - -You can configure each display output individually using the `monitorrule` keyword. - -**Syntax:** - -```ini -monitorrule=name:Values,Parameter:Values,Parameter:Values -``` - -> **Info:** If any of the matching fields (`name`, `make`, `model`, `serial`) are set, **all** of the set ones must match to be considered a match. Use `wlr-randr` to get your monitor's name, make, model, and serial. - -### Parameters - -| Parameter | Type | Values | Description | -| :--- | :--- | :--- | :--- | -| `name` | string | Any | Match by monitor name (supports regex) | -| `make` | string | Any | Match by monitor manufacturer | -| `model` | string | Any | Match by monitor model | -| `serial` | string | Any | Match by monitor serial number | -| `width` | integer | 0-9999 | Monitor width | -| `height` | integer | 0-9999 | Monitor height | -| `refresh` | float | 0.001-9999.0 | Monitor refresh rate | -| `x` | integer | 0-99999 | X position | -| `y` | integer | 0-99999 | Y position | -| `scale` | float | 0.01-100.0 | Monitor scale | -| `vrr` | integer | 0, 1 | Enable variable refresh rate | -| `rr` | integer | 0-7 | Monitor transform | -| `custom` | integer | 0, 1 | Enable custom mode (not supported on all displays — may cause black screen) | - -### Transform Values - -| Value | Rotation | -| :--- | :--- | -| `0` | No transform | -| `1` | 90° counter-clockwise | -| `2` | 180° counter-clockwise | -| `3` | 270° counter-clockwise | -| `4` | 180° vertical flip | -| `5` | Flip + 90° counter-clockwise | -| `6` | Flip + 180° counter-clockwise | -| `7` | Flip + 270° counter-clockwise | - -> **Critical:** If you use XWayland applications, **never use negative coordinates** for your monitor positions. This is a known XWayland bug that causes click events to malfunction. Always arrange your monitors starting from `0,0` and extend into positive coordinates. - -> **Note:** that "name" is a regular expression. If you want an exact match, you need to add `^` and `$` to the beginning and end of the expression, for example, `^eDP-1$` matches exactly the string `eDP-1`. - -### Examples - -```ini -# Laptop display: 1080p, 60Hz, positioned at origin -monitorrule=name:^eDP-1$,width:1920,height:1080,refresh:60,x:0,y:10 - -# Match by make and model instead of name -monitorrule=make:Chimei Innolux Corporation,model:0x15F5,width:1920,height:1080,refresh:60,x:0,y:0 - -# Virtual monitor with pattern matching -monitorrule=name:HEADLESS-.*,width:1920,height:1080,refresh:60,x:1926,y:0,scale:1,rr:0,vrr:0 -``` - ---- - -## Monitor Spec Format - -Several commands (`focusmon`, `tagmon`, `disable_monitor`, `enable_monitor`, `toggle_monitor`, `viewcrossmon`, `tagcrossmon`) accept a **monitor_spec** string to identify a monitor. - -**Format:** - -```text -name:xxx&&make:xxx&&model:xxx&&serial:xxx -``` - -- Any field can be omitted and there is no order requirement. -- If all fields are omitted, the string is treated as the monitor name directly (e.g., `eDP-1`). -- Use `wlr-randr` to find your monitor's name, make, model, and serial. - -**Examples:** - -```bash -# By name (shorthand) -mmsg -d toggle_monitor,eDP-1 - -# By make and model -mmsg -d toggle_monitor,make:Chimei Innolux Corporation&&model:0x15F5 - -# By serial -mmsg -d toggle_monitor,serial:12345678 -``` - ---- - -## Tearing (Game Mode) - -Tearing allows games to bypass the compositor's VSync for lower latency. - -| Setting | Default | Description | -| :--- | :--- | :--- | -| `allow_tearing` | `0` | Global tearing control: `0` (Disable), `1` (Enable), `2` (Fullscreen only). | - -### Configuration - -**Enable Globally:** - -```ini -allow_tearing=1 -``` - -**Enable per Window:** - -Use a window rule to force tearing for specific games. - -```ini -windowrule=force_tearing:1,title:vkcube -``` - -### Tearing Behavior Matrix - -| `force_tearing` \ `allow_tearing` | DISABLED (0) | ENABLED (1) | FULLSCREEN_ONLY (2) | -| :--- | :--- | :--- | :--- | -| **UNSPECIFIED** (0) | Not Allowed | Follows tearing_hint | Only fullscreen follows tearing_hint | -| **ENABLED** (1) | Not Allowed | Allowed | Only fullscreen allowed | -| **DISABLED** (2) | Not Allowed | Not Allowed | Not Allowed | - -### Graphics Card Compatibility - -> **Warning:** Some graphics cards require setting the `WLR_DRM_NO_ATOMIC` environment variable before mango starts to successfully enable tearing. - -Add this to `/etc/environment` and reboot: - -```bash -WLR_DRM_NO_ATOMIC=1 -``` - -Or run mango with the environment variable: - -```bash -WLR_DRM_NO_ATOMIC=1 mango -``` - ---- - -## GPU Compatibility - -If mango cannot display correctly or shows a black screen, try selecting a specific GPU: - -```bash -# Use a single GPU -WLR_DRM_DEVICES=/dev/dri/card1 mango - -# Use multiple GPUs -WLR_DRM_DEVICES=/dev/dri/card0:/dev/dri/card1 mango -``` - -Some GPUs have compatibility issues with `syncobj_enable=1` — it may crash apps like `kitty` that use syncobj. Set `WLR_DRM_NO_ATOMIC=1` in `/etc/environment` and reboot to resolve this. - ---- - -## Power Management - -You can control monitor power using the `mmsg` IPC tool. - -```bash -# Turn off -mmsg -d disable_monitor,eDP-1 - -# Turn on -mmsg -d enable_monitor,eDP-1 - -# Toggle -mmsg -d toggle_monitor,eDP-1 -``` - -You can also use `wlr-randr` for monitor management: - -```bash -# Turn off monitor -wlr-randr --output eDP-1 --off - -# Turn on monitor -wlr-randr --output eDP-1 --on - -# Show all monitors -wlr-randr -``` - ---- - -## Screen Scale - -### Without Global Scale (Recommended) - -- If you do not use XWayland apps, you can use monitor rules or `wlr-randr` to set a global monitor scale. -- If you are using XWayland apps, it is not recommended to set a global monitor scale. - -You can set scale like this, for example with a 1.4 factor. - -**Dependencies:** - -```bash -yay -S xorg-xrdb -yay -S xwayland-satellite -``` - -**In config file:** - -```ini -env=QT_AUTO_SCREEN_SCALE_FACTOR,1 -env=QT_WAYLAND_FORCE_DPI,140 -``` - -**In autostart:** - -```bash -echo "Xft.dpi: 140" | xrdb -merge -gsettings set org.gnome.desktop.interface text-scaling-factor 1.4 -``` - -**Edit autostart for XWayland:** - -```bash -# Start xwayland -/usr/sbin/xwayland-satellite :11 & -# Apply scale 1.4 for xwayland -sleep 0.5s && echo "Xft.dpi: 140" | xrdb -merge -``` - -### Using xwayland-satellite to Prevent Blurry XWayland Apps - -If you use fractional scaling, you can use `xwayland-satellite` to automatically scale XWayland apps to prevent blurriness, for example with a scale of 1.4. - -**Dependencies:** - -```bash -yay -S xwayland-satellite -``` - -**In config file:** - -```ini -env=DISPLAY,:2 -exec-once=xwayland-satellite :2 -monitorrule=name:eDP-1,width:1920,height:1080,refresh:60,x:0,y:0,scale:1.4,vrr:0,rr:0 -``` - -> **Warning:** Use a `DISPLAY` value other than `:1` to avoid conflicting with mangowm. - ---- - -## Virtual Monitors - -You can create and manage virtual displays through IPC commands: - -```bash -# Create virtual output -mmsg -d create_virtual_output - -# Destroy all virtual outputs -mmsg -d destroy_all_virtual_output -``` - -You can configure virtual monitors using `wlr-randr`: - -```bash -# Show all monitors -wlr-randr - -# Configure virtual monitor -wlr-randr --output HEADLESS-1 --pos 1921,0 --scale 1 --custom-mode 1920x1080@60Hz -``` - -Virtual monitors can be used for screen sharing with tools like [Sunshine](https://github.com/LizardByte/Sunshine) and [Moonlight](https://github.com/moonlight-stream/moonlight-android), allowing other devices to act as extended monitors. \ No newline at end of file diff --git a/docs/v0.13.1/configuration/xdg-portals.md b/docs/v0.13.1/configuration/xdg-portals.md deleted file mode 100644 index 27819ad8..00000000 --- a/docs/v0.13.1/configuration/xdg-portals.md +++ /dev/null @@ -1,76 +0,0 @@ ---- -title: XDG Portals -description: Set up screen sharing, clipboard, keyring, and file pickers using XDG portals. ---- - -## Portal Configuration - -You can customize portal settings via the following paths: - -- **User Configuration (Priority):** `~/.config/xdg-desktop-portal/mango-portals.conf` -- **System Fallback:** `/usr/share/xdg-desktop-portal/mango-portals.conf` - -> **Warning:** If you previously added `dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP=wlroots` to your config, remove it. Mango now handles this automatically. - -## Screen Sharing - -To enable screen sharing (OBS, Discord, WebRTC), you need `xdg-desktop-portal-wlr`. - -1. **Install Dependencies** - - `pipewire`, `pipewire-pulse`, `xdg-desktop-portal-wlr` - -2. **Optional: Add to autostart** - - In some situations the portal may not start automatically. You can add this to your autostart script to ensure it launches: - - ```bash - /usr/lib/xdg-desktop-portal-wlr & - ``` - -3. **Restart your computer** to apply changes. - -### Known Issues - -- **Window screen sharing:** Some applications may have issues sharing individual windows. See [#184](https://github.com/mangowm/mango/pull/184) for workarounds. - -- **Screen recording lag:** If you experience stuttering during screen recording, see [xdg-desktop-portal-wlr#351](https://github.com/emersion/xdg-desktop-portal-wlr/issues/351). - -## Clipboard Manager - -Use `cliphist` to manage clipboard history. - -**Dependencies:** `wl-clipboard`, `cliphist`, `wl-clip-persist` - -**Autostart Config:** - -```bash -# Keep clipboard content after app closes -wl-clip-persist --clipboard regular --reconnect-tries 0 & - -# Watch clipboard and store history -wl-paste --type text --watch cliphist store & -``` - -## GNOME Keyring - -If you need to store passwords or secrets (e.g., for VS Code or Minecraft launchers), install `gnome-keyring`. - -**Configuration:** - -Add the following to `~/.config/xdg-desktop-portal/mango-portals.conf`: - -```ini -[preferred] -default=gtk -org.freedesktop.impl.portal.ScreenCast=wlr -org.freedesktop.impl.portal.Screenshot=wlr -org.freedesktop.impl.portal.Secret=gnome-keyring -org.freedesktop.impl.portal.Inhibit=none -``` - -## File Picker (File Selector) - -**Dependencies:** `xdg-desktop-portal`, `xdg-desktop-portal-gtk` - -Reboot your computer once to apply. \ No newline at end of file diff --git a/docs/v0.13.1/faq.md b/docs/v0.13.1/faq.md deleted file mode 100644 index 9c9288de..00000000 --- a/docs/v0.13.1/faq.md +++ /dev/null @@ -1,100 +0,0 @@ ---- -title: FAQ -description: Frequently asked questions and troubleshooting. ---- - -### How do I arrange tiled windows with my mouse? - -You can enable the `drag_tile_to_tile` option in your config. This allows you to drag a tiled window onto another to swap them. - -```ini -drag_tile_to_tile=1 -``` - ---- - -### Why is my background blurry or why does blur look wrong? - -Blur applies to the transparent areas of windows. To disable it entirely, set `blur=0`. - -If you are experiencing **performance issues with blur**, make sure `blur_optimized=1` (the default). This caches the wallpaper as the blur background, which is much cheaper on the GPU: - -```ini -blur_optimized=1 -``` - ---- - -### Blur shows my wallpaper instead of the real background content - -This is expected behavior when `blur_optimized=1` (the default). The optimizer caches the wallpaper to reduce GPU load — windows will blur against the wallpaper rather than the actual content stacked beneath them. - -If you want blur to composite against the true background (i.e., show whatever is actually behind the window), set: - -```ini -blur_optimized=0 -``` - -> **Warning:** Disabling `blur_optimized` significantly increases GPU consumption and may cause rendering lag, especially on lower-end hardware. - ---- - -### My games are lagging or stuttering - -Try enabling **SyncObj** timeline support. - -```ini -syncobj_enable=1 -``` - ---- - -### My games have high input latency - -You can enable **Tearing** (similar to VSync off). - -First, enable it globally: - -```ini -allow_tearing=1 -``` - -Then force it for your specific game: - -```ini -windowrule=force_tearing:1,title:Counter-Strike 2 -``` - -> **Warning:** Some graphics cards require setting `WLR_DRM_NO_ATOMIC=1` before mango starts for tearing to work. Add it to `/etc/environment` and reboot, or launch mango with `WLR_DRM_NO_ATOMIC=1 mango`. See [Monitors — Tearing](/docs/configuration/monitors#tearing-game-mode) for details. - ---- - -### How do I use pipes `|` in spawn commands? - -The standard `spawn` command does not support shell pipes directly. You must use `spawn_shell` instead. - -```ini -bind=SUPER,P,spawn_shell,echo "hello" | rofi -dmenu -``` - ---- - -### Certain key combinations do not work on my keyboard layout. - -`bind` automatically converts keysym to keycode, which is compatible with most layouts but can sometimes be imprecise. If a key combination is not triggering, use the **keycode** directly instead of the key name. - -Run `wev` and press the key to find its keycode, then use it in your bind: - -```ini -# Instead of: -bind=ALT,q,killclient - -# Use the keycode (e.g., code:24 = q on most layouts): -bind=ALT,code:24,killclient -``` - -You can also use `binds` (the `s` flag) to match by keysym instead of keycode: - -```ini -binds=ALT,q,killclient -``` diff --git a/docs/v0.13.1/index.md b/docs/v0.13.1/index.md deleted file mode 100644 index d308370d..00000000 --- a/docs/v0.13.1/index.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: Introduction -description: A lightweight and feature-rich Wayland compositor based on dwl. ---- - - -**mango** is a Wayland compositor based on [dwl](https://codeberg.org/dwl/dwl/). It aims to be as lightweight as `dwl` and can be built completely within a few seconds, without compromising on functionality. - -> **Philosophy:** **Lightweight & Fast**: mango is designed to be minimal yet functional. It compiles in seconds and offers a robust set of features out of the box. - -## Feature Highlights - -Beyond basic window management, mangowm provides a rich set of features designed for a modern Wayland experience. - -- **[Animations](/docs/visuals/animations)** — Smooth, customizable animations for opening, moving, closing windows and tag switching. -- **[Layouts](/docs/window-management/layouts)** — Supports Scroller, Master-Stack, Monocle, Grid, Deck, and more, with per-tag layouts. -- **[Visual Effects](/docs/visuals/effects)** — Built-in blur, shadows, corner radius, and opacity effects powered by scenefx. -- **[IPC & Scripting](/docs/ipc)** — Control the compositor externally with robust IPC support for custom scripts and widgets. - -## Additional Features - -- **XWayland Support** — Excellent compatibility for legacy X11 applications. -- **Tag System** — Uses tags instead of workspaces, allowing separate window layouts for each tag. -- **Input Methods** — Great support for text input v2/v3 (Fcitx5, IBus). -- **Window States** — Rich states including swallow, minimize, maximize, fullscreen, and overlay. -- **Hot-Reload Config** — Simple external configuration that supports hot-reloading without restarting. -- **Scratchpads** — Support for both Sway-like and named scratchpads. - -## Community - -- **[Join the mangowm Discord](https://discord.gg/CPjbDxesh5)** — Chat with the community, get support, share your setup, and stay updated with the latest mangowm news. -- **[Join the GitHub Discussions](https://github.com/mangowm/mango/discussions)** — Ask questions, request features, report issues, or share ideas directly with contributors and other users. - -## Acknowledgements - -This project is built upon the hard work of several open-source projects: - -- **[wlroots](https://gitlab.freedesktop.org/wlroots/wlroots)** — Implementation of the Wayland protocol. -- **[mwc](https://github.com/nikoloc/mwc)** — Basal window animation reference. -- **[dwl](https://codeberg.org/dwl/dwl)** — Basal dwl features. -- **[sway](https://github.com/swaywm/sway)** — Sample implementation of the Wayland protocol. -- **[scenefx](https://github.com/wlrfx/scenefx)** — Library to simplify adding window effects. diff --git a/docs/v0.13.1/installation.md b/docs/v0.13.1/installation.md deleted file mode 100644 index c5d4936c..00000000 --- a/docs/v0.13.1/installation.md +++ /dev/null @@ -1,308 +0,0 @@ ---- -title: Installation -description: Install mangowm on AerynOS, Arch, Fedora, Gentoo, Guix System, NixOS, PikaOS, or build from source. ---- - -## Package Installation - -mangowm is available as a pre-built package on several distributions. Choose your distribution below. - ---- - -### AerynOS - -mangowm is available in the **AerynOS package repository**. - -You can install it using the `moss` package manager: - -```bash -sudo moss install mangowm -``` - ---- - -### Arch Linux - -mangowm is available in the **Arch User Repository (AUR)**. - -You can install it using an AUR helper like `yay` or `paru`: - -```bash -yay -S mangowm-git -``` - -> **Tip:** This package pulls the latest git version, ensuring you have the newest features and fixes. - ---- - -### Fedora - -The package is in the third-party **Terra repository**. First, add the Terra Repository. - -> **Warning:** Both commands require root privileges. Use `sudo` if needed. - -```bash -dnf install --nogpgcheck --repofrompath 'terra,https://repos.fyralabs.com/terra$releasever' terra-release -``` - -Then, install the package: - -```bash -dnf install mangowm -``` - ---- - -### Gentoo - -The package is hosted in the community-maintained **GURU** repository. - -1. **Add the GURU repository** - - ```bash - emerge --ask --verbose eselect-repository - eselect repository enable guru - emerge --sync guru - ``` - -2. **Unmask packages** - Add the required packages to your `package.accept_keywords` file: - - `gui-libs/scenefx` - - `gui-wm/mangowm` - -3. **Install mango** - ```bash - emerge --ask --verbose gui-wm/mangowm - ``` - ---- - -### Guix System - -The package definition is described in the source repository. - -1. **Add mango channel** - Add to `$HOME/.config/guix/channels.scm`: - - ```scheme - (cons (channel - (name 'mangowm) - (url "https://github.com/mangowm/mango.git") - (branch "main")) - %default-channels) - ``` - -2. **Install** - After running `guix pull`, you can install mangowm: - - ```bash - guix install mangowm - ``` - - Or add it to your system configuration using the mangowm module: - - ```scheme - (use-modules (mangowm)) - - (packages (cons* - mangowm-git - ... ;; Other packages - %base-packages)) - ``` - -> **Tip:** For more information, see the [Guix System documentation](https://guix.gnu.org/manual/devel/en/html_node/Channels.html). - ---- - -### NixOS - -The repository provides a Flake with a NixOS module. - -1. **Add flake input** - - ```nix - # flake.nix - { - inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - mangowm = { - url = "github:mangowm/mango"; - inputs.nixpkgs.follows = "nixpkgs"; - }; - # other inputs ... - }; - } - ``` - -2. **Import the NixOS module** - - **Option A — Import in `configuration.nix`:** - - ```nix - # configuration.nix (or any other file that you import) - {inputs, ...}: { - imports = [ - inputs.mangowm.nixosModules.mango - # .. other imports ... - ]; - - # ... - } - ``` - - **Option B — Import directly in flake:** - - ```nix - # flake.nix - { - # ... - - outputs = { self, nixpkgs, mangowm, ...}@inputs: let - inherit (nixpkgs) lib; - # ... - in { - nixosConfigurations.YourHostName = lib.nixosSystem { - modules = [ - mangowm.nixosModules.mango # or inputs.mangowm.nixosModules.mango - # other imports ... - ]; - }; - } - } - ``` - -3. **Enable the module** - - ```nix - # configuration.nix (or any other file that you import) - { - programs.mango.enable = true; - } - ``` - -4. **Start mango on login** - - The following are common examples. Refer to the official NixOS documentation for full configuration options. - - **Option A — greetd:** Autologin on first start; login screen after logout. - - ```nix - services.greetd = { - enable = true; - settings = { - initial_session = { - command = "mango"; - user = "your-username"; # auto-login on first start, no password required - }; - default_session = { - command = "${pkgs.greetd.tuigreet}/bin/tuigreet --cmd mango"; - user = "greeter"; - }; - }; - }; - ``` - - **Option B — Display manager autologin:** Autologin via an existing display manager (e.g. SDDM, GDM). [`addLoginEntry`](/docs/nix-options#addloginentry) (default: `true`) automatically registers mango as a session. - - ```nix - services.displayManager = { - defaultSession = "mango"; # derived from mango.desktop filename - autoLogin = { - enable = true; - user = "your-username"; - }; - }; - ``` - - **Option C — getty autologin:** No login screen, boots directly into mango on TTY1. - - For bash/zsh: - - ```nix - services.getty.autologinUser = "your-username"; - - environment.loginShellInit = '' - [ "$(tty)" = /dev/tty1 ] && exec mango - ''; - ``` - - For fish: - - ```nix - services.getty.autologinUser = "your-username"; - - programs.fish.loginShellInit = '' - if test (tty) = /dev/tty1 - exec mango - end - ''; - ``` - -5. **All available options** - - See [Nix Module Options](/docs/nix-options) for the full list of NixOS and Home Manager options. - ---- - -### PikaOS - -mangowm is available in the **PikaOS package repository**. - -You can install it using the `pikman` package manager: - -```bash -pikman install mangowm -``` - ---- - -## Building from Source - -If your distribution isn't listed above, or you want the latest unreleased changes, you can build mangowm from source. - -> **Info:** Ensure the following dependencies are installed before proceeding: -> -> - `wayland` -> - `wayland-protocols` -> - `libinput` -> - `libdrm` -> - `libxkbcommon` -> - `pixman` -> - `libdisplay-info` -> - `libliftoff` -> - `hwdata` -> - `seatd` -> - `pcre2` -> - `xorg-xwayland` -> - `libxcb` - -You will need to build `wlroots` and `scenefx` manually as well. - -1. **Build wlroots** - Clone and install the specific version required (check README for latest version). - - ```bash - git clone -b 0.19.3 https://gitlab.freedesktop.org/wlroots/wlroots.git - cd wlroots - meson build -Dprefix=/usr - sudo ninja -C build install - ``` - -2. **Build scenefx** - This library handles the visual effects. - - ```bash - git clone -b 0.4.1 https://github.com/wlrfx/scenefx.git - cd scenefx - meson build -Dprefix=/usr - sudo ninja -C build install - ``` - -3. **Build mangowm** - Finally, compile the compositor itself. - ```bash - git clone https://github.com/mangowm/mango.git - cd mango - meson build -Dprefix=/usr - sudo ninja -C build install - ``` diff --git a/docs/v0.13.1/ipc.md b/docs/v0.13.1/ipc.md deleted file mode 100644 index e3ec4a32..00000000 --- a/docs/v0.13.1/ipc.md +++ /dev/null @@ -1,154 +0,0 @@ ---- -title: IPC -description: Control mangowm programmatically using mmsg. ---- - -## Introduction - -mangowm includes a powerful IPC (Inter-Process Communication) tool called `mmsg`. This allows you to query the window manager's state, watch for events, and execute commands from external scripts. - -## Basic Usage - -The general syntax for `mmsg` is: - -```bash -mmsg [-OTLq] -mmsg [-o <output>] -s [-t <tags>] [-l <layout>] [-c <tags>] [-d <cmd>,<arg1>,<arg2>,<arg3>,<arg4>,<arg5>] -mmsg [-o <output>] (-g | -w) [-OotlcvmfxekbA] -``` - -### Options - -| Flag | Description | -| :--- | :--- | -| `-q` | Quit mangowm. | -| `-g` | **Get** values (tags, layout, focused client). | -| `-s` | **Set** values (switch tags, layouts). | -| `-w` | **Watch** mode (streams events). | -| `-O` | Get all output (monitor) information. | -| `-T` | Get number of tags. | -| `-L` | Get all available layouts. | -| `-o` | Select output (monitor). | -| `-t` | Get/set selected tags (set with `[+-^.]`). | -| `-l` | Get/set current layout. | -| `-c` | Get title and appid of focused client. | -| `-v` | Get visibility of statusbar. | -| `-m` | Get fullscreen status. | -| `-f` | Get floating status. | -| `-d` | **Dispatch** an internal command. | -| `-x` | Get focused client geometry. | -| `-e` | Get the name of the last focused layer. | -| `-k` | Get current keyboard layout. | -| `-b` | Get current keybind mode. | -| `-A` | Get scale factor of monitor. | - -## Examples - -### Tag Management - -You can perform arithmetic on tags using the `-t` flag with `-s` (set). - -```bash -# Switch to Tag 1 -mmsg -t 1 - -# Add Tag 2 to current view (Multiview) -mmsg -s -t 2+ - -# Remove Tag 2 from current view -mmsg -s -t 2- - -# Toggle Tag 2 -mmsg -s -t 2^ -``` - -### Layouts - -Switch layouts programmatically. Layout codes: `S` (Scroller), `T` (Tile), `G` (Grid), `M` (Monocle), `K` (Deck), `CT` (Center Tile), `RT` (Right Tile), `VS` (Vertical Scroller), `VT` (Vertical Tile), `VG` (Vertical Grid), `VK` (Vertical Deck), `DW` (Dwindle), `F` (Fair), `VF` (Vertical Fair). - -```bash -# Switch to Scroller -mmsg -l "S" - -# Switch to Tile -mmsg -l "T" -``` - -### Dispatching Commands - -Any command available in `config.conf` keybindings can be run via IPC. - -```bash -# Close the focused window -mmsg -d killclient - -# Resize window by +10 width -mmsg -d resizewin,+10,0 - -# Toggle fullscreen -mmsg -d togglefullscreen - -# Disable a monitor power -mmsg -d disable_monitor,eDP-1 -``` - -### Monitoring & Status - -Use `-g` or `-w` to build custom status bars or automation scripts. - -```bash -# Watch for all message changes -mmsg -w - -# Get all messages without watch -mmsg -g - -# Watch focused client appid and title -mmsg -w -c - -# Get all available outputs -mmsg -O - -# Get all tags message -mmsg -g -t - -# Get current focused client message -mmsg -g -c - -# Get current keyboard layout -mmsg -g -k - -# Get current keybind mode -mmsg -g -b - -# Get scale factor of current monitor -mmsg -g -A -``` - -#### Tag Message Format - -- State: 0 → none, 1 → active, 2 → urgent - -Example output: - -| Monitor | Tag Number | Tag State | Clients in Tag | Focused Client | -|---------|------------|-----------|----------------|----------------| -| eDP-1 | tag 2 | 0 | 1 | 0 | - -| Monitor | occupied tags mask | active tags mask | urgent tags mask | -|---------|--------------------|------------------|------------------| -| eDP-1 | 14 | 6 | 0 | - -## Virtual Monitors - -You can create headless outputs for screen mirroring or remote desktop access (e.g., Sunshine/Moonlight). - -```bash -# Create a virtual output -mmsg -d create_virtual_output - -# Configure it (set resolution) -wlr-randr --output HEADLESS-1 --pos 1920,0 --mode 1920x1080@60Hz - -# Destroy all virtual outputs -mmsg -d destroy_all_virtual_output \ No newline at end of file diff --git a/docs/v0.13.1/meta.json b/docs/v0.13.1/meta.json deleted file mode 100644 index a1f41d47..00000000 --- a/docs/v0.13.1/meta.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "title": "v0.13.1", - "description": "v0.13.1 release", - "root": true, - "pages": [ - "---Getting Started---", - "index", - "installation", - "quick-start", - "---Configuration---", - "configuration", - "visuals", - "window-management", - "bindings", - "---Examples---", - "screenshot", - "---Reference---", - "nix-options", - "ipc", - "faq" - ] -} diff --git a/docs/v0.13.1/nix-options.md b/docs/v0.13.1/nix-options.md deleted file mode 100644 index 2537d9d8..00000000 --- a/docs/v0.13.1/nix-options.md +++ /dev/null @@ -1,519 +0,0 @@ ---- -title: Nix Module Options -description: NixOS and Home Manager configuration options for mangowm. ---- - -> **Note:** This document is automatically generated from the Nix module source code. - -## NixOS - -**System-level options via `programs.mango`.** - -### enable - - - -Whether to enable mango, a wayland compositor based on dwl\. - - - -*Type:* -boolean - - - -*Default:* - -```nix -false -``` - - - -*Example:* - -```nix -true -``` - -*Declared by:* - - [\<mango/nix/nixos-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/nixos-modules.nix) - - - -### package - - - -The mango package to use - - - -*Type:* -package - - - -*Default:* - -```nix -<derivation mango-nightly> -``` - -*Declared by:* - - [\<mango/nix/nixos-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/nixos-modules.nix) - - - -### addLoginEntry - - - -Whether to add a login entry to the display manager for mango\. Only has effect if a display manager is configured (e\.g\. SDDM, GDM via ` services.displayManager `)\. - - - -*Type:* -boolean - - - -*Default:* - -```nix -true -``` - -*Declared by:* - - [\<mango/nix/nixos-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/nixos-modules.nix) - -## Home Manager - -**Configure mangowm declaratively via `wayland.windowManager.mango`.** - -### enable - - - -Whether to enable mangowm, a Wayland compositor based on dwl\. - - - -*Type:* -boolean - - - -*Default:* - -```nix -false -``` - -*Declared by:* - - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) - - - -### package - - - -The mango package to use - - - -*Type:* -package - - - -*Default:* - -```nix -<derivation mango-nightly> -``` - -*Declared by:* - - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) - - - -### autostart_sh - - - -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:* -strings concatenated with “\\n” - - - -*Default:* - -```nix -"" -``` - - - -*Example:* - -```nix -'' - waybar & - dunst & -'' -``` - -*Declared by:* - - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) - - - -### bottomPrefixes - - - -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\. - - - -*Type:* -list of string - - - -*Default:* - -```nix -[ ] -``` - - - -*Example:* - -```nix -[ - "source" -] -``` - -*Declared by:* - - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) - - - -### extraConfig - - - -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\. - - - -*Type:* -strings concatenated with “\\n” - - - -*Default:* - -```nix -"" -``` - - - -*Example:* - -```nix -'' - # Advanced config that doesn't fit structured format - special_option = 1 -'' -``` - -*Declared by:* - - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) - - - -### settings - - - -Mango configuration written in Nix\. Entries with the same key -should be written as lists\. Variables and colors names should be -quoted\. See [https://mangowm\.github\.io/docs](https://mangowm\.github\.io/docs) 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 ` - -Keymodes (submaps) are supported via the special ` keymode ` attribute\. Each keymode -is a nested attribute set under ` keymode ` that contains its own bindings\. - - - -*Type:* -Mango configuration value - - - -*Default:* - -```nix -{ } -``` - - - -*Example:* - -```nix -{ - # 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" - "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" - ]; - }; - }; -} - -``` - -*Declared by:* - - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) - - - -### systemd\.enable - - - -Whether to enable ` mango-session.target ` on -mango startup\. This links to -` graphical-session.target `\. -Some important environment variables will be imported to systemd -and dbus user environment before reaching the target, including - - - ` DISPLAY ` - - ` WAYLAND_DISPLAY ` - - ` XDG_CURRENT_DESKTOP ` - - ` XDG_SESSION_TYPE ` - - ` NIXOS_OZONE_WL ` - You can extend this list using the ` systemd.variables ` option\. - - - -*Type:* -boolean - - - -*Default:* - -```nix -true -``` - - - -*Example:* - -```nix -false -``` - -*Declared by:* - - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) - - - -### systemd\.extraCommands - - - -Extra commands to run after D-Bus activation\. - - - -*Type:* -list of string - - - -*Default:* - -```nix -[ - "systemctl --user reset-failed" - "systemctl --user start mango-session.target" -] -``` - -*Declared by:* - - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) - - - -### systemd\.variables - - - -Environment variables imported into the systemd and D-Bus user environment\. - - - -*Type:* -list of string - - - -*Default:* - -```nix -[ - "DISPLAY" - "WAYLAND_DISPLAY" - "XDG_CURRENT_DESKTOP" - "XDG_SESSION_TYPE" - "NIXOS_OZONE_WL" - "XCURSOR_THEME" - "XCURSOR_SIZE" -] -``` - - - -*Example:* - -```nix -[ - "--all" -] -``` - -*Declared by:* - - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) - - - -### systemd\.xdgAutostart - - - -Whether to enable autostart of applications using -` systemd-xdg-autostart-generator(8) ` -\. - - - -*Type:* -boolean - - - -*Default:* - -```nix -false -``` - - - -*Example:* - -```nix -true -``` - -*Declared by:* - - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) - - - -### topPrefixes - - - -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\. - - - -*Type:* -list of string - - - -*Default:* - -```nix -[ ] -``` - - - -*Example:* - -```nix -[ - "source" -] -``` - -*Declared by:* - - [\<mango/nix/hm-modules\.nix>](https://github.com/mangowm/mango/blob/main/nix/hm-modules.nix) - diff --git a/docs/v0.13.1/quick-start.md b/docs/v0.13.1/quick-start.md deleted file mode 100644 index bc192474..00000000 --- a/docs/v0.13.1/quick-start.md +++ /dev/null @@ -1,88 +0,0 @@ ---- -title: Quick Start -description: Basic configuration and first steps with mangowm. ---- - -Now that you have mangowm installed, let's get your environment set up. - -## Initial Setup - -1. **Create Configuration Directory** - - mangowm looks for configuration files in `~/.config/mango/`. - - ```bash - mkdir -p ~/.config/mango - ``` - -2. **Copy Default Config** - - A default configuration file is provided at `/etc/mango/config.conf`. Copy it to your local directory to start customizing. - - ```bash - cp /etc/mango/config.conf ~/.config/mango/config.conf - ``` - -3. **Launch mangowm** - - You can now start the compositor from your TTY. - - ```bash - mango - ``` - - Optional: To specify a custom config file path: - - ```bash - mango -c /path/to/your/config.conf - ``` - -## Essential Keybindings - -mangowm uses the following keybinds by default: - -| Key Combination | Action | -| :--- | :--- | -| `Alt` + `Return` | Open Terminal (defaults to `foot`) | -| `Alt` + `Space` | Open Launcher (defaults to `rofi`) | -| `Alt` + `Q` | Close (Kill) the active window | -| `Super` + `M` | Quit mangowm | -| `Super` + `F` | Toggle Fullscreen | -| `Alt` + `Arrow Keys` | Move focus (Left, Right, Up, Down) | -| `Ctrl` + `1-9` | Switch to Tag 1-9 | -| `Alt` + `1-9` | Move window to Tag 1-9 | - -> **Warning:** Some default bindings rely on specific tools like `foot` (terminal) and `rofi` (launcher). Ensure you have them installed or update your `config.conf` to use your preferred alternatives. - -## Recommended Tools - -To get a fully functional desktop experience, we recommend installing the following components: - -| Category | Recommended Tools | -| :--- | :--- | -| Application Launcher | rofi, bemenu, wmenu, fuzzel | -| Terminal Emulator | foot, wezterm, alacritty, kitty, ghostty | -| Status Bar | waybar, eww, quickshell, ags | -| Desktop Shell | Noctalia, DankMaterialShell | -| Wallpaper Setup | awww(swww), swaybg | -| Notification Daemon | swaync, dunst, mako | -| Desktop Portal | xdg-desktop-portal, xdg-desktop-portal-wlr, xdg-desktop-portal-gtk | -| Clipboard | wl-clipboard, wl-clip-persist, cliphist | -| Gamma Control / Night Light | wlsunset, gammastep | -| Miscellaneous | xfce-polkit, wlogout | - -## Example Configuration - -Check out the [example configuration](https://github.com/DreamMaoMao/mango-config) by the creator of mangowm, including complete setups for mangowm, Waybar, Rofi, and more. - -```bash -git clone https://github.com/DreamMaoMao/mango-config.git ~/.config/mango -``` - -## Next Steps - -Now that you are up and running, dive deeper into customizing mangowm: - -- [Configure Monitors](/docs/configuration/monitors) — Set up resolution, scaling, and multi-monitor layouts. -- [Window Rules](/docs/window-management/rules#window-rules) — Define how specific applications should behave. -- [Appearance](/docs/visuals/theming) — Customize colors, borders, gaps, and effects. diff --git a/docs/v0.13.1/screenshot.md b/docs/v0.13.1/screenshot.md deleted file mode 100644 index f07cdf0c..00000000 --- a/docs/v0.13.1/screenshot.md +++ /dev/null @@ -1,213 +0,0 @@ ---- - -title: Screenshots -description: Example screenshot keybindings and capture workflows for mangowm. - ---- - -mangowm does not include a built-in screenshot tool. This keeps the compositor lean. -Instead, compose your own workflow from small Wayland utilities and bind them to keys; - -| Tool | Purpose | -| :--- | :--- | -| [`grim`](https://github.com/emersion/grim) | Capture the screen or a region to a file | -| [`slurp`](https://github.com/emersion/slurp) | Interactively select a region for `grim` | -| [`wl-copy`](https://github.com/bugaevc/wl-clipboard) | Copy screenshots directly to the clipboard | -| [`satty`](https://github.com/gabm/Satty) | Annotate screenshots before saving | -| [`wayfreeze`](https://github.com/nicbk/wayfreeze) | Freeze the screen before capture | - -Install the required with your package manager or from source. - -`grim` writes to the file path you give it, but **will not create missing directories**. Create one first: - -```bash -mkdir -p ~/Pictures/Screenshots -``` - -Any directory works. `~/Pictures/Screenshots/` is just a convention. - -## Quick Binds - -Short, single-step commands can be placed directly in `config.conf` with `spawn_shell`. -No script file needed. - -### Fullscreen - -Captures the entire display. - -```ini -bind=NONE,Print,spawn_shell,grim $HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png -``` - -### Region - -Select an area with `slurp` before capturing. - -```ini -bind=SHIFT,Print,spawn_shell,g=$(slurp -d) && [ -n "$g" ] && grim -g "$g" $HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png -``` - -### Pointer - -Captures the full screen including the cursor. - -```ini -bind=ALT,Print,spawn_shell,grim -c $HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png -``` - -### Clipboard - -Captures to a temporary file and copies the image to the clipboard; no file is saved. - -```ini -bind=CTRL,Print,spawn_shell,f=$(mktemp -t screenshot-XXXXXX.png) && grim "$f" && wl-copy < "$f" && rm -f "$f" -``` - -### Annotate - -Captures and opens `satty` for drawing before saving. - -```ini -bind=SUPER,Print,spawn_shell,f=$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png && grim "$f" && satty --filename "$f" --output-filename "$f" --actions-on-enter save-to-file --early-exit -``` - -## Script Binds - -When a command involves multi-step logic, geometry parsing, FIFOs, or screen freezing, -move it into a script and invoke it with `spawn` instead of `spawn_shell`. - -Create the scripts directory first: - -```bash -mkdir -p ~/.config/mango/scripts/screenshot -``` - -### Window - -Uses `mmsg` (ships with mango) to capture the focused window. - -`~/.config/mango/scripts/screenshot/window.sh`: - -```bash -#!/usr/bin/env bash -geometry=$(mmsg -x | awk '/x / {x=$3} /y / {y=$3} /width / {w=$3} /height / {h=$3} END {print x","y" "w"x"h}') -[ -z "$geometry" ] && exit 1 -grim -g "$geometry" "$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png" -``` - -```ini -bind=CTRL+SHIFT,Print,spawn,$HOME/.config/mango/scripts/screenshot/window.sh -``` - -### Freeze - -Freezes the screen with `wayfreeze` before capturing. - -`~/.config/mango/scripts/screenshot/freeze.sh`: - -```bash -#!/usr/bin/env bash -pipe=$(mktemp -u).fifo -mkfifo "$pipe" -wayfreeze --after-freeze-timeout 100 --after-freeze-cmd "echo > $pipe" & -wayfreeze_pid=$! -read -r < "$pipe" -grim "$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png" -kill "$wayfreeze_pid" 2>/dev/null -rm -f "$pipe" -``` - -```ini -bind=CTRL+SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/freeze.sh -``` - -### Freeze + Region - -Freeze, then select a region with `slurp`. Cleans up on cancel. - -`~/.config/mango/scripts/screenshot/freeze-region.sh`: - -```bash -#!/usr/bin/env bash -pipe=$(mktemp -u).fifo -mkfifo "$pipe" -wayfreeze --after-freeze-timeout 100 --after-freeze-cmd "echo > $pipe" & -wayfreeze_pid=$! -read -r < "$pipe" -geometry=$(slurp -d) -if [[ -z "$geometry" ]]; then - kill "$wayfreeze_pid" 2>/dev/null - rm -f "$pipe" - exit 1 -fi -grim -g "$geometry" "$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png" -kill "$wayfreeze_pid" 2>/dev/null -rm -f "$pipe" -``` - -```ini -bind=SHIFT+SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/freeze-region.sh -``` - -Make all three scripts executable: - -```bash -chmod +x ~/.config/mango/scripts/screenshot/*.sh -``` - -## All-in-One Script - -Prefer fewer files? A single script with subcommands covers every mode above. -Place it in the same directory and use it in place of the individual scripts. - -`~/.config/mango/scripts/screenshot/screenshot.sh`: - -```bash -#!/usr/bin/env bash -set -euo pipefail -mkdir -p "$HOME/Pictures/Screenshots" -filepath="$HOME/Pictures/Screenshots/$(date +%Y%m%d%H%M%S).png" - -case "${1:-fullscreen}" in - region) - g=$(slurp -d); [ -z "$g" ] && exit 1 - grim -g "$g" "$filepath" ;; - window) - g=$(mmsg -x | awk '/x / {x=$3} /y / {y=$3} /width / {w=$3} /height / {h=$3} END {print x","y" "w"x"h}') - [ -z "$g" ] && exit 1 - grim -g "$g" "$filepath" ;; - freeze) - p=$(mktemp -u).fifo; mkfifo "$p" - wayfreeze --after-freeze-timeout 100 --after-freeze-cmd "echo > $p" & wp=$! - read -r < "$p"; grim "$filepath" - kill "$wp" 2>/dev/null; rm -f "$p" ;; - freeze-region) - p=$(mktemp -u).fifo; mkfifo "$p" - wayfreeze --after-freeze-timeout 100 --after-freeze-cmd "echo > $p" & wp=$! - read -r < "$p"; g=$(slurp -d) - if [ -z "$g" ]; then kill "$wp" 2>/dev/null; rm -f "$p"; exit 1; fi - grim -g "$g" "$filepath" - kill "$wp" 2>/dev/null; rm -f "$p" ;; - annotate) - grim "$filepath"; satty --filename "$filepath" --output-filename "$filepath" --actions-on-enter save-to-file --early-exit ;; - *) grim "$filepath" ;; -esac -``` - -Make the script executable: - - -```bash -chmod +x ~/.config/mango/scripts/screenshot/screenshot.sh -``` - -Then add the binds to `config.conf`: - -```ini -bind=NONE,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh fullscreen -bind=SHIFT,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh region -bind=CTRL+SHIFT,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh window -bind=CTRL+SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh freeze -bind=SHIFT+SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh freeze-region -bind=SUPER,Print,spawn,$HOME/.config/mango/scripts/screenshot/screenshot.sh annotate -``` diff --git a/docs/v0.13.1/visuals/animations.md b/docs/v0.13.1/visuals/animations.md deleted file mode 100644 index 76477e05..00000000 --- a/docs/v0.13.1/visuals/animations.md +++ /dev/null @@ -1,108 +0,0 @@ ---- -title: Animations -description: Configure smooth transitions for windows and layers. ---- - -## Enabling Animations - -mangowm supports animations for both standard windows and layer shell surfaces (like bars and notifications). - -```ini -animations=1 -layer_animations=1 -``` - -## Animation Types - -You can define different animation styles for opening and closing windows and layer surfaces. - -Available types: `slide`, `zoom`, `fade`, `none`. - -```ini -animation_type_open=zoom -animation_type_close=slide -layer_animation_type_open=slide -layer_animation_type_close=slide -``` - -## Fade Settings - -Control the fade-in and fade-out effects for animations. - -```ini -animation_fade_in=1 -animation_fade_out=1 -fadein_begin_opacity=0.5 -fadeout_begin_opacity=0.5 -``` - -- `animation_fade_in` — Enable fade-in effect (0: disable, 1: enable) -- `animation_fade_out` — Enable fade-out effect (0: disable, 1: enable) -- `fadein_begin_opacity` — Starting opacity for fade-in animations (0.0–1.0) -- `fadeout_begin_opacity` — Starting opacity for fade-out animations (0.0–1.0) - -## Zoom Settings - -Adjust the zoom ratios for zoom animations. - -```ini -zoom_initial_ratio=0.4 -zoom_end_ratio=0.8 -``` - -- `zoom_initial_ratio` — Initial zoom ratio -- `zoom_end_ratio` — End zoom ratio - -## Durations - -Control the speed of animations (in milliseconds). - -| Setting | Type | Default | Description | -| :--- | :--- | :--- | :--- | -| `animation_duration_move` | integer | `500` | Move animation duration (ms) | -| `animation_duration_open` | integer | `400` | Open animation duration (ms) | -| `animation_duration_tag` | integer | `300` | Tag animation duration (ms) | -| `animation_duration_close` | integer | `300` | Close animation duration (ms) | -| `animation_duration_focus` | integer | `0` | Focus change (opacity transition) animation duration (ms) | - -```ini -animation_duration_move=500 -animation_duration_open=400 -animation_duration_tag=300 -animation_duration_close=300 -animation_duration_focus=0 -``` - -## Custom Bezier Curves - -Bezier curves determine the "feel" of an animation (e.g., linear vs. bouncy). The format is `x1,y1,x2,y2`. - -You can visualize and generate curve values using online tools like [cssportal.com](https://www.cssportal.com/css-cubic-bezier-generator/) or [easings.net](https://easings.net). - -| Setting | Type | Default | Description | -| :--- | :--- | :--- | :--- | -| `animation_curve_open` | string | `0.46,1.0,0.29,0.99` | Open animation bezier curve | -| `animation_curve_move` | string | `0.46,1.0,0.29,0.99` | Move animation bezier curve | -| `animation_curve_tag` | string | `0.46,1.0,0.29,0.99` | Tag animation bezier curve | -| `animation_curve_close` | string | `0.46,1.0,0.29,0.99` | Close animation bezier curve | -| `animation_curve_focus` | string | `0.46,1.0,0.29,0.99` | Focus change (opacity transition) animation bezier curve | -| `animation_curve_opafadein` | string | `0.46,1.0,0.29,0.99` | Open opacity animation bezier curve | -| `animation_curve_opafadeout` | string | `0.5,0.5,0.5,0.5` | Close opacity animation bezier curve | - -```ini -animation_curve_open=0.46,1.0,0.29,0.99 -animation_curve_move=0.46,1.0,0.29,0.99 -animation_curve_tag=0.46,1.0,0.29,0.99 -animation_curve_close=0.46,1.0,0.29,0.99 -animation_curve_focus=0.46,1.0,0.29,0.99 -animation_curve_opafadein=0.46,1.0,0.29,0.99 -animation_curve_opafadeout=0.5,0.5,0.5,0.5 -``` - -## Tag Animation Direction - -Control the direction of tag switch animations. - -| Setting | Default | Description | -| :--- | :--- | :--- | -| `tag_animation_direction` | `1` | Tag animation direction (1: horizontal, 0: vertical) | \ No newline at end of file diff --git a/docs/v0.13.1/visuals/effects.md b/docs/v0.13.1/visuals/effects.md deleted file mode 100644 index 23c1f206..00000000 --- a/docs/v0.13.1/visuals/effects.md +++ /dev/null @@ -1,82 +0,0 @@ ---- -title: Window Effects -description: Add visual polish with blur, shadows, and opacity. ---- - -## Blur - -Blur creates a frosted glass effect for transparent windows. - -| Setting | Default | Description | -| :--- | :--- | :--- | -| `blur` | `0` | Enable blur for windows. | -| `blur_layer` | `0` | Enable blur for layer surfaces (like bars/docks). | -| `blur_optimized` | `1` | Caches the wallpaper and blur background, significantly reducing GPU usage. Disabling it will significantly increase GPU consumption and may cause rendering lag. **Highly recommended.** | -| `blur_params_radius` | `5` | The strength (radius) of the blur. | -| `blur_params_num_passes` | `1` | Number of passes. Higher = smoother but more expensive. | -| `blur_params_noise` | `0.02` | Blur noise level. | -| `blur_params_brightness` | `0.9` | Blur brightness adjustment. | -| `blur_params_contrast` | `0.9` | Blur contrast adjustment. | -| `blur_params_saturation` | `1.2` | Blur saturation adjustment. | - -> **Warning:** Blur has a relatively high impact on performance. If your hardware is limited, it is not recommended to enable it. If you experience lag with blur on, ensure `blur_optimized=1` — disabling it will significantly increase GPU consumption and may cause rendering lag. To disable blur entirely, set `blur=0`. - ---- - -## Shadows - -Drop shadows help distinguish floating windows from the background. - -| Setting | Default | Description | -| :--- | :--- | :--- | -| `shadows` | `0` | Enable shadows. | -| `layer_shadows` | `0` | Enable shadows for layer surfaces. | -| `shadow_only_floating` | `1` | Only draw shadows for floating windows (saves performance). | -| `shadows_size` | `10` | Size of the shadow. | -| `shadows_blur` | `15` | Shadow blur amount. | -| `shadows_position_x` | `0` | Shadow X offset. | -| `shadows_position_y` | `0` | Shadow Y offset. | -| `shadowscolor` | `0x000000ff` | Color of the shadow. | - -```ini -# Example shadows configuration -shadows=1 -layer_shadows=1 -shadow_only_floating=1 -shadows_size=12 -shadows_blur=15 -shadows_position_x=0 -shadows_position_y=0 -shadowscolor=0x000000ff -``` - ---- - -## Opacity & Corner Radius - -Control the transparency and roundness of your windows. - -| Setting | Default | Description | -| :--- | :--- | :--- | -| `border_radius` | `0` | Window corner radius in pixels. | -| `border_radius_location_default` | `0` | Corner radius location: `0` (all), `1` (top-left), `2` (top-right), `3` (bottom-left), `4` (bottom-right), `5` (closest corner). | -| `no_radius_when_single` | `0` | Disable radius if only one window is visible. | -| `focused_opacity` | `1.0` | Opacity for the active window (0.0 - 1.0). | -| `unfocused_opacity` | `1.0` | Opacity for inactive windows (0.0 - 1.0). | - -```ini -# Window corner radius in pixels -border_radius=0 - -# Corner radius location (0=all, 1=top-left, 2=top-right, 3=bottom-left, 4=bottom-right) -border_radius_location_default=0 - -# Disable radius if only one window is visible -no_radius_when_single=0 - -# Opacity for the active window (0.0 - 1.0) -focused_opacity=1.0 - -# Opacity for inactive windows -unfocused_opacity=1.0 -``` diff --git a/docs/v0.13.1/visuals/index.mdx b/docs/v0.13.1/visuals/index.mdx deleted file mode 100644 index f71ae2f8..00000000 --- a/docs/v0.13.1/visuals/index.mdx +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: Visuals -description: Customize borders, colors, effects, and animations. -icon: Palette ---- - -Customize the look of your desktop. - -<Cards> - -<Card href="/docs/visuals/theming" title="Theming" description="Borders, colors, and cursor" /> - -<Card href="/docs/visuals/status-bar" title="Status Bar" description="Built-in status bar" /> - -<Card href="/docs/visuals/effects" title="Effects" description="Blur, shadows, rounded corners" /> - -<Card href="/docs/visuals/animations" title="Animations" description="Window and tag animations" /> - -</Cards> \ No newline at end of file diff --git a/docs/v0.13.1/visuals/meta.json b/docs/v0.13.1/visuals/meta.json deleted file mode 100644 index 58723c4e..00000000 --- a/docs/v0.13.1/visuals/meta.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "title": "Visuals", - "pages": ["theming", "status-bar", "effects", "animations"] -} diff --git a/docs/v0.13.1/visuals/status-bar.md b/docs/v0.13.1/visuals/status-bar.md deleted file mode 100644 index f2924e83..00000000 --- a/docs/v0.13.1/visuals/status-bar.md +++ /dev/null @@ -1,141 +0,0 @@ ---- -title: Status Bar -description: Configure Waybar for mangowm. ---- - -## Module Configuration - -mangowm is compatible with Waybar's `ext/workspaces` module (Wayland standard) or the `dwl/tags` module. We recommend `ext/workspaces` for the best experience. - -> **Tip:** You can also use the `dwl/tags` module, but `ext/workspaces` provides better integration with mangowm's features. The `ext/workspaces` module requires **Waybar > 0.14.0**. - -### `config.jsonc` - -Add the following to your Waybar configuration: - -```jsonc -{ - "modules-left": [ - "ext/workspaces", - "dwl/window" - ], - "ext/workspaces": { - "format": "{icon}", - "ignore-hidden": true, - "on-click": "activate", - "on-click-right": "deactivate", - "sort-by-id": true - }, - "dwl/window": { - "format": "[{layout}] {title}" - } -} -``` - -## Styling - -You can style the tags using standard CSS in `style.css`. - -### `style.css` - -```css -#workspaces { - border-radius: 4px; - border-width: 2px; - border-style: solid; - border-color: #c9b890; - margin-left: 4px; - padding-left: 10px; - padding-right: 6px; - background: rgba(40, 40, 40, 0.76); -} - -#workspaces button { - border: none; - background: none; - box-shadow: inherit; - text-shadow: inherit; - color: #ddca9e; - padding: 1px; - padding-left: 1px; - padding-right: 1px; - margin-right: 2px; - margin-left: 2px; -} - -#workspaces button.hidden { - color: #9e906f; - background-color: transparent; -} - -#workspaces button.visible { - color: #ddca9e; -} - -#workspaces button:hover { - color: #d79921; -} - -#workspaces button.active { - background-color: #ddca9e; - color: #282828; - margin-top: 5px; - margin-bottom: 5px; - padding-top: 1px; - padding-bottom: 0px; - border-radius: 3px; -} - -#workspaces button.urgent { - background-color: #ef5e5e; - color: #282828; - margin-top: 5px; - margin-bottom: 5px; - padding-top: 1px; - padding-bottom: 0px; - border-radius: 3px; -} - -#tags { - background-color: transparent; -} - -#tags button { - background-color: #fff; - color: #a585cd; -} - -#tags button:not(.occupied):not(.focused) { - font-size: 0; - min-width: 0; - min-height: 0; - margin: -17px; - padding: 0; - color: transparent; - background-color: transparent; -} - -#tags button.occupied { - background-color: #fff; - color: #cdc885; -} - -#tags button.focused { - background-color: rgb(186, 142, 213); - color: #fff; -} - -#tags button.urgent { - background: rgb(171, 101, 101); - color: #fff; -} - -#window { - background-color: rgb(237, 196, 147); - color: rgb(63, 37, 5); -} -``` - -## Complete Configuration Example - -> **Tip:** You can find a complete Waybar configuration for mangowm at [waybar-config](https://github.com/DreamMaoMao/waybar-config). \ No newline at end of file diff --git a/docs/v0.13.1/visuals/theming.md b/docs/v0.13.1/visuals/theming.md deleted file mode 100644 index 676c575b..00000000 --- a/docs/v0.13.1/visuals/theming.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -title: Theming -description: Customize the visual appearance of borders, colors, and the cursor. ---- - -## Dimensions - -Control the sizing of window borders and gaps. - -| Setting | Default | Description | -| :--- | :--- | :--- | -| `borderpx` | `4` | Border width in pixels. | -| `gappih` | `5` | Horizontal inner gap (between windows). | -| `gappiv` | `5` | Vertical inner gap. | -| `gappoh` | `10` | Horizontal outer gap (between windows and screen edges). | -| `gappov` | `10` | Vertical outer gap. | - -## Colors - -Colors are defined in `0xRRGGBBAA` hex format. - -```ini -# Background color of the root window -rootcolor=0x323232ff - -# Inactive window border -bordercolor=0x444444ff - -# Drop shadow when dragging windows -dropcolor=0x8FBA7C55 - -# Split window border color in manual dwindle layout -splitcolor=0xEB441EFF - -# Active window border -focuscolor=0xc66b25ff - -# Urgent window border (alerts) -urgentcolor=0xad401fff -``` - -### State-Specific Colors - -You can also color-code windows based on their state: - -| State | Config Key | Default Color | -| :--- | :--- | :--- | -| Maximized | `maximizescreencolor` | `0x89aa61ff` | -| Scratchpad | `scratchpadcolor` | `0x516c93ff` | -| Global | `globalcolor` | `0xb153a7ff` | -| Overlay | `overlaycolor` | `0x14a57cff` | - -> **Tip:** For scratchpad window sizing, see [Scratchpad](/docs/window-management/scratchpad) configuration. - -## Cursor Theme - -Set the size and theme of your mouse cursor. - -```ini -cursor_size=24 -cursor_theme=Adwaita -``` diff --git a/docs/v0.13.1/window-management/index.mdx b/docs/v0.13.1/window-management/index.mdx deleted file mode 100644 index b96c5891..00000000 --- a/docs/v0.13.1/window-management/index.mdx +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: Window Management -description: Layouts, rules, and window behavior. -icon: LayoutGrid ---- - -Window management with layouts, rules, and scratchpad support. - -<Cards> - -<Card href="/docs/window-management/layouts" title="Layouts" description="Tile, scroller, monocle, grid, deck" /> - -<Card href="/docs/window-management/rules" title="Rules" description="Window rules and conditions" /> - -<Card href="/docs/window-management/overview" title="Overview" description="Window states and properties" /> - -<Card href="/docs/window-management/scratchpad" title="Scratchpad" description="Quick access to applications" /> - -</Cards> \ No newline at end of file diff --git a/docs/v0.13.1/window-management/layouts.md b/docs/v0.13.1/window-management/layouts.md deleted file mode 100644 index 45b08ec2..00000000 --- a/docs/v0.13.1/window-management/layouts.md +++ /dev/null @@ -1,135 +0,0 @@ ---- -title: Layouts -description: Configure and switch between different window layouts. ---- - -## Supported Layouts - -mangowm supports a variety of layouts that can be assigned per tag. - -- `tile` -- `scroller` -- `monocle` -- `grid` -- `deck` -- `center_tile` -- `vertical_tile` -- `right_tile` -- `vertical_scroller` -- `vertical_grid` -- `vertical_deck` -- `dwindle` -- `fair` -- `vertical_fair` - ---- - -## Scroller Layout - -The Scroller layout positions windows in a scrollable strip, similar to PaperWM. - -### Configuration - -| Setting | Default | Description | -| :--- | :--- | :--- | -| `scroller_structs` | `20` | Width reserved on sides when window ratio is 1. | -| `scroller_default_proportion` | `0.9` | Default width proportion for new windows. | -| `scroller_focus_center` | `0` | Always center the focused window (1 = enable). | -| `scroller_prefer_center` | `0` | Center focused window only if it was outside the view. | -| `scroller_prefer_overspread` | `1` | Allow windows to overspread when there's extra space. | -| `edge_scroller_pointer_focus` | `1` | Focus windows even if partially off-screen. | -| `scroller_proportion_preset` | `0.5,0.8,1.0` | Presets for cycling window widths. | -| `scroller_ignore_proportion_single` | `1` | Ignore proportion adjustments for single windows. | -| `scroller_default_proportion_single` | `1.0` | Default proportion for single windows in scroller. **Requires `scroller_ignore_proportion_single=0` to take effect.** | - -> **Warning:** `scroller_prefer_overspread`, `scroller_focus_center`, and `scroller_prefer_center` interact with each other. Their priority order is: -> -> **scroller_prefer_overspread > scroller_focus_center > scroller_prefer_center** -> -> To ensure a lower-priority setting takes effect, you must set all higher-priority options to `0`. - -```ini -# Example scroller configuration -scroller_structs=20 -scroller_default_proportion=0.9 -scroller_focus_center=0 -scroller_prefer_center=0 -scroller_prefer_overspread=1 -edge_scroller_pointer_focus=1 -scroller_default_proportion_single=1.0 -scroller_proportion_preset=0.5,0.8,1.0 -``` - ---- - -## Master-Stack Layouts - -These settings apply to layouts like `tile` and `center_tile`. - -| Setting | Default | Description | -| :--- | :--- | :--- | -| `new_is_master` | `1` | New windows become the master window. | -| `default_mfact` | `0.55` | The split ratio between master and stack areas. | -| `default_nmaster` | `1` | Number of allowed master windows. | -| `smartgaps` | `0` | Disable gaps when only one window is present. | -| `center_master_overspread` | `0` | (Center Tile) Master spreads across screen if no stack exists. | -| `center_when_single_stack` | `1` | (Center Tile) Center master when only one stack window exists. | - -```ini -# Example master-stack configuration -new_is_master=1 -smartgaps=0 -default_mfact=0.55 -default_nmaster=1 -``` - ---- - -## Dwindle Layout - -The Dwindle layout arranges windows as a binary tree of recursive splits. Each new window splits the focused window's container, producing a spiral-like tiling. - -### Configuration - -| Setting | Default | Description | -| :--- | :--- | :--- | -| `dwindle_split_ratio` | `0.5` | Ratio used for new splits (`0.05`–`0.95`). | -| `dwindle_smart_split` | `0` | Pick the split axis from the cursor's position inside the focused window. The new window appears on the cursor's side. | -| `dwindle_hsplit` | `1` | Side-by-side splits: where the new window goes. `0` = follow cursor, `1` = right, `2` = left. | -| `dwindle_vsplit` | `1` | Top/bottom splits: where the new window goes. `0` = follow cursor, `1` = below, `2` = above. | -| `dwindle_preserve_split` | `0` | Keep the sibling's split orientation when a window is closed. | -| `dwindle_smart_resize` | `0` | When dragging to resize, move the split toward the cursor regardless of which side was grabbed. | -| `dwindle_drop_simple_split` | `1` | Drag-to-tile drop preview. `1` = 2-zone preview matching `dwindle_split_ratio`, `0` = 4-quadrant preview. | -| `dwindle_manual_split` | `0` | Manually split windows mode. | - -```ini -# Example dwindle configuration -dwindle_split_ratio=0.5 -dwindle_smart_split=0 -dwindle_hsplit=0 -dwindle_vsplit=0 -dwindle_preserve_split=0 -dwindle_smart_resize=0 -dwindle_drop_simple_split=1 -``` - ---- - -## Switching Layouts -| Setting | Default | Description | -| :--- | :--- | :--- | -| `circle_layout` | - | A comma-separated list of layouts `switch_layout` cycles through,the value sample:`tile,scroller`. | - -You can switch layouts dynamically or set a default for specific tags using [Tag Rules](/docs/window-management/rules#tag-rules). - -**Keybinding Examples:** - -```ini -# Cycle through layouts -circle_layout=grid,scroller,tile -bind=SUPER,n,switch_layout - -# Set specific layout -bind=SUPER,t,setlayout,tile -bind=SUPER,s,setlayout,scroller -``` diff --git a/docs/v0.13.1/window-management/meta.json b/docs/v0.13.1/window-management/meta.json deleted file mode 100644 index e0937d14..00000000 --- a/docs/v0.13.1/window-management/meta.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "title": "Window Management", - "pages": ["layouts", "rules", "overview", "scratchpad"] -} diff --git a/docs/v0.13.1/window-management/overview.md b/docs/v0.13.1/window-management/overview.md deleted file mode 100644 index 7da6e690..00000000 --- a/docs/v0.13.1/window-management/overview.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -title: Overview -description: Configure the overview mode for window navigation. ---- - -## Overview Settings - -| Setting | Type | Default | Description | -| :--- | :--- | :--- | :--- | -| `hotarea_size` | integer | `10` | Hot area size in pixels. | -| `enable_hotarea` | integer | `1` | Enable hot areas (0: disable, 1: enable). | -| `hotarea_corner` | integer | `2` | Hot area corner (0: top-left, 1: top-right, 2: bottom-left, 3: bottom-right). | -| `ov_tab_mode` | integer | `0` | Overview tab mode (0: disable, 1: enable). | -| `overviewgappi` | integer | `5` | Inner gap in overview mode. | -| `overviewgappo` | integer | `30` | Outer gap in overview mode. | - -### Setting Descriptions - -- `enable_hotarea` — Toggles overview when the cursor enters the configured corner. -- `hotarea_size` — Size of the hot area trigger zone in pixels. -- `hotarea_corner` — Corner that triggers the hot area (0: top-left, 1: top-right, 2: bottom-left, 3: bottom-right). -- `ov_tab_mode` — Circles focus through windows in overview; releasing the mod key exits overview. - -### Mouse Interaction in Overview - -When in overview mode: - -- **Left mouse button** — Jump to (focus) a window. -- **Right mouse button** — Close a window. \ No newline at end of file diff --git a/docs/v0.13.1/window-management/rules.md b/docs/v0.13.1/window-management/rules.md deleted file mode 100644 index 4a295157..00000000 --- a/docs/v0.13.1/window-management/rules.md +++ /dev/null @@ -1,250 +0,0 @@ ---- -title: Rules -description: Define behavior for specific windows, tags, and layers. ---- - -## Window Rules - -Window rules allow you to set specific properties (floating, opacity, size, animations, etc.) for applications based on their `appid` or `title`. You can set all parameters in one line, and if you both set appid and title, the window will only follow the rules when appid and title both match. - -**Format:** - -```ini -windowrule=Parameter:Values,title:Values -windowrule=Parameter:Values,Parameter:Values,appid:Values,title:Values -``` - -### State & Behavior Parameters - -| Parameter | Type | Values | Description | -| :--- | :--- | :--- | :--- | -| `appid` | string | Any | Match by application ID, supports regex | -| `title` | string | Any | Match by window title, supports regex | -| `isfloating` | integer | `0` / `1` | Force floating state | -| `isfullscreen` | integer | `0` / `1` | Force fullscreen state | -| `isfakefullscreen` | integer | `0` / `1` | Force fake-fullscreen state (window stays constrained) | -| `isglobal` | integer | `0` / `1` | Open as global window (sticky across tags) | -| `isoverlay` | integer | `0` / `1` | Make it always in top layer | -| `isopensilent` | integer | `0` / `1` | Open without focus | -| `istagsilent` | integer | `0` / `1` | Don't focus if client is not in current view tag | -| `force_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 | -| `noopenmaximized` | integer | `0` / `1` | Window does not open as maximized mode | -| `single_scratchpad` | integer | `0` / `1` (default 1) | Only show one out of named scratchpads or the normal scratchpad | -| `allow_shortcuts_inhibit` | integer | `0` / `1` (default 1) | Allow shortcuts to be inhibited by clients | -| `indleinhibit_when_focus` | integer | `0` / `1` (default 0) | Automatically keep idle inhibit active when this window is focused | - -### Geometry & Position - -| Parameter | Type | Values | Description | -| :--- | :--- | :--- | :--- | -| `width` | float | 0-9999 | Window width when it becomes a floating window,if the value below 1, it will be the percentage of the screen width,otherwise it will be the pixel value | -| `height` | float | 0-9999 | Window height when it becomes a floating window,if the value below 1, it will be the percentage of the screen height,otherwise it will be the pixel value | -| `offsetx` | integer | -999-999 | X offset from center (%), 100 is the edge of screen with outer gap | -| `offsety` | integer | -999-999 | Y offset from center (%), 100 is the edge of screen with outer gap | -| `monitor` | string | Any | Assign to monitor by [monitor spec](/docs/configuration/monitors#monitor-spec-format) (name, make, model, or serial) | -| `tags` | integer | 1-9 | Assign to specific tag | -| `no_force_center` | integer | `0` / `1` | Window does not force center | -| `isnosizehint` | integer | `0` / `1` | Don't use min size and max size for size hints | - -### Visuals & Decoration - -| Parameter | Type | Values | Description | -| :--- | :--- | :--- | :--- | -| `noblur` | integer | `0` / `1` | Window does not have blur effect | -| `isnoborder` | integer | `0` / `1` | Remove window border | -| `isnoshadow` | integer | `0` / `1` | Not apply shadow | -| `isnoradius` | integer | `0` / `1` | Not apply corner radius | -| `isnoanimation` | integer | `0` / `1` | Not apply animation | -| `focused_opacity` | integer | `0` / `1` | Window focused opacity | -| `unfocused_opacity` | integer | `0` / `1` | Window unfocused opacity | -| `allow_csd` | integer | `0` / `1` | Allow client side decoration | - -> **Tip:** For detailed visual effects configuration, see the [Window Effects](/docs/visuals/effects) page for blur, shadows, and opacity settings. - -### Layout & Scroller - -| Parameter | Type | Values | Description | -| :--- | :--- | :--- | :--- | -| `scroller_proportion` | float | 0.1-1.0 | Set scroller proportion | -| `scroller_proportion_single` | float | 0.1-1.0 | Set scroller auto adjust proportion when it is single window | - -> **Tip:** For comprehensive layout configuration, see the [Layouts](/docs/window-management/layouts) page for all layout options and detailed settings. - -### Animation - -| Parameter | Type | Values | Description | -| :--- | :--- | :--- | :--- | -| `animation_type_open` | string | zoom, slide, fade, none | Set open animation | -| `animation_type_close` | string | zoom, slide, fade, none | Set close animation | -| `nofadein` | integer | `0` / `1` | Window ignores fade-in animation | -| `nofadeout` | integer | `0` / `1` | Window ignores fade-out animation | - -> **Tip:** For detailed animation configuration, see the [Animations](/docs/visuals/animations) page for available types and settings. - -### Terminal & Swallowing - -| Parameter | Type | Values | Description | -| :--- | :--- | :--- | :--- | -| `isterm` | integer | `0` / `1` | A new GUI window will replace the isterm window when it is opened | -| `noswallow` | integer | `0` / `1` | The window will not replace the isterm window | - -### Global & Special Windows - -| Parameter | Type | Values | Description | -| :--- | :--- | :--- | :--- | -| `globalkeybinding` | string | `[mod combination][-][key]` | Global keybinding (only works for Wayland apps) | -| `isunglobal` | integer | `0` / `1` | Open as unmanaged global window (for desktop pets or camera windows) | -| `isnamedscratchpad` | integer | `0` / `1` | 0: disable, 1: named scratchpad | - -> **Tip:** For scratchpad usage, see the [Scratchpad](/docs/window-management/scratchpad) page for detailed configuration examples. - -### Performance & Tearing - -| Parameter | Type | Values | Description | -| :--- | :--- | :--- | :--- | -| `force_tearing` | integer | `0` / `1` | Set window to tearing state, refer to [Tearing](/docs/configuration/monitors#tearing-game-mode) | - -### Examples - -```ini -# Set specific window size and position -windowrule=width:1000,height:900,appid:yesplaymusic,title:Demons - -# Global keybindings for OBS Studio -windowrule=globalkeybinding:ctrl+alt-o,appid:com.obsproject.Studio -windowrule=globalkeybinding:ctrl+alt+n,appid:com.obsproject.Studio -windowrule=isopensilent:1,appid:com.obsproject.Studio - -# Force tearing for games -windowrule=force_tearing:1,title:vkcube -windowrule=force_tearing:1,title:Counter-Strike 2 - -# Named scratchpad for file manager -windowrule=isnamedscratchpad:1,width:1280,height:800,appid:st-yazi - -# Custom opacity for specific apps -windowrule=focused_opacity:0.8,appid:firefox -windowrule=unfocused_opacity:0.6,appid:foot - -# Disable blur for selection tools -windowrule=noblur:1,appid:slurp - -# Position windows relative to screen center -windowrule=offsetx:20,offsety:-30,width:800,height:600,appid:alacritty - -# Send to specific tag and monitor -windowrule=tags:9,monitor:HDMI-A-1,appid:discord - -# Terminal swallowing setup -windowrule=isterm:1,appid:st -windowrule=noswallow:1,appid:foot - -# Disable client-side decorations -windowrule=allow_csd:1,appid:firefox - -# Unmanaged global window (desktop pets, camera) -windowrule=isunglobal:1,appid:cheese - -# Named scratchpad toggle -bind=alt,h,toggle_named_scratchpad,st-yazi,none,st -c st-yazi -e yazi -``` - ---- - -## Tag Rules - -You can set all parameters in one line. If only `id` is set, the rule is followed when the id matches. If any of `monitor_name`, `monitor_make`, `monitor_model`, or `monitor_serial` are set, the rule is followed only if **all** of the set monitor fields match. - -> **Warning:** Layouts set in tag rules have a higher priority than monitor rule layouts. - -**Format:** - -```ini -tagrule=id:Values,Parameter:Values,Parameter:Values -tagrule=id:Values,monitor_name:eDP-1,Parameter:Values,Parameter:Values -tagrule=id:Values,monitor_make:xxx,monitor_model:xxx,Parameter:Values -``` - -> **Tip:** See [Layouts](/docs/window-management/layouts#supported-layouts) for detailed descriptions of each layout type. - -| Parameter | Type | Values | Description | -| :--- | :--- | :--- | :--- | -| `id` | integer | 0-9 | Match by tag id, 0 means the ~0 tag | -| `monitor_name` | string | monitor name | Match by monitor name | -| `monitor_make` | string | monitor make | Match by monitor manufacturer | -| `monitor_model` | string | monitor model | Match by monitor model | -| `monitor_serial` | string | monitor serial | Match by monitor serial number | -| `layout_name` | string | layout name | Layout name to set | -| `no_render_border` | integer | `0` / `1` | Disable render border | -| `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 | - -### Examples - -```ini -# Set layout for specific tags -tagrule=id:1,layout_name:scroller -tagrule=id:2,layout_name:scroller - -# Limit to specific monitor -tagrule=id:1,monitor_name:eDP-1,layout_name:scroller -tagrule=id:2,monitor_name:eDP-1,layout_name:scroller - -# Persistent tags (1-4) with layout assignment -tagrule=id:1,no_hide:1,layout_name:scroller -tagrule=id:2,no_hide:1,layout_name:scroller -tagrule=id:3,monitor_name:eDP-1,no_hide:1,layout_name:scroller -tagrule=id:4,monitor_name:eDP-1,no_hide:1,layout_name:scroller - -# Advanced tag configuration with master layout settings -tagrule=id:5,layout_name:tile,nmaster:2,mfact:0.6 -tagrule=id:6,monitor_name:HDMI-A-1,layout_name:monocle,no_render_border:1 -``` - -> **Tip:** For Waybar configuration with persistent tags, see [Status Bar](/docs/visuals/status-bar) documentation. - ---- - -## Layer Rules - -You can set all parameters in one line. Target "layer shell" surfaces like status bars (`waybar`), launchers (`rofi`), or notification daemons. - -**Format:** - -```ini -layerrule=layer_name:Values,Parameter:Values,Parameter:Values -``` - -> **Tip:** You can use `mmsg -e` to get the last open layer name for debugging. - -| Parameter | Type | Values | Description | -| :--- | :--- | :--- | :--- | -| `layer_name` | string | layer name | Match name of layer, supports regex | -| `animation_type_open` | string | slide, zoom, fade, none | Set open animation | -| `animation_type_close` | string | slide, zoom, fade, none | Set close animation | -| `noblur` | integer | `0` / `1` | Disable blur | -| `noanim` | integer | `0` / `1` | Disable layer animation | -| `noshadow` | integer | `0` / `1` | Disable layer shadow | - -> **Tip:** For animation types, see [Animations](/docs/visuals/animations#animation-types). For visual effects, see [Window Effects](/docs/visuals/effects). - -### Examples - -```ini -# No blur or animation for slurp selection layer (avoids occlusion and ghosting in screenshots) -layerrule=noanim:1,noblur:1,layer_name:selection - -# Zoom animation for Rofi with multiple parameters -layerrule=animation_type_open:zoom,noanim:0,layer_name:rofi - -# Disable animations and shadows for notification daemon -layerrule=noanim:1,noshadow:1,layer_name:swaync - -# Multiple effects for launcher -layerrule=animation_type_open:slide,animation_type_close:fade,noblur:1,layer_name:wofi -``` diff --git a/docs/v0.13.1/window-management/scratchpad.md b/docs/v0.13.1/window-management/scratchpad.md deleted file mode 100644 index 398182f9..00000000 --- a/docs/v0.13.1/window-management/scratchpad.md +++ /dev/null @@ -1,73 +0,0 @@ ---- -title: Scratchpad -description: Manage hidden "scratchpad" windows for quick access. ---- - -mangowm supports two types of scratchpads: the standard pool (Sway-like) and named scratchpads. - -## Standard Scratchpad - -Any window can be sent to the "scratchpad" pile, which hides it. You can then cycle through them. - -**Keybindings:** - -```ini -# Send current window to scratchpad -bind=SUPER,i,minimized - -# Toggle (show/hide) the scratchpad -bind=ALT,z,toggle_scratchpad - -# Retrieve window from scratchpad (restore) -bind=SUPER+SHIFT,i,restore_minimized -``` - ---- - -## Named Scratchpad - -Named scratchpads are bound to specific keys and applications. When triggered, mangowm will either launch the app (if not running) or toggle its visibility. - -**1. Define the Window Rule** - -You must identify the app using a unique `appid` or `title` and mark it as a named scratchpad. The application must support setting a custom appid or title at launch. Common examples: - -- `st -c my-appid` — sets the appid -- `kitty -T my-title` — sets the window title -- `foot --app-id my-appid` — sets the appid - -Use `none` as a placeholder when you only want to match by one field. - -```ini -# Match by appid -windowrule=isnamedscratchpad:1,width:1280,height:800,appid:st-yazi - -# Match by title -windowrule=isnamedscratchpad:1,width:1000,height:700,title:kitty-scratch -``` - -**2. Bind the Toggle Key** - -Format: `bind=MOD,KEY,toggle_named_scratchpad,appid,title,command` - -Use `none` for whichever field you are not matching on. - -```ini -# Match by appid: launch 'st' with class 'st-yazi' running 'yazi' -bind=alt,h,toggle_named_scratchpad,st-yazi,none,st -c st-yazi -e yazi - -# Match by title: launch 'kitty' with window title 'kitty-scratch' -bind=alt,k,toggle_named_scratchpad,none,kitty-scratch,kitty -T kitty-scratch -``` - ---- - -## Appearance - -You can customize the size of scratchpad windows relative to the screen. - -```ini -scratchpad_width_ratio=0.8 -scratchpad_height_ratio=0.9 -scratchpadcolor=0x516c93ff -``` diff --git a/docs/(git)/visuals/animations.md b/docs/visuals/animations.md similarity index 100% rename from docs/(git)/visuals/animations.md rename to docs/visuals/animations.md diff --git a/docs/(git)/visuals/effects.md b/docs/visuals/effects.md similarity index 100% rename from docs/(git)/visuals/effects.md rename to docs/visuals/effects.md diff --git a/docs/(git)/visuals/index.mdx b/docs/visuals/index.mdx similarity index 100% rename from docs/(git)/visuals/index.mdx rename to docs/visuals/index.mdx diff --git a/docs/(git)/visuals/meta.json b/docs/visuals/meta.json similarity index 100% rename from docs/(git)/visuals/meta.json rename to docs/visuals/meta.json diff --git a/docs/(git)/visuals/status-bar.md b/docs/visuals/status-bar.md similarity index 100% rename from docs/(git)/visuals/status-bar.md rename to docs/visuals/status-bar.md diff --git a/docs/(git)/visuals/theming.md b/docs/visuals/theming.md similarity index 100% rename from docs/(git)/visuals/theming.md rename to docs/visuals/theming.md diff --git a/docs/(git)/window-management/index.mdx b/docs/window-management/index.mdx similarity index 100% rename from docs/(git)/window-management/index.mdx rename to docs/window-management/index.mdx diff --git a/docs/(git)/window-management/layouts.md b/docs/window-management/layouts.md similarity index 100% rename from docs/(git)/window-management/layouts.md rename to docs/window-management/layouts.md diff --git a/docs/(git)/window-management/meta.json b/docs/window-management/meta.json similarity index 100% rename from docs/(git)/window-management/meta.json rename to docs/window-management/meta.json diff --git a/docs/(git)/window-management/overview.md b/docs/window-management/overview.md similarity index 100% rename from docs/(git)/window-management/overview.md rename to docs/window-management/overview.md diff --git a/docs/(git)/window-management/rules.md b/docs/window-management/rules.md similarity index 100% rename from docs/(git)/window-management/rules.md rename to docs/window-management/rules.md diff --git a/docs/(git)/window-management/scratchpad.md b/docs/window-management/scratchpad.md similarity index 100% rename from docs/(git)/window-management/scratchpad.md rename to docs/window-management/scratchpad.md From 094a2eac22c0183f8201716d97f850efa7b8af9a Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 26 May 2026 22:24:58 +0800 Subject: [PATCH 263/328] opt: focusid allow cross tag --- src/action/client.h | 28 +++++++++++++++++++++++++++- src/dispatch/bind_define.h | 2 +- src/ext-protocol/foreign-toplevel.h | 18 +----------------- 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/src/action/client.h b/src/action/client.h index 43eb2c7b..b7960d70 100644 --- a/src/action/client.h +++ b/src/action/client.h @@ -60,4 +60,30 @@ void client_tile_resize(Client *c, struct wlr_box geo, int32_t interact) { } static uint32_t next_client_id = 0; -uint32_t generate_client_id(void) { return ++next_client_id; } \ No newline at end of file +uint32_t generate_client_id(void) { return ++next_client_id; } + +void client_active(Client *c) { + uint32_t target; + + if (client_is_unmanaged(c)) { + focusclient(c, 1); + return; + } + + if (c->swallowing || !c->mon) + return; + + if (c->isminimized) { + c->is_in_scratchpad = 0; + c->isnamedscratchpad = 0; + c->is_scratchpad_show = 0; + setborder_color(c); + show_hide_client(c); + arrange(c->mon, true, false); + return; + } + + target = get_tags_first_tag(c->tags); + view_in_mon(&(Arg){.ui = target}, true, c->mon, true); + focusclient(c, 1); +} \ No newline at end of file diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index d6537d89..c2af5fcb 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -2000,6 +2000,6 @@ int32_t focusid(const Arg *arg) { return 0; Client *c = arg->tc; - focusclient(c, 1); + client_active(c); return 0; } \ No newline at end of file diff --git a/src/ext-protocol/foreign-toplevel.h b/src/ext-protocol/foreign-toplevel.h index 0c09e0c2..4fdbbe98 100644 --- a/src/ext-protocol/foreign-toplevel.h +++ b/src/ext-protocol/foreign-toplevel.h @@ -4,24 +4,8 @@ static struct wlr_foreign_toplevel_manager_v1 *foreign_toplevel_manager; void handle_foreign_activate_request(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, foreign_activate_request); - uint32_t target; - if (c->swallowing || !c->mon) - return; - - if (c->isminimized) { - c->is_in_scratchpad = 0; - c->isnamedscratchpad = 0; - c->is_scratchpad_show = 0; - setborder_color(c); - show_hide_client(c); - arrange(c->mon, true, false); - return; - } - - target = get_tags_first_tag(c->tags); - view_in_mon(&(Arg){.ui = target}, true, c->mon, true); - focusclient(c, 1); + client_active(c); } void handle_foreign_maximize_request(struct wl_listener *listener, void *data) { From 1a7f843f5efed41dad6a03bc31faf4a9012a4227 Mon Sep 17 00:00:00 2001 From: xtheeq <atheeq.rhxn@gmail.com> Date: Wed, 27 May 2026 08:21:36 +0530 Subject: [PATCH 264/328] fix(docs): meta file generation override on workflow --- .github/workflows/sync-website.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/sync-website.yml b/.github/workflows/sync-website.yml index f1cb7115..b3c10af0 100644 --- a/.github/workflows/sync-website.yml +++ b/.github/workflows/sync-website.yml @@ -46,6 +46,10 @@ jobs: if [ -d "/tmp/docs-$tag/docs" ]; then name="v${tag#v}" cp -r "/tmp/docs-$tag/docs" "$TARGET/$name" + jq --arg title "$name" --arg desc "$name release" \ + '.title = $title | .description = $desc | .root = true' \ + "$TARGET/$name/meta.json" > "$TARGET/$name/meta.json.tmp" \ + && mv "$TARGET/$name/meta.json.tmp" "$TARGET/$name/meta.json" fi git worktree remove /tmp/docs-"$tag" done From 545445c13aca0d8e005dfe6936014117cd354ef8 Mon Sep 17 00:00:00 2001 From: xtheeq <atheeq.rhxn@gmail.com> Date: Wed, 27 May 2026 08:27:24 +0530 Subject: [PATCH 265/328] fix(docs): screenshot geometry to use new ipc in full script --- docs/screenshot.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/screenshot.md b/docs/screenshot.md index 4ae84c3d..7f85224a 100644 --- a/docs/screenshot.md +++ b/docs/screenshot.md @@ -173,7 +173,7 @@ case "${1:-fullscreen}" in g=$(slurp -d); [ -z "$g" ] && exit 1 grim -g "$g" "$filepath" ;; window) - g=$(mmsg -x | awk '/x / {x=$3} /y / {y=$3} /width / {w=$3} /height / {h=$3} END {print x","y" "w"x"h}') + g=$(mmsg get focusing-client | jq -r '"\(.x),\(.y) \(.width)x\(.height)"') [ -z "$g" ] && exit 1 grim -g "$g" "$filepath" ;; freeze) From 21cd9dcf8005d55d154c15a85c30b1cf51899582 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 27 May 2026 22:22:48 +0800 Subject: [PATCH 266/328] update docs --- docs/visuals/theming.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/visuals/theming.md b/docs/visuals/theming.md index 36c75c57..676c575b 100644 --- a/docs/visuals/theming.md +++ b/docs/visuals/theming.md @@ -27,7 +27,7 @@ rootcolor=0x323232ff bordercolor=0x444444ff # Drop shadow when dragging windows -dropcolor=0xBFBA7C55 +dropcolor=0x8FBA7C55 # Split window border color in manual dwindle layout splitcolor=0xEB441EFF From c3683bde741450ce2f390b154f34c8d423bdf68a Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 28 May 2026 12:45:30 +0800 Subject: [PATCH 267/328] opt: optimize ipc event send --- src/config/parse_config.h | 2 +- src/dispatch/bind_define.h | 16 +++--- src/ext-protocol/dwl-ipc.h | 4 +- src/ipc/ipc.h | 14 ----- src/layout/arrange.h | 2 +- src/mango.c | 114 +++++++++++++++++++++++++++---------- 6 files changed, 97 insertions(+), 55 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 74b36ba4..b15e34a5 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -4008,6 +4008,6 @@ void reset_option(void) { int32_t reload_config(const Arg *arg) { parse_config(); reset_option(); - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); return 1; } diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index c2af5fcb..b834ff5f 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -572,7 +572,7 @@ int32_t setlayout(const Arg *arg) { selmon->pertag->ltidxs[selmon->pertag->curtag] = &layouts[jk]; clear_fullscreen_and_maximized_state(selmon); arrange(selmon, false, false); - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); return 0; } } @@ -586,7 +586,7 @@ int32_t setkeymode(const Arg *arg) { } else { keymode.isdefault = false; } - printstatus(); + printstatus(IPC_WATCH_KEYMODE); return 1; } @@ -1038,7 +1038,7 @@ int32_t switch_keyboard_layout(const Arg *arg) { wlr_seat_keyboard_notify_modifiers(seat, &tkb->modifiers); } - printstatus(); + printstatus(IPC_WATCH_KB_LAYOUT); return 0; } @@ -1082,7 +1082,7 @@ int32_t switch_layout(const Arg *arg) { } clear_fullscreen_and_maximized_state(selmon); arrange(selmon, false, false); - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); return 0; } @@ -1093,7 +1093,7 @@ int32_t switch_layout(const Arg *arg) { jk == LENGTH(layouts) - 1 ? &layouts[0] : &layouts[jk + 1]; clear_fullscreen_and_maximized_state(selmon); arrange(selmon, false, false); - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); return 0; } } @@ -1451,7 +1451,7 @@ int32_t toggletag(const Arg *arg) { focusclient(focustop(selmon), 1); arrange(selmon, false, false); } - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); return 0; } @@ -1477,7 +1477,7 @@ int32_t toggleview(const Arg *arg) { } arrange(selmon, false, false); } - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); return 0; } @@ -1649,7 +1649,7 @@ int32_t comboview(const Arg *arg) { view(&(Arg){.ui = newtags}, false); } - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); return 0; } diff --git a/src/ext-protocol/dwl-ipc.h b/src/ext-protocol/dwl-ipc.h index 1d57a20e..53d96309 100644 --- a/src/ext-protocol/dwl-ipc.h +++ b/src/ext-protocol/dwl-ipc.h @@ -239,7 +239,7 @@ void dwl_ipc_output_set_client_tags(struct wl_client *client, if (selmon == monitor) focusclient(focustop(monitor), 1); arrange(selmon, false, false); - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); } void dwl_ipc_output_set_layout(struct wl_client *client, @@ -258,7 +258,7 @@ void dwl_ipc_output_set_layout(struct wl_client *client, monitor->pertag->ltidxs[monitor->pertag->curtag] = &layouts[index]; clear_fullscreen_and_maximized_state(monitor); arrange(monitor, false, false); - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); } void dwl_ipc_output_set_tags(struct wl_client *client, diff --git a/src/ipc/ipc.h b/src/ipc/ipc.h index 0a36da4a..9e7c8e3f 100644 --- a/src/ipc/ipc.h +++ b/src/ipc/ipc.h @@ -9,20 +9,6 @@ #include <sys/un.h> #include <unistd.h> -enum ipc_watch_type { - IPC_WATCH_NONE, - IPC_WATCH_MONITOR, - IPC_WATCH_CLIENT, - IPC_WATCH_TAGS, - IPC_WATCH_ALL_MONITORS, - IPC_WATCH_ALL_TAGS, - IPC_WATCH_ALL_CLIENTS, - IPC_WATCH_KEYMODE, - IPC_WATCH_KB_LAYOUT, - IPC_WATCH_LAST_OPEN_SURFACE, - IPC_WATCH_FOCUSING_CLIENT -}; - struct ipc_watch_client { struct wl_list link; int fd; diff --git a/src/layout/arrange.h b/src/layout/arrange.h index 3343316e..7223b430 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -1225,5 +1225,5 @@ arrange(Monitor *m, bool want_animation, bool from_view) { checkidleinhibitor(NULL); } - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); } diff --git a/src/mango.c b/src/mango.c index 61cf861a..1bc39153 100644 --- a/src/mango.c +++ b/src/mango.c @@ -151,6 +151,11 @@ #define BAKED_POINTS_COUNT 256 +#define IPC_WATCH_ARRANGGE \ + IPC_WATCH_MONITOR | IPC_WATCH_CLIENT | IPC_WATCH_TAGS | \ + IPC_WATCH_ALL_MONITORS | IPC_WATCH_ALL_TAGS | IPC_WATCH_ALL_CLIENTS | \ + IPC_WATCH_LAST_OPEN_SURFACE | IPC_WATCH_FOCUSING_CLIENT + /* enums */ enum { TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT }; @@ -197,6 +202,20 @@ enum seat_config_shortcuts_inhibit { SHORTCUTS_INHIBIT_ENABLE, }; +enum ipc_watch_type { + IPC_WATCH_NONE = 0, + IPC_WATCH_MONITOR = 1 << 0, + IPC_WATCH_CLIENT = 1 << 1, + IPC_WATCH_TAGS = 1 << 2, + IPC_WATCH_ALL_MONITORS = 1 << 3, + IPC_WATCH_ALL_TAGS = 1 << 4, + IPC_WATCH_ALL_CLIENTS = 1 << 5, + IPC_WATCH_KEYMODE = 1 << 6, + IPC_WATCH_KB_LAYOUT = 1 << 7, + IPC_WATCH_LAST_OPEN_SURFACE = 1 << 8, + IPC_WATCH_FOCUSING_CLIENT = 1 << 9, +}; + typedef struct Pertag Pertag; typedef struct Monitor Monitor; typedef struct Client Client; @@ -448,6 +467,8 @@ typedef struct { struct wl_listener modifiers; struct wl_listener key; struct wl_listener destroy; + + u_int32_t layout_index; } KeyboardGroup; typedef struct { @@ -701,7 +722,7 @@ static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, static void outputmgrtest(struct wl_listener *listener, void *data); static void pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, uint32_t time); -static void printstatus(void); +static void printstatus(enum ipc_watch_type type); static void quitsignal(int32_t signo); static void powermgrsetmode(struct wl_listener *listener, void *data); static void rendermon(struct wl_listener *listener, void *data); @@ -2609,7 +2630,7 @@ void closemon(Monitor *m) { } if (selmon) { focusclient(focustop(selmon), 1); - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); } } @@ -3369,7 +3390,7 @@ void createmon(struct wl_listener *listener, void *data) { add_workspace_by_tag(i, m); } - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); } void // fix for 0.5 @@ -3866,7 +3887,7 @@ void focusclient(Client *c, int32_t lift) { client_activate_surface(old_keyboard_focus_surface, 0); } } - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); if (!c) { @@ -4247,6 +4268,14 @@ void keypressmod(struct wl_listener *listener, void *data) { wlr_seat_keyboard_notify_modifiers( seat, &group->wlr_group->keyboard.modifiers); } + + xkb_layout_index_t current = xkb_state_serialize_layout( + group->wlr_group->keyboard.xkb_state, XKB_STATE_LAYOUT_EFFECTIVE); + + if (current != group->layout_index) { + group->layout_index = current; + printstatus(IPC_WATCH_KB_LAYOUT); + } } void pending_kill_client(Client *c) { @@ -4533,7 +4562,7 @@ mapnotify(struct wl_listener *listener, void *data) { // make sure the animation is open type c->is_pending_open_animation = true; resize(c, c->geom, 0); - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); } void maximizenotify(struct wl_listener *listener, void *data) { @@ -4934,7 +4963,9 @@ void pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, } // 修改printstatus函数,接受掩码参数 -void printstatus(void) { wl_signal_emit(&mango_print_status, NULL); } +void printstatus(enum ipc_watch_type type) { + wl_signal_emit(&mango_print_status, &type); +} void powermgrsetmode(struct wl_listener *listener, void *data) { struct wlr_output_power_v1_set_mode_event *event = data; @@ -5222,7 +5253,7 @@ run(char *startup_cmd) { if (fd_set_nonblock(STDOUT_FILENO) < 0) close(STDOUT_FILENO); - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); /* At this point the outputs are initialized, choose initial selmon * based on cursor position, and set default cursor image */ @@ -5383,7 +5414,7 @@ setfloating(Client *c, int32_t floating) { } setborder_color(c); - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); } void reset_maximizescreen_size(Client *c) { @@ -5709,18 +5740,36 @@ void create_output(struct wlr_backend *backend, void *data) { // 修改信号处理函数,接收掩码参数 void handle_print_status(struct wl_listener *listener, void *data) { - ipc_notify_keymode(); - ipc_notify_kb_layout(); - ipc_notify_focusing_client(); - ipc_notify_all_tags(); - ipc_notify_all_clients(); - ipc_notify_all_monitors(); + enum ipc_watch_type type = *(enum ipc_watch_type *)data; - Client *c = NULL; - wl_list_for_each(c, &clients, link) { - if (c->iskilling) - continue; - ipc_notify_client(c); + if (type & IPC_WATCH_KEYMODE) { + ipc_notify_keymode(); + } + if (type & IPC_WATCH_KB_LAYOUT) { + ipc_notify_kb_layout(); + } + if (type & IPC_WATCH_FOCUSING_CLIENT) { + ipc_notify_focusing_client(); + } + if (type & IPC_WATCH_ALL_TAGS) { + ipc_notify_all_tags(); + } + if (type & IPC_WATCH_ALL_CLIENTS) { + ipc_notify_all_clients(); + } + if (type & + (IPC_WATCH_ALL_MONITORS | IPC_WATCH_KEYMODE | IPC_WATCH_KB_LAYOUT | + IPC_WATCH_FOCUSING_CLIENT | IPC_WATCH_TAGS)) { + ipc_notify_all_monitors(); + } + + if (type & IPC_WATCH_CLIENT) { + Client *c = NULL; + wl_list_for_each(c, &clients, link) { + if (c->iskilling) + continue; + ipc_notify_client(c); + } } Monitor *m = NULL; @@ -5729,9 +5778,16 @@ void handle_print_status(struct wl_listener *listener, void *data) { continue; } - ipc_notify_monitor(m); - ipc_notify_tags(m); - ipc_notify_last_surface_ws_name(m); + if (type & IPC_WATCH_MONITOR) { + ipc_notify_monitor(m); + } + if (type & IPC_WATCH_TAGS) { + ipc_notify_tags(m); + } + + if (type & IPC_WATCH_LAST_OPEN_SURFACE) { + ipc_notify_last_surface_ws_name(m); + } dwl_ext_workspace_printstatus(m); dwl_ipc_output_printstatus(m); @@ -6095,7 +6151,7 @@ void tag_client(const Arg *arg, Client *target_client) { } focusclient(target_client, 1); - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); } // 目标窗口有其他窗口和它同个tag就返回0 @@ -6414,7 +6470,7 @@ void unmapnotify(struct wl_listener *listener, void *data) { c->stack_proportion = 0.0f; wlr_scene_node_destroy(&c->scene->node); - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); motionnotify(0, NULL, 0, 0, 0, 0); } @@ -6568,7 +6624,7 @@ void updatetitle(struct wl_listener *listener, void *data) { if (title && c->foreign_toplevel) wlr_foreign_toplevel_handle_v1_set_title(c->foreign_toplevel, title); if (c == focustop(c->mon)) - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); } void // 17 fix to 0.5 @@ -6588,7 +6644,7 @@ urgent(struct wl_listener *listener, void *data) { c->isurgent = 1; if (client_surface(c)->mapped) setborder_color(c); - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); } } @@ -6647,7 +6703,7 @@ toggleseltags: if (changefocus) focusclient(focustop(m), 1); arrange(m, want_animation, true); - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); } void view(const Arg *arg, bool want_animation) { @@ -6817,7 +6873,7 @@ void activatex11(struct wl_listener *listener, void *data) { arrange(c->mon, false, false); } - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); } void configurex11(struct wl_listener *listener, void *data) { @@ -6906,7 +6962,7 @@ void sethints(struct wl_listener *listener, void *data) { return; c->isurgent = xcb_icccm_wm_hints_get_urgency(c->surface.xwayland->hints); - printstatus(); + printstatus(IPC_WATCH_ARRANGGE); if (c->isurgent && surface && surface->mapped) setborder_color(c); From ce7fc8a4eadb3bd78418ee04f136bac74dacd4e6 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 28 May 2026 18:40:33 +0800 Subject: [PATCH 268/328] fix: fix no monitor map client --- src/mango.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/mango.c b/src/mango.c index 1bc39153..5def179e 100644 --- a/src/mango.c +++ b/src/mango.c @@ -203,17 +203,17 @@ enum seat_config_shortcuts_inhibit { }; enum ipc_watch_type { - IPC_WATCH_NONE = 0, - IPC_WATCH_MONITOR = 1 << 0, - IPC_WATCH_CLIENT = 1 << 1, - IPC_WATCH_TAGS = 1 << 2, - IPC_WATCH_ALL_MONITORS = 1 << 3, - IPC_WATCH_ALL_TAGS = 1 << 4, - IPC_WATCH_ALL_CLIENTS = 1 << 5, - IPC_WATCH_KEYMODE = 1 << 6, - IPC_WATCH_KB_LAYOUT = 1 << 7, - IPC_WATCH_LAST_OPEN_SURFACE = 1 << 8, - IPC_WATCH_FOCUSING_CLIENT = 1 << 9, + IPC_WATCH_NONE = 0, + IPC_WATCH_MONITOR = 1 << 0, + IPC_WATCH_CLIENT = 1 << 1, + IPC_WATCH_TAGS = 1 << 2, + IPC_WATCH_ALL_MONITORS = 1 << 3, + IPC_WATCH_ALL_TAGS = 1 << 4, + IPC_WATCH_ALL_CLIENTS = 1 << 5, + IPC_WATCH_KEYMODE = 1 << 6, + IPC_WATCH_KB_LAYOUT = 1 << 7, + IPC_WATCH_LAST_OPEN_SURFACE = 1 << 8, + IPC_WATCH_FOCUSING_CLIENT = 1 << 9, }; typedef struct Pertag Pertag; @@ -4555,7 +4555,7 @@ mapnotify(struct wl_listener *listener, void *data) { // set border color setborder_color(c); - if (c->mon->isoverview && config.ov_no_resize) { + if (c->mon && c->mon->isoverview && config.ov_no_resize) { overview_backup_surface(c); } From d702cc2c222b5c285c6583c1057478882b3fb08e Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 28 May 2026 23:34:14 +0800 Subject: [PATCH 269/328] opt: ipc send keyboard layout fullname --- src/ipc/ipc.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ipc/ipc.h b/src/ipc/ipc.h index 9e7c8e3f..5bbffcc3 100644 --- a/src/ipc/ipc.h +++ b/src/ipc/ipc.h @@ -66,8 +66,8 @@ static const char *ipc_get_layout_str(void) { xkb_layout_index_t current = xkb_state_serialize_layout( keyboard->xkb_state, XKB_STATE_LAYOUT_EFFECTIVE); static char layout[32]; - get_layout_abbr(layout, - xkb_keymap_layout_get_name(keyboard->keymap, current)); + const char *name = xkb_keymap_layout_get_name(keyboard->keymap, current); + snprintf(layout, sizeof(layout), "%s", name ? name : ""); return layout; } From 8e1de189ce4a77e57cf8723880112f009c83797d Mon Sep 17 00:00:00 2001 From: Ctas <cdrom@tutamail.com> Date: Fri, 29 May 2026 15:12:47 +0300 Subject: [PATCH 270/328] fix: use uint32_t instead of u_int32_t for musl libc compatibility --- src/mango.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mango.c b/src/mango.c index 5def179e..4d08abd5 100644 --- a/src/mango.c +++ b/src/mango.c @@ -468,7 +468,7 @@ typedef struct { struct wl_listener key; struct wl_listener destroy; - u_int32_t layout_index; + uint32_t layout_index; } KeyboardGroup; typedef struct { From 6584ed2fd52235bd93ac54b5f23663699cab9c23 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 29 May 2026 21:04:35 +0800 Subject: [PATCH 271/328] bump version to 0.14.0 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 270c4688..5de13158 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('mango', ['c', 'cpp'], - version : '0.13.1', + version : '0.14.0', ) subdir('protocols') From 404d664fca5780849941550f626e9622cbf918d0 Mon Sep 17 00:00:00 2001 From: Justan <justanw.business@proton.me> Date: Fri, 29 May 2026 13:20:19 -0400 Subject: [PATCH 272/328] update AerynOS install instructions --- docs/installation.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/installation.md b/docs/installation.md index 48c667c5..1fed83d8 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -18,6 +18,7 @@ You can install it using the `moss` package manager: ```bash sudo moss install mangowm ``` +* The Default config will be located at `/usr/share/defaults/mango`. --- From 0aec251028403ecbe917f3a7981ea70801bb2dd0 Mon Sep 17 00:00:00 2001 From: Yappaholic <sav.boyar@gmail.com> Date: Fri, 29 May 2026 23:39:07 +0300 Subject: [PATCH 273/328] guix: fix cjson import module --- mangowm.scm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mangowm.scm b/mangowm.scm index c7cd32e9..fcd7f583 100644 --- a/mangowm.scm +++ b/mangowm.scm @@ -10,7 +10,7 @@ #:use-module (gnu packages pciutils) #:use-module (gnu packages admin) #:use-module (gnu packages pcre) - #:use-module (gnu packages cjson) + #:use-module (gnu packages javascript) #:use-module (gnu packages xorg) #:use-module (gnu packages build-tools) #:use-module (gnu packages ninja) From 6849e5b4db50598867050596155c24a9ab51e69a Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 30 May 2026 11:51:39 +0800 Subject: [PATCH 274/328] opt: more reasonable method to set scoket flag --- src/ipc/ipc.h | 55 ++++++++++++++++++++++++++++++++------------------- src/mango.c | 9 ++++++++- 2 files changed, 43 insertions(+), 21 deletions(-) diff --git a/src/ipc/ipc.h b/src/ipc/ipc.h index 5bbffcc3..1694eb8b 100644 --- a/src/ipc/ipc.h +++ b/src/ipc/ipc.h @@ -217,7 +217,7 @@ static cJSON *build_monitor_tags_response(Monitor *m) { static void send_static_json(int fd, const char *json_str) { size_t len = strlen(json_str); - send(fd, json_str, len, MSG_NOSIGNAL); + send(fd, json_str, len, 0); } /* ---------- 一次性命令处理 ---------- */ @@ -413,7 +413,7 @@ static void handle_command(int client_fd, const char *cmd_raw) { char *msg = malloc(len + 2); if (msg) { snprintf(msg, len + 2, "%s\n", json_str); - send(client_fd, msg, len + 1, MSG_NOSIGNAL); + send(client_fd, msg, len + 1, 0); free(msg); } free(json_str); @@ -434,7 +434,7 @@ static void ipc_notify_json_to_fd(int fd, cJSON *json) { return; } snprintf(msg, len + 2, "%s\n", str); - if (send(fd, msg, len + 1, MSG_NOSIGNAL) < 0) { + if (send(fd, msg, len + 1, 0) < 0) { struct ipc_watch_client *wc, *tmp; wl_list_for_each_safe(wc, tmp, &watch_clients, link) { if (wc->fd == fd) { @@ -462,7 +462,7 @@ static int ipc_watch_data_handler(int fd, uint32_t mask, void *data) { } if (mask & WL_EVENT_READABLE) { char buf[64]; - ssize_t n = recv(fd, buf, sizeof(buf), MSG_DONTWAIT); + ssize_t n = recv(fd, buf, sizeof(buf), 0); if (n == 0 || (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK)) { ipc_remove_watch_client(wc); } @@ -643,8 +643,7 @@ static int ipc_handle_client_data(int fd, uint32_t mask, void *data) { available = client->buf_cap - client->buf_len; } - ssize_t n = recv(fd, client->buf + client->buf_len, available - 1, - MSG_DONTWAIT); + ssize_t n = recv(fd, client->buf + client->buf_len, available - 1, 0); if (n <= 0) goto cleanup; @@ -683,8 +682,12 @@ static int ipc_handle_connection(int fd, uint32_t mask, void *data) { if (client_fd < 0) return 0; + // 设置 O_NONBLOCK int flags = fcntl(client_fd, F_GETFL, 0); fcntl(client_fd, F_SETFL, flags | O_NONBLOCK); + // 设置 FD_CLOEXEC + flags = fcntl(client_fd, F_GETFD, 0); + fcntl(client_fd, F_SETFD, flags | FD_CLOEXEC); struct ipc_client_state *client = calloc(1, sizeof(*client)); client->fd = client_fd; @@ -715,7 +718,7 @@ void ipc_notify_monitor(Monitor *m) { snprintf(json_str, len + 2, "%s\n", raw); free(raw); } - if (send(wc->fd, json_str, len + 1, MSG_NOSIGNAL) < 0) + if (send(wc->fd, json_str, len + 1, 0) < 0) ipc_remove_watch_client(wc); } } @@ -731,10 +734,8 @@ void ipc_notify_last_surface_ws_name(Monitor *m) { if (wc->type != IPC_WATCH_LAST_OPEN_SURFACE) continue; - /* 匹配具体 monitor 名称,或空名称表示默认 selmon */ bool match = false; if (wc->target.monitor.name[0] == '\0') { - /* 订阅的是 selmon */ if (m == selmon) match = true; } else { @@ -759,7 +760,7 @@ void ipc_notify_last_surface_ws_name(Monitor *m) { snprintf(json_str, len + 2, "%s\n", raw); free(raw); } - if (send(wc->fd, json_str, len + 1, MSG_NOSIGNAL) < 0) + if (send(wc->fd, json_str, len + 1, 0) < 0) ipc_remove_watch_client(wc); } free(json_str); @@ -790,7 +791,7 @@ void ipc_notify_focusing_client(void) { snprintf(json_str, len + 2, "%s\n", raw); free(raw); } - if (send(wc->fd, json_str, len + 1, MSG_NOSIGNAL) < 0) + if (send(wc->fd, json_str, len + 1, 0) < 0) ipc_remove_watch_client(wc); } } @@ -814,7 +815,7 @@ void ipc_notify_client(Client *c) { snprintf(json_str, len + 2, "%s\n", raw); free(raw); } - if (send(wc->fd, json_str, len + 1, MSG_NOSIGNAL) < 0) + if (send(wc->fd, json_str, len + 1, 0) < 0) ipc_remove_watch_client(wc); } } @@ -840,7 +841,7 @@ void ipc_notify_tags(Monitor *m) { snprintf(json_str, len + 2, "%s\n", raw); free(raw); } - if (send(wc->fd, json_str, len + 1, MSG_NOSIGNAL) < 0) + if (send(wc->fd, json_str, len + 1, 0) < 0) ipc_remove_watch_client(wc); } } @@ -870,7 +871,7 @@ void ipc_notify_all_monitors(void) { snprintf(json_str, len + 2, "%s\n", raw); free(raw); } - if (send(wc->fd, json_str, len + 1, MSG_NOSIGNAL) < 0) + if (send(wc->fd, json_str, len + 1, 0) < 0) ipc_remove_watch_client(wc); } } @@ -900,7 +901,7 @@ void ipc_notify_all_clients(void) { snprintf(json_str, len + 2, "%s\n", raw); free(raw); } - if (send(wc->fd, json_str, len + 1, MSG_NOSIGNAL) < 0) + if (send(wc->fd, json_str, len + 1, 0) < 0) ipc_remove_watch_client(wc); } } @@ -925,7 +926,7 @@ void ipc_notify_all_tags(void) { snprintf(json_str, len + 2, "%s\n", raw); free(raw); } - if (send(wc->fd, json_str, len + 1, MSG_NOSIGNAL) < 0) + if (send(wc->fd, json_str, len + 1, 0) < 0) ipc_remove_watch_client(wc); } } @@ -951,7 +952,7 @@ void ipc_notify_keymode(void) { snprintf(json_str, len + 2, "%s\n", raw); free(raw); } - if (send(wc->fd, json_str, len + 1, MSG_NOSIGNAL) < 0) + if (send(wc->fd, json_str, len + 1, 0) < 0) ipc_remove_watch_client(wc); } } @@ -977,7 +978,7 @@ void ipc_notify_kb_layout(void) { snprintf(json_str, len + 2, "%s\n", raw); free(raw); } - if (send(wc->fd, json_str, len + 1, MSG_NOSIGNAL) < 0) + if (send(wc->fd, json_str, len + 1, 0) < 0) ipc_remove_watch_client(wc); } } @@ -1000,11 +1001,25 @@ void ipc_init(struct wl_event_loop *event_loop) { snprintf(ipc_socket_path, sizeof(ipc_socket_path), "%s/mango-%d.sock", xdg_runtime, getpid()); - ipc_sock_fd = - socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); + ipc_sock_fd = socket(AF_UNIX, SOCK_STREAM, 0); if (ipc_sock_fd < 0) return; + // 设置 FD_CLOEXEC + int flags = fcntl(ipc_sock_fd, F_GETFD, 0); + if (flags == -1 || fcntl(ipc_sock_fd, F_SETFD, flags | FD_CLOEXEC) == -1) { + wlr_log(WLR_ERROR, "failed to set FD_CLOEXEC on IPC socket"); + close(ipc_sock_fd); + return; + } + // 设置 O_NONBLOCK + flags = fcntl(ipc_sock_fd, F_GETFL, 0); + if (flags == -1 || fcntl(ipc_sock_fd, F_SETFL, flags | O_NONBLOCK) == -1) { + wlr_log(WLR_ERROR, "failed to set O_NONBLOCK on IPC socket"); + close(ipc_sock_fd); + return; + } + struct sockaddr_un addr = {.sun_family = AF_UNIX}; strncpy(addr.sun_path, ipc_socket_path, sizeof(addr.sun_path) - 1); diff --git a/src/mango.c b/src/mango.c index 4d08abd5..0b55dfa1 100644 --- a/src/mango.c +++ b/src/mango.c @@ -5805,13 +5805,20 @@ void setup(void) { } init_baked_points(); - int32_t drm_fd, i, sig[] = {SIGCHLD, SIGINT, SIGTERM, SIGPIPE}; + int32_t drm_fd, i; + int32_t sig[] = {SIGCHLD, SIGINT, + SIGTERM}; // 不设置SIGPIPE,因为ipc发送失败不应该影响主程序 struct sigaction sa = {.sa_flags = SA_RESTART, .sa_handler = handlesig}; sigemptyset(&sa.sa_mask); for (i = 0; i < LENGTH(sig); i++) sigaction(sig[i], &sa, NULL); + // 单独为 SIGPIPE 设置忽略 + struct sigaction sa_pipe = {.sa_flags = 0, .sa_handler = SIG_IGN}; + sigemptyset(&sa_pipe.sa_mask); + sigaction(SIGPIPE, &sa_pipe, NULL); + wlr_log_init(config.log_level, NULL); /* The Wayland display is managed by libwayland. It handles accepting From a11cf12f28b76097972e7ca8c025cb3dd9e52931 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 31 May 2026 22:44:12 +0800 Subject: [PATCH 275/328] mmsg: add man page and usage message --- meson.build | 25 +++++------ mmsg/mmsg.1 | 122 ++++++++++++++++++++++++++++++++++++++++++++++++++++ mmsg/mmsg.c | 74 ++++++++++++++++++++++++++++--- 3 files changed, 200 insertions(+), 21 deletions(-) create mode 100644 mmsg/mmsg.1 diff --git a/meson.build b/meson.build index 5de13158..fdb847a7 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,4 @@ -project('mango', ['c', 'cpp'], +project('mango', ['c'], version : '0.14.0', ) @@ -24,10 +24,6 @@ if sysconfdir.startswith(prefix) and not is_nixos endif endif -# 打印调试信息,确认 sysconfdir 的值 -# message('prefix: ' + prefix) -# message('sysconfdir: ' + sysconfdir) - cc = meson.get_compiler('c') libm = cc.find_library('m') xcb = dependency('xcb', required : get_option('xwayland')) @@ -42,12 +38,11 @@ libscenefx_dep = dependency('scenefx-0.4',version: '>=0.4.1') pixman_dep = dependency('pixman-1') cjson_dep = dependency('libcjson') - -# 获取版本信息 +# version info git = find_program('git', required : false) is_git_repo = false -# 检查当前目录是否是 Git 仓库 +# check if current directory is a git repo if git.found() git_status = run_command(git, 'rev-parse', '--is-inside-work-tree', check : false) if git_status.returncode() == 0 and git_status.stdout().strip() == 'true' @@ -56,18 +51,18 @@ if git.found() endif if is_git_repo - # 如果是 Git 目录,获取 Commit Hash 和最新的 tag + # if current directory is a git repo, get commit hash and latest tag commit_hash = run_command(git, 'rev-parse', '--short', 'HEAD', check : false).stdout().strip() latest_tag = meson.project_version() version_with_hash = '@0@(@1@)'.format(latest_tag, commit_hash) else - # 如果不是 Git 目录,使用项目版本号和 "release" 字符串 + # if not a git repo, use project version and "release" string commit_hash = 'release' latest_tag = meson.project_version() version_with_hash = '@0@(@1@)'.format(latest_tag, commit_hash) endif -# 定义编译参数 +# define compilation args c_args = [ '-g', '-Wno-unused-function', @@ -77,7 +72,7 @@ c_args = [ '-DSYSCONFDIR="@0@"'.format('/etc'), ] -# 仅在 debug 选项启用时添加调试参数 +# add debug args only when debug option is enabled if get_option('asan') c_args += [ '-fsanitize=address', @@ -90,7 +85,7 @@ if xcb.found() and xlibs.found() c_args += '-DXWAYLAND' endif -# 链接参数(根据 debug 状态添加 ASAN) +# define link args link_args = [] if get_option('asan') link_args += '-fsanitize=address' @@ -135,7 +130,7 @@ wayland_scanner_private_code = generator( arguments: ['private-code', '@INPUT@', '@OUTPUT@'] ) -# 在 mmsg 目标中使用生成器 +# use generator in mmsg target executable('mmsg', 'mmsg/mmsg.c', wayland_scanner_private_code.process(dwl_ipc_protocol), @@ -150,8 +145,10 @@ executable('mmsg', ], ) +mandir = get_option('mandir') desktop_install_dir = join_paths(prefix, 'share/wayland-sessions') portal_install_dir = join_paths(prefix, 'share/xdg-desktop-portal') install_data('assets/mango.desktop', install_dir : desktop_install_dir) install_data('assets/mango-portals.conf', install_dir : portal_install_dir) install_data('assets/config.conf', install_dir : join_paths(sysconfdir, 'mango')) +install_data('mmsg/mmsg.1', install_dir : join_paths(mandir, 'man1')) \ No newline at end of file diff --git a/mmsg/mmsg.1 b/mmsg/mmsg.1 new file mode 100644 index 00000000..8a935f50 --- /dev/null +++ b/mmsg/mmsg.1 @@ -0,0 +1,122 @@ +.TH "mmsg" "1" "2026-05-31" "mmsg 0.14.0" "mmsg manual" +.SH NAME +mmsg \- send commands to the mango Wayland compositor and receive JSON responses +.SH SYNOPSIS +\fBmmsg\fR <command> [\fIargs\fR...] +.br +\fBmmsg\fR \-\-help | \-h | help +.SH DESCRIPTION +\fBmmsg\fR connects to a running \fBmango\fR(1) compositor via the UNIX domain socket +specified by the \fIMANGO_INSTANCE_SIGNATURE\fR environment variable. It sends +the given command and prints the JSON reply to standard output. In \fBwatch\fR +mode it keeps the connection open and prints a stream of updates as they +occur. +.PP +If \fB\-\-help\fR, \fB\-h\fR or \fBhelp\fR is given, a complete usage summary is printed and +the program exits with success. +.SH COMMANDS +.SS "One\-shot queries (get)" +All \fBget\fR commands print a single JSON object and then close the connection. +.TP +\fBget version\fR +Return compositor version. +.TP +\fBget keymode\fR +Return current keymode. +.TP +\fBget keyboardlayout\fR +Return current keyboard layout. +.TP +\fBget last_open_surface\fR [\fImonitor\fR] +Return the last open surface (application) for the given monitor. +If \fImonitor\fR is omitted the focused monitor is used. +.TP +\fBget monitor\fR <name> +Return details of the named monitor. +.TP +\fBget focusing\-client\fR +Return details of the currently focused client. +.TP +\fBget client\fR <id> +Return details of the client with the given numeric ID. +.TP +\fBget tag\fR <monitor> <index> +Return details of a specific tag (1\-based index) on the named monitor. +.TP +\fBget all\-clients\fR +List all clients. +.TP +\fBget all\-monitors\fR +List all monitors. +.TP +\fBget all\-tags\fR +List tags for all monitors. +.TP +\fBget tags\fR <monitor> +List tags for a specific monitor. +.TP +\fBdispatch\fR <func>[,arg...] [client,<id>] +Invoke an internal compositor function. +The function name and its arguments are separated by commas. +Optionally add \fBclient,<id>\fR (before or after the function) to target a +specific client. +.br +Examples: +.RS +.IP \(bu 2 +\fBmmsg dispatch togglefloating\fR +.IP \(bu 2 +\fBmmsg dispatch movewin,10,100\fR +.IP \(bu 2 +\fBmmsg dispatch movewin,10,100 client,4\fR +.RE +.SS "Persistent streams (watch)" +\fBWatch\fR commands keep the connection open and continuously output JSON +updates. The initial state is sent immediately, followed by updates whenever +relevant changes occur. +.TP +\fBwatch monitor\fR <name> +Stream updates for the named monitor. +.TP +\fBwatch focusing\-client\fR +Stream updates for the focused client. +.TP +\fBwatch client\fR <id> +Stream updates for the client with the given ID. +.TP +\fBwatch tags\fR <monitor> +Stream tag updates for the named monitor. +.TP +\fBwatch all\-monitors\fR +Stream updates for all monitors. +.TP +\fBwatch all\-tags\fR +Stream updates for all tags. +.TP +\fBwatch all\-clients\fR +Stream updates for all clients. +.TP +\fBwatch keymode\fR +Stream keymode changes. +.TP +\fBwatch keyboardlayout\fR +Stream keyboard layout changes. +.TP +\fBwatch last_open_surface\fR [\fImonitor\fR] +Stream last open surface changes for a specific monitor (or the focused one). +.SH ENVIRONMENT +.TP +\fIMANGO_INSTANCE_SIGNATURE\fR +Path to the compositor's IPC socket. Set automatically by \fBmango\fR(1). +If unset, \fBmmsg\fR prints an error and exits. +.SH EXIT STATUS +.TP +\fB0\fR +Success (or help message printed). +.TP +\fBEXIT_FAILURE\fR +An error occurred (connection refused, send/receive error, etc.). +.SH SEE ALSO +\fBmango\fR(1) +.SH BUGS +Report issues at the project's issue tracker. \ No newline at end of file diff --git a/mmsg/mmsg.c b/mmsg/mmsg.c index 10b24afc..adb0b539 100644 --- a/mmsg/mmsg.c +++ b/mmsg/mmsg.c @@ -7,7 +7,73 @@ #include <sys/un.h> #include <unistd.h> +static void usage(void) { + printf("Usage: mmsg <command> [args...]\n\n"); + printf("One-shot queries (get):\n"); + printf( + " get version Show compositor version\n"); + printf(" get keymode Show current keymode\n"); + printf(" get keyboardlayout Show current keyboard " + "layout\n"); + printf(" get last_open_surface [monitor] Show last open surface " + "(default focused monitor)\n"); + printf(" get monitor <name> Show monitor details\n"); + printf(" get focusing-client Show focused client " + "details\n"); + printf(" get client <id> Show client details by " + "ID\n"); + printf(" get tag <monitor> <index> Show tag details " + "(1‑based index)\n"); + printf(" get all-clients List all clients\n"); + printf(" get all-monitors List all monitors\n"); + printf(" get all-tags List all tags (all " + "monitors)\n"); + printf( + " get tags <monitor> List tags for a monitor\n"); + printf(" dispatch <func>[,arg...] [client,<id>] Call a compositor " + "function\n"); + printf(" <func> and arguments are separated by commas.\n"); + printf(" Add 'client,<id>' at the beginning or end to target a " + "specific client.\n"); + printf(" Examples:\n"); + printf(" dispatch togglefloating\n"); + printf(" dispatch movewin,10,100\n"); + printf(" dispatch movewin,10,100 client,4\n"); + printf("Persistent streams (watch):\n"); + printf( + " watch monitor <name> Stream monitor changes\n"); + printf(" watch focusing-client Stream focused client " + "changes\n"); + printf( + " watch client <id> Stream client changes\n"); + printf(" watch tags <monitor> Stream tag changes for " + "a monitor\n"); + printf(" watch all-monitors Stream all monitors " + "changes\n"); + printf( + " watch all-tags Stream all tags changes\n"); + printf(" watch all-clients Stream all clients " + "changes\n"); + printf( + " watch keymode Stream keymode changes\n"); + printf(" watch keyboardlayout Stream keyboard layout " + "changes\n"); + printf(" watch last_open_surface [monitor] Stream last open " + "surface changes\n\n"); + printf("Environment:\n"); + printf(" MANGO_INSTANCE_SIGNATURE IPC socket path (set by the " + "compositor)\n\n"); + printf("Run 'mmsg --help', '-h' or 'help' to see this message.\n"); +} + int main(int argc, char *argv[]) { + if (argc >= 2 && + (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0 || + strcmp(argv[1], "help") == 0)) { + usage(); + return EXIT_SUCCESS; + } + if (argc < 2) { fprintf(stderr, "Usage: mmsg <command> [args...]\n"); fprintf(stderr, " get <type> ... one-shot request\n"); @@ -37,7 +103,6 @@ int main(int argc, char *argv[]) { return EXIT_FAILURE; } - // 拼接命令,缓冲区大小 4096 以容纳较长参数 char cmd[4096] = {0}; int offset = 0; for (int i = 1; i < argc; i++) { @@ -51,7 +116,6 @@ int main(int argc, char *argv[]) { offset += n; } - // 添加换行符 int n = snprintf(cmd + offset, sizeof(cmd) - offset, "\n"); if (n < 0 || n >= (int)(sizeof(cmd) - offset)) { fprintf(stderr, "Error: command too long to append newline.\n"); @@ -59,14 +123,12 @@ int main(int argc, char *argv[]) { return EXIT_FAILURE; } - // 发送命令,使用 MSG_NOSIGNAL 避免 SIGPIPE if (send(sock, cmd, strlen(cmd), MSG_NOSIGNAL) < 0) { perror("send"); close(sock); return EXIT_FAILURE; } - // 将 socket 封装为行缓冲文件流,自动处理 TCP 拆包,按完整行读取 FILE *stream = fdopen(sock, "r"); if (!stream) { perror("fdopen"); @@ -74,7 +136,6 @@ int main(int argc, char *argv[]) { return EXIT_FAILURE; } - // 按行读取并输出,直到连接关闭(get 模式服务端主动 close)或出错 char *line = NULL; size_t len = 0; while (getline(&line, &len, stream) != -1) { @@ -82,11 +143,10 @@ int main(int argc, char *argv[]) { fflush(stdout); } - // 检查是否因读取错误退出(而非正常 EOF) if (ferror(stream)) { perror("recv"); free(line); - fclose(stream); // 关闭 stream 同时关闭 socket + fclose(stream); return EXIT_FAILURE; } From bea689ca0c1c859ab49a6a21e63034dc07c4ab80 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 3 Jun 2026 08:47:23 +0800 Subject: [PATCH 276/328] fix: exchange not work in vertical scroller --- src/mango.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mango.c b/src/mango.c index 0b55dfa1..47f12325 100644 --- a/src/mango.c +++ b/src/mango.c @@ -5154,7 +5154,8 @@ void exchange_two_client(Client *c1, Client *c2) { const Layout *layout1 = m1->pertag->ltidxs[m1->pertag->curtag]; const Layout *layout2 = m2->pertag->ltidxs[m2->pertag->curtag]; - if (layout1->id == SCROLLER || layout2->id == SCROLLER) { + if (layout1->id == SCROLLER || layout2->id == SCROLLER || + layout1->id == VERTICAL_SCROLLER || layout2->id == VERTICAL_SCROLLER) { exchange_two_scroller_clients(c1, c2); return; } From 13e9cfb2378e7c0d85e2e065df2031749ef7270d Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 3 Jun 2026 23:15:06 +0800 Subject: [PATCH 277/328] fix: crash when use error device pointer in tablet create --- src/ext-protocol/tablet.h | 45 ++++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/src/ext-protocol/tablet.h b/src/ext-protocol/tablet.h index 6aa7ae86..34c82dbf 100644 --- a/src/ext-protocol/tablet.h +++ b/src/ext-protocol/tablet.h @@ -22,6 +22,7 @@ static struct wlr_tablet_manager_v2 *tablet_mgr; struct Tablet { struct wlr_tablet_v2_tablet *tablet_v2; struct wl_listener destroy; + struct wlr_input_device *device; struct wl_list link; }; static struct wl_list tablets; @@ -38,6 +39,7 @@ struct TabletTool { struct TabletPad { struct wlr_tablet_v2_tablet_pad *pad_v2; + struct wlr_input_device *device; struct Tablet *tablet; struct wl_listener tablet_destroy; struct wl_listener attach; @@ -72,11 +74,18 @@ void createtablet(struct wlr_input_device *device) { return; } + tablet->device = device; tablet->tablet_v2 = wlr_tablet_create(tablet_mgr, seat, device); + + if (!tablet->tablet_v2) { + free(tablet); + return; + } + tablet->tablet_v2->wlr_tablet->data = tablet; tablet->destroy.notify = destroytablet; - wl_signal_add(&tablet->tablet_v2->wlr_device->events.destroy, - &tablet->destroy); + wl_signal_add(&tablet->device->events.destroy, &tablet->destroy); + if (libinput_device_config_send_events_get_modes(device_handle)) { libinput_device_config_send_events_set_mode(device_handle, config.send_events_mode); @@ -90,7 +99,7 @@ void createtablet(struct wlr_input_device *device) { wlr_libinput_get_device_handle(device)); struct TabletPad *tablet_pad; wl_list_for_each(tablet_pad, &tablet_pads, link) { - struct wlr_input_device *pad_device = tablet_pad->pad_v2->wlr_device; + struct wlr_input_device *pad_device = tablet_pad->device; if (!wlr_input_device_is_libinput(pad_device)) { continue; } @@ -129,8 +138,7 @@ void attach_tablet_pad(struct TabletPad *tablet_pad, struct Tablet *tablet) { wl_list_remove(&tablet_pad->tablet_destroy.link); tablet_pad->tablet_destroy.notify = tabletpadtabletdestroy; - wl_signal_add(&tablet->tablet_v2->wlr_device->events.destroy, - &tablet_pad->tablet_destroy); + wl_signal_add(&tablet->device->events.destroy, &tablet_pad->tablet_destroy); } void tabletpadattach(struct wl_listener *listener, void *data) { @@ -152,27 +160,38 @@ void createtabletpad(struct wlr_input_device *device) { wlr_log(WLR_ERROR, "could not allocate tablet_pad"); return; } + + tablet_pad->device = device; tablet_pad->pad_v2 = wlr_tablet_pad_create(tablet_mgr, seat, device); + + if (!tablet_pad->pad_v2) { + wlr_log(WLR_ERROR, "could not create tablet_pad_v2 wrapper"); + free(tablet_pad); + return; + } + tablet_pad->destroy.notify = destroytabletpad; tablet_pad->attach.notify = tabletpadattach; wl_list_init(&tablet_pad->tablet_destroy.link); - wl_signal_add(&tablet_pad->pad_v2->wlr_device->events.destroy, - &tablet_pad->destroy); + + wl_signal_add(&device->events.destroy, &tablet_pad->destroy); + wl_signal_add(&tablet_pad->pad_v2->wlr_pad->events.attach_tablet, &tablet_pad->attach); wl_list_insert(&tablet_pads, &tablet_pad->link); /* Search for a sibling tablet */ - if (!wlr_input_device_is_libinput(tablet_pad->pad_v2->wlr_device)) { + if (!wlr_input_device_is_libinput(device)) { /* We can only do this on libinput devices */ return; } struct libinput_device_group *group = libinput_device_get_device_group( - wlr_libinput_get_device_handle(tablet_pad->pad_v2->wlr_device)); + wlr_libinput_get_device_handle(device)); + struct Tablet *tablet; wl_list_for_each(tablet, &tablets, link) { - struct wlr_input_device *tablet_device = tablet->tablet_v2->wlr_device; + struct wlr_input_device *tablet_device = tablet->device; if (!wlr_input_device_is_libinput(tablet_device)) { continue; } @@ -249,11 +268,11 @@ void tablettoolmotion(struct TabletTool *tool, bool change_x, bool change_y, switch (tool->tool_v2->wlr_tool->type) { case WLR_TABLET_TOOL_TYPE_LENS: case WLR_TABLET_TOOL_TYPE_MOUSE: - wlr_cursor_move(cursor, tablet->tablet_v2->wlr_device, dx, dy); + wlr_cursor_move(cursor, tablet->device, dx, dy); break; default: - wlr_cursor_warp_absolute(cursor, tablet->tablet_v2->wlr_device, - change_x ? x : NAN, change_y ? y : NAN); + wlr_cursor_warp_absolute(cursor, tablet->device, change_x ? x : NAN, + change_y ? y : NAN); break; } From cc20d5cff0e21205718fa812d37c1efa3c2b0345 Mon Sep 17 00:00:00 2001 From: Davide Greco <grecodavide.work@proton.me> Date: Thu, 4 Jun 2026 10:42:26 +0800 Subject: [PATCH 278/328] feat: add force option in killclient --- docs/bindings/keys.md | 2 +- src/action/client.h | 6 ++++++ src/config/parse_config.h | 21 +++++++++++++++++++++ src/dispatch/bind_define.h | 6 +++++- src/mango.c | 1 + 5 files changed, 34 insertions(+), 2 deletions(-) diff --git a/docs/bindings/keys.md b/docs/bindings/keys.md index 5abc716b..be8757c2 100644 --- a/docs/bindings/keys.md +++ b/docs/bindings/keys.md @@ -88,7 +88,7 @@ bindr=Super,Super_L,spawn,rofi -show run | Command | Param | Description | | :--- | :--- | :--- | -| `killclient` | - | Close the focused window. | +| `killclient` | `force` | Close the focused window. If `force` is specified, sends `SIGKILL`. | | `togglefloating` | - | Toggle floating state. | | `toggle_all_floating` | - | Toggle all visible clients floating state. | | `togglefullscreen` | - | Toggle fullscreen. | diff --git a/src/action/client.h b/src/action/client.h index b7960d70..f374ca14 100644 --- a/src/action/client.h +++ b/src/action/client.h @@ -86,4 +86,10 @@ void client_active(Client *c) { target = get_tags_first_tag(c->tags); view_in_mon(&(Arg){.ui = target}, true, c->mon, true); focusclient(c, 1); +} + +void client_pending_force_kill(Client *c) { + if (!c) + return; + kill(c->pid, SIGKILL); } \ No newline at end of file diff --git a/src/config/parse_config.h b/src/config/parse_config.h index b15e34a5..5379ddca 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -559,6 +559,26 @@ int32_t parse_direction(const char *str) { } } +int32_t parse_force(const char *str) { + // 将输入字符串转换为小写 + char lowerStr[10]; + int32_t i = 0; + while (str[i] && i < 9) { + lowerStr[i] = tolower(str[i]); + i++; + } + lowerStr[i] = '\0'; + + // 根据转换后的小写字符串返回对应的枚举值 + if (strcmp(lowerStr, "unforce") == 0) { + return UNFORCE; + } else if (strcmp(lowerStr, "force") == 0) { + return FORCE; + } else { + return UNFORCE; + } +} + int32_t parse_fold_state(const char *str) { // 将输入字符串转换为小写 char lowerStr[10]; @@ -1029,6 +1049,7 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value, (*arg).i = atoi(arg_value); } else if (strcmp(func_name, "killclient") == 0) { func = killclient; + (*arg).i = parse_force(arg_value); } else if (strcmp(func_name, "centerwin") == 0) { func = centerwin; } else if (strcmp(func_name, "focuslast") == 0) { diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index b834ff5f..cf0f770a 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -345,7 +345,11 @@ int32_t setmfact(const Arg *arg) { int32_t killclient(const Arg *arg) { Client *c = arg->tc ? arg->tc : (selmon ? selmon->sel : NULL); if (c) { - pending_kill_client(c); + if (arg->i == FORCE) { + client_pending_force_kill(c); + } else { + pending_kill_client(c); + } } return 0; } diff --git a/src/mango.c b/src/mango.c index 47f12325..2f503a5e 100644 --- a/src/mango.c +++ b/src/mango.c @@ -190,6 +190,7 @@ enum { NONE, OPEN, MOVE, CLOSE, TAG, FOCUS, OPAFADEIN, OPAFADEOUT, OVERVIEW }; enum { UNFOLD, FOLD, INVALIDFOLD }; enum { PREV, NEXT }; enum { STATE_UNSPECIFIED = 0, STATE_ENABLED, STATE_DISABLED }; +enum { FORCE, UNFORCE }; enum tearing_mode { TEARING_DISABLED = 0, From 4e87e3b80d4053b9f8fcccc3f4ff477e44b80bc8 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 6 Jun 2026 11:20:19 +0800 Subject: [PATCH 279/328] opt: fix scan out support for fullscreen --- src/animation/client.h | 16 ++++++++++++++-- src/mango.c | 1 + 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/animation/client.h b/src/animation/client.h index 238da9fb..8aa39050 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -305,7 +305,8 @@ void client_draw_shadow(Client *c) { if (c->iskilling || !client_surface(c)->mapped || c->isnoshadow) return; - if (!config.shadows || (!c->isfloating && config.shadow_only_floating)) { + if (!config.shadows || c->isfullscreen || + (!c->isfloating && config.shadow_only_floating)) { if (c->shadow->node.enabled) wlr_scene_node_set_enabled(&c->shadow->node, false); return; @@ -405,7 +406,7 @@ void apply_split_border(Client *c, bool hit_no_border) { const Layout *layout = c->mon->pertag->ltidxs[c->mon->pertag->curtag]; if (hit_no_border || !ISTILED(c) || layout->id != DWINDLE || - !config.dwindle_manual_split) { + !config.dwindle_manual_split || c->isfullscreen) { if (c->splitindicator[0]->node.enabled) { wlr_scene_node_set_enabled(&c->splitindicator[0]->node, false); } @@ -492,6 +493,17 @@ void apply_border(Client *c) { if (!c || c->iskilling || !client_surface(c)->mapped) return; + if (c->isfullscreen) { + if (c->border->node.enabled) { + wlr_scene_node_set_enabled(&c->border->node, false); + } + return; + } else { + if (!c->border->node.enabled) { + wlr_scene_node_set_enabled(&c->border->node, true); + } + } + bool hit_no_border = check_hit_no_border(c); apply_split_border(c, hit_no_border); diff --git a/src/mango.c b/src/mango.c index 2f503a5e..74d4c196 100644 --- a/src/mango.c +++ b/src/mango.c @@ -5526,6 +5526,7 @@ 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); + } else { c->bw = c->isnoborder ? 0 : config.borderpx; if (c->isfloating) From 9459fe51fd91c1d5ac37e8df375dc81e234ae9f8 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 6 Jun 2026 12:30:44 +0800 Subject: [PATCH 280/328] bump version to 0.14.1 --- meson.build | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/meson.build b/meson.build index fdb847a7..553ba0e1 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('mango', ['c'], - version : '0.14.0', + version : '0.14.1', ) subdir('protocols') @@ -151,4 +151,4 @@ portal_install_dir = join_paths(prefix, 'share/xdg-desktop-portal') install_data('assets/mango.desktop', install_dir : desktop_install_dir) install_data('assets/mango-portals.conf', install_dir : portal_install_dir) install_data('assets/config.conf', install_dir : join_paths(sysconfdir, 'mango')) -install_data('mmsg/mmsg.1', install_dir : join_paths(mandir, 'man1')) \ No newline at end of file +install_data('mmsg/mmsg.1', install_dir : join_paths(mandir, 'man1')) From 3ab2780b84a19a6c861b53fb3eb24616208db81e Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 6 Jun 2026 18:16:08 +0800 Subject: [PATCH 281/328] fix: excrescent gap in fullscreen --- src/animation/client.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/animation/client.h b/src/animation/client.h index 8aa39050..f988651e 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -495,6 +495,7 @@ void apply_border(Client *c) { if (c->isfullscreen) { if (c->border->node.enabled) { + wlr_scene_node_set_position(&c->scene_surface->node, 0, 0); wlr_scene_node_set_enabled(&c->border->node, false); } return; From db8d22ae8e39fb53d766c33c974c0c6a5fe43359 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 6 Jun 2026 21:21:34 +0800 Subject: [PATCH 282/328] bump version to 0.14.2 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 553ba0e1..23b59d61 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('mango', ['c'], - version : '0.14.1', + version : '0.14.2', ) subdir('protocols') From ccb58a4f1ab95fb22a3b93395c306df854a4d475 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 7 Jun 2026 18:08:55 +0800 Subject: [PATCH 283/328] docs: fix some error im sample --- docs/window-management/rules.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/window-management/rules.md b/docs/window-management/rules.md index 41b4cc53..7e221bf1 100644 --- a/docs/window-management/rules.md +++ b/docs/window-management/rules.md @@ -115,7 +115,7 @@ windowrule=width:1000,height:900,appid:yesplaymusic,title:Demons # Global keybindings for OBS Studio windowrule=globalkeybinding:ctrl+alt-o,appid:com.obsproject.Studio -windowrule=globalkeybinding:ctrl+alt+n,appid:com.obsproject.Studio +windowrule=globalkeybinding:ctrl+alt-n,appid:com.obsproject.Studio windowrule=isopensilent:1,appid:com.obsproject.Studio # Force tearing for games From 27f4f64173b143baf85d8c000239d96f5e91881f Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 8 Jun 2026 11:40:21 +0800 Subject: [PATCH 284/328] opt: not need send output enter when layer map scene scene-graph API will auto do this --- src/mango.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/mango.c b/src/mango.c index 74d4c196..8c96dba2 100644 --- a/src/mango.c +++ b/src/mango.c @@ -3140,7 +3140,6 @@ void createlayersurface(struct wl_listener *listener, void *data) { LISTEN(&l->scene->node.events.destroy, &l->destroy, destroylayernodenotify); wl_list_insert(&l->mon->layers[layer_surface->pending.layer], &l->link); - wlr_surface_send_enter(surface, layer_surface->output); } void createlocksurface(struct wl_listener *listener, void *data) { From 009e2d21119e36e4f1921d4b2878ddc50f674ab9 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 8 Jun 2026 13:05:04 +0800 Subject: [PATCH 285/328] opt: optimzie xwayland position set --- src/fetch/client.h | 4 ++-- src/mango.c | 58 ++++++++++++++++++++++++++++++++-------------- 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/src/fetch/client.h b/src/fetch/client.h index 85b296a3..77b2d865 100644 --- a/src/fetch/client.h +++ b/src/fetch/client.h @@ -91,9 +91,9 @@ setclient_coordinate_center(Client *c, Monitor *tm, struct wlr_box geom, if (!m) return geom; - uint32_t cbw = check_hit_no_border(c) ? c->bw : 0; + uint32_t cbw = c && check_hit_no_border(c) ? c->bw : 0; - if (!c->no_force_center && m) { + if ((!c || !c->no_force_center) && m) { tempbox.x = m->w.x + (m->w.width - geom.width) / 2; tempbox.y = m->w.y + (m->w.height - geom.height) / 2; } else { diff --git a/src/mango.c b/src/mango.c index 8c96dba2..268ab33f 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1073,7 +1073,7 @@ static struct wl_listener last_cursor_surface_destroy_listener = { .notify = last_cursor_surface_destroy}; #ifdef XWAYLAND -static void fix_xwayland_unmanaged_coordinate(Client *c); +static void fix_xwayland_coordinate(struct wlr_box *geom); static int32_t synckeymap(void *data); static void activatex11(struct wl_listener *listener, void *data); static void configurex11(struct wl_listener *listener, void *data); @@ -1615,7 +1615,7 @@ void applyrules(Client *c) { #ifdef XWAYLAND if (c->isfloating && client_is_x11(c)) { - fix_xwayland_unmanaged_coordinate(c); + fix_xwayland_coordinate(&c->geom); c->float_geom = c->geom; } #endif @@ -4472,7 +4472,11 @@ mapnotify(struct wl_listener *listener, void *data) { /* Unmanaged clients always are floating */ #ifdef XWAYLAND if (client_is_x11(c)) { - fix_xwayland_unmanaged_coordinate(c); + fix_xwayland_coordinate(&c->geom); + wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); + wlr_xwayland_surface_configure(c->surface.xwayland, c->geom.x, + c->geom.y, c->geom.width, + c->geom.height); LISTEN(&c->surface.xwayland->events.set_geometry, &c->set_geometry, setgeometrynotify); } @@ -6824,16 +6828,17 @@ void virtualpointer(struct wl_listener *listener, void *data) { } #ifdef XWAYLAND -void fix_xwayland_unmanaged_coordinate(Client *c) { +void fix_xwayland_coordinate(struct wlr_box *geom) { if (!selmon) return; // 1. 如果窗口已经在当前活动显示器内,直接返回 - if (c->geom.x >= selmon->m.x && c->geom.x < selmon->m.x + selmon->m.width && - c->geom.y >= selmon->m.y && c->geom.y < selmon->m.y + selmon->m.height) + if (geom->x >= selmon->m.x && geom->x <= selmon->m.x + selmon->m.width && + geom->y >= selmon->m.y && geom->y <= selmon->m.y + selmon->m.height) return; - c->geom = setclient_coordinate_center(c, selmon, c->geom, 0, 0); + geom->x = selmon->m.x + (selmon->m.width - geom->width) / 2; + geom->y = selmon->m.y + (selmon->m.height - geom->height) / 2; } int32_t synckeymap(void *data) { @@ -6888,24 +6893,41 @@ void activatex11(struct wl_listener *listener, void *data) { void configurex11(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, configure); struct wlr_xwayland_surface_configure_event *event = data; + struct wlr_box new_geo; + new_geo.x = event->x; + new_geo.y = event->y; + new_geo.width = event->width; + new_geo.height = event->height; + fix_xwayland_coordinate(&new_geo); + if (!client_surface(c) || !client_surface(c)->mapped) { - wlr_xwayland_surface_configure(c->surface.xwayland, event->x, event->y, - event->width, event->height); + + wlr_xwayland_surface_configure(c->surface.xwayland, new_geo.x, + new_geo.y, new_geo.width, + new_geo.height); return; } + if (client_is_unmanaged(c)) { - wlr_scene_node_set_position(&c->scene->node, event->x, event->y); - wlr_xwayland_surface_configure(c->surface.xwayland, event->x, event->y, - event->width, event->height); + wlr_scene_node_set_position(&c->scene->node, new_geo.x, new_geo.y); + wlr_xwayland_surface_configure(c->surface.xwayland, new_geo.x, + new_geo.y, new_geo.width, + new_geo.height); return; } - if ((c->isfloating && c != grabc) || - !c->mon->pertag->ltidxs[c->mon->pertag->curtag]->arrange) { + + if (c->isfloating && c != grabc) { + new_geo.x = new_geo.x - c->bw; + new_geo.y = new_geo.y - c->bw; + new_geo.width = new_geo.width + c->bw * 2; + new_geo.height = new_geo.height + c->bw * 2; + fix_xwayland_coordinate(&new_geo); + resize(c, - (struct wlr_box){.x = event->x - c->bw, - .y = event->y - c->bw, - .width = event->width + c->bw * 2, - .height = event->height + c->bw * 2}, + (struct wlr_box){.x = new_geo.x, + .y = new_geo.y, + .width = new_geo.width, + .height = new_geo.height}, 0); } else { arrange(c->mon, false, false); From a429420b73ac35b6c40aa726fcb5502545e059e6 Mon Sep 17 00:00:00 2001 From: Osama Ragab <theosamaragab@gmail.com> Date: Sun, 7 Jun 2026 16:30:22 +0300 Subject: [PATCH 286/328] feat: add hide cursor on keypress option (#871) --- docs/configuration/miscellaneous.md | 3 ++- src/config/parse_config.h | 6 ++++++ src/mango.c | 5 +++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/configuration/miscellaneous.md b/docs/configuration/miscellaneous.md index 1001c4c1..cd8d167d 100644 --- a/docs/configuration/miscellaneous.md +++ b/docs/configuration/miscellaneous.md @@ -21,6 +21,7 @@ description: Advanced settings for XWayland, focus behavior, and system integrat | `sloppyfocus` | `1` | Focus follows the mouse cursor. | | `warpcursor` | `1` | Warp the cursor to the center of the window when focus changes via keyboard. | | `cursor_hide_timeout` | `0` | Hide the cursor after `N` seconds of inactivity (`0` to disable). | +| `cursor_hide_on_keypress` | `0` | Hide the cursor on keypress. | | `drag_tile_to_tile` | `0` | Allow dragging a tiled window onto another to swap their positions. | | `drag_tile_small` | `1` | Allow dragging a tiled window temporarily to small size.| | `drag_corner` | `3` | Corner for drag-to-tile detection (0: none, 1–3: corners, 4: auto-detect). | @@ -48,4 +49,4 @@ description: Advanced settings for XWayland, focus behavior, and system integrat | `idleinhibit_ignore_visible` | `0` | Allow invisible clients (e.g., background audio players) to inhibit idle. | | `tag_carousel` | `0` | Enable tag carousel (cycling through tags). | | `drag_tile_refresh_interval` | `8.0` | Interval (1.0–16.0) to refresh tiled window resize during drag. Too small may cause application lag. | -| `drag_floating_refresh_interval` | `8.0` | Interval (1.0–16.0) to refresh floating window resize during drag. Too small may cause application lag. | \ No newline at end of file +| `drag_floating_refresh_interval` | `8.0` | Interval (1.0–16.0) to refresh floating window resize during drag. Too small may cause application lag. | diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 5379ddca..72196c5f 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -261,6 +261,7 @@ typedef struct { int32_t overviewgappi; int32_t overviewgappo; uint32_t cursor_hide_timeout; + uint32_t cursor_hide_on_keypress; uint32_t axis_bind_apply_timeout; uint32_t focus_on_activate; @@ -1714,6 +1715,8 @@ bool parse_option(Config *config, char *key, char *value) { config->overviewgappo = atoi(value); } else if (strcmp(key, "cursor_hide_timeout") == 0) { config->cursor_hide_timeout = atoi(value); + } else if (strcmp(key, "cursor_hide_on_keypress") == 0) { + config->cursor_hide_on_keypress = atoi(value); } else if (strcmp(key, "axis_bind_apply_timeout") == 0) { config->axis_bind_apply_timeout = atoi(value); } else if (strcmp(key, "focus_on_activate") == 0) { @@ -3346,6 +3349,8 @@ void override_config(void) { CLAMP_INT(config.no_radius_when_single, 0, 1); config.cursor_hide_timeout = CLAMP_INT(config.cursor_hide_timeout, 0, 36000); + config.cursor_hide_on_keypress = + CLAMP_INT(config.cursor_hide_on_keypress, 0, 1); config.single_scratchpad = CLAMP_INT(config.single_scratchpad, 0, 1); config.repeat_rate = CLAMP_INT(config.repeat_rate, 1, 1000); config.repeat_delay = CLAMP_INT(config.repeat_delay, 1, 20000); @@ -3507,6 +3512,7 @@ void set_value_default() { config.overviewgappi = 5; config.overviewgappo = 30; config.cursor_hide_timeout = 0; + config.cursor_hide_on_keypress = 0; config.warpcursor = 1; config.drag_corner = 3; diff --git a/src/mango.c b/src/mango.c index 74d4c196..b0b93023 100644 --- a/src/mango.c +++ b/src/mango.c @@ -4206,6 +4206,11 @@ void keypress(struct wl_listener *listener, void *data) { } } + if (config.cursor_hide_on_keypress && !cursor_hidden && + event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { + hidecursor(NULL); + } + /* On _press_ if there is no active screen locker, * attempt to process a compositor keybinding. */ for (i = 0; i < nsyms; i++) From 36398a1af2bc06f57154a68934a4f89045c86a77 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 8 Jun 2026 19:28:28 +0800 Subject: [PATCH 287/328] opt: optimize unmanaged client init --- src/mango.c | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/mango.c b/src/mango.c index ee64a551..c18c4b1c 100644 --- a/src/mango.c +++ b/src/mango.c @@ -4473,28 +4473,24 @@ mapnotify(struct wl_listener *listener, void *data) { /* Handle unmanaged clients first so we can return prior create borders */ +#ifdef XWAYLAND if (client_is_unmanaged(c)) { /* Unmanaged clients always are floating */ -#ifdef XWAYLAND - if (client_is_x11(c)) { - fix_xwayland_coordinate(&c->geom); - wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); - wlr_xwayland_surface_configure(c->surface.xwayland, c->geom.x, - c->geom.y, c->geom.width, - c->geom.height); - LISTEN(&c->surface.xwayland->events.set_geometry, &c->set_geometry, - setgeometrynotify); - } -#endif - wlr_scene_node_reparent(&c->scene->node, layers[LyrOverlay]); + fix_xwayland_coordinate(&c->geom); wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); + wlr_xwayland_surface_configure(c->surface.xwayland, c->geom.x, + c->geom.y, c->geom.width, + c->geom.height); + LISTEN(&c->surface.xwayland->events.set_geometry, &c->set_geometry, + setgeometrynotify); + wlr_scene_node_reparent(&c->scene->node, layers[LyrOverlay]); if (client_wants_focus(c)) { focusclient(c, 1); exclusive_focus = c; } return; } - +#endif // extra node for (i = 0; i < 2; i++) { From 47f30454e6e7cd920026b836f6152b03c88a962b Mon Sep 17 00:00:00 2001 From: xtheeq <atheeq.rhxn@gmail.com> Date: Mon, 8 Jun 2026 20:33:21 +0530 Subject: [PATCH 288/328] docs: fix wayfreeze link page --- docs/screenshot.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/screenshot.md b/docs/screenshot.md index 7f85224a..8c512313 100644 --- a/docs/screenshot.md +++ b/docs/screenshot.md @@ -14,7 +14,7 @@ Instead, compose your own workflow from small Wayland utilities and bind them to | [`slurp`](https://github.com/emersion/slurp) | Interactively select a region for `grim` | | [`wl-copy`](https://github.com/bugaevc/wl-clipboard) | Copy screenshots directly to the clipboard | | [`satty`](https://github.com/gabm/Satty) | Annotate screenshots before saving | -| [`wayfreeze`](https://github.com/nicbk/wayfreeze) | Freeze the screen before capture | +| [`wayfreeze`](https://github.com/Jappie3/wayfreeze) | Freeze the screen before capture | Install the required with your package manager or from source. From 52732c928b8e24127fb76cefad8e17db47003569 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 9 Jun 2026 21:50:16 +0800 Subject: [PATCH 289/328] fix: grid layout cant fullscreen --- src/layout/horizontal.h | 33 +++++++++++------------ src/layout/vertical.h | 58 +++++++++++++++++++---------------------- 2 files changed, 44 insertions(+), 47 deletions(-) diff --git a/src/layout/horizontal.h b/src/layout/horizontal.h index 92980731..24eed392 100644 --- a/src/layout/horizontal.h +++ b/src/layout/horizontal.h @@ -588,6 +588,7 @@ void grid(Monitor *m) { int32_t target_gappi = enablegaps ? config.gappih : 0; float single_width_ratio = 0.9; float single_height_ratio = 0.9; + struct wlr_box target_geom; n = m->visible_fake_tiling_clients; @@ -603,11 +604,11 @@ void grid(Monitor *m) { ISFAKETILED(c))) { cw = (m->w.width - 2 * target_gappo) * single_width_ratio; ch = (m->w.height - 2 * target_gappo) * single_height_ratio; - c->geom.x = m->w.x + (m->w.width - cw) / 2; - c->geom.y = m->w.y + (m->w.height - ch) / 2; - c->geom.width = cw; - c->geom.height = ch; - client_tile_resize(c, c->geom, 0); + target_geom.x = m->w.x + (m->w.width - cw) / 2; + target_geom.y = m->w.y + (m->w.height - ch) / 2; + target_geom.width = cw; + target_geom.height = ch; + client_tile_resize(c, target_geom, 0); return; } } @@ -651,16 +652,16 @@ void grid(Monitor *m) { cw = avail_w * (col_pers[i] / sum_col); if (i == 0) { - c->geom.x = m->w.x + target_gappo; + target_geom.x = m->w.x + target_gappo; } else if (i == 1) { // 第二个窗口的 X 坐标紧跟第一个窗口后面 float cw0 = avail_w * (col_pers[0] / sum_col); - c->geom.x = m->w.x + target_gappo + cw0 + target_gappi; + target_geom.x = m->w.x + target_gappo + cw0 + target_gappi; } - c->geom.y = m->w.y + (m->w.height - ch) / 2 + target_gappo; - c->geom.width = cw; - c->geom.height = ch; - client_tile_resize(c, c->geom, 0); + target_geom.y = m->w.y + (m->w.height - ch) / 2 + target_gappo; + target_geom.width = cw; + target_geom.height = ch; + client_tile_resize(c, target_geom, 0); i++; } } @@ -757,11 +758,11 @@ void grid(Monitor *m) { ? (m->w.y + m->w.height - target_gappo - fl_cy) : avail_h * (row_pers[r_idx] / sum_row); - c->geom.x = (int32_t)fl_cx; - c->geom.y = (int32_t)fl_cy; - c->geom.width = (int32_t)fl_cw; - c->geom.height = (int32_t)fl_ch; - client_tile_resize(c, c->geom, 0); + target_geom.x = (int32_t)fl_cx; + target_geom.y = (int32_t)fl_cy; + target_geom.width = (int32_t)fl_cw; + target_geom.height = (int32_t)fl_ch; + client_tile_resize(c, target_geom, 0); i++; } } diff --git a/src/layout/vertical.h b/src/layout/vertical.h index 523467f9..6e058dea 100644 --- a/src/layout/vertical.h +++ b/src/layout/vertical.h @@ -186,14 +186,13 @@ void vertical_grid(Monitor *m) { int32_t cw, ch; int32_t rows, cols, overrows; Client *c = NULL; - int32_t target_gappo = - enablegaps ? m->isoverview ? config.overviewgappo : config.gappov : 0; - int32_t target_gappi = - enablegaps ? m->isoverview ? config.overviewgappi : config.gappiv : 0; - float single_width_ratio = m->isoverview ? 0.7 : 0.9; - float single_height_ratio = m->isoverview ? 0.8 : 0.9; + int32_t target_gappo = enablegaps ? config.gappov : 0; + int32_t target_gappi = enablegaps ? config.gappiv : 0; + float single_width_ratio = 0.9; + float single_height_ratio = 0.9; + struct wlr_box target_geom; - n = m->isoverview ? m->visible_clients : m->visible_fake_tiling_clients; + n = m->visible_fake_tiling_clients; if (n == 0) return; @@ -202,15 +201,14 @@ void vertical_grid(Monitor *m) { if (c->mon != m) continue; if (VISIBLEON(c, m) && !c->isunglobal && - ((m->isoverview && !client_is_x11_popup(c)) || - ISFAKETILED(c))) { + (!client_is_x11_popup(c) || ISFAKETILED(c))) { ch = (m->w.height - 2 * target_gappo) * single_height_ratio; cw = (m->w.width - 2 * target_gappo) * single_width_ratio; - c->geom.x = m->w.x + (m->w.width - cw) / 2; - c->geom.y = m->w.y + (m->w.height - ch) / 2; - c->geom.width = cw; - c->geom.height = ch; - client_tile_resize(c, c->geom, 0); + target_geom.x = m->w.x + (m->w.width - cw) / 2; + target_geom.y = m->w.y + (m->w.height - ch) / 2; + target_geom.width = cw; + target_geom.height = ch; + client_tile_resize(c, target_geom, 0); return; } } @@ -224,8 +222,7 @@ void vertical_grid(Monitor *m) { if (c->mon != m) continue; if (VISIBLEON(c, m) && !c->isunglobal && - ((m->isoverview && !client_is_x11_popup(c)) || - ISFAKETILED(c))) { + (!client_is_x11_popup(c) || ISFAKETILED(c))) { if (i < 2) row_pers[i] = (c->grid_row_per > 0.0f) ? c->grid_row_per : 1.0f; @@ -242,8 +239,7 @@ void vertical_grid(Monitor *m) { if (c->mon != m) continue; if (VISIBLEON(c, m) && !c->isunglobal && - ((m->isoverview && !client_is_x11_popup(c)) || - ISFAKETILED(c))) { + (!client_is_x11_popup(c) || ISFAKETILED(c))) { c->grid_col_idx = 0; c->grid_row_idx = i; c->grid_col_per = 1.0f; @@ -252,17 +248,17 @@ void vertical_grid(Monitor *m) { // 根据分配的权重动态计算当前窗口的高度 ch = avail_h * (row_pers[i] / sum_row); - c->geom.x = m->w.x + (m->w.width - cw) / 2 + target_gappo; + target_geom.x = m->w.x + (m->w.width - cw) / 2 + target_gappo; if (i == 0) { - c->geom.y = m->w.y + target_gappo; + target_geom.y = m->w.y + target_gappo; } else if (i == 1) { // 第二个窗口的 Y 坐标紧跟第一个窗口下面 float ch0 = avail_h * (row_pers[0] / sum_row); - c->geom.y = m->w.y + target_gappo + ch0 + target_gappi; + target_geom.y = m->w.y + target_gappo + ch0 + target_gappi; } - c->geom.width = cw; - c->geom.height = ch; - client_tile_resize(c, c->geom, 0); + target_geom.width = cw; + target_geom.height = ch; + client_tile_resize(c, target_geom, 0); i++; } } @@ -287,7 +283,7 @@ void vertical_grid(Monitor *m) { if (c->mon != m) continue; if (VISIBLEON(c, m) && !c->isunglobal && - ((m->isoverview && !client_is_x11_popup(c)) || ISFAKETILED(c))) { + (!client_is_x11_popup(c) || ISFAKETILED(c))) { int32_t c_idx = i / rows; int32_t r_idx = i % rows; if (r_idx == 0) @@ -314,7 +310,7 @@ void vertical_grid(Monitor *m) { if (c->mon != m) continue; if (VISIBLEON(c, m) && !c->isunglobal && - ((m->isoverview && !client_is_x11_popup(c)) || ISFAKETILED(c))) { + (!client_is_x11_popup(c) || ISFAKETILED(c))) { int32_t c_idx = i / rows; int32_t r_idx = i % rows; @@ -352,11 +348,11 @@ void vertical_grid(Monitor *m) { ? (m->w.x + m->w.width - target_gappo - fl_cx) : avail_w * (col_pers[c_idx] / sum_col); - c->geom.x = (int32_t)fl_cx; - c->geom.y = (int32_t)fl_cy; - c->geom.width = (int32_t)fl_cw; - c->geom.height = (int32_t)fl_ch; - client_tile_resize(c, c->geom, 0); + target_geom.x = (int32_t)fl_cx; + target_geom.y = (int32_t)fl_cy; + target_geom.width = (int32_t)fl_cw; + target_geom.height = (int32_t)fl_ch; + client_tile_resize(c, target_geom, 0); i++; } } From ef59224cdb110731c361a68959f4596665e6d2a2 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 9 Jun 2026 21:56:48 +0800 Subject: [PATCH 290/328] fix: deck layout caculate error when multi master --- src/layout/horizontal.h | 22 +++++++++------------- src/layout/vertical.h | 22 +++++++++++----------- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/src/layout/horizontal.h b/src/layout/horizontal.h index 24eed392..419e218a 100644 --- a/src/layout/horizontal.h +++ b/src/layout/horizontal.h @@ -500,16 +500,13 @@ void deck(Monitor *m) { return; wl_list_for_each(fc, &clients, link) { - if (VISIBLEON(fc, m) && ISFAKETILED(fc)) break; } - // Calculate master width using mfact from pertag mfact = fc->master_mfact_per > 0.0f ? fc->master_mfact_per : m->pertag->mfacts[m->pertag->curtag]; - // Calculate master width including outer gaps if (n > nmasters) mw = nmasters ? round((m->w.width - 2 * cur_gappoh) * mfact) : 0; else @@ -521,16 +518,15 @@ void deck(Monitor *m) { continue; if (i < nmasters) { c->master_mfact_per = mfact; - // Master area clients - client_tile_resize( - c, - (struct wlr_box){.x = m->w.x + cur_gappoh, - .y = m->w.y + cur_gappov + my, - .width = mw, - .height = (m->w.height - 2 * cur_gappov - my) / - (MIN(n, nmasters) - i)}, - 0); - my += c->geom.height; + int32_t h = + (m->w.height - 2 * cur_gappov - my) / (MIN(n, nmasters) - i); + client_tile_resize(c, + (struct wlr_box){.x = m->w.x + cur_gappoh, + .y = m->w.y + cur_gappov + my, + .width = mw, + .height = h}, + 0); + my += h; } else { // Stack area clients c->master_mfact_per = mfact; diff --git a/src/layout/vertical.h b/src/layout/vertical.h index 6e058dea..b1de212e 100644 --- a/src/layout/vertical.h +++ b/src/layout/vertical.h @@ -137,12 +137,10 @@ void vertical_deck(Monitor *m) { return; wl_list_for_each(fc, &clients, link) { - if (VISIBLEON(fc, m) && ISFAKETILED(fc)) break; } - // Calculate master width using mfact from pertag mfact = fc->master_mfact_per > 0.0f ? fc->master_mfact_per : m->pertag->mfacts[m->pertag->curtag]; @@ -156,16 +154,18 @@ void vertical_deck(Monitor *m) { if (!VISIBLEON(c, m) || !ISFAKETILED(c)) continue; if (i < nmasters) { - client_tile_resize( - c, - (struct wlr_box){.x = m->w.x + cur_gappoh + mx, - .y = m->w.y + cur_gappov, - .width = (m->w.width - 2 * cur_gappoh - mx) / - (MIN(n, nmasters) - i), - .height = mh}, - 0); - mx += c->geom.width; + c->master_mfact_per = mfact; + int32_t w = + (m->w.width - 2 * cur_gappoh - mx) / (MIN(n, nmasters) - i); + client_tile_resize(c, + (struct wlr_box){.x = m->w.x + cur_gappoh + mx, + .y = m->w.y + cur_gappov, + .width = w, + .height = mh}, + 0); + mx += w; } else { + c->master_mfact_per = mfact; client_tile_resize( c, (struct wlr_box){.x = m->w.x + cur_gappoh, From 94db68ef88b8a922a3812746465e7374f3465567 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 11 Jun 2026 11:08:44 +0800 Subject: [PATCH 291/328] fix: floating window can't get pointer focus when cross monitor --- src/mango.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mango.c b/src/mango.c index c18c4b1c..35815e38 100644 --- a/src/mango.c +++ b/src/mango.c @@ -4824,13 +4824,13 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, } if (!scroller_focus_lock || !(c && c->mon && !INSIDEMON(c))) { - if (c && c->mon && is_scroller_layout(c->mon) && !INSIDEMON(c)) { + if (c && c->mon && ISSCROLLTILED(c) && is_scroller_layout(c->mon) && !INSIDEMON(c)) { should_lock = true; } if (!((!config.edge_scroller_pointer_focus || speed < config.edge_scroller_focus_allow_speed) && - c && c->mon && is_scroller_layout(c->mon) && !INSIDEMON(c))) { + c && c->mon && ISSCROLLTILED(c) && is_scroller_layout(c->mon) && !INSIDEMON(c))) { pointerfocus(c, surface, sx, sy, time); } From 33cda5afea459aed69ac517a8b50d9082e57a396 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 11 Jun 2026 12:37:48 +0800 Subject: [PATCH 292/328] opt:optimize edge focus judge --- src/mango.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mango.c b/src/mango.c index 35815e38..281d1720 100644 --- a/src/mango.c +++ b/src/mango.c @@ -4812,7 +4812,7 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, if (!surface && !seat->drag && !cursor_hidden) wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); - if (c && c->mon && !c->animation.running && (INSIDEMON(c) || !ISTILED(c))) { + if (c && c->mon && !c->animation.running && (INSIDEMON(c) || !ISSCROLLTILED(c))) { scroller_focus_lock = 0; } From 03e68ba069f51833466c23509a41b78f0a3faf32 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 11 Jun 2026 12:39:17 +0800 Subject: [PATCH 293/328] opt: optimize marco use in client tile resize --- src/action/client.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/action/client.h b/src/action/client.h index f374ca14..1b3c9335 100644 --- a/src/action/client.h +++ b/src/action/client.h @@ -51,7 +51,7 @@ static void finish_exchange_arrange_and_focus(Client *c1, Client *c2, } void client_tile_resize(Client *c, struct wlr_box geo, int32_t interact) { - if (!ISSCROLLTILED(c)) + if (!ISFAKETILED(c)) return; if (!c->isfullscreen && !c->ismaximizescreen) { From 792bfac475cab87bd470ed70bb9f540d72959263 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 11 Jun 2026 23:14:50 +0800 Subject: [PATCH 294/328] fix: can't resize tile scroller window when only two tiled client --- src/layout/arrange.h | 3 ++- src/mango.c | 9 ++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/layout/arrange.h b/src/layout/arrange.h index 7223b430..eab3ac00 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -751,6 +751,7 @@ void resize_tile_grid_fair(Client *grabc, bool isdrag, int32_t offsetx, void resize_tile_scroller(Client *grabc, bool isdrag, int32_t offsetx, int32_t offsety, uint32_t time, bool isvertical) { + if (!grabc || grabc->isfullscreen || grabc->ismaximizescreen) return; if (grabc->mon->isoverview) @@ -772,7 +773,7 @@ void resize_tile_scroller(Client *grabc, bool isdrag, int32_t offsetx, Client *stack_head_client = headnode->client; - if (m->visible_tiling_clients == 1 && + if (m->visible_scroll_tiling_clients == 1 && !config.scroller_ignore_proportion_single) return; diff --git a/src/mango.c b/src/mango.c index 281d1720..731c0ac3 100644 --- a/src/mango.c +++ b/src/mango.c @@ -4812,7 +4812,8 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, if (!surface && !seat->drag && !cursor_hidden) wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); - if (c && c->mon && !c->animation.running && (INSIDEMON(c) || !ISSCROLLTILED(c))) { + if (c && c->mon && !c->animation.running && + (INSIDEMON(c) || !ISSCROLLTILED(c))) { scroller_focus_lock = 0; } @@ -4824,13 +4825,15 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, } if (!scroller_focus_lock || !(c && c->mon && !INSIDEMON(c))) { - if (c && c->mon && ISSCROLLTILED(c) && is_scroller_layout(c->mon) && !INSIDEMON(c)) { + if (c && c->mon && ISSCROLLTILED(c) && is_scroller_layout(c->mon) && + !INSIDEMON(c)) { should_lock = true; } if (!((!config.edge_scroller_pointer_focus || speed < config.edge_scroller_focus_allow_speed) && - c && c->mon && ISSCROLLTILED(c) && is_scroller_layout(c->mon) && !INSIDEMON(c))) { + c && c->mon && ISSCROLLTILED(c) && is_scroller_layout(c->mon) && + !INSIDEMON(c))) { pointerfocus(c, surface, sx, sy, time); } From 7e178369ffda32aa5905856c45b42b81a8859530 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 14 Jun 2026 07:53:57 +0800 Subject: [PATCH 295/328] opt: optimize drop tile client when cross monitor --- src/animation/client.h | 112 +++++++++++++++++++++++++++-------------- src/mango.c | 7 ++- 2 files changed, 78 insertions(+), 41 deletions(-) diff --git a/src/animation/client.h b/src/animation/client.h index f988651e..35d2a30f 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -699,17 +699,6 @@ void client_set_drop_area(Client *c) { bool dwindle_familiar = cur_layout->id == DWINDLE && config.dwindle_drop_simple_split; - uint32_t nmaster = c->mon->pertag->nmasters[c->mon->pertag->curtag]; - - bool should_swap = - (cur_layout->id == DECK || cur_layout->id == VERTICAL_DECK || - cur_layout->id == MONOCLE || cur_layout->id == GRID || - cur_layout->id == FAIR || cur_layout->id == VERTICAL_FAIR || - cur_layout->id == VERTICAL_GRID) || - ((cur_layout->id == TILE || cur_layout->id == VERTICAL_TILE || - cur_layout->id == CENTER_TILE || cur_layout->id == RIGHT_TILE) && - nmaster == 1 && c->ismaster); - if (dwindle_familiar) { bool split_h = c->geom.width >= c->geom.height; float ratio = config.dwindle_split_ratio; @@ -743,42 +732,87 @@ void client_set_drop_area(Client *c) { client_height - (int32_t)(client_height * ratio); } } - } else if (should_swap) { - drop_box.x = bw; - drop_box.y = bw; - drop_box.width = client_width; - drop_box.height = client_height; - drop_direction = UNDIR; } else if (cur_layout->id == TILE || cur_layout->id == DECK || cur_layout->id == CENTER_TILE || cur_layout->id == RIGHT_TILE) { - if (rel_y < client_height * 0.5) { - drop_direction = UP; - drop_box.x = bw; - drop_box.y = bw; - drop_box.width = client_width; - drop_box.height = client_height / 2; + + if (c->ismaster) { + if (c->mon->visible_tiling_clients == 1) { + if (rel_x < client_width * 0.5) { + drop_direction = LEFT; + drop_box.x = bw; + drop_box.y = bw; + drop_box.width = client_width / 2; + drop_box.height = client_height; + } else { + drop_direction = RIGHT; + drop_box.x = bw + client_width / 2; + drop_box.y = bw; + drop_box.width = client_width / 2; + drop_box.height = client_height; + } + } else { + drop_box.x = bw; + drop_box.y = bw; + drop_box.width = client_width; + drop_box.height = client_height; + drop_direction = UNDIR; + } } else { - drop_direction = DOWN; - drop_box.x = bw; - drop_box.y = bw + client_height / 2; - drop_box.width = client_width; - drop_box.height = client_height / 2; + if (rel_y < client_height * 0.5) { + drop_direction = UP; + drop_box.x = bw; + drop_box.y = bw; + drop_box.width = client_width; + drop_box.height = client_height / 2; + } else { + drop_direction = DOWN; + drop_box.x = bw; + drop_box.y = bw + client_height / 2; + drop_box.width = client_width; + drop_box.height = client_height / 2; + } } } else if (cur_layout->id == VERTICAL_TILE || cur_layout->id == VERTICAL_DECK) { - if (rel_x < client_width * 0.5) { - drop_direction = LEFT; - drop_box.x = bw; - drop_box.y = bw; - drop_box.width = client_width / 2; - drop_box.height = client_height; + if (c->ismaster) { + if (c->mon->visible_tiling_clients == 1) { + if (rel_y < client_height * 0.5) { + drop_direction = UP; + drop_box.x = bw; + drop_box.y = bw; + drop_box.width = client_width; + drop_box.height = client_height / 2; + } else { + drop_direction = DOWN; + drop_box.x = bw; + drop_box.y = bw + client_height / 2; + drop_box.width = client_width; + drop_box.height = client_height / 2; + } + } else { + drop_box.x = bw; + drop_box.y = bw; + drop_box.width = client_width; + drop_box.height = client_height; + drop_direction = UNDIR; + } + } else { - drop_direction = RIGHT; - drop_box.x = bw + client_width / 2; - drop_box.y = bw; - drop_box.width = client_width / 2; - drop_box.height = client_height; + if (rel_x < client_width * 0.5) { + drop_direction = LEFT; + drop_box.x = bw; + drop_box.y = bw; + drop_box.width = client_width / 2; + drop_box.height = client_height; + } else { + drop_direction = RIGHT; + drop_box.x = bw + client_width / 2; + drop_box.y = bw; + drop_box.width = client_width / 2; + drop_box.height = client_height; + } } + } else { double dist_left = rel_x; double dist_right = client_width - rel_x; diff --git a/src/mango.c b/src/mango.c index 731c0ac3..1e39ddb5 100644 --- a/src/mango.c +++ b/src/mango.c @@ -2202,9 +2202,10 @@ void hold_end(struct wl_listener *listener, void *data) { Client *find_closest_tiled_client(Client *c) { Client *tc, *closest = NULL; long min_dist = LONG_MAX; + Monitor *cursor_mon = xytomon(cursor->x, cursor->y); wl_list_for_each(tc, &clients, link) { - if (tc == c || !ISTILED(tc) || !VISIBLEON(tc, c->mon)) + if (tc == c || !ISTILED(tc) || !VISIBLEON(tc, cursor_mon)) continue; if (cursor->x >= tc->geom.x && @@ -2236,7 +2237,9 @@ void place_drag_tile_client(Client *c) { if (closest->drop_direction == UNDIR) { setfloating(c, 0); - exchange_two_client(c, closest); + wl_list_remove(&c->link); + wl_list_insert(closest->link.prev, &c->link); + arrange(closest->mon, false, false); return; } From 15fb37f1c63597a54223ad745d779082e088498f Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 14 Jun 2026 13:14:38 +0800 Subject: [PATCH 296/328] update docs --- docs/configuration/monitors.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/configuration/monitors.md b/docs/configuration/monitors.md index 084db60c..a8001ae0 100644 --- a/docs/configuration/monitors.md +++ b/docs/configuration/monitors.md @@ -163,6 +163,8 @@ Some GPUs have compatibility issues with `syncobj_enable=1` — it may crash app ## Power Management You can control monitor power using the `mmsg` IPC tool. +> Notice: This command does not remove the monitor, it only turns it off. +> if you want completely remove monitor, just use `wlr-randr` ```bash # Turn off @@ -178,13 +180,13 @@ mmsg dispatch toggle_monitor,eDP-1 You can also use `wlr-randr` for monitor management: ```bash -# Turn off monitor +# remove a monitor wlr-randr --output eDP-1 --off -# Turn on monitor +# add a monitor wlr-randr --output eDP-1 --on -# Show all monitors +# Show all monitors spec wlr-randr ``` From 83adb33ad2c8532c291a1a8fc75a2004b7e4690c Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 15 Jun 2026 00:00:03 +0800 Subject: [PATCH 297/328] fix: overview cursor jump miss apply when no mousebind --- src/mango.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/mango.c b/src/mango.c index 1e39ddb5..1fad3011 100644 --- a/src/mango.c +++ b/src/mango.c @@ -2338,6 +2338,17 @@ bool handle_buttonpress(struct wlr_pointer_button_event *event) { } } + // overview模式下鼠标左键跳转,右键关闭窗口 + if (selmon && selmon->isoverview && event->button == BTN_LEFT && c) { + toggleoverview(&(Arg){.i = 1}); + return true; + } + + if (selmon && selmon->isoverview && event->button == BTN_RIGHT && c) { + pending_kill_client(c); + return true; + } + // 当鼠标焦点在layer上的时候,不检测虚拟键盘的mod状态, // 避免layer虚拟键盘锁死mod按键状态 hard_keyboard = &kb_group->wlr_group->keyboard; @@ -2354,16 +2365,6 @@ bool handle_buttonpress(struct wlr_pointer_button_event *event) { break; m = &config.mouse_bindings[ji]; - if (selmon->isoverview && event->button == BTN_LEFT && c) { - toggleoverview(&(Arg){.i = 1}); - return true; - } - - if (selmon->isoverview && event->button == BTN_RIGHT && c) { - pending_kill_client(c); - return true; - } - if (CLEANMASK(mods) == CLEANMASK(m->mod) && event->button == m->button && m->func && (CLEANMASK(m->mod) != 0 || From 4daab9e4d54dd4f2ef4347d0b440cc27b9feb263 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 14 Jun 2026 21:40:17 +0800 Subject: [PATCH 298/328] opt: optimzie client layer judge in overveiw --- src/fetch/common.h | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/fetch/common.h b/src/fetch/common.h index f58bd33a..acb5d0e8 100644 --- a/src/fetch/common.h +++ b/src/fetch/common.h @@ -160,11 +160,26 @@ void xytonode(double x, double y, struct wlr_surface **psurface, Client **pc, if (pl) *pl = l; - if (selmon && selmon->isoverview && (!l || layer_ignores_focus(l))) { + if (selmon && selmon->isoverview && config.ov_no_resize) { ovc = xytoclient(x, y); - if (pc) - *pc = ovc; - if (psurface && ovc) - *psurface = client_surface(ovc); + + bool is_below = false; + if (l && l->layer_surface) { + is_below = (l->layer_surface->current.layer == + ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND || + l->layer_surface->current.layer == + ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM); + } + + if (ovc && (!l || layer_ignores_focus(l) || is_below)) { + if (pc) + *pc = ovc; + + if (psurface) + *psurface = ovc ? client_surface(ovc) : NULL; + + if (pl && ovc) + *pl = NULL; + } } } \ No newline at end of file From 2a9c38df1f12f4df8eca4f1e0b23624757c4dbc9 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 15 Jun 2026 09:53:34 +0800 Subject: [PATCH 299/328] fix: cursor not auto hide in overview --- src/dispatch/bind_define.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index cf0f770a..cf15b261 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1760,7 +1760,12 @@ int32_t toggleoverview(const Arg *arg) { if (selmon->isoverview) { wlr_seat_pointer_clear_focus(seat); - wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); + + if (cursor_hidden) { + handlecursoractivity(); + } else { + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); + } wl_list_for_each(c, &clients, link) { if (c && c->mon == selmon && !client_is_unmanaged(c) && From 8a3d065cc1356df36df35be689dd8ef000e75290 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 15 Jun 2026 10:43:54 +0800 Subject: [PATCH 300/328] opt: add ov workspace before remove all tag workspace --- src/ext-protocol/ext-workspace.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ext-protocol/ext-workspace.h b/src/ext-protocol/ext-workspace.h index 480dd5cf..eea0f455 100644 --- a/src/ext-protocol/ext-workspace.h +++ b/src/ext-protocol/ext-workspace.h @@ -188,10 +188,10 @@ void refresh_monitors_workspaces_status(Monitor *m) { int32_t i; if (m->isoverview) { + add_workspace_by_tag(0, m); for (i = 1; i <= LENGTH(tags); i++) { remove_workspace_by_tag(i, m); } - add_workspace_by_tag(0, m); } else { remove_workspace_by_tag(0, m); for (i = 1; i <= LENGTH(tags); i++) { From 8ca013e6c34fac2520272b9c2b887283c8a76569 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 15 Jun 2026 13:33:46 +0800 Subject: [PATCH 301/328] opt: avoid arrange when restore from overview --- src/dispatch/bind_define.h | 8 +++--- src/ext-protocol/foreign-toplevel.h | 8 +++--- src/layout/scroll.h | 8 +++--- src/mango.c | 43 ++++++++++++++++------------- 4 files changed, 36 insertions(+), 31 deletions(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index cf15b261..a8cf0cd4 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1355,9 +1355,9 @@ int32_t togglefullscreen(const Arg *arg) { sel->isnamedscratchpad = 0; if (sel->isfullscreen) - setfullscreen(sel, 0); + setfullscreen(sel, 0, true); else - setfullscreen(sel, 1); + setfullscreen(sel, 1, true); return 0; } @@ -1401,9 +1401,9 @@ int32_t togglemaximizescreen(const Arg *arg) { sel->isnamedscratchpad = 0; if (sel->ismaximizescreen) - setmaximizescreen(sel, 0); + setmaximizescreen(sel, 0, true); else - setmaximizescreen(sel, 1); + setmaximizescreen(sel, 1, true); setborder_color(sel); return 0; diff --git a/src/ext-protocol/foreign-toplevel.h b/src/ext-protocol/foreign-toplevel.h index 4fdbbe98..a8c49c77 100644 --- a/src/ext-protocol/foreign-toplevel.h +++ b/src/ext-protocol/foreign-toplevel.h @@ -16,12 +16,12 @@ void handle_foreign_maximize_request(struct wl_listener *listener, void *data) { return; if (c->ismaximizescreen && !event->maximized) { - setmaximizescreen(c, 0); + setmaximizescreen(c, 0, true); return; } if (!c->ismaximizescreen && event->maximized) { - setmaximizescreen(c, 1); + setmaximizescreen(c, 1, true); return; } } @@ -59,12 +59,12 @@ void handle_foreign_fullscreen_request(struct wl_listener *listener, return; if (c->isfullscreen && !event->fullscreen) { - setfullscreen(c, 0); + setfullscreen(c, 0, true); return; } if (!c->isfullscreen && event->fullscreen) { - setfullscreen(c, 1); + setfullscreen(c, 1, true); return; } } diff --git a/src/layout/scroll.h b/src/layout/scroll.h index f6852daa..ce6defd5 100644 --- a/src/layout/scroll.h +++ b/src/layout/scroll.h @@ -743,9 +743,9 @@ void scroller_insert_stack(Client *c, Client *target_client, return; if (c->isfullscreen) - setfullscreen(c, 0); + setfullscreen(c, 0, true); if (c->ismaximizescreen) - setmaximizescreen(c, 0); + setmaximizescreen(c, 0, true); Monitor *m = c->mon; uint32_t tag = m->pertag->curtag; @@ -785,9 +785,9 @@ void scroller_insert_stack(Client *c, Client *target_client, head = head->prev_in_stack; Client *stack_head = head->client; if (stack_head->ismaximizescreen) - setmaximizescreen(stack_head, 0); + setmaximizescreen(stack_head, 0, true); if (stack_head->isfullscreen) - setfullscreen(stack_head, 0); + setfullscreen(stack_head, 0, true); /* 同步到 Client 字段 */ sync_scroller_state_to_clients(m, tag); diff --git a/src/mango.c b/src/mango.c index 1fad3011..eb184e6d 100644 --- a/src/mango.c +++ b/src/mango.c @@ -735,8 +735,9 @@ static void run(char *startup_cmd); static void setcursor(struct wl_listener *listener, void *data); static void setfloating(Client *c, int32_t floating); static void setfakefullscreen(Client *c, int32_t fakefullscreen); -static void setfullscreen(Client *c, int32_t fullscreen); -static void setmaximizescreen(Client *c, int32_t maximizescreen); +static void setfullscreen(Client *c, int32_t fullscreen, bool rearrange); +static void setmaximizescreen(Client *c, int32_t maximizescreen, + bool rearrange); static void reset_maximizescreen_size(Client *c); static void setgaps(int32_t oh, int32_t ov, int32_t ih, int32_t iv); @@ -1150,11 +1151,11 @@ void clear_fullscreen_flag(Client *c) { } if (c->isfullscreen) { - setfullscreen(c, false); + setfullscreen(c, false, true); } if (c->ismaximizescreen) { - setmaximizescreen(c, 0); + setmaximizescreen(c, 0, true); } } @@ -1748,7 +1749,7 @@ void applyrules(Client *c) { view_in_mon(&(Arg){.ui = c->tags}, true, c->mon, true); } - setfullscreen(c, fullscreen_state_backup); + setfullscreen(c, fullscreen_state_backup, true); if (c->isfakefullscreen) { setfakefullscreen(c, 1); @@ -3943,7 +3944,7 @@ fullscreennotify(struct wl_listener *listener, void *data) { if (!c || c->iskilling) return; - setfullscreen(c, client_wants_fullscreen(c)); + setfullscreen(c, client_wants_fullscreen(c), true); } void requestmonstate(struct wl_listener *listener, void *data) { @@ -4586,9 +4587,9 @@ void maximizenotify(struct wl_listener *listener, void *data) { } if (client_request_maximize(c, data)) { - setmaximizescreen(c, 1); + setmaximizescreen(c, 1, true); } else { - setmaximizescreen(c, 0); + setmaximizescreen(c, 0, true); } } @@ -5453,7 +5454,7 @@ void exit_scroller_stack(Client *c) { } } -void setmaximizescreen(Client *c, int32_t maximizescreen) { +void setmaximizescreen(Client *c, int32_t maximizescreen, bool rearrange) { struct wlr_box maximizescreen_box; if (!c || !c->mon || !client_surface(c)->mapped || c->iskilling) return; @@ -5494,7 +5495,8 @@ void setmaximizescreen(Client *c, int32_t maximizescreen) { client_set_maximized(c, true); } - arrange(c->mon, false, false); + if (rearrange) + arrange(c->mon, false, false); } void setfakefullscreen(Client *c, int32_t fakefullscreen) { @@ -5503,12 +5505,13 @@ void setfakefullscreen(Client *c, int32_t fakefullscreen) { return; if (c->isfullscreen) - setfullscreen(c, 0); + setfullscreen(c, 0, true); client_set_fullscreen(c, fakefullscreen); } -void setfullscreen(Client *c, int32_t fullscreen) // 用自定义全屏代理自带全屏 +void setfullscreen(Client *c, int32_t fullscreen, + bool rearrange) // 用自定义全屏代理自带全屏 { if (!c || !c->mon || !client_surface(c)->mapped || c->iskilling) @@ -5554,7 +5557,8 @@ void setfullscreen(Client *c, int32_t fullscreen) // 用自定义全屏代理自 layers[fullscreen || c->isfloating ? LyrTop : LyrTile]); } - arrange(c->mon, false, false); + if (rearrange) + arrange(c->mon, false, false); } void setgaps(int32_t oh, int32_t ov, int32_t ih, int32_t iv) { @@ -5683,7 +5687,8 @@ void setmon(Client *c, Monitor *m, uint32_t newtags, bool focus) { 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) */ + setfullscreen(c, c->isfullscreen, + true); /* This will call arrange(c->mon) */ } if (focus && !client_is_x11_popup(c)) { @@ -6275,13 +6280,13 @@ void overview_restore(Client *c, const Arg *arg) { resize(c, c->overview_backup_geom, 0); } else if (c->isfullscreen || c->ismaximizescreen) { if (want_restore_fullscreen(c) && c->ismaximizescreen) { - setmaximizescreen(c, 1); + setmaximizescreen(c, 1, false); } else if (want_restore_fullscreen(c) && c->isfullscreen) { - setfullscreen(c, 1); + setfullscreen(c, 1, false); } else { client_pending_fullscreen_state(c, 0); client_pending_maximized_state(c, 0); - setfullscreen(c, false); + setfullscreen(c, false, false); } } else { if (c->is_restoring_from_ov) { @@ -6477,8 +6482,8 @@ void unmapnotify(struct wl_listener *listener, void *data) { } if (c->swallowedby) { - setmaximizescreen(c->swallowedby, c->ismaximizescreen); - setfullscreen(c->swallowedby, c->isfullscreen); + setmaximizescreen(c->swallowedby, c->ismaximizescreen, true); + setfullscreen(c->swallowedby, c->isfullscreen, true); c->swallowedby->swallowing = NULL; c->swallowedby = NULL; } From fa24c606a015a327c17943f23c09c30aebfd7209 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 15 Jun 2026 13:43:56 +0800 Subject: [PATCH 302/328] opt: optimize tag animaiton when tagout from overview --- src/animation/tag.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/animation/tag.h b/src/animation/tag.h index c46b9fd0..893d4b0b 100644 --- a/src/animation/tag.h +++ b/src/animation/tag.h @@ -117,6 +117,9 @@ void set_arrange_hidden(Monitor *m, Client *c, bool want_animation) { c->animation.tagining = false; set_tagout_animation(m, c); } else { + c->animation.running = false; wlr_scene_node_set_enabled(&c->scene->node, false); + c->animainit_geom = c->current = c->pending = c->animation.current = + c->geom; } } From b0fb99b95e498a381f174fd29733661e98b14fa5 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 15 Jun 2026 16:49:42 +0800 Subject: [PATCH 303/328] feat: add scroller property to tagrule --- src/config/parse_config.h | 32 ++++++++++++++++++++++++++++++++ src/layout/arrange.h | 3 +++ src/layout/scroll.h | 30 ++++++++++++++++++------------ src/mango.c | 3 +++ 4 files changed, 56 insertions(+), 12 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 72196c5f..849da3c1 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -167,6 +167,9 @@ typedef struct { char *monitor_serial; float mfact; int32_t nmaster; + float scroller_default_proportion; + float scroller_default_proportion_single; + int32_t scroller_ignore_proportion_single; int32_t no_render_border; int32_t open_as_floating; int32_t no_hide; @@ -2041,6 +2044,9 @@ bool parse_option(Config *config, char *key, char *value) { rule->no_render_border = 0; rule->open_as_floating = 0; rule->no_hide = 0; + rule->scroller_default_proportion = 0.0f; + rule->scroller_default_proportion_single = 0.0f; + rule->scroller_ignore_proportion_single = -1; bool parse_error = false; char *token = strtok(value, ","); @@ -2076,6 +2082,17 @@ bool parse_option(Config *config, char *key, char *value) { rule->nmaster = CLAMP_INT(atoi(val), 1, 99); } else if (strcmp(key, "mfact") == 0) { rule->mfact = CLAMP_FLOAT(atof(val), 0.1f, 0.9f); + } else if (strcmp(key, "scroller_default_proportion") == 0) { + rule->scroller_default_proportion = + CLAMP_FLOAT(atof(val), 0.0f, 1.0f); + } else if (strcmp(key, "scroller_default_proportion_single") == + 0) { + rule->scroller_default_proportion_single = + CLAMP_FLOAT(atof(val), 0.0f, 1.0f); + } else if (strcmp(key, "scroller_ignore_proportion_single") == + 0) { + rule->scroller_ignore_proportion_single = + CLAMP_INT(atoi(val), 0, 1); } else { fprintf(stderr, "\033[1m\033[31m[ERROR]:\033[33m Unknown " @@ -3934,6 +3951,12 @@ void parse_tagrule(Monitor *m) { for (i = 0; i <= LENGTH(tags); i++) { m->pertag->nmasters[i] = config.default_nmaster; m->pertag->mfacts[i] = config.default_mfact; + m->pertag->scroller_default_proportion[i] = + config.scroller_default_proportion; + m->pertag->scroller_default_proportion_single[i] = + config.scroller_default_proportion_single; + m->pertag->scroller_ignore_proportion_single[i] = + config.scroller_ignore_proportion_single; } for (i = 0; i < config.tag_rules_count; i++) { @@ -3988,6 +4011,15 @@ void parse_tagrule(Monitor *m) { 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; + if (tr.scroller_default_proportion > 0.0f) + m->pertag->scroller_default_proportion[tr.id] = + tr.scroller_default_proportion; + if (tr.scroller_default_proportion_single > 0.0f) + m->pertag->scroller_default_proportion_single[tr.id] = + tr.scroller_default_proportion_single; + if (tr.scroller_ignore_proportion_single >= 0) + m->pertag->scroller_ignore_proportion_single[tr.id] = + tr.scroller_ignore_proportion_single; } } diff --git a/src/layout/arrange.h b/src/layout/arrange.h index eab3ac00..0a39b248 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -25,6 +25,9 @@ void set_size_per(Monitor *m, Client *c) { c->master_inner_per = 1.0f; c->stack_inner_per = 1.0f; } + + c->scroller_proportion = + m->pertag->scroller_default_proportion[m->pertag->curtag]; } void resize_tile_master_horizontal(Client *grabc, bool isdrag, int32_t offsetx, diff --git a/src/layout/scroll.h b/src/layout/scroll.h index ce6defd5..b800c5cf 100644 --- a/src/layout/scroll.h +++ b/src/layout/scroll.h @@ -284,6 +284,10 @@ void scroller(Monitor *m) { uint32_t tag = m->pertag->curtag; struct TagScrollerState *st = ensure_scroller_state(m, tag); Client *c = NULL; + float scroller_default_proportion_single = + m->pertag->scroller_default_proportion_single[tag]; + int32_t scroller_ignore_proportion_single = + m->pertag->scroller_ignore_proportion_single[tag]; /* 按全局客户端链表顺序收集所有堆叠头,确保视觉顺序正确 */ struct ScrollerStackNode *heads[64]; @@ -323,14 +327,13 @@ void scroller(Monitor *m) { m->w.width - 2 * config.scroller_structs - cur_gappih; /* 单客户端特例 */ - if (n_heads == 1 && !config.scroller_ignore_proportion_single && + if (n_heads == 1 && !scroller_ignore_proportion_single && !heads[0]->client->isfullscreen && !heads[0]->client->ismaximizescreen) { struct ScrollerStackNode *head = heads[0]; - float single_proportion = - head->scroller_proportion_single > 0.0f - ? head->scroller_proportion_single - : config.scroller_default_proportion_single; + float single_proportion = head->scroller_proportion_single > 0.0f + ? head->scroller_proportion_single + : scroller_default_proportion_single; struct wlr_box target_geom; target_geom.height = m->w.height - 2 * cur_gappov; target_geom.width = (m->w.width - 2 * cur_gappoh) * single_proportion; @@ -420,7 +423,7 @@ void scroller(Monitor *m) { max_client_width) > m->w.width - 2 * config.scroller_structs - cur_gappih))); - if (n_heads == 1 && config.scroller_ignore_proportion_single) { + if (n_heads == 1 && scroller_ignore_proportion_single) { need_scroller = true; } if (start_drag_window) @@ -507,6 +510,10 @@ void vertical_scroller(Monitor *m) { uint32_t tag = m->pertag->curtag; struct TagScrollerState *st = ensure_scroller_state(m, tag); Client *c = NULL; + float scroller_default_proportion_single = + m->pertag->scroller_default_proportion_single[tag]; + int32_t scroller_ignore_proportion_single = + m->pertag->scroller_ignore_proportion_single[tag]; /* 按全局顺序收集堆叠头 */ struct ScrollerStackNode *heads[64]; @@ -542,14 +549,13 @@ void vertical_scroller(Monitor *m) { int32_t max_client_height = m->w.height - 2 * config.scroller_structs - cur_gappiv; - if (n_heads == 1 && !config.scroller_ignore_proportion_single && + if (n_heads == 1 && !scroller_ignore_proportion_single && !heads[0]->client->isfullscreen && !heads[0]->client->ismaximizescreen) { struct ScrollerStackNode *head = heads[0]; - float single_proportion = - head->scroller_proportion_single > 0.0f - ? head->scroller_proportion_single - : config.scroller_default_proportion_single; + float single_proportion = head->scroller_proportion_single > 0.0f + ? head->scroller_proportion_single + : scroller_default_proportion_single; struct wlr_box target_geom; target_geom.width = m->w.width - 2 * cur_gappoh; target_geom.height = (m->w.height - 2 * cur_gappov) * single_proportion; @@ -638,7 +644,7 @@ void vertical_scroller(Monitor *m) { max_client_height) > m->w.height - 2 * config.scroller_structs - cur_gappiv))); - if (n_heads == 1 && config.scroller_ignore_proportion_single) { + if (n_heads == 1 && scroller_ignore_proportion_single) { need_scroller = true; } if (start_drag_window) diff --git a/src/mango.c b/src/mango.c index eb184e6d..fe32761a 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1027,6 +1027,9 @@ struct Pertag { int32_t no_hide[LENGTH(tags) + 1]; int32_t no_render_border[LENGTH(tags) + 1]; int32_t open_as_floating[LENGTH(tags) + 1]; + float scroller_default_proportion[LENGTH(tags) + 1]; + float scroller_default_proportion_single[LENGTH(tags) + 1]; + int32_t scroller_ignore_proportion_single[LENGTH(tags) + 1]; struct DwindleNode *dwindle_root[LENGTH(tags) + 1]; const Layout *ltidxs[LENGTH(tags) + 1]; struct TagScrollerState *scroller_state[LENGTH(tags) + 1]; From ba014d50592775c88e90002842f5253f6589afd2 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 15 Jun 2026 16:56:03 +0800 Subject: [PATCH 304/328] update docs --- docs/window-management/rules.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/window-management/rules.md b/docs/window-management/rules.md index 7e221bf1..b5ae81b1 100644 --- a/docs/window-management/rules.md +++ b/docs/window-management/rules.md @@ -183,6 +183,9 @@ tagrule=id:Values,monitor_make:xxx,monitor_model:xxx,Parameter:Values | `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 | +| `scroller_default_proportion` | float | 0.1-1.0 | Set scroller default proportion. | +| `scroller_default_proportion_single` | float | 0.1-1.0 | Set scroller auto adjust proportion when it is single window(only apply when set `scroller_ignore_proportion_single` to `0`) | +| `scroller_ignore_proportion_single` | integer | `0` / `1` | Ignore scroller single proportion setting. | ### Examples @@ -204,6 +207,10 @@ tagrule=id:4,monitor_name:eDP-1,no_hide:1,layout_name:scroller # Advanced tag configuration with master layout settings tagrule=id:5,layout_name:tile,nmaster:2,mfact:0.6 tagrule=id:6,monitor_name:HDMI-A-1,layout_name:monocle,no_render_border:1 + +# set scroller proportion for specific tag +tagrule=id:1,layout_name:scroller,scroller_default_proportion_single:0.5,scroller_ignore_proportion_single:0,scroller_default_proportion:0.9,monitor_name:HDMI-A-1 + ``` > **Tip:** For Waybar configuration with persistent tags, see [Status Bar](/docs/visuals/status-bar) documentation. From 55d366daf263d8be43fe1c5fbebf09a3099089e2 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 15 Jun 2026 20:44:31 +0800 Subject: [PATCH 305/328] bump version to 0.14.3 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 23b59d61..94861590 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('mango', ['c'], - version : '0.14.2', + version : '0.14.3', ) subdir('protocols') From d81ca73ea1c49e927ee46ec9f25a0e6e9911e0da Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 16 Jun 2026 00:06:49 +0800 Subject: [PATCH 306/328] opt: optimzie dir find logic --- src/dispatch/bind_define.h | 2 +- src/fetch/client.h | 334 ++++++++++--------------------------- src/mango.c | 2 +- 3 files changed, 94 insertions(+), 244 deletions(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index a8cf0cd4..01604ebb 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1918,7 +1918,7 @@ int32_t scroller_stack(const Arg *arg) { if (!c || !c->mon || c->isfloating || !is_scroller_layout(selmon)) return 0; - Client *target_client = find_client_by_direction(c, arg, false, true); + Client *target_client = find_client_by_direction(c, arg, false); return scroller_apply_stack(c, target_client, arg->i); } diff --git a/src/fetch/client.h b/src/fetch/client.h index 77b2d865..4d031255 100644 --- a/src/fetch/client.h +++ b/src/fetch/client.h @@ -163,10 +163,11 @@ Client *center_tiled_select(Monitor *m) { } return target_c; } -Client *find_client_by_direction(Client *tc, const Arg *arg, bool findfloating, - bool ignore_align) { + +Client *find_client_by_direction(Client *tc, const Arg *arg, + bool findfloating) { Client *c = NULL; - Client **tempClients = NULL; // 初始化为 NULL + Client **tempClients = NULL; int32_t last = -1; // 第一次遍历,计算客户端数量 @@ -179,13 +180,12 @@ Client *find_client_by_direction(Client *tc, const Arg *arg, bool findfloating, } if (last < 0) { - return NULL; // 没有符合条件的客户端 + return NULL; } // 动态分配内存 tempClients = malloc((last + 1) * sizeof(Client *)); if (!tempClients) { - // 处理内存分配失败的情况 return NULL; } @@ -200,249 +200,102 @@ Client *find_client_by_direction(Client *tc, const Arg *arg, bool findfloating, } } - int32_t sel_x = tc->geom.x; - int32_t sel_y = tc->geom.y; + // 获取当前窗口的四个边界及中心点 + int32_t tc_l = tc->geom.x; + int32_t tc_r = tc->geom.x + tc->geom.width; + int32_t tc_t = tc->geom.y; + int32_t tc_b = tc->geom.y + tc->geom.height; + int32_t tc_cx = tc_l + tc->geom.width / 2; + int32_t tc_cy = tc_t + tc->geom.height / 2; + int64_t distance = LLONG_MAX; int64_t same_monitor_distance = LLONG_MAX; Client *tempFocusClients = NULL; Client *tempSameMonitorFocusClients = NULL; - switch (arg->i) { - case UP: - if (!ignore_align) { - for (int32_t _i = 0; _i <= last; _i++) { - if (tempClients[_i]->geom.y < sel_y && - tempClients[_i]->geom.x == sel_x && - tempClients[_i]->mon == tc->mon) { - int32_t dis_x = tempClients[_i]->geom.x - sel_x; - int32_t dis_y = tempClients[_i]->geom.y - sel_y; - int64_t tmp_distance = - dis_x * dis_x + dis_y * dis_y; // 计算距离 - if (tmp_distance < distance) { - distance = tmp_distance; - tempFocusClients = tempClients[_i]; - } + for (int32_t step = 0; step < 2; step++) { + if (step == 1 && tempFocusClients) + break; + + for (int32_t _i = 0; _i <= last; _i++) { + c = tempClients[_i]; + if (c == tc) + continue; + + if (step == 0 && + (!client_is_in_same_stack(tc, c, NULL) || c->mon != tc->mon)) { + continue; + } + + // 获取目标窗口的四个边界及中心点 + int32_t c_l = c->geom.x; + int32_t c_r = c->geom.x + c->geom.width; + int32_t c_t = c->geom.y; + int32_t c_b = c->geom.y + c->geom.height; + int32_t c_cx = c_l + c->geom.width / 2; + int32_t c_cy = c_t + c->geom.height / 2; + + int64_t main_dist = 0; + int64_t orth_dist = 0; + bool match_dir = false; + + switch (arg->i) { + case LEFT: + match_dir = (c_cx < tc_cx && c_l < tc_l); + if (match_dir) { + main_dist = tc_l - c_r; + orth_dist = (c_b < tc_t) + ? (tc_t - c_b) + : ((c_t > tc_b) ? (c_t - tc_b) : 0); + } + break; + case RIGHT: + match_dir = (c_cx > tc_cx && c_r > tc_r); + if (match_dir) { + main_dist = c_l - tc_r; + orth_dist = (c_b < tc_t) + ? (tc_t - c_b) + : ((c_t > tc_b) ? (c_t - tc_b) : 0); + } + break; + case UP: + match_dir = (c_cy < tc_cy && c_t < tc_t); + if (match_dir) { + main_dist = tc_t - c_b; + orth_dist = (c_r < tc_l) + ? (tc_l - c_r) + : ((c_l > tc_r) ? (c_l - tc_r) : 0); + } + break; + case DOWN: + match_dir = (c_cy > tc_cy && c_b > tc_b); + if (match_dir) { + main_dist = c_t - tc_b; + orth_dist = (c_r < tc_l) + ? (tc_l - c_r) + : ((c_l > tc_r) ? (c_l - tc_r) : 0); + } + break; + } + + if (match_dir) { + int64_t tmp_distance = + main_dist * main_dist + 2 * orth_dist * orth_dist; + + if (tmp_distance < distance) { + distance = tmp_distance; + tempFocusClients = c; + } + if (c->mon == tc->mon && tmp_distance < same_monitor_distance) { + same_monitor_distance = tmp_distance; + tempSameMonitorFocusClients = c; } } } - if (!tempFocusClients) { - for (int32_t _i = 0; _i <= last; _i++) { - if (tempClients[_i]->geom.y < sel_y && - tempClients[_i]->mon == tc->mon && - client_is_in_same_stack(tc, tempClients[_i], NULL)) { - int32_t dis_x = tempClients[_i]->geom.x - sel_x; - int32_t dis_y = tempClients[_i]->geom.y - sel_y; - int64_t tmp_distance = - dis_x * dis_x + dis_y * dis_y; // 计算距离 - if (tmp_distance < distance) { - distance = tmp_distance; - tempFocusClients = tempClients[_i]; - } - if (tempClients[_i]->mon == tc->mon && - tmp_distance < same_monitor_distance) { - same_monitor_distance = tmp_distance; - tempSameMonitorFocusClients = tempClients[_i]; - } - } - } - } - if (!tempFocusClients) { - for (int32_t _i = 0; _i <= last; _i++) { - if (tempClients[_i]->geom.y < sel_y) { - int32_t dis_x = tempClients[_i]->geom.x - sel_x; - int32_t dis_y = tempClients[_i]->geom.y - sel_y; - int64_t tmp_distance = - dis_x * dis_x + dis_y * dis_y; // 计算距离 - if (tmp_distance < distance) { - distance = tmp_distance; - tempFocusClients = tempClients[_i]; - } - if (tempClients[_i]->mon == tc->mon && - tmp_distance < same_monitor_distance) { - same_monitor_distance = tmp_distance; - tempSameMonitorFocusClients = tempClients[_i]; - } - } - } - } - break; - case DOWN: - if (!ignore_align) { - for (int32_t _i = 0; _i <= last; _i++) { - if (tempClients[_i]->geom.y > sel_y && - tempClients[_i]->geom.x == sel_x && - tempClients[_i]->mon == tc->mon) { - int32_t dis_x = tempClients[_i]->geom.x - sel_x; - int32_t dis_y = tempClients[_i]->geom.y - sel_y; - int64_t tmp_distance = - dis_x * dis_x + dis_y * dis_y; // 计算距离 - if (tmp_distance < distance) { - distance = tmp_distance; - tempFocusClients = tempClients[_i]; - } - } - } - } - if (!tempFocusClients) { - for (int32_t _i = 0; _i <= last; _i++) { - if (tempClients[_i]->geom.y > sel_y && - tempClients[_i]->mon == tc->mon && - client_is_in_same_stack(tc, tempClients[_i], NULL)) { - int32_t dis_x = tempClients[_i]->geom.x - sel_x; - int32_t dis_y = tempClients[_i]->geom.y - sel_y; - int64_t tmp_distance = - dis_x * dis_x + dis_y * dis_y; // 计算距离 - if (tmp_distance < distance) { - distance = tmp_distance; - tempFocusClients = tempClients[_i]; - } - if (tempClients[_i]->mon == tc->mon && - tmp_distance < same_monitor_distance) { - same_monitor_distance = tmp_distance; - tempSameMonitorFocusClients = tempClients[_i]; - } - } - } - } - if (!tempFocusClients) { - for (int32_t _i = 0; _i <= last; _i++) { - if (tempClients[_i]->geom.y > sel_y) { - int32_t dis_x = tempClients[_i]->geom.x - sel_x; - int32_t dis_y = tempClients[_i]->geom.y - sel_y; - int64_t tmp_distance = - dis_x * dis_x + dis_y * dis_y; // 计算距离 - if (tmp_distance < distance) { - distance = tmp_distance; - tempFocusClients = tempClients[_i]; - } - if (tempClients[_i]->mon == tc->mon && - tmp_distance < same_monitor_distance) { - same_monitor_distance = tmp_distance; - tempSameMonitorFocusClients = tempClients[_i]; - } - } - } - } - break; - case LEFT: - if (!ignore_align) { - for (int32_t _i = 0; _i <= last; _i++) { - if (tempClients[_i]->geom.x < sel_x && - tempClients[_i]->geom.y == sel_y && - tempClients[_i]->mon == tc->mon) { - int32_t dis_x = tempClients[_i]->geom.x - sel_x; - int32_t dis_y = tempClients[_i]->geom.y - sel_y; - int64_t tmp_distance = - dis_x * dis_x + dis_y * dis_y; // 计算距离 - if (tmp_distance < distance) { - distance = tmp_distance; - tempFocusClients = tempClients[_i]; - } - } - } - } - if (!tempFocusClients) { - for (int32_t _i = 0; _i <= last; _i++) { - if (tempClients[_i]->geom.x < sel_x && - tempClients[_i]->mon == tc->mon && - client_is_in_same_stack(tc, tempClients[_i], NULL)) { - int32_t dis_x = tempClients[_i]->geom.x - sel_x; - int32_t dis_y = tempClients[_i]->geom.y - sel_y; - int64_t tmp_distance = - dis_x * dis_x + dis_y * dis_y; // 计算距离 - if (tmp_distance < distance) { - distance = tmp_distance; - tempFocusClients = tempClients[_i]; - } - if (tempClients[_i]->mon == tc->mon && - tmp_distance < same_monitor_distance) { - same_monitor_distance = tmp_distance; - tempSameMonitorFocusClients = tempClients[_i]; - } - } - } - } - if (!tempFocusClients) { - for (int32_t _i = 0; _i <= last; _i++) { - if (tempClients[_i]->geom.x < sel_x) { - int32_t dis_x = tempClients[_i]->geom.x - sel_x; - int32_t dis_y = tempClients[_i]->geom.y - sel_y; - int64_t tmp_distance = - dis_x * dis_x + dis_y * dis_y; // 计算距离 - if (tmp_distance < distance) { - distance = tmp_distance; - tempFocusClients = tempClients[_i]; - } - if (tempClients[_i]->mon == tc->mon && - tmp_distance < same_monitor_distance) { - same_monitor_distance = tmp_distance; - tempSameMonitorFocusClients = tempClients[_i]; - } - } - } - } - break; - case RIGHT: - if (!ignore_align) { - for (int32_t _i = 0; _i <= last; _i++) { - if (tempClients[_i]->geom.x > sel_x && - tempClients[_i]->geom.y == sel_y && - tempClients[_i]->mon == tc->mon) { - int32_t dis_x = tempClients[_i]->geom.x - sel_x; - int32_t dis_y = tempClients[_i]->geom.y - sel_y; - int64_t tmp_distance = - dis_x * dis_x + dis_y * dis_y; // 计算距离 - if (tmp_distance < distance) { - distance = tmp_distance; - tempFocusClients = tempClients[_i]; - } - } - } - } - if (!tempFocusClients) { - for (int32_t _i = 0; _i <= last; _i++) { - if (tempClients[_i]->geom.x > sel_x && - tempClients[_i]->mon == tc->mon && - client_is_in_same_stack(tc, tempClients[_i], NULL)) { - int32_t dis_x = tempClients[_i]->geom.x - sel_x; - int32_t dis_y = tempClients[_i]->geom.y - sel_y; - int64_t tmp_distance = - dis_x * dis_x + dis_y * dis_y; // 计算距离 - if (tmp_distance < distance) { - distance = tmp_distance; - tempFocusClients = tempClients[_i]; - } - if (tempClients[_i]->mon == tc->mon && - tmp_distance < same_monitor_distance) { - same_monitor_distance = tmp_distance; - tempSameMonitorFocusClients = tempClients[_i]; - } - } - } - } - if (!tempFocusClients) { - for (int32_t _i = 0; _i <= last; _i++) { - if (tempClients[_i]->geom.x > sel_x) { - int32_t dis_x = tempClients[_i]->geom.x - sel_x; - int32_t dis_y = tempClients[_i]->geom.y - sel_y; - int64_t tmp_distance = - dis_x * dis_x + dis_y * dis_y; // 计算距离 - if (tmp_distance < distance) { - distance = tmp_distance; - tempFocusClients = tempClients[_i]; - } - if (tempClients[_i]->mon == tc->mon && - tmp_distance < same_monitor_distance) { - same_monitor_distance = tmp_distance; - tempSameMonitorFocusClients = tempClients[_i]; - } - } - } - } - break; } - free(tempClients); // 释放内存 + free(tempClients); + if (tempSameMonitorFocusClients) { return tempSameMonitorFocusClients; } else { @@ -462,10 +315,7 @@ Client *direction_select(const Arg *arg) { return NULL; } - return find_client_by_direction( - tc, arg, true, - (is_scroller_layout(selmon) || is_centertile_layout(selmon)) && - !selmon->isoverview); + return find_client_by_direction(tc, arg, true); } /* We probably should change the name of this, it sounds like diff --git a/src/mango.c b/src/mango.c index fe32761a..565a7419 100644 --- a/src/mango.c +++ b/src/mango.c @@ -856,7 +856,7 @@ static float *get_border_color(Client *c); static void clear_fullscreen_and_maximized_state(Monitor *m); static void request_fresh_all_monitors(void); static Client *find_client_by_direction(Client *tc, const Arg *arg, - bool findfloating, bool ignore_align); + bool findfloating); static void exit_scroller_stack(Client *c); static Client *scroll_get_stack_head_client(Client *c); static bool client_only_in_one_tag(Client *c); From ce1879d4173ebb84c662587067f3c67c9b365d6c Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 16 Jun 2026 08:07:54 +0800 Subject: [PATCH 307/328] fix: scroller proportion option miss apply in windowrule --- src/layout/arrange.h | 11 +++++++++-- src/mango.c | 12 ++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/layout/arrange.h b/src/layout/arrange.h index 0a39b248..23b9d080 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -26,8 +26,15 @@ void set_size_per(Monitor *m, Client *c) { c->stack_inner_per = 1.0f; } - c->scroller_proportion = - m->pertag->scroller_default_proportion[m->pertag->curtag]; + if (!c->iscustom_scroller_proportion) { + c->scroller_proportion = + m->pertag->scroller_default_proportion[m->pertag->curtag]; + } + + if (!c->iscustom_scroller_proportion_single) { + c->scroller_proportion_single = + m->pertag->scroller_default_proportion_single[m->pertag->curtag]; + } } void resize_tile_master_horizontal(Client *grabc, bool isdrag, int32_t offsetx, diff --git a/src/mango.c b/src/mango.c index 565a7419..3f8362f4 100644 --- a/src/mango.c +++ b/src/mango.c @@ -381,6 +381,8 @@ struct Client { int32_t is_in_scratchpad; int32_t iscustomsize; int32_t iscustompos; + int32_t iscustom_scroller_proportion; + int32_t iscustom_scroller_proportion_single; int32_t is_scratchpad_show; int32_t isglobal; int32_t isnoborder; @@ -1658,6 +1660,14 @@ void applyrules(Client *c) { c->isfloating = 1; } + if (r->scroller_proportion > 0.0f) { + c->iscustom_scroller_proportion = 1; + } + + if (r->scroller_proportion_single > 0.0f) { + c->iscustom_scroller_proportion_single = 1; + } + // set geometry of floating client if (r->width > 1) @@ -4411,6 +4421,8 @@ void init_client_properties(Client *c) { c->ignore_minimize = 1; c->iscustomsize = 0; c->iscustompos = 0; + c->iscustom_scroller_proportion = 0; + c->iscustom_scroller_proportion_single = 0; c->master_mfact_per = 0.0f; c->master_inner_per = 0.0f; c->stack_inner_per = 0.0f; From 84a7d3d1e994dc872a6c4453c824f9581b1fee7a Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 16 Jun 2026 08:08:30 +0800 Subject: [PATCH 308/328] opt: better direction algorithm for client find --- src/dispatch/bind_define.h | 7 +- src/fetch/client.h | 222 ++++++++++++++++--------------------- 2 files changed, 104 insertions(+), 125 deletions(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 01604ebb..05886953 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -130,9 +130,14 @@ int32_t exchange_stack_client(const Arg *arg) { } int32_t focusdir(const Arg *arg) { + + if(!selmon) + return 0; + Client *c = NULL; c = direction_select(arg); - c = get_focused_stack_client(c, arg->tc); + if(!selmon->isoverview) + c = get_focused_stack_client(c, arg->tc); if (c) { focusclient(c, 1); if (config.warpcursor) diff --git a/src/fetch/client.h b/src/fetch/client.h index 4d031255..25fd2a01 100644 --- a/src/fetch/client.h +++ b/src/fetch/client.h @@ -164,143 +164,117 @@ Client *center_tiled_select(Monitor *m) { return target_c; } -Client *find_client_by_direction(Client *tc, const Arg *arg, - bool findfloating) { - Client *c = NULL; - Client **tempClients = NULL; - int32_t last = -1; +Client *find_client_by_direction(Client *tc, const Arg *arg, bool findfloating) { + Client *c = NULL; + Client *tempFocusClients = NULL; + Client *tempSameMonitorFocusClients = NULL; + int64_t distance = LLONG_MAX; + int64_t same_monitor_distance = LLONG_MAX; - // 第一次遍历,计算客户端数量 - wl_list_for_each(c, &clients, link) { - if (c && (findfloating || !c->isfloating) && !c->isunglobal && - (config.focus_cross_monitor || c->mon == tc->mon) && - (c->tags & c->mon->tagset[c->mon->seltags])) { - last++; - } - } + int32_t tc_l = tc->geom.x; + int32_t tc_r = tc->geom.x + tc->geom.width; + int32_t tc_t = tc->geom.y; + int32_t tc_b = tc->geom.y + tc->geom.height; + int32_t tc_cx = tc_l + tc->geom.width / 2; + int32_t tc_cy = tc_t + tc->geom.height / 2; - if (last < 0) { - return NULL; - } + for (int32_t step = 0; step < 2; step++) { + if (step == 1 && tempFocusClients) + break; - // 动态分配内存 - tempClients = malloc((last + 1) * sizeof(Client *)); - if (!tempClients) { - return NULL; - } + wl_list_for_each(c, &clients, link) { + if (!c || c == tc) + continue; + if (!findfloating && c->isfloating) + continue; + if (c->isunglobal) + continue; + if (!config.focus_cross_monitor && c->mon != tc->mon) + continue; + if (!(c->tags & c->mon->tagset[c->mon->seltags])) + continue; - // 第二次遍历,填充 tempClients - last = -1; - wl_list_for_each(c, &clients, link) { - if (c && (findfloating || !c->isfloating) && !c->isunglobal && - (config.focus_cross_monitor || c->mon == tc->mon) && - (c->tags & c->mon->tagset[c->mon->seltags])) { - last++; - tempClients[last] = c; - } - } + if (step == 0 && ((!tc->mon->isoverview && !client_is_in_same_stack(tc, c, NULL)) || c->mon != tc->mon)) + continue; - // 获取当前窗口的四个边界及中心点 - int32_t tc_l = tc->geom.x; - int32_t tc_r = tc->geom.x + tc->geom.width; - int32_t tc_t = tc->geom.y; - int32_t tc_b = tc->geom.y + tc->geom.height; - int32_t tc_cx = tc_l + tc->geom.width / 2; - int32_t tc_cy = tc_t + tc->geom.height / 2; + int32_t c_l = c->geom.x; + int32_t c_r = c->geom.x + c->geom.width; + int32_t c_t = c->geom.y; + int32_t c_b = c->geom.y + c->geom.height; + int32_t c_cx = c_l + c->geom.width / 2; + int32_t c_cy = c_t + c->geom.height / 2; - int64_t distance = LLONG_MAX; - int64_t same_monitor_distance = LLONG_MAX; - Client *tempFocusClients = NULL; - Client *tempSameMonitorFocusClients = NULL; + int64_t main_dist = 0; + int64_t orth_dist = 0; + bool match_dir = false; - for (int32_t step = 0; step < 2; step++) { - if (step == 1 && tempFocusClients) - break; + switch (arg->i) { + case LEFT: + if (c_cx < tc_cx || (c_cx == tc_cx && c_l < tc_l)) { + match_dir = true; + main_dist = tc_l - c_r; + orth_dist = (c_b < tc_t) ? (tc_t - c_b) : ((c_t > tc_b) ? (c_t - tc_b) : 0); + } + break; + case RIGHT: + if (c_cx > tc_cx || (c_cx == tc_cx && c_l > tc_l)) { + match_dir = true; + main_dist = c_l - tc_r; + orth_dist = (c_b < tc_t) ? (tc_t - c_b) : ((c_t > tc_b) ? (c_t - tc_b) : 0); + } + break; + case UP: + if (c_cy < tc_cy || (c_cy == tc_cy && c_t < tc_t)) { + match_dir = true; + main_dist = tc_t - c_b; + orth_dist = (c_r < tc_l) ? (tc_l - c_r) : ((c_l > tc_r) ? (c_l - tc_r) : 0); + } + break; + case DOWN: + if (c_cy > tc_cy || (c_cy == tc_cy && c_t > tc_t)) { + match_dir = true; + main_dist = c_t - tc_b; + orth_dist = (c_r < tc_l) ? (tc_l - c_r) : ((c_l > tc_r) ? (c_l - tc_r) : 0); + } + break; + default: + continue; + } - for (int32_t _i = 0; _i <= last; _i++) { - c = tempClients[_i]; - if (c == tc) - continue; + if (!match_dir) + continue; - if (step == 0 && - (!client_is_in_same_stack(tc, c, NULL) || c->mon != tc->mon)) { - continue; - } + int64_t penalty = 0; + if (main_dist < 0) { + penalty = 10000000000LL; // 主方向重叠(反方向)的极大惩罚 + main_dist = -main_dist; + } - // 获取目标窗口的四个边界及中心点 - int32_t c_l = c->geom.x; - int32_t c_r = c->geom.x + c->geom.width; - int32_t c_t = c->geom.y; - int32_t c_b = c->geom.y + c->geom.height; - int32_t c_cx = c_l + c->geom.width / 2; - int32_t c_cy = c_t + c->geom.height / 2; + // 正交方向无重叠惩罚,优先选择在同一行/列的窗口 + int64_t no_overlap_penalty = 0; + if (orth_dist > 0) { + // LEFT/RIGHT 时 orth_dist 是垂直间距,>0 表示垂直无重叠 + // UP/DOWN 时 orth_dist 是水平间距,>0 表示水平无重叠 + no_overlap_penalty = 10000000LL; + } - int64_t main_dist = 0; - int64_t orth_dist = 0; - bool match_dir = false; + int64_t tmp_distance = penalty + no_overlap_penalty + + (main_dist * main_dist) + (orth_dist * orth_dist); - switch (arg->i) { - case LEFT: - match_dir = (c_cx < tc_cx && c_l < tc_l); - if (match_dir) { - main_dist = tc_l - c_r; - orth_dist = (c_b < tc_t) - ? (tc_t - c_b) - : ((c_t > tc_b) ? (c_t - tc_b) : 0); - } - break; - case RIGHT: - match_dir = (c_cx > tc_cx && c_r > tc_r); - if (match_dir) { - main_dist = c_l - tc_r; - orth_dist = (c_b < tc_t) - ? (tc_t - c_b) - : ((c_t > tc_b) ? (c_t - tc_b) : 0); - } - break; - case UP: - match_dir = (c_cy < tc_cy && c_t < tc_t); - if (match_dir) { - main_dist = tc_t - c_b; - orth_dist = (c_r < tc_l) - ? (tc_l - c_r) - : ((c_l > tc_r) ? (c_l - tc_r) : 0); - } - break; - case DOWN: - match_dir = (c_cy > tc_cy && c_b > tc_b); - if (match_dir) { - main_dist = c_t - tc_b; - orth_dist = (c_r < tc_l) - ? (tc_l - c_r) - : ((c_l > tc_r) ? (c_l - tc_r) : 0); - } - break; - } + if (tmp_distance < distance) { + distance = tmp_distance; + tempFocusClients = c; + } + if (c->mon == tc->mon && tmp_distance < same_monitor_distance) { + same_monitor_distance = tmp_distance; + tempSameMonitorFocusClients = c; + } + } + } - if (match_dir) { - int64_t tmp_distance = - main_dist * main_dist + 2 * orth_dist * orth_dist; - - if (tmp_distance < distance) { - distance = tmp_distance; - tempFocusClients = c; - } - if (c->mon == tc->mon && tmp_distance < same_monitor_distance) { - same_monitor_distance = tmp_distance; - tempSameMonitorFocusClients = c; - } - } - } - } - - free(tempClients); - - if (tempSameMonitorFocusClients) { - return tempSameMonitorFocusClients; - } else { - return tempFocusClients; - } + if (tempSameMonitorFocusClients) + return tempSameMonitorFocusClients; + return tempFocusClients; } Client *direction_select(const Arg *arg) { From 71689f6ef409fb9220a28beba0c3672a0b7672da Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 16 Jun 2026 08:12:53 +0800 Subject: [PATCH 309/328] opt: warp cursor to focusing client when exchange --- src/action/client.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/action/client.h b/src/action/client.h index 1b3c9335..6984a394 100644 --- a/src/action/client.h +++ b/src/action/client.h @@ -48,6 +48,9 @@ static void finish_exchange_arrange_and_focus(Client *c1, Client *c2, } wl_list_remove(&c2->flink); wl_list_insert(&c1->flink, &c2->flink); + + if (config.warpcursor) + warp_cursor(c1); } void client_tile_resize(Client *c, struct wlr_box geo, int32_t interact) { From 405e8dbc1a98ea5897acf8659d9a83e062da1dce Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 16 Jun 2026 08:20:55 +0800 Subject: [PATCH 310/328] opt: format code --- src/action/client.h | 2 +- src/dispatch/bind_define.h | 4 +- src/fetch/client.h | 208 ++++++++++++++++++++----------------- 3 files changed, 113 insertions(+), 101 deletions(-) diff --git a/src/action/client.h b/src/action/client.h index 6984a394..11602e70 100644 --- a/src/action/client.h +++ b/src/action/client.h @@ -49,7 +49,7 @@ static void finish_exchange_arrange_and_focus(Client *c1, Client *c2, wl_list_remove(&c2->flink); wl_list_insert(&c1->flink, &c2->flink); - if (config.warpcursor) + if (config.warpcursor) warp_cursor(c1); } diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 05886953..db082b68 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -131,12 +131,12 @@ int32_t exchange_stack_client(const Arg *arg) { int32_t focusdir(const Arg *arg) { - if(!selmon) + if (!selmon) return 0; Client *c = NULL; c = direction_select(arg); - if(!selmon->isoverview) + if (!selmon->isoverview) c = get_focused_stack_client(c, arg->tc); if (c) { focusclient(c, 1); diff --git a/src/fetch/client.h b/src/fetch/client.h index 25fd2a01..fb50c798 100644 --- a/src/fetch/client.h +++ b/src/fetch/client.h @@ -164,117 +164,129 @@ Client *center_tiled_select(Monitor *m) { return target_c; } -Client *find_client_by_direction(Client *tc, const Arg *arg, bool findfloating) { - Client *c = NULL; - Client *tempFocusClients = NULL; - Client *tempSameMonitorFocusClients = NULL; - int64_t distance = LLONG_MAX; - int64_t same_monitor_distance = LLONG_MAX; +Client *find_client_by_direction(Client *tc, const Arg *arg, + bool findfloating) { + Client *c = NULL; + Client *tempFocusClients = NULL; + Client *tempSameMonitorFocusClients = NULL; + int64_t distance = LLONG_MAX; + int64_t same_monitor_distance = LLONG_MAX; - int32_t tc_l = tc->geom.x; - int32_t tc_r = tc->geom.x + tc->geom.width; - int32_t tc_t = tc->geom.y; - int32_t tc_b = tc->geom.y + tc->geom.height; - int32_t tc_cx = tc_l + tc->geom.width / 2; - int32_t tc_cy = tc_t + tc->geom.height / 2; + int32_t tc_l = tc->geom.x; + int32_t tc_r = tc->geom.x + tc->geom.width; + int32_t tc_t = tc->geom.y; + int32_t tc_b = tc->geom.y + tc->geom.height; + int32_t tc_cx = tc_l + tc->geom.width / 2; + int32_t tc_cy = tc_t + tc->geom.height / 2; - for (int32_t step = 0; step < 2; step++) { - if (step == 1 && tempFocusClients) - break; + for (int32_t step = 0; step < 2; step++) { + if (step == 1 && tempFocusClients) + break; - wl_list_for_each(c, &clients, link) { - if (!c || c == tc) - continue; - if (!findfloating && c->isfloating) - continue; - if (c->isunglobal) - continue; - if (!config.focus_cross_monitor && c->mon != tc->mon) - continue; - if (!(c->tags & c->mon->tagset[c->mon->seltags])) - continue; + wl_list_for_each(c, &clients, link) { + if (!c || c == tc) + continue; + if (!findfloating && c->isfloating) + continue; + if (c->isunglobal) + continue; + if (!config.focus_cross_monitor && c->mon != tc->mon) + continue; + if (!(c->tags & c->mon->tagset[c->mon->seltags])) + continue; - if (step == 0 && ((!tc->mon->isoverview && !client_is_in_same_stack(tc, c, NULL)) || c->mon != tc->mon)) - continue; + if (step == 0 && ((!tc->mon->isoverview && + !client_is_in_same_stack(tc, c, NULL)) || + c->mon != tc->mon)) + continue; - int32_t c_l = c->geom.x; - int32_t c_r = c->geom.x + c->geom.width; - int32_t c_t = c->geom.y; - int32_t c_b = c->geom.y + c->geom.height; - int32_t c_cx = c_l + c->geom.width / 2; - int32_t c_cy = c_t + c->geom.height / 2; + int32_t c_l = c->geom.x; + int32_t c_r = c->geom.x + c->geom.width; + int32_t c_t = c->geom.y; + int32_t c_b = c->geom.y + c->geom.height; + int32_t c_cx = c_l + c->geom.width / 2; + int32_t c_cy = c_t + c->geom.height / 2; - int64_t main_dist = 0; - int64_t orth_dist = 0; - bool match_dir = false; + int64_t main_dist = 0; + int64_t orth_dist = 0; + bool match_dir = false; - switch (arg->i) { - case LEFT: - if (c_cx < tc_cx || (c_cx == tc_cx && c_l < tc_l)) { - match_dir = true; - main_dist = tc_l - c_r; - orth_dist = (c_b < tc_t) ? (tc_t - c_b) : ((c_t > tc_b) ? (c_t - tc_b) : 0); - } - break; - case RIGHT: - if (c_cx > tc_cx || (c_cx == tc_cx && c_l > tc_l)) { - match_dir = true; - main_dist = c_l - tc_r; - orth_dist = (c_b < tc_t) ? (tc_t - c_b) : ((c_t > tc_b) ? (c_t - tc_b) : 0); - } - break; - case UP: - if (c_cy < tc_cy || (c_cy == tc_cy && c_t < tc_t)) { - match_dir = true; - main_dist = tc_t - c_b; - orth_dist = (c_r < tc_l) ? (tc_l - c_r) : ((c_l > tc_r) ? (c_l - tc_r) : 0); - } - break; - case DOWN: - if (c_cy > tc_cy || (c_cy == tc_cy && c_t > tc_t)) { - match_dir = true; - main_dist = c_t - tc_b; - orth_dist = (c_r < tc_l) ? (tc_l - c_r) : ((c_l > tc_r) ? (c_l - tc_r) : 0); - } - break; - default: - continue; - } + switch (arg->i) { + case LEFT: + if (c_cx < tc_cx || (c_cx == tc_cx && c_l < tc_l)) { + match_dir = true; + main_dist = tc_l - c_r; + orth_dist = (c_b < tc_t) + ? (tc_t - c_b) + : ((c_t > tc_b) ? (c_t - tc_b) : 0); + } + break; + case RIGHT: + if (c_cx > tc_cx || (c_cx == tc_cx && c_l > tc_l)) { + match_dir = true; + main_dist = c_l - tc_r; + orth_dist = (c_b < tc_t) + ? (tc_t - c_b) + : ((c_t > tc_b) ? (c_t - tc_b) : 0); + } + break; + case UP: + if (c_cy < tc_cy || (c_cy == tc_cy && c_t < tc_t)) { + match_dir = true; + main_dist = tc_t - c_b; + orth_dist = (c_r < tc_l) + ? (tc_l - c_r) + : ((c_l > tc_r) ? (c_l - tc_r) : 0); + } + break; + case DOWN: + if (c_cy > tc_cy || (c_cy == tc_cy && c_t > tc_t)) { + match_dir = true; + main_dist = c_t - tc_b; + orth_dist = (c_r < tc_l) + ? (tc_l - c_r) + : ((c_l > tc_r) ? (c_l - tc_r) : 0); + } + break; + default: + continue; + } - if (!match_dir) - continue; + if (!match_dir) + continue; - int64_t penalty = 0; - if (main_dist < 0) { - penalty = 10000000000LL; // 主方向重叠(反方向)的极大惩罚 - main_dist = -main_dist; - } + int64_t penalty = 0; + if (main_dist < 0) { + penalty = 10000000000LL; // 主方向重叠(反方向)的极大惩罚 + main_dist = -main_dist; + } - // 正交方向无重叠惩罚,优先选择在同一行/列的窗口 - int64_t no_overlap_penalty = 0; - if (orth_dist > 0) { - // LEFT/RIGHT 时 orth_dist 是垂直间距,>0 表示垂直无重叠 - // UP/DOWN 时 orth_dist 是水平间距,>0 表示水平无重叠 - no_overlap_penalty = 10000000LL; - } + // 正交方向无重叠惩罚,优先选择在同一行/列的窗口 + int64_t no_overlap_penalty = 0; + if (orth_dist > 0) { + // LEFT/RIGHT 时 orth_dist 是垂直间距,>0 表示垂直无重叠 + // UP/DOWN 时 orth_dist 是水平间距,>0 表示水平无重叠 + no_overlap_penalty = 10000000LL; + } - int64_t tmp_distance = penalty + no_overlap_penalty - + (main_dist * main_dist) + (orth_dist * orth_dist); + int64_t tmp_distance = penalty + no_overlap_penalty + + (main_dist * main_dist) + + (orth_dist * orth_dist); - if (tmp_distance < distance) { - distance = tmp_distance; - tempFocusClients = c; - } - if (c->mon == tc->mon && tmp_distance < same_monitor_distance) { - same_monitor_distance = tmp_distance; - tempSameMonitorFocusClients = c; - } - } - } + if (tmp_distance < distance) { + distance = tmp_distance; + tempFocusClients = c; + } + if (c->mon == tc->mon && tmp_distance < same_monitor_distance) { + same_monitor_distance = tmp_distance; + tempSameMonitorFocusClients = c; + } + } + } - if (tempSameMonitorFocusClients) - return tempSameMonitorFocusClients; - return tempFocusClients; + if (tempSameMonitorFocusClients) + return tempSameMonitorFocusClients; + return tempFocusClients; } Client *direction_select(const Arg *arg) { From 87f00ab5e964f90120a18a9eddacba4cbfc2d3ca Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 16 Jun 2026 13:30:52 +0800 Subject: [PATCH 311/328] fix: dwindle refuse arrange when restore from tty in non-selmon dwindle relies on sel window cutting, but the sel window has been cleared when the display is destroyed and was not set when the display is created --- src/layout/dwindle.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/layout/dwindle.h b/src/layout/dwindle.h index 74e79477..9562b5d8 100644 --- a/src/layout/dwindle.h +++ b/src/layout/dwindle.h @@ -565,6 +565,7 @@ void dwindle(Monitor *m) { break; } + // 清理树中已不存在的客户端 { DwindleNode *leaves[512]; int32_t lc = 0; @@ -602,9 +603,14 @@ void dwindle(Monitor *m) { } } + // 获得焦点客户端,若为空则用第一个可见平铺客户端兜底 Client *focused = focustop(m); if (focused && !dwindle_find_leaf(*root, focused)) focused = m->sel; + + if (!focused && count > 0) + focused = vis[0]; + for (int32_t i = 0; i < count; i++) { if (!dwindle_find_leaf(*root, vis[i])) dwindle_insert_with_config(root, vis[i], focused, ratio); From 1ae6adabc34db8c7caf4d237b25466b39ce09359 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 16 Jun 2026 13:40:32 +0800 Subject: [PATCH 312/328] opt: ensure sel client exist in all monitor --- src/layout/arrange.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/layout/arrange.h b/src/layout/arrange.h index 23b9d080..f1c97f3a 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -1223,6 +1223,10 @@ arrange(Monitor *m, bool want_animation, bool from_view) { if (!m->wlr_output->enabled) return; + if (!m->sel) { + m->sel = focustop(m); + } + pre_caculate_before_arrange(m, want_animation, from_view, false); if (m->isoverview) { From 24fb167ae6ffc0272b2feb0c293b3c8a9a2125ae Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 16 Jun 2026 14:37:58 +0800 Subject: [PATCH 313/328] bump version to 0.14.4 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 94861590..c4e27fd5 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('mango', ['c'], - version : '0.14.3', + version : '0.14.4', ) subdir('protocols') From a515ad9b91b95b41c1e1eb1462984c3081197aad Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 16 Jun 2026 18:59:25 +0800 Subject: [PATCH 314/328] feat: add overview jump mode --- docs/bindings/keys.md | 1 + docs/installation.md | 1 + docs/visuals/theming.md | 12 ++ mangowm.scm | 1 + meson.build | 3 + nix/default.nix | 2 + src/config/parse_config.h | 82 +++++++++- src/dispatch/bind_declare.h | 1 + src/dispatch/bind_define.h | 23 +++ src/draw/text-node.c | 308 ++++++++++++++++++++++++++++++++++++ src/draw/text-node.h | 52 ++++++ src/layout/overview.h | 55 +++++++ src/mango.c | 36 +++++ 13 files changed, 576 insertions(+), 1 deletion(-) create mode 100644 src/draw/text-node.c create mode 100644 src/draw/text-node.h diff --git a/docs/bindings/keys.md b/docs/bindings/keys.md index be8757c2..a7694d74 100644 --- a/docs/bindings/keys.md +++ b/docs/bindings/keys.md @@ -161,6 +161,7 @@ bindr=Super,Super_L,spawn,rofi -show run | `reload_config` | - | Hot-reload configuration. | | `quit` | - | Exit mangowm. | | `toggleoverview` | - | Toggle overview mode. | +| `togglejump` | - | Toggle overview with jump mode. | | `create_virtual_output` | - | Create a headless monitor (for VNC/Sunshine). | | `destroy_all_virtual_output` | - | Destroy all virtual monitors. | | `toggleoverlay` | - | Toggle overlay state for the focused window. | diff --git a/docs/installation.md b/docs/installation.md index 1fed83d8..8cb7eafe 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -274,6 +274,7 @@ If your distribution isn't listed above, or you want the latest unreleased chang > - `hwdata` > - `seatd` > - `pcre2` +> - `pango` > - `cjson` > - `pixman` > - `xorg-xwayland` diff --git a/docs/visuals/theming.md b/docs/visuals/theming.md index 676c575b..71d3a818 100644 --- a/docs/visuals/theming.md +++ b/docs/visuals/theming.md @@ -52,6 +52,18 @@ You can also color-code windows based on their state: > **Tip:** For scratchpad window sizing, see [Scratchpad](/docs/window-management/scratchpad) configuration. +### Overview Jump Mode +| Setting | Default | Description | +| :--- | :--- | :--- | +| `jump_hit_fg_color` | `0xc4939dff` | label text color. | +| `jump_hit_bg_color` | `0x201b14ff` | label background color. +| `jump_hit_border_color` | `0x8BAA9Bff` | label border color. +| `jump_hit_border_width` | `4` | label border width. +| `jump_hit_corner_radius` | `5` | label corner radius. +| `jump_hit_padding_x` | `10` | label horizontal padding. +| `jump_hit_padding_y` | `10` | label vertical padding. +| `jump_hit_font_desc` | `monospace Bold 24` | label font set.| + ## Cursor Theme Set the size and theme of your mouse cursor. diff --git a/mangowm.scm b/mangowm.scm index fcd7f583..e2b0287a 100644 --- a/mangowm.scm +++ b/mangowm.scm @@ -54,6 +54,7 @@ hwdata seatd pcre2 + pango cjson libxcb pixman diff --git a/meson.build b/meson.build index c4e27fd5..f26f87e1 100644 --- a/meson.build +++ b/meson.build @@ -37,6 +37,7 @@ pcre2_dep = dependency('libpcre2-8') libscenefx_dep = dependency('scenefx-0.4',version: '>=0.4.1') pixman_dep = dependency('pixman-1') cjson_dep = dependency('libcjson') +pangocairo_dep = dependency('pangocairo') # version info git = find_program('git', required : false) @@ -94,6 +95,7 @@ endif executable('mango', 'src/mango.c', 'src/common/util.c', + 'src/draw/text-node.c', 'src/ext-protocol/wlr_ext_workspace_v1.c', wayland_sources, dependencies : [ @@ -109,6 +111,7 @@ executable('mango', pcre2_dep, pixman_dep, cjson_dep, + pangocairo_dep, ], install : true, c_args : c_args, diff --git a/nix/default.nix b/nix/default.nix index d7bcab16..cbfc917a 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -5,6 +5,7 @@ libxcb, libxkbcommon, pcre2, + pango, cjson, pixman, pkg-config, @@ -49,6 +50,7 @@ stdenv.mkDerivation { libxcb libxkbcommon pcre2 + pango cjson pixman wayland diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 849da3c1..e41bcf90 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -261,6 +261,7 @@ typedef struct { int32_t enable_hotarea; int32_t ov_tab_mode; int32_t ov_no_resize; + int32_t overviewgappi; int32_t overviewgappo; uint32_t cursor_hide_timeout; @@ -407,6 +408,7 @@ typedef struct { struct xkb_context *ctx; struct xkb_keymap *keymap; + JumphitData jumhitdata; } Config; typedef int32_t (*FuncType)(const Arg *); @@ -1030,6 +1032,9 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value, } else if (strcmp(func_name, "toggleoverview") == 0) { func = toggleoverview; (*arg).i = atoi(arg_value); + } else if (strcmp(func_name, "togglejump") == 0) { + func = togglejump; + (*arg).i = atoi(arg_value); } else if (strcmp(func_name, "set_proportion") == 0) { func = set_proportion; (*arg).f = atof(arg_value); @@ -1758,6 +1763,50 @@ bool parse_option(Config *config, char *key, char *value) { config->cursor_size = atoi(value); } else if (strcmp(key, "cursor_theme") == 0) { config->cursor_theme = strdup(value); + } else if (strcmp(key, "jump_hit_fg_color") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid jump_hit_fg_color " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->jumhitdata.fg_color, color); + } + } else if (strcmp(key, "jump_hit_font_desc") == 0) { + config->jumhitdata.font_desc = strdup(value); + } else if (strcmp(key, "jump_hit_bg_color") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid jump_hit_bg_color " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->jumhitdata.bg_color, color); + } + } else if (strcmp(key, "jump_hit_border_color") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf( + stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid jump_hit_border_color " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->jumhitdata.border_color, color); + } + } else if (strcmp(key, "jump_hit_border_width") == 0) { + config->jumhitdata.border_width = CLAMP_INT(atoi(value), 0, 100); + } else if (strcmp(key, "jump_hit_corner_radius") == 0) { + config->jumhitdata.corner_radius = CLAMP_INT(atoi(value), 0, 100); + } else if (strcmp(key, "jump_hit_padding_x") == 0) { + config->jumhitdata.padding_x = CLAMP_INT(atoi(value), 0, 100); + } else if (strcmp(key, "jump_hit_padding_y") == 0) { + config->jumhitdata.padding_y = CLAMP_INT(atoi(value), 0, 100); } else if (strcmp(key, "disable_while_typing") == 0) { config->disable_while_typing = atoi(value); } else if (strcmp(key, "left_handed") == 0) { @@ -3247,6 +3296,11 @@ void free_config(void) { config.cursor_theme = NULL; } + if (config.jumhitdata.font_desc) { + free((void *)config.jumhitdata.font_desc); + config.jumhitdata.font_desc = NULL; + } + if (config.tablet_map_to_mon) { free(config.tablet_map_to_mon); config.tablet_map_to_mon = NULL; @@ -3437,6 +3491,15 @@ void override_config(void) { config.focused_opacity = CLAMP_FLOAT(config.focused_opacity, 0.0f, 1.0f); config.unfocused_opacity = CLAMP_FLOAT(config.unfocused_opacity, 0.0f, 1.0f); + + config.jumhitdata.border_width = + CLAMP_INT(config.jumhitdata.border_width, 0, 100); + config.jumhitdata.corner_radius = + CLAMP_INT(config.jumhitdata.corner_radius, 0, 100); + config.jumhitdata.padding_x = + CLAMP_INT(config.jumhitdata.padding_x, 0, 100); + config.jumhitdata.padding_y = + CLAMP_INT(config.jumhitdata.padding_y, 0, 100); } void set_value_default() { @@ -3475,7 +3538,6 @@ void set_value_default() { config.log_level = WLR_ERROR; config.numlockon = 0; config.capslock = 0; - config.ov_tab_mode = 1; config.ov_no_resize = 1; config.hotarea_size = 10; @@ -3613,6 +3675,23 @@ void set_value_default() { config.animation_curve_opafadeout[2] = 0.5; config.animation_curve_opafadeout[3] = 0.5; + config.jumhitdata.fg_color[0] = 0xc4 / 255.0f; + config.jumhitdata.fg_color[1] = 0x93 / 255.0f; + config.jumhitdata.fg_color[2] = 0x9d / 255.0f; + config.jumhitdata.fg_color[3] = 1.0f; + config.jumhitdata.bg_color[0] = 0x32 / 255.0f; + config.jumhitdata.bg_color[1] = 0x32 / 255.0f; + config.jumhitdata.bg_color[2] = 0x32 / 255.0f; + config.jumhitdata.bg_color[3] = 1.0f; + config.jumhitdata.border_color[0] = 0x8b / 255.0f; + config.jumhitdata.border_color[1] = 0xaa / 255.0f; + config.jumhitdata.border_color[2] = 0x9b / 255.0f; + config.jumhitdata.border_color[3] = 1.0f; + config.jumhitdata.border_width = 4; + config.jumhitdata.corner_radius = 5; + config.jumhitdata.padding_x = 10; + config.jumhitdata.padding_y = 10; + config.rootcolor[0] = 0x32 / 255.0f; config.rootcolor[1] = 0x32 / 255.0f; config.rootcolor[2] = 0x32 / 255.0f; @@ -3725,6 +3804,7 @@ bool parse_config(void) { config.tag_rules = NULL; config.tag_rules_count = 0; config.cursor_theme = NULL; + config.jumhitdata.font_desc = NULL; config.tablet_map_to_mon = NULL; strcpy(config.keymode, "default"); diff --git a/src/dispatch/bind_declare.h b/src/dispatch/bind_declare.h index a04ba0c1..6960b001 100644 --- a/src/dispatch/bind_declare.h +++ b/src/dispatch/bind_declare.h @@ -3,6 +3,7 @@ int32_t restore_minimized(const Arg *arg); int32_t toggle_scratchpad(const Arg *arg); int32_t focusdir(const Arg *arg); int32_t toggleoverview(const Arg *arg); +int32_t togglejump(const Arg *arg); int32_t set_proportion(const Arg *arg); int32_t switch_proportion_preset(const Arg *arg); int32_t zoom(const Arg *arg); diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index db082b68..c3a35676 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1737,6 +1737,10 @@ int32_t toggleoverview(const Arg *arg) { uint32_t target; uint32_t visible_client_number = 0; + if (!selmon->isoverview && selmon->is_jump_mode) { + finish_jump_mode(selmon); + } + if (selmon->isoverview) { wl_list_for_each(c, &clients, link) if (c && c->mon == selmon && !client_is_unmanaged(c) && @@ -1781,6 +1785,7 @@ int32_t toggleoverview(const Arg *arg) { } } } else { + selmon->tagset[selmon->seltags] = target; wl_list_for_each(c, &clients, link) { if (c && c->mon == selmon && !c->iskilling && @@ -1794,6 +1799,24 @@ int32_t toggleoverview(const Arg *arg) { view(&(Arg){.ui = target}, false); fix_mon_tagset_from_overview(selmon); refresh_monitors_workspaces_status(selmon); + + return 0; +} + +int32_t togglejump(const Arg *arg) { + if (!selmon) + return 0; + + if (!selmon->isoverview) { + begin_jump_mode(selmon); + toggleoverview(arg); + return 0; + } + + if (selmon->isoverview) { + toggleoverview(arg); + } + return 0; } diff --git a/src/draw/text-node.c b/src/draw/text-node.c new file mode 100644 index 00000000..b96cd4ac --- /dev/null +++ b/src/draw/text-node.c @@ -0,0 +1,308 @@ +#include "text-node.h" +#include <cairo.h> +#include <drm_fourcc.h> +#include <glib.h> +#include <pango/pangocairo.h> +#include <stdlib.h> +#include <wayland-server-core.h> +#include <wlr/interfaces/wlr_buffer.h> + +// 自定义 wlr_buffer,用于将 Cairo Surface 包装并注入 wlr_scene +struct mango_text_buffer { + struct wlr_buffer base; + cairo_surface_t *surface; +}; + +// 全局字体描述缓存 +static GHashTable *font_desc_cache = NULL; + +static PangoFontDescription *get_cached_font_desc(const char *font_desc) { + if (!font_desc_cache) { + font_desc_cache = + g_hash_table_new_full(g_str_hash, g_str_equal, g_free, + (GDestroyNotify)pango_font_description_free); + } + + PangoFontDescription *desc = + g_hash_table_lookup(font_desc_cache, font_desc); + if (!desc) { + desc = pango_font_description_from_string(font_desc); + g_hash_table_insert(font_desc_cache, g_strdup(font_desc), desc); + } + return desc; +} + +void mango_text_global_finish(void) { + if (font_desc_cache) { + g_hash_table_destroy(font_desc_cache); + font_desc_cache = NULL; + } +} + +// wlr_buffer 实现 +static void text_buffer_destroy(struct wlr_buffer *wlr_buffer) { + struct mango_text_buffer *buf = wl_container_of(wlr_buffer, buf, base); + cairo_surface_destroy(buf->surface); + free(buf); +} + +static bool text_buffer_begin_data_ptr_access(struct wlr_buffer *wlr_buffer, + uint32_t flags, void **data, + uint32_t *format, + size_t *stride) { + (void)flags; + struct mango_text_buffer *buf = wl_container_of(wlr_buffer, buf, base); + *data = cairo_image_surface_get_data(buf->surface); + *format = DRM_FORMAT_ARGB8888; + *stride = cairo_image_surface_get_stride(buf->surface); + return true; +} + +static void text_buffer_end_data_ptr_access(struct wlr_buffer *wlr_buffer) {} + +static const struct wlr_buffer_impl text_buffer_impl = { + .destroy = text_buffer_destroy, + .begin_data_ptr_access = text_buffer_begin_data_ptr_access, + .end_data_ptr_access = text_buffer_end_data_ptr_access, +}; + +struct mango_text_node *mango_text_node_create(struct wlr_scene_tree *parent, + JumphitData data) { + struct mango_text_node *node = calloc(1, sizeof(*node)); + if (!node) + return NULL; + + node->scene_buffer = wlr_scene_buffer_create(parent, NULL); + if (!node->scene_buffer) { + free(node); + return NULL; + } + + node->fg_color[0] = data.fg_color[0]; + node->fg_color[1] = data.fg_color[1]; + node->fg_color[2] = data.fg_color[2]; + node->fg_color[3] = data.fg_color[3]; + node->bg_color[0] = data.bg_color[0]; + node->bg_color[1] = data.bg_color[1]; + node->bg_color[2] = data.bg_color[2]; + node->bg_color[3] = data.bg_color[3]; + node->border_color[0] = data.border_color[0]; + node->border_color[1] = data.border_color[1]; + node->border_color[2] = data.border_color[2]; + node->border_color[3] = data.border_color[3]; + node->border_width = data.border_width; + node->corner_radius = data.corner_radius; + node->padding_x = data.padding_x; + node->padding_y = data.padding_y; + node->font_desc = data.font_desc ? data.font_desc : "monospace Bold 24"; + + return node; +} + +void mango_text_node_destroy(struct mango_text_node *node) { + if (!node) + return; + + wlr_scene_node_destroy(&node->scene_buffer->node); + free(node); +} + +void mango_text_node_set_background(struct mango_text_node *node, float r, + float g, float b, float a) { + if (!node) + return; + node->bg_color[0] = r; + node->bg_color[1] = g; + node->bg_color[2] = b; + node->bg_color[3] = a; +} + +void mango_text_node_set_border(struct mango_text_node *node, float r, float g, + float b, float a, float width, float radius) { + if (!node) + return; + node->border_color[0] = r; + node->border_color[1] = g; + node->border_color[2] = b; + node->border_color[3] = a; + node->border_width = width > 0.0f ? width : 0.0f; + node->corner_radius = radius >= 0.0f ? radius : 0.0f; +} + +void mango_text_node_set_padding(struct mango_text_node *node, float pad_x, + float pad_y) { + if (!node) + return; + node->padding_x = pad_x >= 0.0f ? pad_x : 0.0f; + node->padding_y = pad_y >= 0.0f ? pad_y : 0.0f; +} + +static void draw_rounded_rect(cairo_t *cr, double x, double y, double w, + double h, double r) { + // 使用 Cairo 的标准圆角矩形路径 + double degrees = G_PI / 180.0; + cairo_new_sub_path(cr); + cairo_arc(cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees); + cairo_arc(cr, x + w - r, y + h - r, r, 0 * degrees, 90 * degrees); + cairo_arc(cr, x + r, y + h - r, r, 90 * degrees, 180 * degrees); + cairo_arc(cr, x + r, y + r, r, 180 * degrees, 270 * degrees); + cairo_close_path(cr); +} + +void mango_text_node_update(struct mango_text_node *node, const char *text, + + float scale) { + if (!node || !text) + return; + if (scale <= 0.0f) + scale = 1.0f; + + const char *font_desc = node->font_desc; + PangoFontDescription *desc = get_cached_font_desc(font_desc); + + // 测量文字像素尺寸(无缩放的实际像素) + cairo_surface_t *dummy_surface = + cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); + cairo_t *dummy_cr = cairo_create(dummy_surface); + PangoContext *dummy_ctx = pango_cairo_create_context(dummy_cr); + pango_cairo_context_set_resolution(dummy_ctx, 96.0 * scale); + PangoLayout *dummy_layout = pango_layout_new(dummy_ctx); + pango_layout_set_font_description(dummy_layout, desc); + pango_layout_set_text(dummy_layout, text, -1); + + int text_pixel_w, text_pixel_h; + pango_layout_get_pixel_size(dummy_layout, &text_pixel_w, &text_pixel_h); + + g_object_unref(dummy_layout); + g_object_unref(dummy_ctx); + cairo_destroy(dummy_cr); + cairo_surface_destroy(dummy_surface); + + if (text_pixel_w <= 0 || text_pixel_h <= 0) { + wlr_scene_buffer_set_buffer(node->scene_buffer, NULL); + node->logical_width = 0; + node->logical_height = 0; + return; + } + + float logical_text_w = text_pixel_w / scale; + float logical_text_h = text_pixel_h / scale; + + float pad_x = node->padding_x; + float pad_y = node->padding_y; + float box_logical_w = logical_text_w + 2.0f * pad_x; + float box_logical_h = logical_text_h + 2.0f * pad_y; + + float border = node->border_width; + bool draw_border = (border > 0.0f) && (node->border_color[3] > 0.0f); + bool draw_bg = (node->bg_color[3] > 0.0f); + + int surface_pixel_w = (int)((box_logical_w + 2.0f * border) * scale + 0.5f); + int surface_pixel_h = (int)((box_logical_h + 2.0f * border) * scale + 0.5f); + + if (surface_pixel_w < 1) + surface_pixel_w = 1; + if (surface_pixel_h < 1) + surface_pixel_h = 1; + + cairo_surface_t *surface = cairo_image_surface_create( + CAIRO_FORMAT_ARGB32, surface_pixel_w, surface_pixel_h); + cairo_t *cr = cairo_create(surface); + + // 清空为全透明 + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.0); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_paint(cr); + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + + double bg_x = border * scale; + double bg_y = border * scale; + double bg_w = box_logical_w * scale; + double bg_h = box_logical_h * scale; + + double radius = node->corner_radius * scale; + + if (radius < 0) { + radius = (bg_w < bg_h ? bg_w : bg_h) / 2.0; + } + if (radius > bg_w / 2.0) + radius = bg_w / 2.0; + if (radius > bg_h / 2.0) + radius = bg_h / 2.0; + + if (draw_bg) { + cairo_set_source_rgba(cr, node->bg_color[0], node->bg_color[1], + node->bg_color[2], node->bg_color[3]); + if (radius > 0.0) { + draw_rounded_rect(cr, bg_x, bg_y, bg_w, bg_h, radius); + cairo_fill(cr); + } else { + cairo_rectangle(cr, bg_x, bg_y, bg_w, bg_h); + cairo_fill(cr); + } + } + + cairo_save(cr); + cairo_translate(cr, (border + pad_x) * scale, (border + pad_y) * scale); + + PangoContext *ctx = pango_cairo_create_context(cr); + pango_cairo_context_set_resolution(ctx, 96.0 * scale); + PangoLayout *layout = pango_layout_new(ctx); + pango_layout_set_font_description(layout, desc); + pango_layout_set_text(layout, text, -1); + + cairo_set_source_rgba(cr, node->fg_color[0], node->fg_color[1], + node->fg_color[2], node->fg_color[3]); + pango_cairo_show_layout(cr, layout); + + g_object_unref(layout); + g_object_unref(ctx); + cairo_restore(cr); + + if (draw_border) { + cairo_set_source_rgba(cr, node->border_color[0], node->border_color[1], + node->border_color[2], node->border_color[3]); + cairo_set_line_width(cr, border * scale); + + double half_lw = border * scale * 0.5; + double bx = bg_x - half_lw; + double by = bg_y - half_lw; + double bw = bg_w + border * scale; + double bh = bg_h + border * scale; + + if (radius > 0.0) { + double outer_radius = radius + half_lw; + if (outer_radius < 0.0) + outer_radius = 0.0; + draw_rounded_rect(cr, bx, by, bw, bh, outer_radius); + cairo_stroke(cr); + } else { + cairo_rectangle(cr, bx, by, bw, bh); + cairo_stroke(cr); + } + } + + cairo_surface_flush(surface); + + // 包装成 wlr_buffer + struct mango_text_buffer *buf = calloc(1, sizeof(*buf)); + if (!buf) { + cairo_surface_destroy(surface); + cairo_destroy(cr); + return; + } + wlr_buffer_init(&buf->base, &text_buffer_impl, surface_pixel_w, + surface_pixel_h); + buf->surface = surface; + + wlr_scene_buffer_set_buffer(node->scene_buffer, &buf->base); + wlr_buffer_drop(&buf->base); + + // 最终逻辑大小 = 背景框逻辑尺寸 + 边框逻辑尺寸 + node->logical_width = (int)(box_logical_w + 2.0f * border); + node->logical_height = (int)(box_logical_h + 2.0f * border); + wlr_scene_buffer_set_dest_size(node->scene_buffer, node->logical_width, + node->logical_height); + + cairo_destroy(cr); +} \ No newline at end of file diff --git a/src/draw/text-node.h b/src/draw/text-node.h new file mode 100644 index 00000000..b8c4900d --- /dev/null +++ b/src/draw/text-node.h @@ -0,0 +1,52 @@ +#ifndef MANGO_TEXT_NODE_H +#define MANGO_TEXT_NODE_H + +#include <wayland-server-core.h> +#include <wlr/types/wlr_scene.h> + +struct mango_text_node { + struct wlr_scene_buffer *scene_buffer; + int logical_width; + int logical_height; + const char *font_desc; + + // 外观属性 + float fg_color[4]; // 文本颜色 RGBA,默认白色 + float bg_color[4]; // 背景色 RGBA,默认透明 + float border_color[4]; // 边框色 RGBA,默认透明 + int32_t border_width; // 边框宽度(逻辑像素),<=0 则不绘制 + int32_t corner_radius; // 圆角半径(逻辑像素),<0 时自动取 + // min(width,height)/2 形成胶囊 + int32_t padding_x; // 文本左右内边距(逻辑像素) + int32_t padding_y; // 文本上下内边距(逻辑像素) +}; + +typedef struct { + float fg_color[4]; + float bg_color[4]; + float border_color[4]; + int32_t border_width; + int32_t corner_radius; + int32_t padding_x; + int32_t padding_y; + const char *font_desc; +} JumphitData; + +struct mango_text_node *mango_text_node_create(struct wlr_scene_tree *parent, + JumphitData data); +void mango_text_node_destroy(struct mango_text_node *node); +void mango_text_node_update(struct mango_text_node *node, const char *text, + float scale); + +// 外观设置接口 +void mango_text_node_set_background(struct mango_text_node *node, float r, + float g, float b, float a); +void mango_text_node_set_border(struct mango_text_node *node, float r, float g, + float b, float a, float width, float radius); +void mango_text_node_set_padding(struct mango_text_node *node, float pad_x, + float pad_y); + +void mango_text_node_destroy(struct mango_text_node *node); +void mango_text_global_finish(void); + +#endif \ No newline at end of file diff --git a/src/layout/overview.h b/src/layout/overview.h index 88fc4258..06e2213f 100644 --- a/src/layout/overview.h +++ b/src/layout/overview.h @@ -353,10 +353,65 @@ void overview_resize(Monitor *m) { free(c_arr); } +void create_jump_hints(Monitor *m) { + const char jump_labels[] = "HJKLASDFGQWERTYUIOPZXCVBNM"; + int label_idx = 0; + Client *c; + + wl_list_for_each(c, &clients, link) { + if (VISIBLEON(c, m) && + ((m->isoverview && !client_is_x11_popup(c)) || ISTILED(c))) { + if (label_idx >= 26) + break; + char c_char = jump_labels[label_idx]; + c->jump_char = c_char; + + // 把字符变成字符串 + char label_text[2] = {c_char, '\0'}; + + mango_text_node_update(c->text_node, label_text, 1.0f); + wlr_scene_node_set_enabled(&c->text_node->scene_buffer->node, true); + wlr_scene_node_raise_to_top(&c->text_node->scene_buffer->node); + wlr_scene_node_set_position( + &c->text_node->scene_buffer->node, + c->geom.width / 2 - c->text_node->logical_width / 2, + c->geom.height / 2 - c->text_node->logical_height / 2); + label_idx++; + } + } +} + +void begin_jump_mode(Monitor *m) { m->is_jump_mode = 1; } + +void finish_jump_mode(Monitor *m) { + Client *c = NULL; + + if (!m->is_jump_mode) { + return; + } + + wl_list_for_each(c, &clients, link) { + if (VISIBLEON(c, m)) { + if (c->text_node->scene_buffer->node.enabled) { + c->jump_char = '\0'; + wlr_scene_node_set_enabled(&c->text_node->scene_buffer->node, + false); + } + } + } + + m->is_jump_mode = 0; +} + void overview(Monitor *m) { + if (config.ov_no_resize) { overview_scale(m); } else { overview_resize(m); } + + if (m->is_jump_mode) { + create_jump_hints(m); + } } \ No newline at end of file diff --git a/src/mango.c b/src/mango.c index 3f8362f4..0c430f47 100644 --- a/src/mango.c +++ b/src/mango.c @@ -96,6 +96,7 @@ #include <xcb/xcb_icccm.h> #endif #include "common/util.h" +#include "draw/text-node.h" /* macros */ #define MAX(A, B) ((A) > (B) ? (A) : (B)) @@ -327,6 +328,7 @@ struct Client { struct wlr_scene_shadow *shadow; struct wlr_scene_tree *scene_surface; struct wlr_scene_tree *overview_scene_surface; + struct mango_text_node *text_node; struct wl_list link; struct wl_list flink; struct wl_list fadeout_link; @@ -433,6 +435,7 @@ struct Client { int32_t allow_shortcuts_inhibit; float scroller_proportion_single; bool isfocusing; + char jump_char; bool enable_drop_area_draw; int32_t drop_direction; struct wlr_box drag_tile_float_backup_geom; @@ -554,6 +557,7 @@ struct Monitor { uint32_t ovbk_prev_tagset; Client *sel, *prevsel; int32_t isoverview; + int32_t is_jump_mode; int32_t is_in_hotarea; int32_t asleep; uint32_t visible_clients; @@ -899,6 +903,10 @@ Client *scroll_get_stack_tail_client(Client *c); static DwindleNode *dwindle_find_leaf(DwindleNode *node, Client *c); static void overview_backup_surface(Client *c); +static void create_jump_hints(Monitor *m); +static void finish_jump_mode(Monitor *m); +static void begin_jump_mode(Monitor *m); + #include "data/static_keymap.h" #include "dispatch/bind_declare.h" #include "layout/layout.h" @@ -2561,6 +2569,8 @@ void cleanup(void) { /* Destroy after the wayland display (when the monitors are already destroyed) to avoid destroying them with an invalid scene output. */ wlr_scene_node_destroy(&scene->tree.node); + + mango_text_global_finish(); } void cleanupmon(struct wl_listener *listener, void *data) { @@ -4254,6 +4264,27 @@ void keypress(struct wl_listener *listener, void *data) { if (handled) return; + if (selmon && selmon->is_jump_mode && + event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { + for (i = 0; i < nsyms; i++) { + xkb_keysym_t sym = xkb_keysym_to_lower(syms[i]); + if (sym >= XKB_KEY_a && sym <= XKB_KEY_z) { + char c_char = 'A' + (sym - XKB_KEY_a); + Client *c; + wl_list_for_each(c, &clients, link) { + if (c->mon == selmon && c->jump_char == c_char) { + focusclient(c, 1); + toggleoverview(&(Arg){.i = 1}); + return; + } + } + } else if (sym == XKB_KEY_Escape) { + togglejump(&(Arg){.i = 0}); + return; + } + } + } + /* don't pass when popup is focused * this is better than having popups (like fuzzel or wmenu) closing * while typing in a passed keybind */ @@ -4513,6 +4544,10 @@ mapnotify(struct wl_listener *listener, void *data) { #endif // extra node + c->text_node = mango_text_node_create(c->scene, config.jumhitdata); + wlr_scene_node_lower_to_bottom(&c->text_node->scene_buffer->node); + wlr_scene_node_set_enabled(&c->text_node->scene_buffer->node, false); + for (i = 0; i < 2; i++) { c->splitindicator[i] = wlr_scene_rect_create( c->scene, 0, 0, @@ -6510,6 +6545,7 @@ void unmapnotify(struct wl_listener *listener, void *data) { c->stack_proportion = 0.0f; + mango_text_node_destroy(c->text_node); wlr_scene_node_destroy(&c->scene->node); printstatus(IPC_WATCH_ARRANGGE); motionnotify(0, NULL, 0, 0, 0, 0); From a7acc7f5f305b870d2b2d192223f7e617836b585 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 17 Jun 2026 08:35:44 +0800 Subject: [PATCH 315/328] opt: Reduce unnecessary text drawing --- src/animation/client.h | 18 +-- src/animation/layer.h | 10 +- src/animation/tag.h | 44 +++--- src/dispatch/bind_define.h | 35 ++--- src/draw/text-node.c | 286 ++++++++++++++++++++++++------------- src/draw/text-node.h | 60 +++++--- src/layout/dwindle.h | 15 +- src/layout/horizontal.h | 10 +- src/layout/vertical.h | 6 +- src/mango.c | 18 +-- 10 files changed, 308 insertions(+), 194 deletions(-) diff --git a/src/animation/client.h b/src/animation/client.h index 35d2a30f..aa0df0c2 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -380,10 +380,10 @@ void client_draw_shadow(Client *c) { top_offset = GEZERO(c->mon->m.y - absolute_shadow_box.y); } - left_offset = MIN(left_offset, shadow_box.width); - right_offset = MIN(right_offset, shadow_box.width); - top_offset = MIN(top_offset, shadow_box.height); - bottom_offset = MIN(bottom_offset, shadow_box.height); + left_offset = MANGO_MIN(left_offset, shadow_box.width); + right_offset = MANGO_MIN(right_offset, shadow_box.width); + top_offset = MANGO_MIN(top_offset, shadow_box.height); + bottom_offset = MANGO_MIN(bottom_offset, shadow_box.height); wlr_scene_node_set_position(&c->shadow->node, shadow_box.x + left_offset, shadow_box.y + top_offset); @@ -577,12 +577,12 @@ void apply_border(Client *c) { if (right_offset > 0) { inner_surface_width = - MIN(clip_box.width, inner_surface_width + right_offset); + MANGO_MIN(clip_box.width, inner_surface_width + right_offset); } if (bottom_offset > 0) { inner_surface_height = - MIN(clip_box.height, inner_surface_height + bottom_offset); + MANGO_MIN(clip_box.height, inner_surface_height + bottom_offset); } struct clipped_region clipped_region = { @@ -1010,7 +1010,7 @@ void fadeout_client_animation_next_tick(Client *c) { double percent = config.fadeout_begin_opacity - (opacity_eased_progress * config.fadeout_begin_opacity); - double opacity = MAX(percent, 0); + double opacity = MANGO_MAX(percent, 0); if (config.animation_fade_out && !c->nofadeout) wlr_scene_node_for_each_buffer(&c->scene->node, @@ -1285,8 +1285,8 @@ void resize(Client *c, struct wlr_box geo, int32_t interact) { if (is_scroller_layout(c->mon) && (!c->isfloating || c == grabc)) { c->geom = geo; - c->geom.width = MAX(1 + 2 * (int32_t)c->bw, c->geom.width); - c->geom.height = MAX(1 + 2 * (int32_t)c->bw, c->geom.height); + c->geom.width = MANGO_MAX(1 + 2 * (int32_t)c->bw, c->geom.width); + c->geom.height = MANGO_MAX(1 + 2 * (int32_t)c->bw, c->geom.height); } else { // 这里会限制不允许窗口划出屏幕 c->geom = geo; applybounds( diff --git a/src/animation/layer.h b/src/animation/layer.h index 90f1b4ee..7ccabe33 100644 --- a/src/animation/layer.h +++ b/src/animation/layer.h @@ -279,7 +279,7 @@ void fadeout_layer_animation_next_tick(LayerSurface *l) { double percent = config.fadeout_begin_opacity - (opacity_eased_progress * config.fadeout_begin_opacity); - double opacity = MAX(percent, 0.0f); + double opacity = MANGO_MAX(percent, 0.0f); if (config.animation_fade_out) wlr_scene_node_for_each_buffer(&l->scene->node, @@ -323,10 +323,10 @@ void layer_animation_next_tick(LayerSurface *l) { double opacity_eased_progress = find_animation_curve_at(animation_passed, OPAFADEIN); - double opacity = - MIN(config.fadein_begin_opacity + - opacity_eased_progress * (1.0 - config.fadein_begin_opacity), - 1.0f); + double opacity = MANGO_MIN(config.fadein_begin_opacity + + opacity_eased_progress * + (1.0 - config.fadein_begin_opacity), + 1.0f); if (config.animation_fade_in) wlr_scene_node_for_each_buffer(&l->scene->node, diff --git a/src/animation/tag.h b/src/animation/tag.h index 893d4b0b..c8eeb425 100644 --- a/src/animation/tag.h +++ b/src/animation/tag.h @@ -23,23 +23,23 @@ void set_tagin_animation(Monitor *m, Client *c) { c->animainit_geom.x = config.tag_animation_direction == VERTICAL ? c->animation.current.x - : MAX(c->mon->m.x + c->mon->m.width, - c->geom.x + c->mon->m.width); + : MANGO_MAX(c->mon->m.x + c->mon->m.width, + c->geom.x + c->mon->m.width); c->animainit_geom.y = config.tag_animation_direction == VERTICAL - ? MAX(c->mon->m.y + c->mon->m.height, - c->geom.y + c->mon->m.height) + ? MANGO_MAX(c->mon->m.y + c->mon->m.height, + c->geom.y + c->mon->m.height) : c->animation.current.y; } else { - c->animainit_geom.x = - config.tag_animation_direction == VERTICAL - ? c->animation.current.x - : MIN(m->m.x - c->geom.width, c->geom.x - c->mon->m.width); - c->animainit_geom.y = - config.tag_animation_direction == VERTICAL - ? MIN(m->m.y - c->geom.height, c->geom.y - c->mon->m.height) - : c->animation.current.y; + c->animainit_geom.x = config.tag_animation_direction == VERTICAL + ? c->animation.current.x + : MANGO_MIN(m->m.x - c->geom.width, + c->geom.x - c->mon->m.width); + c->animainit_geom.y = config.tag_animation_direction == VERTICAL + ? MANGO_MIN(m->m.y - c->geom.height, + c->geom.y - c->mon->m.height) + : c->animation.current.y; } } @@ -84,13 +84,13 @@ void set_tagout_animation(Monitor *m, Client *c) { : m->pertag->curtag > m->pertag->prevtag; if (going_forward) { c->pending = c->geom; - c->pending.x = - config.tag_animation_direction == VERTICAL - ? c->animation.current.x - : MIN(c->mon->m.x - c->geom.width, c->geom.x - c->mon->m.width); + c->pending.x = config.tag_animation_direction == VERTICAL + ? c->animation.current.x + : MANGO_MIN(c->mon->m.x - c->geom.width, + c->geom.x - c->mon->m.width); c->pending.y = config.tag_animation_direction == VERTICAL - ? MIN(c->mon->m.y - c->geom.height, - c->geom.y - c->mon->m.height) + ? MANGO_MIN(c->mon->m.y - c->geom.height, + c->geom.y - c->mon->m.height) : c->animation.current.y; resize(c, c->geom, 0); @@ -98,11 +98,11 @@ void set_tagout_animation(Monitor *m, Client *c) { c->pending = c->geom; c->pending.x = config.tag_animation_direction == VERTICAL ? c->animation.current.x - : MAX(c->mon->m.x + c->mon->m.width, - c->geom.x + c->mon->m.width); + : MANGO_MAX(c->mon->m.x + c->mon->m.width, + c->geom.x + c->mon->m.width); c->pending.y = config.tag_animation_direction == VERTICAL - ? MAX(c->mon->m.y + c->mon->m.height, - c->geom.y + c->mon->m.height) + ? MANGO_MAX(c->mon->m.y + c->mon->m.height, + c->geom.y + c->mon->m.height) : c->animation.current.y; resize(c, c->geom, 0); } diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index c3a35676..1483e296 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -264,7 +264,7 @@ int32_t incnmaster(const Arg *arg) { if (!arg || !selmon) return 0; selmon->pertag->nmasters[selmon->pertag->curtag] = - MAX(selmon->pertag->nmasters[selmon->pertag->curtag] + arg->i, 0); + MANGO_MAX(selmon->pertag->nmasters[selmon->pertag->curtag] + arg->i, 0); arrange(selmon, false, false); return 0; } @@ -736,12 +736,12 @@ int32_t smartmovewin(const Arg *arg) { continue; buttom = tc->geom.y + tc->geom.height + config.gappiv; if (top > buttom && ny < buttom) { - tar = MAX(tar, buttom); + tar = MANGO_MAX(tar, buttom); }; } ny = tar == -99999 ? ny : tar; - ny = MAX(ny, c->mon->w.y + c->mon->gappov); + ny = MANGO_MAX(ny, c->mon->w.y + c->mon->gappov); break; case DOWN: tar = 99999; @@ -756,12 +756,12 @@ int32_t smartmovewin(const Arg *arg) { continue; top = tc->geom.y - config.gappiv; if (buttom < top && (ny + c->geom.height) > top) { - tar = MIN(tar, top - c->geom.height); + tar = MANGO_MIN(tar, top - c->geom.height); }; } ny = tar == 99999 ? ny : tar; - ny = MIN(ny, c->mon->w.y + c->mon->w.height - c->geom.height - - c->mon->gappov); + ny = MANGO_MIN(ny, c->mon->w.y + c->mon->w.height - c->geom.height - + c->mon->gappov); break; case LEFT: tar = -99999; @@ -776,12 +776,12 @@ int32_t smartmovewin(const Arg *arg) { continue; right = tc->geom.x + tc->geom.width + config.gappih; if (left > right && nx < right) { - tar = MAX(tar, right); + tar = MANGO_MAX(tar, right); }; } nx = tar == -99999 ? nx : tar; - nx = MAX(nx, c->mon->w.x + c->mon->gappoh); + nx = MANGO_MAX(nx, c->mon->w.x + c->mon->gappoh); break; case RIGHT: tar = 99999; @@ -795,12 +795,12 @@ int32_t smartmovewin(const Arg *arg) { continue; left = tc->geom.x - config.gappih; if (right < left && (nx + c->geom.width) > left) { - tar = MIN(tar, left - c->geom.width); + tar = MANGO_MIN(tar, left - c->geom.width); }; } nx = tar == 99999 ? nx : tar; - nx = MIN(nx, c->mon->w.x + c->mon->w.width - c->geom.width - - c->mon->gappoh); + nx = MANGO_MIN(nx, c->mon->w.x + c->mon->w.width - c->geom.width - + c->mon->gappoh); break; } @@ -828,7 +828,7 @@ int32_t smartresizewin(const Arg *arg) { switch (arg->i) { case UP: nh -= selmon->w.height / 8; - nh = MAX(nh, selmon->w.height / 10); + nh = MANGO_MAX(nh, selmon->w.height / 10); break; case DOWN: tar = -99999; @@ -843,7 +843,7 @@ int32_t smartresizewin(const Arg *arg) { continue; top = tc->geom.y - config.gappiv; if (buttom < top && (nh + c->geom.y) > top) { - tar = MAX(tar, top - c->geom.y); + tar = MANGO_MAX(tar, top - c->geom.y); }; } nh = tar == -99999 ? nh : tar; @@ -852,7 +852,7 @@ int32_t smartresizewin(const Arg *arg) { break; case LEFT: nw -= selmon->w.width / 16; - nw = MAX(nw, selmon->w.width / 10); + nw = MANGO_MAX(nw, selmon->w.width / 10); break; case RIGHT: tar = 99999; @@ -866,7 +866,7 @@ int32_t smartresizewin(const Arg *arg) { continue; left = tc->geom.x - config.gappih; if (right < left && (nw + c->geom.x) > left) { - tar = MIN(tar, left - c->geom.x); + tar = MANGO_MIN(tar, left - c->geom.x); }; } @@ -1063,7 +1063,7 @@ int32_t switch_layout(const Arg *arg) { if (config.circle_layout_count != 0) { for (jk = 0; jk < config.circle_layout_count; jk++) { - len = MAX( + len = MANGO_MAX( strlen(config.circle_layout[jk]), strlen(selmon->pertag->ltidxs[selmon->pertag->curtag]->name)); @@ -1082,7 +1082,8 @@ int32_t switch_layout(const Arg *arg) { } for (ji = 0; ji < LENGTH(layouts); ji++) { - len = MAX(strlen(layouts[ji].name), strlen(target_layout_name)); + len = + MANGO_MAX(strlen(layouts[ji].name), strlen(target_layout_name)); if (strncmp(layouts[ji].name, target_layout_name, len) == 0) { selmon->pertag->ltidxs[selmon->pertag->curtag] = &layouts[ji]; diff --git a/src/draw/text-node.c b/src/draw/text-node.c index b96cd4ac..40cd2ff0 100644 --- a/src/draw/text-node.c +++ b/src/draw/text-node.c @@ -1,19 +1,14 @@ #include "text-node.h" + #include <cairo.h> #include <drm_fourcc.h> #include <glib.h> #include <pango/pangocairo.h> #include <stdlib.h> +#include <string.h> #include <wayland-server-core.h> #include <wlr/interfaces/wlr_buffer.h> -// 自定义 wlr_buffer,用于将 Cairo Surface 包装并注入 wlr_scene -struct mango_text_buffer { - struct wlr_buffer base; - cairo_surface_t *surface; -}; - -// 全局字体描述缓存 static GHashTable *font_desc_cache = NULL; static PangoFontDescription *get_cached_font_desc(const char *font_desc) { @@ -39,10 +34,8 @@ void mango_text_global_finish(void) { } } -// wlr_buffer 实现 static void text_buffer_destroy(struct wlr_buffer *wlr_buffer) { struct mango_text_buffer *buf = wl_container_of(wlr_buffer, buf, base); - cairo_surface_destroy(buf->surface); free(buf); } @@ -78,23 +71,26 @@ struct mango_text_node *mango_text_node_create(struct wlr_scene_tree *parent, return NULL; } - node->fg_color[0] = data.fg_color[0]; - node->fg_color[1] = data.fg_color[1]; - node->fg_color[2] = data.fg_color[2]; - node->fg_color[3] = data.fg_color[3]; - node->bg_color[0] = data.bg_color[0]; - node->bg_color[1] = data.bg_color[1]; - node->bg_color[2] = data.bg_color[2]; - node->bg_color[3] = data.bg_color[3]; - node->border_color[0] = data.border_color[0]; - node->border_color[1] = data.border_color[1]; - node->border_color[2] = data.border_color[2]; - node->border_color[3] = data.border_color[3]; + memcpy(node->fg_color, data.fg_color, sizeof(node->fg_color)); + memcpy(node->bg_color, data.bg_color, sizeof(node->bg_color)); + memcpy(node->border_color, data.border_color, sizeof(node->border_color)); node->border_width = data.border_width; node->corner_radius = data.corner_radius; node->padding_x = data.padding_x; node->padding_y = data.padding_y; - node->font_desc = data.font_desc ? data.font_desc : "monospace Bold 24"; + node->font_desc = + g_strdup(data.font_desc ? data.font_desc : "monospace Bold 24"); + + node->cached_text = NULL; + node->cached_scale = -1.0f; + node->cached_font_desc = NULL; + + node->measure_surface = + cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); + node->measure_cr = cairo_create(node->measure_surface); + node->measure_context = pango_cairo_create_context(node->measure_cr); + node->measure_layout = pango_layout_new(node->measure_context); + node->measure_scale = 1.0f; return node; } @@ -103,7 +99,31 @@ void mango_text_node_destroy(struct mango_text_node *node) { if (!node) return; + if (node->buffer) { + wlr_buffer_drop(&node->buffer->base); + node->buffer = NULL; + } + + if (node->surface) { + cairo_surface_destroy(node->surface); + node->surface = NULL; + } + + if (node->measure_layout) + g_object_unref(node->measure_layout); + if (node->measure_context) + g_object_unref(node->measure_context); + if (node->measure_cr) + cairo_destroy(node->measure_cr); + if (node->measure_surface) + cairo_surface_destroy(node->measure_surface); + wlr_scene_node_destroy(&node->scene_buffer->node); + + g_free(node->font_desc); + g_free(node->cached_text); + g_free(node->cached_font_desc); + free(node); } @@ -118,28 +138,42 @@ void mango_text_node_set_background(struct mango_text_node *node, float r, } void mango_text_node_set_border(struct mango_text_node *node, float r, float g, - float b, float a, float width, float radius) { + float b, float a, int32_t width, + int32_t radius) { if (!node) return; node->border_color[0] = r; node->border_color[1] = g; node->border_color[2] = b; node->border_color[3] = a; - node->border_width = width > 0.0f ? width : 0.0f; - node->corner_radius = radius >= 0.0f ? radius : 0.0f; + node->border_width = width > 0 ? width : 0; + node->corner_radius = radius; } -void mango_text_node_set_padding(struct mango_text_node *node, float pad_x, - float pad_y) { +void mango_text_node_set_padding(struct mango_text_node *node, int32_t pad_x, + int32_t pad_y) { if (!node) return; - node->padding_x = pad_x >= 0.0f ? pad_x : 0.0f; - node->padding_y = pad_y >= 0.0f ? pad_y : 0.0f; + node->padding_x = pad_x >= 0 ? pad_x : 0; + node->padding_y = pad_y >= 0 ? pad_y : 0; +} + +static void get_text_pixel_size(struct mango_text_node *node, const char *text, + float scale, int32_t *out_w, int32_t *out_h) { + if (node->measure_scale != scale) { + pango_cairo_context_set_resolution(node->measure_context, 96.0 * scale); + node->measure_scale = scale; + } + + PangoFontDescription *desc = get_cached_font_desc(node->font_desc); + pango_layout_set_font_description(node->measure_layout, desc); + pango_layout_set_text(node->measure_layout, text, -1); + + pango_layout_get_pixel_size(node->measure_layout, out_w, out_h); } static void draw_rounded_rect(cairo_t *cr, double x, double y, double w, double h, double r) { - // 使用 Cairo 的标准圆角矩形路径 double degrees = G_PI / 180.0; cairo_new_sub_path(cr); cairo_arc(cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees); @@ -150,86 +184,133 @@ static void draw_rounded_rect(cairo_t *cr, double x, double y, double w, } void mango_text_node_update(struct mango_text_node *node, const char *text, - float scale) { if (!node || !text) return; if (scale <= 0.0f) scale = 1.0f; - const char *font_desc = node->font_desc; - PangoFontDescription *desc = get_cached_font_desc(font_desc); - - // 测量文字像素尺寸(无缩放的实际像素) - cairo_surface_t *dummy_surface = - cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); - cairo_t *dummy_cr = cairo_create(dummy_surface); - PangoContext *dummy_ctx = pango_cairo_create_context(dummy_cr); - pango_cairo_context_set_resolution(dummy_ctx, 96.0 * scale); - PangoLayout *dummy_layout = pango_layout_new(dummy_ctx); - pango_layout_set_font_description(dummy_layout, desc); - pango_layout_set_text(dummy_layout, text, -1); - - int text_pixel_w, text_pixel_h; - pango_layout_get_pixel_size(dummy_layout, &text_pixel_w, &text_pixel_h); - - g_object_unref(dummy_layout); - g_object_unref(dummy_ctx); - cairo_destroy(dummy_cr); - cairo_surface_destroy(dummy_surface); - - if (text_pixel_w <= 0 || text_pixel_h <= 0) { - wlr_scene_buffer_set_buffer(node->scene_buffer, NULL); - node->logical_width = 0; - node->logical_height = 0; + /* 脏检查 */ + if (node->cached_scale == scale && node->cached_font_desc && + strcmp(node->cached_font_desc, node->font_desc) == 0 && + node->cached_text && strcmp(node->cached_text, text) == 0 && + memcmp(node->cached_fg_color, node->fg_color, sizeof(node->fg_color)) == + 0 && + memcmp(node->cached_bg_color, node->bg_color, sizeof(node->bg_color)) == + 0 && + memcmp(node->cached_border_color, node->border_color, + sizeof(node->border_color)) == 0 && + node->cached_border_width == node->border_width && + node->cached_corner_radius == node->corner_radius && + node->cached_padding_x == node->padding_x && + node->cached_padding_y == node->padding_y) { return; } - float logical_text_w = text_pixel_w / scale; - float logical_text_h = text_pixel_h / scale; + /* 更新缓存 */ + g_free(node->cached_text); + node->cached_text = g_strdup(text); + g_free(node->cached_font_desc); + node->cached_font_desc = g_strdup(node->font_desc); + node->cached_scale = scale; + memcpy(node->cached_fg_color, node->fg_color, sizeof(node->fg_color)); + memcpy(node->cached_bg_color, node->bg_color, sizeof(node->bg_color)); + memcpy(node->cached_border_color, node->border_color, + sizeof(node->border_color)); + node->cached_border_width = node->border_width; + node->cached_corner_radius = node->corner_radius; + node->cached_padding_x = node->padding_x; + node->cached_padding_y = node->padding_y; - float pad_x = node->padding_x; - float pad_y = node->padding_y; - float box_logical_w = logical_text_w + 2.0f * pad_x; - float box_logical_h = logical_text_h + 2.0f * pad_y; + int32_t text_pixel_w, text_pixel_h; + get_text_pixel_size(node, text, scale, &text_pixel_w, &text_pixel_h); - float border = node->border_width; - bool draw_border = (border > 0.0f) && (node->border_color[3] > 0.0f); - bool draw_bg = (node->bg_color[3] > 0.0f); + if (text_pixel_w <= 0 || text_pixel_h <= 0) { + wlr_scene_buffer_set_buffer(node->scene_buffer, NULL); + if (node->buffer) { + wlr_buffer_drop(&node->buffer->base); + node->buffer = NULL; + } + if (node->surface) { + cairo_surface_destroy(node->surface); + node->surface = NULL; + } + node->logical_width = 0; + node->logical_height = 0; + wlr_scene_buffer_set_dest_size(node->scene_buffer, 0, 0); + return; + } - int surface_pixel_w = (int)((box_logical_w + 2.0f * border) * scale + 0.5f); - int surface_pixel_h = (int)((box_logical_h + 2.0f * border) * scale + 0.5f); + /* 逻辑尺寸:文本 + 内边距 + 边框(整数计算) */ + int32_t logical_text_w = (int32_t)(text_pixel_w / scale + 0.5f); + int32_t logical_text_h = (int32_t)(text_pixel_h / scale + 0.5f); + int32_t box_logical_w = logical_text_w + 2 * node->padding_x; + int32_t box_logical_h = logical_text_h + 2 * node->padding_y; - if (surface_pixel_w < 1) - surface_pixel_w = 1; - if (surface_pixel_h < 1) - surface_pixel_h = 1; + /* 加上边框后,乘以 scale 得到物理像素(表面尺寸),向上取整 */ + int32_t required_pixel_w = + (int32_t)((box_logical_w + 2 * node->border_width) * scale + 0.5f); + int32_t required_pixel_h = + (int32_t)((box_logical_h + 2 * node->border_width) * scale + 0.5f); + if (required_pixel_w < 1) + required_pixel_w = 1; + if (required_pixel_h < 1) + required_pixel_h = 1; - cairo_surface_t *surface = cairo_image_surface_create( - CAIRO_FORMAT_ARGB32, surface_pixel_w, surface_pixel_h); - cairo_t *cr = cairo_create(surface); + bool surface_size_changed = (!node->surface) || + (node->surface_pixel_w != required_pixel_w) || + (node->surface_pixel_h != required_pixel_h); - // 清空为全透明 + if (surface_size_changed) { + if (node->buffer) { + wlr_buffer_drop(&node->buffer->base); + node->buffer = NULL; + } + if (node->surface) { + cairo_surface_destroy(node->surface); + node->surface = NULL; + } + + node->surface = cairo_image_surface_create( + CAIRO_FORMAT_ARGB32, required_pixel_w, required_pixel_h); + node->surface_pixel_w = required_pixel_w; + node->surface_pixel_h = required_pixel_h; + } + + cairo_t *cr = cairo_create(node->surface); + + /* 清空为透明 */ cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.0); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_paint(cr); cairo_set_operator(cr, CAIRO_OPERATOR_OVER); - double bg_x = border * scale; - double bg_y = border * scale; + /* 计算背景矩形(物理像素) */ + double border = node->border_width * scale; + double bg_x = border; + double bg_y = border; double bg_w = box_logical_w * scale; double bg_h = box_logical_h * scale; - double radius = node->corner_radius * scale; - - if (radius < 0) { + /* 圆角半径(物理像素) */ + double radius; + if (node->corner_radius < 0) { + /* 负数表示自动取半宽/半高作为圆角 */ radius = (bg_w < bg_h ? bg_w : bg_h) / 2.0; + } else { + radius = node->corner_radius * scale; } + /* 限制最大圆角 */ if (radius > bg_w / 2.0) radius = bg_w / 2.0; if (radius > bg_h / 2.0) radius = bg_h / 2.0; + bool draw_bg = (node->bg_color[3] > 0.0f); + bool draw_border = + (node->border_width > 0) && (node->border_color[3] > 0.0f); + + /* 绘制背景 */ if (draw_bg) { cairo_set_source_rgba(cr, node->bg_color[0], node->bg_color[1], node->bg_color[2], node->bg_color[3]); @@ -242,12 +323,16 @@ void mango_text_node_update(struct mango_text_node *node, const char *text, } } + /* 绘制文本 */ cairo_save(cr); - cairo_translate(cr, (border + pad_x) * scale, (border + pad_y) * scale); + double text_x = (node->border_width + node->padding_x) * scale; + double text_y = (node->border_width + node->padding_y) * scale; + cairo_translate(cr, text_x, text_y); PangoContext *ctx = pango_cairo_create_context(cr); pango_cairo_context_set_resolution(ctx, 96.0 * scale); PangoLayout *layout = pango_layout_new(ctx); + PangoFontDescription *desc = get_cached_font_desc(node->font_desc); pango_layout_set_font_description(layout, desc); pango_layout_set_text(layout, text, -1); @@ -259,50 +344,51 @@ void mango_text_node_update(struct mango_text_node *node, const char *text, g_object_unref(ctx); cairo_restore(cr); + /* 绘制边框 */ if (draw_border) { cairo_set_source_rgba(cr, node->border_color[0], node->border_color[1], node->border_color[2], node->border_color[3]); - cairo_set_line_width(cr, border * scale); + cairo_set_line_width(cr, border); - double half_lw = border * scale * 0.5; + double half_lw = border * 0.5; double bx = bg_x - half_lw; double by = bg_y - half_lw; - double bw = bg_w + border * scale; - double bh = bg_h + border * scale; + double bw = bg_w + border; + double bh = bg_h + border; if (radius > 0.0) { double outer_radius = radius + half_lw; if (outer_radius < 0.0) outer_radius = 0.0; draw_rounded_rect(cr, bx, by, bw, bh, outer_radius); - cairo_stroke(cr); } else { cairo_rectangle(cr, bx, by, bw, bh); - cairo_stroke(cr); } + cairo_stroke(cr); } - cairo_surface_flush(surface); + cairo_surface_flush(node->surface); + cairo_destroy(cr); + + /* 更新 wlr_buffer */ + if (node->buffer) { + wlr_buffer_drop(&node->buffer->base); + node->buffer = NULL; + } - // 包装成 wlr_buffer struct mango_text_buffer *buf = calloc(1, sizeof(*buf)); if (!buf) { - cairo_surface_destroy(surface); - cairo_destroy(cr); return; } - wlr_buffer_init(&buf->base, &text_buffer_impl, surface_pixel_w, - surface_pixel_h); - buf->surface = surface; + wlr_buffer_init(&buf->base, &text_buffer_impl, node->surface_pixel_w, + node->surface_pixel_h); + buf->surface = node->surface; + node->buffer = buf; wlr_scene_buffer_set_buffer(node->scene_buffer, &buf->base); - wlr_buffer_drop(&buf->base); - // 最终逻辑大小 = 背景框逻辑尺寸 + 边框逻辑尺寸 - node->logical_width = (int)(box_logical_w + 2.0f * border); - node->logical_height = (int)(box_logical_h + 2.0f * border); + node->logical_width = box_logical_w + 2 * node->border_width; + node->logical_height = box_logical_h + 2 * node->border_width; wlr_scene_buffer_set_dest_size(node->scene_buffer, node->logical_width, node->logical_height); - - cairo_destroy(cr); } \ No newline at end of file diff --git a/src/draw/text-node.h b/src/draw/text-node.h index b8c4900d..8979a774 100644 --- a/src/draw/text-node.h +++ b/src/draw/text-node.h @@ -1,24 +1,50 @@ #ifndef MANGO_TEXT_NODE_H #define MANGO_TEXT_NODE_H +#include <cairo.h> +#include <pango/pangocairo.h> #include <wayland-server-core.h> #include <wlr/types/wlr_scene.h> +// 自定义 wlr_buffer,仅用于包装 cairo surface,不负责 surface 的销毁 +struct mango_text_buffer { + struct wlr_buffer base; + cairo_surface_t *surface; +}; + struct mango_text_node { struct wlr_scene_buffer *scene_buffer; - int logical_width; - int logical_height; - const char *font_desc; - // 外观属性 - float fg_color[4]; // 文本颜色 RGBA,默认白色 - float bg_color[4]; // 背景色 RGBA,默认透明 - float border_color[4]; // 边框色 RGBA,默认透明 - int32_t border_width; // 边框宽度(逻辑像素),<=0 则不绘制 - int32_t corner_radius; // 圆角半径(逻辑像素),<0 时自动取 - // min(width,height)/2 形成胶囊 - int32_t padding_x; // 文本左右内边距(逻辑像素) - int32_t padding_y; // 文本上下内边距(逻辑像素) + float fg_color[4]; + float bg_color[4]; + float border_color[4]; + int32_t border_width; + int32_t corner_radius; + int32_t padding_x, padding_y; + char *font_desc; + + char *cached_text; + float cached_scale; + float cached_fg_color[4]; + float cached_bg_color[4]; + float cached_border_color[4]; + float cached_border_width; + float cached_corner_radius; + float cached_padding_x, cached_padding_y; + char *cached_font_desc; + + cairo_surface_t *surface; + struct mango_text_buffer *buffer; + int32_t surface_pixel_w, surface_pixel_h; + + cairo_surface_t *measure_surface; + cairo_t *measure_cr; + PangoContext *measure_context; + PangoLayout *measure_layout; + float measure_scale; + + int32_t logical_width; + int32_t logical_height; }; typedef struct { @@ -38,15 +64,15 @@ void mango_text_node_destroy(struct mango_text_node *node); void mango_text_node_update(struct mango_text_node *node, const char *text, float scale); -// 外观设置接口 void mango_text_node_set_background(struct mango_text_node *node, float r, float g, float b, float a); void mango_text_node_set_border(struct mango_text_node *node, float r, float g, - float b, float a, float width, float radius); -void mango_text_node_set_padding(struct mango_text_node *node, float pad_x, - float pad_y); + float b, float a, int32_t width, + int32_t radius); + +void mango_text_node_set_padding(struct mango_text_node *node, int32_t pad_x, + int32_t pad_y); -void mango_text_node_destroy(struct mango_text_node *node); void mango_text_global_finish(void); #endif \ No newline at end of file diff --git a/src/layout/dwindle.h b/src/layout/dwindle.h index 9562b5d8..5e56a6bf 100644 --- a/src/layout/dwindle.h +++ b/src/layout/dwindle.h @@ -259,7 +259,8 @@ static void dwindle_assign(DwindleNode *node, int32_t ax, int32_t ay, if (node->client) { if (!node->client->isfullscreen && !node->client->ismaximizescreen) { - struct wlr_box box = {ax, ay, MAX(1, aw), MAX(1, ah)}; + struct wlr_box box = {ax, ay, MANGO_MAX(1, aw), + MANGO_MAX(1, ah)}; resize(node->client, box, 0); } } @@ -273,12 +274,12 @@ static void dwindle_assign(DwindleNode *node, int32_t ax, int32_t ay, node->container_w = aw; node->container_h = ah; if (node->split_h) { - int32_t w1 = MAX(1, (int32_t)(aw * node->ratio) - gap_h / 2); + int32_t w1 = MANGO_MAX(1, (int32_t)(aw * node->ratio) - gap_h / 2); dwindle_assign(node->first, ax, ay, w1, ah, gap_h, gap_v); dwindle_assign(node->second, ax + w1 + gap_h, ay, aw - w1 - gap_h, ah, gap_h, gap_v); } else { - int32_t h1 = MAX(1, (int32_t)(ah * node->ratio) - gap_v / 2); + int32_t h1 = MANGO_MAX(1, (int32_t)(ah * node->ratio) - gap_v / 2); dwindle_assign(node->first, ax, ay, aw, h1, gap_h, gap_v); dwindle_assign(node->second, ax, ay + h1 + gap_v, aw, ah - h1 - gap_v, gap_h, gap_v); @@ -357,7 +358,7 @@ static void dwindle_resize_client(Monitor *m, Client *c) { return; if (dwindle_locked_h_node) { - float cw = (float)MAX(1, dwindle_locked_h_node->container_w); + float cw = (float)MANGO_MAX(1, dwindle_locked_h_node->container_w); float ox = (float)(cursor->x - drag_begin_cursorx); if (config.dwindle_smart_resize) { /* Move the boundary toward the cursor: invert direction when @@ -374,7 +375,7 @@ static void dwindle_resize_client(Monitor *m, Client *c) { } if (dwindle_locked_v_node) { - float ch = (float)MAX(1, dwindle_locked_v_node->container_h); + float ch = (float)MANGO_MAX(1, dwindle_locked_v_node->container_h); float oy = (float)(cursor->y - drag_begin_cursory); if (config.dwindle_smart_resize) { /* Same logic for the vertical split line. */ @@ -427,13 +428,13 @@ static void dwindle_resize_client_step(Monitor *m, Client *c, int32_t dx, return; if (h_node && dx) { - float cw = (float)MAX(1, h_node->container_w); + float cw = (float)MANGO_MAX(1, h_node->container_w); float delta = (float)dx / cw; h_node->ratio = CLAMP_FLOAT(h_node->ratio + delta, 0.05f, 0.95f); } if (v_node && dy) { - float ch = (float)MAX(1, v_node->container_h); + float ch = (float)MANGO_MAX(1, v_node->container_h); float delta = (float)dy / ch; v_node->ratio = CLAMP_FLOAT(v_node->ratio + delta, 0.05f, 0.95f); } diff --git a/src/layout/horizontal.h b/src/layout/horizontal.h index 419e218a..8788c210 100644 --- a/src/layout/horizontal.h +++ b/src/layout/horizontal.h @@ -62,7 +62,7 @@ void tile(Monitor *m) { if (!VISIBLEON(c, m) || !ISFAKETILED(c)) continue; if (i < m->pertag->nmasters[m->pertag->curtag]) { - r = MIN(n, m->pertag->nmasters[m->pertag->curtag]) - i; + r = MANGO_MIN(n, m->pertag->nmasters[m->pertag->curtag]) - i; if (c->master_inner_per > 0.0f) { h = master_surplus_height * c->master_inner_per / master_surplus_ratio; @@ -179,7 +179,7 @@ void right_tile(Monitor *m) { if (!VISIBLEON(c, m) || !ISFAKETILED(c)) continue; if (i < m->pertag->nmasters[m->pertag->curtag]) { - r = MIN(n, m->pertag->nmasters[m->pertag->curtag]) - i; + r = MANGO_MIN(n, m->pertag->nmasters[m->pertag->curtag]) - i; if (c->master_inner_per > 0.0f) { h = master_surplus_height * c->master_inner_per / master_surplus_ratio; @@ -345,7 +345,7 @@ void center_tile(Monitor *m) { if (i < nmasters) { // 主区域窗口 - r = MIN(n, nmasters) - i; + r = MANGO_MIN(n, nmasters) - i; if (c->master_inner_per > 0.0f) { h = master_surplus_height * c->master_inner_per / master_surplus_ratio; @@ -518,8 +518,8 @@ void deck(Monitor *m) { continue; if (i < nmasters) { c->master_mfact_per = mfact; - int32_t h = - (m->w.height - 2 * cur_gappov - my) / (MIN(n, nmasters) - i); + int32_t h = (m->w.height - 2 * cur_gappov - my) / + (MANGO_MIN(n, nmasters) - i); client_tile_resize(c, (struct wlr_box){.x = m->w.x + cur_gappoh, .y = m->w.y + cur_gappov + my, diff --git a/src/layout/vertical.h b/src/layout/vertical.h index b1de212e..6c018399 100644 --- a/src/layout/vertical.h +++ b/src/layout/vertical.h @@ -58,7 +58,7 @@ void vertical_tile(Monitor *m) { if (!VISIBLEON(c, m) || !ISFAKETILED(c)) continue; if (i < m->pertag->nmasters[m->pertag->curtag]) { - r = MIN(n, m->pertag->nmasters[m->pertag->curtag]) - i; + r = MANGO_MIN(n, m->pertag->nmasters[m->pertag->curtag]) - i; if (c->master_inner_per > 0.0f) { w = master_surplus_width * c->master_inner_per / master_surplus_ratio; @@ -155,8 +155,8 @@ void vertical_deck(Monitor *m) { continue; if (i < nmasters) { c->master_mfact_per = mfact; - int32_t w = - (m->w.width - 2 * cur_gappoh - mx) / (MIN(n, nmasters) - i); + int32_t w = (m->w.width - 2 * cur_gappoh - mx) / + (MANGO_MIN(n, nmasters) - i); client_tile_resize(c, (struct wlr_box){.x = m->w.x + cur_gappoh + mx, .y = m->w.y + cur_gappov, diff --git a/src/mango.c b/src/mango.c index 0c430f47..72226d97 100644 --- a/src/mango.c +++ b/src/mango.c @@ -99,8 +99,8 @@ #include "draw/text-node.h" /* macros */ -#define MAX(A, B) ((A) > (B) ? (A) : (B)) -#define MIN(A, B) ((A) < (B) ? (A) : (B)) +#define MANGO_MAX(A, B) ((A) > (B) ? (A) : (B)) +#define MANGO_MIN(A, B) ((A) < (B) ? (A) : (B)) #define GEZERO(A) ((A) >= 0 ? (A) : 0) #define CLEANMASK(mask) (mask & ~WLR_MODIFIER_CAPS) #define INSIDEMON(A) \ @@ -1130,8 +1130,8 @@ void client_change_mon(Client *c, Monitor *m) { void applybounds(Client *c, struct wlr_box *bbox) { /* set minimum possible */ - c->geom.width = MAX(1 + 2 * (int32_t)c->bw, c->geom.width); - c->geom.height = MAX(1 + 2 * (int32_t)c->bw, c->geom.height); + c->geom.width = MANGO_MAX(1 + 2 * (int32_t)c->bw, c->geom.width); + c->geom.height = MANGO_MAX(1 + 2 * (int32_t)c->bw, c->geom.height); if (c->geom.x >= bbox->x + bbox->width) c->geom.x = bbox->x + bbox->width - c->geom.width; @@ -1556,7 +1556,7 @@ void set_float_malposition(Client *tc) { y = tc->geom.y; xreverse = 1; yreverse = 1; - offset = MIN(tc->mon->w.width / 20, tc->mon->w.height / 20); + offset = MANGO_MIN(tc->mon->w.width / 20, tc->mon->w.height / 20); wl_list_for_each(c, &clients, link) { if (c->isfloating && c != tc && VISIBLEON(c, tc->mon) && @@ -5612,10 +5612,10 @@ void setfullscreen(Client *c, int32_t fullscreen, } void setgaps(int32_t oh, int32_t ov, int32_t ih, int32_t iv) { - selmon->gappoh = MAX(oh, 0); - selmon->gappov = MAX(ov, 0); - selmon->gappih = MAX(ih, 0); - selmon->gappiv = MAX(iv, 0); + selmon->gappoh = MANGO_MAX(oh, 0); + selmon->gappov = MANGO_MAX(ov, 0); + selmon->gappih = MANGO_MAX(ih, 0); + selmon->gappiv = MANGO_MAX(iv, 0); arrange(selmon, false, false); } From eaaa67c4d9ddcbf2bd88c33349ca2f80f4cc5152 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 17 Jun 2026 08:42:46 +0800 Subject: [PATCH 316/328] opt: optmize label create judge --- src/layout/overview.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/layout/overview.h b/src/layout/overview.h index 06e2213f..32a9a18d 100644 --- a/src/layout/overview.h +++ b/src/layout/overview.h @@ -359,8 +359,7 @@ void create_jump_hints(Monitor *m) { Client *c; wl_list_for_each(c, &clients, link) { - if (VISIBLEON(c, m) && - ((m->isoverview && !client_is_x11_popup(c)) || ISTILED(c))) { + if (VISIBLEON(c, m) && !c->isunglobal && !client_is_x11_popup(c)) { if (label_idx >= 26) break; char c_char = jump_labels[label_idx]; From 98341b7d41ac02322640115c554537188e00e3da Mon Sep 17 00:00:00 2001 From: DarkGuibrine <darkgui@protonmail.com> Date: Tue, 16 Jun 2026 14:54:04 -0300 Subject: [PATCH 317/328] nix: fix build dependency --- meson.build | 2 ++ nix/default.nix | 2 ++ 2 files changed, 4 insertions(+) diff --git a/meson.build b/meson.build index f26f87e1..c0c22202 100644 --- a/meson.build +++ b/meson.build @@ -26,6 +26,7 @@ endif cc = meson.get_compiler('c') libm = cc.find_library('m') +libdrm = dependency('libdrm') xcb = dependency('xcb', required : get_option('xwayland')) xlibs = dependency('xcb-icccm', required : get_option('xwayland')) wayland_server_dep = dependency('wayland-server',version: '>=1.23.1') @@ -100,6 +101,7 @@ executable('mango', wayland_sources, dependencies : [ libm, + libdrm, xcb, xlibs, libscenefx_dep, diff --git a/nix/default.nix b/nix/default.nix index cbfc917a..8c35eb83 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -3,6 +3,7 @@ libX11, libinput, libxcb, + libdrm, libxkbcommon, pcre2, pango, @@ -58,6 +59,7 @@ stdenv.mkDerivation { wlroots_0_19 scenefx libGL + libdrm ] ++ lib.optionals enableXWayland [ libX11 From 5a60f3906427d801f9ede15ef4c44b28928149dc Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 17 Jun 2026 10:22:37 +0800 Subject: [PATCH 318/328] feat: monocle layout support title tab --- docs/visuals/theming.md | 19 +- src/action/client.h | 20 +- src/animation/client.h | 14 +- src/animation/tag.h | 4 +- src/config/parse_config.h | 145 +++++++---- src/dispatch/bind_define.h | 1 + src/draw/text-node.c | 485 +++++++++++++++++++++++++++++++++++-- src/draw/text-node.h | 130 ++++++++-- src/fetch/monitor.h | 8 + src/layout/arrange.h | 29 +++ src/layout/horizontal.h | 68 ++++-- src/mango.c | 53 +++- 12 files changed, 843 insertions(+), 133 deletions(-) diff --git a/docs/visuals/theming.md b/docs/visuals/theming.md index 71d3a818..928b1d24 100644 --- a/docs/visuals/theming.md +++ b/docs/visuals/theming.md @@ -14,6 +14,7 @@ Control the sizing of window borders and gaps. | `gappiv` | `5` | Vertical inner gap. | | `gappoh` | `10` | Horizontal outer gap (between windows and screen edges). | | `gappov` | `10` | Vertical outer gap. | +| `tab_bar_height` | `50` | Height of the tab bar for monocle layout. | ## Colors @@ -55,14 +56,16 @@ You can also color-code windows based on their state: ### Overview Jump Mode | Setting | Default | Description | | :--- | :--- | :--- | -| `jump_hit_fg_color` | `0xc4939dff` | label text color. | -| `jump_hit_bg_color` | `0x201b14ff` | label background color. -| `jump_hit_border_color` | `0x8BAA9Bff` | label border color. -| `jump_hit_border_width` | `4` | label border width. -| `jump_hit_corner_radius` | `5` | label corner radius. -| `jump_hit_padding_x` | `10` | label horizontal padding. -| `jump_hit_padding_y` | `10` | label vertical padding. -| `jump_hit_font_desc` | `monospace Bold 24` | label font set.| +| `text_decorate_fg_color` | `0xc4939dff` | label text color. | +| `text_decorate_bg_color` | `0x201b14ff` | label background color.| +| `text_decorate_focus_fg_color` | `0x201b14ff` | label text color for focus. | +| `text_decorate_focus_bg_color` | `0xc4939dff` | label background color for focus.| +| `text_decorate_border_color` | `0x8BAA9Bff` | label border color.| +| `text_decorate_border_width` | `4` | label border width.| +| `text_decorate_corner_radius` | `5` | label corner radius.| +| `text_decorate_padding_x` | `10` | label horizontal padding.| +| `text_decorate_padding_y` | `10` | label vertical padding.| +| `text_decorate_font_desc` | `monospace Bold 16` | label font set.| ## Cursor Theme diff --git a/src/action/client.h b/src/action/client.h index 11602e70..d13cb5c6 100644 --- a/src/action/client.h +++ b/src/action/client.h @@ -95,4 +95,22 @@ void client_pending_force_kill(Client *c) { if (!c) return; kill(c->pid, SIGKILL); -} \ No newline at end of file +} + +void client_add_text_node(Client *c) { + c->text_node = mango_text_node_create(c->scene, config.textdata); + wlr_scene_node_lower_to_bottom(&c->text_node->scene_buffer->node); + wlr_scene_node_set_enabled(&c->text_node->scene_buffer->node, false); +} + +void client_add_titlebar_node(Client *c) { + MangoNodeData *mangonodedata = ecalloc(1, sizeof(MangoNodeData)); + mangonodedata->node_data = c; + mangonodedata->type = MANGO_TITLE_NODE; + + c->titlebar_node = mango_titlebar_node_create( + mangonodedata, layers[LyrDecorate], config.textdata, 0, 0); + wlr_scene_node_lower_to_bottom(&c->titlebar_node->scene_buffer->node); + wlr_scene_node_set_enabled(&c->titlebar_node->scene_buffer->node, false); + mango_titlebar_node_update(c->titlebar_node, client_get_title(c), 1.0); +} diff --git a/src/animation/client.h b/src/animation/client.h index aa0df0c2..22a7dcb1 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -398,6 +398,16 @@ void client_draw_shadow(Client *c) { wlr_scene_shadow_set_clipped_region(c->shadow, clipped_region); } +void global_draw_titlebar(Client *c, int32_t x, int32_t y, int32_t width, + int32_t height) { + if (!c->titlebar_node) + return; + + wlr_scene_node_set_position(&c->titlebar_node->scene_buffer->node, x, y); + wlr_scene_node_set_enabled(&c->titlebar_node->scene_buffer->node, true); + mango_titlebar_node_set_size(c->titlebar_node, width, height); +} + void apply_split_border(Client *c, bool hit_no_border) { if (c->iskilling || !c->mon || !client_surface(c)->mapped) @@ -656,8 +666,10 @@ struct ivec2 clip_to_hide(Client *c, struct wlr_box *clip_box) { (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)) { + } else if (c->is_clip_to_hide && VISIBLEON(c, c->mon) && + (!c->is_monocle_hide || !is_monocle_layout(c->mon))) { c->is_clip_to_hide = false; + c->is_monocle_hide = false; wlr_scene_node_set_enabled(&c->scene->node, true); } diff --git a/src/animation/tag.h b/src/animation/tag.h index c8eeb425..859ee1b8 100644 --- a/src/animation/tag.h +++ b/src/animation/tag.h @@ -45,8 +45,10 @@ void set_tagin_animation(Monitor *m, Client *c) { void set_arrange_visible(Monitor *m, Client *c, bool want_animation) { - if (!c->is_clip_to_hide || !ISTILED(c) || !is_scroller_layout(c->mon)) { + if (!ISTILED(c) || ((!c->is_clip_to_hide || !is_scroller_layout(c->mon)) && + (!c->is_monocle_hide || !is_monocle_layout(c->mon)))) { c->is_clip_to_hide = false; + c->is_monocle_hide = false; wlr_scene_node_set_enabled(&c->scene->node, true); wlr_scene_node_set_enabled(&c->scene_surface->node, true); } diff --git a/src/config/parse_config.h b/src/config/parse_config.h index e41bcf90..7b7f0daf 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -332,6 +332,7 @@ typedef struct { uint32_t gappoh; uint32_t gappov; uint32_t borderpx; + uint32_t tab_bar_height; float scratchpad_width_ratio; float scratchpad_height_ratio; float rootcolor[4]; @@ -408,7 +409,7 @@ typedef struct { struct xkb_context *ctx; struct xkb_keymap *keymap; - JumphitData jumhitdata; + TextDrawData textdata; } Config; typedef int32_t (*FuncType)(const Arg *); @@ -1763,50 +1764,76 @@ bool parse_option(Config *config, char *key, char *value) { config->cursor_size = atoi(value); } else if (strcmp(key, "cursor_theme") == 0) { config->cursor_theme = strdup(value); - } else if (strcmp(key, "jump_hit_fg_color") == 0) { + } else if (strcmp(key, "text_decorate_font_desc") == 0) { + config->textdata.font_desc = strdup(value); + } else if (strcmp(key, "text_decorate_fg_color") == 0) { int64_t color = parse_color(value); if (color == -1) { fprintf(stderr, - "\033[1m\033[31m[ERROR]:\033[33m Invalid jump_hit_fg_color " + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "text_decorate_fg_color " "format: %s\n", value); return false; } else { - convert_hex_to_rgba(config->jumhitdata.fg_color, color); + convert_hex_to_rgba(config->textdata.fg_color, color); } - } else if (strcmp(key, "jump_hit_font_desc") == 0) { - config->jumhitdata.font_desc = strdup(value); - } else if (strcmp(key, "jump_hit_bg_color") == 0) { + } else if (strcmp(key, "text_decorate_bg_color") == 0) { int64_t color = parse_color(value); if (color == -1) { fprintf(stderr, - "\033[1m\033[31m[ERROR]:\033[33m Invalid jump_hit_bg_color " + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "text_decorate_bg_color " "format: %s\n", value); return false; } else { - convert_hex_to_rgba(config->jumhitdata.bg_color, color); + convert_hex_to_rgba(config->textdata.bg_color, color); } - } else if (strcmp(key, "jump_hit_border_color") == 0) { + } else if (strcmp(key, "text_decorate_focus_fg_color") == 0) { int64_t color = parse_color(value); if (color == -1) { - fprintf( - stderr, - "\033[1m\033[31m[ERROR]:\033[33m Invalid jump_hit_border_color " - "format: %s\n", - value); + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "text_decorate_focus_fg_color " + "format: %s\n", + value); return false; } else { - convert_hex_to_rgba(config->jumhitdata.border_color, color); + convert_hex_to_rgba(config->textdata.focus_fg_color, color); } - } else if (strcmp(key, "jump_hit_border_width") == 0) { - config->jumhitdata.border_width = CLAMP_INT(atoi(value), 0, 100); - } else if (strcmp(key, "jump_hit_corner_radius") == 0) { - config->jumhitdata.corner_radius = CLAMP_INT(atoi(value), 0, 100); - } else if (strcmp(key, "jump_hit_padding_x") == 0) { - config->jumhitdata.padding_x = CLAMP_INT(atoi(value), 0, 100); - } else if (strcmp(key, "jump_hit_padding_y") == 0) { - config->jumhitdata.padding_y = CLAMP_INT(atoi(value), 0, 100); + } else if (strcmp(key, "text_decorate_focus_bg_color") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "text_decorate_focus_bg_color " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->textdata.focus_bg_color, color); + } + } else if (strcmp(key, "text_decorate_border_color") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "text_decorate_border_color " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->textdata.border_color, color); + } + } else if (strcmp(key, "text_decorate_border_width") == 0) { + config->textdata.border_width = CLAMP_INT(atoi(value), 0, 100); + } else if (strcmp(key, "text_decorate_corner_radius") == 0) { + config->textdata.corner_radius = CLAMP_INT(atoi(value), 0, 100); + } else if (strcmp(key, "text_decorate_padding_x") == 0) { + config->textdata.padding_x = CLAMP_INT(atoi(value), 0, 100); + } else if (strcmp(key, "text_decorate_padding_y") == 0) { + config->textdata.padding_y = CLAMP_INT(atoi(value), 0, 100); } else if (strcmp(key, "disable_while_typing") == 0) { config->disable_while_typing = atoi(value); } else if (strcmp(key, "left_handed") == 0) { @@ -1853,6 +1880,8 @@ bool parse_option(Config *config, char *key, char *value) { config->scratchpad_height_ratio = atof(value); } else if (strcmp(key, "borderpx") == 0) { config->borderpx = atoi(value); + } else if (strcmp(key, "tab_bar_height") == 0) { + config->tab_bar_height = atoi(value); } else if (strcmp(key, "rootcolor") == 0) { int64_t color = parse_color(value); if (color == -1) { @@ -3296,9 +3325,9 @@ void free_config(void) { config.cursor_theme = NULL; } - if (config.jumhitdata.font_desc) { - free((void *)config.jumhitdata.font_desc); - config.jumhitdata.font_desc = NULL; + if (config.textdata.font_desc) { + free((void *)config.textdata.font_desc); + config.textdata.font_desc = NULL; } if (config.tablet_map_to_mon) { @@ -3464,6 +3493,7 @@ void override_config(void) { config.scratchpad_height_ratio = CLAMP_FLOAT(config.scratchpad_height_ratio, 0.1f, 1.0f); config.borderpx = CLAMP_INT(config.borderpx, 0, 200); + config.tab_bar_height = CLAMP_INT(config.tab_bar_height, 5, 500); config.smartgaps = CLAMP_INT(config.smartgaps, 0, 1); config.blur = CLAMP_INT(config.blur, 0, 1); config.blur_layer = CLAMP_INT(config.blur_layer, 0, 1); @@ -3492,14 +3522,12 @@ void override_config(void) { config.unfocused_opacity = CLAMP_FLOAT(config.unfocused_opacity, 0.0f, 1.0f); - config.jumhitdata.border_width = - CLAMP_INT(config.jumhitdata.border_width, 0, 100); - config.jumhitdata.corner_radius = - CLAMP_INT(config.jumhitdata.corner_radius, 0, 100); - config.jumhitdata.padding_x = - CLAMP_INT(config.jumhitdata.padding_x, 0, 100); - config.jumhitdata.padding_y = - CLAMP_INT(config.jumhitdata.padding_y, 0, 100); + config.textdata.border_width = + CLAMP_INT(config.textdata.border_width, 0, 100); + config.textdata.corner_radius = + CLAMP_INT(config.textdata.corner_radius, 0, 100); + config.textdata.padding_x = CLAMP_INT(config.textdata.padding_x, 0, 100); + config.textdata.padding_y = CLAMP_INT(config.textdata.padding_y, 0, 100); } void set_value_default() { @@ -3588,6 +3616,7 @@ void set_value_default() { config.idleinhibit_ignore_visible = 0; config.borderpx = 4; + config.tab_bar_height = 50; config.overviewgappi = 5; config.overviewgappo = 30; config.cursor_hide_timeout = 0; @@ -3675,22 +3704,30 @@ void set_value_default() { config.animation_curve_opafadeout[2] = 0.5; config.animation_curve_opafadeout[3] = 0.5; - config.jumhitdata.fg_color[0] = 0xc4 / 255.0f; - config.jumhitdata.fg_color[1] = 0x93 / 255.0f; - config.jumhitdata.fg_color[2] = 0x9d / 255.0f; - config.jumhitdata.fg_color[3] = 1.0f; - config.jumhitdata.bg_color[0] = 0x32 / 255.0f; - config.jumhitdata.bg_color[1] = 0x32 / 255.0f; - config.jumhitdata.bg_color[2] = 0x32 / 255.0f; - config.jumhitdata.bg_color[3] = 1.0f; - config.jumhitdata.border_color[0] = 0x8b / 255.0f; - config.jumhitdata.border_color[1] = 0xaa / 255.0f; - config.jumhitdata.border_color[2] = 0x9b / 255.0f; - config.jumhitdata.border_color[3] = 1.0f; - config.jumhitdata.border_width = 4; - config.jumhitdata.corner_radius = 5; - config.jumhitdata.padding_x = 10; - config.jumhitdata.padding_y = 10; + config.textdata.fg_color[0] = 0xc4 / 255.0f; + config.textdata.fg_color[1] = 0x93 / 255.0f; + config.textdata.fg_color[2] = 0x9d / 255.0f; + config.textdata.fg_color[3] = 1.0f; + config.textdata.bg_color[0] = 0x32 / 255.0f; + config.textdata.bg_color[1] = 0x32 / 255.0f; + config.textdata.bg_color[2] = 0x32 / 255.0f; + config.textdata.bg_color[3] = 1.0f; + config.textdata.focus_fg_color[0] = 0xed / 255.0f; + config.textdata.focus_fg_color[1] = 0xa6 / 255.0f; + config.textdata.focus_fg_color[2] = 0xb4 / 255.0f; + config.textdata.focus_fg_color[3] = 1.0f; + config.textdata.focus_bg_color[0] = 0x4e / 255.0f; + config.textdata.focus_bg_color[1] = 0x45 / 255.0f; + config.textdata.focus_bg_color[2] = 0x3c / 255.0f; + config.textdata.focus_bg_color[3] = 1.0f; + config.textdata.border_color[0] = 0x8b / 255.0f; + config.textdata.border_color[1] = 0xaa / 255.0f; + config.textdata.border_color[2] = 0x9b / 255.0f; + config.textdata.border_color[3] = 1.0f; + config.textdata.border_width = 4; + config.textdata.corner_radius = 5; + config.textdata.padding_x = 10; + config.textdata.padding_y = 10; config.rootcolor[0] = 0x32 / 255.0f; config.rootcolor[1] = 0x32 / 255.0f; @@ -3804,7 +3841,7 @@ bool parse_config(void) { config.tag_rules = NULL; config.tag_rules_count = 0; config.cursor_theme = NULL; - config.jumhitdata.font_desc = NULL; + config.textdata.font_desc = NULL; config.tablet_map_to_mon = NULL; strcpy(config.keymode, "default"); @@ -3969,6 +4006,10 @@ void reapply_property(void) { c->bw = config.borderpx; } + mango_text_node_apply_config(c->text_node, &config.textdata); + mango_titlebar_node_apply_config(c->titlebar_node, + &config.textdata); + wlr_scene_rect_set_color(c->droparea, config.dropcolor); wlr_scene_rect_set_color(c->splitindicator[0], config.splitcolor); wlr_scene_rect_set_color(c->splitindicator[1], config.splitcolor); diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 1483e296..b6ac2b58 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -136,6 +136,7 @@ int32_t focusdir(const Arg *arg) { Client *c = NULL; c = direction_select(arg); + if (!selmon->isoverview) c = get_focused_stack_client(c, arg->tc); if (c) { diff --git a/src/draw/text-node.c b/src/draw/text-node.c index 40cd2ff0..32164fbe 100644 --- a/src/draw/text-node.c +++ b/src/draw/text-node.c @@ -60,7 +60,7 @@ static const struct wlr_buffer_impl text_buffer_impl = { }; struct mango_text_node *mango_text_node_create(struct wlr_scene_tree *parent, - JumphitData data) { + TextDrawData data) { struct mango_text_node *node = calloc(1, sizeof(*node)); if (!node) return NULL; @@ -73,17 +73,23 @@ struct mango_text_node *mango_text_node_create(struct wlr_scene_tree *parent, memcpy(node->fg_color, data.fg_color, sizeof(node->fg_color)); memcpy(node->bg_color, data.bg_color, sizeof(node->bg_color)); + memcpy(node->focus_fg_color, data.focus_fg_color, + sizeof(node->focus_fg_color)); + memcpy(node->focus_bg_color, data.focus_bg_color, + sizeof(node->focus_bg_color)); memcpy(node->border_color, data.border_color, sizeof(node->border_color)); node->border_width = data.border_width; node->corner_radius = data.corner_radius; node->padding_x = data.padding_x; node->padding_y = data.padding_y; node->font_desc = - g_strdup(data.font_desc ? data.font_desc : "monospace Bold 24"); + g_strdup(data.font_desc ? data.font_desc : "monospace Bold 16"); node->cached_text = NULL; node->cached_scale = -1.0f; node->cached_font_desc = NULL; + node->focused = false; + node->cached_focused = false; node->measure_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); @@ -92,6 +98,8 @@ struct mango_text_node *mango_text_node_create(struct wlr_scene_tree *parent, node->measure_layout = pango_layout_new(node->measure_context); node->measure_scale = 1.0f; + node->scene_buffer->node.data = NULL; + return node; } @@ -190,7 +198,7 @@ void mango_text_node_update(struct mango_text_node *node, const char *text, if (scale <= 0.0f) scale = 1.0f; - /* 脏检查 */ + /* 脏检查,加入 focused 状态 */ if (node->cached_scale == scale && node->cached_font_desc && strcmp(node->cached_font_desc, node->font_desc) == 0 && node->cached_text && strcmp(node->cached_text, text) == 0 && @@ -198,12 +206,17 @@ void mango_text_node_update(struct mango_text_node *node, const char *text, 0 && memcmp(node->cached_bg_color, node->bg_color, sizeof(node->bg_color)) == 0 && + memcmp(node->cached_focus_fg_color, node->focus_fg_color, + sizeof(node->focus_fg_color)) == 0 && + memcmp(node->cached_focus_bg_color, node->focus_bg_color, + sizeof(node->focus_bg_color)) == 0 && memcmp(node->cached_border_color, node->border_color, sizeof(node->border_color)) == 0 && node->cached_border_width == node->border_width && node->cached_corner_radius == node->corner_radius && node->cached_padding_x == node->padding_x && - node->cached_padding_y == node->padding_y) { + node->cached_padding_y == node->padding_y && + node->cached_focused == node->focused) { return; } @@ -215,12 +228,17 @@ void mango_text_node_update(struct mango_text_node *node, const char *text, node->cached_scale = scale; memcpy(node->cached_fg_color, node->fg_color, sizeof(node->fg_color)); memcpy(node->cached_bg_color, node->bg_color, sizeof(node->bg_color)); + memcpy(node->cached_focus_fg_color, node->focus_fg_color, + sizeof(node->focus_fg_color)); + memcpy(node->cached_focus_bg_color, node->focus_bg_color, + sizeof(node->focus_bg_color)); memcpy(node->cached_border_color, node->border_color, sizeof(node->border_color)); node->cached_border_width = node->border_width; node->cached_corner_radius = node->corner_radius; node->cached_padding_x = node->padding_x; node->cached_padding_y = node->padding_y; + node->cached_focused = node->focused; int32_t text_pixel_w, text_pixel_h; get_text_pixel_size(node, text, scale, &text_pixel_w, &text_pixel_h); @@ -241,13 +259,11 @@ void mango_text_node_update(struct mango_text_node *node, const char *text, return; } - /* 逻辑尺寸:文本 + 内边距 + 边框(整数计算) */ int32_t logical_text_w = (int32_t)(text_pixel_w / scale + 0.5f); int32_t logical_text_h = (int32_t)(text_pixel_h / scale + 0.5f); int32_t box_logical_w = logical_text_w + 2 * node->padding_x; int32_t box_logical_h = logical_text_h + 2 * node->padding_y; - /* 加上边框后,乘以 scale 得到物理像素(表面尺寸),向上取整 */ int32_t required_pixel_w = (int32_t)((box_logical_w + 2 * node->border_width) * scale + 0.5f); int32_t required_pixel_h = @@ -279,41 +295,40 @@ void mango_text_node_update(struct mango_text_node *node, const char *text, cairo_t *cr = cairo_create(node->surface); - /* 清空为透明 */ cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.0); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_paint(cr); cairo_set_operator(cr, CAIRO_OPERATOR_OVER); - /* 计算背景矩形(物理像素) */ double border = node->border_width * scale; double bg_x = border; double bg_y = border; double bg_w = box_logical_w * scale; double bg_h = box_logical_h * scale; - /* 圆角半径(物理像素) */ double radius; if (node->corner_radius < 0) { - /* 负数表示自动取半宽/半高作为圆角 */ radius = (bg_w < bg_h ? bg_w : bg_h) / 2.0; } else { radius = node->corner_radius * scale; } - /* 限制最大圆角 */ if (radius > bg_w / 2.0) radius = bg_w / 2.0; if (radius > bg_h / 2.0) radius = bg_h / 2.0; - bool draw_bg = (node->bg_color[3] > 0.0f); + const float *active_bg = + node->focused ? node->focus_bg_color : node->bg_color; + const float *active_fg = + node->focused ? node->focus_fg_color : node->fg_color; + + bool draw_bg = (active_bg[3] > 0.0f); // 使用 active_bg bool draw_border = (node->border_width > 0) && (node->border_color[3] > 0.0f); - /* 绘制背景 */ if (draw_bg) { - cairo_set_source_rgba(cr, node->bg_color[0], node->bg_color[1], - node->bg_color[2], node->bg_color[3]); + cairo_set_source_rgba(cr, active_bg[0], active_bg[1], active_bg[2], + active_bg[3]); if (radius > 0.0) { draw_rounded_rect(cr, bg_x, bg_y, bg_w, bg_h, radius); cairo_fill(cr); @@ -323,7 +338,6 @@ void mango_text_node_update(struct mango_text_node *node, const char *text, } } - /* 绘制文本 */ cairo_save(cr); double text_x = (node->border_width + node->padding_x) * scale; double text_y = (node->border_width + node->padding_y) * scale; @@ -336,15 +350,14 @@ void mango_text_node_update(struct mango_text_node *node, const char *text, pango_layout_set_font_description(layout, desc); pango_layout_set_text(layout, text, -1); - cairo_set_source_rgba(cr, node->fg_color[0], node->fg_color[1], - node->fg_color[2], node->fg_color[3]); + cairo_set_source_rgba(cr, active_fg[0], active_fg[1], active_fg[2], + active_fg[3]); pango_cairo_show_layout(cr, layout); g_object_unref(layout); g_object_unref(ctx); cairo_restore(cr); - /* 绘制边框 */ if (draw_border) { cairo_set_source_rgba(cr, node->border_color[0], node->border_color[1], node->border_color[2], node->border_color[3]); @@ -370,7 +383,6 @@ void mango_text_node_update(struct mango_text_node *node, const char *text, cairo_surface_flush(node->surface); cairo_destroy(cr); - /* 更新 wlr_buffer */ if (node->buffer) { wlr_buffer_drop(&node->buffer->base); node->buffer = NULL; @@ -391,4 +403,437 @@ void mango_text_node_update(struct mango_text_node *node, const char *text, node->logical_height = box_logical_h + 2 * node->border_width; wlr_scene_buffer_set_dest_size(node->scene_buffer, node->logical_width, node->logical_height); +} + +void mango_text_node_set_focus(struct mango_text_node *node, bool focused) { + if (!node || node->focused == focused) + return; + node->focused = focused; + // 使用缓存的文本和缩放触发重绘(如果无文本则不重绘) + if (node->cached_text && node->cached_scale > 0.0f) { + mango_text_node_update(node, node->cached_text, node->cached_scale); + } +} + +struct mango_titlebar_node * +mango_titlebar_node_create(void *mango_node_data, struct wlr_scene_tree *parent, + TextDrawData data, int32_t width, int32_t height) { + struct mango_titlebar_node *node = calloc(1, sizeof(*node)); + if (!node) + return NULL; + + node->scene_buffer = wlr_scene_buffer_create(parent, NULL); + if (!node->scene_buffer) { + free(node); + return NULL; + } + + memcpy(node->fg_color, data.fg_color, sizeof(node->fg_color)); + memcpy(node->bg_color, data.bg_color, sizeof(node->bg_color)); + memcpy(node->focus_fg_color, data.focus_fg_color, + sizeof(node->focus_fg_color)); + memcpy(node->focus_bg_color, data.focus_bg_color, + sizeof(node->focus_bg_color)); + memcpy(node->border_color, data.border_color, sizeof(node->border_color)); + node->border_width = data.border_width; + node->corner_radius = data.corner_radius; + node->padding_x = data.padding_x; + node->padding_y = data.padding_y; + node->font_desc = + g_strdup(data.font_desc ? data.font_desc : "monospace Bold 16"); + + node->target_width = width; + node->target_height = height; + node->focused = false; + node->cached_focused = false; + + node->measure_surface = + cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); + node->measure_cr = cairo_create(node->measure_surface); + node->measure_context = pango_cairo_create_context(node->measure_cr); + node->measure_layout = pango_layout_new(node->measure_context); + node->measure_scale = 1.0f; + + node->cached_scale = -1.0f; + node->last_text = NULL; + node->last_scale = 0.0f; + node->scene_buffer->node.data = mango_node_data; + + return node; +} + +void mango_titlebar_node_destroy(struct mango_titlebar_node *node) { + if (!node) + return; + + if (node->buffer) { + wlr_buffer_drop(&node->buffer->base); + node->buffer = NULL; + } + + if (node->surface) { + cairo_surface_destroy(node->surface); + node->surface = NULL; + } + if (node->measure_surface) { + cairo_surface_destroy(node->measure_surface); + node->measure_surface = NULL; + } + + if (node->measure_layout) + g_object_unref(node->measure_layout); + if (node->measure_context) + g_object_unref(node->measure_context); + if (node->measure_cr) + cairo_destroy(node->measure_cr); + + void *data = node->scene_buffer->node.data; + wlr_scene_node_destroy(&node->scene_buffer->node); + + g_free(node->font_desc); + g_free(node->cached_text); + g_free(node->cached_font_desc); + g_free(node->last_text); + free(data); + free(node); +} + +void mango_titlebar_node_set_size(struct mango_titlebar_node *node, + int32_t width, int32_t height) { + if (!node) + return; + + if (width < 0) + width = 0; + if (height < 0) + height = 0; + + if (node->target_width == width && node->target_height == height) + return; + + node->target_width = width; + node->target_height = height; + + const char *redraw_text = node->last_text ? node->last_text : ""; + float redraw_scale = node->last_scale > 0.0f ? node->last_scale : 1.0f; + + mango_titlebar_node_update(node, redraw_text, redraw_scale); +} + +void mango_titlebar_node_update(struct mango_titlebar_node *node, + const char *text, float scale) { + if (!node || !text) + return; + if (scale <= 0.0f) + scale = 1.0f; + + char *safe_text = g_strdup(text); + + g_free(node->last_text); + node->last_text = safe_text; // 所有权转移 + node->last_scale = scale; + + // 脏检查加入 focused + if (node->cached_scale == scale && node->cached_font_desc && + strcmp(node->cached_font_desc, node->font_desc) == 0 && + node->cached_text && strcmp(node->cached_text, safe_text) == 0 && + memcmp(node->cached_fg_color, node->fg_color, sizeof(node->fg_color)) == + 0 && + memcmp(node->cached_bg_color, node->bg_color, sizeof(node->bg_color)) == + 0 && + memcmp(node->cached_focus_fg_color, node->focus_fg_color, + sizeof(node->focus_fg_color)) == 0 && + memcmp(node->cached_focus_bg_color, node->focus_bg_color, + sizeof(node->focus_bg_color)) == 0 && + memcmp(node->cached_border_color, node->border_color, + sizeof(node->border_color)) == 0 && + node->cached_border_width == node->border_width && + node->cached_corner_radius == node->corner_radius && + node->cached_padding_x == node->padding_x && + node->cached_padding_y == node->padding_y && + node->cached_target_width == node->target_width && + node->cached_target_height == node->target_height && + node->cached_focused == node->focused) { + return; + } + + // 更新缓存 + g_free(node->cached_text); + node->cached_text = g_strdup(safe_text); + + g_free(node->cached_font_desc); + node->cached_font_desc = g_strdup(node->font_desc); + node->cached_scale = scale; + memcpy(node->cached_fg_color, node->fg_color, sizeof(node->fg_color)); + memcpy(node->cached_bg_color, node->bg_color, sizeof(node->bg_color)); + memcpy(node->cached_focus_fg_color, node->focus_fg_color, + sizeof(node->focus_fg_color)); + memcpy(node->cached_focus_bg_color, node->focus_bg_color, + sizeof(node->focus_bg_color)); + memcpy(node->cached_border_color, node->border_color, + sizeof(node->border_color)); + node->cached_border_width = node->border_width; + node->cached_corner_radius = node->corner_radius; + node->cached_padding_x = node->padding_x; + node->cached_padding_y = node->padding_y; + node->cached_target_width = node->target_width; + node->cached_target_height = node->target_height; + node->cached_focused = node->focused; + + if (node->target_width <= 0 || node->target_height <= 0) { + wlr_scene_buffer_set_buffer(node->scene_buffer, NULL); + if (node->buffer) { + wlr_buffer_drop(&node->buffer->base); + node->buffer = NULL; + } + if (node->surface) { + cairo_surface_destroy(node->surface); + node->surface = NULL; + } + node->logical_width = 0; + node->logical_height = 0; + wlr_scene_buffer_set_dest_size(node->scene_buffer, 0, 0); + return; + } + + int32_t box_logical_w = node->target_width - 2 * node->border_width; + int32_t box_logical_h = node->target_height - 2 * node->border_width; + if (box_logical_w < 0) + box_logical_w = 0; + if (box_logical_h < 0) + box_logical_h = 0; + + int32_t required_pixel_w = (int32_t)(node->target_width * scale + 0.5f); + int32_t required_pixel_h = (int32_t)(node->target_height * scale + 0.5f); + if (required_pixel_w < 1) + required_pixel_w = 1; + if (required_pixel_h < 1) + required_pixel_h = 1; + + bool surface_size_changed = (!node->surface) || + (node->surface_pixel_w != required_pixel_w) || + (node->surface_pixel_h != required_pixel_h); + + if (surface_size_changed) { + if (node->buffer) { + wlr_buffer_drop(&node->buffer->base); + node->buffer = NULL; + } + if (node->surface) { + cairo_surface_destroy(node->surface); + node->surface = NULL; + } + node->surface = cairo_image_surface_create( + CAIRO_FORMAT_ARGB32, required_pixel_w, required_pixel_h); + node->surface_pixel_w = required_pixel_w; + node->surface_pixel_h = required_pixel_h; + } + + cairo_t *cr = cairo_create(node->surface); + + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.0); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_paint(cr); + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + + double border_phys = node->border_width * scale; + double bg_x = border_phys; + double bg_y = border_phys; + double bg_w = box_logical_w * scale; + double bg_h = box_logical_h * scale; + + double radius; + if (node->corner_radius < 0) { + radius = (bg_w < bg_h ? bg_w : bg_h) / 2.0; + } else { + radius = node->corner_radius * scale; + } + if (radius > bg_w / 2.0) + radius = bg_w / 2.0; + if (radius > bg_h / 2.0) + radius = bg_h / 2.0; + + const float *active_bg = + node->focused ? node->focus_bg_color : node->bg_color; + const float *active_fg = + node->focused ? node->focus_fg_color : node->fg_color; + + bool draw_bg = (active_bg[3] > 0.0f); + bool draw_border = + (node->border_width > 0) && (node->border_color[3] > 0.0f); + + if (draw_bg) { + cairo_set_source_rgba(cr, active_bg[0], active_bg[1], active_bg[2], + active_bg[3]); + if (radius > 0.0) { + draw_rounded_rect(cr, bg_x, bg_y, bg_w, bg_h, radius); + cairo_fill(cr); + } else { + cairo_rectangle(cr, bg_x, bg_y, bg_w, bg_h); + cairo_fill(cr); + } + } + + int32_t text_area_logical_w = box_logical_w - 2 * node->padding_x; + int32_t text_area_logical_h = box_logical_h - 2 * node->padding_y; + if (text_area_logical_w > 0 && text_area_logical_h > 0) { + cairo_save(cr); + + double text_x = (node->border_width + node->padding_x) * scale; + double text_y = (node->border_width + node->padding_y) * scale; + double text_area_w = text_area_logical_w * scale; + double text_area_h = text_area_logical_h * scale; + + PangoContext *ctx = pango_cairo_create_context(cr); + pango_cairo_context_set_resolution(ctx, 96.0 * scale); + PangoLayout *layout = pango_layout_new(ctx); + PangoFontDescription *desc = get_cached_font_desc(node->font_desc); + pango_layout_set_font_description(layout, desc); + pango_layout_set_text(layout, safe_text, -1); + + pango_layout_set_wrap(layout, PANGO_WRAP_NONE); + pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); + pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER); + pango_layout_set_width(layout, (int)(text_area_w * PANGO_SCALE)); + + int text_pixel_w, text_pixel_h; + pango_layout_get_pixel_size(layout, &text_pixel_w, &text_pixel_h); + double y_offset = (text_area_h - text_pixel_h) / 2.0; + if (y_offset < 0) + y_offset = 0; + + cairo_translate(cr, text_x, text_y + y_offset); + + cairo_set_source_rgba(cr, active_fg[0], active_fg[1], active_fg[2], + active_fg[3]); + pango_cairo_show_layout(cr, layout); + + g_object_unref(layout); + g_object_unref(ctx); + cairo_restore(cr); + } + + if (draw_border) { + cairo_set_source_rgba(cr, node->border_color[0], node->border_color[1], + node->border_color[2], node->border_color[3]); + cairo_set_line_width(cr, border_phys); + + double half_lw = border_phys * 0.5; + double bx = bg_x - half_lw; + double by = bg_y - half_lw; + double bw = bg_w + border_phys; + double bh = bg_h + border_phys; + + if (radius > 0.0) { + double outer_radius = radius + half_lw; + if (outer_radius < 0.0) + outer_radius = 0.0; + draw_rounded_rect(cr, bx, by, bw, bh, outer_radius); + } else { + cairo_rectangle(cr, bx, by, bw, bh); + } + cairo_stroke(cr); + } + + cairo_surface_flush(node->surface); + cairo_destroy(cr); + + if (node->buffer) { + wlr_buffer_drop(&node->buffer->base); + node->buffer = NULL; + } + + struct mango_text_buffer *buf = calloc(1, sizeof(*buf)); + if (!buf) + return; + + wlr_buffer_init(&buf->base, &text_buffer_impl, node->surface_pixel_w, + node->surface_pixel_h); + buf->surface = node->surface; + node->buffer = buf; + + wlr_scene_buffer_set_buffer(node->scene_buffer, &buf->base); + + node->logical_width = node->target_width; + node->logical_height = node->target_height; + wlr_scene_buffer_set_dest_size(node->scene_buffer, node->logical_width, + node->logical_height); +} + +void mango_titlebar_node_set_focus(struct mango_titlebar_node *node, + bool focused) { + if (!node || node->focused == focused) + return; + node->focused = focused; + if (node->last_text) { + float scale = node->last_scale > 0.0f ? node->last_scale : 1.0f; + mango_titlebar_node_update(node, node->last_text, scale); + } +} + +void mango_titlebar_node_set_colors(struct mango_titlebar_node *node, + const float fg[4], const float bg[4]) { + if (!node) + return; + + memcpy(node->fg_color, fg, sizeof(node->fg_color)); + memcpy(node->bg_color, bg, sizeof(node->bg_color)); + + if (!node->focused && node->last_text) { + float scale = node->last_scale > 0.0f ? node->last_scale : 1.0f; + mango_titlebar_node_update(node, node->last_text, scale); + } +} + +void mango_text_node_apply_config(struct mango_text_node *node, + const TextDrawData *data) { + if (!node || !data) + return; + + memcpy(node->fg_color, data->fg_color, sizeof(node->fg_color)); + memcpy(node->bg_color, data->bg_color, sizeof(node->bg_color)); + memcpy(node->focus_fg_color, data->focus_fg_color, + sizeof(node->focus_fg_color)); + memcpy(node->focus_bg_color, data->focus_bg_color, + sizeof(node->focus_bg_color)); + memcpy(node->border_color, data->border_color, sizeof(node->border_color)); + node->border_width = data->border_width; + node->corner_radius = data->corner_radius; + node->padding_x = data->padding_x; + node->padding_y = data->padding_y; + + g_free(node->font_desc); + node->font_desc = + g_strdup(data->font_desc ? data->font_desc : "monospace Bold 16"); + + if (node->cached_text && node->cached_scale > 0.0f) { + mango_text_node_update(node, node->cached_text, node->cached_scale); + } +} + +void mango_titlebar_node_apply_config(struct mango_titlebar_node *node, + const TextDrawData *data) { + if (!node || !data) + return; + + memcpy(node->fg_color, data->fg_color, sizeof(node->fg_color)); + memcpy(node->bg_color, data->bg_color, sizeof(node->bg_color)); + memcpy(node->focus_fg_color, data->focus_fg_color, + sizeof(node->focus_fg_color)); + memcpy(node->focus_bg_color, data->focus_bg_color, + sizeof(node->focus_bg_color)); + memcpy(node->border_color, data->border_color, sizeof(node->border_color)); + node->border_width = data->border_width; + node->corner_radius = data->corner_radius; + node->padding_x = data->padding_x; + node->padding_y = data->padding_y; + + g_free(node->font_desc); + node->font_desc = + g_strdup(data->font_desc ? data->font_desc : "monospace Bold 16"); + + if (node->last_text) { + float scale = node->last_scale > 0.0f ? node->last_scale : 1.0f; + mango_titlebar_node_update(node, node->last_text, scale); + } } \ No newline at end of file diff --git a/src/draw/text-node.h b/src/draw/text-node.h index 8979a774..32b937a1 100644 --- a/src/draw/text-node.h +++ b/src/draw/text-node.h @@ -1,12 +1,27 @@ -#ifndef MANGO_TEXT_NODE_H -#define MANGO_TEXT_NODE_H +#ifndef TEXT_NODE_H +#define TEXT_NODE_H #include <cairo.h> +#include <pango/pango.h> #include <pango/pangocairo.h> -#include <wayland-server-core.h> +#include <stdbool.h> +#include <stdint.h> #include <wlr/types/wlr_scene.h> -// 自定义 wlr_buffer,仅用于包装 cairo surface,不负责 surface 的销毁 +// 原有结构体,假设已存在 +typedef struct { + float fg_color[4]; + float bg_color[4]; + float focus_fg_color[4]; + float focus_bg_color[4]; + float border_color[4]; + int32_t border_width; + int32_t corner_radius; + int32_t padding_x; + int32_t padding_y; + const char *font_desc; +} TextDrawData; + struct mango_text_buffer { struct wlr_buffer base; cairo_surface_t *surface; @@ -14,29 +29,39 @@ struct mango_text_buffer { struct mango_text_node { struct wlr_scene_buffer *scene_buffer; + struct mango_text_buffer *buffer; + cairo_surface_t *surface; + int surface_pixel_w, surface_pixel_h; float fg_color[4]; float bg_color[4]; + float focus_fg_color[4]; + float focus_bg_color[4]; float border_color[4]; int32_t border_width; int32_t corner_radius; - int32_t padding_x, padding_y; + int32_t padding_x; + int32_t padding_y; char *font_desc; + // 缓存 char *cached_text; + char *cached_font_desc; float cached_scale; float cached_fg_color[4]; float cached_bg_color[4]; + float cached_focus_fg_color[4]; + float cached_focus_bg_color[4]; float cached_border_color[4]; - float cached_border_width; - float cached_corner_radius; - float cached_padding_x, cached_padding_y; - char *cached_font_desc; + int32_t cached_border_width; + int32_t cached_corner_radius; + int32_t cached_padding_x; + int32_t cached_padding_y; + bool cached_focused; - cairo_surface_t *surface; - struct mango_text_buffer *buffer; - int32_t surface_pixel_w, surface_pixel_h; + bool focused; + // 测量 cairo_surface_t *measure_surface; cairo_t *measure_cr; PangoContext *measure_context; @@ -47,32 +72,93 @@ struct mango_text_node { int32_t logical_height; }; -typedef struct { +struct mango_titlebar_node { + struct wlr_scene_buffer *scene_buffer; + struct mango_text_buffer *buffer; + cairo_surface_t *surface; + int surface_pixel_w, surface_pixel_h; + + // 初始配置 float fg_color[4]; float bg_color[4]; + float focus_fg_color[4]; + float focus_bg_color[4]; float border_color[4]; int32_t border_width; int32_t corner_radius; int32_t padding_x; int32_t padding_y; - const char *font_desc; -} JumphitData; + char *font_desc; + // 尺寸 + int32_t target_width; + int32_t target_height; + + // 缓存 + char *cached_text; + char *cached_font_desc; + float cached_scale; + float cached_fg_color[4]; + float cached_bg_color[4]; + float cached_focus_fg_color[4]; + float cached_focus_bg_color[4]; + float cached_border_color[4]; + int32_t cached_border_width; + int32_t cached_corner_radius; + int32_t cached_padding_x; + int32_t cached_padding_y; + int32_t cached_target_width; + int32_t cached_target_height; + bool cached_focused; + + bool focused; + + // 上次绘制参数(用于尺寸变化重绘) + char *last_text; + float last_scale; + + // 测量 + cairo_surface_t *measure_surface; + cairo_t *measure_cr; + PangoContext *measure_context; + PangoLayout *measure_layout; + float measure_scale; + + int32_t logical_width; + int32_t logical_height; +}; + +void mango_text_global_finish(void); struct mango_text_node *mango_text_node_create(struct wlr_scene_tree *parent, - JumphitData data); + TextDrawData data); void mango_text_node_destroy(struct mango_text_node *node); -void mango_text_node_update(struct mango_text_node *node, const char *text, - float scale); - void mango_text_node_set_background(struct mango_text_node *node, float r, float g, float b, float a); void mango_text_node_set_border(struct mango_text_node *node, float r, float g, float b, float a, int32_t width, int32_t radius); - void mango_text_node_set_padding(struct mango_text_node *node, int32_t pad_x, int32_t pad_y); +void mango_text_node_update(struct mango_text_node *node, const char *text, + float scale); -void mango_text_global_finish(void); +struct mango_titlebar_node * +mango_titlebar_node_create(void *mango_node_data, struct wlr_scene_tree *parent, + TextDrawData data, int32_t width, int32_t height); +void mango_titlebar_node_destroy(struct mango_titlebar_node *node); +void mango_titlebar_node_set_size(struct mango_titlebar_node *node, + int32_t width, int32_t height); +void mango_titlebar_node_update(struct mango_titlebar_node *node, + const char *text, float scale); -#endif \ No newline at end of file +void mango_text_node_set_focus(struct mango_text_node *node, bool focused); +void mango_titlebar_node_set_focus(struct mango_titlebar_node *node, + bool focused); + +void mango_titlebar_node_set_colors(struct mango_titlebar_node *node, + const float fg[4], const float bg[4]); +void mango_text_node_apply_config(struct mango_text_node *node, + const TextDrawData *data); +void mango_titlebar_node_apply_config(struct mango_titlebar_node *node, + const TextDrawData *data); +#endif // TEXT_NODE_H \ No newline at end of file diff --git a/src/fetch/monitor.h b/src/fetch/monitor.h index 6144ee40..c5a5869a 100644 --- a/src/fetch/monitor.h +++ b/src/fetch/monitor.h @@ -26,6 +26,14 @@ bool is_scroller_layout(Monitor *m) { return false; } +bool is_monocle_layout(Monitor *m) { + + if (m->pertag->ltidxs[m->pertag->curtag]->id == MONOCLE) + return true; + + return false; +} + bool is_centertile_layout(Monitor *m) { if (m->pertag->ltidxs[m->pertag->curtag]->id == CENTER_TILE) diff --git a/src/layout/arrange.h b/src/layout/arrange.h index f1c97f3a..f1c4967a 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -37,6 +37,21 @@ void set_size_per(Monitor *m, Client *c) { } } +void monocle_set_focus(Client *c, bool focused) { + + if (!c || !c->mon) + return; + + c->is_monocle_hide = !focused; + mango_titlebar_node_set_focus(c->titlebar_node, focused); + wlr_scene_node_set_enabled(&c->scene->node, focused); + + if (!focused) { + c->animation.current = c->animainit_geom = c->animation.initial = + c->pending = c->current = c->geom; + } +} + void resize_tile_master_horizontal(Client *grabc, bool isdrag, int32_t offsetx, int32_t offsety, uint32_t time, int32_t type) { @@ -1128,6 +1143,20 @@ void pre_caculate_before_arrange(Monitor *m, bool want_animation, set_size_per(m, c); } + if (m->is_jump_mode && !c->text_node) { + client_add_text_node(c); + } + + if (m->pertag->ltidxs[m->pertag->curtag]->id == MONOCLE && + !c->titlebar_node) { + client_add_titlebar_node(c); + } + + if (c->titlebar_node && c->mon == m) { + wlr_scene_node_set_enabled(&c->titlebar_node->scene_buffer->node, + false); + } + if (c->mon == m && (c->isglobal || c->isunglobal)) { c->tags = m->tagset[m->seltags]; } diff --git a/src/layout/horizontal.h b/src/layout/horizontal.h index 8788c210..4b8fa29d 100644 --- a/src/layout/horizontal.h +++ b/src/layout/horizontal.h @@ -545,32 +545,70 @@ void deck(Monitor *m) { } } -void // 17 -monocle(Monitor *m) { - Client *c = NULL; +void monocle(Monitor *m) { + Client *c, *fc; struct wlr_box geom; - int32_t cur_gappov = enablegaps ? m->gappov : 0; int32_t cur_gappoh = enablegaps ? m->gappoh : 0; + int32_t cur_gapiv = enablegaps ? m->gappiv : 0; + int32_t cur_gapih = enablegaps ? m->gappih : 0; - cur_gappoh = config.smartgaps && m->visible_fake_tiling_clients == 1 - ? 0 - : cur_gappoh; - cur_gappov = config.smartgaps && m->visible_fake_tiling_clients == 1 - ? 0 - : cur_gappov; + if (config.smartgaps && m->visible_fake_tiling_clients == 1) { + cur_gappov = cur_gappoh = cur_gapiv = cur_gapih = 0; + } - wl_list_for_each(c, &clients, link) { - if (!VISIBLEON(c, m) || !ISFAKETILED(c)) + int n = m->visible_fake_tiling_clients; + if (n == 0) + return; + + wl_list_for_each(c, &fstack, flink) { + if (c->iskilling || c->isunglobal || !ISFAKETILED(c)) continue; + if (VISIBLEON(c, m)) { + fc = c; + break; + } + } + + if (n == 1) { geom.x = m->w.x + cur_gappoh; geom.y = m->w.y + cur_gappov; geom.width = m->w.width - 2 * cur_gappoh; geom.height = m->w.height - 2 * cur_gappov; - client_tile_resize(c, geom, 0); + client_tile_resize(fc, geom, 0); + monocle_set_focus(fc, true); + return; + } + + int titlebar_height = config.tab_bar_height; + int title_y = m->w.y + cur_gappov; + int main_y = title_y + titlebar_height + cur_gapiv; + int main_height = + m->w.height - 2 * cur_gappov - 2 * cur_gapiv - titlebar_height; + + int title_area_width = m->w.width - 2 * cur_gappoh; + int tw = (title_area_width - (n - 1) * cur_gapih) / n; + int title_x = m->w.x + cur_gappoh; + + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || !ISFAKETILED(c)) + continue; + + if (c == fc) { + monocle_set_focus(c, true); + } else { + monocle_set_focus(c, false); + } + + geom.x = m->w.x + cur_gappoh; + geom.y = main_y; + geom.width = m->w.width - 2 * cur_gappoh; + geom.height = main_height; + client_tile_resize(c, geom, 0); + + global_draw_titlebar(c, title_x, title_y, tw, titlebar_height); + title_x += tw + cur_gapih; } - if ((c = focustop(m))) - wlr_scene_node_raise_to_top(&c->scene->node); } // 网格布局窗口大小和位置计算 diff --git a/src/mango.c b/src/mango.c index 72226d97..bbdf5779 100644 --- a/src/mango.c +++ b/src/mango.c @@ -169,6 +169,7 @@ enum { LyrBg, LyrBlur, LyrBottom, + LyrDecorate, LyrTile, LyrTop, LyrFadeOut, @@ -177,6 +178,9 @@ enum { LyrBlock, NUM_LAYERS }; /* scene layers */ + +enum mango_node_type { MANGO_TITLE_NODE, MANGO_TEXT_NODE }; + #ifdef XWAYLAND enum { NetWMWindowTypeDialog, @@ -243,6 +247,11 @@ typedef struct { Client *tc; } Arg; +typedef struct { + enum mango_node_type type; + void *node_data; +} MangoNodeData; + typedef struct { uint32_t mod; uint32_t button; @@ -266,8 +275,8 @@ typedef struct { struct wl_list link; struct wlr_input_device *wlr_device; struct libinput_device *libinput_device; - struct wl_listener destroy_listener; // 用于监听设备销毁事件 - void *device_data; // 新增:指向设备特定数据(如 Switch) + struct wl_listener destroy_listener; + void *device_data; } InputDevice; typedef struct { @@ -329,6 +338,7 @@ struct Client { struct wlr_scene_tree *scene_surface; struct wlr_scene_tree *overview_scene_surface; struct mango_text_node *text_node; + struct mango_titlebar_node *titlebar_node; struct wl_list link; struct wl_list flink; struct wl_list fadeout_link; @@ -396,6 +406,7 @@ struct Client { int32_t iskilling; int32_t istagswitching; int32_t isnamedscratchpad; + bool is_monocle_hide; bool is_pending_open_animation; bool is_restoring_from_ov; float scroller_proportion; @@ -841,6 +852,7 @@ static struct wlr_scene_tree * wlr_scene_tree_snapshot(struct wlr_scene_node *node, struct wlr_scene_tree *parent); static bool is_scroller_layout(Monitor *m); +static bool is_monocle_layout(Monitor *m); static bool is_centertile_layout(Monitor *m); static void create_output(struct wlr_backend *backend, void *data); static void get_layout_abbr(char *abbr, const char *full_name); @@ -1262,6 +1274,11 @@ void swallow(Client *c, Client *w) { overview_backup_surface(c); } + if (w->titlebar_node) { + wlr_scene_node_set_enabled(&w->titlebar_node->scene_buffer->node, + false); + } + /* 全局链表替换 */ wl_list_insert(&w->link, &c->link); wl_list_insert(&w->flink, &c->flink); @@ -2371,6 +2388,17 @@ bool handle_buttonpress(struct wlr_pointer_button_event *event) { return true; } + // handle click on tile node + struct wlr_scene_node *node = wlr_scene_node_at( + &layers[LyrDecorate]->node, cursor->x, cursor->y, NULL, NULL); + if (node && node->data) { + MangoNodeData *mangonodedata = (MangoNodeData *)node->data; + if (mangonodedata->type == MANGO_TITLE_NODE) { + Client *c = mangonodedata->node_data; + focusclient(c, 1); + } + } + // 当鼠标焦点在layer上的时候,不检测虚拟键盘的mod状态, // 避免layer虚拟键盘锁死mod按键状态 hard_keyboard = &kb_group->wlr_group->keyboard; @@ -3857,17 +3885,17 @@ void focusclient(Client *c, int32_t lift) { // decide whether need to re-arrange - if (c && selmon->prevsel && - (selmon->prevsel->tags & selmon->tagset[selmon->seltags]) && - (c->tags & selmon->tagset[selmon->seltags]) && !c->isfloating && - is_scroller_layout(selmon)) { - arrange(selmon, false, false); - } - // change focus link position wl_list_remove(&c->flink); wl_list_insert(&fstack, &c->flink); + if (c && selmon->prevsel && + (selmon->prevsel->tags & selmon->tagset[selmon->seltags]) && + (c->tags & selmon->tagset[selmon->seltags]) && !c->isfloating && + (is_scroller_layout(selmon) || is_monocle_layout(selmon))) { + arrange(selmon, false, false); + } + // change border color c->isurgent = 0; } @@ -4394,6 +4422,7 @@ static void iter_xdg_scene_buffers(struct wlr_scene_buffer *buffer, int32_t sx, void init_client_properties(Client *c) { c->grid_col_per = 1.0f; c->grid_row_per = 1.0f; + c->is_monocle_hide = false; c->overview_scene_surface = NULL; c->drop_direction = UNDIR; c->enable_drop_area_draw = false; @@ -4544,10 +4573,6 @@ mapnotify(struct wl_listener *listener, void *data) { #endif // extra node - c->text_node = mango_text_node_create(c->scene, config.jumhitdata); - wlr_scene_node_lower_to_bottom(&c->text_node->scene_buffer->node); - wlr_scene_node_set_enabled(&c->text_node->scene_buffer->node, false); - for (i = 0; i < 2; i++) { c->splitindicator[i] = wlr_scene_rect_create( c->scene, 0, 0, @@ -6546,6 +6571,7 @@ void unmapnotify(struct wl_listener *listener, void *data) { c->stack_proportion = 0.0f; mango_text_node_destroy(c->text_node); + mango_titlebar_node_destroy(c->titlebar_node); wlr_scene_node_destroy(&c->scene->node); printstatus(IPC_WATCH_ARRANGGE); motionnotify(0, NULL, 0, 0, 0, 0); @@ -6698,6 +6724,7 @@ void updatetitle(struct wl_listener *listener, void *data) { const char *title; title = client_get_title(c); + mango_titlebar_node_update(c->titlebar_node, title, 1.0); if (title && c->foreign_toplevel) wlr_foreign_toplevel_handle_v1_set_title(c->foreign_toplevel, title); if (c == focustop(c->mon)) From a5d44d7347b788a3958341333d1d8c1b74285bab Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 18 Jun 2026 10:18:16 +0800 Subject: [PATCH 319/328] opt: tab_bar_decorate and jump_label_decorate config separate --- docs/visuals/theming.md | 40 +++++-- src/action/client.h | 20 ++-- src/animation/client.h | 8 +- src/config/parse_config.h | 234 ++++++++++++++++++++++++++++---------- src/draw/text-node.c | 58 +++++----- src/draw/text-node.h | 52 ++++----- src/layout/arrange.h | 14 +-- src/layout/overview.h | 16 +-- src/mango.c | 16 +-- 9 files changed, 292 insertions(+), 166 deletions(-) diff --git a/docs/visuals/theming.md b/docs/visuals/theming.md index 928b1d24..2f9993be 100644 --- a/docs/visuals/theming.md +++ b/docs/visuals/theming.md @@ -14,7 +14,6 @@ Control the sizing of window borders and gaps. | `gappiv` | `5` | Vertical inner gap. | | `gappoh` | `10` | Horizontal outer gap (between windows and screen edges). | | `gappov` | `10` | Vertical outer gap. | -| `tab_bar_height` | `50` | Height of the tab bar for monocle layout. | ## Colors @@ -56,16 +55,35 @@ You can also color-code windows based on their state: ### Overview Jump Mode | Setting | Default | Description | | :--- | :--- | :--- | -| `text_decorate_fg_color` | `0xc4939dff` | label text color. | -| `text_decorate_bg_color` | `0x201b14ff` | label background color.| -| `text_decorate_focus_fg_color` | `0x201b14ff` | label text color for focus. | -| `text_decorate_focus_bg_color` | `0xc4939dff` | label background color for focus.| -| `text_decorate_border_color` | `0x8BAA9Bff` | label border color.| -| `text_decorate_border_width` | `4` | label border width.| -| `text_decorate_corner_radius` | `5` | label corner radius.| -| `text_decorate_padding_x` | `10` | label horizontal padding.| -| `text_decorate_padding_y` | `10` | label vertical padding.| -| `text_decorate_font_desc` | `monospace Bold 16` | label font set.| +| `jump_label_decorate_fg_color` | `0xc4939dff` | text color. | +| `jump_label_decorate_bg_color` | `0x201b14ff` | background color.| +| `jump_label_decorate_focus_fg_color` | `0x201b14ff` | text color for focus. | +| `jump_label_decorate_focus_bg_color` | `0xc4939dff` | background color for focus.| +| `jump_label_decorate_border_color` | `0x8BAA9Bff` | border color.| +| `jump_label_decorate_border_width` | `4` | border width.| +| `jump_label_decorate_corner_radius` | `5` | corner radius.| +| `jump_label_decorate_padding_x` | `10` | horizontal padding.| +| `jump_label_decorate_padding_y` | `10` | vertical padding.| +| `jump_label_decorate_font_desc` | `monospace Bold 16` | font set.| + +### Tab Bar For Monocle Layout +| Setting | Default | Description | +| :--- | :--- | :--- | +| `tab_bar_height` | `50` | Height of the tab bar for monocle layout. | +| `tab_bar_decorate_fg_color` | `0xc4939dff` | text color. +| `tab_bar_decorate_bg_color` | `0x201b14ff` | background color.| +| `tab_bar_decorate_focus_fg_color` | `0x201b14ff` | text color for focus. | +| `tab_bar_decorate_focus_bg_color` | `0xc4939dff` | background color for focus.| +| `tab_bar_decorate_border_color` | `0x8BAA9Bff` | border color.| +| `tab_bar_decorate_border_width` | `4` | border width.| +| `tab_bar_decorate_corner_radius` | `5` | corner radius.| +| `tab_bar_decorate_padding_x` | `0` | horizontal padding.| +| `tab_bar_decorate_padding_y` | `0` | vertical padding.| +| `tab_bar_decorate_font_desc` | `monospace Bold 16` | font set.| + +## Borders + +Control the appearance of window borders. ## Cursor Theme diff --git a/src/action/client.h b/src/action/client.h index d13cb5c6..80327487 100644 --- a/src/action/client.h +++ b/src/action/client.h @@ -97,20 +97,20 @@ void client_pending_force_kill(Client *c) { kill(c->pid, SIGKILL); } -void client_add_text_node(Client *c) { - c->text_node = mango_text_node_create(c->scene, config.textdata); - wlr_scene_node_lower_to_bottom(&c->text_node->scene_buffer->node); - wlr_scene_node_set_enabled(&c->text_node->scene_buffer->node, false); +void client_add_jump_label_node(Client *c) { + c->jump_label_node = mango_jump_label_node_create(c->scene, config.jumplabeldata); + wlr_scene_node_lower_to_bottom(&c->jump_label_node->scene_buffer->node); + wlr_scene_node_set_enabled(&c->jump_label_node->scene_buffer->node, false); } -void client_add_titlebar_node(Client *c) { +void client_add_tab_bar_node(Client *c) { MangoNodeData *mangonodedata = ecalloc(1, sizeof(MangoNodeData)); mangonodedata->node_data = c; mangonodedata->type = MANGO_TITLE_NODE; - c->titlebar_node = mango_titlebar_node_create( - mangonodedata, layers[LyrDecorate], config.textdata, 0, 0); - wlr_scene_node_lower_to_bottom(&c->titlebar_node->scene_buffer->node); - wlr_scene_node_set_enabled(&c->titlebar_node->scene_buffer->node, false); - mango_titlebar_node_update(c->titlebar_node, client_get_title(c), 1.0); + c->tab_bar_node = mango_tab_bar_node_create( + mangonodedata, layers[LyrDecorate], config.tabdata, 0, 0); + wlr_scene_node_lower_to_bottom(&c->tab_bar_node->scene_buffer->node); + wlr_scene_node_set_enabled(&c->tab_bar_node->scene_buffer->node, false); + mango_tab_bar_node_update(c->tab_bar_node, client_get_title(c), 1.0); } diff --git a/src/animation/client.h b/src/animation/client.h index 22a7dcb1..5e4f3fd7 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -400,12 +400,12 @@ void client_draw_shadow(Client *c) { void global_draw_titlebar(Client *c, int32_t x, int32_t y, int32_t width, int32_t height) { - if (!c->titlebar_node) + if (!c->tab_bar_node) return; - wlr_scene_node_set_position(&c->titlebar_node->scene_buffer->node, x, y); - wlr_scene_node_set_enabled(&c->titlebar_node->scene_buffer->node, true); - mango_titlebar_node_set_size(c->titlebar_node, width, height); + wlr_scene_node_set_position(&c->tab_bar_node->scene_buffer->node, x, y); + wlr_scene_node_set_enabled(&c->tab_bar_node->scene_buffer->node, true); + mango_tab_bar_node_set_size(c->tab_bar_node, width, height); } void apply_split_border(Client *c, bool hit_no_border) { diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 7b7f0daf..df332f7a 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -409,7 +409,8 @@ typedef struct { struct xkb_context *ctx; struct xkb_keymap *keymap; - TextDrawData textdata; + DecorateDrawData jumplabeldata; + DecorateDrawData tabdata; } Config; typedef int32_t (*FuncType)(const Arg *); @@ -1764,76 +1765,146 @@ bool parse_option(Config *config, char *key, char *value) { config->cursor_size = atoi(value); } else if (strcmp(key, "cursor_theme") == 0) { config->cursor_theme = strdup(value); - } else if (strcmp(key, "text_decorate_font_desc") == 0) { - config->textdata.font_desc = strdup(value); - } else if (strcmp(key, "text_decorate_fg_color") == 0) { + } else if (strcmp(key, "tab_bar_decorate_font_desc") == 0) { + config->tabdata.font_desc = strdup(value); + } else if (strcmp(key, "tab_bar_decorate_fg_color") == 0) { int64_t color = parse_color(value); if (color == -1) { fprintf(stderr, "\033[1m\033[31m[ERROR]:\033[33m Invalid " - "text_decorate_fg_color " + "tab_bar_decorate_fg_color " "format: %s\n", value); return false; } else { - convert_hex_to_rgba(config->textdata.fg_color, color); + convert_hex_to_rgba(config->tabdata.fg_color, color); } - } else if (strcmp(key, "text_decorate_bg_color") == 0) { + } else if (strcmp(key, "tab_bar_decorate_bg_color") == 0) { int64_t color = parse_color(value); if (color == -1) { fprintf(stderr, "\033[1m\033[31m[ERROR]:\033[33m Invalid " - "text_decorate_bg_color " + "tab_bar_decorate_bg_color " "format: %s\n", value); return false; } else { - convert_hex_to_rgba(config->textdata.bg_color, color); + convert_hex_to_rgba(config->tabdata.bg_color, color); } - } else if (strcmp(key, "text_decorate_focus_fg_color") == 0) { + } else if (strcmp(key, "tab_bar_decorate_focus_fg_color") == 0) { int64_t color = parse_color(value); if (color == -1) { fprintf(stderr, "\033[1m\033[31m[ERROR]:\033[33m Invalid " - "text_decorate_focus_fg_color " + "tab_bar_decorate_focus_fg_color " "format: %s\n", value); return false; } else { - convert_hex_to_rgba(config->textdata.focus_fg_color, color); + convert_hex_to_rgba(config->tabdata.focus_fg_color, color); } - } else if (strcmp(key, "text_decorate_focus_bg_color") == 0) { + } else if (strcmp(key, "tab_bar_decorate_focus_bg_color") == 0) { int64_t color = parse_color(value); if (color == -1) { fprintf(stderr, "\033[1m\033[31m[ERROR]:\033[33m Invalid " - "text_decorate_focus_bg_color " + "tab_bar_decorate_focus_bg_color " "format: %s\n", value); return false; } else { - convert_hex_to_rgba(config->textdata.focus_bg_color, color); + convert_hex_to_rgba(config->tabdata.focus_bg_color, color); } - } else if (strcmp(key, "text_decorate_border_color") == 0) { + } else if (strcmp(key, "tab_bar_decorate_border_color") == 0) { int64_t color = parse_color(value); if (color == -1) { fprintf(stderr, "\033[1m\033[31m[ERROR]:\033[33m Invalid " - "text_decorate_border_color " + "tab_bar_decorate_border_color " "format: %s\n", value); return false; } else { - convert_hex_to_rgba(config->textdata.border_color, color); + convert_hex_to_rgba(config->tabdata.border_color, color); } - } else if (strcmp(key, "text_decorate_border_width") == 0) { - config->textdata.border_width = CLAMP_INT(atoi(value), 0, 100); - } else if (strcmp(key, "text_decorate_corner_radius") == 0) { - config->textdata.corner_radius = CLAMP_INT(atoi(value), 0, 100); - } else if (strcmp(key, "text_decorate_padding_x") == 0) { - config->textdata.padding_x = CLAMP_INT(atoi(value), 0, 100); - } else if (strcmp(key, "text_decorate_padding_y") == 0) { - config->textdata.padding_y = CLAMP_INT(atoi(value), 0, 100); + } else if (strcmp(key, "tab_bar_decorate_border_width") == 0) { + config->tabdata.border_width = CLAMP_INT(atoi(value), 0, 100); + } else if (strcmp(key, "tab_bar_decorate_corner_radius") == 0) { + config->tabdata.corner_radius = CLAMP_INT(atoi(value), 0, 100); + } else if (strcmp(key, "tab_bar_decorate_padding_x") == 0) { + config->tabdata.padding_x = CLAMP_INT(atoi(value), 0, 100); + } else if (strcmp(key, "tab_bar_decorate_padding_y") == 0) { + config->tabdata.padding_y = CLAMP_INT(atoi(value), 0, 100); + } else if (strcmp(key, "jump_label_decorate_font_desc") == 0) { + config->jumplabeldata.font_desc = strdup(value); + } else if (strcmp(key, "jump_label_decorate_fg_color") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "jump_label_decorate_fg_color " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->jumplabeldata.fg_color, color); + } + } else if (strcmp(key, "jump_label_decorate_bg_color") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "jump_label_decorate_bg_color " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->jumplabeldata.bg_color, color); + } + } else if (strcmp(key, "jump_label_decorate_focus_fg_color") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "jump_label_decorate_focus_fg_color " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->jumplabeldata.focus_fg_color, color); + } + } else if (strcmp(key, "jump_label_decorate_focus_bg_color") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "jump_label_decorate_focus_bg_color " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->jumplabeldata.focus_bg_color, color); + } + } else if (strcmp(key, "jump_label_decorate_border_color") == 0) { + int64_t color = parse_color(value); + if (color == -1) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "jump_label_decorate_border_color " + "format: %s\n", + value); + return false; + } else { + convert_hex_to_rgba(config->jumplabeldata.border_color, color); + } + } else if (strcmp(key, "jump_label_decorate_border_width") == 0) { + config->jumplabeldata.border_width = CLAMP_INT(atoi(value), 0, 100); + } else if (strcmp(key, "jump_label_decorate_corner_radius") == 0) { + config->jumplabeldata.corner_radius = CLAMP_INT(atoi(value), 0, 100); + } else if (strcmp(key, "jump_label_decorate_padding_x") == 0) { + config->jumplabeldata.padding_x = CLAMP_INT(atoi(value), 0, 100); + } else if (strcmp(key, "jump_label_decorate_padding_y") == 0) { + config->jumplabeldata.padding_y = CLAMP_INT(atoi(value), 0, 100); } else if (strcmp(key, "disable_while_typing") == 0) { config->disable_while_typing = atoi(value); } else if (strcmp(key, "left_handed") == 0) { @@ -3325,9 +3396,14 @@ void free_config(void) { config.cursor_theme = NULL; } - if (config.textdata.font_desc) { - free((void *)config.textdata.font_desc); - config.textdata.font_desc = NULL; + if (config.jumplabeldata.font_desc) { + free((void *)config.jumplabeldata.font_desc); + config.jumplabeldata.font_desc = NULL; + } + + if (config.tabdata.font_desc) { + free((void *)config.tabdata.font_desc); + config.tabdata.font_desc = NULL; } if (config.tablet_map_to_mon) { @@ -3522,12 +3598,19 @@ void override_config(void) { config.unfocused_opacity = CLAMP_FLOAT(config.unfocused_opacity, 0.0f, 1.0f); - config.textdata.border_width = - CLAMP_INT(config.textdata.border_width, 0, 100); - config.textdata.corner_radius = - CLAMP_INT(config.textdata.corner_radius, 0, 100); - config.textdata.padding_x = CLAMP_INT(config.textdata.padding_x, 0, 100); - config.textdata.padding_y = CLAMP_INT(config.textdata.padding_y, 0, 100); + config.tabdata.border_width = + CLAMP_INT(config.tabdata.border_width, 0, 100); + config.tabdata.corner_radius = + CLAMP_INT(config.tabdata.corner_radius, 0, 100); + config.tabdata.padding_x = CLAMP_INT(config.tabdata.padding_x, 0, 100); + config.tabdata.padding_y = CLAMP_INT(config.tabdata.padding_y, 0, 100); + + config.jumplabeldata.border_width = + CLAMP_INT(config.jumplabeldata.border_width, 0, 100); + config.jumplabeldata.corner_radius = + CLAMP_INT(config.jumplabeldata.corner_radius, 0, 100); + config.jumplabeldata.padding_x = CLAMP_INT(config.jumplabeldata.padding_x, 0, 100); + config.jumplabeldata.padding_y = CLAMP_INT(config.jumplabeldata.padding_y, 0, 100); } void set_value_default() { @@ -3704,30 +3787,55 @@ void set_value_default() { config.animation_curve_opafadeout[2] = 0.5; config.animation_curve_opafadeout[3] = 0.5; - config.textdata.fg_color[0] = 0xc4 / 255.0f; - config.textdata.fg_color[1] = 0x93 / 255.0f; - config.textdata.fg_color[2] = 0x9d / 255.0f; - config.textdata.fg_color[3] = 1.0f; - config.textdata.bg_color[0] = 0x32 / 255.0f; - config.textdata.bg_color[1] = 0x32 / 255.0f; - config.textdata.bg_color[2] = 0x32 / 255.0f; - config.textdata.bg_color[3] = 1.0f; - config.textdata.focus_fg_color[0] = 0xed / 255.0f; - config.textdata.focus_fg_color[1] = 0xa6 / 255.0f; - config.textdata.focus_fg_color[2] = 0xb4 / 255.0f; - config.textdata.focus_fg_color[3] = 1.0f; - config.textdata.focus_bg_color[0] = 0x4e / 255.0f; - config.textdata.focus_bg_color[1] = 0x45 / 255.0f; - config.textdata.focus_bg_color[2] = 0x3c / 255.0f; - config.textdata.focus_bg_color[3] = 1.0f; - config.textdata.border_color[0] = 0x8b / 255.0f; - config.textdata.border_color[1] = 0xaa / 255.0f; - config.textdata.border_color[2] = 0x9b / 255.0f; - config.textdata.border_color[3] = 1.0f; - config.textdata.border_width = 4; - config.textdata.corner_radius = 5; - config.textdata.padding_x = 10; - config.textdata.padding_y = 10; + config.tabdata.fg_color[0] = 0xc4 / 255.0f; + config.tabdata.fg_color[1] = 0x93 / 255.0f; + config.tabdata.fg_color[2] = 0x9d / 255.0f; + config.tabdata.fg_color[3] = 1.0f; + config.tabdata.bg_color[0] = 0x32 / 255.0f; + config.tabdata.bg_color[1] = 0x32 / 255.0f; + config.tabdata.bg_color[2] = 0x32 / 255.0f; + config.tabdata.bg_color[3] = 1.0f; + config.tabdata.focus_fg_color[0] = 0xed / 255.0f; + config.tabdata.focus_fg_color[1] = 0xa6 / 255.0f; + config.tabdata.focus_fg_color[2] = 0xb4 / 255.0f; + config.tabdata.focus_fg_color[3] = 1.0f; + config.tabdata.focus_bg_color[0] = 0x4e / 255.0f; + config.tabdata.focus_bg_color[1] = 0x45 / 255.0f; + config.tabdata.focus_bg_color[2] = 0x3c / 255.0f; + config.tabdata.focus_bg_color[3] = 1.0f; + config.tabdata.border_color[0] = 0x8b / 255.0f; + config.tabdata.border_color[1] = 0xaa / 255.0f; + config.tabdata.border_color[2] = 0x9b / 255.0f; + config.tabdata.border_color[3] = 1.0f; + config.tabdata.border_width = 4; + config.tabdata.corner_radius = 5; + config.tabdata.padding_x = 0; + config.tabdata.padding_y = 0; + + config.jumplabeldata.fg_color[0] = 0xc4 / 255.0f; + config.jumplabeldata.fg_color[1] = 0x93 / 255.0f; + config.jumplabeldata.fg_color[2] = 0x9d / 255.0f; + config.jumplabeldata.fg_color[3] = 1.0f; + config.jumplabeldata.bg_color[0] = 0x32 / 255.0f; + config.jumplabeldata.bg_color[1] = 0x32 / 255.0f; + config.jumplabeldata.bg_color[2] = 0x32 / 255.0f; + config.jumplabeldata.bg_color[3] = 1.0f; + config.jumplabeldata.focus_fg_color[0] = 0xed / 255.0f; + config.jumplabeldata.focus_fg_color[1] = 0xa6 / 255.0f; + config.jumplabeldata.focus_fg_color[2] = 0xb4 / 255.0f; + config.jumplabeldata.focus_fg_color[3] = 1.0f; + config.jumplabeldata.focus_bg_color[0] = 0x4e / 255.0f; + config.jumplabeldata.focus_bg_color[1] = 0x45 / 255.0f; + config.jumplabeldata.focus_bg_color[2] = 0x3c / 255.0f; + config.jumplabeldata.focus_bg_color[3] = 1.0f; + config.jumplabeldata.border_color[0] = 0x8b / 255.0f; + config.jumplabeldata.border_color[1] = 0xaa / 255.0f; + config.jumplabeldata.border_color[2] = 0x9b / 255.0f; + config.jumplabeldata.border_color[3] = 1.0f; + config.jumplabeldata.border_width = 4; + config.jumplabeldata.corner_radius = 5; + config.jumplabeldata.padding_x = 10; + config.jumplabeldata.padding_y = 10; config.rootcolor[0] = 0x32 / 255.0f; config.rootcolor[1] = 0x32 / 255.0f; @@ -3841,7 +3949,8 @@ bool parse_config(void) { config.tag_rules = NULL; config.tag_rules_count = 0; config.cursor_theme = NULL; - config.textdata.font_desc = NULL; + config.jumplabeldata.font_desc = NULL; + config.tabdata.font_desc = NULL; config.tablet_map_to_mon = NULL; strcpy(config.keymode, "default"); @@ -4006,9 +4115,8 @@ void reapply_property(void) { c->bw = config.borderpx; } - mango_text_node_apply_config(c->text_node, &config.textdata); - mango_titlebar_node_apply_config(c->titlebar_node, - &config.textdata); + mango_jump_label_node_apply_config(c->jump_label_node, &config.jumplabeldata); + mango_tab_bar_node_apply_config(c->tab_bar_node, &config.tabdata); wlr_scene_rect_set_color(c->droparea, config.dropcolor); wlr_scene_rect_set_color(c->splitindicator[0], config.splitcolor); diff --git a/src/draw/text-node.c b/src/draw/text-node.c index 32164fbe..048b7d25 100644 --- a/src/draw/text-node.c +++ b/src/draw/text-node.c @@ -59,9 +59,9 @@ static const struct wlr_buffer_impl text_buffer_impl = { .end_data_ptr_access = text_buffer_end_data_ptr_access, }; -struct mango_text_node *mango_text_node_create(struct wlr_scene_tree *parent, - TextDrawData data) { - struct mango_text_node *node = calloc(1, sizeof(*node)); +struct mango_jump_label_node *mango_jump_label_node_create(struct wlr_scene_tree *parent, + DecorateDrawData data) { + struct mango_jump_label_node *node = calloc(1, sizeof(*node)); if (!node) return NULL; @@ -103,7 +103,7 @@ struct mango_text_node *mango_text_node_create(struct wlr_scene_tree *parent, return node; } -void mango_text_node_destroy(struct mango_text_node *node) { +void mango_jump_label_node_destroy(struct mango_jump_label_node *node) { if (!node) return; @@ -135,7 +135,7 @@ void mango_text_node_destroy(struct mango_text_node *node) { free(node); } -void mango_text_node_set_background(struct mango_text_node *node, float r, +void mango_jump_label_node_set_background(struct mango_jump_label_node *node, float r, float g, float b, float a) { if (!node) return; @@ -145,7 +145,7 @@ void mango_text_node_set_background(struct mango_text_node *node, float r, node->bg_color[3] = a; } -void mango_text_node_set_border(struct mango_text_node *node, float r, float g, +void mango_jump_label_node_set_border(struct mango_jump_label_node *node, float r, float g, float b, float a, int32_t width, int32_t radius) { if (!node) @@ -158,7 +158,7 @@ void mango_text_node_set_border(struct mango_text_node *node, float r, float g, node->corner_radius = radius; } -void mango_text_node_set_padding(struct mango_text_node *node, int32_t pad_x, +void mango_jump_label_node_set_padding(struct mango_jump_label_node *node, int32_t pad_x, int32_t pad_y) { if (!node) return; @@ -166,7 +166,7 @@ void mango_text_node_set_padding(struct mango_text_node *node, int32_t pad_x, node->padding_y = pad_y >= 0 ? pad_y : 0; } -static void get_text_pixel_size(struct mango_text_node *node, const char *text, +static void get_text_pixel_size(struct mango_jump_label_node *node, const char *text, float scale, int32_t *out_w, int32_t *out_h) { if (node->measure_scale != scale) { pango_cairo_context_set_resolution(node->measure_context, 96.0 * scale); @@ -191,7 +191,7 @@ static void draw_rounded_rect(cairo_t *cr, double x, double y, double w, cairo_close_path(cr); } -void mango_text_node_update(struct mango_text_node *node, const char *text, +void mango_jump_label_node_update(struct mango_jump_label_node *node, const char *text, float scale) { if (!node || !text) return; @@ -405,20 +405,20 @@ void mango_text_node_update(struct mango_text_node *node, const char *text, node->logical_height); } -void mango_text_node_set_focus(struct mango_text_node *node, bool focused) { +void mango_jump_label_node_set_focus(struct mango_jump_label_node *node, bool focused) { if (!node || node->focused == focused) return; node->focused = focused; // 使用缓存的文本和缩放触发重绘(如果无文本则不重绘) if (node->cached_text && node->cached_scale > 0.0f) { - mango_text_node_update(node, node->cached_text, node->cached_scale); + mango_jump_label_node_update(node, node->cached_text, node->cached_scale); } } -struct mango_titlebar_node * -mango_titlebar_node_create(void *mango_node_data, struct wlr_scene_tree *parent, - TextDrawData data, int32_t width, int32_t height) { - struct mango_titlebar_node *node = calloc(1, sizeof(*node)); +struct mango_tab_bar_node * +mango_tab_bar_node_create(void *mango_node_data, struct wlr_scene_tree *parent, + DecorateDrawData data, int32_t width, int32_t height) { + struct mango_tab_bar_node *node = calloc(1, sizeof(*node)); if (!node) return NULL; @@ -462,7 +462,7 @@ mango_titlebar_node_create(void *mango_node_data, struct wlr_scene_tree *parent, return node; } -void mango_titlebar_node_destroy(struct mango_titlebar_node *node) { +void mango_tab_bar_node_destroy(struct mango_tab_bar_node *node) { if (!node) return; @@ -498,7 +498,7 @@ void mango_titlebar_node_destroy(struct mango_titlebar_node *node) { free(node); } -void mango_titlebar_node_set_size(struct mango_titlebar_node *node, +void mango_tab_bar_node_set_size(struct mango_tab_bar_node *node, int32_t width, int32_t height) { if (!node) return; @@ -517,10 +517,10 @@ void mango_titlebar_node_set_size(struct mango_titlebar_node *node, const char *redraw_text = node->last_text ? node->last_text : ""; float redraw_scale = node->last_scale > 0.0f ? node->last_scale : 1.0f; - mango_titlebar_node_update(node, redraw_text, redraw_scale); + mango_tab_bar_node_update(node, redraw_text, redraw_scale); } -void mango_titlebar_node_update(struct mango_titlebar_node *node, +void mango_tab_bar_node_update(struct mango_tab_bar_node *node, const char *text, float scale) { if (!node || !text) return; @@ -760,18 +760,18 @@ void mango_titlebar_node_update(struct mango_titlebar_node *node, node->logical_height); } -void mango_titlebar_node_set_focus(struct mango_titlebar_node *node, +void mango_tab_bar_node_set_focus(struct mango_tab_bar_node *node, bool focused) { if (!node || node->focused == focused) return; node->focused = focused; if (node->last_text) { float scale = node->last_scale > 0.0f ? node->last_scale : 1.0f; - mango_titlebar_node_update(node, node->last_text, scale); + mango_tab_bar_node_update(node, node->last_text, scale); } } -void mango_titlebar_node_set_colors(struct mango_titlebar_node *node, +void mango_tab_bar_node_set_colors(struct mango_tab_bar_node *node, const float fg[4], const float bg[4]) { if (!node) return; @@ -781,12 +781,12 @@ void mango_titlebar_node_set_colors(struct mango_titlebar_node *node, if (!node->focused && node->last_text) { float scale = node->last_scale > 0.0f ? node->last_scale : 1.0f; - mango_titlebar_node_update(node, node->last_text, scale); + mango_tab_bar_node_update(node, node->last_text, scale); } } -void mango_text_node_apply_config(struct mango_text_node *node, - const TextDrawData *data) { +void mango_jump_label_node_apply_config(struct mango_jump_label_node *node, + const DecorateDrawData *data) { if (!node || !data) return; @@ -807,12 +807,12 @@ void mango_text_node_apply_config(struct mango_text_node *node, g_strdup(data->font_desc ? data->font_desc : "monospace Bold 16"); if (node->cached_text && node->cached_scale > 0.0f) { - mango_text_node_update(node, node->cached_text, node->cached_scale); + mango_jump_label_node_update(node, node->cached_text, node->cached_scale); } } -void mango_titlebar_node_apply_config(struct mango_titlebar_node *node, - const TextDrawData *data) { +void mango_tab_bar_node_apply_config(struct mango_tab_bar_node *node, + const DecorateDrawData *data) { if (!node || !data) return; @@ -834,6 +834,6 @@ void mango_titlebar_node_apply_config(struct mango_titlebar_node *node, if (node->last_text) { float scale = node->last_scale > 0.0f ? node->last_scale : 1.0f; - mango_titlebar_node_update(node, node->last_text, scale); + mango_tab_bar_node_update(node, node->last_text, scale); } } \ No newline at end of file diff --git a/src/draw/text-node.h b/src/draw/text-node.h index 32b937a1..ae9ac604 100644 --- a/src/draw/text-node.h +++ b/src/draw/text-node.h @@ -1,5 +1,5 @@ -#ifndef TEXT_NODE_H -#define TEXT_NODE_H +#ifndef jump_label_node_H +#define jump_label_node_H #include <cairo.h> #include <pango/pango.h> @@ -20,14 +20,14 @@ typedef struct { int32_t padding_x; int32_t padding_y; const char *font_desc; -} TextDrawData; +} DecorateDrawData; struct mango_text_buffer { struct wlr_buffer base; cairo_surface_t *surface; }; -struct mango_text_node { +struct mango_jump_label_node { struct wlr_scene_buffer *scene_buffer; struct mango_text_buffer *buffer; cairo_surface_t *surface; @@ -72,7 +72,7 @@ struct mango_text_node { int32_t logical_height; }; -struct mango_titlebar_node { +struct mango_tab_bar_node { struct wlr_scene_buffer *scene_buffer; struct mango_text_buffer *buffer; cairo_surface_t *surface; @@ -129,36 +129,36 @@ struct mango_titlebar_node { }; void mango_text_global_finish(void); -struct mango_text_node *mango_text_node_create(struct wlr_scene_tree *parent, - TextDrawData data); -void mango_text_node_destroy(struct mango_text_node *node); -void mango_text_node_set_background(struct mango_text_node *node, float r, +struct mango_jump_label_node *mango_jump_label_node_create(struct wlr_scene_tree *parent, + DecorateDrawData data); +void mango_jump_label_node_destroy(struct mango_jump_label_node *node); +void mango_jump_label_node_set_background(struct mango_jump_label_node *node, float r, float g, float b, float a); -void mango_text_node_set_border(struct mango_text_node *node, float r, float g, +void mango_jump_label_node_set_border(struct mango_jump_label_node *node, float r, float g, float b, float a, int32_t width, int32_t radius); -void mango_text_node_set_padding(struct mango_text_node *node, int32_t pad_x, +void mango_jump_label_node_set_padding(struct mango_jump_label_node *node, int32_t pad_x, int32_t pad_y); -void mango_text_node_update(struct mango_text_node *node, const char *text, +void mango_jump_label_node_update(struct mango_jump_label_node *node, const char *text, float scale); -struct mango_titlebar_node * -mango_titlebar_node_create(void *mango_node_data, struct wlr_scene_tree *parent, - TextDrawData data, int32_t width, int32_t height); -void mango_titlebar_node_destroy(struct mango_titlebar_node *node); -void mango_titlebar_node_set_size(struct mango_titlebar_node *node, +struct mango_tab_bar_node * +mango_tab_bar_node_create(void *mango_node_data, struct wlr_scene_tree *parent, + DecorateDrawData data, int32_t width, int32_t height); +void mango_tab_bar_node_destroy(struct mango_tab_bar_node *node); +void mango_tab_bar_node_set_size(struct mango_tab_bar_node *node, int32_t width, int32_t height); -void mango_titlebar_node_update(struct mango_titlebar_node *node, +void mango_tab_bar_node_update(struct mango_tab_bar_node *node, const char *text, float scale); -void mango_text_node_set_focus(struct mango_text_node *node, bool focused); -void mango_titlebar_node_set_focus(struct mango_titlebar_node *node, +void mango_jump_label_node_set_focus(struct mango_jump_label_node *node, bool focused); +void mango_tab_bar_node_set_focus(struct mango_tab_bar_node *node, bool focused); -void mango_titlebar_node_set_colors(struct mango_titlebar_node *node, +void mango_tab_bar_node_set_colors(struct mango_tab_bar_node *node, const float fg[4], const float bg[4]); -void mango_text_node_apply_config(struct mango_text_node *node, - const TextDrawData *data); -void mango_titlebar_node_apply_config(struct mango_titlebar_node *node, - const TextDrawData *data); -#endif // TEXT_NODE_H \ No newline at end of file +void mango_jump_label_node_apply_config(struct mango_jump_label_node *node, + const DecorateDrawData *data); +void mango_tab_bar_node_apply_config(struct mango_tab_bar_node *node, + const DecorateDrawData *data); +#endif // jump_label_node_H \ No newline at end of file diff --git a/src/layout/arrange.h b/src/layout/arrange.h index f1c4967a..4782b3ea 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -43,7 +43,7 @@ void monocle_set_focus(Client *c, bool focused) { return; c->is_monocle_hide = !focused; - mango_titlebar_node_set_focus(c->titlebar_node, focused); + mango_tab_bar_node_set_focus(c->tab_bar_node, focused); wlr_scene_node_set_enabled(&c->scene->node, focused); if (!focused) { @@ -1143,17 +1143,17 @@ void pre_caculate_before_arrange(Monitor *m, bool want_animation, set_size_per(m, c); } - if (m->is_jump_mode && !c->text_node) { - client_add_text_node(c); + if (m->is_jump_mode && !c->jump_label_node) { + client_add_jump_label_node(c); } if (m->pertag->ltidxs[m->pertag->curtag]->id == MONOCLE && - !c->titlebar_node) { - client_add_titlebar_node(c); + !c->tab_bar_node) { + client_add_tab_bar_node(c); } - if (c->titlebar_node && c->mon == m) { - wlr_scene_node_set_enabled(&c->titlebar_node->scene_buffer->node, + if (c->tab_bar_node && c->mon == m) { + wlr_scene_node_set_enabled(&c->tab_bar_node->scene_buffer->node, false); } diff --git a/src/layout/overview.h b/src/layout/overview.h index 32a9a18d..b259fceb 100644 --- a/src/layout/overview.h +++ b/src/layout/overview.h @@ -368,13 +368,13 @@ void create_jump_hints(Monitor *m) { // 把字符变成字符串 char label_text[2] = {c_char, '\0'}; - mango_text_node_update(c->text_node, label_text, 1.0f); - wlr_scene_node_set_enabled(&c->text_node->scene_buffer->node, true); - wlr_scene_node_raise_to_top(&c->text_node->scene_buffer->node); + mango_jump_label_node_update(c->jump_label_node, label_text, 1.0f); + wlr_scene_node_set_enabled(&c->jump_label_node->scene_buffer->node, true); + wlr_scene_node_raise_to_top(&c->jump_label_node->scene_buffer->node); wlr_scene_node_set_position( - &c->text_node->scene_buffer->node, - c->geom.width / 2 - c->text_node->logical_width / 2, - c->geom.height / 2 - c->text_node->logical_height / 2); + &c->jump_label_node->scene_buffer->node, + c->geom.width / 2 - c->jump_label_node->logical_width / 2, + c->geom.height / 2 - c->jump_label_node->logical_height / 2); label_idx++; } } @@ -391,9 +391,9 @@ void finish_jump_mode(Monitor *m) { wl_list_for_each(c, &clients, link) { if (VISIBLEON(c, m)) { - if (c->text_node->scene_buffer->node.enabled) { + if (c->jump_label_node->scene_buffer->node.enabled) { c->jump_char = '\0'; - wlr_scene_node_set_enabled(&c->text_node->scene_buffer->node, + wlr_scene_node_set_enabled(&c->jump_label_node->scene_buffer->node, false); } } diff --git a/src/mango.c b/src/mango.c index bbdf5779..4b865bb4 100644 --- a/src/mango.c +++ b/src/mango.c @@ -179,7 +179,7 @@ enum { NUM_LAYERS }; /* scene layers */ -enum mango_node_type { MANGO_TITLE_NODE, MANGO_TEXT_NODE }; +enum mango_node_type { MANGO_TITLE_NODE, MANGO_jump_label_node }; #ifdef XWAYLAND enum { @@ -337,8 +337,8 @@ struct Client { struct wlr_scene_shadow *shadow; struct wlr_scene_tree *scene_surface; struct wlr_scene_tree *overview_scene_surface; - struct mango_text_node *text_node; - struct mango_titlebar_node *titlebar_node; + struct mango_jump_label_node *jump_label_node; + struct mango_tab_bar_node *tab_bar_node; struct wl_list link; struct wl_list flink; struct wl_list fadeout_link; @@ -1274,8 +1274,8 @@ void swallow(Client *c, Client *w) { overview_backup_surface(c); } - if (w->titlebar_node) { - wlr_scene_node_set_enabled(&w->titlebar_node->scene_buffer->node, + if (w->tab_bar_node) { + wlr_scene_node_set_enabled(&w->tab_bar_node->scene_buffer->node, false); } @@ -6570,8 +6570,8 @@ void unmapnotify(struct wl_listener *listener, void *data) { c->stack_proportion = 0.0f; - mango_text_node_destroy(c->text_node); - mango_titlebar_node_destroy(c->titlebar_node); + mango_jump_label_node_destroy(c->jump_label_node); + mango_tab_bar_node_destroy(c->tab_bar_node); wlr_scene_node_destroy(&c->scene->node); printstatus(IPC_WATCH_ARRANGGE); motionnotify(0, NULL, 0, 0, 0, 0); @@ -6724,7 +6724,7 @@ void updatetitle(struct wl_listener *listener, void *data) { const char *title; title = client_get_title(c); - mango_titlebar_node_update(c->titlebar_node, title, 1.0); + mango_tab_bar_node_update(c->tab_bar_node, title, 1.0); if (title && c->foreign_toplevel) wlr_foreign_toplevel_handle_v1_set_title(c->foreign_toplevel, title); if (c == focustop(c->mon)) From fbd0863b618004e6d55962034e68e69885c6b40c Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 18 Jun 2026 11:52:16 +0800 Subject: [PATCH 320/328] opt: make sure the node init to null --- src/action/client.h | 3 ++- src/config/parse_config.h | 9 ++++--- src/draw/text-node.c | 54 ++++++++++++++++++++++----------------- src/draw/text-node.h | 42 +++++++++++++++--------------- src/layout/overview.h | 10 +++++--- src/mango.c | 16 +++++++++--- 6 files changed, 78 insertions(+), 56 deletions(-) diff --git a/src/action/client.h b/src/action/client.h index 80327487..adddbbbc 100644 --- a/src/action/client.h +++ b/src/action/client.h @@ -98,7 +98,8 @@ void client_pending_force_kill(Client *c) { } void client_add_jump_label_node(Client *c) { - c->jump_label_node = mango_jump_label_node_create(c->scene, config.jumplabeldata); + c->jump_label_node = + mango_jump_label_node_create(c->scene, config.jumplabeldata); wlr_scene_node_lower_to_bottom(&c->jump_label_node->scene_buffer->node); wlr_scene_node_set_enabled(&c->jump_label_node->scene_buffer->node, false); } diff --git a/src/config/parse_config.h b/src/config/parse_config.h index df332f7a..92794b1e 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -3609,8 +3609,10 @@ void override_config(void) { CLAMP_INT(config.jumplabeldata.border_width, 0, 100); config.jumplabeldata.corner_radius = CLAMP_INT(config.jumplabeldata.corner_radius, 0, 100); - config.jumplabeldata.padding_x = CLAMP_INT(config.jumplabeldata.padding_x, 0, 100); - config.jumplabeldata.padding_y = CLAMP_INT(config.jumplabeldata.padding_y, 0, 100); + config.jumplabeldata.padding_x = + CLAMP_INT(config.jumplabeldata.padding_x, 0, 100); + config.jumplabeldata.padding_y = + CLAMP_INT(config.jumplabeldata.padding_y, 0, 100); } void set_value_default() { @@ -4115,7 +4117,8 @@ void reapply_property(void) { c->bw = config.borderpx; } - mango_jump_label_node_apply_config(c->jump_label_node, &config.jumplabeldata); + mango_jump_label_node_apply_config(c->jump_label_node, + &config.jumplabeldata); mango_tab_bar_node_apply_config(c->tab_bar_node, &config.tabdata); wlr_scene_rect_set_color(c->droparea, config.dropcolor); diff --git a/src/draw/text-node.c b/src/draw/text-node.c index 048b7d25..c85b25dc 100644 --- a/src/draw/text-node.c +++ b/src/draw/text-node.c @@ -59,8 +59,9 @@ static const struct wlr_buffer_impl text_buffer_impl = { .end_data_ptr_access = text_buffer_end_data_ptr_access, }; -struct mango_jump_label_node *mango_jump_label_node_create(struct wlr_scene_tree *parent, - DecorateDrawData data) { +struct mango_jump_label_node * +mango_jump_label_node_create(struct wlr_scene_tree *parent, + DecorateDrawData data) { struct mango_jump_label_node *node = calloc(1, sizeof(*node)); if (!node) return NULL; @@ -135,8 +136,8 @@ void mango_jump_label_node_destroy(struct mango_jump_label_node *node) { free(node); } -void mango_jump_label_node_set_background(struct mango_jump_label_node *node, float r, - float g, float b, float a) { +void mango_jump_label_node_set_background(struct mango_jump_label_node *node, + float r, float g, float b, float a) { if (!node) return; node->bg_color[0] = r; @@ -145,9 +146,9 @@ void mango_jump_label_node_set_background(struct mango_jump_label_node *node, fl node->bg_color[3] = a; } -void mango_jump_label_node_set_border(struct mango_jump_label_node *node, float r, float g, - float b, float a, int32_t width, - int32_t radius) { +void mango_jump_label_node_set_border(struct mango_jump_label_node *node, + float r, float g, float b, float a, + int32_t width, int32_t radius) { if (!node) return; node->border_color[0] = r; @@ -158,16 +159,17 @@ void mango_jump_label_node_set_border(struct mango_jump_label_node *node, float node->corner_radius = radius; } -void mango_jump_label_node_set_padding(struct mango_jump_label_node *node, int32_t pad_x, - int32_t pad_y) { +void mango_jump_label_node_set_padding(struct mango_jump_label_node *node, + int32_t pad_x, int32_t pad_y) { if (!node) return; node->padding_x = pad_x >= 0 ? pad_x : 0; node->padding_y = pad_y >= 0 ? pad_y : 0; } -static void get_text_pixel_size(struct mango_jump_label_node *node, const char *text, - float scale, int32_t *out_w, int32_t *out_h) { +static void get_text_pixel_size(struct mango_jump_label_node *node, + const char *text, float scale, int32_t *out_w, + int32_t *out_h) { if (node->measure_scale != scale) { pango_cairo_context_set_resolution(node->measure_context, 96.0 * scale); node->measure_scale = scale; @@ -191,8 +193,8 @@ static void draw_rounded_rect(cairo_t *cr, double x, double y, double w, cairo_close_path(cr); } -void mango_jump_label_node_update(struct mango_jump_label_node *node, const char *text, - float scale) { +void mango_jump_label_node_update(struct mango_jump_label_node *node, + const char *text, float scale) { if (!node || !text) return; if (scale <= 0.0f) @@ -405,19 +407,22 @@ void mango_jump_label_node_update(struct mango_jump_label_node *node, const char node->logical_height); } -void mango_jump_label_node_set_focus(struct mango_jump_label_node *node, bool focused) { +void mango_jump_label_node_set_focus(struct mango_jump_label_node *node, + bool focused) { if (!node || node->focused == focused) return; node->focused = focused; // 使用缓存的文本和缩放触发重绘(如果无文本则不重绘) if (node->cached_text && node->cached_scale > 0.0f) { - mango_jump_label_node_update(node, node->cached_text, node->cached_scale); + mango_jump_label_node_update(node, node->cached_text, + node->cached_scale); } } struct mango_tab_bar_node * mango_tab_bar_node_create(void *mango_node_data, struct wlr_scene_tree *parent, - DecorateDrawData data, int32_t width, int32_t height) { + DecorateDrawData data, int32_t width, + int32_t height) { struct mango_tab_bar_node *node = calloc(1, sizeof(*node)); if (!node) return NULL; @@ -498,8 +503,8 @@ void mango_tab_bar_node_destroy(struct mango_tab_bar_node *node) { free(node); } -void mango_tab_bar_node_set_size(struct mango_tab_bar_node *node, - int32_t width, int32_t height) { +void mango_tab_bar_node_set_size(struct mango_tab_bar_node *node, int32_t width, + int32_t height) { if (!node) return; @@ -521,7 +526,7 @@ void mango_tab_bar_node_set_size(struct mango_tab_bar_node *node, } void mango_tab_bar_node_update(struct mango_tab_bar_node *node, - const char *text, float scale) { + const char *text, float scale) { if (!node || !text) return; if (scale <= 0.0f) @@ -761,7 +766,7 @@ void mango_tab_bar_node_update(struct mango_tab_bar_node *node, } void mango_tab_bar_node_set_focus(struct mango_tab_bar_node *node, - bool focused) { + bool focused) { if (!node || node->focused == focused) return; node->focused = focused; @@ -772,7 +777,7 @@ void mango_tab_bar_node_set_focus(struct mango_tab_bar_node *node, } void mango_tab_bar_node_set_colors(struct mango_tab_bar_node *node, - const float fg[4], const float bg[4]) { + const float fg[4], const float bg[4]) { if (!node) return; @@ -786,7 +791,7 @@ void mango_tab_bar_node_set_colors(struct mango_tab_bar_node *node, } void mango_jump_label_node_apply_config(struct mango_jump_label_node *node, - const DecorateDrawData *data) { + const DecorateDrawData *data) { if (!node || !data) return; @@ -807,12 +812,13 @@ void mango_jump_label_node_apply_config(struct mango_jump_label_node *node, g_strdup(data->font_desc ? data->font_desc : "monospace Bold 16"); if (node->cached_text && node->cached_scale > 0.0f) { - mango_jump_label_node_update(node, node->cached_text, node->cached_scale); + mango_jump_label_node_update(node, node->cached_text, + node->cached_scale); } } void mango_tab_bar_node_apply_config(struct mango_tab_bar_node *node, - const DecorateDrawData *data) { + const DecorateDrawData *data) { if (!node || !data) return; diff --git a/src/draw/text-node.h b/src/draw/text-node.h index ae9ac604..079af908 100644 --- a/src/draw/text-node.h +++ b/src/draw/text-node.h @@ -129,36 +129,38 @@ struct mango_tab_bar_node { }; void mango_text_global_finish(void); -struct mango_jump_label_node *mango_jump_label_node_create(struct wlr_scene_tree *parent, - DecorateDrawData data); +struct mango_jump_label_node * +mango_jump_label_node_create(struct wlr_scene_tree *parent, + DecorateDrawData data); void mango_jump_label_node_destroy(struct mango_jump_label_node *node); -void mango_jump_label_node_set_background(struct mango_jump_label_node *node, float r, - float g, float b, float a); -void mango_jump_label_node_set_border(struct mango_jump_label_node *node, float r, float g, - float b, float a, int32_t width, - int32_t radius); -void mango_jump_label_node_set_padding(struct mango_jump_label_node *node, int32_t pad_x, - int32_t pad_y); -void mango_jump_label_node_update(struct mango_jump_label_node *node, const char *text, - float scale); +void mango_jump_label_node_set_background(struct mango_jump_label_node *node, + float r, float g, float b, float a); +void mango_jump_label_node_set_border(struct mango_jump_label_node *node, + float r, float g, float b, float a, + int32_t width, int32_t radius); +void mango_jump_label_node_set_padding(struct mango_jump_label_node *node, + int32_t pad_x, int32_t pad_y); +void mango_jump_label_node_update(struct mango_jump_label_node *node, + const char *text, float scale); struct mango_tab_bar_node * mango_tab_bar_node_create(void *mango_node_data, struct wlr_scene_tree *parent, - DecorateDrawData data, int32_t width, int32_t height); + DecorateDrawData data, int32_t width, int32_t height); void mango_tab_bar_node_destroy(struct mango_tab_bar_node *node); -void mango_tab_bar_node_set_size(struct mango_tab_bar_node *node, - int32_t width, int32_t height); +void mango_tab_bar_node_set_size(struct mango_tab_bar_node *node, int32_t width, + int32_t height); void mango_tab_bar_node_update(struct mango_tab_bar_node *node, - const char *text, float scale); + const char *text, float scale); -void mango_jump_label_node_set_focus(struct mango_jump_label_node *node, bool focused); +void mango_jump_label_node_set_focus(struct mango_jump_label_node *node, + bool focused); void mango_tab_bar_node_set_focus(struct mango_tab_bar_node *node, - bool focused); + bool focused); void mango_tab_bar_node_set_colors(struct mango_tab_bar_node *node, - const float fg[4], const float bg[4]); + const float fg[4], const float bg[4]); void mango_jump_label_node_apply_config(struct mango_jump_label_node *node, - const DecorateDrawData *data); + const DecorateDrawData *data); void mango_tab_bar_node_apply_config(struct mango_tab_bar_node *node, - const DecorateDrawData *data); + const DecorateDrawData *data); #endif // jump_label_node_H \ No newline at end of file diff --git a/src/layout/overview.h b/src/layout/overview.h index b259fceb..1ae9e7a0 100644 --- a/src/layout/overview.h +++ b/src/layout/overview.h @@ -369,8 +369,10 @@ void create_jump_hints(Monitor *m) { char label_text[2] = {c_char, '\0'}; mango_jump_label_node_update(c->jump_label_node, label_text, 1.0f); - wlr_scene_node_set_enabled(&c->jump_label_node->scene_buffer->node, true); - wlr_scene_node_raise_to_top(&c->jump_label_node->scene_buffer->node); + wlr_scene_node_set_enabled(&c->jump_label_node->scene_buffer->node, + true); + wlr_scene_node_raise_to_top( + &c->jump_label_node->scene_buffer->node); wlr_scene_node_set_position( &c->jump_label_node->scene_buffer->node, c->geom.width / 2 - c->jump_label_node->logical_width / 2, @@ -393,8 +395,8 @@ void finish_jump_mode(Monitor *m) { if (VISIBLEON(c, m)) { if (c->jump_label_node->scene_buffer->node.enabled) { c->jump_char = '\0'; - wlr_scene_node_set_enabled(&c->jump_label_node->scene_buffer->node, - false); + wlr_scene_node_set_enabled( + &c->jump_label_node->scene_buffer->node, false); } } } diff --git a/src/mango.c b/src/mango.c index 4b865bb4..4b9c5349 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1275,8 +1275,7 @@ void swallow(Client *c, Client *w) { } if (w->tab_bar_node) { - wlr_scene_node_set_enabled(&w->tab_bar_node->scene_buffer->node, - false); + wlr_scene_node_set_enabled(&w->tab_bar_node->scene_buffer->node, false); } /* 全局链表替换 */ @@ -4423,6 +4422,8 @@ void init_client_properties(Client *c) { c->grid_col_per = 1.0f; c->grid_row_per = 1.0f; c->is_monocle_hide = false; + c->jump_label_node = NULL; + c->tab_bar_node = NULL; c->overview_scene_surface = NULL; c->drop_direction = UNDIR; c->enable_drop_area_draw = false; @@ -6570,8 +6571,15 @@ void unmapnotify(struct wl_listener *listener, void *data) { c->stack_proportion = 0.0f; - mango_jump_label_node_destroy(c->jump_label_node); - mango_tab_bar_node_destroy(c->tab_bar_node); + if (c->jump_label_node) { + mango_jump_label_node_destroy(c->jump_label_node); + c->jump_label_node = NULL; + } + if (c->tab_bar_node) { + mango_tab_bar_node_destroy(c->tab_bar_node); + c->tab_bar_node = NULL; + } + wlr_scene_node_destroy(&c->scene->node); printstatus(IPC_WATCH_ARRANGGE); motionnotify(0, NULL, 0, 0, 0, 0); From ed0fb32420d38d3a0e6f208619e064643d82d579 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 18 Jun 2026 13:58:07 +0800 Subject: [PATCH 321/328] opt: focus select exclude monocle hide client --- src/fetch/client.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/fetch/client.h b/src/fetch/client.h index fb50c798..328aa614 100644 --- a/src/fetch/client.h +++ b/src/fetch/client.h @@ -188,6 +188,8 @@ Client *find_client_by_direction(Client *tc, const Arg *arg, continue; if (!findfloating && c->isfloating) continue; + if (c->is_monocle_hide) + continue; if (c->isunglobal) continue; if (!config.focus_cross_monitor && c->mon != tc->mon) @@ -310,7 +312,7 @@ Client *direction_select(const Arg *arg) { Client *focustop(Monitor *m) { Client *c = NULL; wl_list_for_each(c, &fstack, flink) { - if (c->iskilling || c->isunglobal) + if (c->iskilling || c->isunglobal || c->is_monocle_hide) continue; if (VISIBLEON(c, m)) return c; @@ -440,7 +442,7 @@ Client *get_focused_stack_client(Client *sc, Client *custom_focus_client) { return sc; wl_list_for_each(tc, &fstack, flink) { - if (tc->iskilling || tc->isunglobal) + if (tc->iskilling || tc->isunglobal || tc->is_monocle_hide) continue; if (!VISIBLEON(tc, sc->mon)) continue; From 45b752920ef8fa6cda5fa78064c0d4aaf8b734a2 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 18 Jun 2026 14:37:14 +0800 Subject: [PATCH 322/328] opt: Eliminate cumulative monocle layout deviations --- src/layout/horizontal.h | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/layout/horizontal.h b/src/layout/horizontal.h index 4b8fa29d..7adf7f91 100644 --- a/src/layout/horizontal.h +++ b/src/layout/horizontal.h @@ -587,8 +587,13 @@ void monocle(Monitor *m) { m->w.height - 2 * cur_gappov - 2 * cur_gapiv - titlebar_height; int title_area_width = m->w.width - 2 * cur_gappoh; - int tw = (title_area_width - (n - 1) * cur_gapih) / n; + + int total_gaps = (n - 1) * cur_gapih; + int base_width = (title_area_width - total_gaps) / n; + int remainder = (title_area_width - total_gaps) % n; + int title_x = m->w.x + cur_gappoh; + int idx = 0; wl_list_for_each(c, &clients, link) { if (!VISIBLEON(c, m) || !ISFAKETILED(c)) @@ -606,8 +611,11 @@ void monocle(Monitor *m) { geom.height = main_height; client_tile_resize(c, geom, 0); + int tw = base_width + (idx < remainder ? 1 : 0); global_draw_titlebar(c, title_x, title_y, tw, titlebar_height); + title_x += tw + cur_gapih; + idx++; } } From 727b9fe77ebfa5fb58b8f2782f27b3a979a4be37 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 18 Jun 2026 14:54:50 +0800 Subject: [PATCH 323/328] opt: allow focustop get monocle hide client --- src/fetch/client.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fetch/client.h b/src/fetch/client.h index 328aa614..a44f4ca7 100644 --- a/src/fetch/client.h +++ b/src/fetch/client.h @@ -312,7 +312,7 @@ Client *direction_select(const Arg *arg) { Client *focustop(Monitor *m) { Client *c = NULL; wl_list_for_each(c, &fstack, flink) { - if (c->iskilling || c->isunglobal || c->is_monocle_hide) + if (c->iskilling || c->isunglobal) continue; if (VISIBLEON(c, m)) return c; From 550d8d298fd5506abccd9728ee3d383b11b37276 Mon Sep 17 00:00:00 2001 From: SDGDen <sdgden@sdgcloud.nl> Date: Thu, 18 Jun 2026 13:28:09 +0200 Subject: [PATCH 324/328] Updated docs/bindings/keys.md to include the chvt and focusid dispatchers. --- docs/bindings/keys.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/bindings/keys.md b/docs/bindings/keys.md index a7694d74..6c8a8c58 100644 --- a/docs/bindings/keys.md +++ b/docs/bindings/keys.md @@ -106,6 +106,7 @@ bindr=Super,Super_L,spawn,rofi -show run | Command | Param | Description | | :--- | :--- | :--- | +| `focusid` | - | Focus window (can target any window via IPC: `mmsg dispatch focusid client,<id>`) | | `focusdir` | `left/right/up/down` | Focus window in direction. | | `focusstack` | `next/prev` | Cycle focus within the stack. | | `focuslast` | - | Focus the previously active window. | @@ -172,6 +173,8 @@ bindr=Super,Super_L,spawn,rofi -show run | `disable_monitor` | `monitor_spec` | Shutdown monitor. Accepts a [monitor spec](/docs/configuration/monitors#monitor-spec-format). | | `enable_monitor` | `monitor_spec` | Power on monitor. Accepts a [monitor spec](/docs/configuration/monitors#monitor-spec-format). | | `toggle_monitor` | `monitor_spec` | Toggle monitor power. Accepts a [monitor spec](/docs/configuration/monitors#monitor-spec-format). | +| `chvt` | `1-9` | Change virtual terminal (tty, equivalent to using ctrl+alt+Fkeys) | + ### Media Controls From 5583f8901a173b5caab372df4c031913e353bfbc Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 18 Jun 2026 23:35:25 +0800 Subject: [PATCH 325/328] opt: allow tab height to 0 --- src/action/client.h | 5 +++++ src/animation/client.h | 8 ++++++-- src/config/parse_config.h | 2 +- src/layout/horizontal.h | 29 +++++++++++++++++------------ 4 files changed, 29 insertions(+), 15 deletions(-) diff --git a/src/action/client.h b/src/action/client.h index adddbbbc..876284cd 100644 --- a/src/action/client.h +++ b/src/action/client.h @@ -105,6 +105,11 @@ void client_add_jump_label_node(Client *c) { } void client_add_tab_bar_node(Client *c) { + + if (config.tab_bar_height <= 0) { + return; + } + MangoNodeData *mangonodedata = ecalloc(1, sizeof(MangoNodeData)); mangonodedata->node_data = c; mangonodedata->type = MANGO_TITLE_NODE; diff --git a/src/animation/client.h b/src/animation/client.h index 5e4f3fd7..79544cd2 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -398,11 +398,15 @@ void client_draw_shadow(Client *c) { wlr_scene_shadow_set_clipped_region(c->shadow, clipped_region); } -void global_draw_titlebar(Client *c, int32_t x, int32_t y, int32_t width, - int32_t height) { +void global_draw_tab_bar(Client *c, int32_t x, int32_t y, int32_t width, + int32_t height) { if (!c->tab_bar_node) return; + if (height <= 0) { + wlr_scene_node_set_enabled(&c->tab_bar_node->scene_buffer->node, false); + } + wlr_scene_node_set_position(&c->tab_bar_node->scene_buffer->node, x, y); wlr_scene_node_set_enabled(&c->tab_bar_node->scene_buffer->node, true); mango_tab_bar_node_set_size(c->tab_bar_node, width, height); diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 92794b1e..61af421b 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -3569,7 +3569,7 @@ void override_config(void) { config.scratchpad_height_ratio = CLAMP_FLOAT(config.scratchpad_height_ratio, 0.1f, 1.0f); config.borderpx = CLAMP_INT(config.borderpx, 0, 200); - config.tab_bar_height = CLAMP_INT(config.tab_bar_height, 5, 500); + config.tab_bar_height = CLAMP_INT(config.tab_bar_height, 0, 500); config.smartgaps = CLAMP_INT(config.smartgaps, 0, 1); config.blur = CLAMP_INT(config.blur, 0, 1); config.blur_layer = CLAMP_INT(config.blur_layer, 0, 1); diff --git a/src/layout/horizontal.h b/src/layout/horizontal.h index 7adf7f91..96c0ed54 100644 --- a/src/layout/horizontal.h +++ b/src/layout/horizontal.h @@ -546,7 +546,7 @@ void deck(Monitor *m) { } void monocle(Monitor *m) { - Client *c, *fc; + Client *c = NULL, *fc = NULL; struct wlr_box geom; int32_t cur_gappov = enablegaps ? m->gappov : 0; int32_t cur_gappoh = enablegaps ? m->gappoh : 0; @@ -580,19 +580,24 @@ void monocle(Monitor *m) { return; } - int titlebar_height = config.tab_bar_height; - int title_y = m->w.y + cur_gappov; - int main_y = title_y + titlebar_height + cur_gapiv; - int main_height = - m->w.height - 2 * cur_gappov - 2 * cur_gapiv - titlebar_height; + int tab_bar_height = config.tab_bar_height; - int title_area_width = m->w.width - 2 * cur_gappoh; + int tab_bar_inner_gap_height = + config.tab_bar_height > 0 ? 2 * cur_gapiv : 0; + int tab_bar_y_offset = config.tab_bar_height > 0 ? cur_gapiv : 0; + + int tab_y = m->w.y + cur_gappov; + int main_y = tab_y + tab_bar_height + tab_bar_y_offset; + int main_height = m->w.height - 2 * cur_gappov - tab_bar_inner_gap_height - + tab_bar_height; + + int tab_area_width = m->w.width - 2 * cur_gappoh; int total_gaps = (n - 1) * cur_gapih; - int base_width = (title_area_width - total_gaps) / n; - int remainder = (title_area_width - total_gaps) % n; + int base_width = (tab_area_width - total_gaps) / n; + int remainder = (tab_area_width - total_gaps) % n; - int title_x = m->w.x + cur_gappoh; + int tab_x = m->w.x + cur_gappoh; int idx = 0; wl_list_for_each(c, &clients, link) { @@ -612,9 +617,9 @@ void monocle(Monitor *m) { client_tile_resize(c, geom, 0); int tw = base_width + (idx < remainder ? 1 : 0); - global_draw_titlebar(c, title_x, title_y, tw, titlebar_height); + global_draw_tab_bar(c, tab_x, tab_y, tw, tab_bar_height); - title_x += tw + cur_gapih; + tab_x += tw + cur_gapih; idx++; } } From 4c2220384c19988cfc5295a7e63503a692b00f41 Mon Sep 17 00:00:00 2001 From: Cookiez <jaden-puerkenauer@web.de> Date: Thu, 18 Jun 2026 22:23:01 +0200 Subject: [PATCH 326/328] feat: add scroller_proportion field to IPC client response --- src/ipc/ipc.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ipc/ipc.h b/src/ipc/ipc.h index 1694eb8b..5221ca75 100644 --- a/src/ipc/ipc.h +++ b/src/ipc/ipc.h @@ -164,6 +164,7 @@ static cJSON *build_client_json(Client *c) { cJSON_AddNumberToObject(obj, "y", c->geom.y); cJSON_AddNumberToObject(obj, "width", c->geom.width); cJSON_AddNumberToObject(obj, "height", c->geom.height); + cJSON_AddNumberToObject(obj, "scroller_proportion", (double)c->scroller_proportion); return obj; } From da89e2401a239a6f0172350bf4336a00e7b34baa Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 19 Jun 2026 09:56:24 +0800 Subject: [PATCH 327/328] opt: dont override float geom setting when customsize in ono-floating state --- src/mango.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/mango.c b/src/mango.c index 4b9c5349..a77f6c0b 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1728,9 +1728,10 @@ void applyrules(Client *c) { // the hit size if (!c->iscustompos && (!client_is_x11(c) || (c->geom.x == 0 && c->geom.y == 0))) { + struct wlr_box pending_center_geom = c->iscustomsize ? c->float_geom : c->geom; c->float_geom = c->geom = - setclient_coordinate_center(c, mon, c->geom, 0, 0); - } else { + setclient_coordinate_center(c, mon, pending_center_geom, 0, 0); + } else if(!c->iscustomsize) { c->float_geom = c->geom; } From adc12390758caf3599a4921c0cd6374adda8f000 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 19 Jun 2026 11:04:44 +0800 Subject: [PATCH 328/328] opt: ov_tab_mode not conflict ov_jump_mode --- src/dispatch/bind_define.h | 3 ++- src/ipc/ipc.h | 3 ++- src/mango.c | 9 +++++---- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index b6ac2b58..e7cfd020 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1730,7 +1730,8 @@ int32_t toggleoverview(const Arg *arg) { Client *sel = arg->tc ? arg->tc : selmon->sel; - if (selmon->isoverview && config.ov_tab_mode && arg->i != 1 && sel) { + if (selmon->isoverview && config.ov_tab_mode && !selmon->is_jump_mode && + arg->i != 1 && sel) { focusstack(&(Arg){.i = 1}); return 0; } diff --git a/src/ipc/ipc.h b/src/ipc/ipc.h index 5221ca75..9ac1a7b2 100644 --- a/src/ipc/ipc.h +++ b/src/ipc/ipc.h @@ -164,7 +164,8 @@ static cJSON *build_client_json(Client *c) { cJSON_AddNumberToObject(obj, "y", c->geom.y); cJSON_AddNumberToObject(obj, "width", c->geom.width); cJSON_AddNumberToObject(obj, "height", c->geom.height); - cJSON_AddNumberToObject(obj, "scroller_proportion", (double)c->scroller_proportion); + cJSON_AddNumberToObject(obj, "scroller_proportion", + (double)c->scroller_proportion); return obj; } diff --git a/src/mango.c b/src/mango.c index a77f6c0b..26eb0769 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1728,10 +1728,11 @@ void applyrules(Client *c) { // the hit size if (!c->iscustompos && (!client_is_x11(c) || (c->geom.x == 0 && c->geom.y == 0))) { - struct wlr_box pending_center_geom = c->iscustomsize ? c->float_geom : c->geom; + struct wlr_box pending_center_geom = + c->iscustomsize ? c->float_geom : c->geom; c->float_geom = c->geom = setclient_coordinate_center(c, mon, pending_center_geom, 0, 0); - } else if(!c->iscustomsize) { + } else if (!c->iscustomsize) { c->float_geom = c->geom; } @@ -4251,8 +4252,8 @@ void keypress(struct wl_listener *listener, void *data) { wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); // ov tab mode detect moe key release - if (config.ov_tab_mode && !locked && group == kb_group && - event->state == WL_KEYBOARD_KEY_STATE_RELEASED && + if (config.ov_tab_mode && !selmon->is_jump_mode && !locked && + group == kb_group && event->state == WL_KEYBOARD_KEY_STATE_RELEASED && (keycode == 133 || keycode == 37 || keycode == 64 || keycode == 50 || keycode == 134 || keycode == 105 || keycode == 108 || keycode == 62) && selmon && selmon->sel) {