Skip to content

Commit eeced2d

Browse files
authored
Add hybrid cell style and icon strip to icons mode
2 parents e38c9c3 + 7ab52de commit eeced2d

5 files changed

Lines changed: 79 additions & 37 deletions

File tree

Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ config:
6868
echo "GRID_ROWS=2" >> ~/.config/spacemap/config; \
6969
echo "#CELL_STYLE=rects" >> ~/.config/spacemap/config; \
7070
echo "CELL_STYLE=icons" >> ~/.config/spacemap/config; \
71+
echo "#CELL_STYLE=hybrid" >> ~/.config/spacemap/config; \
7172
echo "Created ~/.config/spacemap/config with defaults (8x2, icons)"; \
7273
else \
7374
echo "Config already exists at ~/.config/spacemap/config"; \
@@ -80,5 +81,6 @@ distconfig:
8081
@echo "GRID_ROWS=2" >> ~/.config/spacemap/config
8182
@echo "#CELL_STYLE=rects" >> ~/.config/spacemap/config
8283
@echo "CELL_STYLE=icons" >> ~/.config/spacemap/config
84+
@echo "#CELL_STYLE=hybrid" >> ~/.config/spacemap/config
8385
@echo "Wrote ~/.config/spacemap/config"
8486
@cat ~/.config/spacemap/config

README.md

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,19 @@ A native macOS work`spacemap` that shows your yabai workspace grid on demand. Pr
44

55
You do not have to disable System Integrity Protection to get this setup.
66

7-
Example Configuration:
8-
```
9-
┌────┬────┬────┬────┬────┬────┬────┬────┐
10-
│ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ ← top row
11-
├────┼────┼────┼────┼────┼────┼────┼────┤
12-
│ 9 │ 10 │ 11 │ 12 │ 13 │ 14 │ 15 │ 16 │ ← bottom row
13-
└────┴────┴────┴────┴────┴────┴────┴────┘
14-
↑ active desktop highlighted in blue
15-
```
167
## Visualization
8+
Here I have 4 window open and spacemap is open `ctrl spacebar`
9+
Tou can see that we are on desktop 6 and there are 4 apps open.
10+
<img width="1793" height="943" alt="Screenshot 2026-05-11 at 1 50 18 PM" src="https://github.com/user-attachments/assets/b9f2dafc-d309-4c80-98be-8d0e1f19cc17" />
1711

18-
<img width="751" height="172" alt="Screenshot 2026-05-10 at 10 14 35 AM" src="https://github.com/user-attachments/assets/f7f2556a-9a1e-450e-bae4-2ad4268767cf" />
12+
## Configuration Examples
13+
14+
| spacemap screenshot | spacemap config | skhd config |
15+
|---------------------|-----------------|-------------|
16+
| <img width="711" height="136" alt="Screenshot 2026-05-11 at 1 20 00 PM" src="https://github.com/user-attachments/assets/bd9958db-07ee-466b-934c-e12d28ebd0a3" /> | <img width="195" height="75" alt="Screenshot 2026-05-11 at 1 42 02 PM" src="https://github.com/user-attachments/assets/4d493154-e1b2-4772-a408-639da7e3abe1" /> | - |
17+
| <img width="713" height="137" alt="Screenshot 2026-05-11 at 1 19 31 PM" src="https://github.com/user-attachments/assets/d298e386-6bc2-4ac3-8ba0-7184e2f072a5" /> | <img width="204" height="76" alt="Screenshot 2026-05-11 at 1 41 03 PM" src="https://github.com/user-attachments/assets/1a911ef2-5cb7-4746-87d1-92dddb9ec8b4" /> | - |
18+
| <img width="714" height="139" alt="Screenshot 2026-05-11 at 1 40 41 PM" src="https://github.com/user-attachments/assets/a4595e8b-2494-4d3b-b8cc-fcc41a4daf61" /> | <img width="199" height="76" alt="Screenshot 2026-05-11 at 1 42 45 PM" src="https://github.com/user-attachments/assets/522bc241-8d8a-4221-aad1-cc6e2e20a500" /> | - |
19+
| <img width="364" height="247" alt="Screenshot 2026-05-11 at 1 18 47 PM" src="https://github.com/user-attachments/assets/2eddfbc2-74a9-41dd-9d9a-cdba4f537232" /> | <img width="204" height="77" alt="Screenshot 2026-05-11 at 1 44 32 PM" src="https://github.com/user-attachments/assets/883414bb-b983-47e4-99bf-f46dd5484005" /> | - |
1920

