From 8cc5579e16e8222cd87ce203c64afd3cb204938d Mon Sep 17 00:00:00 2001 From: Gerry Hernandez Date: Thu, 5 Mar 2026 23:59:06 -0800 Subject: [PATCH 1/8] Make number of tags configurable (tag_count) --- mmsg/mmsg.c | 14 ++++++------ src/config/parse_config.h | 15 ++++++++----- src/config/preset.h | 4 +--- src/dispatch/bind_define.h | 4 ++-- src/ext-protocol/dwl-ipc.h | 4 ++-- src/ext-protocol/ext-workspace.h | 18 ++++++++++------ src/fetch/monitor.h | 12 +++++------ src/mango.c | 37 +++++++++++++++++++++----------- 8 files changed, 64 insertions(+), 44 deletions(-) diff --git a/mmsg/mmsg.c b/mmsg/mmsg.c index 4e0e1d8c..7a361a13 100644 --- a/mmsg/mmsg.c +++ b/mmsg/mmsg.c @@ -85,9 +85,9 @@ static void noop_scale(void *data, struct wl_output *wl_output, static void noop_description(void *data, struct wl_output *wl_output, const char *description) {} -// 将 n 转换为 9 位二进制字符串,结果存入 buf(至少长度 10) -void bin_str_9bits(char *buf, uint32_t n) { - for (int32_t i = 8; i >= 0; i--) { +// Convert n to an N-bit binary string, store result in buf (minimum length bits+1) +void bin_str_nbits(char *buf, uint32_t n, int32_t bits) { + for (int32_t i = bits - 1; i >= 0; i--) { *buf++ = ((n >> i) & 1) ? '1' : '0'; } *buf = '\0'; // 字符串结尾 @@ -395,11 +395,11 @@ static void dwl_ipc_output_frame(void *data, printf("%s clients %u\n", output_name, total_clients); - char occ_str[10], seltags_str[10], urg_str[10]; + char occ_str[tagcount + 1], seltags_str[tagcount + 1], urg_str[tagcount + 1]; - bin_str_9bits(occ_str, occ); - bin_str_9bits(seltags_str, seltags); - bin_str_9bits(urg_str, urg); + bin_str_nbits(occ_str, occ, tagcount); + bin_str_nbits(seltags_str, seltags, tagcount); + bin_str_nbits(urg_str, urg, tagcount); printf("%s tags %u %u %u\n", output_name, occ, seltags, urg); printf("%s tags %s %s %s\n", output_name, occ_str, seltags_str, urg_str); diff --git a/src/config/parse_config.h b/src/config/parse_config.h index ce5db532..2a3330b7 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -359,6 +359,8 @@ typedef struct { int32_t allow_shortcuts_inhibit; int32_t allow_lock_transparent; + int32_t tag_count; + struct xkb_rule_names xkb_rules; char keymode[28]; @@ -1111,7 +1113,7 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value, while (token != NULL) { int32_t num = atoi(token); - if (num > 0 && num <= LENGTH(tags)) { + if (num > 0 && num <= tag_count) { mask |= (1 << (num - 1)); } token = strtok_r(NULL, "|", &saveptr); @@ -1573,6 +1575,8 @@ bool parse_option(Config *config, char *key, char *value) { config->default_mfact = atof(value); } else if (strcmp(key, "default_nmaster") == 0) { config->default_nmaster = atoi(value); + } else if (strcmp(key, "tag_count") == 0) { + config->tag_count = CLAMP_INT(atoi(value), 1, 32); } else if (strcmp(key, "center_master_overspread") == 0) { config->center_master_overspread = atoi(value); } else if (strcmp(key, "center_when_single_stack") == 0) { @@ -1893,7 +1897,7 @@ bool parse_option(Config *config, char *key, char *value) { trim_whitespace(val); if (strcmp(key, "id") == 0) { - rule->id = CLAMP_INT(atoi(val), 0, LENGTH(tags)); + rule->id = CLAMP_INT(atoi(val), 0, tag_count); } else if (strcmp(key, "layout_name") == 0) { rule->layout_name = strdup(val); } else if (strcmp(key, "monitor_name") == 0) { @@ -3288,6 +3292,7 @@ void set_value_default() { config.new_is_master = new_is_master; // 新窗口是否插在头部 config.default_mfact = default_mfact; // master 窗口比例 config.default_nmaster = default_nmaster; // 默认master数量 + config.tag_count = tag_count; config.center_master_overspread = center_master_overspread; // 中心master时是否铺满 config.center_when_single_stack = @@ -3731,7 +3736,7 @@ void reapply_master(void) { int32_t i; Monitor *m = NULL; - for (i = 0; i <= LENGTH(tags); i++) { + for (i = 0; i <= tag_count; i++) { wl_list_for_each(m, &mons, link) { if (!m->wlr_output->enabled) { continue; @@ -3752,7 +3757,7 @@ void parse_tagrule(Monitor *m) { Client *c = NULL; bool match_rule = false; - for (i = 0; i <= LENGTH(tags); i++) { + for (i = 0; i <= tag_count; i++) { m->pertag->nmasters[i] = default_nmaster; m->pertag->mfacts[i] = default_mfact; } @@ -3810,7 +3815,7 @@ void parse_tagrule(Monitor *m) { } } - for (i = 1; i <= LENGTH(tags); i++) { + for (i = 1; i <= tag_count; i++) { wl_list_for_each(c, &clients, link) { if ((c->tags & (1 << (i - 1)) & TAGMASK) && ISTILED(c)) { if (m->pertag->mfacts[i] > 0.0f) diff --git a/src/config/preset.h b/src/config/preset.h index d9824588..e8b244bb 100644 --- a/src/config/preset.h +++ b/src/config/preset.h @@ -207,9 +207,7 @@ enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TAP_MAP_LRM; /* If you want to use the windows key for MODKEY, use WLR_MODIFIER_LOGO */ #define MODKEY WLR_MODIFIER_ALT -static const char *tags[] = { - "1", "2", "3", "4", "5", "6", "7", "8", "9", -}; +int32_t tag_count = 9; float focused_opacity = 1.0; float unfocused_opacity = 1.0; diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 5cf41d6c..d6678708 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1515,10 +1515,10 @@ int32_t viewtoright_have_client(const Arg *arg) { return 0; } - if (current >= LENGTH(tags)) + if (current >= tag_count) return 0; - for (n = current + 1; n <= LENGTH(tags); n++) { + for (n = current + 1; n <= tag_count; n++) { if (get_tag_status(n, selmon)) { found = true; break; diff --git a/src/ext-protocol/dwl-ipc.h b/src/ext-protocol/dwl-ipc.h index ab0bdb8d..23934807 100644 --- a/src/ext-protocol/dwl-ipc.h +++ b/src/ext-protocol/dwl-ipc.h @@ -55,7 +55,7 @@ void dwl_ipc_manager_bind(struct wl_client *client, void *data, &dwl_manager_implementation, NULL, dwl_ipc_manager_destroy); - zdwl_ipc_manager_v2_send_tags(manager_resource, LENGTH(tags)); + zdwl_ipc_manager_v2_send_tags(manager_resource, tag_count); for (uint32_t i = 0; i < LENGTH(layouts); i++) zdwl_ipc_manager_v2_send_layout(manager_resource, layouts[i].symbol); @@ -118,7 +118,7 @@ void dwl_ipc_output_printstatus_to(DwlIpcOutput *ipc_output) { focused = focustop(monitor); zdwl_ipc_output_v2_send_active(ipc_output->resource, monitor == selmon); - for (tag = 0; tag < LENGTH(tags); tag++) { + for (tag = 0; tag < tag_count; tag++) { numclients = state = focused_client = 0; tagmask = 1 << tag; if ((tagmask & monitor->tagset[monitor->seltags]) != 0) diff --git a/src/ext-protocol/ext-workspace.h b/src/ext-protocol/ext-workspace.h index 28aaeeeb..fa07b1ca 100644 --- a/src/ext-protocol/ext-workspace.h +++ b/src/ext-protocol/ext-workspace.h @@ -69,10 +69,13 @@ static void handle_ext_workspace_deactivate(struct wl_listener *listener, wlr_log(WLR_INFO, "ext deactivating workspace %d", workspace->tag); } -static const char *get_name_from_tag(uint32_t tag) { - static const char *names[] = {"overview", "1", "2", "3", "4", - "5", "6", "7", "8", "9"}; - return (tag < sizeof(names) / sizeof(names[0])) ? names[tag] : NULL; +static const char *get_name_from_tag(uint32_t tag, char *buf, size_t len) { + if (tag == 0) + return "overview"; + if (tag > (uint32_t)tag_count) + return NULL; + snprintf(buf, len, "%u", tag); + return buf; } void destroy_workspace(struct workspace *workspace) { @@ -103,7 +106,8 @@ static void remove_workspace_by_tag(uint32_t tag, Monitor *m) { } static void add_workspace_by_tag(int32_t tag, Monitor *m) { - const char *name = get_name_from_tag(tag); + char name_buf[4]; + const char *name = get_name_from_tag(tag, name_buf, sizeof(name_buf)); struct workspace *workspace = ecalloc(1, sizeof(*workspace)); wl_list_append(&workspaces, &workspace->link); @@ -165,13 +169,13 @@ void refresh_monitors_workspaces_status(Monitor *m) { int32_t i; if (m->isoverview) { - for (i = 1; i <= LENGTH(tags); i++) { + for (i = 1; i <= tag_count; i++) { remove_workspace_by_tag(i, m); } add_workspace_by_tag(0, m); } else { remove_workspace_by_tag(0, m); - for (i = 1; i <= LENGTH(tags); i++) { + for (i = 1; i <= tag_count; i++) { add_workspace_by_tag(i, m); } } diff --git a/src/fetch/monitor.h b/src/fetch/monitor.h index d2b4fe62..6c1e8a24 100644 --- a/src/fetch/monitor.h +++ b/src/fetch/monitor.h @@ -57,14 +57,14 @@ uint32_t get_tags_first_tag_num(uint32_t source_tags) { return selmon->pertag->curtag; } - for (i = 0; !(tag & 1) && source_tags != 0 && i < LENGTH(tags); i++) { + for (i = 0; !(tag & 1) && source_tags != 0 && i < tag_count; i++) { tag = source_tags >> i; } if (i == 1) { return 1; - } else if (i > 9) { - return 9; + } else if (i > tag_count) { + return tag_count; } else { return i; } @@ -79,14 +79,14 @@ uint32_t get_tags_first_tag(uint32_t source_tags) { return selmon->pertag->curtag; } - for (i = 0; !(tag & 1) && source_tags != 0 && i < LENGTH(tags); i++) { + for (i = 0; !(tag & 1) && source_tags != 0 && i < tag_count; i++) { tag = source_tags >> i; } if (i == 1) { return 1; - } else if (i > 9) { - return 1 << 8; + } else if (i > tag_count) { + return 1 << (tag_count - 1); } else { return 1 << (i - 1); } diff --git a/src/mango.c b/src/mango.c index 3ae5ccc9..3e0ea8f6 100644 --- a/src/mango.c +++ b/src/mango.c @@ -117,7 +117,7 @@ ((C) && (M) && (C)->mon == (M) && ((C)->tags & (M)->tagset[(M)->seltags])) #define LENGTH(X) (sizeof X / sizeof X[0]) #define END(A) ((A) + LENGTH(A)) -#define TAGMASK ((1 << LENGTH(tags)) - 1) +#define TAGMASK (tag_count == 32 ? ~0u : ((1u << tag_count) - 1)) #define LISTEN(E, L, H) wl_signal_add((E), ((L)->notify = (H), (L))) #define ISFULLSCREEN(A) \ ((A)->isfullscreen || (A)->ismaximizescreen || \ @@ -925,13 +925,12 @@ static struct { #include "config/preset.h" struct Pertag { - uint32_t curtag, prevtag; /* current and previous tag */ - int32_t nmasters[LENGTH(tags) + 1]; /* number of windows in master area */ - float mfacts[LENGTH(tags) + 1]; /* mfacts per tag */ - 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 */ + uint32_t curtag, prevtag; /* current and previous tag */ + int32_t *nmasters; /* number of windows in master area */ + float *mfacts; /* mfacts per tag */ + bool *no_hide; /* no_hide per tag */ + bool *no_render_border; /* no_render_border per tag */ + const Layout **ltidxs; /* matrix of tags and layouts indexes */ }; static struct wl_signal mango_print_status; @@ -2292,6 +2291,11 @@ void cleanupmon(struct wl_listener *listener, void *data) { m->skip_frame_timeout = NULL; } m->wlr_output->data = NULL; + free(m->pertag->nmasters); + free(m->pertag->mfacts); + free(m->pertag->no_hide); + free(m->pertag->no_render_border); + free(m->pertag->ltidxs); free(m->pertag); free(m); } @@ -3025,6 +3029,14 @@ void createmon(struct wl_listener *listener, void *data) { wl_list_insert(&mons, &m->link); m->pertag = calloc(1, sizeof(Pertag)); + m->pertag->nmasters = calloc(tag_count + 1, sizeof(int32_t)); + m->pertag->mfacts = calloc(tag_count + 1, sizeof(float)); + m->pertag->no_hide = calloc(tag_count + 1, sizeof(bool)); + m->pertag->no_render_border = calloc(tag_count + 1, sizeof(bool)); + m->pertag->ltidxs = calloc(tag_count + 1, sizeof(const Layout *)); + if (!m->pertag->nmasters || !m->pertag->mfacts || !m->pertag->no_hide || + !m->pertag->no_render_border || !m->pertag->ltidxs) + die("pertag calloc failed"); if (chvt_backup_tag && regex_match(chvt_backup_selmon, m->wlr_output->name)) { m->tagset[0] = m->tagset[1] = (1 << (chvt_backup_tag - 1)) & TAGMASK; @@ -3036,7 +3048,7 @@ void createmon(struct wl_listener *listener, void *data) { m->pertag->curtag = m->pertag->prevtag = 1; } - for (i = 0; i <= LENGTH(tags); i++) { + for (i = 0; i <= tag_count; i++) { m->pertag->nmasters[i] = default_nmaster; m->pertag->mfacts[i] = default_mfact; m->pertag->ltidxs[i] = &layouts[0]; @@ -3077,7 +3089,7 @@ void createmon(struct wl_listener *listener, void *data) { ext_manager, EXT_WORKSPACE_ENABLE_CAPS); wlr_ext_workspace_group_handle_v1_output_enter(m->ext_group, m->wlr_output); - for (i = 1; i <= LENGTH(tags); i++) { + for (i = 1; i <= tag_count; i++) { add_workspace_by_tag(i, m); } @@ -5444,6 +5456,7 @@ void setup(void) { setenv("XDG_CURRENT_DESKTOP", "mango", 1); parse_config(); + tag_count = config.tag_count; init_baked_points(); int32_t drm_fd, i, sig[] = {SIGCHLD, SIGINT, SIGTERM, SIGPIPE}; @@ -6259,10 +6272,10 @@ void view_in_mon(const Arg *arg, bool want_animation, Monitor *m, if (arg->ui == (~0 & TAGMASK)) m->pertag->curtag = 0; else { - for (i = 0; !(arg->ui & 1 << i) && i < LENGTH(tags) && arg->ui != 0; + for (i = 0; !(arg->ui & 1 << i) && i < tag_count && arg->ui != 0; i++) ; - m->pertag->curtag = i >= LENGTH(tags) ? LENGTH(tags) : i + 1; + m->pertag->curtag = i >= tag_count ? tag_count : i + 1; } m->pertag->prevtag = From 139f25a0b9ced69ad3ef964a9970689ceb1d0a92 Mon Sep 17 00:00:00 2001 From: Gerry Hernandez Date: Mon, 9 Mar 2026 19:13:01 -0700 Subject: [PATCH 2/8] Clean up tag_count config parsing; defensively clamp tag snprintf --- src/config/parse_config.h | 2 ++ src/ext-protocol/ext-workspace.h | 4 ++-- src/mango.c | 1 - 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 2a3330b7..dd88230c 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -1577,6 +1577,7 @@ bool parse_option(Config *config, char *key, char *value) { config->default_nmaster = atoi(value); } else if (strcmp(key, "tag_count") == 0) { config->tag_count = CLAMP_INT(atoi(value), 1, 32); + tag_count = config->tag_count; } else if (strcmp(key, "center_master_overspread") == 0) { config->center_master_overspread = atoi(value); } else if (strcmp(key, "center_when_single_stack") == 0) { @@ -3494,6 +3495,7 @@ bool parse_config(void) { config.scroller_proportion_preset_count = 0; config.circle_layout = NULL; config.circle_layout_count = 0; + config.tag_count = 9; config.tag_rules = NULL; config.tag_rules_count = 0; config.cursor_theme = NULL; diff --git a/src/ext-protocol/ext-workspace.h b/src/ext-protocol/ext-workspace.h index fa07b1ca..61bf2ac2 100644 --- a/src/ext-protocol/ext-workspace.h +++ b/src/ext-protocol/ext-workspace.h @@ -74,7 +74,7 @@ static const char *get_name_from_tag(uint32_t tag, char *buf, size_t len) { return "overview"; if (tag > (uint32_t)tag_count) return NULL; - snprintf(buf, len, "%u", tag); + snprintf(buf, len, "%u", CLAMP_INT(tag, 1, 32)); return buf; } @@ -188,4 +188,4 @@ void workspaces_init() { ext_manager = wlr_ext_workspace_manager_v1_create(dpy, 1); /* Initialize the global workspaces list */ wl_list_init(&workspaces); -} \ No newline at end of file +} diff --git a/src/mango.c b/src/mango.c index 3e0ea8f6..c06a4116 100644 --- a/src/mango.c +++ b/src/mango.c @@ -5456,7 +5456,6 @@ void setup(void) { setenv("XDG_CURRENT_DESKTOP", "mango", 1); parse_config(); - tag_count = config.tag_count; init_baked_points(); int32_t drm_fd, i, sig[] = {SIGCHLD, SIGINT, SIGTERM, SIGPIPE}; From 22c23a6a5d08c7882fe7a9e60931aa80a75385ac Mon Sep 17 00:00:00 2001 From: Gerry Hernandez Date: Mon, 9 Mar 2026 19:39:40 -0700 Subject: [PATCH 3/8] Improve readability --- src/config/parse_config.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index dd88230c..79baf1e4 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -313,6 +313,7 @@ typedef struct { char autostart[3][256]; + int32_t tag_count; ConfigTagRule *tag_rules; // 动态数组 int32_t tag_rules_count; // 数量 @@ -359,8 +360,6 @@ typedef struct { int32_t allow_shortcuts_inhibit; int32_t allow_lock_transparent; - int32_t tag_count; - struct xkb_rule_names xkb_rules; char keymode[28]; From 923c7add5bb94393d9f90d6ca0939aaa61f04af8 Mon Sep 17 00:00:00 2001 From: Gerry Hernandez Date: Tue, 10 Mar 2026 11:25:04 -0700 Subject: [PATCH 4/8] Disambiguate tag_(bit|number) and harden get_name_from_tag --- src/ext-protocol/ext-workspace.h | 39 +++++++++++++++++--------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/ext-protocol/ext-workspace.h b/src/ext-protocol/ext-workspace.h index 61bf2ac2..59141c81 100644 --- a/src/ext-protocol/ext-workspace.h +++ b/src/ext-protocol/ext-workspace.h @@ -1,4 +1,7 @@ #include "wlr_ext_workspace_v1.h" +#include + +#define WORKSPACE_NAME_MAX_SIZE 12 #define EXT_WORKSPACE_ENABLE_CAPS \ EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ACTIVATE | \ @@ -8,8 +11,8 @@ typedef struct Monitor Monitor; struct workspace { struct wl_list link; // Link in global workspaces list - uint32_t tag; // Numeric identifier (1-9, 0=overview) - Monitor *m; // Associated monitor + uint32_t tag; // Identifier (tag number) in [0=overview, tag_count] + Monitor *m; // Associated monitor struct wlr_ext_workspace_handle_v1 *ext_workspace; // Protocol object /* Event listeners */ struct wl_listener activate; @@ -22,24 +25,24 @@ struct wlr_ext_workspace_manager_v1 *ext_manager; struct wl_list workspaces; void goto_workspace(struct workspace *target) { - uint32_t tag; - tag = 1 << (target->tag - 1); + uint32_t tag_bit; + tag_bit = 1 << (target->tag - 1); if (target->tag == 0) { toggleoverview(&(Arg){.i = -1}); return; } else { - view(&(Arg){.ui = tag}, true); + view(&(Arg){.ui = tag_bit}, true); } } void toggle_workspace(struct workspace *target) { - uint32_t tag; - tag = 1 << (target->tag - 1); + uint32_t tag_bit; + tag_bit = 1 << (target->tag - 1); if (target->tag == 0) { toggleview(&(Arg){.i = -1}); return; } else { - toggleview(&(Arg){.ui = tag}); + toggleview(&(Arg){.ui = tag_bit}); } } @@ -69,13 +72,13 @@ static void handle_ext_workspace_deactivate(struct wl_listener *listener, wlr_log(WLR_INFO, "ext deactivating workspace %d", workspace->tag); } -static const char *get_name_from_tag(uint32_t tag, char *buf, size_t len) { - if (tag == 0) - return "overview"; - if (tag > (uint32_t)tag_count) - return NULL; - snprintf(buf, len, "%u", CLAMP_INT(tag, 1, 32)); - return buf; +static void get_name_from_tag_number(char *dst_buf, size_t dst_len, + uint32_t tag_number) { + assert(tag_number <= (uint32_t)tag_count); + if (tag_number == 0) + snprintf(dst_buf, dst_len, "overview"); + else + snprintf(dst_buf, dst_len, "%u", tag_number); } void destroy_workspace(struct workspace *workspace) { @@ -105,9 +108,9 @@ static void remove_workspace_by_tag(uint32_t tag, Monitor *m) { } } -static void add_workspace_by_tag(int32_t tag, Monitor *m) { - char name_buf[4]; - const char *name = get_name_from_tag(tag, name_buf, sizeof(name_buf)); +static void add_workspace_by_tag(uint32_t tag, Monitor *m) { + char name[WORKSPACE_NAME_MAX_SIZE]; + get_name_from_tag_number(name, sizeof(name), tag); struct workspace *workspace = ecalloc(1, sizeof(*workspace)); wl_list_append(&workspaces, &workspace->link); From 686bd7f6ec0571737c6349c41e92027603876850 Mon Sep 17 00:00:00 2001 From: Gerry Hernandez Date: Tue, 10 Mar 2026 12:20:29 -0700 Subject: [PATCH 5/8] Improve consistency of tag types; streamline CLAMP macro --- mmsg/mmsg.c | 31 ++++++++++++++++--------------- src/config/parse_config.h | 22 +++++++++++++--------- src/config/preset.h | 2 +- src/ext-protocol/dwl-ipc.h | 8 ++++---- src/ext-protocol/ext-workspace.h | 4 ++-- 5 files changed, 36 insertions(+), 31 deletions(-) diff --git a/mmsg/mmsg.c b/mmsg/mmsg.c index 7a361a13..08932cf5 100644 --- a/mmsg/mmsg.c +++ b/mmsg/mmsg.c @@ -46,7 +46,7 @@ static int32_t Aflag; static uint32_t occ, seltags, total_clients, urg; static char *output_name; -static int32_t tagcount; +static uint32_t tag_count; static char *tagset; static char *layout_name; static int32_t layoutcount, layout_idx; @@ -85,10 +85,11 @@ static void noop_scale(void *data, struct wl_output *wl_output, static void noop_description(void *data, struct wl_output *wl_output, const char *description) {} -// Convert n to an N-bit binary string, store result in buf (minimum length bits+1) -void bin_str_nbits(char *buf, uint32_t n, int32_t bits) { - for (int32_t i = bits - 1; i >= 0; i--) { - *buf++ = ((n >> i) & 1) ? '1' : '0'; +// Convert num to an N-bit binary string, store result in buf (minimum length +// nbits+1) +void bin_str_nbits(char *buf, uint32_t num, uint32_t nbits) { + for (int32_t i = nbits - 1; i >= 0; i--) { + *buf++ = ((num >> i) & 1) ? '1' : '0'; } *buf = '\0'; // 字符串结尾 } @@ -96,9 +97,9 @@ void bin_str_nbits(char *buf, uint32_t n, int32_t bits) { static void dwl_ipc_tags(void *data, struct zdwl_ipc_manager_v2 *dwl_ipc_manager, uint32_t count) { - tagcount = count; + tag_count = count; if (Tflag && mode & GET) - printf("%d\n", tagcount); + printf("%u\n", tag_count); } static void dwl_ipc_layout(void *data, @@ -324,7 +325,7 @@ static void dwl_ipc_output_frame(void *data, if (tflag) { uint32_t mask = seltags; char *t = tagset; - int32_t i = 0; + uint32_t i = 0; for (; *t && *t >= '0' && *t <= '9'; t++) i = *t - '0' + i * 10; @@ -346,7 +347,7 @@ static void dwl_ipc_output_frame(void *data, } } - if ((i - 1) > tagcount) + if ((i - 1) > tag_count) die("bad tagset %s", tagset); zdwl_ipc_output_v2_set_tags(dwl_ipc_output, mask, 0); @@ -354,7 +355,7 @@ static void dwl_ipc_output_frame(void *data, if (cflag) { uint32_t and = ~0, xor = 0; char *t = client_tags; - int32_t i = 0; + uint32_t i = 0; for (; *t && *t >= '0' && *t <= '9'; t++) i = *t - '0' + i * 10; @@ -376,7 +377,7 @@ static void dwl_ipc_output_frame(void *data, break; } } - if ((i - 1) > tagcount) + if ((i - 1) > tag_count) die("bad client tagset %s", client_tags); zdwl_ipc_output_v2_set_client_tags(dwl_ipc_output, and, xor); @@ -395,11 +396,11 @@ static void dwl_ipc_output_frame(void *data, printf("%s clients %u\n", output_name, total_clients); - char occ_str[tagcount + 1], seltags_str[tagcount + 1], urg_str[tagcount + 1]; + char occ_str[tag_count + 1], seltags_str[tag_count + 1], urg_str[tag_count + 1]; - bin_str_nbits(occ_str, occ, tagcount); - bin_str_nbits(seltags_str, seltags, tagcount); - bin_str_nbits(urg_str, urg, tagcount); + bin_str_nbits(occ_str, occ, tag_count); + bin_str_nbits(seltags_str, seltags, tag_count); + bin_str_nbits(urg_str, urg, tag_count); printf("%s tags %u %u %u\n", output_name, occ, seltags, urg); printf("%s tags %s %s %s\n", output_name, occ_str, seltags_str, urg_str); diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 82062d57..dada63c0 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -8,15 +8,19 @@ #define SYSCONFDIR "/etc" #endif +// Clamps value in range while preserving numeric type +#define CLAMP(x, min, max) \ + ((x) < (min) ? (min) : ((x) > (max) ? (max) : (x))) + // 整数版本 - 截断小数部分 +// Deprecated: use CLAMP or CLAMP with explicit casts instead #define CLAMP_INT(x, min, max) \ - ((int32_t)(x) < (int32_t)(min) \ - ? (int32_t)(min) \ - : ((int32_t)(x) > (int32_t)(max) ? (int32_t)(max) : (int32_t)(x))) + CLAMP((int32_t)(x), (int32_t)(min), (int32_t)(max)) // 浮点数版本 - 保留小数部分 +// Deprecated: use CLAMP instead #define CLAMP_FLOAT(x, min, max) \ - ((x) < (min) ? (min) : ((x) > (max) ? (max) : (x))) + CLAMP(x, min, max) enum { NUM_TYPE_MINUS, NUM_TYPE_PLUS, NUM_TYPE_DEFAULT }; @@ -159,7 +163,7 @@ typedef struct { } GestureBinding; typedef struct { - int32_t id; + uint32_t id; char *layout_name; char *monitor_name; char *monitor_make; @@ -314,7 +318,7 @@ typedef struct { char autostart[3][256]; - int32_t tag_count; + uint32_t tag_count; ConfigTagRule *tag_rules; // 动态数组 int32_t tag_rules_count; // 数量 @@ -1586,7 +1590,7 @@ bool parse_option(Config *config, char *key, char *value) { } else if (strcmp(key, "default_nmaster") == 0) { config->default_nmaster = atoi(value); } else if (strcmp(key, "tag_count") == 0) { - config->tag_count = CLAMP_INT(atoi(value), 1, 32); + config->tag_count = CLAMP(atoi(value), 1, 32); tag_count = config->tag_count; } else if (strcmp(key, "center_master_overspread") == 0) { config->center_master_overspread = atoi(value); @@ -3707,7 +3711,7 @@ void reapply_pointer(void) { void reapply_master(void) { - int32_t i; + uint32_t i; Monitor *m = NULL; for (i = 0; i <= tag_count; i++) { wl_list_for_each(m, &mons, link) { @@ -3725,7 +3729,7 @@ void reapply_master(void) { } void parse_tagrule(Monitor *m) { - int32_t i, jk; + uint32_t i, jk; ConfigTagRule tr; Client *c = NULL; bool match_rule = false; diff --git a/src/config/preset.h b/src/config/preset.h index 84a74c71..480423bf 100644 --- a/src/config/preset.h +++ b/src/config/preset.h @@ -207,7 +207,7 @@ enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TAP_MAP_LRM; /* If you want to use the windows key for MODKEY, use WLR_MODIFIER_LOGO */ #define MODKEY WLR_MODIFIER_ALT -int32_t tag_count = 9; +uint32_t tag_count = 9; float focused_opacity = 1.0; float unfocused_opacity = 1.0; diff --git a/src/ext-protocol/dwl-ipc.h b/src/ext-protocol/dwl-ipc.h index 23934807..7a5be561 100644 --- a/src/ext-protocol/dwl-ipc.h +++ b/src/ext-protocol/dwl-ipc.h @@ -112,15 +112,15 @@ void dwl_ipc_output_printstatus_to(DwlIpcOutput *ipc_output) { Client *c = NULL, *focused = NULL; struct wlr_keyboard *keyboard; xkb_layout_index_t current; - int32_t tagmask, state, numclients, focused_client, tag; + uint32_t tagmask, state, numclients, focused_client, tag_idx; const char *title, *appid, *symbol; char kb_layout[32]; focused = focustop(monitor); zdwl_ipc_output_v2_send_active(ipc_output->resource, monitor == selmon); - for (tag = 0; tag < tag_count; tag++) { + for (tag_idx = 0; tag_idx < tag_count; tag_idx++) { numclients = state = focused_client = 0; - tagmask = 1 << tag; + tagmask = 1 << tag_idx; if ((tagmask & monitor->tagset[monitor->seltags]) != 0) state |= ZDWL_IPC_OUTPUT_V2_TAG_STATE_ACTIVE; wl_list_for_each(c, &clients, link) { @@ -134,7 +134,7 @@ void dwl_ipc_output_printstatus_to(DwlIpcOutput *ipc_output) { state |= ZDWL_IPC_OUTPUT_V2_TAG_STATE_URGENT; numclients++; } - zdwl_ipc_output_v2_send_tag(ipc_output->resource, tag, state, + zdwl_ipc_output_v2_send_tag(ipc_output->resource, tag_idx, state, numclients, focused_client); } diff --git a/src/ext-protocol/ext-workspace.h b/src/ext-protocol/ext-workspace.h index 59141c81..648690ef 100644 --- a/src/ext-protocol/ext-workspace.h +++ b/src/ext-protocol/ext-workspace.h @@ -74,7 +74,7 @@ static void handle_ext_workspace_deactivate(struct wl_listener *listener, static void get_name_from_tag_number(char *dst_buf, size_t dst_len, uint32_t tag_number) { - assert(tag_number <= (uint32_t)tag_count); + assert(tag_number <= tag_count); if (tag_number == 0) snprintf(dst_buf, dst_len, "overview"); else @@ -169,7 +169,7 @@ void dwl_ext_workspace_printstatus(Monitor *m) { } void refresh_monitors_workspaces_status(Monitor *m) { - int32_t i; + uint32_t i; if (m->isoverview) { for (i = 1; i <= tag_count; i++) { From 393b075ed1baf675df0856ed270291bc081c337d Mon Sep 17 00:00:00 2001 From: Gerry Hernandez Date: Tue, 10 Mar 2026 14:29:50 -0700 Subject: [PATCH 6/8] Fix edge cases --- mmsg/mmsg.c | 6 +++++- src/config/parse_config.h | 22 +++++++++++++++++++--- src/ext-protocol/ext-workspace.h | 4 ++-- src/mango.c | 4 +++- 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/mmsg/mmsg.c b/mmsg/mmsg.c index 08932cf5..017f3d2a 100644 --- a/mmsg/mmsg.c +++ b/mmsg/mmsg.c @@ -88,10 +88,14 @@ static void noop_description(void *data, struct wl_output *wl_output, // Convert num to an N-bit binary string, store result in buf (minimum length // nbits+1) void bin_str_nbits(char *buf, uint32_t num, uint32_t nbits) { + if (nbits == 0) { + *buf = '\0'; + return; + } for (int32_t i = nbits - 1; i >= 0; i--) { *buf++ = ((num >> i) & 1) ? '1' : '0'; } - *buf = '\0'; // 字符串结尾 + *buf = '\0'; } static void dwl_ipc_tags(void *data, diff --git a/src/config/parse_config.h b/src/config/parse_config.h index dada63c0..4ecf9c82 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -8,9 +8,16 @@ #define SYSCONFDIR "/etc" #endif +static bool config_initialized = false; + // Clamps value in range while preserving numeric type #define CLAMP(x, min, max) \ - ((x) < (min) ? (min) : ((x) > (max) ? (max) : (x))) + ({ \ + __typeof__(x) _x = (x); \ + __typeof__(min) _min = (min); \ + __typeof__(max) _max = (max); \ + _x < _min ? _min : (_x > _max ? _max : _x); \ + }) // 整数版本 - 截断小数部分 // Deprecated: use CLAMP or CLAMP with explicit casts instead @@ -1590,8 +1597,16 @@ bool parse_option(Config *config, char *key, char *value) { } else if (strcmp(key, "default_nmaster") == 0) { config->default_nmaster = atoi(value); } else if (strcmp(key, "tag_count") == 0) { - config->tag_count = CLAMP(atoi(value), 1, 32); - tag_count = config->tag_count; + uint32_t requested = CLAMP(atoi(value), 1, 32); + config->tag_count = requested; + if (!config_initialized) { + tag_count = requested; + } else if (tag_count != requested) { + wlr_log(WLR_INFO, + "tag_count change requires restart (current: %u, " + "requested: %u)", + tag_count, requested); + } } else if (strcmp(key, "center_master_overspread") == 0) { config->center_master_overspread = atoi(value); } else if (strcmp(key, "center_when_single_stack") == 0) { @@ -3555,6 +3570,7 @@ bool parse_config(void) { parse_correct = parse_config_file(&config, filename, true); set_default_key_bindings(&config); override_config(); + config_initialized = true; return parse_correct; } diff --git a/src/ext-protocol/ext-workspace.h b/src/ext-protocol/ext-workspace.h index 648690ef..3bb9e946 100644 --- a/src/ext-protocol/ext-workspace.h +++ b/src/ext-protocol/ext-workspace.h @@ -1,5 +1,4 @@ #include "wlr_ext_workspace_v1.h" -#include #define WORKSPACE_NAME_MAX_SIZE 12 @@ -74,7 +73,8 @@ static void handle_ext_workspace_deactivate(struct wl_listener *listener, static void get_name_from_tag_number(char *dst_buf, size_t dst_len, uint32_t tag_number) { - assert(tag_number <= tag_count); + if (tag_number > tag_count) + die("tag_number %u exceeds tag_count %u", tag_number, tag_count); if (tag_number == 0) snprintf(dst_buf, dst_len, "overview"); else diff --git a/src/mango.c b/src/mango.c index b544863b..d1ca6973 100644 --- a/src/mango.c +++ b/src/mango.c @@ -3032,6 +3032,8 @@ void createmon(struct wl_listener *listener, void *data) { wl_list_insert(&mons, &m->link); m->pertag = calloc(1, sizeof(Pertag)); + if (!m->pertag) + die("pertag calloc failed"); m->pertag->nmasters = calloc(tag_count + 1, sizeof(int32_t)); m->pertag->mfacts = calloc(tag_count + 1, sizeof(float)); m->pertag->no_hide = calloc(tag_count + 1, sizeof(bool)); @@ -3039,7 +3041,7 @@ void createmon(struct wl_listener *listener, void *data) { m->pertag->ltidxs = calloc(tag_count + 1, sizeof(const Layout *)); if (!m->pertag->nmasters || !m->pertag->mfacts || !m->pertag->no_hide || !m->pertag->no_render_border || !m->pertag->ltidxs) - die("pertag calloc failed"); + die("pertag member calloc failed"); if (chvt_backup_tag && regex_match(chvt_backup_selmon, m->wlr_output->name)) { m->tagset[0] = m->tagset[1] = (1 << (chvt_backup_tag - 1)) & TAGMASK; From c3a0c11adc85e0f659fd9a463704a2c757afd72a Mon Sep 17 00:00:00 2001 From: Gerry Hernandez Date: Sun, 15 Mar 2026 18:52:11 -0700 Subject: [PATCH 7/8] Refactor after upstream changes; harden get_name_from_tag_number --- mmsg/mmsg.c | 7 ++++--- src/config/parse_config.h | 36 +++++++++++++++++++------------- src/config/preset.h | 3 --- src/dispatch/bind_define.h | 4 ++-- src/ext-protocol/dwl-ipc.h | 4 ++-- src/ext-protocol/ext-workspace.h | 35 ++++++++++++++++++------------- src/fetch/monitor.h | 12 +++++------ src/mango.c | 20 ++++++++++-------- 8 files changed, 67 insertions(+), 54 deletions(-) diff --git a/mmsg/mmsg.c b/mmsg/mmsg.c index 017f3d2a..5b968444 100644 --- a/mmsg/mmsg.c +++ b/mmsg/mmsg.c @@ -351,7 +351,7 @@ static void dwl_ipc_output_frame(void *data, } } - if ((i - 1) > tag_count) + if (i == 0 || (i - 1) > tag_count) die("bad tagset %s", tagset); zdwl_ipc_output_v2_set_tags(dwl_ipc_output, mask, 0); @@ -381,7 +381,7 @@ static void dwl_ipc_output_frame(void *data, break; } } - if ((i - 1) > tag_count) + if (i == 0 || (i - 1) > tag_count) die("bad client tagset %s", client_tags); zdwl_ipc_output_v2_set_client_tags(dwl_ipc_output, and, xor); @@ -400,7 +400,8 @@ static void dwl_ipc_output_frame(void *data, printf("%s clients %u\n", output_name, total_clients); - char occ_str[tag_count + 1], seltags_str[tag_count + 1], urg_str[tag_count + 1]; + char occ_str[tag_count + 1], seltags_str[tag_count + 1], + urg_str[tag_count + 1]; bin_str_nbits(occ_str, occ, tag_count); bin_str_nbits(seltags_str, seltags, tag_count); diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 208bc853..e9397161 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -8,7 +8,11 @@ #define SYSCONFDIR "/etc" #endif -static bool config_initialized = false; +// We don't want to allow config hot-reloading to change the tag_count and we +// want to require a compositor reload. The code to support hot-reloading a +// change in tag_count is a lot more involved than this minimal solution we have +// for configuring the number of tags available in Mango. +static uint32_t active_tag_count = 0; // Clamps value in range while preserving numeric type #define CLAMP(x, min, max) \ @@ -1136,7 +1140,7 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value, while (token != NULL) { int32_t num = atoi(token); - if (num > 0 && num <= tag_count) { + if (num > 0 && num <= config.tag_count) { mask |= (1 << (num - 1)); } token = strtok_r(NULL, "|", &saveptr); @@ -1604,14 +1608,17 @@ bool parse_option(Config *config, char *key, char *value) { config->default_nmaster = atoi(value); } else if (strcmp(key, "tag_count") == 0) { uint32_t requested = CLAMP(atoi(value), 1, 32); - config->tag_count = requested; - if (!config_initialized) { - tag_count = requested; - } else if (tag_count != requested) { - wlr_log(WLR_INFO, - "tag_count change requires restart (current: %u, " - "requested: %u)", - tag_count, requested); + bool is_initial_config_load = active_tag_count == 0; + if (is_initial_config_load) { + config->tag_count = requested; + } else { + config->tag_count = active_tag_count; + if (active_tag_count != requested) { + wlr_log(WLR_INFO, + "tag_count change requires restart (current: %u, " + "requested: %u)", + active_tag_count, requested); + } } } else if (strcmp(key, "center_master_overspread") == 0) { config->center_master_overspread = atoi(value); @@ -1936,7 +1943,7 @@ bool parse_option(Config *config, char *key, char *value) { trim_whitespace(val); if (strcmp(key, "id") == 0) { - rule->id = CLAMP_INT(atoi(val), 0, tag_count); + rule->id = CLAMP_INT(atoi(val), 0, config->tag_count); } else if (strcmp(key, "layout_name") == 0) { rule->layout_name = strdup(val); } else if (strcmp(key, "monitor_name") == 0) { @@ -3527,7 +3534,7 @@ bool parse_config(void) { config.scroller_proportion_preset_count = 0; config.circle_layout = NULL; config.circle_layout_count = 0; - config.tag_count = 9; + config.tag_count = 0; config.tag_rules = NULL; config.tag_rules_count = 0; config.cursor_theme = NULL; @@ -3561,7 +3568,6 @@ bool parse_config(void) { parse_correct = parse_config_file(&config, filename, true); set_default_key_bindings(&config); override_config(); - config_initialized = true; return parse_correct; } @@ -3729,7 +3735,7 @@ void reapply_master(void) { uint32_t i; Monitor *m = NULL; - for (i = 0; i <= tag_count; i++) { + for (i = 0; i <= config.tag_count; i++) { wl_list_for_each(m, &mons, link) { if (!m->wlr_output->enabled) { continue; @@ -3808,7 +3814,7 @@ void parse_tagrule(Monitor *m) { } } - for (i = 1; i <= tag_count; i++) { + for (i = 1; i <= config.tag_count; i++) { wl_list_for_each(c, &clients, link) { if ((c->tags & (1 << (i - 1)) & TAGMASK) && ISTILED(c)) { if (m->pertag->mfacts[i] > 0.0f) diff --git a/src/config/preset.h b/src/config/preset.h index 6952518e..3b0cb8dd 100644 --- a/src/config/preset.h +++ b/src/config/preset.h @@ -1,8 +1,5 @@ #define MODKEY WLR_MODIFIER_ALT -static const char *tags[] = { - "1", "2", "3", "4", "5", "6", "7", "8", "9", -}; static const struct xkb_rule_names xkb_fallback_rules = { .layout = "us", diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index cbae132d..deec59d0 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1539,10 +1539,10 @@ int32_t viewtoright_have_client(const Arg *arg) { return 0; } - if (current >= tag_count) + if (current >= config.tag_count) return 0; - for (n = current + 1; n <= tag_count; n++) { + for (n = current + 1; n <= config.tag_count; n++) { if (get_tag_status(n, selmon)) { found = true; break; diff --git a/src/ext-protocol/dwl-ipc.h b/src/ext-protocol/dwl-ipc.h index 7a5be561..98b6a107 100644 --- a/src/ext-protocol/dwl-ipc.h +++ b/src/ext-protocol/dwl-ipc.h @@ -55,7 +55,7 @@ void dwl_ipc_manager_bind(struct wl_client *client, void *data, &dwl_manager_implementation, NULL, dwl_ipc_manager_destroy); - zdwl_ipc_manager_v2_send_tags(manager_resource, tag_count); + zdwl_ipc_manager_v2_send_tags(manager_resource, config.tag_count); for (uint32_t i = 0; i < LENGTH(layouts); i++) zdwl_ipc_manager_v2_send_layout(manager_resource, layouts[i].symbol); @@ -118,7 +118,7 @@ void dwl_ipc_output_printstatus_to(DwlIpcOutput *ipc_output) { focused = focustop(monitor); zdwl_ipc_output_v2_send_active(ipc_output->resource, monitor == selmon); - for (tag_idx = 0; tag_idx < tag_count; tag_idx++) { + for (tag_idx = 0; tag_idx < config.tag_count; tag_idx++) { numclients = state = focused_client = 0; tagmask = 1 << tag_idx; if ((tagmask & monitor->tagset[monitor->seltags]) != 0) diff --git a/src/ext-protocol/ext-workspace.h b/src/ext-protocol/ext-workspace.h index 3bb9e946..8bd041aa 100644 --- a/src/ext-protocol/ext-workspace.h +++ b/src/ext-protocol/ext-workspace.h @@ -1,7 +1,5 @@ #include "wlr_ext_workspace_v1.h" -#define WORKSPACE_NAME_MAX_SIZE 12 - #define EXT_WORKSPACE_ENABLE_CAPS \ EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ACTIVATE | \ EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_DEACTIVATE @@ -10,8 +8,8 @@ typedef struct Monitor Monitor; struct workspace { struct wl_list link; // Link in global workspaces list - uint32_t tag; // Identifier (tag number) in [0=overview, tag_count] - Monitor *m; // Associated monitor + uint32_t tag; // Identifier (tag number) in [0=overview, tag_count] + Monitor *m; // Associated monitor struct wlr_ext_workspace_handle_v1 *ext_workspace; // Protocol object /* Event listeners */ struct wl_listener activate; @@ -71,14 +69,22 @@ static void handle_ext_workspace_deactivate(struct wl_listener *listener, wlr_log(WLR_INFO, "ext deactivating workspace %d", workspace->tag); } -static void get_name_from_tag_number(char *dst_buf, size_t dst_len, - uint32_t tag_number) { - if (tag_number > tag_count) - die("tag_number %u exceeds tag_count %u", tag_number, tag_count); +// Returns the size_t that would have been written to dst_buf if dst_len was +// large enough. Safe usage of this function is to call it with +// dst_buf == NULL and dst_len == 0, then use the returned value to allocate the +// destination buffer, then call it again passing in the buffer and size. +static size_t get_name_from_tag_number(char *dst_buf, size_t dst_len, + uint32_t tag_number) { + if (tag_number > config.tag_count) + die("tag_number %u exceeds tag_count %u", tag_number, config.tag_count); + int n; if (tag_number == 0) - snprintf(dst_buf, dst_len, "overview"); + n = snprintf(dst_buf, dst_len, "overview"); else - snprintf(dst_buf, dst_len, "%u", tag_number); + n = snprintf(dst_buf, dst_len, "%u", tag_number); + if (n < 0) + die("snprintf failed for tag_number %u", tag_number); + return (size_t)(n + 1); } void destroy_workspace(struct workspace *workspace) { @@ -109,8 +115,9 @@ static void remove_workspace_by_tag(uint32_t tag, Monitor *m) { } static void add_workspace_by_tag(uint32_t tag, Monitor *m) { - char name[WORKSPACE_NAME_MAX_SIZE]; - get_name_from_tag_number(name, sizeof(name), tag); + size_t name_len = get_name_from_tag_number(NULL, 0, tag); + char name[name_len]; + get_name_from_tag_number(name, name_len, tag); struct workspace *workspace = ecalloc(1, sizeof(*workspace)); wl_list_append(&workspaces, &workspace->link); @@ -172,13 +179,13 @@ void refresh_monitors_workspaces_status(Monitor *m) { uint32_t i; if (m->isoverview) { - for (i = 1; i <= tag_count; i++) { + for (i = 1; i <= config.tag_count; i++) { remove_workspace_by_tag(i, m); } add_workspace_by_tag(0, m); } else { remove_workspace_by_tag(0, m); - for (i = 1; i <= tag_count; i++) { + for (i = 1; i <= config.tag_count; i++) { add_workspace_by_tag(i, m); } } diff --git a/src/fetch/monitor.h b/src/fetch/monitor.h index 6c1e8a24..40cec9f2 100644 --- a/src/fetch/monitor.h +++ b/src/fetch/monitor.h @@ -57,14 +57,14 @@ uint32_t get_tags_first_tag_num(uint32_t source_tags) { return selmon->pertag->curtag; } - for (i = 0; !(tag & 1) && source_tags != 0 && i < tag_count; i++) { + for (i = 0; !(tag & 1) && source_tags != 0 && i < config.tag_count; i++) { tag = source_tags >> i; } if (i == 1) { return 1; - } else if (i > tag_count) { - return tag_count; + } else if (i > config.tag_count) { + return config.tag_count; } else { return i; } @@ -79,14 +79,14 @@ uint32_t get_tags_first_tag(uint32_t source_tags) { return selmon->pertag->curtag; } - for (i = 0; !(tag & 1) && source_tags != 0 && i < tag_count; i++) { + for (i = 0; !(tag & 1) && source_tags != 0 && i < config.tag_count; i++) { tag = source_tags >> i; } if (i == 1) { return 1; - } else if (i > tag_count) { - return 1 << (tag_count - 1); + } else if (i > config.tag_count) { + return 1 << (config.tag_count - 1); } else { return 1 << (i - 1); } diff --git a/src/mango.c b/src/mango.c index 8ff831c5..5314bd61 100644 --- a/src/mango.c +++ b/src/mango.c @@ -117,7 +117,7 @@ ((C) && (M) && (C)->mon == (M) && ((C)->tags & (M)->tagset[(M)->seltags])) #define LENGTH(X) (sizeof X / sizeof X[0]) #define END(A) ((A) + LENGTH(A)) -#define TAGMASK (tag_count == 32 ? ~0u : ((1u << tag_count) - 1)) +#define TAGMASK (config.tag_count == 32 ? ~0u : ((1u << config.tag_count) - 1)) #define LISTEN(E, L, H) wl_signal_add((E), ((L)->notify = (H), (L))) #define ISFULLSCREEN(A) \ ((A)->isfullscreen || (A)->ismaximizescreen || \ @@ -3041,14 +3041,16 @@ void createmon(struct wl_listener *listener, void *data) { wlr_output_state_finish(&state); wl_list_insert(&mons, &m->link); + if (active_tag_count == 0) + active_tag_count = config.tag_count; m->pertag = calloc(1, sizeof(Pertag)); if (!m->pertag) die("pertag calloc failed"); - m->pertag->nmasters = calloc(tag_count + 1, sizeof(int32_t)); - m->pertag->mfacts = calloc(tag_count + 1, sizeof(float)); - m->pertag->no_hide = calloc(tag_count + 1, sizeof(bool)); - m->pertag->no_render_border = calloc(tag_count + 1, sizeof(bool)); - m->pertag->ltidxs = calloc(tag_count + 1, sizeof(const Layout *)); + m->pertag->nmasters = calloc(config.tag_count + 1, sizeof(int32_t)); + m->pertag->mfacts = calloc(config.tag_count + 1, sizeof(float)); + m->pertag->no_hide = calloc(config.tag_count + 1, sizeof(bool)); + m->pertag->no_render_border = calloc(config.tag_count + 1, sizeof(bool)); + m->pertag->ltidxs = calloc(config.tag_count + 1, sizeof(const Layout *)); if (!m->pertag->nmasters || !m->pertag->mfacts || !m->pertag->no_hide || !m->pertag->no_render_border || !m->pertag->ltidxs) die("pertag member calloc failed"); @@ -3103,7 +3105,7 @@ void createmon(struct wl_listener *listener, void *data) { ext_manager, EXT_WORKSPACE_ENABLE_CAPS); wlr_ext_workspace_group_handle_v1_output_enter(m->ext_group, m->wlr_output); - for (i = 1; i <= tag_count; i++) { + for (i = 1; i <= config.tag_count; i++) { add_workspace_by_tag(i, m); } @@ -6307,10 +6309,10 @@ void view_in_mon(const Arg *arg, bool want_animation, Monitor *m, if (arg->ui == (~0 & TAGMASK)) m->pertag->curtag = 0; else { - for (i = 0; !(arg->ui & 1 << i) && i < tag_count && arg->ui != 0; + for (i = 0; !(arg->ui & 1u << i) && i < config.tag_count && arg->ui != 0; i++) ; - m->pertag->curtag = i >= tag_count ? tag_count : i + 1; + m->pertag->curtag = i >= config.tag_count ? config.tag_count : i + 1; } m->pertag->prevtag = From 342464d3d13ed7ee6d069c5ef78e0d4f7bfb00fd Mon Sep 17 00:00:00 2001 From: Gerry Hernandez Date: Sun, 15 Mar 2026 20:31:49 -0700 Subject: [PATCH 8/8] Update documentation with tag_count config var --- docs/bindings/keys.md | 12 ++++++------ docs/configuration/miscellaneous.md | 4 +++- docs/window-management/rules.md | 4 ++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/bindings/keys.md b/docs/bindings/keys.md index 64ba64b7..08cab9ad 100644 --- a/docs/bindings/keys.md +++ b/docs/bindings/keys.md @@ -116,20 +116,20 @@ bindr=Super,Super_L,spawn,rofi -show run | Command | Param | Description | | :--- | :--- | :--- | -| `view` | `-1/0/1-9` or `mask [,synctag]` | View tag. `-1` = previous tagset, `0` = all tags, `1-9` = specific tag, mask e.g. `1\|3\|5`. Optional `synctag` (0/1) syncs the action to all monitors. | +| `view` | `-1/0/[1, tag_count]` or `mask [,synctag]` | View tag. `-1` = previous tagset, `0` = all tags, mask e.g. `1\|3\|5`. Optional `synctag` (0/1) syncs the action to all monitors. | | `viewtoleft` | `[synctag]` | View previous tag. Optional `synctag` (0/1) syncs to all monitors. | | `viewtoright` | `[synctag]` | View next tag. Optional `synctag` (0/1) syncs to all monitors. | | `viewtoleft_have_client` | `[synctag]` | View left tag and focus client if present. Optional `synctag` (0/1). | | `viewtoright_have_client` | `[synctag]` | View right tag and focus client if present. Optional `synctag` (0/1). | | `viewcrossmon` | `tag,monitor_spec` | View specified tag on specified monitor. | -| `tag` | `1-9 [,synctag]` | Move window to tag. Optional `synctag` (0/1) syncs to all monitors. | -| `tagsilent` | `1-9` | Move window to tag without focusing it. | +| `tag` | `[1, tag_count] [,synctag]` | Move window to tag. Optional `synctag` (0/1) syncs to all monitors. | +| `tagsilent` | `[1, tag_count]` | Move window to tag without focusing it. | | `tagtoleft` | `[synctag]` | Move window to left tag. Optional `synctag` (0/1). | | `tagtoright` | `[synctag]` | Move window to right tag. Optional `synctag` (0/1). | | `tagcrossmon` | `tag,monitor_spec` | Move window to specified tag on specified monitor. | -| `toggletag` | `0-9` | Toggle tag on window (0 means all tags). | -| `toggleview` | `1-9` | Toggle tag view. | -| `comboview` | `1-9` | View multi tags pressed simultaneously. | +| `toggletag` | `[0, tag_count]` | Toggle tag on window (0 means all tags). | +| `toggleview` | `[1, tag_count]` | Toggle tag view. | +| `comboview` | `[1, tag_count]` | View multi tags pressed simultaneously. | | `focusmon` | `left/right/up/down/monitor_spec` | Focus monitor by direction or [monitor spec](/docs/configuration/monitors#monitor-spec-format). | | `tagmon` | `left/right/up/down/monitor_spec,[keeptag]` | Move window to monitor by direction or [monitor spec](/docs/configuration/monitors#monitor-spec-format). `keeptag` is 0 or 1. | diff --git a/docs/configuration/miscellaneous.md b/docs/configuration/miscellaneous.md index 2e5a1e92..6b695e40 100644 --- a/docs/configuration/miscellaneous.md +++ b/docs/configuration/miscellaneous.md @@ -31,6 +31,7 @@ description: Advanced settings for XWayland, focus behavior, and system integrat | Setting | Default | Description | | :--- | :--- | :--- | +| `tag_count` | `9` | Number of tags/workspaces available. Supports range `[1–32]`. **Requires restart to take effect.** | | `focus_cross_monitor` | `0` | Allow directional focus to cross monitor boundaries. | | `exchange_cross_monitor` | `0` | Allow exchanging clients across monitor boundaries. | | `focus_cross_tag` | `0` | Allow directional focus to cross into other tags. | @@ -48,4 +49,5 @@ description: Advanced settings for XWayland, focus behavior, and system integrat | `no_border_when_single` | `0` | Remove window borders when only one window is visible on the tag. | | `idleinhibit_ignore_visible` | `0` | Allow invisible clients (e.g., background audio players) to inhibit idle. | | `drag_tile_refresh_interval` | `8.0` | Interval (1.0–16.0) to refresh tiled window resize during drag. Too small may cause application lag. | -| `drag_floating_refresh_interval` | `8.0` | Interval (1.0–16.0) to refresh floating window resize during drag. Too small may cause application lag. | \ No newline at end of file +| `drag_floating_refresh_interval` | `8.0` | Interval (1.0–16.0) to refresh floating window resize during drag. Too small may cause application lag. | + diff --git a/docs/window-management/rules.md b/docs/window-management/rules.md index 996c172f..d3e60029 100644 --- a/docs/window-management/rules.md +++ b/docs/window-management/rules.md @@ -45,7 +45,7 @@ windowrule=Parameter:Values,Parameter:Values,appid:Values,title:Values | `offsetx` | integer | -999-999 | X offset from center (%), 100 is the edge of screen with outer gap | | `offsety` | integer | -999-999 | Y offset from center (%), 100 is the edge of screen with outer gap | | `monitor` | string | Any | Assign to monitor by [monitor spec](/docs/configuration/monitors#monitor-spec-format) (name, make, model, or serial) | -| `tags` | integer | 1-9 | Assign to specific tag | +| `tags` | integer | `[1, tag_count]` | Assign to specific tag | | `no_force_center` | integer | `0` / `1` | Window does not force center | | `isnosizehint` | integer | `0` / `1` | Don't use min size and max size for size hints | @@ -172,7 +172,7 @@ tagrule=id:Values,monitor_make:xxx,monitor_model:xxx,Parameter:Values | Parameter | Type | Values | Description | | :--- | :--- | :--- | :--- | -| `id` | integer | 0-9 | Match by tag id, 0 means the ~0 tag | +| `id` | integer | `[1, tag_count]` | Match by tag id, 0 means the ~0 tag | | `monitor_name` | string | monitor name | Match by monitor name | | `monitor_make` | string | monitor make | Match by monitor manufacturer | | `monitor_model` | string | monitor model | Match by monitor model |