Omahub
← All plugins
R

Workspace Tidy

by ronnie

Left click: defragment all workspaces. Right click: empty the active workspace onto the nearest empty one

Security review

No obvious issues detected

Deterministic scan — not a security guarantee

None
Risk level
None
Analyzed commit
2ba7bbd
Scanned
1 month ago

No potentially dangerous behavior detected in the analyzed commit.

Automated analysis only — not a security guarantee.

AI advisory review

No obvious issues detected

Language-model assessment · ~deepseek/deepseek-v4-flash-latest — advisory only

Low
AI risk level
Low
Recommendation
install
Model
~deepseek/deepseek-v4-flash-latest
Analyzed commit
2ba7bbd
Reviewed
1 month ago

The plugin is a bar widget that moves windows between Hyprland workspaces. It uses hyprctl dispatch with Lua dispatcher API, but validates all values with strict regex allowlists before embedding them, and bounds every hyprctl call with timeout. No destructive, persistent, or credential-related behavior was found; the only minor concern is the use of `eval` on jq output, but that data comes from hyprctl and is validated before use.

  • The scripts use `eval` on jq output (e.g., `eval "occupied_arr=(${occupied_line:-})"`). While the data originates from hyprctl and each element is validated against strict regexes before use, `eval` is generally discouraged and could be a risk if hyprctl output were ever compromised.
  • The plugin relies on the Lua dispatcher API (`hl.dsp.*`) which can execute arbitrary commands if values break out of their quoted positions; the scripts mitigate this with allowlist validation, but the risk is inherent to the API.
How this check works

This review combines the deterministic scan (the rule-based results above) with an independent look at the plugin's code by a language model. The model reads a trimmed sample of the repository's files, the manifest, and the README, then gives a plain-language risk level and a recommendation: install (no notable danger), review (look closer first), or avoid (clearly dangerous).

It runs on the same analyzed commit as the deterministic scan and is strictly advisory — it is not a security guarantee and never blocks a plugin by itself. A human moderator still reviews plugins before they are listed.

AI advisory only — automated analysis, not a security guarantee.

Install
$ omarchy plugin add https://github.com/Macs9319/omarchy-workspace-tidy --enable
Desktop #Hyprland #bar #workspaces

Workspace Tidy

An Omarchy bar widget plugin that cleans up cluttered Hyprland workspaces with one click: empty out the workspace you're on, or defragment every workspace at once.

Workspace Tidy icon in the bar

Behavior

  • Left click: Optimize all — packs every occupied workspace toward the lowest free numbers, closing empty gaps left behind by workspaces you've emptied out over time. Each workspace's windows stay grouped together and in relative order; only the workspace number they land on changes.
  • Right click: Tidy this workspace — moves every window on your current workspace to the nearest empty workspace (checking the next higher number before the next lower one at each distance), so the crowded workspace you're looking at becomes empty.

In both cases your view stays exactly where you started — the plugin restores focus to your original workspace once it's done moving things around.

Requirements

  • Hyprland with the Lua dispatcher API (hl.dsp.* — check with hyprctl repl 'return 1'; this landed in the Hyprland 0.56.x series). The plugin uses hl.dsp.window.move and hl.dsp.focus under the hood instead of the classic hyprctl dispatch movetoworkspacesilent ... string syntax.
  • jq (used to parse hyprctl -j output).

Installation

Via Omarchy Plugin Manager

omarchy plugin add https://github.com/Macs9319/omarchy-workspace-tidy.git --enable

Manual Installation

mkdir -p ~/.config/omarchy/plugins/
cp -r omarchy-workspace-tidy ~/.config/omarchy/plugins/workspace-tidy
omarchy plugin enable workspace-tidy --section right

Removal

Via Omarchy Plugin Manager

omarchy plugin remove workspace-tidy

Manual Removal

rm -rf ~/.config/omarchy/plugins/workspace-tidy
omarchy restart shell

Usage & IPC

  • Bar interaction: left-click to optimize all workspaces, right-click to tidy the active one.
  • CLI / keybinding: control the plugin via shell IPC, e.g. to wire it up as a Hyprland keybinding:
    omarchy-shell workspace-tidy tidy          # tidy the active workspace
    omarchy-shell workspace-tidy tidy 7        # tidy a specific workspace
    omarchy-shell workspace-tidy optimizeAll   # defragment every workspace
    

How it works

tidy.sh and optimize-all.sh read the current layout with hyprctl clients -j / hyprctl workspaces -j, then move windows individually with hyprctl dispatch 'hl.dsp.window.move({workspace = N, window = "address:0x..."})'. That dispatcher always switches your view to follow the moved window (there's no silent variant on this Hyprland version), so both scripts capture your active workspace before moving anything and explicitly re-focus it with hl.dsp.focus once every move is done.

An earlier version used hl.dsp.workspace.change_id to rename whole workspaces in one call instead of moving windows one by one — it worked, but the Omarchy bar's per-workspace window-count indicator never picked up windows that arrived via a rename, so occupied workspaces kept showing as empty. Moving windows individually fixes that, since it fires the events the bar actually listens for.

Every hyprctl dispatch call's stderr is checked explicitly — a rejected dispatch on this Hyprland version still exits 0 and only logs a warning:/error: line, so $? alone isn't enough to detect failure.

Security

  • No shell string-building. The QML side passes the workspace id argument to the scripts as its own Process.command array element (never interpolated into a shell string), and the scripts pass it on to hyprctl the same way — argv, not string concatenation.
  • Exact-format allowlisting, not sanity-checking. Every value that ends up embedded inside an hyprctl dispatch string (window addresses, workspace ids) is validated against a strict regex (^0x[0-9a-fA-F]+$ for addresses, ^[0-9]{1,6}$ for workspace ids) immediately before use, even though these values come from hyprctl's own JSON output and aren't attacker-controlled in normal operation. This matters more than usual here: hyprctl dispatch on this Hyprland version parses its argument as Lua source (hl.dispatch(...)), and that Lua environment exposes dispatchers that can run arbitrary commands (hl.dsp.exec_cmd/exec_raw) — so a value that could break out of its quoted position would be Lua/command injection into the compositor process, not just a shell-quoting bug.
  • Bounded execution. Every hyprctl call is wrapped in timeout 5, so a wedged compositor socket can't hang the script — or the bar widget, which stays "busy" until the process exits — indefinitely.
  • No privilege escalation. Nothing here calls sudo/pkexec, reads secrets, or writes outside the plugin's own IPC-triggered scope. hl.dsp.window.move always switches your view to the moved window (no silent variant found on this Hyprland version); both scripts explicitly restore your original focus afterward as a correctness measure, not a security one.

License

MIT