From 818652d20f43598b245199cac9329edebbbbbae4 Mon Sep 17 00:00:00 2001 From: Lin Xianyi Date: Sat, 7 Feb 2026 12:12:13 +0800 Subject: [PATCH 01/22] fix: -p exits with failure if there are parse failures --- src/config/parse_config.h | 25 +++++++++++++++---------- src/mango.c | 3 +-- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 7e24d43f..b8426e2d 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -362,7 +362,7 @@ typedef struct { typedef int32_t (*FuncType)(const Arg *); Config config; -void parse_config_file(Config *config, const char *file_path); +bool parse_config_file(Config *config, const char *file_path); // Helper function to trim whitespace from start and end of a string void trim_whitespace(char *str) { @@ -2639,7 +2639,7 @@ bool parse_config_line(Config *config, const char *line) { return parse_option(config, key, value); } -void parse_config_file(Config *config, const char *file_path) { +bool parse_config_file(Config *config, const char *file_path) { FILE *file; char full_path[1024]; @@ -2658,7 +2658,7 @@ void parse_config_file(Config *config, const char *file_path) { fprintf(stderr, "\033[1m\033[31m[ERROR]:\033[33m HOME environment " "variable not set.\n"); - return; + return false; } snprintf(full_path, sizeof(full_path), "%s/.config/mango/%s", home, file_path + 1); @@ -2673,7 +2673,7 @@ void parse_config_file(Config *config, const char *file_path) { if (!home) { fprintf(stderr, "\033[1m\033[31m[ERROR]:\033[33m HOME environment " "variable not set.\n"); - return; + return false; } snprintf(full_path, sizeof(full_path), "%s%s", home, file_path + 1); file = fopen(full_path, "r"); @@ -2688,19 +2688,21 @@ void parse_config_file(Config *config, const char *file_path) { "\033[1;31m\033[1;33m[ERROR]:\033[0m Failed to open " "config file: %s\n", file_path); - return; + return false; } char line[512]; bool parse_correct = true; + bool parse_line_correct = true; uint32_t line_count = 0; while (fgets(line, sizeof(line), file)) { line_count++; if (line[0] == '#' || line[0] == '\n') { continue; } - parse_correct = parse_config_line(config, line); - if (!parse_correct) { + parse_line_correct = parse_config_line(config, line); + if (!parse_line_correct) { + parse_correct = false; fprintf(stderr, "\033[1;31m╰─\033[1;33m[Index]\033[0m " "\033[1;36m%s\033[0m:\033[1;35m%d\033[0m\n" @@ -2710,6 +2712,7 @@ void parse_config_file(Config *config, const char *file_path) { } fclose(file); + return parse_correct; } void free_circle_layout(Config *config) { @@ -3369,7 +3372,7 @@ void set_default_key_bindings(Config *config) { config->key_bindings_count += default_key_bindings_count; } -void parse_config(void) { +bool parse_config(void) { char filename[1024]; @@ -3422,7 +3425,7 @@ void parse_config(void) { const char *homedir = getenv("HOME"); if (!homedir) { // 如果获取失败,则无法继续 - return; + return false; } // 构建日志文件路径 snprintf(filename, sizeof(filename), "%s/.config/mango/config.conf", @@ -3436,10 +3439,12 @@ void parse_config(void) { } } + bool parse_correct = true; set_value_default(); - parse_config_file(&config, filename); + parse_correct = parse_config_file(&config, filename); set_default_key_bindings(&config); override_config(); + return parse_correct; } void reset_blur_params(void) { diff --git a/src/mango.c b/src/mango.c index 591d4f65..6b8a3903 100644 --- a/src/mango.c +++ b/src/mango.c @@ -6168,8 +6168,7 @@ int32_t main(int32_t argc, char *argv[]) { } else if (c == 'c') { cli_config_path = optarg; } else if (c == 'p') { - parse_config(); - return EXIT_SUCCESS; + return parse_config() ? EXIT_SUCCESS : EXIT_FAILURE; } else { goto usage; } From 2734d91e6aec9992d0647b447cd0bf23f2bda84a Mon Sep 17 00:00:00 2001 From: Lin Xianyi Date: Sat, 7 Feb 2026 13:31:30 +0800 Subject: [PATCH 02/22] fix: improve help message for mmsg --- mmsg/mmsg.c | 47 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/mmsg/mmsg.c b/mmsg/mmsg.c index ba073aad..ab87b2b1 100644 --- a/mmsg/mmsg.c +++ b/mmsg/mmsg.c @@ -500,12 +500,47 @@ static const struct wl_registry_listener registry_listener = { static void usage(void) { fprintf(stderr, - "usage:" - "\t%s [-OTLq]\n" - "\t%s [-o ] -s [-t ] [-l ] [-c ] [-d " - ",,,,,]\n" - "\t%s [-o ] (-g | -w) [-OotlcvmfxekbA]\n", - argv0, argv0, argv0); + "mmsg - MangoWC IPC\n" + "\n" + "SYNOPSIS:\n" + "\tmmsg [-OTLq]\n" + "\tmmsg [-o ] -s [-t ] [-l ] [-c ] [-d ,,,,,]\n" + "\tmmsg [-o ] (-g | -w) [-OotlcvmfxekbA]\n" + "\n" + "OPERATION MODES:\n" + "\t-g Get values (tags, layout, focused client)\n" + "\t-s Set values (switch tags, layouts)\n" + "\t-w Watch mode (stream events)\n" + "\n" + "GENERAL OPTIONS:\n" + "\t-O Get all output (monitor) information\n" + "\t-T Get number of tags\n" + "\t-L Get all available layouts\n" + "\t-q Quit MangoWC\n" + "\t-o Select output (monitor)\n" + "\n" + "GET OPTIONS (used with -g or -w):\n" + "\t-O Get output name\n" + "\t-o Get output (monitor) focus information\n" + "\t-t Get selected tags\n" + "\t-l Get current layout\n" + "\t-c Get title and appid of focused clients\n" + "\t-v Get visibility of statusbar\n" + "\t-m Get fullscreen status\n" + "\t-f Get floating status\n" + "\t-x Get focused client geometry\n" + "\t-e Get name of last focused layer\n" + "\t-k Get current keyboard layout\n" + "\t-b Get current keybind mode\n" + "\t-A Get scale factor of monitor\n" + "\n" + "SET OPTIONS (used with -s):\n" + "\t-o Select output (monitor)\n" + "\t-t Set selected tags (can be used with [+-^.] modifiers)\n" + "\t-l Set current layout\n" + "\t-c Get title and appid of focused client\n" + "\t-d , Dispatch internal command (max 5 args)\n" + ); exit(2); } From f8dfeedff1f88e16338c392f50baa7f4f965726e Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 7 Feb 2026 14:04:42 +0800 Subject: [PATCH 03/22] fix: headless backend cant use keybarod and pointer --- src/mango.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/mango.c b/src/mango.c index 6b8a3903..dcd2753b 100644 --- a/src/mango.c +++ b/src/mango.c @@ -3419,7 +3419,9 @@ void requestmonstate(struct wl_listener *listener, void *data) { void inputdevice(struct wl_listener *listener, void *data) { /* This event is raised by the backend when a new input device becomes - * available. */ + * available. + * when the backend is a headless backend, this event will never be triggered. + */ struct wlr_input_device *device = data; uint32_t caps; @@ -5973,6 +5975,8 @@ void handle_keyboard_shortcuts_inhibit_new_inhibitor( void virtualkeyboard(struct wl_listener *listener, void *data) { struct wlr_virtual_keyboard_v1 *kb = data; /* virtual keyboards shouldn't share keyboard group */ + wlr_seat_set_capabilities(seat, + seat->capabilities | WL_SEAT_CAPABILITY_KEYBOARD); KeyboardGroup *group = createkeyboardgroup(); /* Set the keymap to match the group keymap */ wlr_keyboard_set_keymap(&kb->keyboard, group->wlr_group->keyboard.keymap); @@ -6003,7 +6007,8 @@ void warp_cursor_to_selmon(Monitor *m) { void virtualpointer(struct wl_listener *listener, void *data) { struct wlr_virtual_pointer_v1_new_pointer_event *event = data; struct wlr_input_device *device = &event->new_pointer->pointer.base; - + wlr_seat_set_capabilities(seat, + seat->capabilities | WL_SEAT_CAPABILITY_POINTER); wlr_cursor_attach_input_device(cursor, device); if (event->suggested_output) wlr_cursor_map_input_to_output(cursor, device, event->suggested_output); From 3db2ac58b4220dcf1b2e5e2266ed661c4ea31578 Mon Sep 17 00:00:00 2001 From: Yujonpradhananga Date: Fri, 6 Feb 2026 15:18:31 +0545 Subject: [PATCH 04/22] opt: fix spelling mistake --- README.md | 2 +- src/animation/client.h | 74 +++++++++++++++++++++--------------------- src/client/client.h | 4 +-- 3 files changed, 40 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index d512f39f..8aaaeb9b 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ https://github.com/user-attachments/assets/014c893f-115c-4ae9-8342-f9ae3e9a0df0 - libxcb ## Arch Linux -The package is in the Arch User Repository and is availble for manual download [here](https://aur.archlinux.org/packages/mangowc-git) or through a AUR helper like yay: +The package is in the Arch User Repository and is available for manual download [here](https://aur.archlinux.org/packages/mangowc-git) or through a AUR helper like yay: ```bash yay -S mangowc-git diff --git a/src/animation/client.h b/src/animation/client.h index 8de9b068..2588fb1c 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -47,7 +47,7 @@ bool is_horizontal_right_stack_layout(Monitor *m) { return false; } -int32_t is_special_animaiton_rule(Client *c) { +int32_t is_special_animation_rule(Client *c) { if (is_scroller_layout(c->mon) && !c->isfloating) { return DOWN; @@ -70,7 +70,7 @@ int32_t is_special_animaiton_rule(Client *c) { } } -void set_client_open_animaiton(Client *c, struct wlr_box geo) { +void set_client_open_animation(Client *c, struct wlr_box geo) { int32_t slide_direction; int32_t horizontal, horizontal_value; int32_t vertical, vertical_value; @@ -96,7 +96,7 @@ void set_client_open_animaiton(Client *c, struct wlr_box geo) { geo.y + (geo.height - c->animainit_geom.height) / 2; return; } else { - special_direction = is_special_animaiton_rule(c); + special_direction = is_special_animation_rule(c); center_x = c->geom.x + c->geom.width / 2; center_y = c->geom.y + c->geom.height / 2; if (special_direction == UNDIR) { @@ -772,71 +772,71 @@ void init_fadeout_client(Client *c) { return; } - Client *fadeout_cient = ecalloc(1, sizeof(*fadeout_cient)); + Client *fadeout_client = ecalloc(1, sizeof(*fadeout_client)); wlr_scene_node_set_enabled(&c->scene->node, true); client_set_border_color(c, bordercolor); - fadeout_cient->scene = + fadeout_client->scene = wlr_scene_tree_snapshot(&c->scene->node, layers[LyrFadeOut]); wlr_scene_node_set_enabled(&c->scene->node, false); - if (!fadeout_cient->scene) { - free(fadeout_cient); + if (!fadeout_client->scene) { + free(fadeout_client); return; } - fadeout_cient->animation.duration = animation_duration_close; - fadeout_cient->geom = fadeout_cient->current = - fadeout_cient->animainit_geom = fadeout_cient->animation.initial = + fadeout_client->animation.duration = animation_duration_close; + fadeout_client->geom = fadeout_client->current = + fadeout_client->animainit_geom = fadeout_client->animation.initial = c->animation.current; - fadeout_cient->mon = c->mon; - fadeout_cient->animation_type_close = c->animation_type_close; - fadeout_cient->animation.action = CLOSE; - fadeout_cient->bw = c->bw; - fadeout_cient->nofadeout = c->nofadeout; + fadeout_client->mon = c->mon; + fadeout_client->animation_type_close = c->animation_type_close; + fadeout_client->animation.action = CLOSE; + fadeout_client->bw = c->bw; + fadeout_client->nofadeout = c->nofadeout; // 这里snap节点的坐标设置是使用的相对坐标,所以不能加上原来坐标 // 这跟普通node有区别 - fadeout_cient->animation.initial.x = 0; - fadeout_cient->animation.initial.y = 0; + fadeout_client->animation.initial.x = 0; + fadeout_client->animation.initial.y = 0; if ((!c->animation_type_close && strcmp(animation_type_close, "fade") == 0) || (c->animation_type_close && strcmp(c->animation_type_close, "fade") == 0)) { - fadeout_cient->current.x = 0; - fadeout_cient->current.y = 0; - fadeout_cient->current.width = 0; - fadeout_cient->current.height = 0; + fadeout_client->current.x = 0; + fadeout_client->current.y = 0; + fadeout_client->current.width = 0; + fadeout_client->current.height = 0; } else if ((c->animation_type_close && strcmp(c->animation_type_close, "slide") == 0) || (!c->animation_type_close && strcmp(animation_type_close, "slide") == 0)) { - fadeout_cient->current.y = + fadeout_client->current.y = c->geom.y + c->geom.height / 2 > c->mon->m.y + c->mon->m.height / 2 ? c->mon->m.height - (c->animation.current.y - c->mon->m.y) // down out : c->mon->m.y - c->geom.height; // up out - fadeout_cient->current.x = 0; // x无偏差,垂直划出 + fadeout_client->current.x = 0; // x无偏差,垂直划出 } else { - fadeout_cient->current.y = - (fadeout_cient->geom.height - - fadeout_cient->geom.height * zoom_end_ratio) / + fadeout_client->current.y = + (fadeout_client->geom.height - + fadeout_client->geom.height * zoom_end_ratio) / 2; - fadeout_cient->current.x = - (fadeout_cient->geom.width - - fadeout_cient->geom.width * zoom_end_ratio) / + fadeout_client->current.x = + (fadeout_client->geom.width - + fadeout_client->geom.width * zoom_end_ratio) / 2; - fadeout_cient->current.width = - fadeout_cient->geom.width * zoom_end_ratio; - fadeout_cient->current.height = - fadeout_cient->geom.height * zoom_end_ratio; + fadeout_client->current.width = + fadeout_client->geom.width * zoom_end_ratio; + fadeout_client->current.height = + fadeout_client->geom.height * zoom_end_ratio; } - fadeout_cient->animation.time_started = get_now_in_ms(); - wlr_scene_node_set_enabled(&fadeout_cient->scene->node, true); - wl_list_insert(&fadeout_clients, &fadeout_cient->fadeout_link); + fadeout_client->animation.time_started = get_now_in_ms(); + wlr_scene_node_set_enabled(&fadeout_client->scene->node, true); + wl_list_insert(&fadeout_clients, &fadeout_client->fadeout_link); // 请求刷新屏幕 request_fresh_all_monitors(); @@ -971,7 +971,7 @@ void resize(Client *c, struct wlr_box geo, int32_t interact) { c->animainit_geom.height = c->animation.current.height; c->animainit_geom.width = c->animation.current.width; } else if (c->is_pending_open_animation) { - set_client_open_animaiton(c, c->geom); + set_client_open_animation(c, c->geom); } else { c->animainit_geom = c->animation.current; } diff --git a/src/client/client.h b/src/client/client.h index 8995a5d0..49ab3988 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -254,7 +254,7 @@ static inline int32_t client_is_stopped(Client *c) { wl_client_get_credentials(c->surface.xdg->client->client, &pid, NULL, NULL); if (waitid(P_PID, pid, &in, WNOHANG | WCONTINUED | WSTOPPED | WNOWAIT) < 0) { - /* This process is not our child process, while is very unluckely that + /* This process is not our child process, while is very unlikely that * it is stopped, in order to do not skip frames assume that it is. */ if (errno == ECHILD) return 1; @@ -547,4 +547,4 @@ static inline void client_set_size_bound(Client *c) { state.max_height > 0) { c->geom.height = state.max_height + 2 * c->bw; } -} \ No newline at end of file +} From e658f5c90d7e5337bd90cf818bc7428cae44bcd3 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 7 Feb 2026 19:57:07 +0800 Subject: [PATCH 05/22] opt: fix format --- mmsg/mmsg.c | 9 +++++---- src/mango.c | 5 +++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/mmsg/mmsg.c b/mmsg/mmsg.c index ab87b2b1..fb1d04fd 100644 --- a/mmsg/mmsg.c +++ b/mmsg/mmsg.c @@ -504,7 +504,8 @@ static void usage(void) { "\n" "SYNOPSIS:\n" "\tmmsg [-OTLq]\n" - "\tmmsg [-o ] -s [-t ] [-l ] [-c ] [-d ,,,,,]\n" + "\tmmsg [-o ] -s [-t ] [-l ] [-c ] [-d " + ",,,,,]\n" "\tmmsg [-o ] (-g | -w) [-OotlcvmfxekbA]\n" "\n" "OPERATION MODES:\n" @@ -536,11 +537,11 @@ static void usage(void) { "\n" "SET OPTIONS (used with -s):\n" "\t-o Select output (monitor)\n" - "\t-t Set selected tags (can be used with [+-^.] modifiers)\n" + "\t-t Set selected tags (can be used with [+-^.] " + "modifiers)\n" "\t-l Set current layout\n" "\t-c Get title and appid of focused client\n" - "\t-d , Dispatch internal command (max 5 args)\n" - ); + "\t-d , Dispatch internal command (max 5 args)\n"); exit(2); } diff --git a/src/mango.c b/src/mango.c index dcd2753b..8883181e 100644 --- a/src/mango.c +++ b/src/mango.c @@ -3419,8 +3419,9 @@ void requestmonstate(struct wl_listener *listener, void *data) { void inputdevice(struct wl_listener *listener, void *data) { /* This event is raised by the backend when a new input device becomes - * available. - * when the backend is a headless backend, this event will never be triggered. + * available. + * when the backend is a headless backend, this event will never be + * triggered. */ struct wlr_input_device *device = data; uint32_t caps; From 9b92f139c06d319d9fc6066ee194b967b672e369 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 7 Feb 2026 21:46:18 +0800 Subject: [PATCH 06/22] fix: correct dmabuf version --- src/mango.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mango.c b/src/mango.c index 8883181e..d75faed0 100644 --- a/src/mango.c +++ b/src/mango.c @@ -5180,7 +5180,7 @@ void setup(void) { if (wlr_renderer_get_texture_formats(drw, WLR_BUFFER_CAP_DMABUF)) { wlr_drm_create(dpy, drw); wlr_scene_set_linux_dmabuf_v1( - scene, wlr_linux_dmabuf_v1_create_with_renderer(dpy, 5, drw)); + scene, wlr_linux_dmabuf_v1_create_with_renderer(dpy, 4, drw)); } if (syncobj_enable && (drm_fd = wlr_renderer_get_drm_fd(drw)) >= 0 && From 5de87db8ca5870d37a181a4eaf4e8559f04397dc Mon Sep 17 00:00:00 2001 From: Lin Xianyi Date: Sun, 8 Feb 2026 10:46:18 +0800 Subject: [PATCH 07/22] feat: add a source-optional keyword --- src/config/parse_config.h | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index b8426e2d..a7959617 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -362,7 +362,7 @@ typedef struct { typedef int32_t (*FuncType)(const Arg *); Config config; -bool parse_config_file(Config *config, const char *file_path); +bool parse_config_file(Config *config, const char *file_path, bool must_exist); // Helper function to trim whitespace from start and end of a string void trim_whitespace(char *str) { @@ -2610,8 +2610,10 @@ bool parse_option(Config *config, char *key, char *value) { config->gesture_bindings_count++; } + } else if (strncmp(key, "source-optional", 15) == 0) { + parse_config_file(config, value, false); } else if (strncmp(key, "source", 6) == 0) { - parse_config_file(config, value); + parse_config_file(config, value, true); } else { fprintf(stderr, "\033[1m\033[31m[ERROR]:\033[33m Unknown keyword: " @@ -2639,7 +2641,7 @@ bool parse_config_line(Config *config, const char *line) { return parse_option(config, key, value); } -bool parse_config_file(Config *config, const char *file_path) { +bool parse_config_file(Config *config, const char *file_path, bool must_exist) { FILE *file; char full_path[1024]; @@ -2684,11 +2686,15 @@ bool parse_config_file(Config *config, const char *file_path) { } if (!file) { - fprintf(stderr, - "\033[1;31m\033[1;33m[ERROR]:\033[0m Failed to open " - "config file: %s\n", - file_path); - return false; + if (must_exist) { + fprintf(stderr, + "\033[1;31m\033[1;33m[ERROR]:\033[0m Failed to open " + "config file: %s\n", + file_path); + return false; + } else { + return true; + } } char line[512]; @@ -3441,7 +3447,7 @@ bool parse_config(void) { bool parse_correct = true; set_value_default(); - parse_correct = parse_config_file(&config, filename); + parse_correct = parse_config_file(&config, filename, true); set_default_key_bindings(&config); override_config(); return parse_correct; From 241afb4b9770a3bb7de2d28966a4298b059f61c0 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 8 Feb 2026 11:18:38 +0800 Subject: [PATCH 08/22] fix: warpcursor not apply in some case --- src/mango.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/mango.c b/src/mango.c index d75faed0..54262812 100644 --- a/src/mango.c +++ b/src/mango.c @@ -5989,8 +5989,7 @@ void virtualkeyboard(struct wl_listener *listener, void *data) { } void warp_cursor(const Client *c) { - if (cursor->x < c->geom.x || cursor->x > c->geom.x + c->geom.width || - cursor->y < c->geom.y || cursor->y > c->geom.y + c->geom.height) { + if (INSIDEMON(c)) { wlr_cursor_warp_closest(cursor, NULL, c->geom.x + c->geom.width / 2.0, c->geom.y + c->geom.height / 2.0); motionnotify(0, NULL, 0, 0, 0, 0); From faf2e1e9da2290d08592f3f625640487b2eeb494 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 8 Feb 2026 12:13:04 +0800 Subject: [PATCH 09/22] opt: sync keymap to xwayland after xwayland ready --- src/dispatch/bind_define.h | 5 ----- src/mango.c | 16 +++++++++++++++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index c0e51dc2..1d29bc97 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -907,9 +907,7 @@ int32_t switch_keyboard_layout(const Arg *arg) { uint32_t latched = keyboard->modifiers.latched; uint32_t locked = keyboard->modifiers.locked; - wlr_keyboard_set_keymap(keyboard, keyboard->keymap); wlr_keyboard_notify_modifiers(keyboard, depressed, latched, locked, next); - keyboard->modifiers.group = 0; // 7. 更新 seat wlr_seat_set_keyboard(seat, keyboard); @@ -923,10 +921,7 @@ int32_t switch_keyboard_layout(const Arg *arg) { struct wlr_keyboard *tkb = (struct wlr_keyboard *)id->device_data; - wlr_keyboard_set_keymap(tkb, keyboard->keymap); wlr_keyboard_notify_modifiers(tkb, depressed, latched, locked, next); - tkb->modifiers.group = 0; - // 7. 更新 seat wlr_seat_set_keyboard(seat, tkb); wlr_seat_keyboard_notify_modifiers(seat, &tkb->modifiers); diff --git a/src/mango.c b/src/mango.c index 54262812..6609270c 100644 --- a/src/mango.c +++ b/src/mango.c @@ -730,6 +730,7 @@ static void set_rect_size(struct wlr_scene_rect *rect, int32_t width, static Client *center_tiled_select(Monitor *m); static void handlecursoractivity(void); static int32_t hidecursor(void *data); +static int32_t synckeymap(void *data); static bool check_hit_no_border(Client *c); static void reset_keyboard_layout(void); static void client_update_oldmonname_record(Client *c, Monitor *m); @@ -876,6 +877,7 @@ struct dvec2 *baked_points_opafadein; struct dvec2 *baked_points_opafadeout; static struct wl_event_source *hide_source; +static struct wl_event_source *sync_keymap; static bool cursor_hidden = false; static bool tag_combo = false; static const char *cli_config_path = NULL; @@ -5327,7 +5329,8 @@ void setup(void) { &request_set_cursor_shape); hide_source = wl_event_loop_add_timer(wl_display_get_event_loop(dpy), hidecursor, cursor); - + sync_keymap = wl_event_loop_add_timer(wl_display_get_event_loop(dpy), + synckeymap, NULL); /* * Configures a seat, which is a single "seat" at which a user sits and * operates the computer. This conceptually includes up to one keyboard, @@ -5556,6 +5559,13 @@ int32_t hidecursor(void *data) { return 1; } +int32_t synckeymap(void *data) { + reset_keyboard_layout(); + // we only need to sync keymap once + wl_event_source_timer_update(sync_keymap, 0); + return 1; +} + void unlocksession(struct wl_listener *listener, void *data) { SessionLock *lock = wl_container_of(listener, lock, unlock); destroylock(lock, 1); @@ -6147,6 +6157,10 @@ void xwaylandready(struct wl_listener *listener, void *data) { xwayland, xcursor->images[0]->buffer, xcursor->images[0]->width * 4, xcursor->images[0]->width, xcursor->images[0]->height, xcursor->images[0]->hotspot_x, xcursor->images[0]->hotspot_y); + /* xwayland can't auto sync the keymap, so we do it manually + and we need to wait the xwayland completely inited + */ + wl_event_source_timer_update(sync_keymap, 500); } static void setgeometrynotify(struct wl_listener *listener, void *data) { From 4820b7a8abe95c9319ccc76642607a7ab9e7992a Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 8 Feb 2026 12:15:44 +0800 Subject: [PATCH 10/22] feat: allow single mod keybind --- src/mango.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/mango.c b/src/mango.c index 6609270c..e15b08fe 100644 --- a/src/mango.c +++ b/src/mango.c @@ -3495,11 +3495,6 @@ keybinding(uint32_t state, bool locked, uint32_t mods, xkb_keysym_t sym, int32_t ji; int32_t isbreak = 0; - // not allow modifier keys to be used as a keybinding - if (keycode == 50 || keycode == 37 || keycode == 133 || keycode == 64 || - keycode == 62 || keycode == 108 || keycode == 105 || keycode == 134) - return false; - if (is_keyboard_shortcut_inhibitor(seat->keyboard_state.focused_surface)) { return false; } From e8bf6380fba8b7acf7b29f178d9dba8e3481eb16 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 8 Feb 2026 12:44:56 +0800 Subject: [PATCH 11/22] opt: turn keymap sync into XWAYLAND macro --- src/mango.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/mango.c b/src/mango.c index e15b08fe..d6876c5b 100644 --- a/src/mango.c +++ b/src/mango.c @@ -730,7 +730,6 @@ static void set_rect_size(struct wlr_scene_rect *rect, int32_t width, static Client *center_tiled_select(Monitor *m); static void handlecursoractivity(void); static int32_t hidecursor(void *data); -static int32_t synckeymap(void *data); static bool check_hit_no_border(Client *c); static void reset_keyboard_layout(void); static void client_update_oldmonname_record(Client *c, Monitor *m); @@ -877,7 +876,6 @@ struct dvec2 *baked_points_opafadein; struct dvec2 *baked_points_opafadeout; static struct wl_event_source *hide_source; -static struct wl_event_source *sync_keymap; static bool cursor_hidden = false; static bool tag_combo = false; static const char *cli_config_path = NULL; @@ -944,6 +942,7 @@ static struct wl_listener keyboard_shortcuts_inhibit_new_inhibitor = { .notify = handle_keyboard_shortcuts_inhibit_new_inhibitor}; #ifdef XWAYLAND +static int32_t synckeymap(void *data); static void activatex11(struct wl_listener *listener, void *data); static void configurex11(struct wl_listener *listener, void *data); static void createnotifyx11(struct wl_listener *listener, void *data); @@ -955,6 +954,7 @@ static void setgeometrynotify(struct wl_listener *listener, void *data); static struct wl_listener new_xwayland_surface = {.notify = createnotifyx11}; static struct wl_listener xwayland_ready = {.notify = xwaylandready}; static struct wlr_xwayland *xwayland; +static struct wl_event_source *sync_keymap; #endif #include "animation/client.h" @@ -5324,8 +5324,6 @@ void setup(void) { &request_set_cursor_shape); hide_source = wl_event_loop_add_timer(wl_display_get_event_loop(dpy), hidecursor, cursor); - sync_keymap = wl_event_loop_add_timer(wl_display_get_event_loop(dpy), - synckeymap, NULL); /* * Configures a seat, which is a single "seat" at which a user sits and * operates the computer. This conceptually includes up to one keyboard, @@ -5417,6 +5415,8 @@ void setup(void) { fprintf(stderr, "failed to setup XWayland X server, continuing without it\n"); } + sync_keymap = wl_event_loop_add_timer(wl_display_get_event_loop(dpy), + synckeymap, NULL); #endif } @@ -5554,13 +5554,6 @@ int32_t hidecursor(void *data) { return 1; } -int32_t synckeymap(void *data) { - reset_keyboard_layout(); - // we only need to sync keymap once - wl_event_source_timer_update(sync_keymap, 0); - return 1; -} - void unlocksession(struct wl_listener *listener, void *data) { SessionLock *lock = wl_container_of(listener, lock, unlock); destroylock(lock, 1); @@ -6022,6 +6015,13 @@ void virtualpointer(struct wl_listener *listener, void *data) { } #ifdef XWAYLAND +int32_t synckeymap(void *data) { + reset_keyboard_layout(); + // we only need to sync keymap once + wl_event_source_timer_update(sync_keymap, 0); + return 1; +} + void activatex11(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, activate); bool need_arrange = false; From 69703158223d53e4d40f43a055c6af7d5bc98b6f Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 8 Feb 2026 18:17:55 +0800 Subject: [PATCH 12/22] Update README.md --- README.md | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/README.md b/README.md index 8aaaeb9b..b78ccfa9 100644 --- a/README.md +++ b/README.md @@ -24,18 +24,7 @@ This project's development is based on [dwl](https://codeberg.org/dwl/dwl/). - Hycov-like overview - Window effects from scenefx (blur, shadow, corner radius, opacity) -Master-Stack Layout - -https://github.com/user-attachments/assets/a9d4776e-b50b-48fb-94ce-651d8a749b8a - -Scroller Layout - -https://github.com/user-attachments/assets/c9bf9415-fad1-4400-bcdc-3ad2d76de85a - -Layer animation - -https://github.com/user-attachments/assets/014c893f-115c-4ae9-8342-f9ae3e9a0df0 - +https://github.com/user-attachments/assets/bb83004a-0563-4b48-ad89-6461a9b78b1f # Our discord [mangowc](https://discord.gg/CPjbDxesh5) From 6b79a432a4e81e757775ce9edf89acc1b4859d74 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 9 Feb 2026 07:52:10 +0800 Subject: [PATCH 13/22] opt: allow none mode in some mouse button --- config.conf | 5 ++--- src/mango.c | 19 +++++++++++++------ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/config.conf b/config.conf index 6cbb3a20..15b654c1 100644 --- a/config.conf +++ b/config.conf @@ -240,12 +240,11 @@ bind=CTRL+ALT,Left,resizewin,-50,+0 bind=CTRL+ALT,Right,resizewin,+50,+0 # Mouse Button Bindings -# NONE mode key only work in ov mode +# btn_left and btn_right can't bind none mod key mousebind=SUPER,btn_left,moveresize,curmove mousebind=NONE,btn_middle,togglemaximizescreen,0 mousebind=SUPER,btn_right,moveresize,curresize -mousebind=NONE,btn_left,toggleoverview,1 -mousebind=NONE,btn_right,killclient,0 + # Axis Bindings axisbind=SUPER,UP,viewtoleft_have_client diff --git a/src/mango.c b/src/mango.c index d6876c5b..44f67285 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1998,14 +1998,21 @@ buttonpress(struct wl_listener *listener, void *data) { if (config.mouse_bindings_count < 1) break; m = &config.mouse_bindings[ji]; + + if (selmon->isoverview && event->button == BTN_LEFT && c) { + toggleoverview(&(Arg){.i = 1}); + return; + } + + if (selmon->isoverview && event->button == BTN_RIGHT && c) { + pending_kill_client(c); + return; + } + if (CLEANMASK(mods) == CLEANMASK(m->mod) && event->button == m->button && m->func && - (selmon->isoverview == 1 || m->button == BTN_MIDDLE) && c) { - m->func(&m->arg); - return; - } else if (CLEANMASK(mods) == CLEANMASK(m->mod) && - event->button == m->button && m->func && - CLEANMASK(m->mod) != 0) { + (CLEANMASK(m->mod) != 0 || + (event->button != BTN_LEFT && event->button != BTN_RIGHT))) { m->func(&m->arg); return; } From b05bc1ce65bdb32b9d2db10ef4e1514c34bf10dd Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 9 Feb 2026 11:50:54 +0800 Subject: [PATCH 14/22] opt: add btn_left and btn_right bind check in config check --- src/config/parse_config.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index a7959617..1ab243c7 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -2359,6 +2359,17 @@ bool parse_option(Config *config, char *key, char *value) { binding->arg.v = NULL; binding->arg.v2 = NULL; binding->arg.v3 = NULL; + + // TODO: remove this in next version + if (binding->mod == 0 && + (binding->button == BTN_LEFT || binding->button == BTN_RIGHT)) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m \033[31m%s\033[33m can't " + "bind to \033[31m%s\033[33m mod key\n", + button_str, mod_str); + return false; + } + binding->func = parse_func_name(func_name, &binding->arg, arg_value, arg_value2, arg_value3, arg_value4, arg_value5); @@ -2376,6 +2387,7 @@ bool parse_option(Config *config, char *key, char *value) { free(binding->arg.v3); binding->arg.v3 = NULL; } + if (!binding->func) fprintf(stderr, "\033[1m\033[31m[ERROR]:\033[33m Unknown " From c7f90cbc6901cd2bed2de079c75717052515ef87 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 9 Feb 2026 18:08:28 +0800 Subject: [PATCH 15/22] bump version to 0.12.1 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 05656093..478ef0e6 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('mango', ['c', 'cpp'], - version : '0.12.0', + version : '0.12.1', ) subdir('protocols') From 783cb86c56f2c0c433df1e3cd4c0bdeb93db6973 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 10 Feb 2026 10:03:08 +0800 Subject: [PATCH 16/22] feat: support match monitor make model serial --- src/config/parse_config.h | 117 ++++++++++++++++++++++++++++++++++---- src/mango.c | 35 +++++++++++- 2 files changed, 140 insertions(+), 12 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 1ab243c7..556fc352 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -101,13 +101,14 @@ typedef struct { } ConfigWinRule; typedef struct { - const char *name; // Monitor name - int32_t rr; // Rotate and flip (assume integer) - float scale; // Monitor scale factor - int32_t x, y; // Monitor position - int32_t width, height; // Monitor resolution - float refresh; // Refresh rate - int32_t vrr; // variable refresh rate + const char *name; // Monitor name + char *make, *model, *serial; // may be NULL + int32_t rr; // Rotate and flip (assume integer) + float scale; // Monitor scale factor + int32_t x, y; // Monitor position + int32_t width, height; // Monitor resolution + float refresh; // Refresh rate + int32_t vrr; // variable refresh rate } ConfigMonitorRule; // 修改后的宏定义 @@ -157,6 +158,9 @@ typedef struct { int32_t id; char *layout_name; char *monitor_name; + char *monitor_make; + char *monitor_model; + char *monitor_serial; float mfact; int32_t nmaster; int32_t no_render_border; @@ -1774,6 +1778,9 @@ bool parse_option(Config *config, char *key, char *value) { // 设置默认值 rule->name = NULL; + rule->make = NULL; + rule->model = NULL; + rule->serial = NULL; rule->rr = 0; rule->scale = 1.0f; rule->x = INT32_MAX; @@ -1797,6 +1804,12 @@ bool parse_option(Config *config, char *key, char *value) { if (strcmp(key, "name") == 0) { rule->name = strdup(val); + } else if (strcmp(key, "make") == 0) { + rule->make = strdup(val); + } else if (strcmp(key, "model") == 0) { + rule->model = strdup(val); + } else if (strcmp(key, "serial") == 0) { + rule->serial = strdup(val); } else if (strcmp(key, "rr") == 0) { rule->rr = CLAMP_INT(atoi(val), 0, 7); } else if (strcmp(key, "scale") == 0) { @@ -1845,6 +1858,9 @@ bool parse_option(Config *config, char *key, char *value) { rule->id = 0; rule->layout_name = NULL; rule->monitor_name = NULL; + rule->monitor_make = NULL; + rule->monitor_model = NULL; + rule->monitor_serial = NULL; rule->nmaster = 0; rule->mfact = 0.0f; rule->no_render_border = 0; @@ -1868,6 +1884,12 @@ bool parse_option(Config *config, char *key, char *value) { rule->layout_name = strdup(val); } else if (strcmp(key, "monitor_name") == 0) { rule->monitor_name = strdup(val); + } else if (strcmp(key, "monitor_make") == 0) { + rule->monitor_make = strdup(val); + } else if (strcmp(key, "monitor_model") == 0) { + rule->monitor_model = strdup(val); + } else if (strcmp(key, "monitor_serial") == 0) { + rule->monitor_serial = strdup(val); } else if (strcmp(key, "no_render_border") == 0) { rule->no_render_border = CLAMP_INT(atoi(val), 0, 1); } else if (strcmp(key, "no_hide") == 0) { @@ -2925,6 +2947,12 @@ void free_config(void) { free((void *)config.tag_rules[i].layout_name); if (config.tag_rules[i].monitor_name) free((void *)config.tag_rules[i].monitor_name); + if (config.tag_rules[i].monitor_make) + free((void *)config.tag_rules[i].monitor_make); + if (config.tag_rules[i].monitor_model) + free((void *)config.tag_rules[i].monitor_model); + if (config.tag_rules[i].monitor_serial) + free((void *)config.tag_rules[i].monitor_serial); } free(config.tag_rules); config.tag_rules = NULL; @@ -2936,6 +2964,12 @@ void free_config(void) { for (int32_t i = 0; i < config.monitor_rules_count; i++) { if (config.monitor_rules[i].name) free((void *)config.monitor_rules[i].name); + if (config.monitor_rules[i].make) + free((void *)config.monitor_rules[i].make); + if (config.monitor_rules[i].model) + free((void *)config.monitor_rules[i].model); + if (config.monitor_rules[i].serial) + free((void *)config.monitor_rules[i].serial); } free(config.monitor_rules); config.monitor_rules = NULL; @@ -3500,6 +3534,7 @@ void reapply_monitor_rules(void) { struct wlr_output_state state; struct wlr_output_mode *internal_mode = NULL; wlr_output_state_init(&state); + bool match_rule = false; wl_list_for_each(m, &mons, link) { if (!m->wlr_output->enabled) { @@ -3511,8 +3546,40 @@ void reapply_monitor_rules(void) { break; mr = &config.monitor_rules[ji]; - if (regex_match(mr->name, m->wlr_output->name)) { + // 检查是否匹配的变量 + match_rule = true; + + // 检查四个标识字段的匹配 + if (mr->name != NULL) { + if (!regex_match(mr->name, m->wlr_output->name)) { + match_rule = false; + } + } + + if (mr->make != NULL) { + if (m->wlr_output->make == NULL || + strcmp(mr->make, m->wlr_output->make) != 0) { + match_rule = false; + } + } + + if (mr->model != NULL) { + if (m->wlr_output->model == NULL || + strcmp(mr->model, m->wlr_output->model) != 0) { + match_rule = false; + } + } + + if (mr->serial != NULL) { + if (m->wlr_output->serial == NULL || + strcmp(mr->serial, m->wlr_output->serial) != 0) { + match_rule = false; + } + } + + // 只有当所有指定的标识都匹配时才应用规则 + if (match_rule) { mx = mr->x == INT32_MAX ? m->m.x : mr->x; my = mr->y == INT32_MAX ? m->m.y : mr->y; vrr = mr->vrr >= 0 ? mr->vrr : 0; @@ -3646,6 +3713,7 @@ void parse_tagrule(Monitor *m) { int32_t i, jk; ConfigTagRule tr; Client *c = NULL; + bool match_rule = false; for (i = 0; i <= LENGTH(tags); i++) { m->pertag->nmasters[i] = default_nmaster; @@ -3656,9 +3724,36 @@ void parse_tagrule(Monitor *m) { tr = config.tag_rules[i]; - if (config.tag_rules_count > 0 && - (!tr.monitor_name || - regex_match(tr.monitor_name, m->wlr_output->name))) { + match_rule = true; + + if (tr.monitor_name != NULL) { + if (!regex_match(tr.monitor_name, m->wlr_output->name)) { + match_rule = false; + } + } + + if (tr.monitor_make != NULL) { + if (m->wlr_output->make == NULL || + strcmp(tr.monitor_make, m->wlr_output->make) != 0) { + match_rule = false; + } + } + + if (tr.monitor_model != NULL) { + if (m->wlr_output->model == NULL || + strcmp(tr.monitor_model, m->wlr_output->model) != 0) { + match_rule = false; + } + } + + if (tr.monitor_serial != NULL) { + if (m->wlr_output->serial == NULL || + strcmp(tr.monitor_serial, m->wlr_output->serial) != 0) { + match_rule = false; + } + } + + if (config.tag_rules_count > 0 && match_rule) { for (jk = 0; jk < LENGTH(layouts); jk++) { if (tr.layout_name && diff --git a/src/mango.c b/src/mango.c index 44f67285..495cba0f 100644 --- a/src/mango.c +++ b/src/mango.c @@ -2737,6 +2737,7 @@ void createmon(struct wl_listener *listener, void *data) { Monitor *m = NULL; struct wlr_output_mode *internal_mode = NULL; bool custom_monitor_mode = false; + bool match_rule = false; if (!wlr_output_init_render(wlr_output, alloc, drw)) return; @@ -2777,7 +2778,39 @@ void createmon(struct wl_listener *listener, void *data) { break; r = &config.monitor_rules[ji]; - if (regex_match(r->name, wlr_output->name)) { + + // 检查是否匹配的变量 + match_rule = true; + + // 检查四个标识字段的匹配 + if (r->name != NULL) { + if (!regex_match(r->name, m->wlr_output->name)) { + match_rule = false; + } + } + + if (r->make != NULL) { + if (m->wlr_output->make == NULL || + strcmp(r->make, m->wlr_output->make) != 0) { + match_rule = false; + } + } + + if (r->model != NULL) { + if (m->wlr_output->model == NULL || + strcmp(r->model, m->wlr_output->model) != 0) { + match_rule = false; + } + } + + if (r->serial != NULL) { + if (m->wlr_output->serial == NULL || + strcmp(r->serial, m->wlr_output->serial) != 0) { + match_rule = false; + } + } + + if (match_rule) { m->m.x = r->x == INT32_MAX ? INT32_MAX : r->x; m->m.y = r->y == INT32_MAX ? INT32_MAX : r->y; vrr = r->vrr >= 0 ? r->vrr : 0; From b5a157038caea46fc1fb710cf7d93a093db0cd25 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 11 Feb 2026 08:31:22 +0800 Subject: [PATCH 17/22] opt: tell the synckeymap timer not need to call anymore --- src/mango.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mango.c b/src/mango.c index 495cba0f..31d38ea6 100644 --- a/src/mango.c +++ b/src/mango.c @@ -6058,8 +6058,9 @@ void virtualpointer(struct wl_listener *listener, void *data) { int32_t synckeymap(void *data) { reset_keyboard_layout(); // we only need to sync keymap once + wlr_log(WLR_INFO, "timer to synckeymap done"); wl_event_source_timer_update(sync_keymap, 0); - return 1; + return 0; } void activatex11(struct wl_listener *listener, void *data) { From 8484093e324a5e305e35dbf4c369d9065a0ad26c Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 11 Feb 2026 20:44:44 +0800 Subject: [PATCH 18/22] fix: crash when pointerfocus to a null scene client --- src/mango.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mango.c b/src/mango.c index 31d38ea6..3ce74222 100644 --- a/src/mango.c +++ b/src/mango.c @@ -4322,7 +4322,7 @@ void pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, uint32_t time) { struct timespec now; - if (sloppyfocus && !start_drag_window && c && time && + if (sloppyfocus && !start_drag_window && c && time && c->scene && c->scene->node.enabled && !c->animation.tagining && (surface != seat->pointer_state.focused_surface) && !client_is_unmanaged(c) && VISIBLEON(c, c->mon)) From 53ee82a726ba1974ddfb67e182cec486678bbf1b Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 12 Feb 2026 11:19:39 +0800 Subject: [PATCH 19/22] feat: make force_tiled_state as a option --- src/config/parse_config.h | 4 ++++ src/mango.c | 27 ++++++++++++++++++++++----- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 556fc352..7e6db028 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -89,6 +89,7 @@ typedef struct { int32_t isterm; int32_t allow_csd; int32_t force_maximize; + int32_t force_tiled_state; int32_t force_tearing; int32_t noswallow; int32_t noblur; @@ -2011,6 +2012,7 @@ bool parse_option(Config *config, char *key, char *value) { rule->isterm = -1; rule->allow_csd = -1; rule->force_maximize = -1; + rule->force_tiled_state = -1; rule->force_tearing = -1; rule->noswallow = -1; rule->noblur = -1; @@ -2123,6 +2125,8 @@ bool parse_option(Config *config, char *key, char *value) { rule->allow_csd = atoi(val); } else if (strcmp(key, "force_maximize") == 0) { rule->force_maximize = atoi(val); + } else if (strcmp(key, "force_tiled_state") == 0) { + rule->force_tiled_state = atoi(val); } else if (strcmp(key, "force_tearing") == 0) { rule->force_tearing = atoi(val); } else if (strcmp(key, "noswallow") == 0) { diff --git a/src/mango.c b/src/mango.c index 3ce74222..63f0e275 100644 --- a/src/mango.c +++ b/src/mango.c @@ -3,6 +3,7 @@ */ #include "wlr-layer-shell-unstable-v1-protocol.h" #include "wlr/util/box.h" +#include "wlr/util/edges.h" #include #include #include @@ -390,6 +391,7 @@ struct Client { int32_t isterm, noswallow; int32_t allow_csd; int32_t force_maximize; + int32_t force_tiled_state; pid_t pid; Client *swallowing, *swallowedby; bool is_clip_to_hide; @@ -1283,6 +1285,7 @@ static void apply_rule_properties(Client *c, const ConfigWinRule *r) { APPLY_INT_PROP(c, r, isterm); APPLY_INT_PROP(c, r, allow_csd); APPLY_INT_PROP(c, r, force_maximize); + APPLY_INT_PROP(c, r, force_tiled_state); APPLY_INT_PROP(c, r, force_tearing); APPLY_INT_PROP(c, r, noswallow); APPLY_INT_PROP(c, r, nofocus); @@ -2438,9 +2441,6 @@ void commitnotify(struct wl_listener *listener, void *data) { setmon(c, NULL, 0, true); /* Make sure to reapply rules in mapnotify() */ - client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | - WLR_EDGE_RIGHT); - uint32_t serial = wlr_xdg_surface_schedule_configure(c->surface.xdg); if (serial > 0) { c->configure_serial = serial; @@ -3856,6 +3856,7 @@ void init_client_properties(Client *c) { c->isterm = 0; c->allow_csd = 0; c->force_maximize = 0; + c->force_tiled_state = 1; c->force_tearing = 0; c->allow_shortcuts_inhibit = SHORTCUTS_INHIBIT_ENABLE; c->scroller_proportion_single = 0.0f; @@ -3970,8 +3971,10 @@ mapnotify(struct wl_listener *listener, void *data) { applyrules(c); - client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | - WLR_EDGE_RIGHT); + if (!c->isfloating || c->force_tiled_state) { + client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | + WLR_EDGE_RIGHT); + } // apply buffer effects of client wlr_scene_node_for_each_buffer(&c->scene_surface->node, @@ -4796,6 +4799,13 @@ setfloating(Client *c, int32_t floating) { if (!c->force_maximize) client_set_maximized(c, false); + if (!c->isfloating || c->force_tiled_state) { + client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | + WLR_EDGE_RIGHT); + } else { + client_set_tiled(c, WLR_EDGE_NONE); + } + arrange(c->mon, false, false); setborder_color(c); printstatus(); @@ -5531,6 +5541,9 @@ void overview_backup(Client *c) { c->ismaximizescreen = 0; } c->bw = c->isnoborder ? 0 : borderpx; + + client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | + WLR_EDGE_RIGHT); } // overview切回到普通视图还原窗口的状态 @@ -5570,6 +5583,10 @@ void overview_restore(Client *c, const Arg *arg) { !c->isfullscreen) { // 如果是在ov模式中创建的窗口,没有bw记录 c->bw = c->isnoborder ? 0 : borderpx; } + + if (c->isfloating && !c->force_tiled_state) { + client_set_tiled(c, WLR_EDGE_NONE); + } } void handlecursoractivity(void) { From bc52b95c1ef885127c1aa1a63c847db9d2b0c818 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 12 Feb 2026 16:38:19 +0800 Subject: [PATCH 20/22] opt: make x11 unmanaged window coordinate auto ajust the monitor change --- src/mango.c | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/src/mango.c b/src/mango.c index 63f0e275..e84c4989 100644 --- a/src/mango.c +++ b/src/mango.c @@ -944,6 +944,7 @@ static struct wl_listener keyboard_shortcuts_inhibit_new_inhibitor = { .notify = handle_keyboard_shortcuts_inhibit_new_inhibitor}; #ifdef XWAYLAND +static void fix_xwayland_unmanaged_coordinate(struct wlr_box *box); static int32_t synckeymap(void *data); static void activatex11(struct wl_listener *listener, void *data); static void configurex11(struct wl_listener *listener, void *data); @@ -3914,18 +3915,19 @@ mapnotify(struct wl_listener *listener, void *data) { */ if (client_is_unmanaged(c)) { /* Unmanaged clients always are floating */ +#ifdef XWAYLAND + if (client_is_x11(c)) { + fix_xwayland_unmanaged_coordinate(&c->geom); + LISTEN(&c->surface.xwayland->events.set_geometry, &c->set_geometry, + setgeometrynotify); + } +#endif wlr_scene_node_reparent(&c->scene->node, layers[LyrOverlay]); wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); if (client_wants_focus(c)) { focusclient(c, 1); exclusive_focus = c; } -#ifdef XWAYLAND - if (client_is_x11(c)) { - LISTEN(&c->surface.xwayland->events.set_geometry, &c->set_geometry, - setgeometrynotify); - } -#endif return; } @@ -6072,6 +6074,25 @@ void virtualpointer(struct wl_listener *listener, void *data) { } #ifdef XWAYLAND +void fix_xwayland_unmanaged_coordinate(struct wlr_box *box) { + if (!selmon) + return; + if (box->x >= selmon->m.x && box->x <= selmon->m.x + selmon->m.width && + box->y >= selmon->m.y && box->y <= selmon->m.y + selmon->m.height) + return; + + Monitor *source_monitor = xytomon(box->x, box->y); + + if (!source_monitor) + return; + + int xoffset = box->x - source_monitor->m.x; + int yoffset = box->y - source_monitor->m.y; + + box->x = selmon->m.x + xoffset; + box->y = selmon->m.y + yoffset; +} + int32_t synckeymap(void *data) { reset_keyboard_layout(); // we only need to sync keymap once From 17acdae69c2fbda70dfdd22f0cfa268dc75ba5c9 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 12 Feb 2026 18:12:01 +0800 Subject: [PATCH 21/22] opt: make x11 floating window coordinate auto ajust the monitor change --- src/mango.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/mango.c b/src/mango.c index e84c4989..8633dcd1 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1375,6 +1375,14 @@ void applyrules(Client *c) { Monitor *mon = parent && parent->mon ? parent->mon : selmon; c->isfloating = client_is_float_type(c) || parent; + +#ifdef XWAYLAND + if (c->isfloating && client_is_x11(c)) { + fix_xwayland_unmanaged_coordinate(&c->geom); + c->float_geom = c->geom; + } +#endif + if (!(appid = client_get_appid(c))) appid = broken; if (!(title = client_get_title(c))) From 313adefd10c6de9671aef85fc715df3f7564cf21 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 12 Feb 2026 18:44:56 +0800 Subject: [PATCH 22/22] opt: better x11 coordinate adjust --- src/mango.c | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/mango.c b/src/mango.c index 8633dcd1..ca25dbca 100644 --- a/src/mango.c +++ b/src/mango.c @@ -944,7 +944,7 @@ static struct wl_listener keyboard_shortcuts_inhibit_new_inhibitor = { .notify = handle_keyboard_shortcuts_inhibit_new_inhibitor}; #ifdef XWAYLAND -static void fix_xwayland_unmanaged_coordinate(struct wlr_box *box); +static void fix_xwayland_unmanaged_coordinate(Client *c); static int32_t synckeymap(void *data); static void activatex11(struct wl_listener *listener, void *data); static void configurex11(struct wl_listener *listener, void *data); @@ -1378,7 +1378,7 @@ void applyrules(Client *c) { #ifdef XWAYLAND if (c->isfloating && client_is_x11(c)) { - fix_xwayland_unmanaged_coordinate(&c->geom); + fix_xwayland_unmanaged_coordinate(c); c->float_geom = c->geom; } #endif @@ -3925,7 +3925,7 @@ mapnotify(struct wl_listener *listener, void *data) { /* Unmanaged clients always are floating */ #ifdef XWAYLAND if (client_is_x11(c)) { - fix_xwayland_unmanaged_coordinate(&c->geom); + fix_xwayland_unmanaged_coordinate(c); LISTEN(&c->surface.xwayland->events.set_geometry, &c->set_geometry, setgeometrynotify); } @@ -6082,23 +6082,16 @@ void virtualpointer(struct wl_listener *listener, void *data) { } #ifdef XWAYLAND -void fix_xwayland_unmanaged_coordinate(struct wlr_box *box) { +void fix_xwayland_unmanaged_coordinate(Client *c) { if (!selmon) return; - if (box->x >= selmon->m.x && box->x <= selmon->m.x + selmon->m.width && - box->y >= selmon->m.y && box->y <= selmon->m.y + selmon->m.height) + + // 1. 如果窗口已经在当前活动显示器内,直接返回 + if (c->geom.x >= selmon->m.x && c->geom.x < selmon->m.x + selmon->m.width && + c->geom.y >= selmon->m.y && c->geom.y < selmon->m.y + selmon->m.height) return; - Monitor *source_monitor = xytomon(box->x, box->y); - - if (!source_monitor) - return; - - int xoffset = box->x - source_monitor->m.x; - int yoffset = box->y - source_monitor->m.y; - - box->x = selmon->m.x + xoffset; - box->y = selmon->m.y + yoffset; + c->geom = setclient_coordinate_center(c, selmon, c->geom, 0, 0); } int32_t synckeymap(void *data) {