2021

2122
Window positions are drawn as colored rectangles inside each cell (one color per app, derived from app name). The active cell updates instantly as you switch desktops, driven by yabai's `space_changed` signal.
@@ -24,8 +25,9 @@ Window positions are drawn as colored rectangles inside each cell (one color per
2425

2526
- macOS 13+
2627
- [yabai](https://github.com/koekeishiya/yabai) installed at `/opt/homebrew/bin/yabai` and running
28+
- [skhd](https://github.com/asmvik/skhd) installed at `/opt/homebrew/bin/skhd` and running
2729
- Xcode Command Line Tools (`xcode-select --install`)
28-
- Accessibility permission (prompted on first launch)
30+
- Accessibility permission (prompted on first launch). This is not the same as disabling SIP protection, which is not required.
2931
<img width="722" height="844" alt="Screenshot 2026-05-10 at 10 58 32 AM" src="https://github.com/user-attachments/assets/b6d90393-2100-44bc-ac6f-00fe1de66a98" />
3032

3133

@@ -59,21 +61,15 @@ Change these to match your yabai space layout. A 4×4 grid would be `GRID_COLS=4
5961
| Value | Description |
6062
|-------|-------------|
6163
| `rects` | Colored rectangles scaled from real window geometry (default) |
62-
| `icons` | App icons positioned at each window's scaled location |
64+
| `icons` | App icons at each visible window's scaled position; all apps on the workspace shown as a small icon strip at the bottom |
65+
| `hybrid` | Colored rectangles (like `rects`) plus a small icon strip at the bottom showing all apps on the workspace |
6366

6467
```bash
6568
CELL_STYLE=rects # default
6669
CELL_STYLE=icons
70+
CELL_STYLE=hybrid
6771
```
6872

69-
**`CELL_STYLE=rects`**
70-
71-
<img width="752" height="476" alt="rects style" src="https://github.com/user-attachments/assets/1ee3e85c-12e4-4f34-a265-cb9f9fd69b56" />
72-
73-
**`CELL_STYLE=icons`**
74-
75-
<img width="718" height="464" alt="icons style" src="https://github.com/user-attachments/assets/5d35aa23-d5ef-4da1-8a74-df7278b43112" />
76-
7773
Run `make distconfig` to write a fresh config with `icons` active and `rects` commented out.
7874

7975
## Usage

Sources/spacemap/CellView.swift

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,16 @@ struct CellView: View {
1818

1919
ForEach(windows, id: \.id) { window in
2020
switch cellStyle {
21-
case .rects: windowRect(window)
22-
case .icons: windowIcon(window)
21+
case .rects: windowRect(window)
22+
case .icons: windowIcon(window)
23+
case .hybrid: windowRect(window)
2324
}
2425
}
2526

27+
if cellStyle == .icons || cellStyle == .hybrid {
28+
iconStrip()
29+
}
30+
2631
Text("\(spaceIndex)")
2732
.font(.system(size: 10, weight: .medium, design: .monospaced))
2833
.foregroundColor(isFocused ? Color(hex: 0x4a9eff) : .white.opacity(0.4))
@@ -53,23 +58,49 @@ struct CellView: View {
5358

5459
@ViewBuilder
5560
private func windowIcon(_ window: YabaiWindow) -> some View {
56-
let scaleX = cellSize.width / displayBounds.width
57-
let scaleY = cellSize.height / displayBounds.height
58-
let x = (window.cgFrame.minX - displayBounds.minX) * scaleX
59-
let y = (window.cgFrame.minY - displayBounds.minY) * scaleY
60-
let w = max(window.cgFrame.width * scaleX, 14)
61-
let h = max(window.cgFrame.height * scaleY, 14)
62-
let iconSize = min(w, h)
61+
if !window.isHidden && !window.isMinimized {
62+
let scaleX = cellSize.width / displayBounds.width
63+
let scaleY = cellSize.height / displayBounds.height
64+
let x = (window.cgFrame.minX - displayBounds.minX) * scaleX
65+
let y = (window.cgFrame.minY - displayBounds.minY) * scaleY
66+
let w = max(window.cgFrame.width * scaleX, 14)
67+
let h = max(window.cgFrame.height * scaleY, 14)
68+
let iconSize = min(w, h)
6369

64-
if let icon = appIcon(for: window.app) {
65-
Image(nsImage: icon)
66-
.resizable()
67-
.aspectRatio(contentMode: .fit)
68-
.frame(width: iconSize, height: iconSize)
69-
.offset(x: x, y: y)
70+
if let icon = appIcon(for: window.app) {
71+
Image(nsImage: icon)
72+
.resizable()
73+
.aspectRatio(contentMode: .fit)
74+
.frame(width: iconSize, height: iconSize)
75+
.offset(x: x, y: y)
76+
}
7077
}
7178
}
7279

80+
@ViewBuilder
81+
private func iconStrip() -> some View {
82+
let icons = uniqueIconWindows()
83+
HStack(spacing: 2) {
84+
ForEach(icons, id: \.id) { window in
85+
if let icon = appIcon(for: window.app) {
86+
Image(nsImage: icon)
87+
.resizable()
88+
.aspectRatio(contentMode: .fit)
89+
.frame(width: 12, height: 12)
90+
}
91+
}
92+
}
93+
.frame(maxWidth: .infinity, alignment: .leading)
94+
.padding(.horizontal, 3)
95+
.frame(maxHeight: .infinity, alignment: .bottom)
96+
.padding(.bottom, 3)
97+
}
98+
99+
private func uniqueIconWindows() -> [YabaiWindow] {
100+
var seen = Set<String>()
101+
return windows.filter { seen.insert($0.app).inserted }
102+
}
103+
73104
private func appIcon(for appName: String) -> NSImage? {
74105
NSWorkspace.shared.runningApplications
75106
.first { $0.localizedName == appName }

Sources/spacemap/ConfigReader.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@ enum ConfigReader {
2020
switch key {
2121
case "GRID_COLS": cols = Int(value) ?? cols
2222
case "GRID_ROWS": rows = Int(value) ?? rows
23-
case "CELL_STYLE": cellStyle = value == "icons" ? .icons : .rects
23+
case "CELL_STYLE":
24+
switch value {
25+
case "icons": cellStyle = .icons
26+
case "hybrid": cellStyle = .hybrid
27+
default: cellStyle = .rects
28+
}
2429
default: break
2530
}
2631
}

Sources/spacemap/Models.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import Foundation
22
import CoreGraphics
33

4-
enum CellStyle { case rects, icons }
4+
enum CellStyle { case rects, icons, hybrid }
55

66
struct GridConfig {
77
var cols: Int
@@ -28,6 +28,8 @@ struct YabaiWindow: Decodable {
2828
let app: String
2929
let space: Int
3030
let frame: WindowFrame
31+
let isHidden: Bool
32+
let isMinimized: Bool
3133

3234
struct WindowFrame: Decodable {
3335
let x: CGFloat
@@ -36,6 +38,12 @@ struct YabaiWindow: Decodable {
3638
let h: CGFloat
3739
}
3840

41+
enum CodingKeys: String, CodingKey {
42+
case id, app, space, frame
43+
case isHidden = "is-hidden"
44+
case isMinimized = "is-minimized"
45+
}
46+
3947
var cgFrame: CGRect {
4048
CGRect(x: frame.x, y: frame.y, width: frame.w, height: frame.h)
4149
}

0 commit comments

Comments
 (0)