The stock Omarchy command menu (SUPER + SPACE), with
one thing added: a Menu Look row under Style that resizes this menu, rounds
its corners, sets its border width and its transparency — live, as you drag —
kept across theme switches, and without touching the bar, panels, or
anything else. A theme that wants a different shape can still keep its own.
The Menu Look editor — every knob previews on the menu itself as you drag:
Untouched, it's the built-in menu exactly — just with one extra row:
- The menu feels a touch small (or large) for your monitor and you want it a little bigger without scaling the whole shell up.
- You run a light theme and a dark theme and want the menu shaped slightly differently in each.
- You just want rounder corners, a thicker outline, or a see-through menu.
If not, this is a faithful clone of the built-in menu — installing it changes nothing until you touch a slider.
The menu's corner radius, font size and border width all come from the shell's
global style tokens (Style.cornerRadius, Style.font.*, …) — the same
ones the bar and every panel read. Nothing lets a plugin restyle only the
menu from the outside. The supported way to get menu-only controls is to own
the menu: omarchy plugin clone omarchy.menu, which is what this is.
That means Menu.qml is a fork. The diff against the built-in menu is kept
small and to a few clearly marked seams (see What's changed below) so
re-merging upstream changes after an omarchy update stays easy. MenuModel.js
and BarWidget.qml are byte-for-byte the built-in menu.
- Omarchy 4 with its Quickshell-based shell.
python3— used for the bounded,O_NOFOLLOWread and write of this plugin's own small state file (see Notes for reviewers). Present on a normal Omarchy install.
No network. No elevated privileges. No packages.
omarchy plugin add https://github.com/Deunnis/OmaMenu.git --enable
omarchy restart shellEnabling it replaces the built-in menu; the SUPER + SPACE binding and every
omarchy.menu IPC call route here automatically (that's what clonedFrom in
the manifest does).
Open the menu → Style → Menu Look. Drag a slider and the menu restyles instantly:
| Knob | Range | Default |
|---|---|---|
| Size | 0.8×–1.5× | 1.0× |
| Corner roundness | 0–24 px | the theme's decoration:rounding |
| Border width | 0–6 px | the theme's menu border width |
| Transparency | 0–90 % | 0 % (opaque) |
- Applies to — All themes (the default). One shared look that stays put
when the theme changes, however it changes:
omarchy theme set, the picker, or a rotator like OmaShuffle. - Applies to — Only scopes the knobs to the active theme instead, for when a light theme and a dark theme want different shapes. That theme then ignores the shared look; every other theme keeps following it. Switching the scope re-files the current values, it doesn't change them.
- Back: the
‹at the top of the editor, orEsc/←. - Reset clears whichever entry the scope points at: in All themes the four values go back to "inherit from the shell theme"; in Only the override is dropped and the theme rejoins the shared look.
One file: ~/.local/state/omarchy/io.github.omamenu/style.json, a small JSON
object keyed by theme slug, plus "*" for the shared look:
{
"*": { "scale": 1.15, "cornerRadius": 14, "borderWidth": 2, "transparency": 15 },
"rose-pine": { "scale": 1.0, "cornerRadius": -1, "borderWidth": -1, "transparency": 0 }
}A theme's own entry wins where it exists, "*" applies everywhere else, and
-1 means "inherit from the theme". "*" can't collide with a real theme —
slugs are validated against /^[a-z0-9][a-z0-9._-]*$/ before being used as a
key. Delete the file for a clean slate; nothing else is stored anywhere.
All in Menu.qml, at marked seams:
cornerRadiusand the card's border width now read the effective (config-or-inherit) value instead ofStyle.*directly.- The card gets a
scaletransform and a background-alpha override for the Size and Transparency knobs. - A
kind: "action"Menu Look row is injected under Style and intercepted inactivateIndex()to openMenuLookEditor.qmlin place of the row list;Esc/←in that view route togoBack(). - Bounded-reader / bounded-writer
Processhelpers forstyle.jsonand the active-theme name, plus oneFileViewused purely as a change notifier for the theme name.
MenuLookEditor.qml is the only new UI file. MenuModel.js and
BarWidget.qml are unchanged.
- No network, no privileged calls, installs nothing. The only subprocesses
are
timeout … python3 -chelpers for reading and writing this plugin's own state file. - Every file read goes through a descriptor-pinned, byte-capped, deadlined
helper — never a
FileView.text().style.jsonand~/.local/state/omarchy/current/theme.nameboth live under~/.local/statewhere another local process could plant a symlink, a FIFO, or an oversized file first. Each read:timeout -k 2 5bounds wall time,O_RDONLY | O_NOFOLLOW | O_NONBLOCKrefuses a symlink and never blocks on a FIFO, anfstaton that descriptor requires a regular file, and the read stops atlimit + 1bytes (64 KiB forstyle.json, 256 for the theme name). - The write helper creates the state dir then re-checks it is a real
directory owned by this uid (not a symlink swap), refuses to proceed if the
target already exists as anything but a regular file, and lands the payload
through an
O_EXCLmkstempin that same directory followed byos.replace()— which renames the link itself and never writes through it. - The theme name is only ever used as a JSON object key; it's
^[a-z0-9][a-z0-9._-]*$-validated and length-capped before use. - Nothing global is written. No
shell.toml, no Hyprland config, no theme files — only~/.local/state/omarchy/io.github.omamenu/style.json. - The rest of the menu is unmodified
omarchy.menu.finishRequest()(the select/dmenu result-file writer), the provider and guardbash -lchelpers, and the JSONC menu-fileFileViews are byte-for-byte the built-in menu — rundiffagainst$OMARCHY_PATH/shell/plugins/menu/. This is aclonedFromplugin; those paths are Omarchy's, not introduced here.
- The sliders are pointer-driven; there's no keyboard focus/Tab navigation
inside the editor (the rest of the menu is unchanged).
Esc/←/ the‹get you back out. - Size is a render-scale transform on the menu card. At the top of the range on a very short display the card can run past the screen edge; dial it back or the menu will clamp.
omarchy plugin remove io.github.omamenu
omarchy restart shellThe built-in menu takes over again automatically. Delete
~/.local/state/omarchy/io.github.omamenu/ to clear the saved looks.
omarchy plugin validate ~/.config/omarchy/plugins/io.github.omamenu
/usr/lib/qt6/bin/qmllint -I /usr/lib/qt6/qml -I /usr/share/omarchy/shell \
Menu.qml MenuLookEditor.qml # import/unqualified warnings only
omarchy restart shell # plugin QML is cached by URL — a
# save-triggered hot reload does not
# pick up JS-logic changesMIT — see LICENSE.


