Snake
A snake game as a native Omarchy shell plugin.
It runs inside the same long-lived omarchy-shell Quickshell process that
draws your bar, and offers two surfaces:
- a bar widget that drops the game into a standard popup panel
- a fullscreen overlay summoned by a keybind
Both host the same board, so there is one implementation of the rules and one set of high scores. Colours come from the shell's theme singletons, so the game follows whatever theme you are on.
Install
omarchy plugin add https://github.com/yamz8/omarchy-snake.git --enable
That clones the repo into ~/.config/omarchy/plugins/yamz8.snake/, validates
it, and offers to place the bar widget. To update later:
omarchy plugin update yamz8.snake
Remove
omarchy plugin remove yamz8.snake
That deletes ~/.config/omarchy/plugins/yamz8.snake/ and drops the plugin's
entry from ~/.config/omarchy/shell.json, taking the bar widget with it.
Two things it does not touch, so remove them by hand if you want them gone:
- your high scores, at
~/.local/state/omarchy/snake.json - any keybind you added to
~/.config/hypr/bindings.lua
Requirements
Omarchy 4 (Quattro) or newer. No external dependencies, no packages to
install, no network access, and no privileged operations — the plugin is QML
and JavaScript running inside the existing omarchy-shell process, and uses
only the shell's own qs.Ui and qs.Commons modules.
The only file it writes is its own high-score file above.
node is needed to run the test suite, but never to use the plugin.
A keybind for the fullscreen overlay
Add to ~/.config/hypr/bindings.lua:
o.bind("SUPER + CTRL + G", "Snake", "omarchy-shell shell toggle yamz8.snake")
Playing
Arrows, WASD, or hjkl to move. Space pauses, Enter restarts, Esc
closes. The panel accepts arrows and hjkl because that is what Omarchy's
PanelKeyCatcher already emits — the panel's native navigation is the
controls.
Edges wrap around by default. Walls are opt-in.
Configuration
Settings live on the plugin's entry in ~/.config/omarchy/shell.json and
apply the moment you save the file. The entry is in bar.layout when the
widget is placed in the bar, and in plugins[] when only the overlay is
enabled; either location works.
{ "id": "yamz8.snake", "wrap": false, "speed": "fast" }
| Key | Values | Default | Meaning |
|---|---|---|---|
wrap |
true / false |
true |
false makes the walls deadly |
speed |
slow / normal / fast |
normal |
How fast the game starts and how low it plateaus |
An unrecognised speed falls back to normal — a typo should not make the
game unplayable — and the header shows the mode the game is actually running,
not the string you typed. shell.json is hand-edited and its value reaches a
text label, so it is resolved to one of the three names before anything
displays it.
Wrap and walls keep separate high scores, since wrap is materially easier
and a shared best would permanently bury the harder mode's. They are stored in
~/.local/state/omarchy/snake.json.
That path is predictable and sits in a directory anything running as you can write, while the shell reading it is a long-lived process shared by the whole desktop. So the file is treated as untrusted input: the read refuses symlinks and anything that is not a regular file, opens non-blocking so a planted FIFO cannot stall the shell, and stops at 512 bytes; the write builds a fresh 0600 file and renames it over the destination, which replaces a symlink instead of following it.
The directory gets the same treatment as the file, since a symlink planted at
~/.local/state/omarchy — or at any parent — would redirect the whole
operation no matter how carefully the last name is checked. Both helpers walk
the chain refusing a symlink at every step, then hold the directory open and
work through that descriptor, so nothing swapped in afterwards can move the
read or the write.
Development
manifest.json kinds, entry points, bar widget metadata
SnakeGame.js the rules — pure functions, no QML types
Board.qml the game: state, timer, rendering. Hosted by both surfaces
Panel.qml bar widget + popup panel
Snake.qml fullscreen overlay
tests/ node tests for the rules and the score-file handling
Run the tests:
node tests/rules.test.mjs
SnakeGame.js is deliberately free of QML types so the rules can be exercised
under plain node without a running shell.
Editing QML requires omarchy restart shell. Saving a plugin file logs a
reload, but a keepLoaded surface keeps serving the old instance from Qt's
component cache. Config changes in shell.json are live and need no restart.
License
MIT — see LICENSE.