Skip to content

Commit 47c1169

Browse files
SsniblesDreamMaoMao
authored andcommitted
feat: implement special tag overlay workspace (scratchpad)
1 parent 7b4266f commit 47c1169

26 files changed

Lines changed: 772 additions & 247 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/.cache
22
/.vscode
33
/result
4+
/build
45
/mango-wlonly
56
config.h
67
mango

assets/config.conf

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,11 @@ gappoh=10
128128
gappov=10
129129
scratchpad_width_ratio=0.8
130130
scratchpad_height_ratio=0.9
131+
special_dim=0.5
132+
special_gappih=10
133+
special_gappiv=10
134+
special_gappoh=20
135+
special_gappov=20
131136
borderpx=4
132137
rootcolor=0x201b14ff
133138
bordercolor=0x444444ff
@@ -191,6 +196,9 @@ bind=SUPER,i,minimized,
191196
bind=SUPER,o,toggleoverlay,
192197
bind=SUPER+SHIFT,I,restore_minimized
193198
bind=ALT,z,toggle_scratchpad
199+
bind=SUPER,s,toggle_special_tag
200+
bind=SUPER+SHIFT,s,tag_special_tag
201+
bind=SUPER+CTRL,s,tag_special_silent
194202

195203
# scroller layout
196204
bind=ALT,e,set_proportion,1.0

docs/bindings/keys.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,12 @@ bindr=Super,Super_L,spawn,rofi -show run
105105
| `toggle_render_border` | - | Toggle border rendering. |
106106
| `centerwin` | - | Center the floating window. |
107107
| `minimized` | - | Minimize window to scratchpad. |
108-
| `restore_minimized` | `0/1` | Restore minimized window to its previous state.(`1` means keep previous tags, `0` means restore to current tags.) |
108+
| `restore_minimized` | - | Restore minimized window to the currently focused tag. |
109109
| `toggle_scratchpad` | - | Toggle scratchpad. |
110110
| `toggle_named_scratchpad` | `appid,title,cmd` | Toggle named scratchpad. Launches app if not running, otherwise shows/hides it. |
111+
| `toggle_special_tag` | - | Toggle special workspace overlay (tiling scratchpad). |
112+
| `tag_special_tag` | - | Move focused window to/from the special workspace overlay. |
113+
| `tag_special_silent` | - | Silently move focused window to/from the special workspace overlay. |
111114

112115
### Focus & Movement
113116

