How I patched the COSMIC compositor: a 5×5 workspace grid for Pop!_OS
Context
My workspaces are a map, not a scroll. One 4-finger swipe moves me north, south, east, or west across a 5×5 grid that wraps at the edges, and login drops me on the center cell — so no workspace is ever more than two gestures away, and my hands navigate it without looking. COSMIC, the desktop this runs on, is System76's Rust/Wayland desktop environment for Pop!_OS, built on the Smithay compositor library and led by Victoria Brekenfeld (Drakulix). In stock COSMIC 1.0.0, workspaces live on a linear strip: a flat Vec<Workspace> that only supports vertical or horizontal movement. I wanted the bounded 5×5 grid with edge-wrapping that starts on the center cell.
Approach
I patched two crates: cosmic-comp, the compositor itself, and cosmic-workspaces, the overview app. Both are pinned to the exact versions installed on my machine (cosmic-comp at commit bb584aa, cosmic-workspaces at 1.0.12), and both carry a branch named cosmic-grid. The feature turns on with one line in the user RON config: workspace_grid: Some((5, 5)).
The intellectual core of the patch is that a grid is a row-major mapping, not a workspace-model rewrite. idx = row * cols + col over the existing flat Vec. cosmic-comp already exposes 2D workspace coordinates end-to-end: set_workspace_coordinates emits the ext-workspace protocol Coordinates event, which cctk surfaces as WorkspaceInfo.coordinates, which cosmic-workspaces renders. The grid only changes how those coordinates are computed and rendered. Everything upstream of that pipeline stays stock.
The config decision was to add workspace_grid: Option<(u32, u32)> with #[serde(default)] to cosmic-comp-config/src/workspace.rs. An optional field, not a new WorkspaceLayout enum variant, so stock readers like cosmic-settings and the applets ignore it. No other package needed rebuilding or pinning.
Gesture semantics preserve the existing natural-scroll convention exactly. Up/Down swipe means ±cols, Left/Right means ±1 over the flat Vec. Up/down muscle memory is unchanged; left/right is additive. Each axis wraps around independently. Login starts at the center cell (rows/2, cols/2). Workspaces are still created on demand and removed when empty, so RAM behavior matches stock.
The build and rollback story was designed for safety from day one. scripts/build.sh does cargo release builds. scripts/install.sh backs up the stock binaries to stock/, installs the patched binaries to /usr/bin/, and runs apt-mark hold on both packages so a system update can't silently overwrite the patch. scripts/rollback.sh restores the archived originals and releases the holds. Reverting is one command. The archive is real: stock/cosmic-comp.orig at 27.3 MB and stock/cosmic-workspaces.orig at 30.2 MB.
Architecture
graph LR
A[4-finger swipe / Super+Arrow] --> B[cosmic-comp input<br/>input/mod.rs + input/actions.rs]
B --> C{Direction → delta<br/>Up/Down = ±cols, Left/Right = ±1}
C --> D[flat Vec<Workspace><br/>idx = row*cols + col]
D --> E[set_workspace_coordinates<br/>[row, col]]
E --> F[ext-workspace protocol<br/>Coordinates event]
F --> G[cosmic-workspaces<br/>2D grid sidebar]
The same safety-first thinking shaped the install path:
graph LR
A[scripts/build.sh<br/>cargo release build] --> B[scripts/install.sh<br/>backup to stock/ + install to /usr/bin/]
B --> C[apt-mark hold<br/>cosmic-comp + cosmic-workspaces]
C --> D[patched binaries live]
D --> E[scripts/rollback.sh<br/>restore stock/ + release holds]
Evidence
The patch branch cosmic-grid on cosmic-comp carries two commits on top of the pin:
-
dbe7678"Add optional 2D workspace grid mode (workspace_grid config)": 319 insertions, 28 deletions across 6 files (cosmic-comp-config/src/workspace.rs +12; src/input/actions.rs +137; src/input/gestures/mod.rs +7; src/input/mod.rs +83; src/shell/mod.rs +104; src/shell/workspace.rs +4) -
54f7719"Animate workspace swipes along the gesture axis (grid mode) instead of the layout axis": 67 insertions, 22 deletions across 4 files (src/input/actions.rs, src/input/mod.rs, src/shell/focus/order.rs, src/shell/mod.rs)
Total: 384 insertions, 48 deletions across 7 files. Roughly 432 lines of Rust to add a workspace grid to a compositor.
cosmic-workspaces carries two commits of its own: e021ea8 "Render workspace sidebar as a 2D grid when workspace_grid is set" (src/view/mod.rs: 135 insertions, 14 deletions) and 71e81b8 "Fix grid sidebar build: resize_with for non-Clone cells, Space::new spacer". A [patch."https://github.com/pop-os/cosmic-comp"] section in its Cargo.toml points cosmic-comp-config at the local patched copy.
The grid mode is enabled in ~/.config/cosmic/com.system76.CosmicComp/v1/workspaces:
(
workspace_mode: OutputBound,
workspace_layout: Vertical,
action_on_typing: r#None,
workspace_wraparound: true,
workspace_grid: Some((5, 5)),
)
Keyboard navigation is rebound in ~/.config/cosmic/com.system76.CosmicSettings.Shortcuts/v1/custom (custom bindings override defaults):
( modifiers: [Super], key: "Left", description: Some("grid: workspace left") ): PreviousWorkspace,
( modifiers: [Super], key: "Right", description: Some("grid: workspace right") ): NextWorkspace,
( modifiers: [Super], key: "Up", description: Some("grid: workspace up") ): PreviousWorkspace,
( modifiers: [Super], key: "Down", description: Some("grid: workspace down") ): NextWorkspace,
For grid movement, Super+Left/Up map to PreviousWorkspace and Super+Right/Down to NextWorkspace, because the patched code infers grid direction from the key direction plus the natural-scroll convention. Window-focus navigation stays on Super+h/j/k/l.
Putting it together, here's the full control surface:
| Action | Input |
|---|---|
| Move to neighbor cell (4 directions) | 4-finger swipe / Super+Arrow |
| Open workspace overview | Super+W |
| Move window to neighbor cell | drag window into a cell in overview |
| Switch to cell 1-9 | Super+1…9 (top row, left-to-right; cells created on demand) |
I've been running this grid as my daily driver for about a month now. Navigation genuinely beats Alt+Tab: when I lose track of which window is where, one Super+W glance at the grid answers it. No compositor crashes in that time. The one recurring hiccup is sudo apt update — the held packages (by design) surface a warning I haven't fully looked into yet.
What went wrong
Two things, both real. First, the swipe animation. In the first version of the patch, the slide axis followed the workspace layout (vertical) instead of the gesture direction. Horizontal swipes landed on the right cell but animated vertically, which felt broken. The second commit, 54f7719, fixed it by animating along the gesture axis. That's the honest iteration story: v1 shipped with the flaw, v1.1 fixed it.
Second, the keyboard-grid edge case. workspace_mode: Global isn't grid-aware, so the grid only works in OutputBound mode, which is the default. I documented it as a limitation rather than fixing it. That was a deliberate scope cut: the Global path uses a different coordinate model, and making it grid-aware wasn't worth the cost for a feature I use in the default mode.
Known limitations
- Super+Shift+Arrows (move window) still uses linear next/previous, not the grid.
- Drag-reorder of workspace cells is disabled; windows can still be dragged into cells.
-
workspace_mode: Globalisn't grid-aware.OutputBound, the default, works.
Lessons learned
- A grid is a coordinate mapping, not a data structure. Reusing the flat
Vecand changing only the coordinate math kept the patch to roughly 432 lines. - Optional config fields with
#[serde(default)]are a cheap compatibility contract. Stock readers ignore the field, so nothing else needs rebuilding. - Make rollback trivial before installing anything.
apt-mark holdplus archived originals means a bad patch costs one command to undo. - Pin to the installed version. Patching against a moving target turns a small diff into a merge problem.
- Ship the fix for the visible flaw. The animation bug was the difference between "it works" and "it feels right."
Links
- Repo: Shaarkymoo/cosmic-grid — design spec, build/install/rollback scripts, pinned submodule refs
- Patch commits:
dbe7678+54f7719on my fork of cosmic-comp,e021ea8+71e81b8on my fork of cosmic-workspaces-epoch, carried on thecosmic-gridbranch of each - COSMIC: System76's Rust/Wayland desktop environment
- Smithay: the Rust Wayland compositor library
- Victoria Brekenfeld (Drakulix): COSMIC lead
- Pop!_OS
I'm open to Software Engineer and SecDevOps roles.

Top comments (1)
The
workspace_grid: Option<(u32, u32)>with#[serde(default)]is the elegant call here — no new enum variant means cosmic-settings and the applets keep reading stock configs without you rebuilding or pinning a single package. Same for the apt-mark hold plus the rollback archive: that is the safety story most downstream compositor patches never bother writing down.My question is about the rebase treadmill. 137 added lines in
input/actions.rssits right where System76 keeps touching gesture handling. How much churn are you seeing per cosmic-comp release, and does the cosmic-grid branch survive on its own or do you re-resolve conflicts every time?