<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Shaarav Agarwal</title>
    <description>The latest articles on DEV Community by Shaarav Agarwal (@shaarkymoo).</description>
    <link>https://dev.to/shaarkymoo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4123829%2F2dc96570-4887-4646-8f99-77525b442661.png</url>
      <title>DEV Community: Shaarav Agarwal</title>
      <link>https://dev.to/shaarkymoo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shaarkymoo"/>
    <language>en</language>
    <item>
      <title>Grid workspace for the COSMIC compositor</title>
      <dc:creator>Shaarav Agarwal</dc:creator>
      <pubDate>Mon, 14 Sep 2026 22:53:03 +0000</pubDate>
      <link>https://dev.to/shaarkymoo/grid-workspace-for-the-cosmic-compositor-4g5f</link>
      <guid>https://dev.to/shaarkymoo/grid-workspace-for-the-cosmic-compositor-4g5f</guid>
      <description>&lt;h1&gt;
  
  
  How I patched the COSMIC compositor: a 5×5 workspace grid for Pop!_OS
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;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 &lt;code&gt;Vec&amp;lt;Workspace&amp;gt;&lt;/code&gt; that only supports vertical or horizontal movement. I wanted the bounded 5×5 grid with edge-wrapping that starts on the center cell.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approach
&lt;/h2&gt;

&lt;p&gt;I patched two crates: &lt;code&gt;cosmic-comp&lt;/code&gt;, the compositor itself, and &lt;code&gt;cosmic-workspaces&lt;/code&gt;, the overview app. Both are pinned to the exact versions installed on my machine (cosmic-comp at commit &lt;code&gt;bb584aa&lt;/code&gt;, cosmic-workspaces at 1.0.12), and both carry a branch named &lt;code&gt;cosmic-grid&lt;/code&gt;. The feature turns on with one line in the user RON config: &lt;code&gt;workspace_grid: Some((5, 5))&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The intellectual core of the patch is that a grid is a row-major mapping, not a workspace-model rewrite. &lt;code&gt;idx = row * cols + col&lt;/code&gt; over the existing flat &lt;code&gt;Vec&lt;/code&gt;. cosmic-comp already exposes 2D workspace coordinates end-to-end: &lt;code&gt;set_workspace_coordinates&lt;/code&gt; emits the ext-workspace protocol &lt;code&gt;Coordinates&lt;/code&gt; event, which cctk surfaces as &lt;code&gt;WorkspaceInfo.coordinates&lt;/code&gt;, which cosmic-workspaces renders. The grid only changes how those coordinates are computed and rendered. Everything upstream of that pipeline stays stock.&lt;/p&gt;

&lt;p&gt;The config decision was to add &lt;code&gt;workspace_grid: Option&amp;lt;(u32, u32)&amp;gt;&lt;/code&gt; with &lt;code&gt;#[serde(default)]&lt;/code&gt; to &lt;code&gt;cosmic-comp-config/src/workspace.rs&lt;/code&gt;. An optional field, not a new &lt;code&gt;WorkspaceLayout&lt;/code&gt; enum variant, so stock readers like cosmic-settings and the applets ignore it. No other package needed rebuilding or pinning.&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;(rows/2, cols/2)&lt;/code&gt;. Workspaces are still created on demand and removed when empty, so RAM behavior matches stock.&lt;/p&gt;

&lt;p&gt;The build and rollback story was designed for safety from day one. &lt;code&gt;scripts/build.sh&lt;/code&gt; does cargo release builds. &lt;code&gt;scripts/install.sh&lt;/code&gt; backs up the stock binaries to &lt;code&gt;stock/&lt;/code&gt;, installs the patched binaries to &lt;code&gt;/usr/bin/&lt;/code&gt;, and runs &lt;code&gt;apt-mark hold&lt;/code&gt; on both packages so a system update can't silently overwrite the patch. &lt;code&gt;scripts/rollback.sh&lt;/code&gt; restores the archived originals and releases the holds. Reverting is one command. The archive is real: &lt;code&gt;stock/cosmic-comp.orig&lt;/code&gt; at 27.3 MB and &lt;code&gt;stock/cosmic-workspaces.orig&lt;/code&gt; at 30.2 MB.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;



&lt;pre data-lang="mermaid"&gt;&lt;code&gt;graph LR
    A[4-finger swipe / Super+Arrow] --&amp;gt; B[cosmic-comp input&amp;lt;br/&amp;gt;input/mod.rs + input/actions.rs]
    B --&amp;gt; C{Direction → delta&amp;lt;br/&amp;gt;Up/Down = ±cols, Left/Right = ±1}
    C --&amp;gt; D[flat Vec&amp;amp;lt;Workspace&amp;amp;gt;&amp;lt;br/&amp;gt;idx = row*cols + col]
    D --&amp;gt; E[set_workspace_coordinates&amp;lt;br/&amp;gt;[row, col]]
    E --&amp;gt; F[ext-workspace protocol&amp;lt;br/&amp;gt;Coordinates event]
    F --&amp;gt; G[cosmic-workspaces&amp;lt;br/&amp;gt;2D grid sidebar]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;The same safety-first thinking shaped the install path:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;graph LR
    A[scripts/build.sh&amp;lt;br/&amp;gt;cargo release build] --&amp;gt; B[scripts/install.sh&amp;lt;br/&amp;gt;backup to stock/ + install to /usr/bin/]
    B --&amp;gt; C[apt-mark hold&amp;lt;br/&amp;gt;cosmic-comp + cosmic-workspaces]
    C --&amp;gt; D[patched binaries live]
    D --&amp;gt; E[scripts/rollback.sh&amp;lt;br/&amp;gt;restore stock/ + release holds]&lt;/code&gt;&lt;/pre&gt;



