Desktop Shell
The unified Quickshell shell is the default desktop shell: one long-running
process that draws the top bar, OSD, panels, notifications, lock, launcher, and
polkit password dialog. marchyo.shell.enable defaults to on with
marchyo.desktop.enable. Setting it to false keeps only hyprlock and
hyprpolkitagent, with no bar, OSD or notification daemon.
Options
Section titled “Options”| Option | Default | Description |
|---|---|---|
marchyo.shell.enable |
true with desktop |
Enable the unified Quickshell shell (false keeps the discrete stack) |
marchyo.shell.settings |
{} |
Runtime config serialized to ~/.config/marchyo/shell.json |
marchyo.shell.plugins |
[] |
Shell plugins baked into the store shell (build-time) |
marchyo.shell.extraPlugins |
[] |
Additive plugin specs (pinned git), managed by marchyo plugin |
marchyo.shell.diskWarnPercent |
10 |
Free-space percentage of the Nix store filesystem at or below which marchyo.disk appears |
Runtime configuration
Section titled “Runtime configuration”marchyo.shell.settings is serialized to a generated, read-only
~/.config/marchyo/shell.json that the shell reads live — a rebuild’s new
layout applies without restarting the shell (it is watched, with a short poll to
catch atomic replacements). This is the build-time model: the file is generated
from your configuration and stays reproducible, but is re-read at runtime.
Recognised keys are typed options; any other key passes through to
shell.json unchanged, at every level. Typed keys left unset are omitted from
the file, so the shell’s own defaults apply.
| Key | Type | Description |
|---|---|---|
bar.layout.{left,centerBegin,center,centerEnd,right} |
list of { id; settings?; } |
Widget entries for that bar section. center stays at the middle of the bar, centerBegin grows leftwards from it and centerEnd rightwards. A section left unset falls back to the shipped default layout. |
bar.style |
"flat" or "segmented" |
Bar look. segmented fills each cluster of widgets with a two-tone background (the theme’s subtle-background colour and an accent-tinted surface colour) joined by curved edges. Layout is unchanged. Shell default "flat". |
idle.screensaver |
positive int | Seconds of idle before the screensaver (shell default 150). |
idle.lock |
positive int | Seconds of idle before the lock (shell default 300). |
clock.seconds |
bool | Live seconds in the bar clock (shell default false); right-click the clock flips it for the session. |
battery.percentage |
bool | Charge number beside the battery glyph (shell default true); right-click the widget flips it for the session. |
caffeine.autoVideo |
bool | Hold an idle inhibitor while a video plays (shell default true). |
monitors.<output> |
per-output overrides | See Per-monitor overrides. |
marchyo.shell.settings = { bar.layout = { left = [ { id = "marchyo.session"; } { id = "marchyo.workspaces"; } ]; center = [ { id = "marchyo.clock"; } ]; right = [ { id = "marchyo.network"; } { id = "marchyo.audio"; } { id = "marchyo.battery"; } ]; }; idle = { screensaver = 150; lock = 300; };};First-party widget ids: marchyo.session, marchyo.workspaces,
marchyo.activeWindow, marchyo.clock, marchyo.screenRecording,
marchyo.reminders, marchyo.nightLight, marchyo.tray, marchyo.media,
marchyo.dictation,
marchyo.caffeine, marchyo.theme, marchyo.dnd, marchyo.keyboardLayout,
marchyo.bluetooth, marchyo.network, marchyo.camera, marchyo.mic,
marchyo.audio, marchyo.cpu, marchyo.powerProfile, marchyo.peripherals,
marchyo.battery, marchyo.tailscale, marchyo.weather, marchyo.lockKeys,
marchyo.update, marchyo.disk,
marchyo.health, marchyo.controlCenter.
marchyo.screenRecording (visible only while a recording runs) and
marchyo.reminders (visible only while marchyo reminder timers are pending)
are self-hiding and ship in the default layout; marchyo.nightLight is a
warm/neutral toggle, registered but not placed by default — add it to a section
to show it. marchyo.tailscale (a VPN status indicator) is also registered but
not placed by default; it only works — and only appears — when
services.tailscale.enable is on (marchyo’s default), which is what bakes the tailscale
CLI into the shell. Clicking it opens a read-only detail panel (this device,
exit-node status, and online tailnet peers with their addresses).
marchyo.camera is the camera privacy indicator next to marchyo.mic: it is
self-hiding and ships in the default layout. It shows while an app captures
from a camera, through PipeWire (a link out of a Video/Source node)
or by opening /dev/video* directly (a /proc fd scan every 3 s, run only
while PipeWire reports a camera node); the tooltip names the apps.
marchyo.lockKeys shows CAPS / NUM while Caps Lock or Num Lock is on, read
from the keyboard LEDs in /sys/class/leds. It is registered but not placed by
default: it polls those LEDs every 500 ms while placed, since lock-key toggles
raise no compositor or sysfs event.
The Network panel gains a Share (QR) button on Wi-Fi networks: it opens an
overlay with a scannable QR of the current network’s credentials (rendered as a
UTF8 text grid by qrencode; the PSK is read via nmcli -s, falling back to an
open-network QR when it is unavailable). marchyo.weather (current conditions from wttr.in,
IP-geolocated) is registered but not placed by default; placing it is what
starts the periodic outbound fetch, so a bar without it makes no network
request.
Status indicators
Section titled “Status indicators”Three self-hiding indicators are registered but not placed by default. Add the ones you want to a bar section; each starts its own slow probe only when placed.
marchyo.update: shows when the nixpkgs revision pinned in the flake lock differs from the running system (marchyo update --check, local, hourly). Runmarchyo upgradeormarchyo rebuildto apply it.marchyo.disk: shows when free space on the filesystem holding/nixis at or belowmarchyo.shell.diskWarnPercent(default 10), checked every 5 minutes.marchyo.health: shows whenmarchyo doctorreports failing checks (every 15 minutes). The tooltip lists them; a click runsmarchyo doctorin a terminal.
The Power panel has a rollback button that runs marchyo rollback in a
terminal.
Bar style
Section titled “Bar style”bar.style = "segmented" gives each cluster of widgets (the groups between
separators, or a whole section without them) its own filled background. The
backgrounds alternate between the theme’s subtle-background colour and its
surface colour tinted with the accent, so a theme change recolours them, and
neighbouring segments meet in a curved join where the separator rule was.
Hovering a clickable widget highlights it with a stronger accent tint. Widget widths and positions are the
same as in the default "flat" style.
marchyo.shell.settings = { bar.style = "segmented"; # Keep the plain look on the TV. monitors."HDMI-A-1".bar.style = "flat";};Per-monitor overrides
Section titled “Per-monitor overrides”monitors.<output> overrides the global settings for one output, keyed by the
output name hyprctl monitors reports (DP-1, HDMI-A-1, eDP-1, …). The
override is merged over the global values for that output only: objects merge
key by key, and lists replace the global value whole, so a per-monitor
bar.layout.right is that output’s complete right section. Sections the
override leaves unset keep the global (or default) layout.
marchyo.shell.settings = { bar.layout.right = [ { id = "marchyo.network"; } { id = "marchyo.audio"; } { id = "marchyo.battery"; } ]; # A slim bar on the TV: clock only on the right, global left and center. monitors."HDMI-A-1".bar.layout.right = [ { id = "marchyo.clock"; } ];};These keys are always global; a per-monitor value for them is ignored:
idle: the seat has one idle timer.caffeine: the seat has one idle inhibitor.monitors: overrides do not nest.
The overrides are part of the same generated shell.json, so a rebuild applies
them live like the rest of the file.
Runtime bar layout
Section titled “Runtime bar layout”marchyo bar edits the layout without a rebuild. It records ops (put, move,
remove, set) in $XDG_STATE_HOME/marchyo/shell/bar-layout.json; the shell watches
that file and applies the ops over the layout resolved from shell.json (or the
default) for every output, so shell.json stays generated and read-only. A moved
widget keeps its settings. marchyo bar reset deletes the file, and marchyo runtime restore rewrites it after a lost state directory. marchyo bar background and
marchyo bar position are the bar-related runtime switches (marchyo theme bar-background and bar-position). Double-clicking empty bar space steps the
background through opaque, glassy and clear.
The centre of the bar has three sections: center (the clock) stays at the middle, and
centerBegin and centerEnd grow outwards from its edges.
The marchyo.tray entry takes settings.pinned, a list of tray ids or titles that
stay on the bar; every other icon folds behind the · drawer button.
Control Center
Section titled “Control Center”The Control Center is a quick-settings panel: output-volume and microphone sliders with a live microphone input meter (active only while the panel is open and the mic is unmuted), plus tiles for Wi-Fi, Bluetooth, Tailscale, do-not-disturb, caffeine, night light, and the power profile. Each tile shows the live state and flips it on click; its chevron opens the matching detail panel (network, Tailscale, notifications, power), with a back button to return. Tiles the host cannot offer (no Wi-Fi device, no Bluetooth adapter, Tailscale off) are hidden.
Open it with Super + Shift + Q, marchyo shell toggle controlcenter, or the
marchyo.controlCenter bar widget at the right end of the default layout. The
Tailscale tile needs services.tailscale.enable, and tailscaled accepts
connect/disconnect only from root or the tailnet operator. With the shell on,
marchyo sets the operator to the first enabled marchyo.users entry (by
attribute name) through services.tailscale.extraSetFlags. Tailscale allows one
operator; pick another with marchyo.services.tailscale.operator = "alice"; or
leave it unmanaged with null. Without an operator the tile reports the
refusal.
An Appearance section under the tiles holds a window-gaps toggle, the bar
position (top or bottom), the bar background (opaque, glassy or clear), a
reduce-motion toggle, and the Themes… and Wallpapers… buttons. They
run marchyo theme gaps, marchyo theme bar-position,
marchyo theme bar-background, marchyo toggle reduce-motion and open the
pickers below. The shell reads the choices from
$XDG_STATE_HOME/marchyo/shell/style.json (reduceMotion, uiFont,
barPosition, barBackground); a missing key means the declarative default.
With the bar at the bottom, the panel cards, tooltips, toasts and tray menus
move to the bottom edge; the volume overlay and the window overview keep their
top-bar layout.
Theme and wallpaper pickers
Section titled “Theme and wallpaper pickers”Super + Ctrl + T opens the theme picker and Super + Ctrl + Shift + W the
wallpaper picker (marchyo shell toggle themes and backgrounds). Each shows a
filter field, a grid and a large preview of the selected item. The arrow keys
move, Enter applies (marchyo theme set or marchyo bg set) and Esc
closes. A theme without a preview.png shows a plain tile with its name.
Wallpaper thumbnails are cached in $XDG_CACHE_HOME/marchyo/thumbs, keyed by
size and modification time; the full image shows until its thumbnail exists.
In the launcher, >font lists fonts: start with mono or ui to pick one
kind.
Panel keyboard control
Section titled “Panel keyboard control”A panel opened by a key chord or marchyo shell open <panel> takes keyboard
focus; one opened by clicking a bar widget does not. Esc closes the panel,
Up, Down and Tab move between its controls, and Enter or Space
activates the focused control. Hover highlights are ignored while the keyboard
drives and return when the pointer moves. marchyo shell gallery [section] opens
a dev panel that shows every shell control in every state.
Elsewhen world clock
Section titled “Elsewhen world clock”Elsewhen is a built-in panel: a coastline map (Natural Earth 110m, public
domain) with your cities, and one row per city showing local time, weekday,
UTC offset, the difference to home and the current temperature. Open it with
Super + Ctrl + Alt + E or marchyo shell toggle elsewhen.
- Click a row to make that city home (click again to return home to the local zone). Offsets are relative to home.
- Type in the search field (or press
/) to look up a place through Open-Meteo geocoding;Enteradds the first result. - The people button on a row edits a comma-separated list of names shown under the city.
tflips 12 and 24 hour time,Alt + Tflips Celsius and Fahrenheit,Esccloses.
Zone offsets come from date with TZ= (the shell’s JavaScript engine has no
Intl) and refresh every 5 minutes while the panel is open. The list, home,
people and the two toggles persist in the shell’s state directory
(elsewhen.json). The panel makes network requests to Open-Meteo only while it
is open.
Polkit authentication
Section titled “Polkit authentication”With the shell on, the shell is the session’s polkit agent and hyprpolkitagent
is not started. When an app asks for elevated rights (pkexec, a systemd
action, a 1Password unlock), a centered dialog shows the request, the account it
authenticates as, and a password field on the focused monitor. Enter
authenticates and Escape cancels. A wrong password shakes the dialog and lets
you retry. With the shell off, hyprpolkitagent stays the agent.
Audio routing
Section titled “Audio routing”Moving an app to another output (in wiremix, Super + Ctrl + A, or
pavucontrol) persists across app and session restarts: WirePlumber remembers each
app’s target and restores it when the app plays again. The Audio panel’s device
picker sets the default output, which WirePlumber also remembers.
Plugins
Section titled “Plugins”The shell supports third-party plugins using the same manifest.json schema as
upstream omarchy, but on a build-time (declarative) model: plugins are
Nix-declared, validated, and baked into the store shell. There is no runtime
plugin directory and no hot-reload — changing the plugin set is a rebuild.
A plugin is a directory with a manifest.json (schemaVersion: 1, an id
outside the reserved marchyo.* namespace, kinds, and entryPoints) plus its
QML. Declare one with pkgs.mkMarchyoShellPlugin, which validates the manifest
at build time (matching id, kinds, entry points and prefix, safe relative entry
points, no symlinks):
marchyo.shell.plugins = [ (pkgs.mkMarchyoShellPlugin { src = pkgs.fetchgit { url = "https://example.com/acme-weather.git"; rev = "…"; hash = "…"; }; id = "acme.weather"; kinds = [ "bar-widget" ]; entryPoints = { barWidget = "Panel.qml"; }; })];kinds is a closed set, and entryPoints carries exactly one QML file per
declared kind:
| Kind | Entry point key | What it does |
|---|---|---|
bar-widget |
barWidget |
A bar item, placed from marchyo.shell.settings.bar.layout by its manifest id (e.g. acme.weather) |
launcher |
launcher |
A launcher provider whose root is a qs.Launcher Provider; typing its prefix in the apps palette routes the query to it |
daemon |
daemon |
A non-visual Item or QtObject, created once when the shell starts and kept for its lifetime |
A launcher plugin sets prefix in both its manifest and the
mkMarchyoShellPlugin call. The prefix is required, non-empty, has no
whitespace, and must not start with =, >, # or !, which belong to the
built-in launcher providers. Plugin ids match [A-Za-z0-9][A-Za-z0-9._-]*
without .., and the marchyo.* namespace is reserved. Plugin ids and
prefixes must be unique. A
launcher or daemon plugin that fails to load is logged and skipped, so the
rest of the shell keeps running.
(pkgs.mkMarchyoShellPlugin { src = ./echo; id = "acme.echo"; kinds = [ "launcher" ]; prefix = "?"; entryPoints = { launcher = "Provider.qml"; };})Every shell build records its plugins in
share/marchyo/shell/plugins.lock.json inside the shell package: each plugin’s
id, name, version, kinds, entry points, prefix, store path, and source
({ url, rev } for CLI-added plugins, null for flake-declared ones). The file
exists with an empty plugins list when no plugins are declared.
Adding plugins with the CLI
Section titled “Adding plugins with the CLI”marchyo plugin is the CLI leg of the same build-time model. It pins a git
source and persists a raw spec into the marchyoCliState sidecar
(marchyo.shell.extraPlugins), which is appended to your flake-declared
marchyo.shell.plugins at mkDefault priority — so a hand-written plugins
list always wins. Every change is baked in by a rebuild; there is no runtime
plugin directory.
marchyo plugin add https://example.com/acme-weather.git # pin + bake on rebuildmarchyo plugin add https://example.com/acme-weather.git --rev <sha>marchyo plugin list # every plugin baked into the shellmarchyo plugin update acme.weather # re-pin at the remote HEAD (or --rev)marchyo plugin remove acme.weatheradd resolves the revision and hash with nix-prefetch-git, reads and
validates the repo’s manifest.json (schema version, non-reserved id, entry
points), then writes the pinned spec and rebuilds. Pass -n/--dry-run to
print the state change without writing or rebuilding.
marchyo plugin validate <dir> runs the build’s manifest checks on a checkout.
Compatibility host API
Section titled “Compatibility host API”The shell ships a compatibility layer (shell/compat, exposed as the QML
modules qs.compat.Ui / qs.compat.Commons) that provides the “Quattro”
bar-widget host API — BarWidget, Panel, KeyboardPanel, PluginBarApi and
the Style/Color/Border tokens — mapped onto marchyo’s own bar, theme, and
services. When a bar-widget plugin is baked in, its import qs.Ui /
import qs.Commons lines are rewritten to the compat namespace, and the shell
injects a marchyo-backed bar facade.