From 49921eadfa251641379c2f2cd824034eb7075e2f Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 21 Jan 2026 13:43:40 +0800 Subject: [PATCH 01/78] feat: add drag_corner drag_warp_cursor --- src/config/parse_config.h | 10 ++++++++++ src/config/preset.h | 4 +++- src/dispatch/bind_define.h | 30 ++++++++++++++++++++++++++---- src/mango.c | 32 ++++++++++++++++++++++++++------ 4 files changed, 65 insertions(+), 11 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index b2535b1..15857ae 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -243,6 +243,8 @@ typedef struct { int32_t idleinhibit_ignore_visible; int32_t sloppyfocus; int32_t warpcursor; + int32_t drag_corner; + int32_t drag_warp_cursor; /* keyboard */ int32_t repeat_rate; @@ -1475,6 +1477,10 @@ void parse_option(Config *config, char *key, char *value) { config->sloppyfocus = atoi(value); } else if (strcmp(key, "warpcursor") == 0) { config->warpcursor = atoi(value); + } else if (strcmp(key, "drag_corner") == 0) { + config->drag_corner = atoi(value); + } else if (strcmp(key, "drag_warp_cursor") == 0) { + config->drag_warp_cursor = atoi(value); } else if (strcmp(key, "smartgaps") == 0) { config->smartgaps = atoi(value); } else if (strcmp(key, "repeat_rate") == 0) { @@ -2757,6 +2763,8 @@ void override_config(void) { CLAMP_INT(config.idleinhibit_ignore_visible, 0, 1); sloppyfocus = CLAMP_INT(config.sloppyfocus, 0, 1); warpcursor = CLAMP_INT(config.warpcursor, 0, 1); + drag_corner = CLAMP_INT(config.drag_corner, 0, 4); + drag_warp_cursor = CLAMP_INT(config.drag_warp_cursor, 0, 1); focus_cross_monitor = CLAMP_INT(config.focus_cross_monitor, 0, 1); exchange_cross_monitor = CLAMP_INT(config.exchange_cross_monitor, 0, 1); scratchpad_cross_monitor = CLAMP_INT(config.scratchpad_cross_monitor, 0, 1); @@ -2954,6 +2962,8 @@ void set_value_default() { config.cursor_hide_timeout = cursor_hide_timeout; config.warpcursor = warpcursor; /* Warp cursor to focused client */ + config.drag_corner = drag_corner; + config.drag_warp_cursor = drag_warp_cursor; config.repeat_rate = repeat_rate; config.repeat_delay = repeat_delay; diff --git a/src/config/preset.h b/src/config/preset.h index 6f3cd89..935ea0c 100644 --- a/src/config/preset.h +++ b/src/config/preset.h @@ -101,7 +101,9 @@ int32_t overviewgappo = 30; /* overview时 窗口与窗口 缝隙大小 */ * behavior */ float fullscreen_bg[] = {0.1, 0.1, 0.1, 1.0}; -int32_t warpcursor = 1; /* Warp cursor to focused client */ +int32_t warpcursor = 1; +int32_t drag_corner = 3; +int32_t drag_warp_cursor = 1; int32_t xwayland_persistence = 1; /* xwayland persistence */ int32_t syncobj_enable = 0; int32_t adaptive_sync = 0; diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 95d365c..f27ed67 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -338,6 +338,9 @@ int32_t killclient(const Arg *arg) { } int32_t moveresize(const Arg *arg) { + const char *cursors[] = {"nw-resize", "ne-resize", "sw-resize", + "se-resize"}; + if (cursor_mode != CurNormal && cursor_mode != CurPressed) return 0; xytonode(cursor->x, cursor->y, NULL, &grabc, NULL, NULL, NULL); @@ -363,10 +366,29 @@ int32_t moveresize(const Arg *arg) { /* Doesn't work for X11 output - the next absolute motion event * returns the cursor to where it started */ if (grabc->isfloating) { - wlr_cursor_warp_closest(cursor, NULL, - grabc->geom.x + grabc->geom.width, - grabc->geom.y + grabc->geom.height); - wlr_cursor_set_xcursor(cursor, cursor_mgr, "bottom_right_corner"); + rzcorner = drag_corner; + grabcx = (int)round(cursor->x); + grabcy = (int)round(cursor->y); + if (rzcorner == 4) + /* identify the closest corner index */ + rzcorner = (grabcx - grabc->geom.x < + grabc->geom.x + grabc->geom.width - grabcx + ? 0 + : 1) + + (grabcy - grabc->geom.y < + grabc->geom.y + grabc->geom.height - grabcy + ? 0 + : 2); + + if (drag_warp_cursor) { + grabcx = rzcorner & 1 ? grabc->geom.x + grabc->geom.width + : grabc->geom.x; + grabcy = rzcorner & 2 ? grabc->geom.y + grabc->geom.height + : grabc->geom.y; + wlr_cursor_warp_closest(cursor, NULL, grabcx, grabcy); + } + + wlr_cursor_set_xcursor(cursor, cursor_mgr, cursors[rzcorner]); } else { wlr_cursor_set_xcursor(cursor, cursor_mgr, "grab"); } diff --git a/src/mango.c b/src/mango.c index 3a2331d..f61e27f 100644 --- a/src/mango.c +++ b/src/mango.c @@ -841,6 +841,7 @@ static struct wl_list inputdevices; static struct wl_list keyboard_shortcut_inhibitors; static uint32_t cursor_mode; static Client *grabc; +static int32_t rzcorner; static int32_t grabcx, grabcy; /* client-relative */ static int32_t drag_begin_cursorx, drag_begin_cursory; /* client-relative */ static bool start_drag_window = false; @@ -3988,6 +3989,30 @@ void motionabsolute(struct wl_listener *listener, void *data) { motionnotify(event->time_msec, &event->pointer->base, dx, dy, dx, dy); } +void resize_floating_window(Client *grabc) { + int cdx = (int)round(cursor->x) - grabcx; + int cdy = (int)round(cursor->y) - grabcy; + + cdx = !(rzcorner & 1) && grabc->geom.width - 2 * (int)grabc->bw - cdx < 1 + ? 0 + : cdx; + cdy = !(rzcorner & 2) && grabc->geom.height - 2 * (int)grabc->bw - cdy < 1 + ? 0 + : cdy; + + const struct wlr_box box = { + .x = grabc->geom.x + (rzcorner & 1 ? 0 : cdx), + .y = grabc->geom.y + (rzcorner & 2 ? 0 : cdy), + .width = grabc->geom.width + (rzcorner & 1 ? cdx : -cdx), + .height = grabc->geom.height + (rzcorner & 2 ? cdy : -cdy)}; + + grabc->float_geom = box; + + resize(grabc, box, 1); + grabcx += cdx; + grabcy += cdy; +} + void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy, double dx_unaccel, double dy_unaccel) { double sx = 0, sy = 0, sx_confined, sy_confined; @@ -4065,14 +4090,9 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, } else if (cursor_mode == CurResize) { if (grabc->isfloating) { grabc->iscustomsize = 1; - grabc->float_geom = (struct wlr_box){ - .x = grabc->geom.x, - .y = grabc->geom.y, - .width = (int32_t)round(cursor->x) - grabc->geom.x, - .height = (int32_t)round(cursor->y) - grabc->geom.y}; if (last_apply_drap_time == 0 || time - last_apply_drap_time > drag_refresh_interval) { - resize(grabc, grabc->float_geom, 1); + resize_floating_window(grabc); last_apply_drap_time = time; } return; From 334bc076a0d0b368efef5626f8e6d4b429133c3f Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 24 Jan 2026 11:43:24 +0800 Subject: [PATCH 02/78] opt: optimize smartgap --- src/fetch/client.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/fetch/client.h b/src/fetch/client.h index e843d69..bf30e17 100644 --- a/src/fetch/client.h +++ b/src/fetch/client.h @@ -12,7 +12,9 @@ bool check_hit_no_border(Client *c) { } } - if (no_border_when_single && c && c->mon && c->mon->visible_clients == 1) { + if (no_border_when_single && c && c->mon && + ((ISSCROLLTILED(c) && c->mon->visible_scroll_tiling_clients == 1) || + c->mon->visible_clients == 1)) { hit_no_border = true; } return hit_no_border; From 0652f99e6e30aaa7e85c6828ba729bf4075a8270 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 24 Jan 2026 12:04:17 +0800 Subject: [PATCH 03/78] opt: change preset config prefer --- src/config/preset.h | 3 +-- src/layout/layout.h | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/config/preset.h b/src/config/preset.h index 935ea0c..5dea6cb 100644 --- a/src/config/preset.h +++ b/src/config/preset.h @@ -62,7 +62,7 @@ float scratchpad_height_ratio = 0.9; int32_t scroller_structs = 20; float scroller_default_proportion = 0.9; float scroller_default_proportion_single = 1.0; -int32_t scroller_ignore_proportion_single = 0; +int32_t scroller_ignore_proportion_single = 1; int32_t scroller_focus_center = 0; int32_t scroller_prefer_center = 0; int32_t focus_cross_monitor = 0; @@ -235,4 +235,3 @@ double shadows_blur = 15; int32_t shadows_position_x = 0; int32_t shadows_position_y = 0; float shadowscolor[] = COLOR(0x000000ff); -; diff --git a/src/layout/layout.h b/src/layout/layout.h index 169ab11..f896ac2 100644 --- a/src/layout/layout.h +++ b/src/layout/layout.h @@ -17,8 +17,8 @@ static void tgmix(Monitor *m); Layout overviewlayout = {"󰃇", overview, "overview"}; enum { - SCROLLER, TILE, + SCROLLER, GRID, MONOCLE, DECK, @@ -34,8 +34,8 @@ enum { Layout layouts[] = { // 最少两个,不能删除少于两个 /* symbol arrange function name */ - {"S", scroller, "scroller", SCROLLER}, // 滚动布局 {"T", tile, "tile", TILE}, // 平铺布局 + {"S", scroller, "scroller", SCROLLER}, // 滚动布局 {"G", grid, "grid", GRID}, // 格子布局 {"M", monocle, "monocle", MONOCLE}, // 单屏布局 {"K", deck, "deck", DECK}, // 卡片布局 From f1cca251b8ef2bfce0f5dadbb0d1d609a51bd0ca Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 24 Jan 2026 19:48:43 +0800 Subject: [PATCH 04/78] fix: bordercorlor not update when it is in open animaiton --- src/animation/client.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/animation/client.h b/src/animation/client.h index 849bc10..3f44727 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -1131,6 +1131,7 @@ bool client_apply_focus_opacity(Client *c) { target_opacity = opacity; } client_set_opacity(c, target_opacity); + client_set_border_color(c, c->opacity_animation.target_border_color); } else if (animations && c->opacity_animation.running) { struct timespec now; From 00de5230393d75d22416c0fbe9c93a6ec5434dea Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 24 Jan 2026 23:02:49 +0800 Subject: [PATCH 05/78] fix: border apply error for smartgap --- src/animation/client.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/animation/client.h b/src/animation/client.h index 3f44727..15a0d16 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -363,7 +363,6 @@ void apply_border(Client *c) { current_corner_location = set_client_corner_location(c); } - // Handle no-border cases if (hit_no_border && smartgaps) { c->bw = 0; c->fake_no_border = true; @@ -981,6 +980,12 @@ void resize(Client *c, struct wlr_box geo, int32_t interact) { c->bw = 0; } + bool hit_no_border = check_hit_no_border(c); + if (hit_no_border && smartgaps) { + c->bw = 0; + c->fake_no_border = true; + } + // c->geom 是真实的窗口大小和位置,跟过度的动画无关,用于计算布局 c->configure_serial = client_set_size(c, c->geom.width - 2 * c->bw, c->geom.height - 2 * c->bw); From 05010a1114bb66cb7a3d0df0f1939e00c338def2 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 26 Jan 2026 18:35:08 +0800 Subject: [PATCH 06/78] fix: crash when some qt app commit a null surface --- src/mango.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/mango.c b/src/mango.c index f61e27f..e285a89 100644 --- a/src/mango.c +++ b/src/mango.c @@ -2443,12 +2443,12 @@ void commitpopup(struct wl_listener *listener, void *data) { struct wlr_box box; int32_t type = -1; - if (!popup->base->initial_commit) - return; + if (!popup || !popup->base->initial_commit) + goto commitpopup_listen_free; type = toplevel_from_wlr_surface(popup->base->surface, &c, &l); - if (!popup->parent || type < 0) - return; + if (!popup->parent || !popup->parent->data || type < 0) + goto commitpopup_listen_free; wlr_scene_node_raise_to_top(popup->parent->data); @@ -2456,13 +2456,14 @@ void commitpopup(struct wl_listener *listener, void *data) { wlr_scene_xdg_surface_create(popup->parent->data, popup->base); if ((l && !l->mon) || (c && !c->mon)) { wlr_xdg_popup_destroy(popup); - return; + goto commitpopup_listen_free; } box = type == LayerShell ? l->mon->m : c->mon->w; box.x -= (type == LayerShell ? l->scene->node.x : c->geom.x); box.y -= (type == LayerShell ? l->scene->node.y : c->geom.y); wlr_xdg_popup_unconstrain_from_box(popup, &box); +commitpopup_listen_free: wl_list_remove(&listener->link); free(listener); } From 6624d80522b512a381e88315787e1d5ed8bcdd19 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 27 Jan 2026 14:45:28 +0800 Subject: [PATCH 07/78] break change: new monitorrule format --- src/config/parse_config.h | 237 ++++++++++++++++++++------------------ src/layout/horizontal.h | 9 +- src/layout/vertical.h | 9 +- src/mango.c | 42 +++---- 4 files changed, 149 insertions(+), 148 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 15857ae..f94e679 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -101,15 +101,13 @@ typedef struct { } ConfigWinRule; typedef struct { - const char *name; // 显示器名称 - float mfact; // 主区域比例 - int32_t nmaster; // 主区域窗口数量 - const char *layout; // 布局名称(字符串) - int32_t rr; // 旋转和翻转(假设为整数) - float scale; // 显示器缩放比例 - int32_t x, y; // 显示器位置 - int32_t width, height; // 显示器分辨率 - float refresh; // 刷新率 + 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 } ConfigMonitorRule; // 修改后的宏定义 @@ -156,9 +154,11 @@ typedef struct { } GestureBinding; typedef struct { - int32_t id; // 标签ID (1-9) - char *layout_name; // 布局名称 + int32_t id; + char *layout_name; char *monitor_name; + float mfact; + int32_t nmaster; int32_t no_render_border; int32_t no_hide; } ConfigTagRule; @@ -1605,6 +1605,66 @@ void parse_option(Config *config, char *key, char *value) { } else { convert_hex_to_rgba(config->overlaycolor, color); } + } else if (strcmp(key, "monitorrule") == 0) { + config->monitor_rules = + realloc(config->monitor_rules, (config->monitor_rules_count + 1) * + sizeof(ConfigMonitorRule)); + if (!config->monitor_rules) { + fprintf(stderr, + "Error: Failed to allocate memory for monitor rules\n"); + return; + } + + ConfigMonitorRule *rule = + &config->monitor_rules[config->monitor_rules_count]; + memset(rule, 0, sizeof(ConfigMonitorRule)); + + // 设置默认值 + rule->name = NULL; + rule->rr = 0; + rule->scale = 1.0f; + rule->x = INT32_MAX; + rule->y = INT32_MAX; + rule->width = -1; + rule->height = -1; + rule->refresh = 0.0f; + rule->vrr = 0; + + char *token = strtok(value, ","); + while (token != NULL) { + char *colon = strchr(token, ':'); + if (colon != NULL) { + *colon = '\0'; + char *key = token; + char *val = colon + 1; + + trim_whitespace(key); + trim_whitespace(val); + + if (strcmp(key, "name") == 0) { + rule->name = strdup(val); + } else if (strcmp(key, "rr") == 0) { + rule->rr = CLAMP_INT(atoi(val), 0, 7); + } else if (strcmp(key, "scale") == 0) { + rule->scale = CLAMP_FLOAT(atof(val), 0.001f, 1000.0f); + } else if (strcmp(key, "x") == 0) { + rule->x = atoi(val); + } else if (strcmp(key, "y") == 0) { + rule->y = atoi(val); + } else if (strcmp(key, "width") == 0) { + rule->width = CLAMP_INT(atoi(val), 1, INT32_MAX); + } else if (strcmp(key, "height") == 0) { + rule->height = CLAMP_INT(atoi(val), 1, INT32_MAX); + } else if (strcmp(key, "refresh") == 0) { + rule->refresh = CLAMP_FLOAT(atof(val), 0.001f, 1000.0f); + } else if (strcmp(key, "vrr") == 0) { + rule->vrr = CLAMP_INT(atoi(val), 0, 1); + } + } + token = strtok(NULL, ","); + } + + config->monitor_rules_count++; } else if (strcmp(key, "tagrule") == 0) { config->tag_rules = realloc(config->tag_rules, @@ -1621,6 +1681,10 @@ void parse_option(Config *config, char *key, char *value) { rule->id = 0; rule->layout_name = NULL; rule->monitor_name = NULL; + rule->nmaster = 0; + rule->mfact = 0.0f; + rule->no_render_border = 0; + rule->no_hide = 0; char *token = strtok(value, ","); while (token != NULL) { @@ -1643,6 +1707,10 @@ void parse_option(Config *config, char *key, char *value) { rule->no_render_border = CLAMP_INT(atoi(val), 0, 1); } else if (strcmp(key, "no_hide") == 0) { rule->no_hide = CLAMP_INT(atoi(val), 0, 1); + } else if (strcmp(key, "nmaster") == 0) { + rule->nmaster = CLAMP_INT(atoi(val), 1, 99); + } else if (strcmp(key, "mfact") == 0) { + rule->mfact = CLAMP_FLOAT(atof(val), 0.1f, 0.9f); } } token = strtok(NULL, ","); @@ -1871,75 +1939,6 @@ void parse_option(Config *config, char *key, char *value) { token = strtok(NULL, ","); } config->window_rules_count++; - } else if (strcmp(key, "monitorrule") == 0) { - config->monitor_rules = - realloc(config->monitor_rules, (config->monitor_rules_count + 1) * - sizeof(ConfigMonitorRule)); - if (!config->monitor_rules) { - fprintf(stderr, - "Error: Failed to allocate memory for monitor rules\n"); - return; - } - - ConfigMonitorRule *rule = - &config->monitor_rules[config->monitor_rules_count]; - memset(rule, 0, sizeof(ConfigMonitorRule)); - - // 临时存储每个字段的原始字符串 - char raw_name[256], raw_layout[256]; - char raw_mfact[256], raw_nmaster[256], raw_rr[256]; - char raw_scale[256], raw_x[256], raw_y[256], raw_width[256], - raw_height[256], raw_refresh[256]; - - // 先读取所有字段为字符串 - int32_t parsed = - sscanf(value, - "%255[^,],%255[^,],%255[^,],%255[^,],%255[^,],%255[" - "^,],%255[^,],%255[^,],%255[^,],%255[^,],%255s", - raw_name, raw_mfact, raw_nmaster, raw_layout, raw_rr, - raw_scale, raw_x, raw_y, raw_width, raw_height, raw_refresh); - - if (parsed == 11) { - // 修剪每个字段的空格 - trim_whitespace(raw_name); - trim_whitespace(raw_mfact); - trim_whitespace(raw_nmaster); - trim_whitespace(raw_layout); - trim_whitespace(raw_rr); - trim_whitespace(raw_scale); - trim_whitespace(raw_x); - trim_whitespace(raw_y); - trim_whitespace(raw_width); - trim_whitespace(raw_height); - trim_whitespace(raw_refresh); - - // 转换修剪后的字符串为特定类型 - rule->name = strdup(raw_name); - rule->layout = strdup(raw_layout); - rule->mfact = atof(raw_mfact); - rule->nmaster = atoi(raw_nmaster); - rule->rr = atoi(raw_rr); - rule->scale = atof(raw_scale); - rule->x = atoi(raw_x); - rule->y = atoi(raw_y); - rule->width = atoi(raw_width); - rule->height = atoi(raw_height); - rule->refresh = atof(raw_refresh); - - if (!rule->name || !rule->layout) { - if (rule->name) - free((void *)rule->name); - if (rule->layout) - free((void *)rule->layout); - fprintf(stderr, - "Error: Failed to allocate memory for monitor rule\n"); - return; - } - - config->monitor_rules_count++; - } else { - fprintf(stderr, "Error: Invalid monitorrule format: %s\n", value); - } } else if (strncmp(key, "env", 3) == 0) { char env_type[256], env_value[256]; @@ -2483,18 +2482,6 @@ void free_config(void) { config.window_rules_count = 0; } - // 释放 monitor_rules - if (config.monitor_rules) { - for (int32_t i = 0; i < config.monitor_rules_count; i++) { - ConfigMonitorRule *rule = &config.monitor_rules[i]; - free((void *)rule->name); - free((void *)rule->layout); - } - free(config.monitor_rules); - config.monitor_rules = NULL; - config.monitor_rules_count = 0; - } - // 释放 key_bindings if (config.key_bindings) { for (i = 0; i < config.key_bindings_count; i++) { @@ -2613,6 +2600,17 @@ void free_config(void) { config.tag_rules_count = 0; } + // 释放 monitor_rules + if (config.monitor_rules) { + for (int32_t i = 0; i < config.monitor_rules_count; i++) { + if (config.monitor_rules[i].name) + free((void *)config.monitor_rules[i].name); + } + free(config.monitor_rules); + config.monitor_rules = NULL; + config.monitor_rules_count = 0; + } + // 释放 layer_rules if (config.layer_rules) { for (int32_t i = 0; i < config.layer_rules_count; i++) { @@ -3164,7 +3162,8 @@ void reset_blur_params(void) { void reapply_monitor_rules(void) { ConfigMonitorRule *mr; Monitor *m = NULL; - int32_t ji, jk; + int32_t ji, vrr; + int32_t mx, my; struct wlr_output_state state; struct wlr_output_mode *internal_mode = NULL; wlr_output_state_init(&state); @@ -3179,20 +3178,11 @@ void reapply_monitor_rules(void) { break; mr = &config.monitor_rules[ji]; - if (!mr->name || regex_match(mr->name, m->wlr_output->name)) { + if (regex_match(mr->name, m->wlr_output->name)) { - m->mfact = mr->mfact; - m->nmaster = mr->nmaster; - m->m.x = mr->x; - m->m.y = mr->y; - - if (mr->layout) { - for (jk = 0; jk < LENGTH(layouts); jk++) { - if (strcmp(layouts[jk].name, mr->layout) == 0) { - m->lt = &layouts[jk]; - } - } - } + 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; if (mr->width > 0 && mr->height > 0 && mr->refresh > 0) { internal_mode = get_nearest_output_mode( @@ -3206,17 +3196,16 @@ void reapply_monitor_rules(void) { } } + if (vrr) { + enable_adaptive_sync(m, &state); + } + wlr_output_state_set_scale(&state, mr->scale); wlr_output_state_set_transform(&state, mr->rr); - wlr_output_layout_add(output_layout, m->wlr_output, mr->x, - mr->y); + wlr_output_layout_add(output_layout, m->wlr_output, mx, my); } } - if (adaptive_sync) { - enable_adaptive_sync(m, &state); - } - wlr_output_commit_state(m->wlr_output, &state); wlr_output_state_finish(&state); updatemons(NULL, NULL); @@ -3319,6 +3308,12 @@ void reapply_master(void) { void parse_tagrule(Monitor *m) { int32_t i, jk; ConfigTagRule tr; + Client *c = NULL; + + for (i = 0; i <= LENGTH(tags); i++) { + m->pertag->nmasters[i] = default_nmaster; + m->pertag->mfacts[i] = default_mfact; + } for (i = 0; i < config.tag_rules_count; i++) { @@ -3335,7 +3330,23 @@ void parse_tagrule(Monitor *m) { } } - m->pertag->no_hide[tr.id] = tr.no_hide; + if (tr.no_hide >= 0) + m->pertag->no_hide[tr.id] = tr.no_hide; + if (tr.nmaster >= 1) + m->pertag->nmasters[tr.id] = tr.nmaster; + if (tr.mfact > 0.0f) + m->pertag->mfacts[tr.id] = tr.mfact; + if (tr.no_render_border >= 0) + m->pertag->no_render_border[tr.id] = tr.no_render_border; + } + } + + for (i = 1; i <= LENGTH(tags); i++) { + wl_list_for_each(c, &clients, link) { + if ((c->tags & (1 << (i - 1)) & TAGMASK) && ISTILED(c)) { + if (m->pertag->mfacts[i] > 0.0f) + c->master_mfact_per = m->pertag->mfacts[i]; + } } } } diff --git a/src/layout/horizontal.h b/src/layout/horizontal.h index 87b6033..e1a335d 100644 --- a/src/layout/horizontal.h +++ b/src/layout/horizontal.h @@ -117,6 +117,7 @@ void deck(Monitor *m) { Client *c = NULL; Client *fc = NULL; float mfact; + uint32_t nmasters = m->pertag->nmasters[m->pertag->curtag]; int32_t cur_gappih = enablegaps ? m->gappih : 0; int32_t cur_gappoh = enablegaps ? m->gappoh : 0; @@ -142,8 +143,8 @@ void deck(Monitor *m) { : m->pertag->mfacts[m->pertag->curtag]; // Calculate master width including outer gaps - if (n > m->nmaster) - mw = m->nmaster ? round((m->w.width - 2 * cur_gappoh) * mfact) : 0; + if (n > nmasters) + mw = nmasters ? round((m->w.width - 2 * cur_gappoh) * mfact) : 0; else mw = m->w.width - 2 * cur_gappoh; @@ -151,7 +152,7 @@ void deck(Monitor *m) { wl_list_for_each(c, &clients, link) { if (!VISIBLEON(c, m) || !ISTILED(c)) continue; - if (i < m->nmaster) { + if (i < nmasters) { c->master_mfact_per = mfact; // Master area clients resize( @@ -160,7 +161,7 @@ void deck(Monitor *m) { .y = m->w.y + cur_gappov + my, .width = mw, .height = (m->w.height - 2 * cur_gappov - my) / - (MIN(n, m->nmaster) - i)}, + (MIN(n, nmasters) - i)}, 0); my += c->geom.height; } else { diff --git a/src/layout/vertical.h b/src/layout/vertical.h index 2c2dc66..f7bd442 100644 --- a/src/layout/vertical.h +++ b/src/layout/vertical.h @@ -110,6 +110,7 @@ void vertical_deck(Monitor *m) { Client *c = NULL; Client *fc = NULL; float mfact; + uint32_t nmasters = m->pertag->nmasters[m->pertag->curtag]; int32_t cur_gappiv = enablegaps ? m->gappiv : 0; int32_t cur_gappoh = enablegaps ? m->gappoh : 0; @@ -134,8 +135,8 @@ void vertical_deck(Monitor *m) { mfact = fc->master_mfact_per > 0.0f ? fc->master_mfact_per : m->pertag->mfacts[m->pertag->curtag]; - if (n > m->nmaster) - mh = m->nmaster ? round((m->w.height - 2 * cur_gappov) * mfact) : 0; + if (n > nmasters) + mh = nmasters ? round((m->w.height - 2 * cur_gappov) * mfact) : 0; else mh = m->w.height - 2 * cur_gappov; @@ -143,13 +144,13 @@ void vertical_deck(Monitor *m) { wl_list_for_each(c, &clients, link) { if (!VISIBLEON(c, m) || !ISTILED(c)) continue; - if (i < m->nmaster) { + if (i < nmasters) { resize( c, (struct wlr_box){.x = m->w.x + cur_gappoh + mx, .y = m->w.y + cur_gappov, .width = (m->w.width - 2 * cur_gappoh - mx) / - (MIN(n, m->nmaster) - i), + (MIN(n, nmasters) - i), .height = mh}, 0); mx += c->geom.width; diff --git a/src/mango.c b/src/mango.c index e285a89..ed4e12a 100644 --- a/src/mango.c +++ b/src/mango.c @@ -500,11 +500,8 @@ struct Monitor { struct wlr_box m; /* monitor area, layout-relative */ struct wlr_box w; /* window area, layout-relative */ struct wl_list layers[4]; /* LayerSurface::link */ - const Layout *lt; uint32_t seltags; uint32_t tagset[2]; - double mfact; - int32_t nmaster; struct wl_list dwl_ipc_outputs; int32_t gappih; /* horizontal gap between windows */ @@ -898,6 +895,7 @@ struct Pertag { int32_t nmasters[LENGTH(tags) + 1]; /* number of windows in master area */ float mfacts[LENGTH(tags) + 1]; /* mfacts per tag */ bool no_hide[LENGTH(tags) + 1]; /* no_hide per tag */ + bool no_render_border[LENGTH(tags) + 1]; /* no_render_border per tag */ const Layout *ltidxs[LENGTH(tags) + 1]; /* matrix of tags and layouts indexes */ }; @@ -2667,7 +2665,7 @@ void createmon(struct wl_listener *listener, void *data) { struct wlr_output *wlr_output = data; const ConfigMonitorRule *r; uint32_t i; - int32_t ji, jk; + int32_t ji, vrr; struct wlr_output_state state; Monitor *m = NULL; struct wlr_output_mode *internal_mode = NULL; @@ -2703,30 +2701,19 @@ void createmon(struct wl_listener *listener, void *data) { m->sel = NULL; m->is_in_hotarea = 0; float scale = 1; - m->mfact = default_mfact; - m->nmaster = default_nmaster; enum wl_output_transform rr = WL_OUTPUT_TRANSFORM_NORMAL; wlr_output_state_set_scale(&state, scale); wlr_output_state_set_transform(&state, rr); - m->lt = &layouts[0]; for (ji = 0; ji < config.monitor_rules_count; ji++) { if (config.monitor_rules_count < 1) break; r = &config.monitor_rules[ji]; - if (!r->name || regex_match(r->name, wlr_output->name)) { - m->mfact = r->mfact; - m->nmaster = r->nmaster; - m->m.x = r->x; - m->m.y = r->y; - if (r->layout) { - for (jk = 0; jk < LENGTH(layouts); jk++) { - if (strcmp(layouts[jk].name, r->layout) == 0) { - m->lt = &layouts[jk]; - } - } - } + if (regex_match(r->name, wlr_output->name)) { + 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; scale = r->scale; rr = r->rr; @@ -2743,6 +2730,11 @@ void createmon(struct wl_listener *listener, void *data) { (int32_t)roundf(r->refresh * 1000)); } } + + if (vrr) { + enable_adaptive_sync(m, &state); + } + wlr_output_state_set_scale(&state, r->scale); wlr_output_state_set_transform(&state, r->rr); break; @@ -2757,10 +2749,6 @@ void createmon(struct wl_listener *listener, void *data) { wlr_output_state_set_mode(&state, wlr_output_preferred_mode(wlr_output)); - if (adaptive_sync) { - enable_adaptive_sync(m, &state); - } - /* Set up event listeners */ LISTEN(&wlr_output->events.frame, &m->frame, rendermon); LISTEN(&wlr_output->events.destroy, &m->destroy, cleanupmon); @@ -2785,9 +2773,9 @@ void createmon(struct wl_listener *listener, void *data) { } for (i = 0; i <= LENGTH(tags); i++) { - m->pertag->nmasters[i] = m->nmaster; - m->pertag->mfacts[i] = m->mfact; - m->pertag->ltidxs[i] = m->lt; + m->pertag->nmasters[i] = default_nmaster; + m->pertag->mfacts[i] = default_mfact; + m->pertag->ltidxs[i] = &layouts[0]; } // apply tag rule @@ -2809,7 +2797,7 @@ void createmon(struct wl_listener *listener, void *data) { * output (such as DPI, scale factor, manufacturer, etc). */ m->scene_output = wlr_scene_output_create(scene, wlr_output); - if (m->m.x == -1 && m->m.y == -1) + if (m->m.x == INT32_MAX || m->m.y == INT32_MAX) wlr_output_layout_add_auto(output_layout, wlr_output); else wlr_output_layout_add(output_layout, wlr_output, m->m.x, m->m.y); From 2e6e23633e90c52fcf5fe3af1910326918f7baf0 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 27 Jan 2026 16:20:45 +0800 Subject: [PATCH 08/78] break change: remove useless option adaptive_sync --- src/config/parse_config.h | 5 ----- src/config/preset.h | 1 - 2 files changed, 6 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index f94e679..81eda9d 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -346,7 +346,6 @@ typedef struct { int32_t single_scratchpad; int32_t xwayland_persistence; int32_t syncobj_enable; - int32_t adaptive_sync; int32_t allow_tearing; int32_t allow_shortcuts_inhibit; int32_t allow_lock_transparent; @@ -1278,8 +1277,6 @@ void parse_option(Config *config, char *key, char *value) { config->xwayland_persistence = atoi(value); } else if (strcmp(key, "syncobj_enable") == 0) { config->syncobj_enable = atoi(value); - } else if (strcmp(key, "adaptive_sync") == 0) { - 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) { @@ -2750,7 +2747,6 @@ void override_config(void) { // 杂项设置 xwayland_persistence = CLAMP_INT(config.xwayland_persistence, 0, 1); 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); allow_lock_transparent = CLAMP_INT(config.allow_lock_transparent, 0, 1); @@ -2937,7 +2933,6 @@ void set_value_default() { config.single_scratchpad = single_scratchpad; config.xwayland_persistence = xwayland_persistence; config.syncobj_enable = syncobj_enable; - config.adaptive_sync = adaptive_sync; config.allow_tearing = allow_tearing; config.allow_shortcuts_inhibit = allow_shortcuts_inhibit; config.allow_lock_transparent = allow_lock_transparent; diff --git a/src/config/preset.h b/src/config/preset.h index 5dea6cb..c8474dd 100644 --- a/src/config/preset.h +++ b/src/config/preset.h @@ -106,7 +106,6 @@ int32_t drag_corner = 3; int32_t drag_warp_cursor = 1; int32_t xwayland_persistence = 1; /* xwayland persistence */ int32_t syncobj_enable = 0; -int32_t adaptive_sync = 0; int32_t allow_lock_transparent = 0; double drag_refresh_interval = 16.0; int32_t allow_tearing = TEARING_DISABLED; From 30692f6da035b69a17ef59c48b1f6a4e1cb4c4ba Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 27 Jan 2026 19:02:16 +0800 Subject: [PATCH 09/78] feat: add option hotarea_corner --- src/config/parse_config.h | 9 ++++-- src/config/preset.h | 5 ++-- src/mango.c | 58 ++++++++++++++++++++++++++++++++++----- 3 files changed, 61 insertions(+), 11 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 81eda9d..c345836 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -232,6 +232,7 @@ typedef struct { int32_t center_when_single_stack; uint32_t hotarea_size; + uint32_t hotarea_corner; uint32_t enable_hotarea; uint32_t ov_tab_mode; int32_t overviewgappi; @@ -1452,6 +1453,8 @@ void parse_option(Config *config, char *key, char *value) { config->center_when_single_stack = atoi(value); } else if (strcmp(key, "hotarea_size") == 0) { config->hotarea_size = atoi(value); + } else if (strcmp(key, "hotarea_corner") == 0) { + config->hotarea_corner = atoi(value); } else if (strcmp(key, "enable_hotarea") == 0) { config->enable_hotarea = atoi(value); } else if (strcmp(key, "ov_tab_mode") == 0) { @@ -2739,6 +2742,7 @@ void override_config(void) { // 概述模式设置 hotarea_size = CLAMP_INT(config.hotarea_size, 1, 1000); + hotarea_corner = CLAMP_INT(config.hotarea_corner, 0, 3); enable_hotarea = CLAMP_INT(config.enable_hotarea, 0, 1); ov_tab_mode = CLAMP_INT(config.ov_tab_mode, 0, 1); overviewgappi = CLAMP_INT(config.overviewgappi, 0, 1000); @@ -2901,8 +2905,9 @@ void set_value_default() { config.numlockon = numlockon; // 是否打开右边小键盘 - config.ov_tab_mode = ov_tab_mode; // alt tab切换模式 - config.hotarea_size = hotarea_size; // 热区大小,10x10 + config.ov_tab_mode = ov_tab_mode; // alt tab切换模式 + config.hotarea_size = hotarea_size; // 热区大小,10x10 + config.hotarea_corner = hotarea_corner; config.enable_hotarea = enable_hotarea; // 是否启用鼠标热区 config.smartgaps = smartgaps; /* 1 means no outer gap when there is only one window */ diff --git a/src/config/preset.h b/src/config/preset.h index c8474dd..3d05161 100644 --- a/src/config/preset.h +++ b/src/config/preset.h @@ -47,8 +47,9 @@ int32_t log_level = WLR_ERROR; uint32_t numlockon = 0; // 是否打开右边小键盘 uint32_t capslock = 0; // 是否启用快捷键 -uint32_t ov_tab_mode = 0; // alt tab切换模式 -uint32_t hotarea_size = 10; // 热区大小,10x10 +uint32_t ov_tab_mode = 0; // alt tab切换模式 +uint32_t hotarea_size = 10; // 热区大小,10x10 +uint32_t hotarea_corner = BOTTOM_LEFT; uint32_t enable_hotarea = 1; // 是否启用鼠标热区 int32_t smartgaps = 0; /* 1 means no outer gap when there is only one window */ int32_t sloppyfocus = 1; /* focus follows mouse */ diff --git a/src/mango.c b/src/mango.c index ed4e12a..a32be83 100644 --- a/src/mango.c +++ b/src/mango.c @@ -143,6 +143,8 @@ #define BAKED_POINTS_COUNT 256 /* enums */ +enum { TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT }; + enum { VERTICAL, HORIZONTAL }; enum { SWIPE_UP, SWIPE_DOWN, SWIPE_LEFT, SWIPE_RIGHT }; enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ @@ -1215,17 +1217,59 @@ void toggle_hotarea(int32_t x_root, int32_t y_root) { if (grabc) return; - unsigned hx = selmon->m.x + hotarea_size; - unsigned hy = selmon->m.y + selmon->m.height - hotarea_size; + // 根据热角位置计算不同的热区坐标 + unsigned hx, hy; - if (enable_hotarea == 1 && selmon->is_in_hotarea == 0 && y_root > hy && - x_root < hx && x_root >= selmon->m.x && - y_root <= (selmon->m.y + selmon->m.height)) { + switch (hotarea_corner) { + case BOTTOM_RIGHT: // 右下角 + hx = selmon->m.x + selmon->m.width - hotarea_size; + hy = selmon->m.y + selmon->m.height - hotarea_size; + break; + case TOP_LEFT: // 左上角 + hx = selmon->m.x + hotarea_size; + hy = selmon->m.y + hotarea_size; + break; + case TOP_RIGHT: // 右上角 + hx = selmon->m.x + selmon->m.width - hotarea_size; + hy = selmon->m.y + hotarea_size; + break; + case BOTTOM_LEFT: // 左下角(默认) + default: + hx = selmon->m.x + hotarea_size; + hy = selmon->m.y + selmon->m.height - hotarea_size; + break; + } + + // 判断鼠标是否在热区内 + int in_hotarea = 0; + + switch (hotarea_corner) { + case BOTTOM_RIGHT: // 右下角 + in_hotarea = (y_root > hy && x_root > hx && + x_root <= (selmon->m.x + selmon->m.width) && + y_root <= (selmon->m.y + selmon->m.height)); + break; + case TOP_LEFT: // 左上角 + in_hotarea = (y_root < hy && x_root < hx && x_root >= selmon->m.x && + y_root >= selmon->m.y); + break; + case TOP_RIGHT: // 右上角 + in_hotarea = (y_root < hy && x_root > hx && + x_root <= (selmon->m.x + selmon->m.width) && + y_root >= selmon->m.y); + break; + case BOTTOM_LEFT: // 左下角(默认) + default: + in_hotarea = (y_root > hy && x_root < hx && x_root >= selmon->m.x && + y_root <= (selmon->m.y + selmon->m.height)); + break; + } + + if (enable_hotarea == 1 && selmon->is_in_hotarea == 0 && in_hotarea) { toggleoverview(&arg); selmon->is_in_hotarea = 1; } else if (enable_hotarea == 1 && selmon->is_in_hotarea == 1 && - (y_root <= hy || x_root >= hx || x_root < selmon->m.x || - y_root > (selmon->m.y + selmon->m.height))) { + !in_hotarea) { selmon->is_in_hotarea = 0; } } From e535aea28bccaad3dd6dde66fa2e80a68f90f844 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 28 Jan 2026 14:50:17 +0800 Subject: [PATCH 10/78] fix: avoid redundant frame requests --- 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 15a0d16..7bc47ac 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -1061,9 +1061,9 @@ void client_set_focused_opacity_animation(Client *c) { c->opacity_animation.initial_opacity = c->opacity_animation.current_opacity; } else { - memcpy(c->opacity_animation.initial_border_color, border_color, + memcpy(c->opacity_animation.initial_border_color, bordercolor, sizeof(c->opacity_animation.initial_border_color)); - memcpy(c->opacity_animation.current_border_color, border_color, + memcpy(c->opacity_animation.current_border_color, bordercolor, sizeof(c->opacity_animation.current_border_color)); c->opacity_animation.initial_opacity = c->unfocused_opacity; c->opacity_animation.current_opacity = c->unfocused_opacity; @@ -1167,7 +1167,7 @@ bool client_apply_focus_opacity(Client *c) { eased_progress; } client_set_border_color(c, c->opacity_animation.current_border_color); - if (linear_progress == 1.0f) { + if (linear_progress >= 1.0f) { c->opacity_animation.running = false; } else { return true; From 4591b69e4d8e212b2b4986abbb1598f968b557d0 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 29 Jan 2026 09:34:49 +0800 Subject: [PATCH 11/78] fix: accel_profile can't set to 0 --- src/mango.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/mango.c b/src/mango.c index a32be83..0360d4f 100644 --- a/src/mango.c +++ b/src/mango.c @@ -2958,9 +2958,14 @@ void configure_pointer(struct libinput_device *device) { if (libinput_device_config_send_events_get_modes(device)) libinput_device_config_send_events_set_mode(device, send_events_mode); - if (libinput_device_config_accel_is_available(device)) { + if (accel_profile && libinput_device_config_accel_is_available(device)) { libinput_device_config_accel_set_profile(device, accel_profile); libinput_device_config_accel_set_speed(device, accel_speed); + } else { + // profile cannot be directly applied to 0, need to set to 1 first + libinput_device_config_accel_set_profile(device, 1); + libinput_device_config_accel_set_profile(device, 0); + libinput_device_config_accel_set_speed(device, 0); } } From d9f679a8e371991785e5a8a18f6f5cac11fcd601 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 29 Jan 2026 09:42:54 +0800 Subject: [PATCH 12/78] fix: tagcrossmon not apply in current monitor --- src/dispatch/bind_define.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index f27ed67..c0e51dc 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1444,9 +1444,14 @@ int32_t viewcrossmon(const Arg *arg) { } int32_t tagcrossmon(const Arg *arg) { - if (!selmon->sel) + if (!selmon || !selmon->sel) return 0; + if (regex_match(selmon->wlr_output->name, arg->v)) { + tag_client(arg, selmon->sel); + return 0; + } + tagmon(&(Arg){.ui = arg->ui, .i = UNDIR, .v = arg->v}); return 0; } From 672706c71f4775dba304af1700451e0eadf8cea1 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 29 Jan 2026 11:58:28 +0800 Subject: [PATCH 13/78] fix: not apply vrr disalbe when enable it at the beginning --- src/config/parse_config.h | 2 ++ src/mango.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index c345836..baef79d 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -3198,6 +3198,8 @@ void reapply_monitor_rules(void) { if (vrr) { enable_adaptive_sync(m, &state); + } else { + wlr_output_state_set_adaptive_sync_enabled(&state, false); } wlr_output_state_set_scale(&state, mr->scale); diff --git a/src/mango.c b/src/mango.c index 0360d4f..86ea8a9 100644 --- a/src/mango.c +++ b/src/mango.c @@ -2777,6 +2777,8 @@ void createmon(struct wl_listener *listener, void *data) { if (vrr) { enable_adaptive_sync(m, &state); + } else { + wlr_output_state_set_adaptive_sync_enabled(&state, false); } wlr_output_state_set_scale(&state, r->scale); From aa5241fb7b0d5059fdceb3688309cf07fb94fbf2 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 29 Jan 2026 21:59:39 +0800 Subject: [PATCH 14/78] fix: allow layer surface attach null buffer --- src/mango.c | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/mango.c b/src/mango.c index 86ea8a9..ae49a73 100644 --- a/src/mango.c +++ b/src/mango.c @@ -480,6 +480,7 @@ typedef struct { char *animation_type_open; char *animation_type_close; bool need_output_flush; + bool being_unmapped; } LayerSurface; typedef struct { @@ -592,7 +593,7 @@ static void cursorwarptohint(void); static void destroydecoration(struct wl_listener *listener, void *data); static void destroydragicon(struct wl_listener *listener, void *data); static void destroyidleinhibitor(struct wl_listener *listener, void *data); -static void destroylayersurfacenotify(struct wl_listener *listener, void *data); +static void destroylayernodenotify(struct wl_listener *listener, void *data); static void destroylock(SessionLock *lock, int32_t unlocked); static void destroylocksurface(struct wl_listener *listener, void *data); static void destroynotify(struct wl_listener *listener, void *data); @@ -1521,6 +1522,9 @@ void arrangelayer(Monitor *m, struct wl_list *list, struct wlr_box *usable_area, !layer_surface->initialized) continue; + if (l->being_unmapped) + continue; + wlr_scene_layer_surface_v1_configure(l->scene_layer, &full_area, usable_area); wlr_scene_node_set_position(&l->popups->node, l->scene->node.x, @@ -2337,6 +2341,15 @@ void commitlayersurfacenotify(struct wl_listener *listener, void *data) { return; } + // 检查surface是否有buffer + if (!layer_surface->surface->buffer) { + // 空buffer,只是隐藏,不改变mapped状态 + wlr_scene_node_set_enabled(&l->scene->node, false); + return; + } else { + wlr_scene_node_set_enabled(&l->scene->node, true); + } + get_layer_target_geometry(l, &box); if (animations && layer_animations && !l->noanim && l->mapped && @@ -2633,8 +2646,6 @@ void createlayersurface(struct wl_listener *listener, void *data) { LISTEN(&surface->events.commit, &l->surface_commit, commitlayersurfacenotify); LISTEN(&surface->events.unmap, &l->unmap, unmaplayersurfacenotify); - LISTEN(&layer_surface->events.destroy, &l->destroy, - destroylayersurfacenotify); l->layer_surface = layer_surface; l->mon = layer_surface->output->data; @@ -2647,6 +2658,8 @@ void createlayersurface(struct wl_listener *listener, void *data) { : scene_layer); l->scene->node.data = l->popups->node.data = l; + LISTEN(&l->scene->node.events.destroy, &l->destroy, destroylayernodenotify); + wl_list_insert(&l->mon->layers[layer_surface->pending.layer], &l->link); wlr_surface_send_enter(surface, layer_surface->output); } @@ -3112,7 +3125,7 @@ void destroyidleinhibitor(struct wl_listener *listener, void *data) { free(listener); } -void destroylayersurfacenotify(struct wl_listener *listener, void *data) { +void destroylayernodenotify(struct wl_listener *listener, void *data) { LayerSurface *l = wl_container_of(listener, l, destroy); wl_list_remove(&l->link); @@ -3120,7 +3133,6 @@ void destroylayersurfacenotify(struct wl_listener *listener, void *data) { wl_list_remove(&l->map.link); wl_list_remove(&l->unmap.link); wl_list_remove(&l->surface_commit.link); - wlr_scene_node_destroy(&l->scene->node); wlr_scene_node_destroy(&l->popups->node); free(l); } @@ -5554,6 +5566,7 @@ void unmaplayersurfacenotify(struct wl_listener *listener, void *data) { LayerSurface *l = wl_container_of(listener, l, unmap); l->mapped = 0; + l->being_unmapped = true; init_fadeout_layers(l); @@ -5565,6 +5578,9 @@ void unmaplayersurfacenotify(struct wl_listener *listener, void *data) { if (l->layer_surface->surface == seat->keyboard_state.focused_surface) focusclient(focustop(selmon), 1); motionnotify(0, NULL, 0, 0, 0, 0); + l->being_unmapped = false; + wlr_scene_node_destroy(&l->shadow->node); + l->shadow = NULL; } void unmapnotify(struct wl_listener *listener, void *data) { From ca09aa69db29adf7e9cbaab74885724b4d469c66 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 30 Jan 2026 08:47:12 +0800 Subject: [PATCH 15/78] opt: not hide null buffer layer if is not mapped --- src/mango.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mango.c b/src/mango.c index ae49a73..1d332c4 100644 --- a/src/mango.c +++ b/src/mango.c @@ -2342,8 +2342,8 @@ void commitlayersurfacenotify(struct wl_listener *listener, void *data) { } // 检查surface是否有buffer - if (!layer_surface->surface->buffer) { - // 空buffer,只是隐藏,不改变mapped状态 + // 空buffer,只是隐藏,不改变mapped状态 + if (l->mapped && !layer_surface->surface->buffer) { wlr_scene_node_set_enabled(&l->scene->node, false); return; } else { From 5172444e08b2c2be24bef0b5ccff2d2367332118 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 1 Feb 2026 15:33:05 +0800 Subject: [PATCH 16/78] bump version to 0.12.0 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index d71eb59..0565609 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('mango', ['c', 'cpp'], - version : '0.11.0', + version : '0.12.0', ) subdir('protocols') From bcee63fa76245c900800c21eac0e999b45b86251 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 1 Feb 2026 19:21:01 +0800 Subject: [PATCH 17/78] feat: add config check in mango cli --- src/config/parse_config.h | 441 ++++++++++++++++++++++++++++---------- src/mango.c | 30 ++- 2 files changed, 354 insertions(+), 117 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index baef79d..f7ce848 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -405,7 +405,10 @@ int32_t parse_double_array(const char *input, double *output, char *endptr; double val = strtod(token, &endptr); if (endptr == token || *endptr != '\0') { - fprintf(stderr, "Error: Invalid number in array: %s\n", token); + fprintf( + stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid number in array: %s\n", + token); free(dup); return -1; } @@ -457,7 +460,9 @@ void parse_bind_flags(const char *str, KeyBinding *kb) { kb->ispassapply = true; break; default: - // 忽略其他字符或可根据需要处理错误 + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Unknown bind flag: %c\n", + suffix[i]); break; } } @@ -547,13 +552,14 @@ static bool starts_with_ignore_case(const char *str, const char *prefix) { uint32_t parse_mod(const char *mod_str) { if (!mod_str || !*mod_str) { - return 0; + return UINT32_MAX; } uint32_t mod = 0; char input_copy[256]; char *token; char *saveptr = NULL; + bool match_success = false; // 复制并转换为小写 strncpy(input_copy, mod_str, sizeof(input_copy) - 1); @@ -591,35 +597,56 @@ uint32_t parse_mod(const char *mod_str) { case 108: mod |= WLR_MODIFIER_ALT; break; + default: + fprintf(stderr, + "unknown modifier keycode: \033[1m\033[31m%s\n", + token); + break; } } } else { // 完整的 modifier 检查(保留原始所有检查项) - if (strstr(token, "super") || strstr(token, "super_l") || - strstr(token, "super_r")) { + if (!strcmp(token, "super") || !strcmp(token, "super_l") || + !strcmp(token, "super_r")) { mod |= WLR_MODIFIER_LOGO; + match_success = true; } - if (strstr(token, "ctrl") || strstr(token, "ctrl_l") || - strstr(token, "ctrl_r")) { + if (!strcmp(token, "ctrl") || !strcmp(token, "ctrl_l") || + !strcmp(token, "ctrl_r")) { mod |= WLR_MODIFIER_CTRL; + match_success = true; } - if (strstr(token, "shift") || strstr(token, "shift_l") || - strstr(token, "shift_r")) { + if (!strcmp(token, "shift") || !strcmp(token, "shift_l") || + !strcmp(token, "shift_r")) { mod |= WLR_MODIFIER_SHIFT; + match_success = true; } - if (strstr(token, "alt") || strstr(token, "alt_l") || - strstr(token, "alt_r")) { + if (!strcmp(token, "alt") || !strcmp(token, "alt_l") || + !strcmp(token, "alt_r")) { mod |= WLR_MODIFIER_ALT; + match_success = true; } - if (strstr(token, "hyper") || strstr(token, "hyper_l") || - strstr(token, "hyper_r")) { + if (!strcmp(token, "hyper") || !strcmp(token, "hyper_l") || + !strcmp(token, "hyper_r")) { mod |= WLR_MODIFIER_MOD3; + match_success = true; + } + if (!strcmp(token, "none")) { + match_success = true; } } token = strtok_r(NULL, "+", &saveptr); } + if (!match_success) { + mod = UINT32_MAX; + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Unknown modifier: " + "\033[1m\033[31m%s\n", + mod_str); + } + return mod; } @@ -735,13 +762,17 @@ KeySymCode parse_key(const char *key_str, bool isbindsym) { // 无法解析的键名 kc.type = KEY_TYPE_SYM; kc.keysym = XKB_KEY_NoSymbol; + fprintf( + stderr, + "\033[1m\033[31m[ERROR]:\033[33m Unknown key: \033[1m\033[31m%s\n", + key_str); // keycode 字段保持为0 } return kc; } -int32_t parse_button(const char *str) { +uint32_t parse_button(const char *str) { // 将输入字符串转换为小写 char lowerStr[20]; int32_t i = 0; @@ -769,7 +800,11 @@ int32_t parse_button(const char *str) { } else if (strcmp(lowerStr, "btn_task") == 0) { return BTN_TASK; } else { - return 0; + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Unknown button: " + "\033[1m\033[31m%s\n", + str); + return UINT32_MAX; } } @@ -1115,7 +1150,7 @@ void run_exec_once() { } } -void parse_option(Config *config, char *key, char *value) { +bool parse_option(Config *config, char *key, char *value) { if (strcmp(key, "keymode") == 0) { snprintf(config->keymode, sizeof(config->keymode), "%.27s", value); } else if (strcmp(key, "animations") == 0) { @@ -1166,53 +1201,70 @@ void parse_option(Config *config, char *key, char *value) { int32_t num = parse_double_array(value, config->animation_curve_move, 4); if (num != 4) { - fprintf(stderr, "Error: Failed to parse animation_curve_move: %s\n", + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Failed to parse " + "animation_curve_move: %s\n", value); + return false; } } else if (strcmp(key, "animation_curve_open") == 0) { int32_t num = parse_double_array(value, config->animation_curve_open, 4); if (num != 4) { - fprintf(stderr, "Error: Failed to parse animation_curve_open: %s\n", + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Failed to parse " + "animation_curve_open: %s\n", value); + return false; } } else if (strcmp(key, "animation_curve_tag") == 0) { int32_t num = parse_double_array(value, config->animation_curve_tag, 4); if (num != 4) { - fprintf(stderr, "Error: Failed to parse animation_curve_tag: %s\n", + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Failed to parse " + "animation_curve_tag: %s\n", value); + return false; } } else if (strcmp(key, "animation_curve_close") == 0) { int32_t num = parse_double_array(value, config->animation_curve_close, 4); if (num != 4) { fprintf(stderr, - "Error: Failed to parse animation_curve_close: %s\n", + "\033[1m\033[31m[ERROR]:\033[33m Failed to parse " + "animation_curve_close: %s\n", value); + return false; } } else if (strcmp(key, "animation_curve_focus") == 0) { int32_t num = parse_double_array(value, config->animation_curve_focus, 4); if (num != 4) { fprintf(stderr, - "Error: Failed to parse animation_curve_focus: %s\n", + "\033[1m\033[31m[ERROR]:\033[33m Failed to parse " + "animation_curve_focus: %s\n", value); + return false; } } else if (strcmp(key, "animation_curve_opafadein") == 0) { int32_t num = parse_double_array(value, config->animation_curve_opafadein, 4); if (num != 4) { fprintf(stderr, - "Error: Failed to parse animation_curve_opafadein: %s\n", + "\033[1m\033[31m[ERROR]:\033[33m Failed to parse " + "animation_curve_opafadein: %s\n", value); + return false; } } else if (strcmp(key, "animation_curve_opafadeout") == 0) { int32_t num = parse_double_array(value, config->animation_curve_opafadeout, 4); if (num != 4) { fprintf(stderr, - "Error: Failed to parse animation_curve_opafadeout: %s\n", + "\033[1m\033[31m[ERROR]:\033[33m Failed to parse " + "animation_curve_opafadeout: %s\n", value); + return false; } } else if (strcmp(key, "scroller_structs") == 0) { config->scroller_structs = atoi(value); @@ -1333,8 +1385,10 @@ void parse_option(Config *config, char *key, char *value) { config->scroller_proportion_preset = (float *)malloc(float_count * sizeof(float)); if (!config->scroller_proportion_preset) { - fprintf(stderr, "Error: Memory allocation failed\n"); - return; + fprintf( + stderr, + "\033[1m\033[31m[ERROR]:\033[33m Memory allocation failed\n"); + return false; } // 3. 解析 value 中的浮点数 @@ -1346,14 +1400,15 @@ void parse_option(Config *config, char *key, char *value) { while (token != NULL && i < float_count) { if (sscanf(token, "%f", &value_set) != 1) { - fprintf(stderr, - "Error: Invalid float value in " - "scroller_proportion_preset: %s\n", - token); + fprintf( + stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid float value in " + "scroller_proportion_preset: %s\n", + token); free(value_copy); free(config->scroller_proportion_preset); config->scroller_proportion_preset = NULL; - return; + return false; } // Clamp the value between 0.0 and 1.0 (or your desired range) @@ -1367,13 +1422,14 @@ void parse_option(Config *config, char *key, char *value) { // 4. 检查解析的浮点数数量是否匹配 if (i != float_count) { fprintf(stderr, - "Error: Invalid scroller_proportion_preset format: %s\n", + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "scroller_proportion_preset format: %s\n", value); free(value_copy); free(config->scroller_proportion_preset); // 释放已分配的内存 config->scroller_proportion_preset = NULL; // 防止野指针 config->scroller_proportion_preset_count = 0; - return; + return false; } config->scroller_proportion_preset_count = float_count; @@ -1392,8 +1448,10 @@ void parse_option(Config *config, char *key, char *value) { config->circle_layout = (char **)malloc(string_count * sizeof(char *)); memset(config->circle_layout, 0, string_count * sizeof(char *)); if (!config->circle_layout) { - fprintf(stderr, "Error: Memory allocation failed\n"); - return; + fprintf( + stderr, + "\033[1m\033[31m[ERROR]:\033[33m Memory allocation failed\n"); + return false; } // 3. 解析 value 中的字符串 @@ -1408,7 +1466,9 @@ void parse_option(Config *config, char *key, char *value) { config->circle_layout[i] = strdup(cleaned_token); if (!config->circle_layout[i]) { fprintf(stderr, - "Error: Memory allocation failed for string: %s\n", + "\033[1m\033[31m[ERROR]:\033[33m Memory allocation " + "failed for " + "string: %s\n", token); // 释放之前分配的内存 for (int32_t j = 0; j < i; j++) { @@ -1418,7 +1478,7 @@ void parse_option(Config *config, char *key, char *value) { free(value_copy); config->circle_layout = NULL; // 防止野指针 config->circle_layout_count = 0; - return; + return false; } token = strtok(NULL, ","); i++; @@ -1426,7 +1486,10 @@ void parse_option(Config *config, char *key, char *value) { // 4. 检查解析的字符串数量是否匹配 if (i != string_count) { - fprintf(stderr, "Error: Invalid circle_layout format: %s\n", value); + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid circle_layout " + "format: %s\n", + value); // 释放之前分配的内存 for (int32_t j = 0; j < i; j++) { free(config->circle_layout[j]); @@ -1435,7 +1498,7 @@ void parse_option(Config *config, char *key, char *value) { free(value_copy); config->circle_layout = NULL; // 防止野指针 config->circle_layout_count = 0; - return; + return false; } config->circle_layout_count = string_count; @@ -1542,7 +1605,11 @@ void parse_option(Config *config, char *key, char *value) { } else if (strcmp(key, "rootcolor") == 0) { int64_t color = parse_color(value); if (color == -1) { - fprintf(stderr, "Error: Invalid rootcolor format: %s\n", value); + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid rootcolor format: " + "%s\n", + value); + return false; } else { convert_hex_to_rgba(config->rootcolor, color); } @@ -1550,58 +1617,89 @@ void parse_option(Config *config, char *key, char *value) { } else if (strcmp(key, "shadowscolor") == 0) { int64_t color = parse_color(value); if (color == -1) { - fprintf(stderr, "Error: Invalid shadowscolor format: %s\n", value); + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid shadowscolor " + "format: %s\n", + value); + return false; } else { convert_hex_to_rgba(config->shadowscolor, color); } } else if (strcmp(key, "bordercolor") == 0) { int64_t color = parse_color(value); if (color == -1) { - fprintf(stderr, "Error: Invalid bordercolor format: %s\n", value); + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid bordercolor " + "format: %s\n", + value); + return false; } else { convert_hex_to_rgba(config->bordercolor, color); } } else if (strcmp(key, "focuscolor") == 0) { int64_t color = parse_color(value); if (color == -1) { - fprintf(stderr, "Error: Invalid focuscolor format: %s\n", value); + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid focuscolor " + "format: %s\n", + value); + return false; } else { convert_hex_to_rgba(config->focuscolor, color); } } else if (strcmp(key, "maximizescreencolor") == 0) { int64_t color = parse_color(value); if (color == -1) { - fprintf(stderr, "Error: Invalid maximizescreencolor format: %s\n", - value); + fprintf( + stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid maximizescreencolor " + "format: %s\n", + value); + return false; } else { convert_hex_to_rgba(config->maximizescreencolor, color); } } else if (strcmp(key, "urgentcolor") == 0) { int64_t color = parse_color(value); if (color == -1) { - fprintf(stderr, "Error: Invalid urgentcolor format: %s\n", value); + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid urgentcolor " + "format: %s\n", + value); + return false; } else { convert_hex_to_rgba(config->urgentcolor, color); } } else if (strcmp(key, "scratchpadcolor") == 0) { int64_t color = parse_color(value); if (color == -1) { - fprintf(stderr, "Error: Invalid scratchpadcolor format: %s\n", + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid scratchpadcolor " + "format: %s\n", value); + return false; } else { convert_hex_to_rgba(config->scratchpadcolor, color); } } else if (strcmp(key, "globalcolor") == 0) { int64_t color = parse_color(value); if (color == -1) { - fprintf(stderr, "Error: Invalid globalcolor format: %s\n", value); + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid globalcolor " + "format: %s\n", + value); + return false; } else { convert_hex_to_rgba(config->globalcolor, color); } } else if (strcmp(key, "overlaycolor") == 0) { int64_t color = parse_color(value); if (color == -1) { - fprintf(stderr, "Error: Invalid overlaycolor format: %s\n", value); + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid overlaycolor " + "format: %s\n", + value); + return false; } else { convert_hex_to_rgba(config->overlaycolor, color); } @@ -1611,8 +1709,9 @@ void parse_option(Config *config, char *key, char *value) { sizeof(ConfigMonitorRule)); if (!config->monitor_rules) { fprintf(stderr, - "Error: Failed to allocate memory for monitor rules\n"); - return; + "\033[1m\033[31m[ERROR]:\033[33m Failed to allocate " + "memory for monitor rules\n"); + return false; } ConfigMonitorRule *rule = @@ -1630,6 +1729,7 @@ void parse_option(Config *config, char *key, char *value) { rule->refresh = 0.0f; rule->vrr = 0; + bool parse_error = false; char *token = strtok(value, ","); while (token != NULL) { char *colon = strchr(token, ':'); @@ -1659,19 +1759,29 @@ void parse_option(Config *config, char *key, char *value) { rule->refresh = CLAMP_FLOAT(atof(val), 0.001f, 1000.0f); } else if (strcmp(key, "vrr") == 0) { rule->vrr = CLAMP_INT(atoi(val), 0, 1); + } else { + fprintf( + stderr, + "\033[1m\033[31m[ERROR]:\033[33m Unknown monitor rule " + "option:\033[1m\033[31m %s\n", + key); + parse_error = true; } } token = strtok(NULL, ","); } config->monitor_rules_count++; + return !parse_error; } else if (strcmp(key, "tagrule") == 0) { config->tag_rules = realloc(config->tag_rules, (config->tag_rules_count + 1) * sizeof(ConfigTagRule)); if (!config->tag_rules) { - fprintf(stderr, "Error: Failed to allocate memory for tag rules\n"); - return; + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Failed to allocate " + "memory for tag rules\n"); + return false; } ConfigTagRule *rule = &config->tag_rules[config->tag_rules_count]; @@ -1686,6 +1796,7 @@ void parse_option(Config *config, char *key, char *value) { rule->no_render_border = 0; rule->no_hide = 0; + bool parse_error = false; char *token = strtok(value, ","); while (token != NULL) { char *colon = strchr(token, ':'); @@ -1711,20 +1822,28 @@ void parse_option(Config *config, char *key, char *value) { rule->nmaster = CLAMP_INT(atoi(val), 1, 99); } else if (strcmp(key, "mfact") == 0) { rule->mfact = CLAMP_FLOAT(atof(val), 0.1f, 0.9f); + } else { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Unknown tag rule " + "option:\033[1m\033[31m %s\n", + key); + parse_error = true; } } token = strtok(NULL, ","); } config->tag_rules_count++; + return !parse_error; } else if (strcmp(key, "layerrule") == 0) { config->layer_rules = realloc(config->layer_rules, (config->layer_rules_count + 1) * sizeof(ConfigLayerRule)); if (!config->layer_rules) { fprintf(stderr, - "Error: Failed to allocate memory for layer rules\n"); - return; + "\033[1m\033[31m[ERROR]:\033[33m Failed to allocate " + "memory for layer rules\n"); + return false; } ConfigLayerRule *rule = &config->layer_rules[config->layer_rules_count]; @@ -1738,6 +1857,7 @@ void parse_option(Config *config, char *key, char *value) { rule->noanim = 0; rule->noshadow = 0; + bool parse_error = false; char *token = strtok(value, ","); while (token != NULL) { char *colon = strchr(token, ':'); @@ -1761,6 +1881,13 @@ void parse_option(Config *config, char *key, char *value) { rule->noanim = CLAMP_INT(atoi(val), 0, 1); } else if (strcmp(key, "noshadow") == 0) { rule->noshadow = CLAMP_INT(atoi(val), 0, 1); + } else { + fprintf( + stderr, + "\033[1m\033[31m[ERROR]:\033[33m Unknown layer rule " + "option:\033[1m\033[31m %s\n", + key); + parse_error = true; } } token = strtok(NULL, ","); @@ -1772,14 +1899,16 @@ void parse_option(Config *config, char *key, char *value) { } config->layer_rules_count++; + return !parse_error; } else if (strcmp(key, "windowrule") == 0) { config->window_rules = realloc(config->window_rules, (config->window_rules_count + 1) * sizeof(ConfigWinRule)); if (!config->window_rules) { fprintf(stderr, - "Error: Failed to allocate memory for window rules\n"); - return; + "\033[1m\033[31m[ERROR]:\033[33m Failed to allocate " + "memory for window rules\n"); + return false; } ConfigWinRule *rule = &config->window_rules[config->window_rules_count]; @@ -1835,6 +1964,7 @@ void parse_option(Config *config, char *key, char *value) { rule->globalkeybinding = (KeyBinding){0}; + bool parse_error = false; char *token = strtok(value, ","); while (token != NULL) { char *colon = strchr(token, ':'); @@ -1934,17 +2064,37 @@ void parse_option(Config *config, char *key, char *value) { rule->globalkeybinding.mod = parse_mod(mod_str); rule->globalkeybinding.keysymcode = parse_key(keysym_str, false); + if (rule->globalkeybinding.mod == UINT32_MAX) { + return false; + } + if (rule->globalkeybinding.keysymcode.type == + KEY_TYPE_SYM && + rule->globalkeybinding.keysymcode.keysym == + XKB_KEY_NoSymbol) { + return false; + } + } else { + fprintf( + stderr, + "\033[1m\033[31m[ERROR]:\033[33m Unknown window rule " + "option:\033[1m\033[31m %s\n", + key); + parse_error = true; } } token = strtok(NULL, ","); } config->window_rules_count++; + return !parse_error; } else if (strncmp(key, "env", 3) == 0) { char env_type[256], env_value[256]; if (sscanf(value, "%255[^,],%255[^\n]", env_type, env_value) < 2) { - fprintf(stderr, "Error: Invalid bind format: %s\n", value); - return; + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid bind format: " + "\033[1m\033[31m%s\n", + value); + return false; } trim_whitespace(env_type); trim_whitespace(env_value); @@ -1959,8 +2109,9 @@ void parse_option(Config *config, char *key, char *value) { free(env->type); free(env->value); free(env); - fprintf(stderr, "Error: Failed to allocate memory for env\n"); - return; + fprintf(stderr, "\033[1m\033[31m[ERROR]:\033[33m Failed to " + "allocate memory for env\n"); + return false; } config->env[config->env_count] = env; @@ -1970,15 +2121,18 @@ void parse_option(Config *config, char *key, char *value) { char **new_exec = realloc(config->exec, (config->exec_count + 1) * sizeof(char *)); if (!new_exec) { - fprintf(stderr, "Error: Failed to allocate memory for exec\n"); - return; + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Failed to allocate " + "memory for exec\n"); + return false; } config->exec = new_exec; config->exec[config->exec_count] = strdup(value); if (!config->exec[config->exec_count]) { - fprintf(stderr, "Error: Failed to duplicate exec string\n"); - return; + fprintf(stderr, "\033[1m\033[31m[ERROR]:\033[33m Failed to " + "duplicate exec string\n"); + return false; } config->exec_count++; @@ -1988,15 +2142,19 @@ void parse_option(Config *config, char *key, char *value) { char **new_exec_once = realloc( config->exec_once, (config->exec_once_count + 1) * sizeof(char *)); if (!new_exec_once) { - fprintf(stderr, "Error: Failed to allocate memory for exec_once\n"); - return; + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Failed to allocate " + "memory for exec_once\n"); + return false; } config->exec_once = new_exec_once; config->exec_once[config->exec_once_count] = strdup(value); if (!config->exec_once[config->exec_once_count]) { - fprintf(stderr, "Error: Failed to duplicate exec_once string\n"); - return; + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Failed to duplicate " + "exec_once string\n"); + return false; } config->exec_once_count++; @@ -2007,8 +2165,9 @@ void parse_option(Config *config, char *key, char *value) { (config->key_bindings_count + 1) * sizeof(KeyBinding)); if (!config->key_bindings) { fprintf(stderr, - "Error: Failed to allocate memory for key bindings\n"); - return; + "\033[1m\033[31m[ERROR]:\033[33m Failed to allocate " + "memory for key bindings\n"); + return false; } KeyBinding *binding = &config->key_bindings[config->key_bindings_count]; @@ -2023,8 +2182,11 @@ void parse_option(Config *config, char *key, char *value) { "^,],%255[^\n]", mod_str, keysym_str, func_name, arg_value, arg_value2, arg_value3, arg_value4, arg_value5) < 3) { - fprintf(stderr, "Error: Invalid bind format: %s\n", value); - return; + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid bind format: " + "\033[1m\033[31m%s\n", + value); + return false; } trim_whitespace(mod_str); trim_whitespace(keysym_str); @@ -2057,7 +2219,9 @@ void parse_option(Config *config, char *key, char *value) { binding->func = parse_func_name(func_name, &binding->arg, arg_value, arg_value2, arg_value3, arg_value4, arg_value5); - if (!binding->func) { + if (!binding->func || binding->mod == UINT32_MAX || + (binding->keysymcode.type == KEY_TYPE_SYM && + binding->keysymcode.keysym == XKB_KEY_NoSymbol)) { if (binding->arg.v) { free(binding->arg.v); binding->arg.v = NULL; @@ -2070,7 +2234,13 @@ void parse_option(Config *config, char *key, char *value) { free(binding->arg.v3); binding->arg.v3 = NULL; } - fprintf(stderr, "Error: Unknown function in bind: %s\n", func_name); + if (!binding->func) + fprintf( + stderr, + "\033[1m\033[31m[ERROR]:\033[33m Unknown dispatch in bind: " + "\033[1m\033[31m%s\n", + func_name); + return false; } else { config->key_bindings_count++; } @@ -2081,8 +2251,9 @@ void parse_option(Config *config, char *key, char *value) { (config->mouse_bindings_count + 1) * sizeof(MouseBinding)); if (!config->mouse_bindings) { fprintf(stderr, - "Error: Failed to allocate memory for mouse bindings\n"); - return; + "\033[1m\033[31m[ERROR]:\033[33m Failed to allocate " + "memory for mouse bindings\n"); + return false; } MouseBinding *binding = @@ -2098,8 +2269,11 @@ void parse_option(Config *config, char *key, char *value) { "^,],%255[^\n]", mod_str, button_str, func_name, arg_value, arg_value2, arg_value3, arg_value4, arg_value5) < 3) { - fprintf(stderr, "Error: Invalid mousebind format: %s\n", value); - return; + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid mousebind format: " + "%s\n", + value); + return false; } trim_whitespace(mod_str); trim_whitespace(button_str); @@ -2118,7 +2292,8 @@ void parse_option(Config *config, char *key, char *value) { binding->func = parse_func_name(func_name, &binding->arg, arg_value, arg_value2, arg_value3, arg_value4, arg_value5); - if (!binding->func) { + if (!binding->func || binding->mod == UINT32_MAX || + binding->button == UINT32_MAX) { if (binding->arg.v) { free(binding->arg.v); binding->arg.v = NULL; @@ -2131,8 +2306,12 @@ void parse_option(Config *config, char *key, char *value) { free(binding->arg.v3); binding->arg.v3 = NULL; } - fprintf(stderr, "Error: Unknown function in mousebind: %s\n", - func_name); + if (!binding->func) + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Unknown dispatch in " + "mousebind: \033[1m\033[31m%s\n", + func_name); + return false; } else { config->mouse_bindings_count++; } @@ -2142,8 +2321,9 @@ void parse_option(Config *config, char *key, char *value) { (config->axis_bindings_count + 1) * sizeof(AxisBinding)); if (!config->axis_bindings) { fprintf(stderr, - "Error: Failed to allocate memory for axis bindings\n"); - return; + "\033[1m\033[31m[ERROR]:\033[33m Failed to allocate " + "memory for axis bindings\n"); + return false; } AxisBinding *binding = @@ -2159,8 +2339,11 @@ void parse_option(Config *config, char *key, char *value) { "^,],%255[^\n]", mod_str, dir_str, func_name, arg_value, arg_value2, arg_value3, arg_value4, arg_value5) < 3) { - fprintf(stderr, "Error: Invalid axisbind format: %s\n", value); - return; + fprintf( + stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid axisbind format: %s\n", + value); + return false; } trim_whitespace(mod_str); @@ -2181,7 +2364,7 @@ void parse_option(Config *config, char *key, char *value) { parse_func_name(func_name, &binding->arg, arg_value, arg_value2, arg_value3, arg_value4, arg_value5); - if (!binding->func) { + if (!binding->func || binding->mod == UINT32_MAX) { if (binding->arg.v) { free(binding->arg.v); binding->arg.v = NULL; @@ -2194,8 +2377,12 @@ void parse_option(Config *config, char *key, char *value) { free(binding->arg.v3); binding->arg.v3 = NULL; } - fprintf(stderr, "Error: Unknown function in axisbind: %s\n", - func_name); + if (!binding->func) + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Unknown dispatch in " + "axisbind: \033[1m\033[31m%s\n", + func_name); + return false; } else { config->axis_bindings_count++; } @@ -2206,8 +2393,9 @@ void parse_option(Config *config, char *key, char *value) { sizeof(SwitchBinding)); if (!config->switch_bindings) { fprintf(stderr, - "Error: Failed to allocate memory for switch bindings\n"); - return; + "\033[1m\033[31m[ERROR]:\033[33m Failed to allocate " + "memory for switch bindings\n"); + return false; } SwitchBinding *binding = @@ -2223,8 +2411,11 @@ void parse_option(Config *config, char *key, char *value) { "^\n]", fold_str, func_name, arg_value, arg_value2, arg_value3, arg_value4, arg_value5) < 3) { - fprintf(stderr, "Error: Invalid switchbind format: %s\n", value); - return; + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid switchbind " + "format: %s\n", + value); + return false; } trim_whitespace(fold_str); trim_whitespace(func_name); @@ -2252,8 +2443,13 @@ void parse_option(Config *config, char *key, char *value) { free(binding->arg.v3); binding->arg.v3 = NULL; } - fprintf(stderr, "Error: Unknown function in switchbind: %s\n", + + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Unknown dispatch in " + "switchbind: " + "\033[1m\033[31m%s\n", func_name); + return false; } else { config->switch_bindings_count++; } @@ -2264,8 +2460,9 @@ void parse_option(Config *config, char *key, char *value) { (config->gesture_bindings_count + 1) * sizeof(GestureBinding)); if (!config->gesture_bindings) { fprintf(stderr, - "Error: Failed to allocate memory for axis gesturebind\n"); - return; + "\033[1m\033[31m[ERROR]:\033[33m Failed to allocate " + "memory for axis gesturebind\n"); + return false; } GestureBinding *binding = @@ -2281,8 +2478,11 @@ void parse_option(Config *config, char *key, char *value) { "^,],%255[^,],%255[^\n]", mod_str, motion_str, fingers_count_str, func_name, arg_value, arg_value2, arg_value3, arg_value4, arg_value5) < 4) { - fprintf(stderr, "Error: Invalid gesturebind format: %s\n", value); - return; + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid gesturebind " + "format: %s\n", + value); + return false; } trim_whitespace(mod_str); @@ -2305,7 +2505,7 @@ void parse_option(Config *config, char *key, char *value) { parse_func_name(func_name, &binding->arg, arg_value, arg_value2, arg_value3, arg_value4, arg_value5); - if (!binding->func) { + if (!binding->func || binding->mod == UINT32_MAX) { if (binding->arg.v) { free(binding->arg.v); binding->arg.v = NULL; @@ -2318,8 +2518,12 @@ void parse_option(Config *config, char *key, char *value) { free(binding->arg.v3); binding->arg.v3 = NULL; } - fprintf(stderr, "Error: Unknown function in axisbind: %s\n", - func_name); + if (!binding->func) + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Unknown dispatch in " + "axisbind: \033[1m\033[31m%s\n", + func_name); + return false; } else { config->gesture_bindings_count++; } @@ -2327,22 +2531,30 @@ void parse_option(Config *config, char *key, char *value) { } else if (strncmp(key, "source", 6) == 0) { parse_config_file(config, value); } else { - fprintf(stderr, "Error: Unknown key: %s\n", key); + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Unknown keyword: " + "\033[1m\033[31m%s\n", + key); + return false; } + + return true; } -void parse_config_line(Config *config, const char *line) { +bool parse_config_line(Config *config, const char *line) { char key[256], value[256]; if (sscanf(line, "%255[^=]=%255[^\n]", key, value) != 2) { - // fprintf(stderr, "Error: Invalid line format: %s\n", line); - return; + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid line format: %s", + line); + return false; } // Then trim each part separately trim_whitespace(key); trim_whitespace(value); - parse_option(config, key, value); + return parse_option(config, key, value); } void parse_config_file(Config *config, const char *file_path) { @@ -2361,7 +2573,9 @@ void parse_config_file(Config *config, const char *file_path) { } else { const char *home = getenv("HOME"); if (!home) { - fprintf(stderr, "Error: HOME environment variable not set.\n"); + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m HOME environment " + "variable not set.\n"); return; } snprintf(full_path, sizeof(full_path), "%s/.config/mango/%s", home, @@ -2375,7 +2589,8 @@ void parse_config_file(Config *config, const char *file_path) { const char *home = getenv("HOME"); if (!home) { - fprintf(stderr, "Error: HOME environment variable not set.\n"); + fprintf(stderr, "\033[1m\033[31m[ERROR]:\033[33m HOME environment " + "variable not set.\n"); return; } snprintf(full_path, sizeof(full_path), "%s%s", home, file_path + 1); @@ -2392,11 +2607,21 @@ void parse_config_file(Config *config, const char *file_path) { } char line[512]; + bool parse_correct = true; + uint32_t line_count = 0; while (fgets(line, sizeof(line), file)) { + line_count++; if (line[0] == '#' || line[0] == '\n') { continue; } - parse_config_line(config, line); + parse_correct = parse_config_line(config, line); + if (!parse_correct) { + fprintf(stderr, + "\033[1;31m╰─\033[1;33m[Index]\033[0m " + "\033[1;36m%s\033[0m:\033[1;35m%d\033[0m\n" + " \033[1;36m╰─\033[0;33m%s\033[0m\n\n", + file_path, line_count, line); + } } fclose(file); diff --git a/src/mango.c b/src/mango.c index 1d332c4..5a8b6fc 100644 --- a/src/mango.c +++ b/src/mango.c @@ -6160,17 +6160,22 @@ int32_t main(int32_t argc, char *argv[]) { char *startup_cmd = NULL; int32_t c; - while ((c = getopt(argc, argv, "s:c:hdv")) != -1) { - if (c == 's') + while ((c = getopt(argc, argv, "s:c:hdvp")) != -1) { + if (c == 's') { startup_cmd = optarg; - else if (c == 'd') + } else if (c == 'd') { log_level = WLR_DEBUG; - else if (c == 'v') - die("mango " VERSION); - else if (c == 'c') + } else if (c == 'v') { + printf("mango " VERSION "\n"); + return EXIT_SUCCESS; + } else if (c == 'c') { cli_config_path = optarg; - else + } else if (c == 'p') { + parse_config(); + return EXIT_SUCCESS; + } else { goto usage; + } } if (optind < argc) goto usage; @@ -6184,7 +6189,14 @@ int32_t main(int32_t argc, char *argv[]) { run(startup_cmd); cleanup(); return EXIT_SUCCESS; - usage: - die("Usage: %s [-v] [-d] [-c config file] [-s startup command]", argv[0]); + printf("Usage: mango [OPTIONS]\n" + "\n" + "Options:\n" + " -v Show mango version\n" + " -d Enable debug log\n" + " -c Use custom configuration file\n" + " -s Execute startup command\n" + " -p Check configuration file error\n"); + return EXIT_SUCCESS; } From eb0607501d3cdfa352dcc29ba35c46b79bc2fba8 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 2 Feb 2026 14:47:13 +0800 Subject: [PATCH 18/78] opt: change drag resize request limit to 120hz for floating window --- src/config/preset.h | 3 ++- src/layout/arrange.h | 6 +++--- src/mango.c | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/config/preset.h b/src/config/preset.h index 3d05161..ae4424f 100644 --- a/src/config/preset.h +++ b/src/config/preset.h @@ -108,7 +108,8 @@ int32_t drag_warp_cursor = 1; int32_t xwayland_persistence = 1; /* xwayland persistence */ int32_t syncobj_enable = 0; int32_t allow_lock_transparent = 0; -double drag_refresh_interval = 16.0; +double drag_tile_refresh_interval = 16.0; +double drag_floating_refresh_interval = 8.0; int32_t allow_tearing = TEARING_DISABLED; int32_t allow_shortcuts_inhibit = SHORTCUTS_INHIBIT_ENABLE; diff --git a/src/layout/arrange.h b/src/layout/arrange.h index 6ff1dd2..3721364 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -210,7 +210,7 @@ void resize_tile_master_horizontal(Client *grabc, bool isdrag, int32_t offsetx, } if (last_apply_drap_time == 0 || - time - last_apply_drap_time > drag_refresh_interval) { + time - last_apply_drap_time > drag_tile_refresh_interval) { arrange(grabc->mon, false, false); last_apply_drap_time = time; } @@ -367,7 +367,7 @@ void resize_tile_master_vertical(Client *grabc, bool isdrag, int32_t offsetx, } if (last_apply_drap_time == 0 || - time - last_apply_drap_time > drag_refresh_interval) { + time - last_apply_drap_time > drag_tile_refresh_interval) { arrange(grabc->mon, false, false); last_apply_drap_time = time; } @@ -548,7 +548,7 @@ void resize_tile_scroller(Client *grabc, bool isdrag, int32_t offsetx, } if (last_apply_drap_time == 0 || - time - last_apply_drap_time > drag_refresh_interval) { + time - last_apply_drap_time > drag_tile_refresh_interval) { arrange(grabc->mon, false, false); last_apply_drap_time = time; } diff --git a/src/mango.c b/src/mango.c index 5a8b6fc..f5583c3 100644 --- a/src/mango.c +++ b/src/mango.c @@ -4143,7 +4143,7 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, if (grabc->isfloating) { grabc->iscustomsize = 1; if (last_apply_drap_time == 0 || - time - last_apply_drap_time > drag_refresh_interval) { + time - last_apply_drap_time > drag_floating_refresh_interval) { resize_floating_window(grabc); last_apply_drap_time = time; } From 0546a2d4c472390f23171f7750b5702f1acb8582 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 3 Feb 2026 10:06:26 +0800 Subject: [PATCH 19/78] opt: allow use comma symbol in spawn shell value --- src/config/parse_config.h | 171 +++++++++++++++++++++++++------------- 1 file changed, 114 insertions(+), 57 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index f7ce848..1a025e9 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -550,6 +550,48 @@ static bool starts_with_ignore_case(const char *str, const char *prefix) { return true; } +static char *combine_args_until_empty(char *values[], int count) { + // find the first empty string + int first_empty = count; + for (int i = 0; i < count; i++) { + // check if it's empty: empty string or only contains "0" (initialized) + if (values[i][0] == '\0' || + (strlen(values[i]) == 1 && values[i][0] == '0')) { + first_empty = i; + break; + } + } + + // if there are no valid parameters, return an empty string + if (first_empty == 0) { + return strdup(""); + } + + // calculate the total length + size_t total_len = 0; + for (int i = 0; i < first_empty; i++) { + total_len += strlen(values[i]); + } + // plus the number of commas (first_empty-1 commas) + total_len += (first_empty - 1); + + // allocate memory and concatenate + char *combined = malloc(total_len + 1); + if (combined == NULL) { + return strdup(""); + } + + combined[0] = '\0'; + for (int i = 0; i < first_empty; i++) { + if (i > 0) { + strcat(combined, ","); + } + strcat(combined, values[i]); + } + + return combined; +} + uint32_t parse_mod(const char *mod_str) { if (!mod_str || !*mod_str) { return UINT32_MAX; @@ -1005,10 +1047,14 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value, (*arg).ui = atoi(arg_value); } else if (strcmp(func_name, "spawn") == 0) { func = spawn; - (*arg).v = strdup(arg_value); + char *values[] = {arg_value, arg_value2, arg_value3, arg_value4, + arg_value5}; + (*arg).v = combine_args_until_empty(values, 5); } else if (strcmp(func_name, "spawn_shell") == 0) { func = spawn_shell; - (*arg).v = strdup(arg_value); + char *values[] = {arg_value, arg_value2, arg_value3, arg_value4, + arg_value5}; + (*arg).v = combine_args_until_empty(values, 5); } else if (strcmp(func_name, "spawn_on_empty") == 0) { func = spawn_on_empty; (*arg).v = strdup(arg_value); // 注意:之后需要释放这个内存 @@ -1385,9 +1431,8 @@ bool parse_option(Config *config, char *key, char *value) { config->scroller_proportion_preset = (float *)malloc(float_count * sizeof(float)); if (!config->scroller_proportion_preset) { - fprintf( - stderr, - "\033[1m\033[31m[ERROR]:\033[33m Memory allocation failed\n"); + fprintf(stderr, "\033[1m\033[31m[ERROR]:\033[33m Memory " + "allocation failed\n"); return false; } @@ -1400,18 +1445,19 @@ bool parse_option(Config *config, char *key, char *value) { while (token != NULL && i < float_count) { if (sscanf(token, "%f", &value_set) != 1) { - fprintf( - stderr, - "\033[1m\033[31m[ERROR]:\033[33m Invalid float value in " - "scroller_proportion_preset: %s\n", - token); + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid float " + "value in " + "scroller_proportion_preset: %s\n", + token); free(value_copy); free(config->scroller_proportion_preset); config->scroller_proportion_preset = NULL; return false; } - // Clamp the value between 0.0 and 1.0 (or your desired range) + // Clamp the value between 0.0 and 1.0 (or your desired + // range) config->scroller_proportion_preset[i] = CLAMP_FLOAT(value_set, 0.1f, 1.0f); @@ -1448,9 +1494,8 @@ bool parse_option(Config *config, char *key, char *value) { config->circle_layout = (char **)malloc(string_count * sizeof(char *)); memset(config->circle_layout, 0, string_count * sizeof(char *)); if (!config->circle_layout) { - fprintf( - stderr, - "\033[1m\033[31m[ERROR]:\033[33m Memory allocation failed\n"); + fprintf(stderr, "\033[1m\033[31m[ERROR]:\033[33m Memory " + "allocation failed\n"); return false; } @@ -1606,7 +1651,8 @@ bool parse_option(Config *config, char *key, char *value) { int64_t color = parse_color(value); if (color == -1) { fprintf(stderr, - "\033[1m\033[31m[ERROR]:\033[33m Invalid rootcolor format: " + "\033[1m\033[31m[ERROR]:\033[33m Invalid rootcolor " + "format: " "%s\n", value); return false; @@ -1650,11 +1696,11 @@ bool parse_option(Config *config, char *key, char *value) { } else if (strcmp(key, "maximizescreencolor") == 0) { int64_t color = parse_color(value); if (color == -1) { - fprintf( - stderr, - "\033[1m\033[31m[ERROR]:\033[33m Invalid maximizescreencolor " - "format: %s\n", - value); + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "maximizescreencolor " + "format: %s\n", + value); return false; } else { convert_hex_to_rgba(config->maximizescreencolor, color); @@ -1674,7 +1720,8 @@ bool parse_option(Config *config, char *key, char *value) { int64_t color = parse_color(value); if (color == -1) { fprintf(stderr, - "\033[1m\033[31m[ERROR]:\033[33m Invalid scratchpadcolor " + "\033[1m\033[31m[ERROR]:\033[33m Invalid " + "scratchpadcolor " "format: %s\n", value); return false; @@ -1760,11 +1807,11 @@ bool parse_option(Config *config, char *key, char *value) { } else if (strcmp(key, "vrr") == 0) { rule->vrr = CLAMP_INT(atoi(val), 0, 1); } else { - fprintf( - stderr, - "\033[1m\033[31m[ERROR]:\033[33m Unknown monitor rule " - "option:\033[1m\033[31m %s\n", - key); + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Unknown " + "monitor rule " + "option:\033[1m\033[31m %s\n", + key); parse_error = true; } } @@ -1824,7 +1871,8 @@ bool parse_option(Config *config, char *key, char *value) { rule->mfact = CLAMP_FLOAT(atof(val), 0.1f, 0.9f); } else { fprintf(stderr, - "\033[1m\033[31m[ERROR]:\033[33m Unknown tag rule " + "\033[1m\033[31m[ERROR]:\033[33m Unknown " + "tag rule " "option:\033[1m\033[31m %s\n", key); parse_error = true; @@ -1882,11 +1930,11 @@ bool parse_option(Config *config, char *key, char *value) { } else if (strcmp(key, "noshadow") == 0) { rule->noshadow = CLAMP_INT(atoi(val), 0, 1); } else { - fprintf( - stderr, - "\033[1m\033[31m[ERROR]:\033[33m Unknown layer rule " - "option:\033[1m\033[31m %s\n", - key); + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Unknown " + "layer rule " + "option:\033[1m\033[31m %s\n", + key); parse_error = true; } } @@ -2074,11 +2122,11 @@ bool parse_option(Config *config, char *key, char *value) { return false; } } else { - fprintf( - stderr, - "\033[1m\033[31m[ERROR]:\033[33m Unknown window rule " - "option:\033[1m\033[31m %s\n", - key); + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Unknown " + "window rule " + "option:\033[1m\033[31m %s\n", + key); parse_error = true; } } @@ -2178,7 +2226,8 @@ bool parse_option(Config *config, char *key, char *value) { 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[^,],%255[^,],%255[^,],%255[^" + ",],%255[" "^,],%255[^\n]", mod_str, keysym_str, func_name, arg_value, arg_value2, arg_value3, arg_value4, arg_value5) < 3) { @@ -2235,11 +2284,11 @@ bool parse_option(Config *config, char *key, char *value) { binding->arg.v3 = NULL; } if (!binding->func) - fprintf( - stderr, - "\033[1m\033[31m[ERROR]:\033[33m Unknown dispatch in bind: " - "\033[1m\033[31m%s\n", - func_name); + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Unknown " + "dispatch in bind: " + "\033[1m\033[31m%s\n", + func_name); return false; } else { config->key_bindings_count++; @@ -2265,12 +2314,14 @@ bool parse_option(Config *config, char *key, char *value) { 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[^,],%255[^,],%255[^,],%255[^" + ",],%255[" "^,],%255[^\n]", mod_str, button_str, func_name, arg_value, arg_value2, arg_value3, arg_value4, arg_value5) < 3) { fprintf(stderr, - "\033[1m\033[31m[ERROR]:\033[33m Invalid mousebind format: " + "\033[1m\033[31m[ERROR]:\033[33m Invalid mousebind " + "format: " "%s\n", value); return false; @@ -2308,7 +2359,8 @@ bool parse_option(Config *config, char *key, char *value) { } if (!binding->func) fprintf(stderr, - "\033[1m\033[31m[ERROR]:\033[33m Unknown dispatch in " + "\033[1m\033[31m[ERROR]:\033[33m Unknown " + "dispatch in " "mousebind: \033[1m\033[31m%s\n", func_name); return false; @@ -2335,14 +2387,15 @@ bool parse_option(Config *config, char *key, char *value) { 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[^,],%255[^,],%255[^,],%255[^" + ",],%255[" "^,],%255[^\n]", mod_str, dir_str, func_name, arg_value, arg_value2, arg_value3, arg_value4, arg_value5) < 3) { - fprintf( - stderr, - "\033[1m\033[31m[ERROR]:\033[33m Invalid axisbind format: %s\n", - value); + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Invalid axisbind " + "format: %s\n", + value); return false; } @@ -2379,7 +2432,8 @@ bool parse_option(Config *config, char *key, char *value) { } if (!binding->func) fprintf(stderr, - "\033[1m\033[31m[ERROR]:\033[33m Unknown dispatch in " + "\033[1m\033[31m[ERROR]:\033[33m Unknown " + "dispatch in " "axisbind: \033[1m\033[31m%s\n", func_name); return false; @@ -2407,7 +2461,8 @@ bool parse_option(Config *config, char *key, char *value) { 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[^,],%255[^,],%255[^,],%255[^" + ",],%255[" "^\n]", fold_str, func_name, arg_value, arg_value2, arg_value3, arg_value4, arg_value5) < 3) { @@ -2474,7 +2529,8 @@ bool parse_option(Config *config, char *key, char *value) { 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[^,],%255[^,],%255[^,],%255[^" + ",],%255[" "^,],%255[^,],%255[^\n]", mod_str, motion_str, fingers_count_str, func_name, arg_value, arg_value2, arg_value3, arg_value4, arg_value5) < 4) { @@ -2520,7 +2576,8 @@ bool parse_option(Config *config, char *key, char *value) { } if (!binding->func) fprintf(stderr, - "\033[1m\033[31m[ERROR]:\033[33m Unknown dispatch in " + "\033[1m\033[31m[ERROR]:\033[33m Unknown " + "dispatch in " "axisbind: \033[1m\033[31m%s\n", func_name); return false; @@ -3134,8 +3191,8 @@ void set_value_default() { config.hotarea_size = hotarea_size; // 热区大小,10x10 config.hotarea_corner = hotarea_corner; config.enable_hotarea = enable_hotarea; // 是否启用鼠标热区 - config.smartgaps = - smartgaps; /* 1 means no outer gap when there is only one window */ + config.smartgaps = smartgaps; /* 1 means no outer gap when there is + only one window */ config.sloppyfocus = sloppyfocus; /* focus follows mouse */ config.gappih = gappih; /* horiz inner gap between windows */ config.gappiv = gappiv; /* vert inner gap between windows */ From 50b67ac539e65f68ae177de258c7f2fca59bc350 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 3 Feb 2026 10:15:42 +0800 Subject: [PATCH 20/78] fix: miss init arg value --- src/config/parse_config.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 1a025e9..e0193c4 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -2262,6 +2262,12 @@ bool parse_option(Config *config, char *key, char *value) { binding->keysymcode = parse_key(keysym_str, binding->keysymcode.type == KEY_TYPE_SYM); binding->mod = parse_mod(mod_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; @@ -2337,6 +2343,12 @@ bool parse_option(Config *config, char *key, char *value) { binding->mod = parse_mod(mod_str); binding->button = parse_button(button_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; @@ -2554,6 +2566,12 @@ bool parse_option(Config *config, char *key, char *value) { binding->mod = parse_mod(mod_str); binding->motion = parse_direction(motion_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; From 336f873d83582a41188893fc0c0849867d28d589 Mon Sep 17 00:00:00 2001 From: quadratic Date: Tue, 3 Feb 2026 21:11:41 +0100 Subject: [PATCH 21/78] fix: uninitialised argument fields when calling mmsg -d --- src/config/parse_config.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index e0193c4..3fcd518 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -897,6 +897,12 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value, char *arg_value5) { FuncType func = NULL; + (*arg).i = 0; + (*arg).i2 = 0; + (*arg).f = 0.0f; + (*arg).f2 = 0.0f; + (*arg).ui = 0; + (*arg).ui2 = 0; (*arg).v = NULL; (*arg).v2 = NULL; (*arg).v3 = NULL; From 8ba259fbb7737e4cef29ca20c731ed0a93e4017d Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 4 Feb 2026 09:45:26 +0800 Subject: [PATCH 22/78] opt: avoid opacity flickering in focus animation when open new window --- src/animation/client.h | 41 +++++++++++++-------------------------- src/config/parse_config.h | 12 ++++++------ src/mango.c | 6 ++++++ 3 files changed, 25 insertions(+), 34 deletions(-) diff --git a/src/animation/client.h b/src/animation/client.h index 7bc47ac..8de9b06 100644 --- a/src/animation/client.h +++ b/src/animation/client.h @@ -1054,20 +1054,11 @@ void client_set_focused_opacity_animation(Client *c) { sizeof(c->opacity_animation.target_border_color)); c->opacity_animation.target_opacity = c->focused_opacity; c->opacity_animation.time_started = get_now_in_ms(); - if (c->opacity_animation.running) { - memcpy(c->opacity_animation.initial_border_color, - c->opacity_animation.current_border_color, - sizeof(c->opacity_animation.initial_border_color)); - c->opacity_animation.initial_opacity = - c->opacity_animation.current_opacity; - } else { - memcpy(c->opacity_animation.initial_border_color, bordercolor, - sizeof(c->opacity_animation.initial_border_color)); - memcpy(c->opacity_animation.current_border_color, bordercolor, - sizeof(c->opacity_animation.current_border_color)); - c->opacity_animation.initial_opacity = c->unfocused_opacity; - c->opacity_animation.current_opacity = c->unfocused_opacity; - } + memcpy(c->opacity_animation.initial_border_color, + c->opacity_animation.current_border_color, + sizeof(c->opacity_animation.initial_border_color)); + c->opacity_animation.initial_opacity = c->opacity_animation.current_opacity; + c->opacity_animation.running = true; } @@ -1087,20 +1078,10 @@ void client_set_unfocused_opacity_animation(Client *c) { c->opacity_animation.target_opacity = c->unfocused_opacity; c->opacity_animation.time_started = get_now_in_ms(); - if (c->opacity_animation.running) { - memcpy(c->opacity_animation.initial_border_color, - c->opacity_animation.current_border_color, - sizeof(c->opacity_animation.initial_border_color)); - c->opacity_animation.initial_opacity = - c->opacity_animation.current_opacity; - } else { - memcpy(c->opacity_animation.initial_border_color, border_color, - sizeof(c->opacity_animation.initial_border_color)); - memcpy(c->opacity_animation.current_border_color, border_color, - sizeof(c->opacity_animation.current_border_color)); - c->opacity_animation.initial_opacity = c->focused_opacity; - c->opacity_animation.current_opacity = c->focused_opacity; - } + memcpy(c->opacity_animation.initial_border_color, + c->opacity_animation.current_border_color, + sizeof(c->opacity_animation.initial_border_color)); + c->opacity_animation.initial_opacity = c->opacity_animation.current_opacity; c->opacity_animation.running = true; } @@ -1135,6 +1116,10 @@ bool client_apply_focus_opacity(Client *c) { if (target_opacity > opacity) { target_opacity = opacity; } + memcpy(c->opacity_animation.current_border_color, + c->opacity_animation.target_border_color, + sizeof(c->opacity_animation.current_border_color)); + c->opacity_animation.current_opacity = target_opacity; client_set_opacity(c, target_opacity); client_set_border_color(c, c->opacity_animation.target_border_color); } else if (animations && c->opacity_animation.running) { diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 3fcd518..efc9c96 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -897,12 +897,12 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value, char *arg_value5) { FuncType func = NULL; - (*arg).i = 0; - (*arg).i2 = 0; - (*arg).f = 0.0f; - (*arg).f2 = 0.0f; - (*arg).ui = 0; - (*arg).ui2 = 0; + (*arg).i = 0; + (*arg).i2 = 0; + (*arg).f = 0.0f; + (*arg).f2 = 0.0f; + (*arg).ui = 0; + (*arg).ui2 = 0; (*arg).v = NULL; (*arg).v2 = NULL; (*arg).v3 = NULL; diff --git a/src/mango.c b/src/mango.c index f5583c3..2584613 100644 --- a/src/mango.c +++ b/src/mango.c @@ -3825,6 +3825,12 @@ void init_client_properties(Client *c) { c->stack_proportion = 0.0f; c->next_in_stack = NULL; c->prev_in_stack = NULL; + memcpy(c->opacity_animation.initial_border_color, bordercolor, + sizeof(c->opacity_animation.initial_border_color)); + memcpy(c->opacity_animation.current_border_color, bordercolor, + sizeof(c->opacity_animation.current_border_color)); + c->opacity_animation.initial_opacity = c->unfocused_opacity; + c->opacity_animation.current_opacity = c->unfocused_opacity; } void // old fix to 0.5 From b0f839468c6b4a9bd5a037d380f2ed4ee2a89dfe Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 4 Feb 2026 17:42:13 +0800 Subject: [PATCH 23/78] opt: don't change stack inner per when swallow --- src/mango.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/mango.c b/src/mango.c index 2584613..b37cc1f 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1071,6 +1071,9 @@ void swallow(Client *c, Client *w) { c->tags = w->tags; c->geom = w->geom; c->float_geom = w->float_geom; + c->stack_inner_per = w->stack_inner_per; + c->master_inner_per = w->master_inner_per; + c->master_mfact_per = w->master_mfact_per; c->scroller_proportion = w->scroller_proportion; c->next_in_stack = w->next_in_stack; c->prev_in_stack = w->prev_in_stack; @@ -4678,6 +4681,7 @@ setfloating(Client *c, int32_t floating) { Client *fc = NULL; struct wlr_box target_box; + int32_t old_floating_state = c->isfloating; c->isfloating = floating; bool window_size_outofrange = false; @@ -4747,7 +4751,7 @@ setfloating(Client *c, int32_t floating) { layers[c->isfloating ? LyrTop : LyrTile]); } - if (!c->isfloating) { + if (!c->isfloating && old_floating_state) { set_size_per(c->mon, c); } @@ -4796,6 +4800,7 @@ void setmaximizescreen(Client *c, int32_t maximizescreen) { if (c->mon->isoverview) return; + int32_t old_maximizescreen_state = c->ismaximizescreen; c->ismaximizescreen = maximizescreen; if (maximizescreen) { @@ -4825,7 +4830,7 @@ void setmaximizescreen(Client *c, int32_t maximizescreen) { wlr_scene_node_reparent(&c->scene->node, layers[c->isfloating ? LyrTop : LyrTile]); - if (!c->ismaximizescreen) { + if (!c->ismaximizescreen && old_maximizescreen_state) { set_size_per(c->mon, c); } @@ -4858,6 +4863,7 @@ void setfullscreen(Client *c, int32_t fullscreen) // 用自定义全屏代理自 if (c->mon->isoverview) return; + int32_t old_fullscreen_state = c->isfullscreen; c->isfullscreen = fullscreen; client_set_fullscreen(c, fullscreen); @@ -4895,7 +4901,7 @@ void setfullscreen(Client *c, int32_t fullscreen) // 用自定义全屏代理自 layers[fullscreen || c->isfloating ? LyrTop : LyrTile]); } - if (!c->isfullscreen) { + if (!c->isfullscreen && old_fullscreen_state) { set_size_per(c->mon, c); } From a4faf2c494cc09b95ff222959d7f3566d8234c1d Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 4 Feb 2026 21:29:37 +0800 Subject: [PATCH 24/78] opt: avoid stack inner per change when switch tty --- src/mango.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mango.c b/src/mango.c index b37cc1f..171401e 100644 --- a/src/mango.c +++ b/src/mango.c @@ -5810,7 +5810,8 @@ void updatemons(struct wl_listener *listener, void *data) { if (selmon && selmon->wlr_output->enabled) { wl_list_for_each(c, &clients, link) { if (!c->mon && client_surface(c)->mapped) { - client_change_mon(c, selmon); + c->mon = selmon; + reset_foreign_tolevel(c); } } focusclient(focustop(selmon), 1); From 2bda8e3bf15b82676dc8765c7cf25c67e5041952 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 4 Feb 2026 21:42:05 +0800 Subject: [PATCH 25/78] opt: avoid double reset foreign toplevel handle --- src/mango.c | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/src/mango.c b/src/mango.c index 171401e..591d4f6 100644 --- a/src/mango.c +++ b/src/mango.c @@ -969,7 +969,6 @@ static struct wlr_xwayland *xwayland; void client_change_mon(Client *c, Monitor *m) { setmon(c, m, c->tags, true); - reset_foreign_tolevel(c); if (c->isfloating) { c->float_geom = c->geom = setclient_coordinate_center(c, c->mon, c->geom, 0, 0); @@ -2026,7 +2025,6 @@ buttonpress(struct wl_listener *listener, void *data) { selmon = xytomon(cursor->x, cursor->y); client_update_oldmonname_record(grabc, selmon); setmon(grabc, selmon, 0, true); - reset_foreign_tolevel(grabc); selmon->prevsel = ISTILED(selmon->sel) ? selmon->sel : NULL; selmon->sel = grabc; tmpc = grabc; @@ -5030,6 +5028,7 @@ void setmon(Client *c, Monitor *m, uint32_t newtags, bool focus) { arrange(oldmon, false, false); if (m) { /* Make sure window actually overlaps with the monitor */ + reset_foreign_tolevel(c); resize(c, c->geom, 0); c->tags = newtags ? newtags @@ -5041,21 +5040,6 @@ void setmon(Client *c, Monitor *m, uint32_t newtags, bool focus) { if (focus && !client_is_x11_popup(c)) { focusclient(focustop(selmon), 1); } - - if (m) { - - if (c->foreign_toplevel) { - remove_foreign_topleve(c); - } - - add_foreign_toplevel(c); - if (m->sel && m->sel->foreign_toplevel) - wlr_foreign_toplevel_handle_v1_set_activated( - m->sel->foreign_toplevel, false); - if (c->foreign_toplevel) - wlr_foreign_toplevel_handle_v1_set_activated(c->foreign_toplevel, - true); - } } void setpsel(struct wl_listener *listener, void *data) { From 65fcd58949ce55eca3195bdbc6f935058fc3b33c Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 4 Feb 2026 21:50:44 +0800 Subject: [PATCH 26/78] opt: optimize file detect message --- src/config/parse_config.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index efc9c96..a559432 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -2683,7 +2683,10 @@ void parse_config_file(Config *config, const char *file_path) { } if (!file) { - perror("Error opening file"); + fprintf(stderr, + "\033[1;31m\033[1;33m[ERROR]:\033[0m Failed to open " + "config file: %s\n", + file_path); return; } From 8e898417a75bc48cdc79409dba2b415f692d7ea6 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 5 Feb 2026 11:06:40 +0800 Subject: [PATCH 27/78] opt: key name case insensitive in keybind --- src/config/parse_config.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index a559432..b795a81 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -779,8 +779,9 @@ KeySymCode parse_key(const char *key_str, bool isbindsym) { return kc; } - // 普通键名直接转换 - xkb_keysym_t sym = xkb_keysym_from_name(key_str, XKB_KEYSYM_NO_FLAGS); + // change key string to keysym, case insensitive + xkb_keysym_t sym = + xkb_keysym_from_name(key_str, XKB_KEYSYM_CASE_INSENSITIVE); if (isbindsym) { kc.type = KEY_TYPE_SYM; From 00a7e579c94bd786cf1fd379b5b666e475dd4735 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 6 Feb 2026 07:43:09 +0800 Subject: [PATCH 28/78] fix: miss reset root color when reload config --- src/config/parse_config.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index b795a81..7e24d43 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -3558,6 +3558,8 @@ void reapply_cursor_style(void) { } } +void reapply_rootbg(void) { wlr_scene_rect_set_color(root_bg, rootcolor); } + void reapply_border(void) { Client *c = NULL; @@ -3683,6 +3685,7 @@ void reset_option(void) { reapply_cursor_style(); reapply_border(); + reapply_rootbg(); reapply_keyboard(); reapply_pointer(); reapply_master(); From 080dfaa3ae41cca9d02df9a529ed6a65edb97b41 Mon Sep 17 00:00:00 2001 From: Talmed Date: Fri, 6 Feb 2026 19:04:16 +1100 Subject: [PATCH 29/78] Add DesktopNames entry to mango.desktop --- mango.desktop | 1 + 1 file changed, 1 insertion(+) diff --git a/mango.desktop b/mango.desktop index 0c109ce..37c4fad 100644 --- a/mango.desktop +++ b/mango.desktop @@ -1,6 +1,7 @@ [Desktop Entry] Encoding=UTF-8 Name=Mango +DesktopNames=mango;wlroots Comment=mango WM Exec=mango Icon=mango From 818652d20f43598b245199cac9329edebbbbbae4 Mon Sep 17 00:00:00 2001 From: Lin Xianyi Date: Sat, 7 Feb 2026 12:12:13 +0800 Subject: [PATCH 30/78] 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 7e24d43..b8426e2 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 591d4f6..6b8a390 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 31/78] 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 ba073aa..ab87b2b 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 32/78] 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 6b8a390..dcd2753 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 33/78] 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 d512f39..8aaaeb9 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 8de9b06..2588fb1 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 8995a5d..49ab398 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 34/78] 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 ab87b2b..fb1d04f 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 dcd2753..8883181 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 35/78] 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 8883181..d75faed 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 36/78] 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 b8426e2..a795961 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 37/78] 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 d75faed..5426281 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 38/78] 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 c0e51dc..1d29bc9 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 5426281..6609270 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 39/78] 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 6609270..e15b08f 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 40/78] 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 e15b08f..d6876c5 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 41/78] Update README.md --- README.md | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/README.md b/README.md index 8aaaeb9..b78ccfa 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 454145f6e03a462b743c64776d2b17121c2637ea Mon Sep 17 00:00:00 2001 From: Daniel Jampen Date: Sun, 8 Feb 2026 17:56:26 +0100 Subject: [PATCH 42/78] support isfakefullscreen as windowrule property --- src/config/parse_config.h | 4 ++++ src/mango.c | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 7e24d43..49936de 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -58,6 +58,7 @@ typedef struct { uint32_t tags; int32_t isfloating; int32_t isfullscreen; + int32_t isfakefullscreen; float scroller_proportion; const char *animation_type_open; const char *animation_type_close; @@ -1972,6 +1973,7 @@ bool parse_option(Config *config, char *key, char *value) { // int32_t rule value, relay to a client property rule->isfloating = -1; rule->isfullscreen = -1; + rule->isfakefullscreen = -1; rule->isnoborder = -1; rule->isnoshadow = -1; rule->isnoradius = -1; @@ -2111,6 +2113,8 @@ bool parse_option(Config *config, char *key, char *value) { rule->scroller_proportion = atof(val); } else if (strcmp(key, "isfullscreen") == 0) { rule->isfullscreen = atoi(val); + } else if (strcmp(key, "isfakefullscreen") == 0) { + rule->isfakefullscreen = atoi(val); } else if (strcmp(key, "globalkeybinding") == 0) { char mod_str[256], keysym_str[256]; sscanf(val, "%255[^-]-%255[a-zA-Z]", mod_str, keysym_str); diff --git a/src/mango.c b/src/mango.c index 591d4f6..8ec1b5b 100644 --- a/src/mango.c +++ b/src/mango.c @@ -1289,6 +1289,7 @@ static void apply_rule_properties(Client *c, const ConfigWinRule *r) { APPLY_INT_PROP(c, r, no_force_center); APPLY_INT_PROP(c, r, isfloating); APPLY_INT_PROP(c, r, isfullscreen); + APPLY_INT_PROP(c, r, isfakefullscreen); APPLY_INT_PROP(c, r, isnoborder); APPLY_INT_PROP(c, r, isnoshadow); APPLY_INT_PROP(c, r, isnoradius); @@ -1482,6 +1483,10 @@ void applyrules(Client *c) { setfullscreen(c, fullscreen_state_backup); + if (c->isfakefullscreen) { + setfakefullscreen(c, 1); + } + /* if there is a new non-floating window in the current tag, the fullscreen window in the current tag will exit fullscreen and participate in tiling From 6b79a432a4e81e757775ce9edf89acc1b4859d74 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 9 Feb 2026 07:52:10 +0800 Subject: [PATCH 43/78] 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 6cbb3a2..15b654c 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 d6876c5..44f6728 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 44/78] 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 a795961..1ab243c 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 45/78] 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 0565609..478ef0e 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 46/78] 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 1ab243c..556fc35 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 44f6728..495cba0 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 47/78] 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 495cba0..31d38ea 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 48/78] 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 31d38ea..3ce7422 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 49/78] 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 556fc35..7e6db02 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 3ce7422..63f0e27 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 50/78] 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 63f0e27..e84c498 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 51/78] 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 e84c498..8633dcd 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 52/78] 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 8633dcd..ca25dbc 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) { From 5ae8975b11395dd73594fa7ea187fd5798510e30 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 13 Feb 2026 10:44:17 +0800 Subject: [PATCH 53/78] bump version to 0.12.2 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 478ef0e..e7979bd 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('mango', ['c', 'cpp'], - version : '0.12.1', + version : '0.12.2', ) subdir('protocols') From f25161552455d076f0f15cadf522a8b57b829e2f Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 13 Feb 2026 11:06:12 +0800 Subject: [PATCH 54/78] opt: flush the blur background cache when unmap a background layer --- src/mango.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/mango.c b/src/mango.c index ca25dbc..e8fd2e6 100644 --- a/src/mango.c +++ b/src/mango.c @@ -2275,6 +2275,19 @@ static void iter_layer_scene_buffers(struct wlr_scene_buffer *buffer, } } +void layer_flush_blur_background(LayerSurface *l) { + if (!blur) + return; + + // 如果背景层发生变化,标记优化的模糊背景缓存需要更新 + if (l->layer_surface->current.layer == + ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND) { + if (l->mon) { + wlr_scene_optimized_blur_mark_dirty(l->mon->blur); + } + } +} + void maplayersurfacenotify(struct wl_listener *listener, void *data) { LayerSurface *l = wl_container_of(listener, l, map); struct wlr_layer_surface_v1 *layer_surface = l->layer_surface; @@ -2402,15 +2415,7 @@ void commitlayersurfacenotify(struct wl_listener *listener, void *data) { } } - if (blur) { - // 如果背景层发生变化,标记优化的模糊背景缓存需要更新 - if (layer_surface->current.layer == - ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND) { - if (l->mon) { - wlr_scene_optimized_blur_mark_dirty(l->mon->blur); - } - } - } + layer_flush_blur_background(l); if (layer_surface == exclusive_focus && layer_surface->current.keyboard_interactive != @@ -5643,6 +5648,7 @@ void unmaplayersurfacenotify(struct wl_listener *listener, void *data) { focusclient(focustop(selmon), 1); motionnotify(0, NULL, 0, 0, 0, 0); l->being_unmapped = false; + layer_flush_blur_background(l); wlr_scene_node_destroy(&l->shadow->node); l->shadow = NULL; } From 711498490bdf3119ac5cb4debb60f6379e5d5df1 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 13 Feb 2026 18:11:18 +0800 Subject: [PATCH 55/78] opt: not back to ov tag when view prev tag --- src/dispatch/bind_define.h | 15 ++++++++++++++- src/mango.c | 2 ++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 1d29bc9..ea213e4 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1521,6 +1521,16 @@ int32_t minimized(const Arg *arg) { return 0; } +void fix_mon_tagset_from_overview(Monitor *m) { + if (m->tagset[m->seltags] == (m->ovbk_prev_tagset & TAGMASK)) { + m->tagset[m->seltags ^ 1] = m->ovbk_current_tagset; + m->pertag->prevtag = get_tags_first_tag_num(m->ovbk_current_tagset); + } else { + m->tagset[m->seltags ^ 1] = m->ovbk_prev_tagset; + m->pertag->prevtag = get_tags_first_tag_num(m->ovbk_prev_tagset); + } +} + int32_t toggleoverview(const Arg *arg) { Client *c = NULL; @@ -1542,6 +1552,8 @@ int32_t toggleoverview(const Arg *arg) { visible_client_number++; } if (visible_client_number > 0) { + selmon->ovbk_current_tagset = selmon->tagset[selmon->seltags]; + selmon->ovbk_prev_tagset = selmon->tagset[selmon->seltags ^ 1]; target = ~0 & TAGMASK; } else { selmon->isoverview ^= 1; @@ -1552,6 +1564,7 @@ int32_t toggleoverview(const Arg *arg) { } else if (!selmon->isoverview && !selmon->sel) { target = (1 << (selmon->pertag->prevtag - 1)); view(&(Arg){.ui = target}, false); + fix_mon_tagset_from_overview(selmon); refresh_monitors_workspaces_status(selmon); return 0; } @@ -1574,7 +1587,7 @@ int32_t toggleoverview(const Arg *arg) { } view(&(Arg){.ui = target}, false); - + fix_mon_tagset_from_overview(selmon); refresh_monitors_workspaces_status(selmon); return 0; } diff --git a/src/mango.c b/src/mango.c index e8fd2e6..5c0a610 100644 --- a/src/mango.c +++ b/src/mango.c @@ -514,6 +514,8 @@ struct Monitor { int32_t gappoh; /* horizontal outer gaps */ int32_t gappov; /* vertical outer gaps */ Pertag *pertag; + uint32_t ovbk_current_tagset; + uint32_t ovbk_prev_tagset; Client *sel, *prevsel; int32_t isoverview; int32_t is_in_hotarea; From 8a924494c6693529eb83b043bf56cbff696e78e1 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 13 Feb 2026 18:16:06 +0800 Subject: [PATCH 56/78] opt: the tagset is current tagset when open window in ov mode --- src/mango.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/mango.c b/src/mango.c index 5c0a610..b0fde12 100644 --- a/src/mango.c +++ b/src/mango.c @@ -5097,9 +5097,15 @@ void setmon(Client *c, Monitor *m, uint32_t newtags, bool focus) { /* Make sure window actually overlaps with the monitor */ reset_foreign_tolevel(c); resize(c, c->geom, 0); - c->tags = - newtags ? newtags - : m->tagset[m->seltags]; /* assign tags of target monitor */ + if (!newtags && !m->isoverview) { + c->tags = m->tagset[m->seltags]; + } else if (!newtags && m->isoverview) { + c->tags = m->ovbk_current_tagset; + } else if (newtags) { + c->tags = newtags; + } else { + c->tags = m->tagset[m->seltags]; + } setfloating(c, c->isfloating); setfullscreen(c, c->isfullscreen); /* This will call arrange(c->mon) */ } From c05eec7f53a8f1af25826cdc5bb3104b791e8cbb Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 13 Feb 2026 20:02:23 +0800 Subject: [PATCH 57/78] feat: support restore stack from non-tile state --- src/dispatch/bind_define.h | 8 +++++--- src/layout/arrange.h | 39 ++++++++++++++++++++++++++++++++++++++ src/mango.c | 6 +++--- 3 files changed, 47 insertions(+), 6 deletions(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index ea213e4..0bfab15 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1217,13 +1217,15 @@ int32_t togglefloating(const Arg *arg) { if (!sel) return 0; + bool isfloating = sel->isfloating; + if ((sel->isfullscreen || sel->ismaximizescreen)) { - sel->isfloating = 1; + isfloating = 1; } else { - sel->isfloating = !sel->isfloating; + isfloating = !sel->isfloating; } - setfloating(sel, sel->isfloating); + setfloating(sel, isfloating); return 0; } diff --git a/src/layout/arrange.h b/src/layout/arrange.h index 3721364..cc4bc07 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -1,3 +1,42 @@ +void restore_size_per(Monitor *m, Client *c) { + Client *fc = NULL; + double total_master_inner_per = 0; + double total_stack_inner_per = 0; + + if (!m || !c) + return; + + const Layout *current_layout = m->pertag->ltidxs[m->pertag->curtag]; + + if (current_layout->id == SCROLLER || + current_layout->id == VERTICAL_SCROLLER || current_layout->id == GRID || + current_layout->id == VERTICAL_GRID || current_layout->id == DECK || + current_layout->id == VERTICAL_DECK || + current_layout->id == CENTER_TILE || current_layout->id == MONOCLE) { + return; + } + + if (current_layout->id == CENTER_TILE || c->ismaster) { + set_size_per(m, c); + return; + } + + wl_list_for_each(fc, &clients, link) { + if (VISIBLEON(fc, m) && ISTILED(fc) && fc != c) { + if (fc->ismaster) { + total_master_inner_per += fc->master_inner_per; + } else { + total_stack_inner_per += fc->stack_inner_per; + } + } + } + + if (!c->ismaster && total_stack_inner_per) { + c->stack_inner_per = total_stack_inner_per * c->stack_inner_per / + (1 - c->stack_inner_per); + } +} + void set_size_per(Monitor *m, Client *c) { Client *fc = NULL; bool found = false; diff --git a/src/mango.c b/src/mango.c index b0fde12..45e0289 100644 --- a/src/mango.c +++ b/src/mango.c @@ -4810,7 +4810,7 @@ setfloating(Client *c, int32_t floating) { } if (!c->isfloating && old_floating_state) { - set_size_per(c->mon, c); + restore_size_per(c->mon, c); } if (!c->force_maximize) @@ -4896,7 +4896,7 @@ void setmaximizescreen(Client *c, int32_t maximizescreen) { wlr_scene_node_reparent(&c->scene->node, layers[c->isfloating ? LyrTop : LyrTile]); if (!c->ismaximizescreen && old_maximizescreen_state) { - set_size_per(c->mon, c); + restore_size_per(c->mon, c); } if (!c->force_maximize && !c->ismaximizescreen) { @@ -4967,7 +4967,7 @@ void setfullscreen(Client *c, int32_t fullscreen) // 用自定义全屏代理自 } if (!c->isfullscreen && old_fullscreen_state) { - set_size_per(c->mon, c); + restore_size_per(c->mon, c); } arrange(c->mon, false, false); From 0fe87e6286f2a6b899fa8d7683a982f831c983af Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 13 Feb 2026 20:23:03 +0800 Subject: [PATCH 58/78] fix: fix multi master focus record error --- src/fetch/client.h | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/fetch/client.h b/src/fetch/client.h index bf30e17..8fccb26 100644 --- a/src/fetch/client.h +++ b/src/fetch/client.h @@ -574,23 +574,29 @@ bool client_is_in_same_stack(Client *sc, Client *tc, Client *fc) { if (id == TILE || id == VERTICAL_TILE || id == DECK || id == VERTICAL_DECK || id == RIGHT_TILE) { - if (fc && !fc->ismaster) + if (tc->ismaster ^ sc->ismaster) return false; - else if (!sc->ismaster) + if (fc && !(fc->ismaster ^ sc->ismaster)) + return false; + else return true; } if (id == TGMIX) { - if (fc && !fc->ismaster) + if (tc->ismaster ^ sc->ismaster) + return false; + if (fc && !(fc->ismaster ^ sc->ismaster)) return false; if (!sc->ismaster && sc->mon->visible_tiling_clients <= 3) return true; } if (id == CENTER_TILE) { - if (fc && !fc->ismaster) + if (tc->ismaster ^ sc->ismaster) return false; - if (!sc->ismaster && sc->geom.x == tc->geom.x) + if (fc && !(fc->ismaster ^ sc->ismaster)) + return false; + if (sc->geom.x == tc->geom.x) return true; else return false; From 89413aacf53e11340d0558e7d18576720a067158 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 14 Feb 2026 08:35:30 +0800 Subject: [PATCH 59/78] fix: fix center tile size per reset --- src/layout/arrange.h | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/layout/arrange.h b/src/layout/arrange.h index cc4bc07..1ef89c3 100644 --- a/src/layout/arrange.h +++ b/src/layout/arrange.h @@ -11,13 +11,16 @@ void restore_size_per(Monitor *m, Client *c) { if (current_layout->id == SCROLLER || current_layout->id == VERTICAL_SCROLLER || current_layout->id == GRID || current_layout->id == VERTICAL_GRID || current_layout->id == DECK || - current_layout->id == VERTICAL_DECK || - current_layout->id == CENTER_TILE || current_layout->id == MONOCLE) { + current_layout->id == VERTICAL_DECK || current_layout->id == MONOCLE) { return; } if (current_layout->id == CENTER_TILE || c->ismaster) { - set_size_per(m, c); + wl_list_for_each(fc, &clients, link) { + if (VISIBLEON(fc, m) && ISTILED(fc) && !c->ismaster) { + set_size_per(m, fc); + } + } return; } @@ -44,8 +47,13 @@ void set_size_per(Monitor *m, Client *c) { if (!m || !c) return; + const Layout *current_layout = m->pertag->ltidxs[m->pertag->curtag]; + wl_list_for_each(fc, &clients, link) { if (VISIBLEON(fc, m) && ISTILED(fc) && fc != c) { + if (current_layout->id == CENTER_TILE && + !(fc->isleftstack ^ c->isleftstack)) + continue; c->master_mfact_per = fc->master_mfact_per; c->master_inner_per = fc->master_inner_per; c->stack_inner_per = fc->stack_inner_per; From fdd54afb7e49d0dfdf3976b1824a660a83e09713 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 15 Feb 2026 08:00:44 +0800 Subject: [PATCH 60/78] fix: some app frame skip fail when disable animaitons --- src/mango.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/mango.c b/src/mango.c index 2bc21c8..87d2f7d 100644 --- a/src/mango.c +++ b/src/mango.c @@ -3835,6 +3835,7 @@ static void iter_xdg_scene_buffers(struct wlr_scene_buffer *buffer, int32_t sx, void init_client_properties(Client *c) { c->isfocusing = false; + c->isfloating = 0; c->ismaximizescreen = 0; c->isfullscreen = 0; c->need_float_size_reduce = 0; @@ -4441,8 +4442,8 @@ void rendermon(struct wl_listener *listener, void *data) { wl_list_for_each(c, &clients, link) { need_more_frames = client_draw_frame(c) || need_more_frames; if (!animations && !(allow_tearing && frame_allow_tearing) && - c->configure_serial && !c->isfloating && - client_is_rendered_on_mon(c, m) && !client_is_stopped(c)) { + c->configure_serial && client_is_rendered_on_mon(c, m) && + !client_is_stopped(c)) { goto skip; } } From 02067e3b1e00b568b63dabb4c1c57b188911c349 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 15 Feb 2026 08:07:00 +0800 Subject: [PATCH 61/78] fix: some client property missing init --- src/mango.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/mango.c b/src/mango.c index 87d2f7d..6cdd554 100644 --- a/src/mango.c +++ b/src/mango.c @@ -3836,6 +3836,22 @@ static void iter_xdg_scene_buffers(struct wlr_scene_buffer *buffer, int32_t sx, void init_client_properties(Client *c) { c->isfocusing = false; c->isfloating = 0; + c->isfakefullscreen = 0; + c->isnoanimation = 0; + c->isopensilent = 0; + c->istagsilent = 0; + c->noswallow = 0; + c->isterm = 0; + c->noblur = 0; + c->tearing_hint = 0; + c->overview_isfullscreenbak = 0; + c->overview_ismaximizescreenbak = 0; + c->overview_isfloatingbak = 0; + c->pid = 0; + c->swallowing = NULL; + c->swallowedby = NULL; + c->ismaster = 0; + c->isleftstack = 0; c->ismaximizescreen = 0; c->isfullscreen = 0; c->need_float_size_reduce = 0; @@ -3889,6 +3905,7 @@ void init_client_properties(Client *c) { c->stack_proportion = 0.0f; c->next_in_stack = NULL; c->prev_in_stack = NULL; + memset(c->oldmonname, 0, sizeof(c->oldmonname)); memcpy(c->opacity_animation.initial_border_color, bordercolor, sizeof(c->opacity_animation.initial_border_color)); memcpy(c->opacity_animation.current_border_color, bordercolor, From 842b45b584bd4b9409e474757e79ab7f08e05b21 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 15 Feb 2026 08:32:49 +0800 Subject: [PATCH 62/78] feat: add skip timer to avoid rermanently block render --- src/mango.c | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/mango.c b/src/mango.c index 6cdd554..ded5958 100644 --- a/src/mango.c +++ b/src/mango.c @@ -502,6 +502,7 @@ struct Monitor { struct wl_listener request_state; struct wl_listener destroy_lock_surface; struct wlr_session_lock_surface_v1 *lock_surface; + struct wl_event_source *skip_timeout; struct wlr_box m; /* monitor area, layout-relative */ struct wlr_box w; /* window area, layout-relative */ struct wl_list layers[4]; /* LayerSurface::link */ @@ -783,6 +784,7 @@ static Client *get_scroll_stack_head(Client *c); static bool client_only_in_one_tag(Client *c); static Client *get_focused_stack_client(Client *sc); static bool client_is_in_same_stack(Client *sc, Client *tc, Client *fc); +static void destroy_monitor_skip_timer(Monitor *m); #include "data/static_keymap.h" #include "dispatch/bind_declare.h" @@ -2223,6 +2225,9 @@ void cleanupmon(struct wl_listener *listener, void *data) { wlr_scene_node_destroy(&m->blur->node); m->blur = NULL; } + if (m->skip_timeout) { + destroy_monitor_skip_timer(m); + } m->wlr_output->data = NULL; free(m->pertag); free(m); @@ -2774,6 +2779,7 @@ void createmon(struct wl_listener *listener, void *data) { m = wlr_output->data = ecalloc(1, sizeof(*m)); m->wlr_output = wlr_output; m->wlr_output->data = m; + m->skip_timeout = NULL; wl_list_init(&m->dwl_ipc_outputs); @@ -4419,6 +4425,37 @@ void client_set_opacity(Client *c, double opacity) { scene_buffer_apply_opacity, &opacity); } +void destroy_monitor_skip_timer(Monitor *m) { + if (m->skip_timeout) { + wl_event_source_timer_update(m->skip_timeout, 0); + wl_event_source_remove(m->skip_timeout); + m->skip_timeout = NULL; + } +} + +static int skip_timeout_callback(void *data) { + Monitor *m = data; + Client *c, *tmp; + + wl_list_for_each_safe(c, tmp, &clients, link) { c->configure_serial = 0; } + + if (m->skip_timeout) { + destroy_monitor_skip_timer(m); + } + return 0; +} + +void check_skip_timeout(Monitor *m) { + if (m->skip_timeout) { + return; + } + struct wl_event_loop *loop = wl_display_get_event_loop(dpy); + m->skip_timeout = wl_event_loop_add_timer(loop, skip_timeout_callback, m); + if (m->skip_timeout) { + wl_event_source_timer_update(m->skip_timeout, 100); // 100ms + } +} + void rendermon(struct wl_listener *listener, void *data) { Monitor *m = wl_container_of(listener, m, frame); Client *c = NULL, *tmp = NULL; @@ -4461,10 +4498,15 @@ void rendermon(struct wl_listener *listener, void *data) { if (!animations && !(allow_tearing && frame_allow_tearing) && c->configure_serial && client_is_rendered_on_mon(c, m) && !client_is_stopped(c)) { + check_skip_timeout(m); goto skip; } } + if (m->skip_timeout) { + destroy_monitor_skip_timer(m); + } + // 只有在需要帧时才构建和提交状态 if (allow_tearing && frame_allow_tearing) { apply_tear_state(m); From 0696fe964de6b820e172ecbed6d3af942996b6bd Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 15 Feb 2026 10:31:23 +0800 Subject: [PATCH 63/78] opt: optimize frame skip logic --- src/mango.c | 47 +++++++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/src/mango.c b/src/mango.c index ded5958..8ae2603 100644 --- a/src/mango.c +++ b/src/mango.c @@ -508,6 +508,7 @@ struct Monitor { struct wl_list layers[4]; /* LayerSurface::link */ uint32_t seltags; uint32_t tagset[2]; + bool skiping_frame; struct wl_list dwl_ipc_outputs; int32_t gappih; /* horizontal gap between windows */ @@ -784,7 +785,8 @@ static Client *get_scroll_stack_head(Client *c); static bool client_only_in_one_tag(Client *c); static Client *get_focused_stack_client(Client *sc); static bool client_is_in_same_stack(Client *sc, Client *tc, Client *fc); -static void destroy_monitor_skip_timer(Monitor *m); +static void monitor_stop_skip_timer(Monitor *m); +static int monitor_skip_frame_timeout_callback(void *data); #include "data/static_keymap.h" #include "dispatch/bind_declare.h" @@ -2226,7 +2228,9 @@ void cleanupmon(struct wl_listener *listener, void *data) { m->blur = NULL; } if (m->skip_timeout) { - destroy_monitor_skip_timer(m); + monitor_stop_skip_timer(m); + wl_event_source_remove(m->skip_timeout); + m->skip_timeout = NULL; } m->wlr_output->data = NULL; free(m->pertag); @@ -2776,10 +2780,15 @@ void createmon(struct wl_listener *listener, void *data) { return; } + struct wl_event_loop *loop = wl_display_get_event_loop(dpy); m = wlr_output->data = ecalloc(1, sizeof(*m)); + + m->skip_timeout = + wl_event_loop_add_timer(loop, monitor_skip_frame_timeout_callback, m); + m->skiping_frame = false; + m->wlr_output = wlr_output; m->wlr_output->data = m; - m->skip_timeout = NULL; wl_list_init(&m->dwl_ipc_outputs); @@ -4425,34 +4434,32 @@ void client_set_opacity(Client *c, double opacity) { scene_buffer_apply_opacity, &opacity); } -void destroy_monitor_skip_timer(Monitor *m) { - if (m->skip_timeout) { +void monitor_stop_skip_timer(Monitor *m) { + if (m->skip_timeout) wl_event_source_timer_update(m->skip_timeout, 0); - wl_event_source_remove(m->skip_timeout); - m->skip_timeout = NULL; - } + m->skiping_frame = false; } -static int skip_timeout_callback(void *data) { +static int monitor_skip_frame_timeout_callback(void *data) { Monitor *m = data; Client *c, *tmp; wl_list_for_each_safe(c, tmp, &clients, link) { c->configure_serial = 0; } - if (m->skip_timeout) { - destroy_monitor_skip_timer(m); - } - return 0; + monitor_stop_skip_timer(m); + wlr_output_schedule_frame(m->wlr_output); + + return 1; } -void check_skip_timeout(Monitor *m) { - if (m->skip_timeout) { +void monitor_check_skip_frame_timeout(Monitor *m) { + if (m->skiping_frame) { return; } - struct wl_event_loop *loop = wl_display_get_event_loop(dpy); - m->skip_timeout = wl_event_loop_add_timer(loop, skip_timeout_callback, m); + if (m->skip_timeout) { wl_event_source_timer_update(m->skip_timeout, 100); // 100ms + m->skiping_frame = true; } } @@ -4498,13 +4505,13 @@ void rendermon(struct wl_listener *listener, void *data) { if (!animations && !(allow_tearing && frame_allow_tearing) && c->configure_serial && client_is_rendered_on_mon(c, m) && !client_is_stopped(c)) { - check_skip_timeout(m); + monitor_check_skip_frame_timeout(m); goto skip; } } - if (m->skip_timeout) { - destroy_monitor_skip_timer(m); + if (m->skiping_frame) { + monitor_stop_skip_timer(m); } // 只有在需要帧时才构建和提交状态 From 49cb5a9d7efb03e726e4eea1579242e3549f6d8b Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 15 Feb 2026 12:29:12 +0800 Subject: [PATCH 64/78] feat: support frame skip for x11 app resize --- src/client/client.h | 16 +++++++++++++++- src/mango.c | 18 ++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/client/client.h b/src/client/client.h index 49ab398..fbb7a24 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -319,9 +319,23 @@ static inline uint32_t client_set_size(Client *c, uint32_t width, uint32_t height) { #ifdef XWAYLAND if (client_is_x11(c)) { + + struct wlr_surface_state *state = + &c->surface.xwayland->surface->current; + struct wlr_box new_geo = {0}; + new_geo.width = state->width; + new_geo.height = state->height; + if (c->geom.width - 2 * c->bw == new_geo.width && + c->geom.height - 2 * c->bw == new_geo.height && + c->surface.xwayland->x == c->geom.x + c->bw && + c->surface.xwayland->y == c->geom.y + c->bw) { + c->configure_serial = 0; + return 0; + } + wlr_xwayland_surface_configure(c->surface.xwayland, c->geom.x + c->bw, c->geom.y + c->bw, width, height); - return 0; + return 1; } #endif if ((int32_t)width == c->surface.xdg->toplevel->current.width && diff --git a/src/mango.c b/src/mango.c index 8ae2603..f0aefec 100644 --- a/src/mango.c +++ b/src/mango.c @@ -338,6 +338,7 @@ struct Client { struct wl_listener configure; struct wl_listener set_hints; struct wl_listener set_geometry; + struct wl_listener commmitx11; #endif uint32_t bw; uint32_t tags, oldtags, mini_restore_tag; @@ -956,6 +957,7 @@ static void activatex11(struct wl_listener *listener, void *data); static void configurex11(struct wl_listener *listener, void *data); static void createnotifyx11(struct wl_listener *listener, void *data); static void dissociatex11(struct wl_listener *listener, void *data); +static void commitx11(struct wl_listener *listener, void *data); static void associatex11(struct wl_listener *listener, void *data); static void sethints(struct wl_listener *listener, void *data); static void xwaylandready(struct wl_listener *listener, void *data); @@ -6280,17 +6282,33 @@ void createnotifyx11(struct wl_listener *listener, void *data) { LISTEN(&xsurface->events.request_minimize, &c->minimize, minimizenotify); } +void commitx11(struct wl_listener *listener, void *data) { + Client *c = wl_container_of(listener, c, commmitx11); + struct wlr_surface_state *state = &c->surface.xwayland->surface->current; + struct wlr_box new_geo = {0}; + new_geo.width = state->width; + new_geo.height = state->height; + if (c->geom.width - 2 * c->bw == new_geo.width && + c->geom.height - 2 * c->bw == new_geo.height && + c->surface.xwayland->x == c->geom.x + c->bw && + c->surface.xwayland->y == c->geom.y + c->bw) { + c->configure_serial = 0; + } +} + void associatex11(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, associate); LISTEN(&client_surface(c)->events.map, &c->map, mapnotify); LISTEN(&client_surface(c)->events.unmap, &c->unmap, unmapnotify); + LISTEN(&client_surface(c)->events.commit, &c->commmitx11, commitx11); } void dissociatex11(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, dissociate); wl_list_remove(&c->map.link); wl_list_remove(&c->unmap.link); + wl_list_remove(&c->commmitx11.link); } void sethints(struct wl_listener *listener, void *data) { From 7ccbeae8b8bcf0c9b506579758c6ac499f3d5dab Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 15 Feb 2026 14:08:12 +0800 Subject: [PATCH 65/78] fix: if the progress not the child of main, not assume it is stop --- src/client/client.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/client/client.h b/src/client/client.h index fbb7a24..e951910 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -254,10 +254,13 @@ static inline int32_t client_is_stopped(Client *c) { wl_client_get_credentials(c->surface.xdg->client->client, &pid, NULL, NULL); if (waitid(P_PID, pid, &in, WNOHANG | WCONTINUED | WSTOPPED | WNOWAIT) < 0) { - /* This process is not our child process, while is very unlikely that - * it is stopped, in order to do not skip frames assume that it is. */ + /* This process is not our child process. We cannot determine its + * stopped state; assume it is not stopped to avoid blocking frame skip. + */ if (errno == ECHILD) - return 1; + return 0; // if not our child, assume not stopped + /* Other errors, also assume not stopped. */ + return 0; } else if (in.si_pid) { if (in.si_code == CLD_STOPPED || in.si_code == CLD_TRAPPED) return 1; From 62ab00a7a3656fcdc39008d048b13369ffe1ad11 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sun, 15 Feb 2026 16:43:02 +0800 Subject: [PATCH 66/78] update readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b78ccfa..9d14d06 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ This project's development is based on [dwl](https://codeberg.org/dwl/dwl/). - Ipc support(get/send message from/to compositor by external program) - Hycov-like overview - Window effects from scenefx (blur, shadow, corner radius, opacity) + - Zero flickering - every frame is perfect. https://github.com/user-attachments/assets/bb83004a-0563-4b48-ad89-6461a9b78b1f From f75efa13124e6017fbb935d7e3b8d86077660669 Mon Sep 17 00:00:00 2001 From: Ricardo Squassina Lee <8495707+squassina@users.noreply.github.com> Date: Sun, 15 Feb 2026 07:08:35 -0300 Subject: [PATCH 67/78] Fix wayland protocol directory variable retrieval --- protocols/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocols/meson.build b/protocols/meson.build index cafab64..922a76e 100644 --- a/protocols/meson.build +++ b/protocols/meson.build @@ -1,6 +1,6 @@ wayland_scanner = find_program('wayland-scanner') wayland_protos_dep = dependency('wayland-protocols') -wl_protocol_dir = wayland_protos_dep.get_pkgconfig_variable('pkgdatadir') +wl_protocol_dir = wayland_protos_dep.get_variable(pkgconfig:'pkgdatadir') wayland_scanner_code = generator( wayland_scanner, output: '@BASENAME@-protocol.c', From 26a616e3d5a99811708b8b0afa62eafd0ca490be Mon Sep 17 00:00:00 2001 From: Yappaholic Date: Sun, 15 Feb 2026 16:50:07 +0300 Subject: [PATCH 68/78] docs: add guix installation instructions --- README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/README.md b/README.md index 9d14d06..9b558fd 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,34 @@ Then, install the package: dnf install mangowc ``` +## GuixSD +The package definition is described in the source repository. +First, add `mangowc` channel to `channels.scm` file: + +```scheme +;; In $HOME/.config/guix/channels.scm +(cons (channel + (name 'mangowc) + (url "https://github.com/DreamMaoMao/mangowc.git")) + ... ;; Your other channels + %default-channels) +``` + +Then, run `guix pull` and after update you can either run +`guix install mangowc` or add it to your configuration via: + +```scheme +(use-modules (mangowc)) ;; Add mangowc module + +;; Add mangowc to packages list +(packages (cons + mangowc + ... ;; Other packages you specified + %base-packages)) +``` + +And then rebuild your system. + ## Other ```bash From 1158fb2e3c09424e1d0f3aaf9b77968743cc235e Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 16 Feb 2026 07:46:42 +0800 Subject: [PATCH 69/78] opt: don't skip frame when grab client --- src/mango.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mango.c b/src/mango.c index f0aefec..cbdc53a 100644 --- a/src/mango.c +++ b/src/mango.c @@ -4506,7 +4506,7 @@ void rendermon(struct wl_listener *listener, void *data) { need_more_frames = client_draw_frame(c) || need_more_frames; if (!animations && !(allow_tearing && frame_allow_tearing) && c->configure_serial && client_is_rendered_on_mon(c, m) && - !client_is_stopped(c)) { + !client_is_stopped(c) && !grabc) { monitor_check_skip_frame_timeout(m); goto skip; } From 112fb5c0074f6f2110e70ae4765fe0a46ff04adb Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 16 Feb 2026 10:06:16 +0800 Subject: [PATCH 70/78] opt: optimize code struct --- src/client/client.h | 16 +++++++++------- src/mango.c | 15 ++++++++------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/client/client.h b/src/client/client.h index e951910..2174c6f 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -325,13 +325,15 @@ static inline uint32_t client_set_size(Client *c, uint32_t width, struct wlr_surface_state *state = &c->surface.xwayland->surface->current; - struct wlr_box new_geo = {0}; - new_geo.width = state->width; - new_geo.height = state->height; - if (c->geom.width - 2 * c->bw == new_geo.width && - c->geom.height - 2 * c->bw == new_geo.height && - c->surface.xwayland->x == c->geom.x + c->bw && - c->surface.xwayland->y == c->geom.y + c->bw) { + + if ((int32_t)c->geom.width - 2 * (int32_t)c->bw == + (int32_t)state->width && + (int32_t)c->geom.height - 2 * (int32_t)c->bw == + (int32_t)state->height && + (int32_t)c->surface.xwayland->x == + (int32_t)c->geom.x + (int32_t)c->bw && + (int32_t)c->surface.xwayland->y == + (int32_t)c->geom.y + (int32_t)c->bw) { c->configure_serial = 0; return 0; } diff --git a/src/mango.c b/src/mango.c index cbdc53a..540395a 100644 --- a/src/mango.c +++ b/src/mango.c @@ -6285,13 +6285,14 @@ void createnotifyx11(struct wl_listener *listener, void *data) { void commitx11(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, commmitx11); struct wlr_surface_state *state = &c->surface.xwayland->surface->current; - struct wlr_box new_geo = {0}; - new_geo.width = state->width; - new_geo.height = state->height; - if (c->geom.width - 2 * c->bw == new_geo.width && - c->geom.height - 2 * c->bw == new_geo.height && - c->surface.xwayland->x == c->geom.x + c->bw && - c->surface.xwayland->y == c->geom.y + c->bw) { + + if ((int32_t)c->geom.width - 2 * (int32_t)c->bw == (int32_t)state->width && + (int32_t)c->geom.height - 2 * (int32_t)c->bw == + (int32_t)state->height && + (int32_t)c->surface.xwayland->x == + (int32_t)c->geom.x + (int32_t)c->bw && + (int32_t)c->surface.xwayland->y == + (int32_t)c->geom.y + (int32_t)c->bw) { c->configure_serial = 0; } } From fa88ebace0cb352cb25fe5f989388df06d709e9c Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Mon, 16 Feb 2026 11:56:42 +0800 Subject: [PATCH 71/78] project: version not use latest tag --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index e7979bd..5b19f9f 100644 --- a/meson.build +++ b/meson.build @@ -56,7 +56,7 @@ endif if is_git_repo # 如果是 Git 目录,获取 Commit Hash 和最新的 tag commit_hash = run_command(git, 'rev-parse', '--short', 'HEAD', check : false).stdout().strip() - latest_tag = run_command(git, 'describe', '--tags', '--abbrev=0', check : false).stdout().strip() + latest_tag = meson.project_version() version_with_hash = '@0@(@1@)'.format(latest_tag, commit_hash) else # 如果不是 Git 目录,使用项目版本号和 "release" 字符串 From c3dcee2c8e3ae4e2839fda699a25e2d0a836b341 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Tue, 17 Feb 2026 08:33:45 +0800 Subject: [PATCH 72/78] opt: remove useless code --- src/client/client.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/client/client.h b/src/client/client.h index 2174c6f..fd81a80 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -334,7 +334,6 @@ static inline uint32_t client_set_size(Client *c, uint32_t width, (int32_t)c->geom.x + (int32_t)c->bw && (int32_t)c->surface.xwayland->y == (int32_t)c->geom.y + (int32_t)c->bw) { - c->configure_serial = 0; return 0; } From 259fdb3a875e4c5842f75579c3e0dc85d897a4f9 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 18 Feb 2026 13:26:24 +0800 Subject: [PATCH 73/78] bump versiont to 0.12.3 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 5b19f9f..06ccfd7 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('mango', ['c', 'cpp'], - version : '0.12.2', + version : '0.12.3', ) subdir('protocols') From 6924ca8512090c37d64386bb5e6fabb4a6410ecb Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 18 Feb 2026 23:07:23 +0800 Subject: [PATCH 74/78] feat: add global option prefer_scroller_overspread --- src/config/parse_config.h | 6 ++++ src/config/preset.h | 1 + src/layout/horizontal.h | 66 +++++++++++++++++++++++++++++++++------ src/layout/vertical.h | 62 +++++++++++++++++++++++++++++++----- 4 files changed, 118 insertions(+), 17 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index b7f89d5..64afd88 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -211,6 +211,7 @@ typedef struct { int32_t scroller_ignore_proportion_single; int32_t scroller_focus_center; int32_t scroller_prefer_center; + int32_t scroller_prefer_overspread; int32_t edge_scroller_pointer_focus; int32_t focus_cross_monitor; int32_t exchange_cross_monitor; @@ -1337,6 +1338,8 @@ bool parse_option(Config *config, char *key, char *value) { config->scroller_focus_center = atoi(value); } else if (strcmp(key, "scroller_prefer_center") == 0) { config->scroller_prefer_center = atoi(value); + } else if (strcmp(key, "scroller_prefer_overspread") == 0) { + config->scroller_prefer_overspread = atoi(value); } else if (strcmp(key, "edge_scroller_pointer_focus") == 0) { config->edge_scroller_pointer_focus = atoi(value); } else if (strcmp(key, "focus_cross_monitor") == 0) { @@ -3102,6 +3105,8 @@ void override_config(void) { CLAMP_INT(config.scroller_ignore_proportion_single, 0, 1); scroller_focus_center = CLAMP_INT(config.scroller_focus_center, 0, 1); scroller_prefer_center = CLAMP_INT(config.scroller_prefer_center, 0, 1); + scroller_prefer_overspread = + CLAMP_INT(config.scroller_prefer_overspread, 0, 1); edge_scroller_pointer_focus = CLAMP_INT(config.edge_scroller_pointer_focus, 0, 1); scroller_structs = CLAMP_INT(config.scroller_structs, 0, 1000); @@ -3301,6 +3306,7 @@ void set_value_default() { scroller_ignore_proportion_single; config.scroller_focus_center = scroller_focus_center; config.scroller_prefer_center = scroller_prefer_center; + config.scroller_prefer_overspread = scroller_prefer_overspread; config.edge_scroller_pointer_focus = edge_scroller_pointer_focus; config.focus_cross_monitor = focus_cross_monitor; config.exchange_cross_monitor = exchange_cross_monitor; diff --git a/src/config/preset.h b/src/config/preset.h index ae4424f..d982458 100644 --- a/src/config/preset.h +++ b/src/config/preset.h @@ -66,6 +66,7 @@ float scroller_default_proportion_single = 1.0; int32_t scroller_ignore_proportion_single = 1; int32_t scroller_focus_center = 0; int32_t scroller_prefer_center = 0; +int32_t scroller_prefer_overspread = 1; int32_t focus_cross_monitor = 0; int32_t focus_cross_tag = 0; int32_t exchange_cross_monitor = 0; diff --git a/src/layout/horizontal.h b/src/layout/horizontal.h index e1a335d..b8016d5 100644 --- a/src/layout/horizontal.h +++ b/src/layout/horizontal.h @@ -283,6 +283,7 @@ void scroller(Monitor *m) { struct wlr_box target_geom; int32_t focus_client_index = 0; bool need_scroller = false; + bool over_overspread_to_left = false; int32_t cur_gappih = enablegaps ? m->gappih : 0; int32_t cur_gappoh = enablegaps ? m->gappoh : 0; int32_t cur_gappov = enablegaps ? m->gappov : 0; @@ -371,6 +372,45 @@ void scroller(Monitor *m) { } } + bool need_apply_overspread = + scroller_prefer_overspread && m->visible_scroll_tiling_clients > 1 && + tempClients[focus_client_index]->scroller_proportion < 1.0f; + + if (need_apply_overspread) { + + if (focus_client_index == 0) { + over_overspread_to_left = true; + } else { + over_overspread_to_left = false; + } + + if (over_overspread_to_left && + (!INSIDEMON(tempClients[1]) || + (tempClients[1]->scroller_proportion + + tempClients[focus_client_index]->scroller_proportion >= + 1.0f))) { + need_scroller = true; + } else if (!over_overspread_to_left && + (!INSIDEMON(tempClients[focus_client_index - 1]) || + (tempClients[focus_client_index - 1]->scroller_proportion + + tempClients[focus_client_index]->scroller_proportion >= + 1.0f))) { + need_scroller = true; + } else { + need_apply_overspread = false; + } + } + + bool need_apply_center = + scroller_focus_center || m->visible_scroll_tiling_clients == 1 || + (scroller_prefer_center && !need_apply_overspread && + (!m->prevsel || + (ISSCROLLTILED(m->prevsel) && + (m->prevsel->scroller_proportion * max_client_width) + + (tempClients[focus_client_index]->scroller_proportion * + max_client_width) > + m->w.width - 2 * scroller_structs - cur_gappih))); + if (n == 1 && scroller_ignore_proportion_single) { need_scroller = true; } @@ -394,18 +434,26 @@ void scroller(Monitor *m) { tempClients[focus_client_index], &target_geom); arrange_stack(tempClients[focus_client_index], target_geom, cur_gappiv); } else if (need_scroller) { - if (scroller_focus_center || - ((!m->prevsel || - (ISSCROLLTILED(m->prevsel) && - (m->prevsel->scroller_proportion * max_client_width) + - (root_client->scroller_proportion * max_client_width) > - m->w.width - 2 * scroller_structs - cur_gappih)) && - scroller_prefer_center)) { + if (need_apply_center) { target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2; + } else if (need_apply_overspread) { + if (over_overspread_to_left) { + target_geom.x = m->w.x + scroller_structs; + } else { + target_geom.x = + m->w.x + + (m->w.width - + tempClients[focus_client_index]->scroller_proportion * + max_client_width - + scroller_structs); + } + } else { - target_geom.x = root_client->geom.x > m->w.x + (m->w.width) / 2 + target_geom.x = tempClients[focus_client_index]->geom.x > + m->w.x + (m->w.width) / 2 ? m->w.x + (m->w.width - - root_client->scroller_proportion * + tempClients[focus_client_index] + ->scroller_proportion * max_client_width - scroller_structs) : m->w.x + scroller_structs; diff --git a/src/layout/vertical.h b/src/layout/vertical.h index f7bd442..f036ca4 100644 --- a/src/layout/vertical.h +++ b/src/layout/vertical.h @@ -270,6 +270,7 @@ void vertical_scroller(Monitor *m) { struct wlr_box target_geom; int32_t focus_client_index = 0; bool need_scroller = false; + bool over_overspread_to_up = false; int32_t cur_gappiv = enablegaps ? m->gappiv : 0; int32_t cur_gappov = enablegaps ? m->gappov : 0; int32_t cur_gappoh = enablegaps ? m->gappoh : 0; @@ -355,6 +356,45 @@ void vertical_scroller(Monitor *m) { } } + bool need_apply_overspread = + scroller_prefer_overspread && m->visible_scroll_tiling_clients > 1 && + tempClients[focus_client_index]->scroller_proportion < 1.0f; + + if (need_apply_overspread) { + + if (focus_client_index == 0) { + over_overspread_to_up = true; + } else { + over_overspread_to_up = false; + } + + if (over_overspread_to_up && + (!INSIDEMON(tempClients[1]) || + (tempClients[1]->scroller_proportion + + tempClients[focus_client_index]->scroller_proportion >= + 1.0f))) { + need_scroller = true; + } else if (!over_overspread_to_up && + (!INSIDEMON(tempClients[focus_client_index - 1]) || + (tempClients[focus_client_index - 1]->scroller_proportion + + tempClients[focus_client_index]->scroller_proportion >= + 1.0f))) { + need_scroller = true; + } else { + need_apply_overspread = false; + } + } + + bool need_apply_center = + scroller_focus_center || m->visible_scroll_tiling_clients == 1 || + (scroller_prefer_center && !need_apply_overspread && + (!m->prevsel || + (ISSCROLLTILED(m->prevsel) && + (m->prevsel->scroller_proportion * max_client_height) + + (tempClients[focus_client_index]->scroller_proportion * + max_client_height) > + m->w.height - 2 * scroller_structs - cur_gappiv))); + if (n == 1 && scroller_ignore_proportion_single) { need_scroller = true; } @@ -381,18 +421,24 @@ void vertical_scroller(Monitor *m) { arrange_stack_vertical(tempClients[focus_client_index], target_geom, cur_gappih); } else if (need_scroller) { - if (scroller_focus_center || - ((!m->prevsel || - (ISSCROLLTILED(m->prevsel) && - (m->prevsel->scroller_proportion * max_client_height) + - (root_client->scroller_proportion * max_client_height) > - m->w.height - 2 * scroller_structs - cur_gappiv)) && - scroller_prefer_center)) { + if (need_apply_center) { target_geom.y = m->w.y + (m->w.height - target_geom.height) / 2; + } else if (need_apply_overspread) { + if (over_overspread_to_up) { + target_geom.y = m->w.y + scroller_structs; + } else { + target_geom.y = + m->w.y + + (m->w.height - + tempClients[focus_client_index]->scroller_proportion * + max_client_height - + scroller_structs); + } } else { target_geom.y = root_client->geom.y > m->w.y + (m->w.height) / 2 ? m->w.y + (m->w.height - - root_client->scroller_proportion * + tempClients[focus_client_index] + ->scroller_proportion * max_client_height - scroller_structs) : m->w.y + scroller_structs; From 23d7b11e27a1078dba9492943662a1fa444a82d7 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 19 Feb 2026 08:41:42 +0800 Subject: [PATCH 75/78] fix: only apply scroller overspread to head and tail client --- src/layout/horizontal.h | 1 + src/layout/vertical.h | 1 + 2 files changed, 2 insertions(+) diff --git a/src/layout/horizontal.h b/src/layout/horizontal.h index b8016d5..8140934 100644 --- a/src/layout/horizontal.h +++ b/src/layout/horizontal.h @@ -374,6 +374,7 @@ void scroller(Monitor *m) { bool need_apply_overspread = scroller_prefer_overspread && m->visible_scroll_tiling_clients > 1 && + (focus_client_index == 0 || focus_client_index == n - 1) && tempClients[focus_client_index]->scroller_proportion < 1.0f; if (need_apply_overspread) { diff --git a/src/layout/vertical.h b/src/layout/vertical.h index f036ca4..4759e7a 100644 --- a/src/layout/vertical.h +++ b/src/layout/vertical.h @@ -358,6 +358,7 @@ void vertical_scroller(Monitor *m) { bool need_apply_overspread = scroller_prefer_overspread && m->visible_scroll_tiling_clients > 1 && + (focus_client_index == 0 || focus_client_index == n - 1) && tempClients[focus_client_index]->scroller_proportion < 1.0f; if (need_apply_overspread) { From 68075c00445e23028ec49fa1008bec8c9bb1cdb7 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 19 Feb 2026 09:59:58 +0800 Subject: [PATCH 76/78] feat: support index arg in switch_keyboard_layout --- src/config/parse_config.h | 1 + src/dispatch/bind_define.h | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 64afd88..830d22b 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -1017,6 +1017,7 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value, (*arg).v = strdup(arg_value); } else if (strcmp(func_name, "switch_keyboard_layout") == 0) { func = switch_keyboard_layout; + (*arg).i = CLAMP_INT(atoi(arg_value), 0, 100); } else if (strcmp(func_name, "setlayout") == 0) { func = setlayout; (*arg).v = strdup(arg_value); diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 0bfab15..bd06514 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -900,7 +900,13 @@ int32_t switch_keyboard_layout(const Arg *arg) { wlr_log(WLR_INFO, "Only one layout available"); return 0; } - xkb_layout_index_t next = (current + 1) % num_layouts; + + xkb_layout_index_t next = 0; + if (arg->i > 0 && arg->i <= num_layouts) { + next = arg->i - 1; + } else { + next = (current + 1) % num_layouts; + } // 6. 应用新 keymap uint32_t depressed = keyboard->modifiers.depressed; From 2f12f46919a3e224156f6c83e07aeff1f48b72e2 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 19 Feb 2026 19:07:36 +0800 Subject: [PATCH 77/78] opt: use base surface of client when xytonode in rect node --- src/fetch/common.h | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/fetch/common.h b/src/fetch/common.h index 58e69dc..57a1a8e 100644 --- a/src/fetch/common.h +++ b/src/fetch/common.h @@ -100,10 +100,6 @@ void xytonode(double x, double y, struct wlr_surface **psurface, Client **pc, surface = wlr_scene_surface_try_from_buffer( wlr_scene_buffer_from_node(node)) ->surface; - else if (node->type == WLR_SCENE_NODE_RECT) { - surface = NULL; - break; - } /* start from the topmost layer, find a sureface that can be focused by pointer, @@ -119,6 +115,13 @@ void xytonode(double x, double y, struct wlr_surface **psurface, Client **pc, l = pnode->data; } } + + if (node->type == WLR_SCENE_NODE_RECT) { + if (c) { + surface = client_surface(c); + } + break; + } } if (psurface) From f0259c6285a8a1f1ca5759414fb1bcbe54799956 Mon Sep 17 00:00:00 2001 From: Emil Miler Date: Thu, 19 Feb 2026 15:02:03 +0100 Subject: [PATCH 78/78] Include packaging status in README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 9b558fd..05eaaae 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,8 @@ https://github.com/user-attachments/assets/bb83004a-0563-4b48-ad89-6461a9b78b1f # Installation +[![Packaging status](https://repology.org/badge/vertical-allrepos/mangowc.svg)](https://repology.org/project/mangowc/versions) + ## Dependencies - glibc