docs/window-management/rules.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ windowrule=Parameter:Values,Parameter:Values,appid:Values,title:Values
5050
| `offsetx` | integer | -999-999 | X offset from center (%), 100 is the edge of screen with outer gap |
5151
| `offsety` | integer | -999-999 | Y offset from center (%), 100 is the edge of screen with outer gap |
5252
| `monitor` | string | Any | Assign to monitor by [monitor spec](/docs/configuration/monitors#monitor-spec-format) (name, make, model, or serial) |
53-
| `tags` | mask | `1-9` / `1\|3\|5` | Assign to specific one tag or multiple tags(use `\|` to split multiple tags) |
53+
| `tags` | mask | `0-9` / `1\|3\|5` | Assign to specific one tag (use `0` for special workspace overlay) or multiple tags (use `\|` to split multiple tags) |
5454
| `no_force_center` | integer | `0` / `1` | Window does not force center |
5555
| `isnosizehint` | integer | `0` / `1` | Don't use min size and max size for size hints |
5656

docs/window-management/scratchpad.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,47 @@ scratchpad_width_ratio=0.8
7171
scratchpad_height_ratio=0.9
7272
scratchpadcolor=0x516c93ff
7373
```
74+
75+
---
76+
77+
## Special Workspace (Tag 0)
78+
79+
The special workspace (Tag 0) provides an overlay workspace of windows that can be summoned anywhere with full layout support (Scroller, Master/Stack, Dwindle, etc.). When Tag 0 is active, windows from the underlying workspace remain visible in the background, and all windows on the special workspace are displayed above them.
80+
81+
### Keybindings
82+
83+
```ini
84+
# Toggle the special workspace overlay (Tag 0)
85+
bind=SUPER,s,toggle_special_tag
86+
87+
# Move focused window to/from the special workspace
88+
bind=SUPER+SHIFT,s,tag_special_tag
89+
90+
# Silently send active window to the special workspace without switching
91+
bind=SUPER+CTRL,s,tag_special_silent
92+
```
93+
94+
### Window Rules for Special Workspace
95+
96+
You can automatically assign applications to launch directly on the special workspace using `tags:0`:
97+
98+
```ini
99+
# Automatically open Spotify and Discord in the special workspace
100+
windowrule=tags:0,appid:spotify
101+
windowrule=tags:0,appid:discord
102+
```
103+
104+
### Configuration Options
105+
106+
You can configure background dimming and custom layout gaps for the special workspace:
107+
108+
```ini
109+
# Background dim level when special workspace is active (0.0 to 1.0, default 0.5)
110+
special_dim=0.5
111+
112+
# Inner and outer gaps for windows on the special workspace
113+
special_gappih=10
114+
special_gappiv=10
115+
special_gappoh=20
116+
special_gappov=20
117+
```

nix/default.nix

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,14 @@ stdenv.mkDerivation {
2828
pname = "mango";
2929
version = "nightly";
3030

31-
src = builtins.path {
32-
path = ../.;
31+
src = lib.cleanSourceWith {
32+
src = ../.;
33+
filter =
34+
path: type:
35+
let
36+
base = baseNameOf (toString path);
37+
in
38+
(lib.cleanSourceFilter path type) && base != "build" && base != "result";
3339
name = "source";
3440
};
3541

src/animation/client.h

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -60,18 +60,14 @@ struct fx_corner_radii set_client_corner_location(Client *c) {
6060
}
6161

6262
bool is_horizontal_stack_layout(Monitor *m) {
63-
if (m->pertag->curtag &&
64-
(m->pertag->ltidxs[m->pertag->curtag]->id == TILE ||
65-
m->pertag->ltidxs[m->pertag->curtag]->id == DECK))
66-
return true;
67-
return false;
63+
uint32_t tag = get_mon_curtag(m);
64+
return m->pertag->ltidxs[tag]->id == TILE ||
65+
m->pertag->ltidxs[tag]->id == DECK;
6866
}
6967

7068
bool is_horizontal_right_stack_layout(Monitor *m) {
71-
if (m->pertag->curtag &&
72-
m->pertag->ltidxs[m->pertag->curtag]->id == RIGHT_TILE)
73-
return true;
74-
return false;
69+
uint32_t tag = get_mon_curtag(m);
70+
return m->pertag->ltidxs[tag]->id == RIGHT_TILE;
7571
}
7672

7773
int32_t is_special_animation_rule(Client *c) {
@@ -542,7 +538,8 @@ void client_draw_split_border(Client *c, bool hit_no_border,
542538
if (c->iskilling || !c->mon || !client_surface(c)->mapped)
543539
return;
544540

545-
const Layout *layout = c->mon->pertag->ltidxs[c->mon->pertag->curtag];
541+
uint32_t tag = get_client_tag_idx(c);
542+
const Layout *layout = c->mon->pertag->ltidxs[tag];
546543

547544
if (hit_no_border || !ISTILED(c) || layout->id != DWINDLE ||
548545
!config.dwindle_manual_split || c->isfullscreen) {
@@ -553,7 +550,7 @@ void client_draw_split_border(Client *c, bool hit_no_border,
553550
return;
554551
}
555552

556-
DwindleNode **root = &c->mon->pertag->dwindle_root[c->mon->pertag->curtag];
553+
DwindleNode **root = &c->mon->pertag->dwindle_root[tag];
557554
DwindleNode *dnode = dwindle_find_leaf(*root, c);
558555
if (!dnode) {
559556
wlr_scene_node_set_enabled(&c->splitindicator[0]->node, false);
@@ -748,7 +745,7 @@ void client_set_drop_area(Client *c) {
748745
double rel_y = cursor->y - c->geom.y - bw;
749746

750747
struct wlr_box drop_box;
751-
const Layout *cur_layout = c->mon->pertag->ltidxs[c->mon->pertag->curtag];
748+
const Layout *cur_layout = c->mon->pertag->ltidxs[get_client_tag_idx(c)];
752749
bool dwindle_familiar =
753750
cur_layout->id == DWINDLE && config.dwindle_drop_simple_split;
754751

src/animation/tag.h

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ void set_arrange_visible(Monitor *m, Client *c, bool want_animation) {
4949
if (c->is_logic_hide)
5050
return;
5151

52+
bool was_enabled = c->scene->node.enabled;
53+
5254
if (!ISTILED(c) || (!c->is_clip_to_hide || !is_scroller_layout(c->mon))) {
5355
c->is_clip_to_hide = false;
5456
c->is_logic_hide = false;
@@ -58,7 +60,25 @@ void set_arrange_visible(Monitor *m, Client *c, bool want_animation) {
5860
wlr_scene_node_set_enabled(&c->scene_surface->node, true);
5961
}
6062

61-
if (!c->animation.tag_from_rule && want_animation &&
63+
/* Special workspace clients slide in from above the monitor */
64+
if (c->tags & TAG0_MASK) {
65+
c->animation.tag_from_rule = false;
66+
c->animation.tagouting = false;
67+
c->animation.tagouted = false;
68+
client_raise_group(c);
69+
if (want_animation && config.animations) {
70+
c->animation.tagining = true;
71+
c->animainit_geom = c->geom;
72+
c->animainit_geom.y = c->mon->m.y - c->geom.height;
73+
} else {
74+
c->animainit_geom.x = c->animation.current.x;
75+
c->animainit_geom.y = c->animation.current.y;
76+
}
77+
resize_apply(c, c->geom, (ResizeOpts){.skip_ov_enter_anim = true});
78+
return;
79+
}
80+
81+
if (!was_enabled && !c->animation.tag_from_rule && want_animation &&
6282
m->pertag->prevtag != 0 && m->pertag->curtag != 0 &&
6383
config.animations) {
6484
c->animation.tagining = true;
@@ -132,6 +152,28 @@ void set_arrange_hidden(Monitor *m, Client *c, bool want_animation) {
132152
return;
133153
}
134154

155+
/* Special workspace windows should animate out or hide when special
156+
* workspace is not active */
157+
if (c->tags & TAG0_MASK) {
158+
if (want_animation && config.animations && !c->animation.tagouted &&
159+
c->scene->node.enabled) {
160+
c->animation.tagouting = true;
161+
c->animation.tagining = false;
162+
c->pending = c->geom;
163+
c->pending.y = c->mon->m.y - c->geom.height;
164+
client_raise_group(c);
165+
resize(c, c->geom, 0);
166+
} else {
167+
c->animation.running = false;
168+
c->animation.tagouting = false;
169+
c->animation.tagining = false;
170+
wlr_scene_node_set_enabled(&c->scene->node, false);
171+
c->animainit_geom = c->current = c->pending = c->animation.current =
172+
c->geom;
173+
}
174+
return;
175+
}
176+
135177
if ((c->tags & (1 << (m->pertag->prevtag - 1))) &&
136178
m->pertag->prevtag != 0 && m->pertag->curtag != 0 &&
137179
config.animations) {

src/config/parse_config.h

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -437,10 +437,15 @@ typedef struct {
437437
uint32_t gappiv;
438438
uint32_t gappoh;
439439
uint32_t gappov;
440+
uint32_t special_gappih;
441+
uint32_t special_gappiv;
442+
uint32_t special_gappoh;
443+
uint32_t special_gappov;
440444
uint32_t borderpx;
441445
uint32_t group_bar_height;
442446
float scratchpad_width_ratio;
443447
float scratchpad_height_ratio;
448+
float special_dim;
444449
float rootcolor[4];
445450
float bordercolor[4];
446451
float dropcolor[4];
@@ -1177,8 +1182,11 @@ uint32_t parse_tag_mask(char *str) {
11771182
token = strtok_r(arg_copy, "|", &saveptr);
11781183

11791184
while (token != NULL) {
1185+
trim_whitespace(token);
11801186
int32_t num = atoi(token);
1181-
if (num > 0 && num <= tag_num_MAX) {
1187+
if (num == 0 && strcmp(token, "0") == 0) {
1188+
mask |= TAG0_MASK;
1189+
} else if (num > 0 && num <= tag_num_MAX) {
11821190
mask |= (1 << (num - 1));
11831191
}
11841192
token = strtok_r(NULL, "|", &saveptr);
@@ -1187,6 +1195,10 @@ uint32_t parse_tag_mask(char *str) {
11871195
free(arg_copy);
11881196
}
11891197

1198+
// tag0 and normal tags are exclusive; keep tag0 when mixed
1199+
if ((mask & TAG0_MASK) && (mask & TAGMASK))
1200+
mask = TAG0_MASK;
1201+
11901202
uint32_t result = 0;
11911203

11921204
if (mask) {
@@ -1390,7 +1402,6 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value,
13901402
func = minimized;
13911403
} else if (strcmp(func_name, "restore_minimized") == 0) {
13921404
func = restore_minimized;
1393-
(*arg).i = atoi(arg_value);
13941405
} else if (strcmp(func_name, "toggle_scratchpad") == 0) {
13951406
func = toggle_scratchpad;
13961407
} else if (strcmp(func_name, "toggle_render_border") == 0) {
@@ -1504,6 +1515,12 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value,
15041515
(*arg).v = strdup(arg_value);
15051516
(*arg).v2 = strdup(arg_value2);
15061517
(*arg).v3 = strdup(arg_value3);
1518+
} else if (strcmp(func_name, "toggle_special_tag") == 0) {
1519+
func = toggle_special_tag;
1520+
} else if (strcmp(func_name, "tag_special_tag") == 0) {
1521+
func = tag_special_tag;
1522+
} else if (strcmp(func_name, "tag_special_silent") == 0) {
1523+
func = tag_special_silent;
15071524
} else if (strcmp(func_name, "disable_monitor") == 0) {
15081525
func = disable_monitor;
15091526
(*arg).v = strdup(arg_value);
@@ -2265,6 +2282,16 @@ bool parse_option(Config *config, char *key, char *value, int line_number) {
22652282
config->scratchpad_width_ratio = atof(value);
22662283
} else if (strcmp(key, "scratchpad_height_ratio") == 0) {
22672284
config->scratchpad_height_ratio = atof(value);
2285+
} else if (strcmp(key, "special_dim") == 0) {
2286+
config->special_dim = atof(value);
2287+
} else if (strcmp(key, "special_gappih") == 0) {
2288+
config->special_gappih = atoi(value);
2289+
} else if (strcmp(key, "special_gappiv") == 0) {
2290+
config->special_gappiv = atoi(value);
2291+
} else if (strcmp(key, "special_gappoh") == 0) {
2292+
config->special_gappoh = atoi(value);
2293+
} else if (strcmp(key, "special_gappov") == 0) {
2294+
config->special_gappov = atoi(value);
22682295
} else if (strcmp(key, "borderpx") == 0) {
22692296
config->borderpx = atoi(value);
22702297
} else if (strcmp(key, "group_bar_height") == 0) {
@@ -4415,6 +4442,11 @@ void override_config(void) {
44154442
CLAMP_FLOAT(config.scratchpad_width_ratio, 0.1f, 1.0f);
44164443
config.scratchpad_height_ratio =
44174444
CLAMP_FLOAT(config.scratchpad_height_ratio, 0.1f, 1.0f);
4445+
config.special_dim = CLAMP_FLOAT(config.special_dim, 0.0f, 1.0f);
4446+
config.special_gappih = CLAMP_INT(config.special_gappih, 0, 1000);
4447+
config.special_gappiv = CLAMP_INT(config.special_gappiv, 0, 1000);
4448+
config.special_gappoh = CLAMP_INT(config.special_gappoh, 0, 1000);
4449+
config.special_gappov = CLAMP_INT(config.special_gappov, 0, 1000);
44184450
config.borderpx = CLAMP_INT(config.borderpx, 0, 200);
44194451
config.group_bar_height = CLAMP_INT(config.group_bar_height, 0, 500);
44204452
config.smartgaps = CLAMP_INT(config.smartgaps, 0, 1);
@@ -4515,6 +4547,11 @@ void set_value_default() {
45154547
config.gappov = 10;
45164548
config.scratchpad_width_ratio = 0.8f;
45174549
config.scratchpad_height_ratio = 0.9f;
4550+
config.special_dim = 0.5f;
4551+
config.special_gappih = 10;
4552+
config.special_gappiv = 10;
4553+
config.special_gappoh = 20;
4554+
config.special_gappov = 20;
45184555

45194556
config.scroller_structs = 20;
45204557
config.scroller_default_proportion = 0.9f;
@@ -5094,7 +5131,7 @@ void reapply_master(void) {
50945131

50955132
int32_t i;
50965133
Monitor *m = NULL;
5097-
for (i = 0; i <= config.tag_num; i++) {
5134+
for (i = 0; i < PERTAG_SLOTS; i++) {
50985135
wl_list_for_each(m, &mons, link) {
50995136
if (!m->wlr_output->enabled) {
51005137
continue;
@@ -5105,6 +5142,10 @@ void reapply_master(void) {
51055142
m->gappiv = config.gappiv;
51065143
m->gappoh = config.gappoh;
51075144
m->gappov = config.gappov;
5145+
m->special_gappih = config.special_gappih;
5146+
m->special_gappiv = config.special_gappiv;
5147+
m->special_gappoh = config.special_gappoh;
5148+
m->special_gappov = config.special_gappov;
51085149
}
51095150
}
51105151
}
@@ -5180,6 +5221,8 @@ void parse_tagrule(Monitor *m) {
51805221
// Set defaults for every tag.
51815222
for (i = 0; i <= config.tag_num; i++)
51825223
tag_slot_set_defaults(m, i);
5224+
// dedicated state slot for the all-tags view
5225+
tag_slot_set_defaults(m, PERTAG_ALL_TAGS_IDX);
51835226

51845227
for (i = 0; i < config.tag_rules_count; i++) {
51855228
const ConfigTagRule *tr = &config.tag_rules[i];

src/dispatch/bind_declare.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,6 @@ void dwindle_split_horizontal(const Arg *arg);
8989
void dwindle_split_vertical(const Arg *arg);
9090
void dwindle_toggle_current_split(const Arg *arg);
9191
void focusid(const Arg *arg);
92+
void toggle_special_tag(const Arg *arg);
93+
void tag_special_tag(const Arg *arg);
94+
void tag_special_silent(const Arg *arg);

0 commit comments

Comments
 (0)