&lt;h2&gt;
  
  
  Evidence
&lt;/h2&gt;

&lt;p&gt;The patch branch &lt;code&gt;cosmic-grid&lt;/code&gt; on cosmic-comp carries two commits on top of the pin:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;dbe7678&lt;/code&gt; "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)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;54f7719&lt;/code&gt; "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)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Total: 384 insertions, 48 deletions across 7 files. Roughly 432 lines of Rust to add a workspace grid to a compositor.&lt;/p&gt;

&lt;p&gt;cosmic-workspaces carries two commits of its own: &lt;code&gt;e021ea8&lt;/code&gt; "Render workspace sidebar as a 2D grid when workspace_grid is set" (src/view/mod.rs: 135 insertions, 14 deletions) and &lt;code&gt;71e81b8&lt;/code&gt; "Fix grid sidebar build: resize_with for non-Clone cells, Space::new spacer". A &lt;code&gt;[patch."https://github.com/pop-os/cosmic-comp"]&lt;/code&gt; section in its Cargo.toml points &lt;code&gt;cosmic-comp-config&lt;/code&gt; at the local patched copy.&lt;/p&gt;

&lt;p&gt;The grid mode is enabled in &lt;code&gt;~/.config/cosmic/com.system76.CosmicComp/v1/workspaces&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(
    workspace_mode: OutputBound,
    workspace_layout: Vertical,
    action_on_typing: r#None,
    workspace_wraparound: true,
    workspace_grid: Some((5, 5)),
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keyboard navigation is rebound in &lt;code&gt;~/.config/cosmic/com.system76.CosmicSettings.Shortcuts/v1/custom&lt;/code&gt; (custom bindings override defaults):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;( 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,
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Putting it together, here's the full control surface:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Move to neighbor cell (4 directions)&lt;/td&gt;
&lt;td&gt;4-finger swipe / Super+Arrow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open workspace overview&lt;/td&gt;
&lt;td&gt;Super+W&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move window to neighbor cell&lt;/td&gt;
&lt;td&gt;drag window into a cell in overview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Switch to cell 1-9&lt;/td&gt;
&lt;td&gt;Super+1…9 (top row, left-to-right; cells created on demand)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmy5d1u59ku9lwvukz4c2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmy5d1u59ku9lwvukz4c2.png" alt="5×5 workspace grid overview (Super+W)" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;sudo apt update&lt;/code&gt; — the held packages (by design) surface a warning I haven't fully looked into yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  What went wrong
&lt;/h2&gt;

&lt;p&gt;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, &lt;code&gt;54f7719&lt;/code&gt;, fixed it by animating along the gesture axis. That's the honest iteration story: v1 shipped with the flaw, v1.1 fixed it.&lt;/p&gt;

&lt;p&gt;Second, the keyboard-grid edge case. &lt;code&gt;workspace_mode: Global&lt;/code&gt; isn't grid-aware, so the grid only works in &lt;code&gt;OutputBound&lt;/code&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Known limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Super+Shift+Arrows (move window) still uses linear next/previous, not the grid.&lt;/li&gt;
&lt;li&gt;Drag-reorder of workspace cells is disabled; windows can still be dragged into cells.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;workspace_mode: Global&lt;/code&gt; isn't grid-aware. &lt;code&gt;OutputBound&lt;/code&gt;, the default, works.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Lessons learned
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A grid is a coordinate mapping, not a data structure. Reusing the flat &lt;code&gt;Vec&lt;/code&gt; and changing only the coordinate math kept the patch to roughly 432 lines.&lt;/li&gt;
&lt;li&gt;Optional config fields with &lt;code&gt;#[serde(default)]&lt;/code&gt; are a cheap compatibility contract. Stock readers ignore the field, so nothing else needs rebuilding.&lt;/li&gt;
&lt;li&gt;Make rollback trivial before installing anything. &lt;code&gt;apt-mark hold&lt;/code&gt; plus archived originals means a bad patch costs one command to undo.&lt;/li&gt;
&lt;li&gt;Pin to the installed version. Patching against a moving target turns a small diff into a merge problem.&lt;/li&gt;
&lt;li&gt;Ship the fix for the visible flaw. The animation bug was the difference between "it works" and "it feels right."&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/Shaarkymoo/cosmic-grid" rel="noopener noreferrer"&gt;Shaarkymoo/cosmic-grid&lt;/a&gt; — design spec, build/install/rollback scripts, pinned submodule refs&lt;/li&gt;
&lt;li&gt;Patch commits: &lt;code&gt;dbe7678&lt;/code&gt; + &lt;code&gt;54f7719&lt;/code&gt; on &lt;a href="https://github.com/Shaarkymoo/cosmic-comp" rel="noopener noreferrer"&gt;my fork of cosmic-comp&lt;/a&gt;, &lt;code&gt;e021ea8&lt;/code&gt; + &lt;code&gt;71e81b8&lt;/code&gt; on &lt;a href="https://github.com/Shaarkymoo/cosmic-workspaces-epoch" rel="noopener noreferrer"&gt;my fork of cosmic-workspaces-epoch&lt;/a&gt;, carried on the &lt;code&gt;cosmic-grid&lt;/code&gt; branch of each&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/pop-os/cosmic-comp" rel="noopener noreferrer"&gt;COSMIC&lt;/a&gt;: System76's Rust/Wayland desktop environment&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Smithay/smithay" rel="noopener noreferrer"&gt;Smithay&lt;/a&gt;: the Rust Wayland compositor library&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Drakulix" rel="noopener noreferrer"&gt;Victoria Brekenfeld (Drakulix)&lt;/a&gt;: COSMIC lead&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pop.system76.com/" rel="noopener noreferrer"&gt;Pop!_OS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm open to Software Engineer and SecDevOps roles.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>linux</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
