From 58790c0e538910f4c77240ef21f2472354f495c0 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 7 Nov 2025 10:02:51 +0800 Subject: [PATCH 01/42] fix: adjust scenefx blur node feature --- src/animation/client.h | 12 +++++++++++ src/animation/common.h | 8 ++------ src/animation/layer.h | 4 ++++ src/mango.c | 46 +++++++++++++++++++++++++++--------------- 4 files changed, 48 insertions(+), 22 deletions(-) diff --git a/src/animation/client.h b/src/animation/client.h index 90b664b5..e9459d55 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -245,6 +245,13 @@ void buffer_set_effect(Client *c, BufferData data) { data.corner_location = CORNER_LOCATION_NONE; } + if (blur && !c->noblur) { + wlr_scene_blur_set_size(c->blur, c->animation.current.width - 2 * c->bw, + c->animation.current.height - 2 * c->bw); + + wlr_scene_blur_set_corner_radius(c->blur, border_radius, + data.corner_location); + } wlr_scene_node_for_each_buffer(&c->scene_surface->node, scene_buffer_apply_effect, &data); } @@ -535,6 +542,7 @@ void client_apply_clip(Client *c, float factor) { } 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}); @@ -987,6 +995,10 @@ void resize(Client *c, struct wlr_box geo, int interact) { apply_border(c); client_get_clip(c, &clip); wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip); + if (blur && !c->noblur) + wlr_scene_blur_set_size(c->blur, + c->animation.current.width - 2 * c->bw, + c->animation.current.height - 2 * c->bw); return; } // 如果不是工作区切换时划出去的窗口,就让动画的结束位置,就是上面的真实位置和大小 diff --git a/src/animation/common.h b/src/animation/common.h index 06ffe1ab..7079df86 100644 --- a/src/animation/common.h +++ b/src/animation/common.h @@ -158,12 +158,6 @@ static bool scene_node_snapshot(struct wlr_scene_node *node, int lx, int ly, 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 = @@ -198,6 +192,8 @@ static bool scene_node_snapshot(struct wlr_scene_node *node, int lx, int ly, break; } + case WLR_SCENE_NODE_BLUR: + break; case WLR_SCENE_NODE_OPTIMIZED_BLUR: return true; } diff --git a/src/animation/layer.h b/src/animation/layer.h index 48ceb211..80caec56 100644 --- a/src/animation/layer.h +++ b/src/animation/layer.h @@ -356,6 +356,10 @@ void layer_animation_next_tick(LayerSurface *l) { .height = height, }; + if (blur && blur_layer && !l->noblur && l->blur) + wlr_scene_blur_set_size(l->blur, l->animation.current.width, + l->animation.current.height); + if (animation_passed >= 1.0) { l->animation.running = false; l->need_output_flush = false; diff --git a/src/mango.c b/src/mango.c index 36cbbbfa..69208d63 100644 --- a/src/mango.c +++ b/src/mango.c @@ -276,6 +276,7 @@ struct Client { struct wlr_scene_tree *scene; struct wlr_scene_rect *border; /* top, bottom, left, right */ struct wlr_scene_shadow *shadow; + struct wlr_scene_blur *blur; struct wlr_scene_tree *scene_surface; struct wl_list link; struct wl_list flink; @@ -405,6 +406,7 @@ typedef struct { struct wlr_scene_tree *scene; struct wlr_scene_tree *popups; struct wlr_scene_shadow *shadow; + struct wlr_scene_blur *blur; struct wlr_scene_layer_surface_v1 *scene_layer; struct wl_list link; struct wl_list fadeout_link; @@ -2081,12 +2083,16 @@ static void iter_layer_scene_buffers(struct wlr_scene_buffer *buffer, int sx, return; } - wlr_scene_buffer_set_backdrop_blur(buffer, true); - wlr_scene_buffer_set_backdrop_blur_ignore_transparent(buffer, true); + LayerSurface *l = (LayerSurface *)user_data; + + wlr_scene_node_set_enabled(&l->blur->node, true); + wlr_scene_blur_set_transparency_mask_source(l->blur, buffer); + wlr_scene_blur_set_size(l->blur, l->geom.width, l->geom.height); + if (blur_optimized) { - wlr_scene_buffer_set_backdrop_blur_optimized(buffer, true); + wlr_scene_blur_set_should_only_blur_bottom_layer(l->blur, true); } else { - wlr_scene_buffer_set_backdrop_blur_optimized(buffer, false); + wlr_scene_blur_set_should_only_blur_bottom_layer(l->blur, false); } } @@ -2131,13 +2137,17 @@ 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 && + if (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); - wlr_scene_node_lower_to_bottom(&l->shadow->node); - wlr_scene_node_set_enabled(&l->shadow->node, true); + if (layer_surface->current.exclusive_zone == 0) { + l->shadow = wlr_scene_shadow_create(l->scene, 0, 0, border_radius, + shadows_blur, shadowscolor); + wlr_scene_node_lower_to_bottom(&l->shadow->node); + wlr_scene_node_set_enabled(&l->shadow->node, true); + } + + l->blur = wlr_scene_blur_create(l->scene, 0, 0); + wlr_scene_node_lower_to_bottom(&l->blur->node); } // 初始化动画 @@ -2194,7 +2204,8 @@ void commitlayersurfacenotify(struct wl_listener *listener, void *data) { if (!l->noblur && layer_surface->current.layer != ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM && layer_surface->current.layer != - ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND) { + ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND && + l->blur) { wlr_scene_node_for_each_buffer(&l->scene->node, iter_layer_scene_buffers, l); @@ -3521,15 +3532,15 @@ static void iter_xdg_scene_buffers(struct wlr_scene_buffer *buffer, int sx, return; if (blur && c && !c->noblur) { - wlr_scene_buffer_set_backdrop_blur(buffer, true); - wlr_scene_buffer_set_backdrop_blur_ignore_transparent(buffer, false); + wlr_scene_node_set_enabled(&c->blur->node, true); + // wlr_scene_blur_set_transparency_mask_source(c->blur, buffer); if (blur_optimized) { - wlr_scene_buffer_set_backdrop_blur_optimized(buffer, true); + wlr_scene_blur_set_should_only_blur_bottom_layer(c->blur, true); } else { - wlr_scene_buffer_set_backdrop_blur_optimized(buffer, false); + wlr_scene_blur_set_should_only_blur_bottom_layer(c->blur, false); } } else { - wlr_scene_buffer_set_backdrop_blur(buffer, false); + wlr_scene_node_set_enabled(&c->blur->node, false); } } @@ -3642,6 +3653,9 @@ mapnotify(struct wl_listener *listener, void *data) { c->shadow = wlr_scene_shadow_create(c->scene, 0, 0, border_radius, shadows_blur, shadowscolor); + c->blur = wlr_scene_blur_create(c->scene_surface, 0, 0); + wlr_scene_node_lower_to_bottom(&c->blur->node); + wlr_scene_node_lower_to_bottom(&c->shadow->node); wlr_scene_node_set_enabled(&c->shadow->node, true); From 9b97d11c836857d6db691a7341a36998a989acd6 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 7 Nov 2025 21:56:25 +0800 Subject: [PATCH 02/42] fix: crash when click waybar overview button --- src/ext-protocol/ext-workspace.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/ext-protocol/ext-workspace.h b/src/ext-protocol/ext-workspace.h index 61e36da3..930e6c98 100644 --- a/src/ext-protocol/ext-workspace.h +++ b/src/ext-protocol/ext-workspace.h @@ -47,6 +47,11 @@ static void handle_ext_workspace_activate(struct wl_listener *listener, void *data) { struct workspace *workspace = wl_container_of(listener, workspace, activate); + + if (workspace->m->isoverview) { + return; + } + goto_workspace(workspace); wlr_log(WLR_INFO, "ext activating workspace %d", workspace->tag); } @@ -55,6 +60,11 @@ static void handle_ext_workspace_deactivate(struct wl_listener *listener, void *data) { struct workspace *workspace = wl_container_of(listener, workspace, deactivate); + + if (workspace->m->isoverview) { + return; + } + toggle_workspace(workspace); wlr_log(WLR_INFO, "ext deactivating workspace %d", workspace->tag); } From 16dc4fa9cdb5327514d4f2777631a5957323313c Mon Sep 17 00:00:00 2001 From: eater <=@eater.me> Date: Fri, 7 Nov 2025 15:44:47 +0100 Subject: [PATCH 03/42] check is drm_release_manager is set before cleaning up to avoid segfault --- src/mango.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mango.c b/src/mango.c index 69208d63..54ca7116 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1966,7 +1966,9 @@ void cleanuplisteners(void) { wl_list_remove(&request_start_drag.link); wl_list_remove(&start_drag.link); wl_list_remove(&new_session_lock.link); - wl_list_remove(&drm_lease_request.link); + if (drm_lease_manager) { + wl_list_remove(&drm_lease_request.link); + } wl_list_remove(&tearing_new_object.link); #ifdef XWAYLAND wl_list_remove(&new_xwayland_surface.link); From a42939f0c2b64e365494d67c41fd4dc95238ae7b Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 8 Nov 2025 10:35:16 +0800 Subject: [PATCH 04/42] feat: add global option scroller_ignore_proportion_single --- src/config/parse_config.h | 7 +++++++ src/config/preset.h | 1 + src/layout/horizontal.h | 6 +++++- src/layout/vertical.h | 9 ++++++++- 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 19e64b70..30e118e8 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -193,6 +193,7 @@ typedef struct { int scroller_structs; float scroller_default_proportion; float scroller_default_proportion_single; + int scroller_ignore_proportion_single; int scroller_focus_center; int scroller_prefer_center; int edge_scroller_pointer_focus; @@ -1210,6 +1211,8 @@ void parse_option(Config *config, char *key, char *value) { config->scroller_default_proportion = atof(value); } else if (strcmp(key, "scroller_default_proportion_single") == 0) { config->scroller_default_proportion_single = atof(value); + } else if (strcmp(key, "scroller_ignore_proportion_single") == 0) { + config->scroller_ignore_proportion_single = atoi(value); } else if (strcmp(key, "scroller_focus_center") == 0) { config->scroller_focus_center = atoi(value); } else if (strcmp(key, "scroller_prefer_center") == 0) { @@ -2665,6 +2668,8 @@ void override_config(void) { CLAMP_FLOAT(config.scroller_default_proportion, 0.1f, 1.0f); scroller_default_proportion_single = CLAMP_FLOAT(config.scroller_default_proportion_single, 0.1f, 1.0f); + scroller_ignore_proportion_single = + CLAMP_INT(config.scroller_ignore_proportion_single, 0, 1); scroller_focus_center = CLAMP_INT(config.scroller_focus_center, 0, 1); scroller_prefer_center = CLAMP_INT(config.scroller_prefer_center, 0, 1); edge_scroller_pointer_focus = @@ -2852,6 +2857,8 @@ void set_value_default() { config.scroller_default_proportion = scroller_default_proportion; config.scroller_default_proportion_single = scroller_default_proportion_single; + config.scroller_ignore_proportion_single = + scroller_ignore_proportion_single; config.scroller_focus_center = scroller_focus_center; config.scroller_prefer_center = scroller_prefer_center; config.edge_scroller_pointer_focus = edge_scroller_pointer_focus; diff --git a/src/config/preset.h b/src/config/preset.h index 2f994ec1..be1c1b0a 100644 --- a/src/config/preset.h +++ b/src/config/preset.h @@ -60,6 +60,7 @@ float scratchpad_height_ratio = 0.9; int scroller_structs = 20; float scroller_default_proportion = 0.9; float scroller_default_proportion_single = 1.0; +int scroller_ignore_proportion_single = 0; int scroller_focus_center = 0; int scroller_prefer_center = 0; int focus_cross_monitor = 0; diff --git a/src/layout/horizontal.h b/src/layout/horizontal.h index b2d76bb3..178ae9e0 100644 --- a/src/layout/horizontal.h +++ b/src/layout/horizontal.h @@ -231,7 +231,7 @@ void scroller(Monitor *m) { } } - if (n == 1) { + if (n == 1 && !scroller_ignore_proportion_single) { c = tempClients[0]; target_geom.height = m->w.height - 2 * cur_gappov; target_geom.width = @@ -274,6 +274,10 @@ void scroller(Monitor *m) { } } + if (n == 1 && scroller_ignore_proportion_single) { + need_scroller = true; + } + if (start_drag_window) need_scroller = false; diff --git a/src/layout/vertical.h b/src/layout/vertical.h index 3c744639..6ae21a6c 100644 --- a/src/layout/vertical.h +++ b/src/layout/vertical.h @@ -192,7 +192,7 @@ void vertical_scroller(Monitor *m) { } } - if (n == 1) { + if (n == 1 && !scroller_ignore_proportion_single) { c = tempClients[0]; target_geom.width = m->w.width - 2 * cur_gappoh; target_geom.height = @@ -235,6 +235,13 @@ void vertical_scroller(Monitor *m) { } } + if (n == 1 && scroller_ignore_proportion_single) { + need_scroller = true; + } + + if (start_drag_window) + need_scroller = false; + target_geom.width = m->w.width - 2 * cur_gappoh; target_geom.height = max_client_height * c->scroller_proportion; target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2; From c51cf7d6cce3fe5767d2681a03f9002e5db3d2cc Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 8 Nov 2025 12:39:41 +0800 Subject: [PATCH 05/42] opt:optimize code struct --- src/mango.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mango.c b/src/mango.c index 54ca7116..e1e7703e 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1966,10 +1966,10 @@ void cleanuplisteners(void) { wl_list_remove(&request_start_drag.link); wl_list_remove(&start_drag.link); wl_list_remove(&new_session_lock.link); + wl_list_remove(&tearing_new_object.link); if (drm_lease_manager) { wl_list_remove(&drm_lease_request.link); } - wl_list_remove(&tearing_new_object.link); #ifdef XWAYLAND wl_list_remove(&new_xwayland_surface.link); wl_list_remove(&xwayland_ready.link); From 9fecdb6c3ab0954190d57f34a035c2069a4bf99f Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 8 Nov 2025 19:59:05 +0800 Subject: [PATCH 06/42] fix: blur node size and position are wrong --- src/animation/client.h | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/animation/client.h b/src/animation/client.h index e9459d55..3792358d 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -246,9 +246,6 @@ void buffer_set_effect(Client *c, BufferData data) { } if (blur && !c->noblur) { - wlr_scene_blur_set_size(c->blur, c->animation.current.width - 2 * c->bw, - c->animation.current.height - 2 * c->bw); - wlr_scene_blur_set_corner_radius(c->blur, border_radius, data.corner_location); } @@ -352,6 +349,14 @@ void client_draw_shadow(Client *c) { wlr_scene_shadow_set_clipped_region(c->shadow, clipped_region); } +void client_draw_blur(Client *c, struct wlr_box clip_box, struct ivec2 offset) { + if (blur && !c->noblur) { + wlr_scene_node_set_position(&c->blur->node, offset.x, offset.y); + wlr_scene_blur_set_size(c->blur, clip_box.width - c->bw, + clip_box.height - c->bw); + } +} + void apply_border(Client *c) { if (!c || c->iskilling || !client_surface(c)->mapped) return; @@ -536,6 +541,7 @@ void client_apply_clip(Client *c, float factor) { apply_border(c); client_draw_shadow(c); + client_draw_blur(c, clip_box, offset); if (clip_box.width <= 0 || clip_box.height <= 0) { return; @@ -574,6 +580,7 @@ void client_apply_clip(Client *c, float factor) { // 应用窗口装饰 apply_border(c); client_draw_shadow(c); + client_draw_blur(c, clip_box, offset); // 如果窗口剪切区域已经剪切到0,则不渲染窗口表面 if (clip_box.width <= 0 || clip_box.height <= 0) { From e59b9c10b5c3d7a09c3c01270442bb9107edc0b9 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 8 Nov 2025 20:10:11 +0800 Subject: [PATCH 07/42] opt: optimize shadow node and blur node enable --- src/animation/client.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/animation/client.h b/src/animation/client.h index 3792358d..70da39e9 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -259,8 +259,12 @@ void client_draw_shadow(Client *c) { return; if (!shadows || (!c->isfloating && shadow_only_floating)) { - wlr_scene_shadow_set_size(c->shadow, 0, 0); + if (c->shadow->node.enabled) + wlr_scene_node_set_enabled(&c->shadow->node, false); return; + } else { + if (c->scene_surface->node.enabled && !c->shadow->node.enabled) + wlr_scene_node_set_enabled(&c->shadow->node, true); } bool hit_no_border = check_hit_no_border(c); @@ -351,9 +355,14 @@ void client_draw_shadow(Client *c) { void client_draw_blur(Client *c, struct wlr_box clip_box, struct ivec2 offset) { if (blur && !c->noblur) { + if (c->scene_surface->node.enabled && !c->blur->node.enabled) + wlr_scene_node_set_enabled(&c->blur->node, true); wlr_scene_node_set_position(&c->blur->node, offset.x, offset.y); wlr_scene_blur_set_size(c->blur, clip_box.width - c->bw, clip_box.height - c->bw); + } else { + if (c->blur->node.enabled) + wlr_scene_node_set_enabled(&c->blur->node, false); } } From 8ac331aa64e07867258a3bfdb7724e9a6127f812 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 8 Nov 2025 20:17:16 +0800 Subject: [PATCH 08/42] opt: disable resize scroller window when it force to default single size --- src/layout/arrange.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/layout/arrange.h b/src/layout/arrange.h index 4df97865..b53592f3 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -374,6 +374,10 @@ void resize_tile_scroller(Client *grabc, bool isdrag, int offsetx, int offsety, float delta_x, delta_y; float new_scroller_proportion; + if (grabc && grabc->mon->visible_tiling_clients == 1 && + !scroller_ignore_proportion_single) + return; + if (!start_drag_window && isdrag) { drag_begin_cursorx = cursor->x; drag_begin_cursory = cursor->y; From ee789613ff06f39f2f46b4d4eec5a8a3c45c390e Mon Sep 17 00:00:00 2001 From: Yappaholic Date: Sat, 8 Nov 2025 16:43:20 +0300 Subject: [PATCH 09/42] nix: bump scenefx flake and fix build --- flake.lock | 46 ++++++++++++++++++++++++++++++++++++++++++---- flake.nix | 5 +++-- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index 2917a6fd..6ed92805 100644 --- a/flake.lock +++ b/flake.lock @@ -18,6 +18,27 @@ "type": "github" } }, + "flake-utils": { + "inputs": { + "systems": [ + "scenefx", + "systems" + ] + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, "nixpkgs": { "locked": { "lastModified": 1750386251, @@ -58,16 +79,18 @@ }, "scenefx": { "inputs": { + "flake-utils": "flake-utils", "nixpkgs": [ "nixpkgs" - ] + ], + "systems": "systems" }, "locked": { - "lastModified": 1750785057, - "narHash": "sha256-tGX6j4W91rcb+glXJo43sjPI9zQvPotonknG1BdihR4=", + "lastModified": 1762447505, + "narHash": "sha256-VEBQ8KXkSS4c+kdAhmvq06lEd9WNeCXdRK1U+qSilFw=", "owner": "wlrfx", "repo": "scenefx", - "rev": "3a6cfb12e4ba97b43326357d14f7b3e40897adfc", + "rev": "7f9e7409f6169fa637f1265895c121a8f8b70272", "type": "github" }, "original": { @@ -75,6 +98,21 @@ "repo": "scenefx", "type": "github" } + }, + "systems": { + "locked": { + "lastModified": 1689347949, + "narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=", + "owner": "nix-systems", + "repo": "default-linux", + "rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default-linux", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index b7158bbd..0207cd49 100644 --- a/flake.nix +++ b/flake.nix @@ -25,12 +25,13 @@ perSystem = { config, + system, pkgs, ... }: let - inherit (pkgs) callPackage ; + inherit (pkgs) callPackage; mango = callPackage ./nix { - inherit (inputs.scenefx.packages.${pkgs.stdenv.hostPlatform.system}) scenefx; + scenefx = inputs.scenefx.packages.${system}.default; }; shellOverride = old: { nativeBuildInputs = old.nativeBuildInputs ++ []; From 276560840b43fc4ad790eebdbfadefb3288e6375 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 8 Nov 2025 22:47:18 +0800 Subject: [PATCH 10/42] opt: remove increase_proportion dispatch should use resizewin to replace it --- src/config/parse_config.h | 3 --- src/dispatch/bind_declare.h | 1 - src/dispatch/bind_define.h | 12 ------------ 3 files changed, 16 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 30e118e8..321de209 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -887,9 +887,6 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value, } else if (strcmp(func_name, "set_proportion") == 0) { func = set_proportion; (*arg).f = atof(arg_value); - } else if (strcmp(func_name, "increase_proportion") == 0) { - func = increase_proportion; - (*arg).f = atof(arg_value); } else if (strcmp(func_name, "switch_proportion_preset") == 0) { func = switch_proportion_preset; } else if (strcmp(func_name, "viewtoleft") == 0) { diff --git a/src/dispatch/bind_declare.h b/src/dispatch/bind_declare.h index b38e2da5..5bc215a2 100644 --- a/src/dispatch/bind_declare.h +++ b/src/dispatch/bind_declare.h @@ -4,7 +4,6 @@ int toggle_scratchpad(const Arg *arg); int focusdir(const Arg *arg); int toggleoverview(const Arg *arg); int set_proportion(const Arg *arg); -int increase_proportion(const Arg *arg); int switch_proportion_preset(const Arg *arg); int zoom(const Arg *arg); int tagsilent(const Arg *arg); diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 57288b4c..464e2a6c 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -281,18 +281,6 @@ int incovgaps(const Arg *arg) { return 0; } -int increase_proportion(const Arg *arg) { - if (selmon->sel) { - unsigned int max_client_width = - selmon->w.width - 2 * scroller_structs - gappih; - selmon->sel->scroller_proportion = - MIN(MAX(arg->f + selmon->sel->scroller_proportion, 0.1), 1.0); - selmon->sel->geom.width = max_client_width * arg->f; - arrange(selmon, false); - } - return 0; -} - int setmfact(const Arg *arg) { float f; Client *c = NULL; From 3cfcaa21c0645b11ac94a83bfb95516d0f837bcd Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 8 Nov 2025 22:48:53 +0800 Subject: [PATCH 11/42] opt: not resizewin in overview --- src/layout/arrange.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/layout/arrange.h b/src/layout/arrange.h index b53592f3..aafb79e0 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -479,6 +479,9 @@ void resize_tile_client(Client *grabc, bool isdrag, int offsetx, int offsety, if (!grabc || grabc->isfullscreen || grabc->ismaximizescreen) return; + if (grabc->mon->isoverview) + return; + const Layout *current_layout = grabc->mon->pertag->ltidxs[grabc->mon->pertag->curtag]; if (current_layout->id == TILE || current_layout->id == DECK || From 11dfab478a2b1d1e149751ac6a76a30c6eb5ed43 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 8 Nov 2025 23:26:52 +0800 Subject: [PATCH 12/42] opt: disable switch proportion action in some case --- src/dispatch/bind_define.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 464e2a6c..65c2b03e 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -518,6 +518,14 @@ int setkeymode(const Arg *arg) { } int set_proportion(const Arg *arg) { + + if (selmon->isoverview || !is_scroller_layout(selmon)) + return 0; + + if (selmon->visible_tiling_clients == 1 && + !scroller_ignore_proportion_single) + return 0; + if (selmon->sel) { unsigned int max_client_width = selmon->w.width - 2 * scroller_structs - gappih; @@ -919,6 +927,13 @@ int switch_proportion_preset(const Arg *arg) { return 0; } + if (selmon->isoverview || !is_scroller_layout(selmon)) + return 0; + + if (selmon->visible_tiling_clients == 1 && + !scroller_ignore_proportion_single) + return 0; + if (selmon->sel) { for (int i = 0; i < config.scroller_proportion_preset_count; i++) { From c20669325797b3141e4883bcf65caca6a680f1ef Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 9 Nov 2025 15:40:51 +0800 Subject: [PATCH 13/42] feat: support nofucs rule for some special window --- src/config/parse_config.h | 4 ++++ src/mango.c | 42 +++++++++++++++++++++++---------------- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 321de209..0e9e15c5 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -77,6 +77,7 @@ typedef struct { int offsety; int width; int height; + int nofocus; int nofadein; int nofadeout; int no_force_center; @@ -1716,6 +1717,7 @@ void parse_option(Config *config, char *key, char *value) { rule->force_tearing = -1; rule->noswallow = -1; rule->noblur = -1; + rule->nofocus = -1; rule->nofadein = -1; rule->nofadeout = -1; rule->no_force_center = -1; @@ -1770,6 +1772,8 @@ void parse_option(Config *config, char *key, char *value) { rule->offsetx = atoi(val); } else if (strcmp(key, "offsety") == 0) { rule->offsety = atoi(val); + } else if (strcmp(key, "nofocus") == 0) { + rule->nofocus = atoi(val); } else if (strcmp(key, "nofadein") == 0) { rule->nofadein = atoi(val); } else if (strcmp(key, "nofadeout") == 0) { diff --git a/src/mango.c b/src/mango.c index e1e7703e..74345bd5 100644 --- a/src/mango.c +++ b/src/mango.c @@ -353,6 +353,7 @@ struct Client { bool drag_to_tile; bool scratchpad_switching_mon; bool fake_no_border; + int nofocus; int nofadein; int nofadeout; int no_force_center; @@ -1132,6 +1133,7 @@ static void apply_rule_properties(Client *c, const ConfigWinRule *r) { APPLY_INT_PROP(c, r, force_maximize); APPLY_INT_PROP(c, r, force_tearing); APPLY_INT_PROP(c, r, noswallow); + APPLY_INT_PROP(c, r, nofocus); APPLY_INT_PROP(c, r, nofadein); APPLY_INT_PROP(c, r, nofadeout); APPLY_INT_PROP(c, r, no_force_center); @@ -1245,11 +1247,16 @@ void applyrules(Client *c) { const char *appid, *title; unsigned int i, newtags = 0; const ConfigWinRule *r; - Monitor *mon = selmon, *m = NULL; + Monitor *m = NULL; Client *fc = NULL; bool hit_rule_pos = false; + Client *parent = NULL; - c->isfloating = client_is_float_type(c); + parent = client_get_parent(c); + + Monitor *mon = parent && parent->mon ? parent->mon : selmon; + + c->isfloating = client_is_float_type(c) || parent; if (!(appid = client_get_appid(c))) appid = broken; if (!(title = client_get_title(c))) @@ -1266,8 +1273,14 @@ void applyrules(Client *c) { // set general properties apply_rule_properties(c, r); - // set tags - newtags |= (r->tags > 0) ? r->tags : 0; + // // set tags + if (r->tags > 0) { + newtags |= r->tags; + } else if (parent) { + newtags = parent->tags; + } else { + newtags |= 0; + } // set monitor of client wl_list_for_each(m, &mons, link) { @@ -1336,8 +1349,9 @@ void applyrules(Client *c) { int fullscreen_state_backup = c->isfullscreen || client_wants_fullscreen(c); setmon(c, mon, newtags, - !c->isopensilent && (!c->istagsilent || !newtags || - newtags & mon->tagset[mon->seltags])); + !c->isopensilent && !client_should_ignore_focus(c) && + (!c->istagsilent || !newtags || + newtags & mon->tagset[mon->seltags])); if (c->mon && !(c->mon == selmon && c->tags & c->mon->tagset[c->mon->seltags]) && @@ -3095,6 +3109,9 @@ void focusclient(Client *c, int lift) { if (c && client_should_ignore_focus(c) && client_is_x11_popup(c)) return; + if (c && c->nofocus) + return; + /* Raise client in stacking order if requested */ if (c && lift) wlr_scene_node_raise_to_top(&c->scene->node); // 将视图提升到顶层 @@ -3571,6 +3588,7 @@ void init_client_properties(Client *c) { c->fake_no_border = false; c->focused_opacity = focused_opacity; c->unfocused_opacity = unfocused_opacity; + c->nofocus = 0; c->nofadein = 0; c->nofadeout = 0; c->no_force_center = 0; @@ -3591,7 +3609,6 @@ void init_client_properties(Client *c) { void // old fix to 0.5 mapnotify(struct wl_listener *listener, void *data) { /* Called when the surface is mapped, or ready to display on-screen. */ - Client *p = NULL; Client *at_client = NULL; Client *c = wl_container_of(listener, c, map); /* Create scene tree for this client and its border */ @@ -3686,16 +3703,7 @@ mapnotify(struct wl_listener *listener, void *data) { wl_list_insert(clients.prev, &c->link); // 尾部入栈 wl_list_insert(&fstack, &c->flink); - /* Set initial monitor, tags, floating status, and focus: - * we always consider floating, clients that have parent and thus - * we set the same tags and monitor than its parent, if not - * try to apply rules for them */ - if ((p = client_get_parent(c))) { - c->isfloating = 1; - setmon(c, p->mon, p->tags, true); - } else { - applyrules(c); - } + applyrules(c); client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | WLR_EDGE_RIGHT); From d0a51f47856b62ed1d135eaa2a1824377efc85a4 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 9 Nov 2025 23:19:49 +0800 Subject: [PATCH 14/42] opt: remove useless code --- src/mango.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/mango.c b/src/mango.c index 74345bd5..960b5cb5 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1278,8 +1278,6 @@ void applyrules(Client *c) { newtags |= r->tags; } else if (parent) { newtags = parent->tags; - } else { - newtags |= 0; } // set monitor of client From 64d8764f58d6469ae641bde9079938702022f309 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 10 Nov 2025 14:30:20 +0800 Subject: [PATCH 15/42] update readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c90cee1f..8e982b60 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ emerge --ask --verbose gui-wm/mangowc ## Other ```bash -git clone -b 0.19.1 https://gitlab.freedesktop.org/wlroots/wlroots.git +git clone -b 0.19.2 https://gitlab.freedesktop.org/wlroots/wlroots.git cd wlroots meson build -Dprefix=/usr sudo ninja -C build install @@ -119,7 +119,7 @@ sudo ninja -C build install ## Suggested Tools -### integrated component +### Hybrid component - [dms-shell](https://github.com/AvengeMedia/DankMaterialShell) ### Independent component From 77512f112e23110571e05e20d67deba8e1345385 Mon Sep 17 00:00:00 2001 From: Jorrit van der Heide Date: Tue, 11 Nov 2025 17:03:43 +0100 Subject: [PATCH 16/42] fix: nixos example --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8e982b60..967e66a7 100644 --- a/README.md +++ b/README.md @@ -189,7 +189,10 @@ Here's an example of using the modules in a flake: inputs.nixpkgs.follows = "nixpkgs"; }; flake-parts.url = "github:hercules-ci/flake-parts"; - mango.url = "github:DreamMaoMao/mango"; + mango = { + url = "github:DreamMaoMao/mango"; + inputs.nixpkgs.follows = "nixpkgs"; + }; }; outputs = inputs@{ self, flake-parts, ... }: From 7ed12cf921fb2d6ca176fa2a18412c507fb5c5a7 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 12 Nov 2025 12:55:06 +0800 Subject: [PATCH 17/42] feat: support keyboard shortcut inhibitor --- src/config/parse_config.h | 9 ++++ src/config/preset.h | 1 + src/mango.c | 88 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 97 insertions(+), 1 deletion(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 0e9e15c5..a71d4f1e 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -69,6 +69,7 @@ typedef struct { int isunglobal; int isglobal; int isoverlay; + int allow_shortcuts_inhibit; int ignore_maximize; int ignore_minimize; int isnosizehint; @@ -336,6 +337,7 @@ typedef struct { int syncobj_enable; int adaptive_sync; int allow_tearing; + int allow_shortcuts_inhibit; struct xkb_rule_names xkb_rules; @@ -1271,6 +1273,8 @@ void parse_option(Config *config, char *key, char *value) { config->adaptive_sync = atoi(value); } else if (strcmp(key, "allow_tearing") == 0) { config->allow_tearing = atoi(value); + } else if (strcmp(key, "allow_shortcuts_inhibit") == 0) { + config->allow_shortcuts_inhibit = atoi(value); } else if (strcmp(key, "no_border_when_single") == 0) { config->no_border_when_single = atoi(value); } else if (strcmp(key, "no_radius_when_single") == 0) { @@ -1708,6 +1712,7 @@ void parse_option(Config *config, char *key, char *value) { rule->isunglobal = -1; rule->isglobal = -1; rule->isoverlay = -1; + rule->allow_shortcuts_inhibit = -1; rule->ignore_maximize = -1; rule->ignore_minimize = -1; rule->isnosizehint = -1; @@ -1806,6 +1811,8 @@ void parse_option(Config *config, char *key, char *value) { rule->focused_opacity = atof(val); } else if (strcmp(key, "isoverlay") == 0) { rule->isoverlay = atoi(val); + } else if (strcmp(key, "allow_shortcuts_inhibit") == 0) { + rule->allow_shortcuts_inhibit = atoi(val); } else if (strcmp(key, "ignore_maximize") == 0) { rule->ignore_maximize = atoi(val); } else if (strcmp(key, "ignore_minimize") == 0) { @@ -2696,6 +2703,7 @@ void override_config(void) { syncobj_enable = CLAMP_INT(config.syncobj_enable, 0, 1); adaptive_sync = CLAMP_INT(config.adaptive_sync, 0, 1); allow_tearing = CLAMP_INT(config.allow_tearing, 0, 2); + allow_shortcuts_inhibit = CLAMP_INT(config.allow_shortcuts_inhibit, 0, 1); axis_bind_apply_timeout = CLAMP_INT(config.axis_bind_apply_timeout, 0, 1000); focus_on_activate = CLAMP_INT(config.focus_on_activate, 0, 1); @@ -2873,6 +2881,7 @@ void set_value_default() { config.syncobj_enable = syncobj_enable; config.adaptive_sync = adaptive_sync; config.allow_tearing = allow_tearing; + config.allow_shortcuts_inhibit = allow_shortcuts_inhibit; config.no_border_when_single = no_border_when_single; config.no_radius_when_single = no_radius_when_single; config.snap_distance = snap_distance; diff --git a/src/config/preset.h b/src/config/preset.h index be1c1b0a..eaa7be22 100644 --- a/src/config/preset.h +++ b/src/config/preset.h @@ -105,6 +105,7 @@ int syncobj_enable = 0; int adaptive_sync = 0; double drag_refresh_interval = 30.0; int allow_tearing = TEARING_DISABLED; +int allow_shortcuts_inhibit = SHORTCUTS_INHIBIT_ENABLE; /* keyboard */ diff --git a/src/mango.c b/src/mango.c index 960b5cb5..cd830b40 100644 --- a/src/mango.c +++ b/src/mango.c @@ -48,6 +48,7 @@ #include #include #include +#include #include #include #include @@ -172,6 +173,11 @@ enum tearing_mode { TEARING_FULLSCREEN_ONLY, }; +enum seat_config_shortcuts_inhibit { + SHORTCUTS_INHIBIT_DISABLE, + SHORTCUTS_INHIBIT_ENABLE, +}; + typedef struct Pertag Pertag; typedef struct Monitor Monitor; typedef struct Client Client; @@ -370,6 +376,7 @@ struct Client { bool isleftstack; int tearing_hint; int force_tearing; + int allow_shortcuts_inhibit; }; typedef struct { @@ -399,6 +406,12 @@ typedef struct { struct wl_listener destroy; } KeyboardGroup; +typedef struct { + struct wlr_keyboard_shortcuts_inhibitor_v1 *inhibitor; + struct wl_listener destroy; + struct wl_list link; +} KeyboardShortcutsInhibitor; + typedef struct { /* Must keep these three elements in this order */ unsigned int type; /* LayerShell */ @@ -624,7 +637,9 @@ static void urgent(struct wl_listener *listener, void *data); static void view(const Arg *arg, bool want_animation); static void handlesig(int signo); - +static void +handle_keyboard_shortcuts_inhibit_new_inhibitor(struct wl_listener *listener, + void *data); static void virtualkeyboard(struct wl_listener *listener, void *data); static void virtualpointer(struct wl_listener *listener, void *data); static void warp_cursor(const Client *c); @@ -752,6 +767,8 @@ static struct wlr_idle_inhibit_manager_v1 *idle_inhibit_mgr; static struct wlr_layer_shell_v1 *layer_shell; static struct wlr_output_manager_v1 *output_mgr; static struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard_mgr; +static struct wlr_keyboard_shortcuts_inhibit_manager_v1 + *keyboard_shortcuts_inhibit; static struct wlr_virtual_pointer_manager_v1 *virtual_pointer_mgr; static struct wlr_output_power_manager_v1 *power_mgr; static struct wlr_pointer_gestures_v1 *pointer_gestures; @@ -775,6 +792,7 @@ static struct wlr_pointer_constraint_v1 *active_constraint; static struct wlr_seat *seat; static KeyboardGroup *kb_group; static struct wl_list inputdevices; +static struct wl_list keyboard_shortcut_inhibitors; static unsigned int cursor_mode; static Client *grabc; static int grabcx, grabcy; /* client-relative */ @@ -861,6 +879,8 @@ static struct wl_listener request_start_drag = {.notify = requeststartdrag}; static struct wl_listener start_drag = {.notify = startdrag}; static struct wl_listener new_session_lock = {.notify = locksession}; static struct wl_listener drm_lease_request = {.notify = requestdrmlease}; +static struct wl_listener keyboard_shortcuts_inhibit_new_inhibitor = { + .notify = handle_keyboard_shortcuts_inhibit_new_inhibitor}; #ifdef XWAYLAND static void activatex11(struct wl_listener *listener, void *data); @@ -1152,6 +1172,7 @@ static void apply_rule_properties(Client *c, const ConfigWinRule *r) { APPLY_INT_PROP(c, r, isnosizehint); APPLY_INT_PROP(c, r, isunglobal); APPLY_INT_PROP(c, r, noblur); + APPLY_INT_PROP(c, r, allow_shortcuts_inhibit); APPLY_FLOAT_PROP(c, r, scroller_proportion); APPLY_FLOAT_PROP(c, r, focused_opacity); @@ -1979,6 +2000,7 @@ void cleanuplisteners(void) { wl_list_remove(&start_drag.link); wl_list_remove(&new_session_lock.link); wl_list_remove(&tearing_new_object.link); + wl_list_remove(&keyboard_shortcuts_inhibit_new_inhibitor.link); if (drm_lease_manager) { wl_list_remove(&drm_lease_request.link); } @@ -3277,6 +3299,17 @@ int keyrepeat(void *data) { return 0; } +bool is_keyboard_shortcut_inhibitor(struct wlr_surface *surface) { + KeyboardShortcutsInhibitor *kbsinhibitor; + + wl_list_for_each(kbsinhibitor, &keyboard_shortcut_inhibitors, link) { + if (kbsinhibitor->inhibitor->surface == surface) { + return true; + } + } + return false; +} + int // 17 keybinding(unsigned int state, bool locked, unsigned int mods, xkb_keysym_t sym, unsigned int keycode) { @@ -3295,6 +3328,10 @@ keybinding(unsigned int state, bool locked, unsigned int mods, xkb_keysym_t sym, keycode == 62 || keycode == 108 || keycode == 105 || keycode == 134) return false; + if (is_keyboard_shortcut_inhibitor(seat->keyboard_state.focused_surface)) { + return false; + } + for (ji = 0; ji < config.key_bindings_count; ji++) { if (config.key_bindings_count < 1) break; @@ -3602,6 +3639,7 @@ void init_client_properties(Client *c) { c->allow_csd = 0; c->force_maximize = 0; c->force_tearing = 0; + c->allow_shortcuts_inhibit = SHORTCUTS_INHIBIT_ENABLE; } void // old fix to 0.5 @@ -5017,6 +5055,7 @@ void setup(void) { * to let us know when new input devices are available on the backend. */ wl_list_init(&inputdevices); + wl_list_init(&keyboard_shortcut_inhibitors); wl_signal_add(&backend->events.new_input, &new_input_device); virtual_keyboard_mgr = wlr_virtual_keyboard_manager_v1_create(dpy); wl_signal_add(&virtual_keyboard_mgr->events.new_virtual_keyboard, @@ -5046,6 +5085,10 @@ void setup(void) { kb_group = createkeyboardgroup(); wl_list_init(&kb_group->destroy.link); + keyboard_shortcuts_inhibit = wlr_keyboard_shortcuts_inhibit_v1_create(dpy); + wl_signal_add(&keyboard_shortcuts_inhibit->events.new_inhibitor, + &keyboard_shortcuts_inhibit_new_inhibitor); + output_mgr = wlr_output_manager_v1_create(dpy); wl_signal_add(&output_mgr->events.apply, &output_mgr_apply); wl_signal_add(&output_mgr->events.test, &output_mgr_test); @@ -5573,6 +5616,49 @@ void view(const Arg *arg, bool want_animation) { } } +static void +handle_keyboard_shortcuts_inhibitor_destroy(struct wl_listener *listener, + void *data) { + KeyboardShortcutsInhibitor *inhibitor = + wl_container_of(listener, inhibitor, destroy); + + wlr_log(WLR_DEBUG, "Removing keyboard shortcuts inhibitor"); + + wl_list_remove(&inhibitor->link); + wl_list_remove(&inhibitor->destroy.link); + free(inhibitor); +} + +void handle_keyboard_shortcuts_inhibit_new_inhibitor( + struct wl_listener *listener, void *data) { + + struct wlr_keyboard_shortcuts_inhibitor_v1 *inhibitor = data; + + if (allow_shortcuts_inhibit == SHORTCUTS_INHIBIT_DISABLE) { + return; + } + + // per-view, seat-agnostic config via criteria + Client *c = get_client_from_surface(inhibitor->surface); + if (c && !c->allow_shortcuts_inhibit) { + return; + } + + wlr_log(WLR_DEBUG, "Adding keyboard shortcuts inhibitor"); + + KeyboardShortcutsInhibitor *kbsinhibitor = + calloc(1, sizeof(KeyboardShortcutsInhibitor)); + + kbsinhibitor->inhibitor = inhibitor; + + kbsinhibitor->destroy.notify = handle_keyboard_shortcuts_inhibitor_destroy; + wl_signal_add(&inhibitor->events.destroy, &kbsinhibitor->destroy); + + wl_list_insert(&keyboard_shortcut_inhibitors, &kbsinhibitor->link); + + wlr_keyboard_shortcuts_inhibitor_v1_activate(inhibitor); +} + void virtualkeyboard(struct wl_listener *listener, void *data) { struct wlr_virtual_keyboard_v1 *kb = data; /* virtual keyboards shouldn't share keyboard group */ From ade00f88f016ec796ae21382c929dcddee452c5f Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 12 Nov 2025 23:10:09 +0800 Subject: [PATCH 18/42] fix: crash in some crossmon dispatch --- src/dispatch/bind_define.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 65c2b03e..168852f4 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -971,7 +971,7 @@ int tag(const Arg *arg) { } int tagmon(const Arg *arg) { - Monitor *m = NULL; + Monitor *m = NULL,*cm = NULL; Client *c = focustop(selmon); if (!c) @@ -980,11 +980,12 @@ int tagmon(const Arg *arg) { if (arg->i != UNDIR) { m = dirtomon(arg->i); } else if (arg->v) { - wl_list_for_each(m, &mons, link) { - if (!m->wlr_output->enabled) { + wl_list_for_each(cm, &mons, link) { + if (!cm->wlr_output->enabled) { continue; } - if (regex_match(arg->v, m->wlr_output->name)) { + if (regex_match(arg->v, cm->wlr_output->name)) { + m = cm; break; } } From 0ff4d9436596c4103d6c260454df9c60dde8511b Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 13 Nov 2025 10:42:35 +0800 Subject: [PATCH 19/42] opt: remove useless normalize keysym convert --- src/config/parse_config.h | 55 -------------------------------------- src/dispatch/bind_define.h | 2 +- src/mango.c | 4 +-- 3 files changed, 3 insertions(+), 58 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index a71d4f1e..a6cfb647 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -517,61 +517,6 @@ long int parse_color(const char *hex_str) { return hex_num; } -xkb_keysym_t normalize_keysym(xkb_keysym_t sym) { - // 首先转换为小写(主要影响字母键) - sym = xkb_keysym_to_lower(sym); - - // 将数字小键盘键转换为普通数字键 - switch (sym) { - // 小键盘数字转换 - case XKB_KEY_KP_0: - return XKB_KEY_0; - case XKB_KEY_KP_1: - return XKB_KEY_1; - case XKB_KEY_KP_2: - return XKB_KEY_2; - case XKB_KEY_KP_3: - return XKB_KEY_3; - case XKB_KEY_KP_4: - return XKB_KEY_4; - case XKB_KEY_KP_5: - return XKB_KEY_5; - case XKB_KEY_KP_6: - return XKB_KEY_6; - case XKB_KEY_KP_7: - return XKB_KEY_7; - case XKB_KEY_KP_8: - return XKB_KEY_8; - case XKB_KEY_KP_9: - return XKB_KEY_9; - - // 将 Shift+数字 的符号转换回基础数字 - case XKB_KEY_exclam: - return XKB_KEY_1; // ! - case XKB_KEY_at: - return XKB_KEY_2; // @ - case XKB_KEY_numbersign: - return XKB_KEY_3; // # - case XKB_KEY_dollar: - return XKB_KEY_4; // $ - case XKB_KEY_percent: - return XKB_KEY_5; // % - case XKB_KEY_asciicircum: - return XKB_KEY_6; // ^ - case XKB_KEY_ampersand: - return XKB_KEY_7; // & - case XKB_KEY_asterisk: - return XKB_KEY_8; // * - case XKB_KEY_parenleft: - return XKB_KEY_9; // ( - case XKB_KEY_parenright: - return XKB_KEY_0; // ) - - default: - return sym; - } -} - // 辅助函数:检查字符串是否以指定的前缀开头(忽略大小写) static bool starts_with_ignore_case(const char *str, const char *prefix) { while (*prefix) { diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 168852f4..d3ab6298 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -971,7 +971,7 @@ int tag(const Arg *arg) { } int tagmon(const Arg *arg) { - Monitor *m = NULL,*cm = NULL; + Monitor *m = NULL, *cm = NULL; Client *c = focustop(selmon); if (!c) diff --git a/src/mango.c b/src/mango.c index cd830b40..edd50567 100644 --- a/src/mango.c +++ b/src/mango.c @@ -3356,8 +3356,8 @@ keybinding(unsigned int state, bool locked, unsigned int mods, xkb_keysym_t sym, (strcmp(keymode.mode, k->mode) == 0)) && CLEANMASK(mods) == CLEANMASK(k->mod) && ((k->keysymcode.type == KEY_TYPE_SYM && - normalize_keysym(sym) == - normalize_keysym(k->keysymcode.keysym)) || + xkb_keysym_to_lower(sym) == + xkb_keysym_to_lower(k->keysymcode.keysym)) || (k->keysymcode.type == KEY_TYPE_CODE && (keycode == k->keysymcode.keycode.keycode1 || keycode == k->keysymcode.keycode.keycode2 || From 43424152f398618c765951457600440e52a5abaa Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 13 Nov 2025 10:49:44 +0800 Subject: [PATCH 20/42] opt: spawn_on_empty and toggle_named_scratchapd use spawn_shell --- src/dispatch/bind_define.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index d3ab6298..27f79775 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -805,7 +805,7 @@ int spawn_on_empty(const Arg *arg) { return 0; } else { view(arg, true); - spawn(arg); + spawn_shell(arg); } return 0; } @@ -1088,7 +1088,7 @@ int toggle_named_scratchpad(const Arg *arg) { if (!target_client && arg->v3) { Arg arg_spawn = {.v = arg->v3}; - spawn(&arg_spawn); + spawn_shell(&arg_spawn); return 0; } From 94e47bc3b039dafc264e64f465c9fabb523d359d Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 13 Nov 2025 10:55:44 +0800 Subject: [PATCH 21/42] opt: optmize restore_minimized size and not restore namedscratchpad --- src/dispatch/bind_define.h | 2 +- src/mango.c | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 27f79775..d653bb18 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -476,7 +476,7 @@ int restore_minimized(const Arg *arg) { } wl_list_for_each(c, &clients, link) { - if (c->isminied) { + if (c->isminied && !c->isnamedscratchpad) { c->is_scratchpad_show = 0; c->is_in_scratchpad = 0; c->isnamedscratchpad = 0; diff --git a/src/mango.c b/src/mango.c index edd50567..999c3561 100644 --- a/src/mango.c +++ b/src/mango.c @@ -4807,8 +4807,11 @@ void setsel(struct wl_listener *listener, void *data) { } void show_hide_client(Client *c) { + unsigned int target = 1; + + set_size_per(c->mon, c); + target = get_tags_first_tag(c->oldtags); - unsigned int target = get_tags_first_tag(c->oldtags); if (!c->is_in_scratchpad) { tag_client(&(Arg){.ui = target}, c); } else { From cfb66111def6e760df8670e6fc6be9640c6d26a9 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 14 Nov 2025 11:53:41 +0800 Subject: [PATCH 22/42] fix: tagrule not apply correctly --- src/config/parse_config.h | 46 +++++++++++++++++++++------------------ src/mango.c | 13 +---------- 2 files changed, 26 insertions(+), 33 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index a6cfb647..59cb37bc 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -3165,33 +3165,37 @@ void reapply_master(void) { } } +void parse_tagrule(Monitor *m) { + int i, jk; + + for (i = 0; i < config.tag_rules_count; i++) { + + if (config.tag_rules_count > 0 && + (!config.tag_rules[i].monitor_name || + regex_match(config.tag_rules[i].monitor_name, + m->wlr_output->name))) { + + for (jk = 0; jk < LENGTH(layouts); jk++) { + if (config.tag_rules[i].layout_name && + strcmp(layouts[jk].name, config.tag_rules[i].layout_name) == + 0) { + m->pertag->ltidxs[config.tag_rules[i].id] = &layouts[jk]; + } + } + + m->pertag->no_hide[config.tag_rules[i].id] = + config.tag_rules[i].no_hide; + } + } +} + void reapply_tagrule(void) { Monitor *m = NULL; - int i, jk; - char *rule_monitor_name = NULL; wl_list_for_each(m, &mons, link) { if (!m->wlr_output->enabled) { continue; } - - // apply tag rule - for (i = 1; i <= config.tag_rules_count; i++) { - rule_monitor_name = config.tag_rules[i - 1].monitor_name; - if (regex_match(rule_monitor_name, m->wlr_output->name) || - !rule_monitor_name) { - for (jk = 0; jk < LENGTH(layouts); jk++) { - if (config.tag_rules_count > 0 && - config.tag_rules[i - 1].layout_name && - strcmp(layouts[jk].name, - config.tag_rules[i - 1].layout_name) == 0) { - m->pertag->ltidxs[config.tag_rules[i - 1].id] = - &layouts[jk]; - m->pertag->no_hide[config.tag_rules[i - 1].id] = - config.tag_rules[i - 1].no_hide; - } - } - } - } + parse_tagrule(m); } } diff --git a/src/mango.c b/src/mango.c index 999c3561..285df099 100644 --- a/src/mango.c +++ b/src/mango.c @@ -2705,18 +2705,7 @@ void createmon(struct wl_listener *listener, void *data) { } // apply tag rule - for (i = 1; i <= config.tag_rules_count; i++) { - for (jk = 0; jk < LENGTH(layouts); jk++) { - if (config.tag_rules_count > 0 && - config.tag_rules[i - 1].layout_name && - strcmp(layouts[jk].name, config.tag_rules[i - 1].layout_name) == - 0) { - m->pertag->ltidxs[config.tag_rules[i - 1].id] = &layouts[jk]; - m->pertag->no_hide[config.tag_rules[i - 1].id] = - config.tag_rules[i - 1].no_hide; - } - } - } + parse_tagrule(m); /* The xdg-protocol specifies: * From bd6a71f05e0870a72f97b8b68de9f03dba80826b Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 14 Nov 2025 12:15:34 +0800 Subject: [PATCH 23/42] fix: fix border color change when swithc mon focus --- src/animation/client.h | 2 +- src/dispatch/bind_define.h | 2 +- src/fetch/client.h | 5 ++++- src/mango.c | 9 +++++++-- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/animation/client.h b/src/animation/client.h index 70da39e9..a5236f04 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -1084,7 +1084,7 @@ void client_set_focused_opacity_animation(Client *c) { c->opacity_animation.running = true; } -void cleint_set_unfocused_opacity_animation(Client *c) { +void client_set_unfocused_opacity_animation(Client *c) { // Start border color animation to unfocused float *border_color = get_border_color(c); diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index d653bb18..c812bf14 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -202,7 +202,7 @@ int focusmon(const Arg *arg) { focusclient(c, 1); if (old_selmon_sel) { - setborder_color(old_selmon_sel); + client_set_unfocused_opacity_animation(old_selmon_sel); } return 0; } diff --git a/src/fetch/client.h b/src/fetch/client.h index 6e675223..5f62d234 100644 --- a/src/fetch/client.h +++ b/src/fetch/client.h @@ -374,7 +374,10 @@ Client *get_next_stack_client(Client *c, bool reverse) { } float *get_border_color(Client *c) { - if (c->isurgent) { + + if (c->mon != selmon) { + return bordercolor; + } else if (c->isurgent) { return urgentcolor; } else if (c->is_in_scratchpad && selmon && c == selmon->sel) { return scratchpadcolor; diff --git a/src/mango.c b/src/mango.c index 285df099..41822bc4 100644 --- a/src/mango.c +++ b/src/mango.c @@ -3103,6 +3103,9 @@ void destroykeyboardgroup(struct wl_listener *listener, void *data) { } void focusclient(Client *c, int lift) { + + Client *last_focus_client = NULL; + struct wlr_surface *old_keyboard_focus_surface = seat->keyboard_state.focused_surface; @@ -3137,12 +3140,14 @@ void focusclient(Client *c, int lift) { if (c && !c->iskilling && !client_is_unmanaged(c) && c->mon) { + last_focus_client = selmon->sel; selmon = c->mon; selmon->prevsel = selmon->sel; selmon->sel = c; - if (selmon->prevsel && !selmon->prevsel->iskilling) { - cleint_set_unfocused_opacity_animation(selmon->prevsel); + if (last_focus_client && !last_focus_client->iskilling && + last_focus_client != c) { + client_set_unfocused_opacity_animation(last_focus_client); } client_set_focused_opacity_animation(c); From d2e0df024dc195fef8416b3a875c8344a87e9aaf Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 14 Nov 2025 12:32:07 +0800 Subject: [PATCH 24/42] opt: optimize code struct --- src/config/parse_config.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 59cb37bc..63580830 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -3167,24 +3167,24 @@ void reapply_master(void) { void parse_tagrule(Monitor *m) { int i, jk; + ConfigTagRule tr; for (i = 0; i < config.tag_rules_count; i++) { + tr = config.tag_rules[i]; + if (config.tag_rules_count > 0 && - (!config.tag_rules[i].monitor_name || - regex_match(config.tag_rules[i].monitor_name, - m->wlr_output->name))) { + (!tr.monitor_name || + regex_match(tr.monitor_name, m->wlr_output->name))) { for (jk = 0; jk < LENGTH(layouts); jk++) { - if (config.tag_rules[i].layout_name && - strcmp(layouts[jk].name, config.tag_rules[i].layout_name) == - 0) { - m->pertag->ltidxs[config.tag_rules[i].id] = &layouts[jk]; + if (tr.layout_name && + strcmp(layouts[jk].name, tr.layout_name) == 0) { + m->pertag->ltidxs[tr.id] = &layouts[jk]; } } - m->pertag->no_hide[config.tag_rules[i].id] = - config.tag_rules[i].no_hide; + m->pertag->no_hide[tr.id] = tr.no_hide; } } } From 389f417a3b1208b1fd2fc0ace399d23fc9dc6f3c Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 14 Nov 2025 15:39:04 +0800 Subject: [PATCH 25/42] feat: add windowrule option scroller_proportion_single --- src/config/parse_config.h | 4 ++++ src/layout/horizontal.h | 9 +++++++-- src/layout/vertical.h | 10 ++++++++-- src/mango.c | 3 +++ 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 63580830..074660bf 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -90,6 +90,7 @@ typedef struct { int noblur; float focused_opacity; float unfocused_opacity; + float scroller_proportion_single; uint32_t passmod; xkb_keysym_t keysym; KeyBinding globalkeybinding; @@ -1679,6 +1680,7 @@ void parse_option(Config *config, char *key, char *value) { // float rule value, relay to a client property rule->focused_opacity = 0; rule->unfocused_opacity = 0; + rule->scroller_proportion_single = 0.0f; rule->scroller_proportion = 0; // special rule value,not directly set to client property @@ -1750,6 +1752,8 @@ void parse_option(Config *config, char *key, char *value) { rule->isunglobal = atoi(val); } else if (strcmp(key, "isglobal") == 0) { rule->isglobal = atoi(val); + } else if (strcmp(key, "scroller_proportion_single") == 0) { + rule->scroller_proportion_single = atof(val); } else if (strcmp(key, "unfocused_opacity") == 0) { rule->unfocused_opacity = atof(val); } else if (strcmp(key, "focused_opacity") == 0) { diff --git a/src/layout/horizontal.h b/src/layout/horizontal.h index 178ae9e0..934dc136 100644 --- a/src/layout/horizontal.h +++ b/src/layout/horizontal.h @@ -192,6 +192,7 @@ void deck(Monitor *m) { // 滚动布局 void scroller(Monitor *m) { unsigned int i, n, j; + float single_proportion = 1.0; Client *c = NULL, *root_client = NULL; Client **tempClients = NULL; // 初始化为 NULL @@ -233,9 +234,13 @@ void scroller(Monitor *m) { if (n == 1 && !scroller_ignore_proportion_single) { c = tempClients[0]; + + single_proportion = c->scroller_proportion_single > 0.0f + ? c->scroller_proportion_single + : scroller_default_proportion_single; + target_geom.height = m->w.height - 2 * cur_gappov; - target_geom.width = - (m->w.width - 2 * cur_gappoh) * scroller_default_proportion_single; + target_geom.width = (m->w.width - 2 * cur_gappoh) * single_proportion; target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2; target_geom.y = m->w.y + (m->w.height - target_geom.height) / 2; resize(c, target_geom, 0); diff --git a/src/layout/vertical.h b/src/layout/vertical.h index 6ae21a6c..46ca3b65 100644 --- a/src/layout/vertical.h +++ b/src/layout/vertical.h @@ -157,6 +157,8 @@ void vertical_deck(Monitor *m) { void vertical_scroller(Monitor *m) { unsigned int i, n, j; + float single_proportion = 1.0; + Client *c = NULL, *root_client = NULL; Client **tempClients = NULL; struct wlr_box target_geom; @@ -194,9 +196,13 @@ void vertical_scroller(Monitor *m) { if (n == 1 && !scroller_ignore_proportion_single) { c = tempClients[0]; + + single_proportion = c->scroller_proportion_single > 0.0f + ? c->scroller_proportion_single + : scroller_default_proportion_single; + target_geom.width = m->w.width - 2 * cur_gappoh; - target_geom.height = - (m->w.height - 2 * cur_gappov) * scroller_default_proportion_single; + target_geom.height = (m->w.height - 2 * cur_gappov) * single_proportion; target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2; target_geom.y = m->w.y + (m->w.height - target_geom.height) / 2; resize(c, target_geom, 0); diff --git a/src/mango.c b/src/mango.c index 41822bc4..e5d9e337 100644 --- a/src/mango.c +++ b/src/mango.c @@ -377,6 +377,7 @@ struct Client { int tearing_hint; int force_tearing; int allow_shortcuts_inhibit; + float scroller_proportion_single; }; typedef struct { @@ -1175,6 +1176,7 @@ static void apply_rule_properties(Client *c, const ConfigWinRule *r) { APPLY_INT_PROP(c, r, allow_shortcuts_inhibit); APPLY_FLOAT_PROP(c, r, scroller_proportion); + APPLY_FLOAT_PROP(c, r, scroller_proportion_single); APPLY_FLOAT_PROP(c, r, focused_opacity); APPLY_FLOAT_PROP(c, r, unfocused_opacity); @@ -3634,6 +3636,7 @@ void init_client_properties(Client *c) { c->force_maximize = 0; c->force_tearing = 0; c->allow_shortcuts_inhibit = SHORTCUTS_INHIBIT_ENABLE; + c->scroller_proportion_single = 0.0f; } void // old fix to 0.5 From b3578344dcdb1ac7a235fcd9246c858ee5e38d38 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 14 Nov 2025 17:13:26 +0800 Subject: [PATCH 26/42] opt: optimize init focus for x11 window --- src/mango.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mango.c b/src/mango.c index e5d9e337..65202f32 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1370,7 +1370,8 @@ void applyrules(Client *c) { int fullscreen_state_backup = c->isfullscreen || client_wants_fullscreen(c); setmon(c, mon, newtags, - !c->isopensilent && !client_should_ignore_focus(c) && + !c->isopensilent && + !(client_is_x11_popup(c) && client_should_ignore_focus(c)) && (!c->istagsilent || !newtags || newtags & mon->tagset[mon->seltags])); From 4c6bef64dc01ecefb3f4686abd266d97ef73367c Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 15 Nov 2025 00:08:57 +0800 Subject: [PATCH 27/42] feat: support scroll maximize and fullscreen window --- src/dispatch/bind_define.h | 11 +++- src/ext-protocol/dwl-ipc.h | 1 + src/fetch/client.h | 113 ++++++++++++++++++++----------------- src/layout/arrange.h | 5 ++ src/layout/horizontal.h | 71 ++++++++++++++++++----- src/layout/vertical.h | 72 ++++++++++++++++++----- src/mango.c | 45 ++++++++++++--- 7 files changed, 228 insertions(+), 90 deletions(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index c812bf14..d79a7c65 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -80,8 +80,12 @@ int defaultgaps(const Arg *arg) { int exchange_client(const Arg *arg) { Client *c = selmon->sel; - if (!c || c->isfloating || c->isfullscreen || c->ismaximizescreen) + if (!c || c->isfloating) return 0; + + if ((c->isfullscreen || c->ismaximizescreen) && !is_scroller_layout(c->mon)) + return 0; + exchange_two_client(c, direction_select(arg)); return 0; } @@ -497,7 +501,7 @@ int setlayout(const Arg *arg) { for (jk = 0; jk < LENGTH(layouts); jk++) { if (strcmp(layouts[jk].name, arg->v) == 0) { selmon->pertag->ltidxs[selmon->pertag->curtag] = &layouts[jk]; - + clear_fullscreen_and_maximized_state(selmon); arrange(selmon, false); printstatus(); return 0; @@ -901,7 +905,7 @@ int switch_layout(const Arg *arg) { break; } } - + clear_fullscreen_and_maximized_state(selmon); arrange(selmon, false); printstatus(); return 0; @@ -912,6 +916,7 @@ int switch_layout(const Arg *arg) { selmon->pertag->ltidxs[selmon->pertag->curtag]->name) == 0) { selmon->pertag->ltidxs[selmon->pertag->curtag] = jk == LENGTH(layouts) - 1 ? &layouts[0] : &layouts[jk + 1]; + clear_fullscreen_and_maximized_state(selmon); arrange(selmon, false); printstatus(); return 0; diff --git a/src/ext-protocol/dwl-ipc.h b/src/ext-protocol/dwl-ipc.h index 6b0c4c54..15b2376a 100644 --- a/src/ext-protocol/dwl-ipc.h +++ b/src/ext-protocol/dwl-ipc.h @@ -258,6 +258,7 @@ void dwl_ipc_output_set_layout(struct wl_client *client, index = 0; monitor->pertag->ltidxs[monitor->pertag->curtag] = &layouts[index]; + clear_fullscreen_and_maximized_state(monitor); arrange(monitor, false); printstatus(); } diff --git a/src/fetch/client.h b/src/fetch/client.h index 5f62d234..c2b0abdd 100644 --- a/src/fetch/client.h +++ b/src/fetch/client.h @@ -142,7 +142,7 @@ Client *center_tiled_select(Monitor *m) { return target_c; } Client *find_client_by_direction(Client *tc, const Arg *arg, bool findfloating, - bool align) { + bool ignore_align) { Client *c = NULL; Client **tempClients = NULL; // 初始化为 NULL int last = -1; @@ -185,21 +185,23 @@ Client *find_client_by_direction(Client *tc, const Arg *arg, bool findfloating, switch (arg->i) { case UP: - for (int _i = 0; _i <= last; _i++) { - if (tempClients[_i]->geom.y < sel_y && - tempClients[_i]->geom.x == sel_x && - tempClients[_i]->mon == tc->mon) { - int dis_x = tempClients[_i]->geom.x - sel_x; - int dis_y = tempClients[_i]->geom.y - sel_y; - long long int tmp_distance = - dis_x * dis_x + dis_y * dis_y; // 计算距离 - if (tmp_distance < distance) { - distance = tmp_distance; - tempFocusClients = tempClients[_i]; + if (!ignore_align) { + for (int _i = 0; _i <= last; _i++) { + if (tempClients[_i]->geom.y < sel_y && + tempClients[_i]->geom.x == sel_x && + tempClients[_i]->mon == tc->mon) { + int dis_x = tempClients[_i]->geom.x - sel_x; + int dis_y = tempClients[_i]->geom.y - sel_y; + long long int tmp_distance = + dis_x * dis_x + dis_y * dis_y; // 计算距离 + if (tmp_distance < distance) { + distance = tmp_distance; + tempFocusClients = tempClients[_i]; + } } } } - if (!tempFocusClients && !align) { + if (!tempFocusClients) { for (int _i = 0; _i <= last; _i++) { if (tempClients[_i]->geom.y < sel_y) { int dis_x = tempClients[_i]->geom.x - sel_x; @@ -215,21 +217,23 @@ Client *find_client_by_direction(Client *tc, const Arg *arg, bool findfloating, } break; case DOWN: - for (int _i = 0; _i <= last; _i++) { - if (tempClients[_i]->geom.y > sel_y && - tempClients[_i]->geom.x == sel_x && - tempClients[_i]->mon == tc->mon) { - int dis_x = tempClients[_i]->geom.x - sel_x; - int dis_y = tempClients[_i]->geom.y - sel_y; - long long int tmp_distance = - dis_x * dis_x + dis_y * dis_y; // 计算距离 - if (tmp_distance < distance) { - distance = tmp_distance; - tempFocusClients = tempClients[_i]; + if (!ignore_align) { + for (int _i = 0; _i <= last; _i++) { + if (tempClients[_i]->geom.y > sel_y && + tempClients[_i]->geom.x == sel_x && + tempClients[_i]->mon == tc->mon) { + int dis_x = tempClients[_i]->geom.x - sel_x; + int dis_y = tempClients[_i]->geom.y - sel_y; + long long int tmp_distance = + dis_x * dis_x + dis_y * dis_y; // 计算距离 + if (tmp_distance < distance) { + distance = tmp_distance; + tempFocusClients = tempClients[_i]; + } } } } - if (!tempFocusClients && !align) { + if (!tempFocusClients) { for (int _i = 0; _i <= last; _i++) { if (tempClients[_i]->geom.y > sel_y) { int dis_x = tempClients[_i]->geom.x - sel_x; @@ -245,21 +249,23 @@ Client *find_client_by_direction(Client *tc, const Arg *arg, bool findfloating, } break; case LEFT: - for (int _i = 0; _i <= last; _i++) { - if (tempClients[_i]->geom.x < sel_x && - tempClients[_i]->geom.y == sel_y && - tempClients[_i]->mon == tc->mon) { - int dis_x = tempClients[_i]->geom.x - sel_x; - int dis_y = tempClients[_i]->geom.y - sel_y; - long long int tmp_distance = - dis_x * dis_x + dis_y * dis_y; // 计算距离 - if (tmp_distance < distance) { - distance = tmp_distance; - tempFocusClients = tempClients[_i]; + if (!ignore_align) { + for (int _i = 0; _i <= last; _i++) { + if (tempClients[_i]->geom.x < sel_x && + tempClients[_i]->geom.y == sel_y && + tempClients[_i]->mon == tc->mon) { + int dis_x = tempClients[_i]->geom.x - sel_x; + int dis_y = tempClients[_i]->geom.y - sel_y; + long long int tmp_distance = + dis_x * dis_x + dis_y * dis_y; // 计算距离 + if (tmp_distance < distance) { + distance = tmp_distance; + tempFocusClients = tempClients[_i]; + } } } } - if (!tempFocusClients && !align) { + if (!tempFocusClients) { for (int _i = 0; _i <= last; _i++) { if (tempClients[_i]->geom.x < sel_x) { int dis_x = tempClients[_i]->geom.x - sel_x; @@ -275,21 +281,23 @@ Client *find_client_by_direction(Client *tc, const Arg *arg, bool findfloating, } break; case RIGHT: - for (int _i = 0; _i <= last; _i++) { - if (tempClients[_i]->geom.x > sel_x && - tempClients[_i]->geom.y == sel_y && - tempClients[_i]->mon == tc->mon) { - int dis_x = tempClients[_i]->geom.x - sel_x; - int dis_y = tempClients[_i]->geom.y - sel_y; - long long int tmp_distance = - dis_x * dis_x + dis_y * dis_y; // 计算距离 - if (tmp_distance < distance) { - distance = tmp_distance; - tempFocusClients = tempClients[_i]; + if (!ignore_align) { + for (int _i = 0; _i <= last; _i++) { + if (tempClients[_i]->geom.x > sel_x && + tempClients[_i]->geom.y == sel_y && + tempClients[_i]->mon == tc->mon) { + int dis_x = tempClients[_i]->geom.x - sel_x; + int dis_y = tempClients[_i]->geom.y - sel_y; + long long int tmp_distance = + dis_x * dis_x + dis_y * dis_y; // 计算距离 + if (tmp_distance < distance) { + distance = tmp_distance; + tempFocusClients = tempClients[_i]; + } } } } - if (!tempFocusClients && !align) { + if (!tempFocusClients) { for (int _i = 0; _i <= last; _i++) { if (tempClients[_i]->geom.x > sel_x) { int dis_x = tempClients[_i]->geom.x - sel_x; @@ -317,12 +325,13 @@ Client *direction_select(const Arg *arg) { if (!tc) return NULL; - if (tc && (tc->isfullscreen || tc->ismaximizescreen)) { - // 不支持全屏窗口的焦点切换 + if (tc && (tc->isfullscreen || tc->ismaximizescreen) && + (!is_scroller_layout(selmon) || tc->isfloating)) { return NULL; } - return find_client_by_direction(tc, arg, true, false); + return find_client_by_direction( + tc, arg, true, is_scroller_layout(selmon) && !selmon->isoverview); } /* We probably should change the name of this, it sounds like diff --git a/src/layout/arrange.h b/src/layout/arrange.h index aafb79e0..ba1391e6 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -599,6 +599,7 @@ arrange(Monitor *m, bool want_animation) { return; m->visible_clients = 0; m->visible_tiling_clients = 0; + m->visible_scroll_tiling_clients = 0; m->has_visible_fullscreen_client = false; wl_list_for_each(c, &clients, link) { @@ -619,6 +620,10 @@ arrange(Monitor *m, bool want_animation) { if (ISTILED(c)) { m->visible_tiling_clients++; } + + if (ISSCROLLTILED(c)) { + m->visible_scroll_tiling_clients++; + } } } diff --git a/src/layout/horizontal.h b/src/layout/horizontal.h index 934dc136..ff517cc1 100644 --- a/src/layout/horizontal.h +++ b/src/layout/horizontal.h @@ -189,6 +189,38 @@ void deck(Monitor *m) { } } +void horizontal_scroll_adjust_fullandmax(Client *c, + struct wlr_box *target_geom) { + Monitor *m = c->mon; + unsigned int cur_gappih = enablegaps ? m->gappih : 0; + unsigned int cur_gappoh = enablegaps ? m->gappoh : 0; + unsigned int cur_gappov = enablegaps ? m->gappov : 0; + + cur_gappih = + smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappih; + cur_gappoh = + smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappoh; + cur_gappov = + smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappov; + + if (c->isfullscreen) { + target_geom->height = m->m.height; + target_geom->width = m->m.width; + target_geom->y = m->m.y; + return; + } + + if (c->ismaximizescreen) { + target_geom->height = m->w.height - 2 * cur_gappov; + target_geom->width = m->w.width - 2 * cur_gappoh; + target_geom->y = m->w.y + cur_gappov; + return; + } + + target_geom->height = m->w.height - 2 * cur_gappov; + target_geom->y = m->w.y + (m->w.height - target_geom->height) / 2; +} + // 滚动布局 void scroller(Monitor *m) { unsigned int i, n, j; @@ -203,14 +235,17 @@ void scroller(Monitor *m) { unsigned int cur_gappoh = enablegaps ? m->gappoh : 0; unsigned int cur_gappov = enablegaps ? m->gappov : 0; - cur_gappih = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappih; - cur_gappoh = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; - cur_gappov = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; + cur_gappih = + smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappih; + cur_gappoh = + smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappoh; + cur_gappov = + smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappov; unsigned int max_client_width = m->w.width - 2 * scroller_structs - cur_gappih; - n = m->visible_tiling_clients; + n = m->visible_scroll_tiling_clients; if (n == 0) { return; // 没有需要处理的客户端,直接返回 @@ -226,13 +261,14 @@ void scroller(Monitor *m) { // 第二次遍历,填充 tempClients j = 0; wl_list_for_each(c, &clients, link) { - if (VISIBLEON(c, m) && ISTILED(c)) { + if (VISIBLEON(c, m) && ISSCROLLTILED(c)) { tempClients[j] = c; j++; } } - if (n == 1 && !scroller_ignore_proportion_single) { + if (n == 1 && !scroller_ignore_proportion_single && + !tempClients[0]->isfullscreen && !tempClients[0]->ismaximizescreen) { c = tempClients[0]; single_proportion = c->scroller_proportion_single > 0.0f @@ -248,11 +284,10 @@ void scroller(Monitor *m) { return; } - if (m->sel && !client_is_unmanaged(m->sel) && !m->sel->isfloating && - !m->sel->ismaximizescreen && !m->sel->isfullscreen) { + if (m->sel && !client_is_unmanaged(m->sel) && !m->sel->isfloating) { root_client = m->sel; - } else if (m->prevsel && ISTILED(m->prevsel) && VISIBLEON(m->prevsel, m) && - !client_is_unmanaged(m->prevsel)) { + } else if (m->prevsel && ISSCROLLTILED(m->prevsel) && + VISIBLEON(m->prevsel, m) && !client_is_unmanaged(m->prevsel)) { root_client = m->prevsel; } else { root_client = center_tiled_select(m); @@ -289,11 +324,18 @@ void scroller(Monitor *m) { target_geom.height = m->w.height - 2 * cur_gappov; target_geom.width = max_client_width * c->scroller_proportion; target_geom.y = m->w.y + (m->w.height - target_geom.height) / 2; - - if (need_scroller) { + horizontal_scroll_adjust_fullandmax(tempClients[focus_client_index], + &target_geom); + if (tempClients[focus_client_index]->isfullscreen) { + target_geom.x = m->m.x; + resize(tempClients[focus_client_index], target_geom, 0); + } else if (tempClients[focus_client_index]->ismaximizescreen) { + target_geom.x = m->w.x + cur_gappoh; + resize(tempClients[focus_client_index], target_geom, 0); + } else if (need_scroller) { if (scroller_focus_center || ((!m->prevsel || - (ISTILED(m->prevsel) && + (ISSCROLLTILED(m->prevsel) && (m->prevsel->scroller_proportion * max_client_width) + (root_client->scroller_proportion * max_client_width) > m->w.width - 2 * scroller_structs - cur_gappih)) && @@ -316,14 +358,17 @@ void scroller(Monitor *m) { for (i = 1; i <= focus_client_index; i++) { c = tempClients[focus_client_index - i]; target_geom.width = max_client_width * c->scroller_proportion; + horizontal_scroll_adjust_fullandmax(c, &target_geom); target_geom.x = tempClients[focus_client_index - i + 1]->geom.x - cur_gappih - target_geom.width; + resize(c, target_geom, 0); } for (i = 1; i < n - focus_client_index; i++) { c = tempClients[focus_client_index + i]; target_geom.width = max_client_width * c->scroller_proportion; + horizontal_scroll_adjust_fullandmax(c, &target_geom); target_geom.x = tempClients[focus_client_index + i - 1]->geom.x + cur_gappih + tempClients[focus_client_index + i - 1]->geom.width; diff --git a/src/layout/vertical.h b/src/layout/vertical.h index 46ca3b65..cef0e3bb 100644 --- a/src/layout/vertical.h +++ b/src/layout/vertical.h @@ -155,6 +155,38 @@ void vertical_deck(Monitor *m) { } } +void vertical_scroll_adjust_fullandmax(Client *c, struct wlr_box *target_geom) { + Monitor *m = c->mon; + unsigned int cur_gappiv = enablegaps ? m->gappiv : 0; + unsigned int cur_gappov = enablegaps ? m->gappov : 0; + unsigned int cur_gappoh = enablegaps ? m->gappoh : 0; + + cur_gappiv = + smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappiv; + cur_gappov = + smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappov; + cur_gappoh = + smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappoh; + + if (c->isfullscreen) { + target_geom->width = m->m.width; + target_geom->height = m->m.height; + target_geom->x = m->m.x; + return; + } + + if (c->ismaximizescreen) { + target_geom->width = m->w.width - 2 * cur_gappoh; + target_geom->height = m->w.height - 2 * cur_gappov; + target_geom->x = m->w.x + cur_gappoh; + return; + } + + target_geom->width = m->w.width - 2 * cur_gappoh; + target_geom->x = m->w.x + (m->w.width - target_geom->width) / 2; +} + +// 竖屏滚动布局 void vertical_scroller(Monitor *m) { unsigned int i, n, j; float single_proportion = 1.0; @@ -168,14 +200,17 @@ void vertical_scroller(Monitor *m) { unsigned int cur_gappov = enablegaps ? m->gappov : 0; unsigned int cur_gappoh = enablegaps ? m->gappoh : 0; - cur_gappiv = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappiv; - cur_gappov = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappov; - cur_gappoh = smartgaps && m->visible_tiling_clients == 1 ? 0 : cur_gappoh; + cur_gappiv = + smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappiv; + cur_gappov = + smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappov; + cur_gappoh = + smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappoh; unsigned int max_client_height = m->w.height - 2 * scroller_structs - cur_gappiv; - n = m->visible_tiling_clients; + n = m->visible_scroll_tiling_clients; if (n == 0) { return; @@ -188,13 +223,14 @@ void vertical_scroller(Monitor *m) { j = 0; wl_list_for_each(c, &clients, link) { - if (VISIBLEON(c, m) && ISTILED(c)) { + if (VISIBLEON(c, m) && ISSCROLLTILED(c)) { tempClients[j] = c; j++; } } - if (n == 1 && !scroller_ignore_proportion_single) { + if (n == 1 && !scroller_ignore_proportion_single && + !tempClients[0]->isfullscreen && !tempClients[0]->ismaximizescreen) { c = tempClients[0]; single_proportion = c->scroller_proportion_single > 0.0f @@ -203,18 +239,17 @@ void vertical_scroller(Monitor *m) { target_geom.width = m->w.width - 2 * cur_gappoh; target_geom.height = (m->w.height - 2 * cur_gappov) * single_proportion; - target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2; target_geom.y = m->w.y + (m->w.height - target_geom.height) / 2; + target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2; resize(c, target_geom, 0); free(tempClients); return; } - if (m->sel && !client_is_unmanaged(m->sel) && !m->sel->isfloating && - !m->sel->ismaximizescreen && !m->sel->isfullscreen) { + if (m->sel && !client_is_unmanaged(m->sel) && !m->sel->isfloating) { root_client = m->sel; - } else if (m->prevsel && ISTILED(m->prevsel) && VISIBLEON(m->prevsel, m) && - !client_is_unmanaged(m->prevsel)) { + } else if (m->prevsel && ISSCROLLTILED(m->prevsel) && + VISIBLEON(m->prevsel, m) && !client_is_unmanaged(m->prevsel)) { root_client = m->prevsel; } else { root_client = center_tiled_select(m); @@ -251,11 +286,19 @@ void vertical_scroller(Monitor *m) { target_geom.width = m->w.width - 2 * cur_gappoh; target_geom.height = max_client_height * c->scroller_proportion; target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2; + vertical_scroll_adjust_fullandmax(tempClients[focus_client_index], + &target_geom); - if (need_scroller) { + if (tempClients[focus_client_index]->isfullscreen) { + target_geom.y = m->m.y; + resize(tempClients[focus_client_index], target_geom, 0); + } else if (tempClients[focus_client_index]->ismaximizescreen) { + target_geom.y = m->w.y + cur_gappov; + resize(tempClients[focus_client_index], target_geom, 0); + } else if (need_scroller) { if (scroller_focus_center || ((!m->prevsel || - (ISTILED(m->prevsel) && + (ISSCROLLTILED(m->prevsel) && (m->prevsel->scroller_proportion * max_client_height) + (root_client->scroller_proportion * max_client_height) > m->w.height - 2 * scroller_structs - cur_gappiv)) && @@ -278,14 +321,17 @@ void vertical_scroller(Monitor *m) { for (i = 1; i <= focus_client_index; i++) { c = tempClients[focus_client_index - i]; target_geom.height = max_client_height * c->scroller_proportion; + vertical_scroll_adjust_fullandmax(c, &target_geom); target_geom.y = tempClients[focus_client_index - i + 1]->geom.y - cur_gappiv - target_geom.height; + resize(c, target_geom, 0); } for (i = 1; i < n - focus_client_index; i++) { c = tempClients[focus_client_index + i]; target_geom.height = max_client_height * c->scroller_proportion; + vertical_scroll_adjust_fullandmax(c, &target_geom); target_geom.y = tempClients[focus_client_index + i - 1]->geom.y + cur_gappiv + tempClients[focus_client_index + i - 1]->geom.height; diff --git a/src/mango.c b/src/mango.c index 65202f32..0e3509d2 100644 --- a/src/mango.c +++ b/src/mango.c @@ -104,6 +104,9 @@ #define ISTILED(A) \ (A && !(A)->isfloating && !(A)->isminied && !(A)->iskilling && \ !(A)->ismaximizescreen && !(A)->isfullscreen && !(A)->isunglobal) +#define ISSCROLLTILED(A) \ + (A && !(A)->isfloating && !(A)->isminied && !(A)->iskilling && \ + !(A)->isunglobal) #define VISIBLEON(C, M) \ ((C) && (M) && (C)->mon == (M) && ((C)->tags & (M)->tagset[(M)->seltags])) #define LENGTH(X) (sizeof X / sizeof X[0]) @@ -481,6 +484,7 @@ struct Monitor { int asleep; unsigned int visible_clients; unsigned int visible_tiling_clients; + unsigned int visible_scroll_tiling_clients; bool has_visible_fullscreen_client; struct wlr_scene_optimized_blur *blur; char last_surface_ws_name[256]; @@ -735,6 +739,7 @@ static void refresh_monitors_workspaces_status(Monitor *m); static void init_client_properties(Client *c); static float *get_border_color(Client *c); static void request_fresh_all_monitors(void); +static void clear_fullscreen_and_maximized_state(Monitor *m); #include "data/static_keymap.h" #include "dispatch/bind_declare.h" @@ -932,8 +937,26 @@ void applybounds(Client *c, struct wlr_box *bbox) { c->geom.y = bbox->y; } +void clear_fullscreen_and_maximized_state(Monitor *m) { + Client *fc = NULL; + wl_list_for_each(fc, &clients, link) { + if (fc && VISIBLEON(fc, m) && ISFULLSCREEN(fc)) { + clear_fullscreen_flag(fc); + } + } +} + /*清除全屏标志,还原全屏时清0的border*/ void clear_fullscreen_flag(Client *c) { + + if ((c->mon->pertag->ltidxs[get_tags_first_tag_num(c->tags)]->id == + SCROLLER || + c->mon->pertag->ltidxs[get_tags_first_tag_num(c->tags)]->id == + VERTICAL_SCROLLER) && + !c->isfloating) { + return; + } + if (c->isfullscreen) { setfullscreen(c, false); } @@ -3160,7 +3183,6 @@ void focusclient(Client *c, int lift) { if (c && selmon->prevsel && (selmon->prevsel->tags & selmon->tagset[selmon->seltags]) && (c->tags & selmon->tagset[selmon->seltags]) && !c->isfloating && - !c->isfullscreen && !c->ismaximizescreen && is_scroller_layout(selmon)) { arrange(selmon, false); } @@ -3716,9 +3738,9 @@ mapnotify(struct wl_listener *listener, void *data) { // tile at the top wl_list_insert(&clients, &c->link); // 新窗口是master,头部入栈 else if (selmon && is_scroller_layout(selmon) && - selmon->visible_tiling_clients > 0) { + selmon->visible_scroll_tiling_clients > 0) { - if (selmon->sel && ISTILED(selmon->sel) && + if (selmon->sel && ISSCROLLTILED(selmon->sel) && VISIBLEON(selmon->sel, selmon)) { at_client = selmon->sel; } else { @@ -4550,7 +4572,8 @@ void setmaximizescreen(Client *c, int maximizescreen) { maximizescreen_box.width = c->mon->w.width - 2 * gappoh; maximizescreen_box.height = c->mon->w.height - 2 * gappov; wlr_scene_node_raise_to_top(&c->scene->node); // 将视图提升到顶层 - resize(c, maximizescreen_box, 0); + if (!is_scroller_layout(c->mon) || c->isfloating) + resize(c, maximizescreen_box, 0); c->ismaximizescreen = 1; } else { c->bw = c->isnoborder ? 0 : borderpx; @@ -4559,9 +4582,8 @@ void setmaximizescreen(Client *c, int maximizescreen) { setfloating(c, 1); } - wlr_scene_node_reparent(&c->scene->node, layers[maximizescreen ? LyrTile - : c->isfloating ? LyrTop - : LyrTile]); + wlr_scene_node_reparent(&c->scene->node, + layers[c->isfloating ? LyrTop : LyrTile]); if (!c->ismaximizescreen) { set_size_per(c->mon, c); } @@ -4607,7 +4629,8 @@ void setfullscreen(Client *c, int fullscreen) // 用自定义全屏代理自带 c->bw = 0; wlr_scene_node_raise_to_top(&c->scene->node); // 将视图提升到顶层 - resize(c, c->mon->m, 1); + if (!is_scroller_layout(c->mon) || c->isfloating) + resize(c, c->mon->m, 1); c->isfullscreen = 1; } else { c->bw = c->isnoborder ? 0 : borderpx; @@ -5182,7 +5205,11 @@ unsigned int want_restore_fullscreen(Client *target_client) { Client *c = NULL; wl_list_for_each(c, &clients, link) { if (c && c != target_client && c->tags == target_client->tags && - c == selmon->sel) { + c == selmon->sel && + c->mon->pertag->ltidxs[get_tags_first_tag_num(c->tags)]->id != + SCROLLER && + c->mon->pertag->ltidxs[get_tags_first_tag_num(c->tags)]->id != + VERTICAL_SCROLLER) { return 0; } } From a4f851b81a72b205dc926e982d502d6c305160db Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 16 Nov 2025 07:19:29 +0800 Subject: [PATCH 28/42] fix: fullscreen and maximize window overflow screen --- src/animation/client.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/animation/client.h b/src/animation/client.h index a5236f04..c4a21e72 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -463,7 +463,7 @@ struct ivec2 clip_to_hide(Client *c, struct wlr_box *clip_box) { int offsetx = 0, offsety = 0, offsetw = 0, offseth = 0; struct ivec2 offset = {0, 0, 0, 0}; - if (!ISTILED(c) && !c->animation.tagining && !c->animation.tagouted && + if (!ISSCROLLTILED(c) && !c->animation.tagining && !c->animation.tagouted && !c->animation.tagouting) return offset; @@ -484,7 +484,7 @@ struct ivec2 clip_to_hide(Client *c, struct wlr_box *clip_box) { 需要主要border超出屏幕的时候不计算如偏差之内而是 要等窗口表面超出才开始计算偏差 */ - if (ISTILED(c) || c->animation.tagining || c->animation.tagouted || + if (ISSCROLLTILED(c) || c->animation.tagining || c->animation.tagouted || c->animation.tagouting) { if (left_out_offset > 0) { offsetx = GEZERO(left_out_offset - bw); @@ -512,7 +512,7 @@ struct ivec2 clip_to_hide(Client *c, struct wlr_box *clip_box) { offset.height = offseth; if ((clip_box->width + bw <= 0 || clip_box->height + bw <= 0) && - (ISTILED(c) || c->animation.tagouting || c->animation.tagining)) { + (ISSCROLLTILED(c) || c->animation.tagouting || c->animation.tagining)) { c->is_clip_to_hide = true; wlr_scene_node_set_enabled(&c->scene->node, false); } else if (c->is_clip_to_hide && VISIBLEON(c, c->mon)) { From 26d9a24e1874891e8c22cbefa38d602dbd9cae68 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 18 Nov 2025 19:21:19 +0800 Subject: [PATCH 29/42] opt: support hot reload cursor config --- src/config/parse_config.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 074660bf..d7b32234 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -3110,6 +3110,12 @@ void reapply_monitor_rules(void) { } } +void reapply_cursor_style(void) { + if (cursor_mgr) + wlr_xcursor_manager_destroy(cursor_mgr); + cursor_mgr = wlr_xcursor_manager_create(config.cursor_theme, cursor_size); +} + void reapply_border(void) { Client *c = NULL; @@ -3211,6 +3217,7 @@ void reset_option(void) { set_env(); run_exec(); + reapply_cursor_style(); reapply_border(); reapply_keyboard(); reapply_pointer(); From 7b5bf9c51072aedfd998e3ae1c9bec30a3fb4e80 Mon Sep 17 00:00:00 2001 From: Ching Pei Yang Date: Fri, 14 Nov 2025 16:28:27 +0100 Subject: [PATCH 30/42] feat: apply touch-input patch --- src/mango.c | 158 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) diff --git a/src/mango.c b/src/mango.c index 0e3509d2..8b1e9afe 100644 --- a/src/mango.c +++ b/src/mango.c @@ -70,6 +70,7 @@ #include #include #include +#include #include #include #include @@ -410,6 +411,12 @@ typedef struct { struct wl_listener destroy; } KeyboardGroup; +typedef struct TouchGroup { + struct wl_list link; + struct wlr_touch *touch; + Monitor *m; +} TouchGroup; + typedef struct { struct wlr_keyboard_shortcuts_inhibitor_v1 *inhibitor; struct wl_listener destroy; @@ -556,6 +563,7 @@ static void createpointerconstraint(struct wl_listener *listener, void *data); static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); static void commitpopup(struct wl_listener *listener, void *data); static void createpopup(struct wl_listener *listener, void *data); +static void createtouch(struct wlr_touch *touch); static void cursorframe(struct wl_listener *listener, void *data); static void cursorwarptohint(void); static void destroydecoration(struct wl_listener *listener, void *data); @@ -633,6 +641,11 @@ static void setsel(struct wl_listener *listener, void *data); static void setup(void); static void startdrag(struct wl_listener *listener, void *data); +static void touchdown(struct wl_listener *listener, void *data); +static void touchup(struct wl_listener *listener, void *data); +static void touchframe(struct wl_listener *listener, void *data); +static void touchmotion(struct wl_listener *listener, void *data); + static void unlocksession(struct wl_listener *listener, void *data); static void unmaplayersurfacenotify(struct wl_listener *listener, void *data); static void unmapnotify(struct wl_listener *listener, void *data); @@ -810,6 +823,7 @@ static struct wlr_output_layout *output_layout; static struct wlr_box sgeom; static struct wl_list mons; static Monitor *selmon; +static struct wl_list touches; static int enablegaps = 1; /* enables gaps, used by togglegaps */ static int axis_apply_time = 0; @@ -883,6 +897,10 @@ static struct wl_listener request_set_sel = {.notify = setsel}; static struct wl_listener request_set_cursor_shape = {.notify = setcursorshape}; static struct wl_listener request_start_drag = {.notify = requeststartdrag}; static struct wl_listener start_drag = {.notify = startdrag}; +static struct wl_listener touch_down = {.notify = touchdown}; +static struct wl_listener touch_frame = {.notify = touchframe}; +static struct wl_listener touch_motion = {.notify = touchmotion}; +static struct wl_listener touch_up = {.notify = touchup}; 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 = { @@ -2024,6 +2042,10 @@ void cleanuplisteners(void) { wl_list_remove(&request_set_cursor_shape.link); wl_list_remove(&request_start_drag.link); wl_list_remove(&start_drag.link); + wl_list_remove(&touch_down.link); + wl_list_remove(&touch_frame.link); + wl_list_remove(&touch_motion.link); + wl_list_remove(&touch_up.link); wl_list_remove(&new_session_lock.link); wl_list_remove(&tearing_new_object.link); wl_list_remove(&keyboard_shortcuts_inhibit_new_inhibitor.link); @@ -2962,6 +2984,14 @@ void createpopup(struct wl_listener *listener, void *data) { LISTEN_STATIC(&popup->base->surface->events.commit, commitpopup); } +void createtouch(struct wlr_touch *wlr_touch) { + TouchGroup *touch = ecalloc(1, sizeof(TouchGroup)); + + touch->touch = wlr_touch; + wl_list_insert(&touches, &touch->link); + wlr_cursor_attach_input_device(cursor, &wlr_touch->base); +} + void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint) { if (active_constraint == constraint) return; @@ -3285,6 +3315,9 @@ void inputdevice(struct wl_listener *listener, void *data) { case WLR_INPUT_DEVICE_SWITCH: createswitch(wlr_switch_from_input_device(device)); break; + case WLR_INPUT_DEVICE_TOUCH: + createtouch(wlr_touch_from_input_device(device)); + break; default: /* TODO handle other input device types */ break; @@ -3298,6 +3331,8 @@ void inputdevice(struct wl_listener *listener, void *data) { caps = WL_SEAT_CAPABILITY_POINTER; if (!wl_list_empty(&kb_group->wlr_group->devices)) caps |= WL_SEAT_CAPABILITY_KEYBOARD; + if (!wl_list_empty(&touches)) + caps |= WL_SEAT_CAPABILITY_TOUCH; wlr_seat_set_capabilities(seat, caps); } @@ -5065,6 +5100,13 @@ void setup(void) { wl_signal_add(&cursor->events.axis, &cursor_axis); wl_signal_add(&cursor->events.frame, &cursor_frame); + wl_list_init(&touches); + + wl_signal_add(&cursor->events.touch_down, &touch_down); + wl_signal_add(&cursor->events.touch_frame, &touch_frame); + wl_signal_add(&cursor->events.touch_motion, &touch_motion); + wl_signal_add(&cursor->events.touch_up, &touch_up); + // 这两句代码会造成obs窗口里的鼠标光标消失,不知道注释有什么影响 cursor_shape_mgr = wlr_cursor_shape_manager_v1_create(dpy, 1); wl_signal_add(&cursor_shape_mgr->events.request_set_shape, @@ -5175,6 +5217,122 @@ void startdrag(struct wl_listener *listener, void *data) { LISTEN_STATIC(&drag->icon->events.destroy, destroydragicon); } +void touchdown(struct wl_listener *listener, void *data) { + struct wlr_touch_down_event *event = data; + double lx, ly; + double sx, sy; + struct wlr_surface *surface; + Client *c = NULL; + uint32_t serial = 0; + Monitor *m; + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + + // Map the input to the appropriate output, to ensure that rotation is + // handled. + wl_list_for_each(m, &mons, link) { + if (m == NULL || m->wlr_output == NULL) { + continue; + } + if (event->touch->output_name != NULL && + 0 != strcmp(event->touch->output_name, m->wlr_output->name)) { + continue; + } + + wlr_cursor_map_input_to_output(cursor, &event->touch->base, + m->wlr_output); + } + + wlr_cursor_absolute_to_layout_coords(cursor, &event->touch->base, event->x, + event->y, &lx, &ly); + + /* Find the client under the pointer and send the event along. */ + xytonode(lx, ly, &surface, &c, NULL, &sx, &sy); + if (sloppyfocus) + focusclient(c, 0); + + if (surface != NULL) { + serial = wlr_seat_touch_notify_down(seat, surface, event->time_msec, + event->touch_id, sx, sy); + } + + if (serial && wlr_seat_touch_num_points(seat) == 1) { + /* Emulate a mouse click if the touch event wasn't handled */ + struct wlr_pointer_button_event *button_event = data; + struct wlr_pointer_motion_absolute_event *motion_event = data; + double dx, dy; + + wlr_cursor_absolute_to_layout_coords( + cursor, &motion_event->pointer->base, motion_event->x, + motion_event->y, &lx, &ly); + wlr_cursor_warp_closest(cursor, &motion_event->pointer->base, lx, ly); + dx = lx - cursor->x; + dy = ly - cursor->y; + motionnotify(motion_event->time_msec, &motion_event->pointer->base, dx, + dy, dx, dy); + + button_event->button = BTN_LEFT; + button_event->state = WL_POINTER_BUTTON_STATE_PRESSED; + buttonpress(listener, button_event); + } +} + +void touchup(struct wl_listener *listener, void *data) { + struct wlr_touch_up_event *event = data; + + if (!wlr_seat_touch_get_point(seat, event->touch_id)) { + return; + } + + if (wlr_seat_touch_num_points(seat) == 1) { + struct wlr_pointer_button_event *button_event = data; + + button_event->button = BTN_LEFT; + button_event->state = WL_POINTER_BUTTON_STATE_RELEASED; + buttonpress(listener, button_event); + } + + wlr_seat_touch_notify_up(seat, event->time_msec, event->touch_id); + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); +} + +void touchframe(struct wl_listener *listener, void *data) { + wlr_seat_touch_notify_frame(seat); + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); +} + +void touchmotion(struct wl_listener *listener, void *data) { + struct wlr_touch_motion_event *event = data; + double lx, ly; + double sx, sy; + struct wlr_surface *surface; + Client *c = NULL; + + if (!wlr_seat_touch_get_point(seat, event->touch_id)) { + return; + } + + wlr_cursor_absolute_to_layout_coords(cursor, &event->touch->base, event->x, + event->y, &lx, &ly); + xytonode(lx, ly, &surface, &c, NULL, &sx, &sy); + + if (c != NULL && surface != NULL) { + if (sloppyfocus) + focusclient(c, 0); + wlr_seat_touch_point_focus(seat, surface, event->time_msec, + event->touch_id, sx, sy); + } else { + if (sloppyfocus) + focusclient(NULL, 0); + wlr_seat_touch_point_clear_focus(seat, event->time_msec, + event->touch_id); + } + wlr_seat_touch_notify_motion(seat, event->time_msec, event->touch_id, sx, + sy); + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); +} + void tag_client(const Arg *arg, Client *target_client) { Client *fc = NULL; if (target_client && arg->ui & TAGMASK) { From dff4cdfcbb8e280d3cf26866ef73e4df73f52b66 Mon Sep 17 00:00:00 2001 From: Ching Pei Yang Date: Fri, 14 Nov 2025 16:58:27 +0100 Subject: [PATCH 31/42] fix: reuse client in touch motion event --- src/mango.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/mango.c b/src/mango.c index 8b1e9afe..4e41ff32 100644 --- a/src/mango.c +++ b/src/mango.c @@ -5307,16 +5307,20 @@ void touchmotion(struct wl_listener *listener, void *data) { double sx, sy; struct wlr_surface *surface; Client *c = NULL; + struct wlr_touch_point *p = wlr_seat_touch_get_point(seat, event->touch_id); - if (!wlr_seat_touch_get_point(seat, event->touch_id)) { + if (!p) { return; } wlr_cursor_absolute_to_layout_coords(cursor, &event->touch->base, event->x, event->y, &lx, &ly); - xytonode(lx, ly, &surface, &c, NULL, &sx, &sy); + surface = p->surface; + c = surface ? get_client_from_surface(surface) : NULL; + sx = lx - c->current.x; + sy = ly - c->current.y; - if (c != NULL && surface != NULL) { + if (c != NULL) { if (sloppyfocus) focusclient(c, 0); wlr_seat_touch_point_focus(seat, surface, event->time_msec, From 75aca6c5f34df15f10c7b5a7b1c567a12203247f Mon Sep 17 00:00:00 2001 From: werapi Date: Sun, 30 Nov 2025 07:10:46 +0100 Subject: [PATCH 32/42] fix: prevent client focus loss on touch interaction with layer surfaces --- src/mango.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mango.c b/src/mango.c index 4e41ff32..106ceaac 100644 --- a/src/mango.c +++ b/src/mango.c @@ -5248,7 +5248,7 @@ void touchdown(struct wl_listener *listener, void *data) { /* Find the client under the pointer and send the event along. */ xytonode(lx, ly, &surface, &c, NULL, &sx, &sy); - if (sloppyfocus) + if (sloppyfocus && c) focusclient(c, 0); if (surface != NULL) { From 55a29750e0bcb00c3d353b82dc849da8710ee427 Mon Sep 17 00:00:00 2001 From: werapi Date: Sun, 30 Nov 2025 06:51:05 +0100 Subject: [PATCH 33/42] fix: correct emulation of cursor pointer from unhandled touch events --- src/mango.c | 104 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 67 insertions(+), 37 deletions(-) diff --git a/src/mango.c b/src/mango.c index 106ceaac..dbc2768c 100644 --- a/src/mango.c +++ b/src/mango.c @@ -825,6 +825,9 @@ static struct wl_list mons; static Monitor *selmon; static struct wl_list touches; +static bool emulating_pointer_from_touch = false; +static int32_t emulated_pointer_touch_id; + static int enablegaps = 1; /* enables gaps, used by togglegaps */ static int axis_apply_time = 0; static int axis_apply_dir = 0; @@ -5221,9 +5224,9 @@ void touchdown(struct wl_listener *listener, void *data) { struct wlr_touch_down_event *event = data; double lx, ly; double sx, sy; + double dx, dy; struct wlr_surface *surface; Client *c = NULL; - uint32_t serial = 0; Monitor *m; wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); @@ -5248,48 +5251,54 @@ void touchdown(struct wl_listener *listener, void *data) { /* Find the client under the pointer and send the event along. */ xytonode(lx, ly, &surface, &c, NULL, &sx, &sy); - if (sloppyfocus && c) - focusclient(c, 0); + if (surface != NULL && wlr_surface_accepts_touch(surface, seat)) { + if (sloppyfocus && c) + focusclient(c, 0); - if (surface != NULL) { - serial = wlr_seat_touch_notify_down(seat, surface, event->time_msec, - event->touch_id, sx, sy); + wlr_seat_touch_notify_down(seat, surface, event->time_msec, + event->touch_id, sx, sy); + emulating_pointer_from_touch = false; + return; } - if (serial && wlr_seat_touch_num_points(seat) == 1) { - /* Emulate a mouse click if the touch event wasn't handled */ - struct wlr_pointer_button_event *button_event = data; - struct wlr_pointer_motion_absolute_event *motion_event = data; - double dx, dy; + /* Emulate a mouse click if the touch event wasn't handled */ + if (!emulating_pointer_from_touch) { + emulating_pointer_from_touch = true; + emulated_pointer_touch_id = event->touch_id; - wlr_cursor_absolute_to_layout_coords( - cursor, &motion_event->pointer->base, motion_event->x, - motion_event->y, &lx, &ly); - wlr_cursor_warp_closest(cursor, &motion_event->pointer->base, lx, ly); + wlr_cursor_warp_closest(cursor, &event->touch->base, lx, ly); dx = lx - cursor->x; dy = ly - cursor->y; - motionnotify(motion_event->time_msec, &motion_event->pointer->base, dx, - dy, dx, dy); + motionnotify(event->time_msec, &event->touch->base, dx, dy, dx, dy); - button_event->button = BTN_LEFT; - button_event->state = WL_POINTER_BUTTON_STATE_PRESSED; - buttonpress(listener, button_event); + struct wlr_pointer_button_event button_event = { + .pointer = (struct wlr_pointer *)event->touch, + .time_msec = event->time_msec, + .button = BTN_LEFT, + .state = WL_POINTER_BUTTON_STATE_PRESSED}; + buttonpress(listener, &button_event); } } void touchup(struct wl_listener *listener, void *data) { struct wlr_touch_up_event *event = data; - if (!wlr_seat_touch_get_point(seat, event->touch_id)) { + if (emulating_pointer_from_touch) { + if (emulated_pointer_touch_id == event->touch_id) { + struct wlr_pointer_button_event button_event = { + .pointer = (struct wlr_pointer *)event->touch, + .time_msec = event->time_msec, + .button = BTN_LEFT, + .state = WL_POINTER_BUTTON_STATE_RELEASED}; + buttonpress(listener, &button_event); + + emulating_pointer_from_touch = false; + } return; } - if (wlr_seat_touch_num_points(seat) == 1) { - struct wlr_pointer_button_event *button_event = data; - - button_event->button = BTN_LEFT; - button_event->state = WL_POINTER_BUTTON_STATE_RELEASED; - buttonpress(listener, button_event); + if (!wlr_seat_touch_get_point(seat, event->touch_id)) { + return; } wlr_seat_touch_notify_up(seat, event->time_msec, event->touch_id); @@ -5297,7 +5306,11 @@ void touchup(struct wl_listener *listener, void *data) { } void touchframe(struct wl_listener *listener, void *data) { - wlr_seat_touch_notify_frame(seat); + if (emulating_pointer_from_touch) { + wlr_seat_pointer_notify_frame(seat); + } else { + wlr_seat_touch_notify_frame(seat); + } wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); } @@ -5305,9 +5318,23 @@ void touchmotion(struct wl_listener *listener, void *data) { struct wlr_touch_motion_event *event = data; double lx, ly; double sx, sy; + double dx, dy; struct wlr_surface *surface; Client *c = NULL; - struct wlr_touch_point *p = wlr_seat_touch_get_point(seat, event->touch_id); + struct wlr_touch_point *p = NULL; + + if (emulating_pointer_from_touch) { + if (emulated_pointer_touch_id == event->touch_id) { + wlr_cursor_absolute_to_layout_coords(cursor, &event->touch->base, + event->x, event->y, &lx, &ly); + dx = lx - cursor->x; + dy = ly - cursor->y; + motionnotify(event->time_msec, &event->touch->base, dx, dy, dx, dy); + } + return; + } + + p = wlr_seat_touch_get_point(seat, event->touch_id); if (!p) { return; @@ -5317,20 +5344,23 @@ void touchmotion(struct wl_listener *listener, void *data) { event->y, &lx, &ly); surface = p->surface; c = surface ? get_client_from_surface(surface) : NULL; - sx = lx - c->current.x; - sy = ly - c->current.y; - if (c != NULL) { - if (sloppyfocus) - focusclient(c, 0); - wlr_seat_touch_point_focus(seat, surface, event->time_msec, - event->touch_id, sx, sy); - } else { + if (!c) { if (sloppyfocus) focusclient(NULL, 0); wlr_seat_touch_point_clear_focus(seat, event->time_msec, event->touch_id); + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + return; } + + sx = lx - c->current.x; + sy = ly - c->current.y; + + if (sloppyfocus) + focusclient(c, 0); + wlr_seat_touch_point_focus(seat, surface, event->time_msec, event->touch_id, + sx, sy); wlr_seat_touch_notify_motion(seat, event->time_msec, event->touch_id, sx, sy); From ad55132a56249492c63db7fc1644603d372375e4 Mon Sep 17 00:00:00 2001 From: werapi Date: Mon, 1 Dec 2025 14:45:18 +0100 Subject: [PATCH 34/42] opt: hide cursor on touchdown --- src/mango.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/mango.c b/src/mango.c index dbc2768c..33dc4c3a 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1877,7 +1877,11 @@ buttonpress(struct wl_listener *listener, void *data) { struct wlr_surface *old_pointer_focus_surface = seat->pointer_state.focused_surface; - handlecursoractivity(); + if (!event->pointer || + event->pointer->base.type != WLR_INPUT_DEVICE_TOUCH) { + handlecursoractivity(); + } + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); if (check_trackpad_disabled(event->pointer)) { @@ -3968,7 +3972,9 @@ void motionnotify(unsigned int time, struct wlr_input_device *device, double dx, } wlr_cursor_move(cursor, device, dx, dy); - handlecursoractivity(); + if (!device || device->type != WLR_INPUT_DEVICE_TOUCH) { + handlecursoractivity(); + } wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); /* Update selmon (even while dragging a window) */ @@ -5231,6 +5237,10 @@ void touchdown(struct wl_listener *listener, void *data) { wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + if (!cursor_hidden) { + hidecursor(NULL); + } + // Map the input to the appropriate output, to ensure that rotation is // handled. wl_list_for_each(m, &mons, link) { From 455a85723d0fd04c745f3ba65960e0112c22d5ec Mon Sep 17 00:00:00 2001 From: werapi Date: Mon, 1 Dec 2025 19:35:29 +0100 Subject: [PATCH 35/42] fix: prevent crash when touch motion occurs over layer surfaces --- src/mango.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/mango.c b/src/mango.c index 33dc4c3a..390242b4 100644 --- a/src/mango.c +++ b/src/mango.c @@ -5364,11 +5364,17 @@ void touchmotion(struct wl_listener *listener, void *data) { return; } - sx = lx - c->current.x; - sy = ly - c->current.y; + if (c->type == XDGShell || c->type == X11) { + sx = lx - c->current.x; + sy = ly - c->current.y; + if (sloppyfocus) + focusclient(c, 0); + } else { + LayerSurface *l = (LayerSurface *)c; + sx = lx - l->current.x; + sy = ly - l->current.y; + } - if (sloppyfocus) - focusclient(c, 0); wlr_seat_touch_point_focus(seat, surface, event->time_msec, event->touch_id, sx, sy); wlr_seat_touch_notify_motion(seat, event->time_msec, event->touch_id, sx, From 193e0f3b31acc8ae84dfd39f3681edfc58cfb716 Mon Sep 17 00:00:00 2001 From: werapi Date: Sat, 20 Dec 2025 20:15:07 +0100 Subject: [PATCH 36/42] fix: use scene node coordinates for touch motion events --- src/mango.c | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/src/mango.c b/src/mango.c index 390242b4..c42ca25b 100644 --- a/src/mango.c +++ b/src/mango.c @@ -5329,8 +5329,10 @@ void touchmotion(struct wl_listener *listener, void *data) { double lx, ly; double sx, sy; double dx, dy; + int node_x, node_y; struct wlr_surface *surface; Client *c = NULL; + struct wlr_scene_tree *tree; struct wlr_touch_point *p = NULL; if (emulating_pointer_from_touch) { @@ -5353,34 +5355,29 @@ void touchmotion(struct wl_listener *listener, void *data) { wlr_cursor_absolute_to_layout_coords(cursor, &event->touch->base, event->x, event->y, &lx, &ly); surface = p->surface; - c = surface ? get_client_from_surface(surface) : NULL; + if (surface && surface->data) { + tree = surface->data; + wlr_scene_node_coords(&tree->node, &node_x, &node_y); + sx = lx - node_x; + sy = ly - node_y; - if (!c) { + toplevel_from_wlr_surface(surface, &c, NULL); + if (sloppyfocus && c) + focusclient(c, 0); + + wlr_seat_touch_point_focus(seat, surface, event->time_msec, + event->touch_id, sx, sy); + wlr_seat_touch_notify_motion(seat, event->time_msec, event->touch_id, + sx, sy); + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + } else { if (sloppyfocus) focusclient(NULL, 0); wlr_seat_touch_point_clear_focus(seat, event->time_msec, event->touch_id); wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); - return; } - - if (c->type == XDGShell || c->type == X11) { - sx = lx - c->current.x; - sy = ly - c->current.y; - if (sloppyfocus) - focusclient(c, 0); - } else { - LayerSurface *l = (LayerSurface *)c; - sx = lx - l->current.x; - sy = ly - l->current.y; - } - - wlr_seat_touch_point_focus(seat, surface, event->time_msec, event->touch_id, - sx, sy); - wlr_seat_touch_notify_motion(seat, event->time_msec, event->touch_id, sx, - sy); - - wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); } void tag_client(const Arg *arg, Client *target_client) { From 6283bc1a3df425121650b707709ef0f30f7870ff Mon Sep 17 00:00:00 2001 From: werapi Date: Wed, 7 Jan 2026 10:51:18 +0100 Subject: [PATCH 37/42] opt: remove sloppyfocus guards from touch focus --- src/mango.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/mango.c b/src/mango.c index c42ca25b..a19c2a95 100644 --- a/src/mango.c +++ b/src/mango.c @@ -5262,7 +5262,7 @@ void touchdown(struct wl_listener *listener, void *data) { /* Find the client under the pointer and send the event along. */ xytonode(lx, ly, &surface, &c, NULL, &sx, &sy); if (surface != NULL && wlr_surface_accepts_touch(surface, seat)) { - if (sloppyfocus && c) + if (c) focusclient(c, 0); wlr_seat_touch_notify_down(seat, surface, event->time_msec, @@ -5362,7 +5362,7 @@ void touchmotion(struct wl_listener *listener, void *data) { sy = ly - node_y; toplevel_from_wlr_surface(surface, &c, NULL); - if (sloppyfocus && c) + if (c) focusclient(c, 0); wlr_seat_touch_point_focus(seat, surface, event->time_msec, @@ -5372,8 +5372,7 @@ void touchmotion(struct wl_listener *listener, void *data) { wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); } else { - if (sloppyfocus) - focusclient(NULL, 0); + focusclient(NULL, 0); wlr_seat_touch_point_clear_focus(seat, event->time_msec, event->touch_id); wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); From e9b93c7c7d90a171c27fa3f25b0f4e32c1266e0e Mon Sep 17 00:00:00 2001 From: werapi Date: Thu, 19 Mar 2026 13:15:20 +0100 Subject: [PATCH 38/42] opt: handle touch_cancel event --- src/mango.c | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/mango.c b/src/mango.c index 22ef2c2a..41015510 100644 --- a/src/mango.c +++ b/src/mango.c @@ -693,6 +693,7 @@ static void touchdown(struct wl_listener *listener, void *data); static void touchup(struct wl_listener *listener, void *data); static void touchframe(struct wl_listener *listener, void *data); static void touchmotion(struct wl_listener *listener, void *data); +static void touchcancel(struct wl_listener *listener, void *data); static void unlocksession(struct wl_listener *listener, void *data); static void unmaplayersurfacenotify(struct wl_listener *listener, void *data); @@ -995,6 +996,7 @@ static struct wl_listener touch_down = {.notify = touchdown}; static struct wl_listener touch_frame = {.notify = touchframe}; static struct wl_listener touch_motion = {.notify = touchmotion}; static struct wl_listener touch_up = {.notify = touchup}; +static struct wl_listener touch_cancel = {.notify = touchcancel}; 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 = { @@ -2254,6 +2256,7 @@ void cleanuplisteners(void) { wl_list_remove(&touch_frame.link); wl_list_remove(&touch_motion.link); wl_list_remove(&touch_up.link); + wl_list_remove(&touch_cancel.link); wl_list_remove(&new_session_lock.link); wl_list_remove(&tearing_new_object.link); wl_list_remove(&keyboard_shortcuts_inhibit_new_inhibitor.link); @@ -5728,6 +5731,7 @@ void setup(void) { wl_signal_add(&cursor->events.touch_frame, &touch_frame); wl_signal_add(&cursor->events.touch_motion, &touch_motion); wl_signal_add(&cursor->events.touch_up, &touch_up); + wl_signal_add(&cursor->events.touch_cancel, &touch_cancel); // 这两句代码会造成obs窗口里的鼠标光标消失,不知道注释有什么影响 cursor_shape_mgr = wlr_cursor_shape_manager_v1_create(dpy, 1); @@ -5996,6 +6000,43 @@ void touchmotion(struct wl_listener *listener, void *data) { } } +void touchcancel(struct wl_listener *listener, void *data) { + struct wlr_touch_cancel_event *event = data; + struct wlr_touch_point *p = NULL; + struct wl_client *client = NULL; + struct wlr_seat_client *seat_client = NULL; + + if (emulating_pointer_from_touch) { + if (emulated_pointer_touch_id == event->touch_id) { + struct wlr_pointer_button_event button_event = { + .pointer = (struct wlr_pointer *)event->touch, + .time_msec = event->time_msec, + .button = BTN_LEFT, + .state = WL_POINTER_BUTTON_STATE_RELEASED}; + buttonpress(listener, &button_event); + + emulating_pointer_from_touch = false; + } + return; + } + + p = wlr_seat_touch_get_point(seat, event->touch_id); + + if (!p) { + return; + } + + if (p->surface) { + client = wl_resource_get_client(p->surface->resource); + seat_client = wlr_seat_client_for_wl_client(seat, client); + if (seat_client != NULL) { + wlr_seat_touch_notify_cancel(seat, seat_client); + } + } + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); +} + void tag_client(const Arg *arg, Client *target_client) { Client *fc = NULL; if (target_client && arg->ui & TAGMASK) { From 545c452136786eb931a7a5bac48d7890c875ed65 Mon Sep 17 00:00:00 2001 From: werapi Date: Wed, 25 Mar 2026 22:29:52 +0100 Subject: [PATCH 39/42] fix: drop events for cancelled touch points --- src/mango.c | 62 +++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/src/mango.c b/src/mango.c index 41fe45e5..576ee6eb 100644 --- a/src/mango.c +++ b/src/mango.c @@ -452,9 +452,15 @@ typedef struct { struct wl_listener destroy; } KeyboardGroup; +typedef struct { + struct wl_list link; + int32_t touch_id; +} TouchPoint; + typedef struct TouchGroup { struct wl_list link; struct wlr_touch *touch; + struct wl_list touch_points; Monitor *m; } TouchGroup; @@ -891,7 +897,7 @@ static struct wlr_output_layout *output_layout; static struct wlr_box sgeom; static struct wl_list mons; static Monitor *selmon; -static struct wl_list touches; +static struct wl_list touch_groups; static bool emulating_pointer_from_touch = false; static int32_t emulated_pointer_touch_id; @@ -3351,7 +3357,9 @@ void createtouch(struct wlr_touch *wlr_touch) { TouchGroup *touch = ecalloc(1, sizeof(TouchGroup)); touch->touch = wlr_touch; - wl_list_insert(&touches, &touch->link); + wl_list_init(&touch->touch_points); + wl_list_insert(&touch_groups, &touch->link); + wlr_touch->data = touch; wlr_cursor_attach_input_device(cursor, &wlr_touch->base); } @@ -3731,7 +3739,7 @@ void inputdevice(struct wl_listener *listener, void *data) { caps = WL_SEAT_CAPABILITY_POINTER; if (!wl_list_empty(&kb_group->wlr_group->devices)) caps |= WL_SEAT_CAPABILITY_KEYBOARD; - if (!wl_list_empty(&touches)) + if (!wl_list_empty(&touch_groups)) caps |= WL_SEAT_CAPABILITY_TOUCH; wlr_seat_set_capabilities(seat, caps); } @@ -5747,7 +5755,7 @@ void setup(void) { wl_signal_add(&cursor->events.axis, &cursor_axis); wl_signal_add(&cursor->events.frame, &cursor_frame); - wl_list_init(&touches); + wl_list_init(&touch_groups); wl_signal_add(&cursor->events.touch_down, &touch_down); wl_signal_add(&cursor->events.touch_frame, &touch_frame); @@ -5871,6 +5879,8 @@ void startdrag(struct wl_listener *listener, void *data) { void touchdown(struct wl_listener *listener, void *data) { struct wlr_touch_down_event *event = data; + TouchGroup *tg = event->touch->data; + TouchPoint *t = ecalloc(1, sizeof(TouchPoint)); double lx, ly; double sx, sy; double dx, dy; @@ -5878,6 +5888,9 @@ void touchdown(struct wl_listener *listener, void *data) { Client *c = NULL; Monitor *m; + t->touch_id = event->touch_id; + wl_list_insert(&tg->touch_points, &t->link); + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); if (!cursor_hidden) { @@ -5935,6 +5948,20 @@ void touchdown(struct wl_listener *listener, void *data) { void touchup(struct wl_listener *listener, void *data) { struct wlr_touch_up_event *event = data; + TouchGroup *tg = event->touch->data; + TouchPoint *t = NULL; + TouchPoint *t_iter; + + wl_list_for_each(t_iter, &tg->touch_points, link) { + if (t_iter->touch_id == event->touch_id) { + t = t_iter; + break; + } + } + if (!t) // invalid or cancelled + return; + wl_list_remove(&t->link); + free(t); if (emulating_pointer_from_touch) { if (emulated_pointer_touch_id == event->touch_id) { @@ -5969,6 +5996,9 @@ void touchframe(struct wl_listener *listener, void *data) { void touchmotion(struct wl_listener *listener, void *data) { struct wlr_touch_motion_event *event = data; + TouchGroup *tg = event->touch->data; + TouchPoint *t = NULL; + TouchPoint *t_iter; double lx, ly; double sx, sy; double dx, dy; @@ -5978,6 +6008,15 @@ void touchmotion(struct wl_listener *listener, void *data) { struct wlr_scene_tree *tree; struct wlr_touch_point *p = NULL; + wl_list_for_each(t_iter, &tg->touch_points, link) { + if (t_iter->touch_id == event->touch_id) { + t = t_iter; + break; + } + } + if (!t) // invalid or cancelled + return; + if (emulating_pointer_from_touch) { if (emulated_pointer_touch_id == event->touch_id) { wlr_cursor_absolute_to_layout_coords(cursor, &event->touch->base, @@ -6024,10 +6063,25 @@ void touchmotion(struct wl_listener *listener, void *data) { void touchcancel(struct wl_listener *listener, void *data) { struct wlr_touch_cancel_event *event = data; + TouchGroup *tg = event->touch->data; + TouchPoint *t = NULL; + TouchPoint *t_iter; struct wlr_touch_point *p = NULL; struct wl_client *client = NULL; struct wlr_seat_client *seat_client = NULL; + wl_list_for_each(t_iter, &tg->touch_points, link) { + if (t_iter->touch_id == event->touch_id) { + t = t_iter; + break; + } + } + if (!t) + return; + + wl_list_remove(&t->link); + free(t); + if (emulating_pointer_from_touch) { if (emulated_pointer_touch_id == event->touch_id) { struct wlr_pointer_button_event button_event = { From 9d7edec710aebe453ebd770217f16ec6f292b4dd Mon Sep 17 00:00:00 2001 From: werapi Date: Thu, 26 Mar 2026 19:30:16 +0100 Subject: [PATCH 40/42] feat: touchgesturebinds based on lisgd --- src/config/parse_config.h | 248 ++++++++++++++++++++++++++++++++++++++ src/dispatch/gesture.h | 237 ++++++++++++++++++++++++++++++++++++ src/mango.c | 94 ++++++++++++--- 3 files changed, 562 insertions(+), 17 deletions(-) create mode 100644 src/dispatch/gesture.h diff --git a/src/config/parse_config.h b/src/config/parse_config.h index fda401d9..0d1fc20d 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -158,6 +158,15 @@ typedef struct { Arg arg; } GestureBinding; +typedef struct { + uint32_t swipe; + uint32_t edge; + uint32_t distance; + uint32_t fingers_count; + int32_t (*func)(const Arg *); + Arg arg; +} TouchGestureBinding; + typedef struct { int32_t id; char *layout_name; @@ -282,6 +291,14 @@ typedef struct { double axis_scroll_factor; + double touch_distance_threshold; + double touch_degrees_leniency; + uint32_t touch_timeoutms; + double touch_edge_size_left; + double touch_edge_size_top; + double touch_edge_size_right; + double touch_edge_size_bottom; + int32_t blur; int32_t blur_layer; int32_t blur_optimized; @@ -344,6 +361,9 @@ typedef struct { GestureBinding *gesture_bindings; int32_t gesture_bindings_count; + TouchGestureBinding *touch_gesture_bindings; + int32_t touch_gesture_bindings_count; + ConfigEnv **env; int32_t env_count; @@ -532,6 +552,92 @@ int32_t parse_direction(const char *str) { } } +int32_t parse_touch_direction(const char *str) { + char lowerStr[11]; + int32_t i = 0; + while (str[i] && i < 10) { + lowerStr[i] = tolower(str[i]); + i++; + } + lowerStr[i] = '\0'; + + if (strcmp(lowerStr, "up") == 0) { + return TOUCH_SWIPE_UP; + } else if (strcmp(lowerStr, "down") == 0) { + return TOUCH_SWIPE_DOWN; + } else if (strcmp(lowerStr, "left") == 0) { + return TOUCH_SWIPE_LEFT; + } else if (strcmp(lowerStr, "right") == 0) { + return TOUCH_SWIPE_RIGHT; + } else if (strcmp(lowerStr, "up_left") == 0) { + return TOUCH_SWIPE_UP_LEFT; + } else if (strcmp(lowerStr, "up_right") == 0) { + return TOUCH_SWIPE_UP_RIGHT; + } else if (strcmp(lowerStr, "down_left") == 0) { + return TOUCH_SWIPE_DOWN_LEFT; + } else if (strcmp(lowerStr, "down_right") == 0) { + return TOUCH_SWIPE_DOWN_RIGHT; + } else { + return TOUCH_SWIPE_NONE; + } +} + +int32_t parse_touch_edge(const char *str) { + char lowerStr[13]; + int32_t i = 0; + while (str[i] && i < 12) { + lowerStr[i] = tolower(str[i]); + i++; + } + lowerStr[i] = '\0'; + + if (strcmp(lowerStr, "any") == 0) { + return EDGE_ANY; + } else if (strcmp(lowerStr, "none") == 0) { + return EDGE_NONE; + } else if (strcmp(lowerStr, "left") == 0) { + return EDGE_LEFT; + } else if (strcmp(lowerStr, "right") == 0) { + return EDGE_RIGHT; + } else if (strcmp(lowerStr, "top") == 0) { + return EDGE_TOP; + } else if (strcmp(lowerStr, "bottom") == 0) { + return EDGE_BOTTOM; + } else if (strcmp(lowerStr, "top_left") == 0) { + return CORNER_TOP_LEFT; + } else if (strcmp(lowerStr, "top_right") == 0) { + return CORNER_TOP_RIGHT; + } else if (strcmp(lowerStr, "bottom_left") == 0) { + return CORNER_BOTTOM_LEFT; + } else if (strcmp(lowerStr, "bottom_right") == 0) { + return CORNER_BOTTOM_RIGHT; + } else { + return EDGE_ANY; + } +} + +int32_t parse_distance(const char *str) { + char lowerStr[7]; + int32_t i = 0; + while (str[i] && i < 6) { + lowerStr[i] = tolower(str[i]); + i++; + } + lowerStr[i] = '\0'; + + if (strcmp(lowerStr, "any") == 0) { + return DISTANCE_ANY; + } else if (strcmp(lowerStr, "short") == 0) { + return DISTANCE_SHORT; + } else if (strcmp(lowerStr, "medium") == 0) { + return DISTANCE_MEDIUM; + } else if (strcmp(lowerStr, "long") == 0) { + return DISTANCE_LONG; + } else { + return DISTANCE_ANY; + } +} + int32_t parse_fold_state(const char *str) { // 将输入字符串转换为小写 char lowerStr[10]; @@ -1677,6 +1783,20 @@ 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, "touch_distance_threshold") == 0) { + config->touch_distance_threshold = atof(value); + } else if (strcmp(key, "touch_degrees_leniency") == 0) { + config->touch_degrees_leniency = atof(value); + } else if (strcmp(key, "touch_timeoutms") == 0) { + config->touch_timeoutms = atoi(value); + } else if (strcmp(key, "touch_edge_size_left") == 0) { + config->touch_edge_size_left = atof(value); + } else if (strcmp(key, "touch_edge_size_top") == 0) { + config->touch_edge_size_top = atof(value); + } else if (strcmp(key, "touch_edge_size_right") == 0) { + config->touch_edge_size_right = atof(value); + } else if (strcmp(key, "touch_edge_size_bottom") == 0) { + config->touch_edge_size_bottom = atof(value); } else if (strcmp(key, "gappih") == 0) { config->gappih = atoi(value); } else if (strcmp(key, "gappiv") == 0) { @@ -2699,6 +2819,91 @@ bool parse_option(Config *config, char *key, char *value) { config->gesture_bindings_count++; } + } else if (strncmp(key, "touchgesturebind", 16) == 0) { + config->touch_gesture_bindings = + realloc(config->touch_gesture_bindings, + (config->touch_gesture_bindings_count + 1) * + sizeof(TouchGestureBinding)); + if (!config->touch_gesture_bindings) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Failed to allocate " + "memory for axis touchgesturebind\n"); + return false; + } + + TouchGestureBinding *binding = + &config + ->touch_gesture_bindings[config->touch_gesture_bindings_count]; + memset(binding, 0, sizeof(TouchGestureBinding)); + + char swipe_str[256], edge_str[256], distance_str[256], + fingers_count_str[256], func_name[256], + arg_value[256] = "0\0", arg_value2[256] = "0\0", + arg_value3[256] = "0\0", arg_value4[256] = "0\0", + arg_value5[256] = "0\0"; + if (sscanf(value, + "%255[^,],%255[^,],%255[^,],%255[^,],%255[" + "^,],%255[^,],%255[^,],%255[^,],%255[^,],%255[^\n]", + swipe_str, edge_str, distance_str, fingers_count_str, + func_name, arg_value, arg_value2, arg_value3, arg_value4, + arg_value5) < 4) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid touchgesturebind " + "format: %s\n", + value); + return false; + } + + trim_whitespace(swipe_str); + trim_whitespace(edge_str); + trim_whitespace(distance_str); + trim_whitespace(fingers_count_str); + trim_whitespace(func_name); + trim_whitespace(arg_value); + trim_whitespace(arg_value2); + trim_whitespace(arg_value3); + trim_whitespace(arg_value4); + trim_whitespace(arg_value5); + + binding->swipe = parse_touch_direction(swipe_str); + binding->edge = parse_touch_edge(edge_str); + binding->distance = parse_distance(distance_str); + binding->fingers_count = atoi(fingers_count_str); + binding->arg.i = 0; + binding->arg.i2 = 0; + binding->arg.f = 0.0f; + binding->arg.f2 = 0.0f; + binding->arg.ui = 0; + binding->arg.ui2 = 0; + binding->arg.v = NULL; + binding->arg.v2 = NULL; + binding->arg.v3 = NULL; + binding->func = + parse_func_name(func_name, &binding->arg, arg_value, arg_value2, + arg_value3, arg_value4, arg_value5); + + if (!binding->func) { + if (binding->arg.v) { + free(binding->arg.v); + binding->arg.v = NULL; + } + if (binding->arg.v2) { + free(binding->arg.v2); + binding->arg.v2 = NULL; + } + if (binding->arg.v3) { + free(binding->arg.v3); + binding->arg.v3 = NULL; + } + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Unknown " + "dispatch in " + "touchgesturebind: \033[1m\033[31m%s\n", + func_name); + return false; + } else { + config->touch_gesture_bindings_count++; + } } else if (strncmp(key, "source-optional", 15) == 0) { parse_config_file(config, value, false); } else if (strncmp(key, "source", 6) == 0) { @@ -2995,6 +3200,27 @@ void free_config(void) { config.gesture_bindings_count = 0; } + // release touch_gesture_bindings + if (config.touch_gesture_bindings) { + for (i = 0; i < config.touch_gesture_bindings_count; i++) { + if (config.touch_gesture_bindings[i].arg.v) { + free((void *)config.touch_gesture_bindings[i].arg.v); + config.touch_gesture_bindings[i].arg.v = NULL; + } + if (config.touch_gesture_bindings[i].arg.v2) { + free((void *)config.touch_gesture_bindings[i].arg.v2); + config.touch_gesture_bindings[i].arg.v2 = NULL; + } + if (config.touch_gesture_bindings[i].arg.v3) { + free((void *)config.touch_gesture_bindings[i].arg.v3); + config.touch_gesture_bindings[i].arg.v3 = NULL; + } + } + free(config.touch_gesture_bindings); + config.touch_gesture_bindings = NULL; + config.touch_gesture_bindings_count = 0; + } + // 释放 tag_rules if (config.tag_rules) { for (int32_t i = 0; i < config.tag_rules_count; i++) { @@ -3219,6 +3445,19 @@ 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.touch_distance_threshold = + CLAMP_FLOAT(config.touch_distance_threshold, 1.0f, 10000.0f); + config.touch_degrees_leniency = + CLAMP_FLOAT(config.touch_degrees_leniency, 0.0f, 45.0f); + config.touch_timeoutms = CLAMP_INT(config.touch_timeoutms, 1, 60000); + config.touch_edge_size_left = + CLAMP_FLOAT(config.touch_edge_size_left, 1.0f, 10000.0f); + config.touch_edge_size_top = + CLAMP_FLOAT(config.touch_edge_size_top, 1.0f, 10000.0f); + config.touch_edge_size_right = + CLAMP_FLOAT(config.touch_edge_size_right, 1.0f, 10000.0f); + config.touch_edge_size_bottom = + CLAMP_FLOAT(config.touch_edge_size_bottom, 1.0f, 10000.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 +3550,13 @@ void set_value_default() { config.scratchpad_cross_monitor = 0; config.focus_cross_tag = 0; config.axis_scroll_factor = 1.0; + config.touch_distance_threshold = 50.0; + config.touch_degrees_leniency = 15.0; + config.touch_timeoutms = 800; + config.touch_edge_size_left = 50.0; + config.touch_edge_size_top = 50.0; + config.touch_edge_size_right = 50.0; + config.touch_edge_size_bottom = 50.0; config.view_current_to_back = 0; config.single_scratchpad = 1; config.xwayland_persistence = 1; @@ -3505,6 +3751,8 @@ bool parse_config(void) { config.switch_bindings_count = 0; config.gesture_bindings = NULL; config.gesture_bindings_count = 0; + config.touch_gesture_bindings = NULL; + config.touch_gesture_bindings_count = 0; config.env = NULL; config.env_count = 0; config.exec = NULL; diff --git a/src/dispatch/gesture.h b/src/dispatch/gesture.h new file mode 100644 index 00000000..c2d63c56 --- /dev/null +++ b/src/dispatch/gesture.h @@ -0,0 +1,237 @@ +int32_t gesture_calculate_swipe_within_degrees(double gestdegrees, + double wantdegrees) { + return (gestdegrees >= wantdegrees - config.touch_degrees_leniency && + gestdegrees <= wantdegrees + config.touch_degrees_leniency); +} + +uint32_t gesture_calculate_swipe(double x0, double y0, double x1, double y1) { + double t, degrees, distance; + + t = atan2(x1 - x0, y0 - y1); + degrees = 57.2957795130823209 * (t < 0 ? t + 6.2831853071795865 : t); + distance = sqrt(pow(x1 - x0, 2) + pow(y1 - y0, 2)); + + wlr_log(WLR_DEBUG, "Swipe distance=[%.2f]; degrees=[%.2f]", distance, + degrees); + + if (distance < config.touch_distance_threshold) + return TOUCH_SWIPE_NONE; + else if (gesture_calculate_swipe_within_degrees(degrees, 0)) + return TOUCH_SWIPE_UP; + else if (gesture_calculate_swipe_within_degrees(degrees, 45)) + return TOUCH_SWIPE_UP_RIGHT; + else if (gesture_calculate_swipe_within_degrees(degrees, 90)) + return TOUCH_SWIPE_RIGHT; + else if (gesture_calculate_swipe_within_degrees(degrees, 135)) + return TOUCH_SWIPE_DOWN_RIGHT; + else if (gesture_calculate_swipe_within_degrees(degrees, 180)) + return TOUCH_SWIPE_DOWN; + else if (gesture_calculate_swipe_within_degrees(degrees, 225)) + return TOUCH_SWIPE_DOWN_LEFT; + else if (gesture_calculate_swipe_within_degrees(degrees, 270)) + return TOUCH_SWIPE_LEFT; + else if (gesture_calculate_swipe_within_degrees(degrees, 315)) + return TOUCH_SWIPE_UP_LEFT; + else if (gesture_calculate_swipe_within_degrees(degrees, 360)) + return TOUCH_SWIPE_UP; + + return TOUCH_SWIPE_NONE; +} + +uint32_t gesture_calculate_distance(Monitor *m, double x0, double y0, double x1, + double y1, int32_t swipe) { + double distance = sqrt(pow(x1 - x0, 2) + pow(y1 - y0, 2)); + double diag = sqrt(pow(m->m.width, 2) + pow(m->m.height, 2)); + switch (swipe) { + case TOUCH_SWIPE_UP: + case TOUCH_SWIPE_DOWN: + if (distance >= m->m.height * 0.66) { + return DISTANCE_LONG; + } else if (distance >= m->m.height * 0.33) { + return DISTANCE_MEDIUM; + } else { + return DISTANCE_SHORT; + } + break; + case TOUCH_SWIPE_RIGHT: + case TOUCH_SWIPE_LEFT: + if (distance >= m->m.width * 0.66) { + return DISTANCE_LONG; + } else if (distance >= m->m.width * 0.33) { + return DISTANCE_MEDIUM; + } else { + return DISTANCE_SHORT; + } + break; + case TOUCH_SWIPE_UP_RIGHT: + case TOUCH_SWIPE_UP_LEFT: + case TOUCH_SWIPE_DOWN_RIGHT: + case TOUCH_SWIPE_DOWN_LEFT: + if (distance >= diag * 0.66) { + return DISTANCE_LONG; + } else if (distance >= diag * 0.33) { + return DISTANCE_MEDIUM; + } else { + return DISTANCE_SHORT; + } + break; + } + + return 0; // shouldn't happen +} + +uint32_t gesture_calculate_edge(Monitor *m, double x0, double y0, double x1, + double y1) { + uint32_t horizontal = EDGE_NONE; + uint32_t vertical = EDGE_NONE; + + if (x0 <= config.touch_edge_size_left) { + horizontal = EDGE_LEFT; + } else if (x0 >= m->m.width - config.touch_edge_size_right) { + horizontal = EDGE_RIGHT; + } else if (x1 <= config.touch_edge_size_left) { + horizontal = EDGE_LEFT; + } else if (x1 >= m->m.width - config.touch_edge_size_right) { + horizontal = EDGE_RIGHT; + } + if (y0 <= config.touch_edge_size_top) { + vertical = EDGE_TOP; + } else if (y0 >= m->m.height - config.touch_edge_size_bottom) { + vertical = EDGE_BOTTOM; + } else if (y1 <= config.touch_edge_size_top) { + vertical = EDGE_TOP; + } else if (y1 >= m->m.height - config.touch_edge_size_bottom) { + vertical = EDGE_BOTTOM; + } + if (horizontal == EDGE_LEFT && vertical == EDGE_TOP) { + return CORNER_TOP_LEFT; + } else if (horizontal == EDGE_RIGHT && vertical == EDGE_TOP) { + return CORNER_TOP_RIGHT; + } else if (horizontal == EDGE_LEFT && vertical == EDGE_BOTTOM) { + return CORNER_BOTTOM_LEFT; + } else if (horizontal == EDGE_RIGHT && vertical == EDGE_BOTTOM) { + return CORNER_BOTTOM_RIGHT; + } else if (horizontal != EDGE_NONE) { + return horizontal; + } else { + return vertical; + } +} + +int32_t gesture_execute(int32_t nfingers, uint32_t swipe, uint32_t edge, + uint32_t distance) { + int32_t i; + int32_t handled = 0; + + wlr_log(WLR_DEBUG, "f:%d s:%d e:%d d:%d", nfingers, swipe, edge, distance); + + TouchGestureBinding *g; + for (i = 0; i < config.touch_gesture_bindings_count; i++) { + g = &config.touch_gesture_bindings[i]; + if (swipe == g->swipe && nfingers == g->fingers_count && + distance >= g->distance && + (g->edge == EDGE_ANY || edge == g->edge || + ((edge == CORNER_TOP_LEFT || edge == CORNER_TOP_RIGHT) && + g->edge == EDGE_TOP) || + ((edge == CORNER_BOTTOM_LEFT || edge == CORNER_BOTTOM_RIGHT) && + g->edge == EDGE_BOTTOM) || + ((edge == CORNER_TOP_LEFT || edge == CORNER_BOTTOM_LEFT) && + g->edge == EDGE_LEFT) || + ((edge == CORNER_TOP_RIGHT || edge == CORNER_BOTTOM_RIGHT) && + g->edge == EDGE_RIGHT)) && + g->func) { + g->func(&g->arg); + handled = 1; + } + } + return handled; +} + +void gesture_consume(TouchGroup *tg, TouchPoint *t) { + if (t->consumed_by_gesture) + return; + + struct timespec now; + struct wlr_touch_cancel_event *event = + ecalloc(1, sizeof(struct wlr_touch_cancel_event)); + + clock_gettime(CLOCK_MONOTONIC_RAW, &now); + + event->touch_id = t->touch_id; + event->touch = tg->touch; + event->time_msec = now.tv_sec * 1000 + now.tv_nsec / 1000000; + + wlr_log(WLR_DEBUG, "gesture_consume id: %d", t->touch_id); + + t->consumed_by_gesture = true; + + handle_touchcancel(event); + + free(event); +} + +void gesture_touch_down(TouchGroup *tg, TouchPoint *t, double x, double y) { + wlr_log(WLR_DEBUG, "touch_down id: %d", t->touch_id); + + t->start_x = x; + t->start_y = y; + t->end_x = x; + t->end_y = y; + + if (wl_list_empty(&tg->touch_points)) + clock_gettime(CLOCK_MONOTONIC_RAW, &tg->time_down); +} + +void gesture_touch_motion(TouchGroup *tg, TouchPoint *t, double x, double y) { + t->end_x = x; + t->end_y = y; +} + +void gesture_touch_up(TouchGroup *tg, TouchPoint *t) { + struct timespec now; + + wlr_log(WLR_DEBUG, "touch_up id: %d", t->touch_id); + wlr_log(WLR_DEBUG, "len: %d", wl_list_length(&tg->touch_points)); + + clock_gettime(CLOCK_MONOTONIC_RAW, &now); + + uint32_t swipe = + gesture_calculate_swipe(t->start_x, t->start_y, t->end_x, t->end_y); + + if (swipe == TOUCH_SWIPE_NONE) { + goto cleanup; + } + + if (tg->touch_points_pending_swipe == 0) { + tg->pending_swipe = swipe; + } + if (tg->pending_swipe != swipe) { + goto cleanup; + } + + tg->touch_points_pending_swipe++; + + // All fingers up - check if within millisecond limit, exec, & reset + // (we are the last finger) + if (wl_list_length(&tg->touch_points) == 1) { + uint32_t edge = gesture_calculate_edge(tg->m, t->start_x, t->start_y, + t->end_x, t->end_y); + uint32_t distance = gesture_calculate_distance( + tg->m, t->start_x, t->start_y, t->end_x, t->end_y, swipe); + + if (config.touch_timeoutms > + ((now.tv_sec - tg->time_down.tv_sec) * 1000 + + (now.tv_nsec - tg->time_down.tv_nsec) / 1000000)) { + if (gesture_execute(tg->touch_points_pending_swipe, + tg->pending_swipe, edge, distance)) + gesture_consume(tg, t); + + tg->touch_points_pending_swipe = 0; + } + return; + } + +cleanup: + if (wl_list_length(&tg->touch_points) == 1) + tg->touch_points_pending_swipe = 0; +} diff --git a/src/mango.c b/src/mango.c index 576ee6eb..f9bebda4 100644 --- a/src/mango.c +++ b/src/mango.c @@ -149,6 +149,32 @@ enum { TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT }; enum { VERTICAL, HORIZONTAL }; enum { SWIPE_UP, SWIPE_DOWN, SWIPE_LEFT, SWIPE_RIGHT }; +enum { + TOUCH_SWIPE_UP, + TOUCH_SWIPE_DOWN, + TOUCH_SWIPE_RIGHT, + TOUCH_SWIPE_LEFT, + TOUCH_SWIPE_UP_RIGHT, + TOUCH_SWIPE_UP_LEFT, + TOUCH_SWIPE_DOWN_LEFT, + TOUCH_SWIPE_DOWN_RIGHT, + TOUCH_SWIPE_NONE +}; + +enum { + EDGE_ANY, + EDGE_NONE, + EDGE_LEFT, + EDGE_RIGHT, + EDGE_TOP, + EDGE_BOTTOM, + CORNER_TOP_LEFT, + CORNER_TOP_RIGHT, + CORNER_BOTTOM_LEFT, + CORNER_BOTTOM_RIGHT, +}; + +enum { DISTANCE_ANY, DISTANCE_SHORT, DISTANCE_MEDIUM, DISTANCE_LONG }; enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ enum { XDGShell, LayerShell, X11 }; /* client types */ enum { AxisUp, AxisDown, AxisLeft, AxisRight }; // 滚轮滚动的方向 @@ -455,12 +481,17 @@ typedef struct { typedef struct { struct wl_list link; int32_t touch_id; + double start_x, start_y, end_x, end_y; + bool consumed_by_gesture; } TouchPoint; typedef struct TouchGroup { struct wl_list link; struct wlr_touch *touch; struct wl_list touch_points; + struct timespec time_down; + uint32_t pending_swipe; + uint32_t touch_points_pending_swipe; Monitor *m; } TouchGroup; @@ -700,6 +731,7 @@ static void touchup(struct wl_listener *listener, void *data); static void touchframe(struct wl_listener *listener, void *data); static void touchmotion(struct wl_listener *listener, void *data); static void touchcancel(struct wl_listener *listener, void *data); +static void handle_touchcancel(struct wlr_touch_cancel_event *event); static void unlocksession(struct wl_listener *listener, void *data); static void unmaplayersurfacenotify(struct wl_listener *listener, void *data); @@ -1034,6 +1066,7 @@ static struct wl_event_source *sync_keymap; #include "animation/layer.h" #include "animation/tag.h" #include "dispatch/bind_define.h" +#include "dispatch/gesture.h" #include "ext-protocol/all.h" #include "fetch/fetch.h" #include "layout/arrange.h" @@ -5886,10 +5919,8 @@ void touchdown(struct wl_listener *listener, void *data) { double dx, dy; struct wlr_surface *surface; Client *c = NULL; - Monitor *m; - - t->touch_id = event->touch_id; - wl_list_insert(&tg->touch_points, &t->link); + Monitor *m = NULL; + Monitor *m_iter; wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); @@ -5899,8 +5930,8 @@ void touchdown(struct wl_listener *listener, void *data) { // Map the input to the appropriate output, to ensure that rotation is // handled. - wl_list_for_each(m, &mons, link) { - if (m == NULL || m->wlr_output == NULL) { + wl_list_for_each(m_iter, &mons, link) { + if (m_iter == NULL || m_iter->wlr_output == NULL) { continue; } if (event->touch->output_name != NULL && @@ -5909,12 +5940,23 @@ void touchdown(struct wl_listener *listener, void *data) { } wlr_cursor_map_input_to_output(cursor, &event->touch->base, - m->wlr_output); + m_iter->wlr_output); + m = m_iter; + break; } + /* ensure touch group has a monitor */ + if (!tg->m) + tg->m = m; // TODO: properly handle output_name = null, instead of + // falling back to last monitor in the list; + wlr_cursor_absolute_to_layout_coords(cursor, &event->touch->base, event->x, event->y, &lx, &ly); + t->touch_id = event->touch_id; + gesture_touch_down(tg, t, lx, ly); + wl_list_insert(&tg->touch_points, &t->link); + /* Find the client under the pointer and send the event along. */ xytonode(lx, ly, &surface, &c, NULL, &sx, &sy); if (surface != NULL && wlr_surface_accepts_touch(surface, seat)) { @@ -5942,7 +5984,7 @@ void touchdown(struct wl_listener *listener, void *data) { .time_msec = event->time_msec, .button = BTN_LEFT, .state = WL_POINTER_BUTTON_STATE_PRESSED}; - buttonpress(listener, &button_event); + buttonpress(NULL, &button_event); } } @@ -5960,6 +6002,15 @@ void touchup(struct wl_listener *listener, void *data) { } if (!t) // invalid or cancelled return; + + gesture_touch_up(tg, t); + + if (t->consumed_by_gesture) { + wl_list_remove(&t->link); + free(t); + return; + } + wl_list_remove(&t->link); free(t); @@ -5970,7 +6021,7 @@ void touchup(struct wl_listener *listener, void *data) { .time_msec = event->time_msec, .button = BTN_LEFT, .state = WL_POINTER_BUTTON_STATE_RELEASED}; - buttonpress(listener, &button_event); + buttonpress(NULL, &button_event); emulating_pointer_from_touch = false; } @@ -6017,10 +6068,13 @@ void touchmotion(struct wl_listener *listener, void *data) { if (!t) // invalid or cancelled return; + wlr_cursor_absolute_to_layout_coords(cursor, &event->touch->base, event->x, + event->y, &lx, &ly); + + gesture_touch_motion(tg, t, lx, ly); + if (emulating_pointer_from_touch) { if (emulated_pointer_touch_id == event->touch_id) { - wlr_cursor_absolute_to_layout_coords(cursor, &event->touch->base, - event->x, event->y, &lx, &ly); dx = lx - cursor->x; dy = ly - cursor->y; motionnotify(event->time_msec, &event->touch->base, dx, dy, dx, dy); @@ -6034,8 +6088,6 @@ void touchmotion(struct wl_listener *listener, void *data) { return; } - wlr_cursor_absolute_to_layout_coords(cursor, &event->touch->base, event->x, - event->y, &lx, &ly); surface = p->surface; if (surface && surface->data) { tree = surface->data; @@ -6066,9 +6118,6 @@ void touchcancel(struct wl_listener *listener, void *data) { TouchGroup *tg = event->touch->data; TouchPoint *t = NULL; TouchPoint *t_iter; - struct wlr_touch_point *p = NULL; - struct wl_client *client = NULL; - struct wlr_seat_client *seat_client = NULL; wl_list_for_each(t_iter, &tg->touch_points, link) { if (t_iter->touch_id == event->touch_id) { @@ -6082,6 +6131,17 @@ void touchcancel(struct wl_listener *listener, void *data) { wl_list_remove(&t->link); free(t); + if (wl_list_length(&tg->touch_points) == 0) + tg->touch_points_pending_swipe = 0; + + handle_touchcancel(event); +} + +void handle_touchcancel(struct wlr_touch_cancel_event *event) { + struct wlr_touch_point *p = NULL; + struct wl_client *client = NULL; + struct wlr_seat_client *seat_client = NULL; + if (emulating_pointer_from_touch) { if (emulated_pointer_touch_id == event->touch_id) { struct wlr_pointer_button_event button_event = { @@ -6089,7 +6149,7 @@ void touchcancel(struct wl_listener *listener, void *data) { .time_msec = event->time_msec, .button = BTN_LEFT, .state = WL_POINTER_BUTTON_STATE_RELEASED}; - buttonpress(listener, &button_event); + buttonpress(NULL, &button_event); emulating_pointer_from_touch = false; } From 9633ac31ecf9babcce9c77ef22aaff2026d1bc10 Mon Sep 17 00:00:00 2001 From: werapi Date: Fri, 27 Mar 2026 20:59:52 +0100 Subject: [PATCH 41/42] opt: refine touch gesture distance judge --- src/dispatch/gesture.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dispatch/gesture.h b/src/dispatch/gesture.h index c2d63c56..d884ca87 100644 --- a/src/dispatch/gesture.h +++ b/src/dispatch/gesture.h @@ -129,7 +129,7 @@ int32_t gesture_execute(int32_t nfingers, uint32_t swipe, uint32_t edge, for (i = 0; i < config.touch_gesture_bindings_count; i++) { g = &config.touch_gesture_bindings[i]; if (swipe == g->swipe && nfingers == g->fingers_count && - distance >= g->distance && + (distance == g->distance || g->distance == DISTANCE_ANY) && (g->edge == EDGE_ANY || edge == g->edge || ((edge == CORNER_TOP_LEFT || edge == CORNER_TOP_RIGHT) && g->edge == EDGE_TOP) || From c89f30827ce5947fe7697bcffd9b11061fb5b16d Mon Sep 17 00:00:00 2001 From: werapi Date: Sun, 5 Apr 2026 14:04:27 +0200 Subject: [PATCH 42/42] fix: correct touch motion surface-local coordinate calculation --- src/mango.c | 38 +++++++++++++++----------------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/src/mango.c b/src/mango.c index 1e0ead3f..dae07d78 100644 --- a/src/mango.c +++ b/src/mango.c @@ -455,6 +455,7 @@ typedef struct { typedef struct { struct wl_list link; int32_t touch_id; + double start_x, start_y, start_surface_x, start_surface_y; } TouchPoint; typedef struct TouchGroup { @@ -5910,6 +5911,8 @@ void touchdown(struct wl_listener *listener, void *data) { Monitor *m; t->touch_id = event->touch_id; + t->start_x = lx; + t->start_y = ly; wl_list_insert(&tg->touch_points, &t->link); wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); @@ -5938,6 +5941,8 @@ void touchdown(struct wl_listener *listener, void *data) { /* Find the client under the pointer and send the event along. */ xytonode(lx, ly, &surface, &c, NULL, &sx, &sy); + t->start_surface_x = sx; + t->start_surface_y = sy; if (surface != NULL && wlr_surface_accepts_touch(surface, seat)) { if (c) focusclient(c, 0); @@ -6023,10 +6028,8 @@ void touchmotion(struct wl_listener *listener, void *data) { double lx, ly; double sx, sy; double dx, dy; - int32_t node_x, node_y; struct wlr_surface *surface; Client *c = NULL; - struct wlr_scene_tree *tree; struct wlr_touch_point *p = NULL; wl_list_for_each(t_iter, &tg->touch_points, link) { @@ -6038,10 +6041,11 @@ void touchmotion(struct wl_listener *listener, void *data) { if (!t) // invalid or cancelled return; + wlr_cursor_absolute_to_layout_coords(cursor, &event->touch->base, event->x, + event->y, &lx, &ly); + if (emulating_pointer_from_touch) { if (emulated_pointer_touch_id == event->touch_id) { - wlr_cursor_absolute_to_layout_coords(cursor, &event->touch->base, - event->x, event->y, &lx, &ly); dx = lx - cursor->x; dy = ly - cursor->y; motionnotify(event->time_msec, &event->touch->base, dx, dy, dx, dy); @@ -6055,31 +6059,19 @@ void touchmotion(struct wl_listener *listener, void *data) { return; } - wlr_cursor_absolute_to_layout_coords(cursor, &event->touch->base, event->x, - event->y, &lx, &ly); + sx = t->start_surface_x + (lx - t->start_x); + sy = t->start_surface_y + (ly - t->start_y); + surface = p->surface; if (surface && surface->data) { - tree = surface->data; - wlr_scene_node_coords(&tree->node, &node_x, &node_y); - sx = lx - node_x; - sy = ly - node_y; - toplevel_from_wlr_surface(surface, &c, NULL); if (c) focusclient(c, 0); - - wlr_seat_touch_point_focus(seat, surface, event->time_msec, - event->touch_id, sx, sy); - wlr_seat_touch_notify_motion(seat, event->time_msec, event->touch_id, - sx, sy); - - wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); - } else { - focusclient(NULL, 0); - wlr_seat_touch_point_clear_focus(seat, event->time_msec, - event->touch_id); - wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); } + wlr_seat_touch_notify_motion(seat, event->time_msec, event->touch_id, sx, + sy); + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); } void touchcancel(struct wl_listener *listener, void *data) {