Skip to content

Shell (Quickshell)

Omarchy 4 replaces Waybar with a single long-running Quickshell process, omarchy-shell. That one process hosts the bar, the notification daemon, the on-screen display, the launcher, and the settings panel. Restarting “the shell” restarts all of them together.

These dotfiles do not fork the shell. They extend it through supported user configuration: a generated shell.json that lays out the bar, a stowed shell.toml style override, and a small set of user plugins that the bar loads as extra widgets.

Four files drive the shell customisation, and none is hand-edited live:

  • ~/.config/omarchy/shell.json is generated, not stowed. dot renders it from Omarchy’s shipped default and inserts the personal modules. The generator is dot/src/lib/omarchyShellConfig.ts (mergeOmarchyShellConfig). The live file is mode 0600 and tracked by neither dotfiles repo.
  • ~/.config/omarchy/shell.toml is stowed from omarchy/.config/omarchy/shell.toml. It keeps the shell-wide 12px type scale while setting the compact bar surface to 12px.
  • ~/.config/omarchy/extensions/omarchy-menu.jsonc is stowed from omarchy/.config/omarchy/extensions/omarchy-menu.jsonc. It adds Dotfiles (dot update) and Topgrade (topgrade) to the stock Update submenu without replacing the upstream menu.
  • Bar plugins live under omarchy/.config/omarchy/plugins/<id>/ in this repo and stow to ~/.config/omarchy/plugins/<id>/. Each plugin is a manifest.json plus an entry-point QML file.

To change the bar, edit the generator (then rebuild dot) or edit a plugin’s QML, never the live shell.json.

dot stow regenerates shell.json for the active host, starting from Omarchy’s default and applying the declarative plugin layout in omarchy-plugins.json. The merge is idempotent: it only rewrites the file when the rendered content changes.

Per-host differences:

  • Clock format: compact HH:mm d MMM, based on the final pre-Quattro Waybar clock without its weekday or ordinal day suffix. The timmo.clock clone reduces the stock clock’s 8.75px cell padding to 6px. Left-click opens the calendar and world clocks, middle-click opens timezone settings, and right-click has no action. The popup shows Pacific, Mountain, Central, Eastern, and Local time with a -24 to +24 hour slider that shifts every clock together while dragging. Right-click the slider to return to now; closing the popup also resets it. SUPER+CTRL+T toggles the same popup.
  • Idle timers: screensaver at 2.5 minutes and lock at 5 minutes on laptop; screensaver at 30 minutes and lock at 60 minutes on every other host.
  • Home Assistant dashboard: desktop uses the office temperature and CO₂ sensors and adds office curtain controls; laptop uses the living-room sensors, adds VOC and dining-room temperature rows, and provides left, middle, and right living-room blind tilt controls. Each host puts its Low/High fan controls inline with the climate status at the top of the Air conditioner subsection.
  • System Bridge and Notes Capture: timmo.system-bridge sits directly after Home Assistant, followed by timmo.notes-capture.

Secondary outputs keep the core menu, timmo.workspaces, timmo.clock, and built-in system widgets; Twitch, Git, command cells, Home Assistant, System Bridge, and Notes Capture collapse without starting per-output pollers or loading their panels.

The personal status widgets read from bar-agnostic scripts, dot JSON output, and Home Assistant. Command cells and the Home Assistant aggregate render at 10px; stock-sized custom icons, the clock, and workspaces render at 11px. See Bar Integrations for the JSON commands behind the bar and dashboard.

The generated config starts from Omarchy Quattro’s shipped shell.json and modifies that layout rather than replacing it wholesale. omarchy-plugins.json owns custom widget placement, replacements, shared settings, host-specific settings, and stock widget removals. TypeScript validates and applies those declarations without embedding personal widget settings.

timmo.workspaces replaces the stock workspace widget, shows only workspaces that currently exist, displays the focused workspace number at full opacity, and dims the others. The Home Assistant dashboard replaces the stock weather widget; its outdoor row opens the same Met Office weather entity and hourly forecast. The stock omarchy.agents widget is intentionally removed without replacement.

The Home Assistant plugin uses one bar widget whose width follows its visible content. It shows each currently visible row’s original compact icon and value with its configured colour, or the Home Assistant icon when every row is quiet. In a Call is icon-only while active. Conditional rows appear only while active, warning, or critical; regular readings remain visible whenever their source output is available, except outdoor temperature, which appears only above 25 °C. The desktop air-conditioner target appears while its enabled input is on; the laptop target appears below 26 °C. It is blue while the matching climate entity is cooling, faded blue otherwise, and hidden while that climate entity is unavailable. Hovering does not reveal extra states. Clicking the widget opens the complete dashboard panel, whose heading includes the Home Assistant logo and whose rows include quiet and unavailable states. Activating a row that opens a link closes the panel first.

The stock alternate clock format, opaque bar, config version, and plugin list are also preserved. The normal clock format uses the compact pre-Quattro layout.

SettingStock QuattroDesktopLaptop
Screensaver2.5 minutes30 minutes2.5 minutes
Lock5 minutes60 minutes5 minutes

Home Assistant entity IDs vary by host. The laptop adds the VOC and dining-room temperature rows; the desktop omits them. The background doorbell watcher uses the active workspace on both hosts.

The session keeps its normal Qt scale for applications, while the ~/.config/hypr/bin/quickshell wrapper resets QT_SCALE_FACTOR to 1 only for Omarchy shell launches. Wayland output scaling still handles the shell’s HiDPI rendering, avoiding an additional Qt multiplier across the bar, notifications, and popup plugins.

A plugin is a folder with manifest.json (schema version 1, an id like timmo.<name>, its kinds, and entry-point QML) plus the QML itself. A bar widget extends BarWidget, reads per-instance settings from shell.json via setting(name, fallback), and uses WidgetButton for clickable cells.

PluginKindWhat it does
timmo.clockbar-widgetKeeps the stock clock and calendar behaviour, adds five world clocks with a -24 to +24 hour slider, and uses compact 6px cell padding.
timmo.commandbar-widgetRuns a shell command on an interval and renders its status-bar JSON (text / tooltip / class) with compact 6px horizontal cell margins. The Waybar custom/* equivalent.
timmo.home-assistantservice, bar-widgetSummarises active HA schedule, status, NAS, and environment rows in one widget and adds a native dashboard panel while keeping the doorbell watcher alive in the background.
timmo.gitservice, bar-widgetCombines repository state and filtered GitHub notifications in one widget and native panel.
timmo.notes-capturebar-widgetCaptures multiline text directly through the local Notes processor with Automatic or searchable repository targeting.
timmo.system-bridgeservice, bar-widgetStreams local CPU, memory, root disk, fan, GPU, temperature, system, and optional battery health into a compact widget and read-only panel.
timmo.stream-commandbar-widgetRuns a long-running command that streams status-bar JSON lines and renders the latest line with compact 6px horizontal cell margins (for watchers like ha-watch-singleton).
timmo.twitchservice, bar-widgetShows live Twitch state and opens an attached panel for channels and notification controls.
timmo.workspacesbar-widgetWorkspace numbers without persistent workspaces: only existing workspaces show, the focused one at full opacity and the rest dimmed.

timmo.command and timmo.stream-command both support classColors (class-name to colour), hideClasses, hiddenText, onClick / onClickRight, and revealOnHover, so the generator can style and wire every cell without bespoke QML per module. Shell-launched web apps run through the reusable launch-floating-webapp command, which places only the new window at mobile size in the monitor’s bottom-right corner. Normal launches of the same sites remain tiled. TUI click targets use the existing TUI.float app id.

timmo.twitch keeps one polling service for the whole shell and shares it across bar instances. Left click opens its channel panel, middle click rechecks notifications, and right click restarts the notifier. The active state stays hidden until the bar is hovered; live and unavailable states remain visible.

timmo.git polls dot git-diff --bar-json and dot git-notifications --bar-json once per minute through one shell service. Each source appears only while its count is above zero, so a clean source contributes neither an icon nor a count. Important notifications are red, ordinary changes or unread notifications amber, pull-only repositories green, private-only dirt blue, and unavailable state grey. When both sources are clear, the widget collapses and reveals both bare icons dimmed while hovering the bar or while its panel is open. Left click opens the panel with actions, changed repository rows, and notification rows; right click refreshes both sources. Activating a changed repository opens dot git-diff directly in lazygit for that repository; quitting lazygit resumes the selected diff TUI. Other actions open the full Changed TUI, Other TUI, filtered notifications TUI, or a notification URL.

timmo.system-bridge is published from timmo001/system-bridge into the generated timmo001/omarchy-system-bridge repository, which dotfiles pins as a managed submodule. It requires the system-bridge-git package and keeps one system-bridge client data watch process loaded for the shell. It subscribes to CPU, memory, disks, sensors, GPUs, system, and battery updates, accepts DATA_UPDATE NDJSON one module at a time, and retains the last valid module values when an individual line is malformed. The widget becomes online only after a valid update, restarts the watcher five seconds after it exits, and terminates a watcher that produces no valid update for 150 seconds so the normal exit path can restart it. CPU and RAM percentages use the normal foreground; loading and offline states are grey. CPU or RAM at 90%, a reported high sensor temperature, stale data, a pending reboot, or a newer System Bridge version turns the widget amber, while a reported critical sensor temperature turns it urgent. The tooltip is limited to status, CPU, memory, load, and CPU temperature. Its lazy, read-only panel presents CPU, memory, load, temperatures, root filesystem usage, fan speeds, available GPU load/memory/power/temperature metrics, uptime, and a pending reboot as a scrollable, keyboard-filterable list with primary labels and secondary values. CTRL+ALT+S toggles it. It supports Escape, panel switching, and the timmo.system-bridge IPC target’s open, close, show, hide, and toggle methods.

timmo.notes-capture opens a keyboard-first multiline capture panel on the primary output. SUPER+CTRL+ALT+C toggles it, Ctrl+Enter submits to notes capture through the private notes-capture-local adapter, and Escape closes it. Send sits directly below the editor. Each submission clears the editor immediately and joins a sequential background queue, so another note can be entered while processing continues; the panel reports active, queued, completed, and failed states. The Clear heading action resets the editor, repository search, repository selection, and visible status. The searchable picker puts Dotfiles, Skills, Notes, Context, and Workflows first, then keeps the remaining notification-enabled private repositories in configuration order; Automatic leaves repository selection to the Notes agent. The panel checks the local OpenCode service every 15 seconds and disables Send while it is unavailable. Unsaved editor text is cached and restored when the panel reloads. Processing failures raise a desktop notification and save the failed submission separately under the user’s cache directory. It never opens or submits to the hosted web capture. Its IPC target is timmo.notes-capture with open, close, show, hide, and toggle methods.

timmo.home-assistant keeps the HA pollers and singleton streams in one shell service. Config.qml owns the desktop and laptop entity mappings, commands, actions, aggregate show conditions, labels, icons, colours, and panel sizing; the other QML files only run and render that configuration. The host-specific target-temperature reading keeps its desktop/laptop visibility gates and cooling colour in the bar, but is omitted from the Environment section because the Controls section already provides its editor. The Controls section starts with Air conditioner and separately groups climate status, target editing, and enabled controls without repeating that name on each row. The climate status row shows Cool while cooling, followed by its labelled fan mode and target temperature separated by bullets, opens Home Assistant more-info, and puts Low (1) and High (2) fan actions inline; the row and actions are omitted while the host’s climate entity is unavailable. The target editor has decrement and increment buttons using the helper’s configured step, while its label and the desktop enabled-control label open their Home Assistant more-info windows. Laptop includes a second row of presets ordered 22.6 °C, 22.8 °C, 23.2 °C, 23.4 °C, 23.6 °C, 23.8 °C, 24.2 °C, 24.4 °C, and Off (36 °C). A sofa-labelled Blinds subsection follows with left, middle, and right living-room rows showing their state and current tilt position and using cover.set_cover_tilt_position presets ordered 100%, 80%, 60%, 40%, 20%, and 0%. Desktop instead has an enabled toggle below the target, followed by an Office curtains status row and a compact left-to-right grid for the Close, 10%, 20%, 30%, and 60% presets on cover.curtain; these panel-only actions do not affect the bar. Clicking Calendar or a sensor opens its existing floating full view; Time check and In a call retain their direct toggle actions. The doorbell stream stays loaded without a visible row and continues opening the camera popup on an active transition.

Twitch, Git, and Home Assistant share the same quick-filter controls. Typing filters their visible actions and rows. Up and Down move through matches, Enter activates one, Backspace edits the filter, Ctrl+Backspace removes a word, Ctrl+U clears it, Ctrl+R refreshes, and Escape clears an active filter before closing the panel.

omarchy plugin add keeps Omarchy’s normal review and enable prompts, then asks where a bar widget belongs. After choosing left, centre, or right, choose an existing neighbour and whether the new widget goes before or after it. The lifecycle hook imports the validated checkout as a submodule under omarchy/.config/omarchy/plugins/, records its placement in omarchy-plugins.json, and runs dot stow.

The parent repository pins the exact plugin SHA. .gitmodules records the upstream URL and branch or tag, and Renovate proposes newer pins. omarchy plugin update moves managed pins after validation; omarchy plugin remove removes the submodule and placement declaration. After an interactive add or remove, choose No (the default), Commit, or Commit and push. Non-interactive runs leave ordinary unstaged dotfiles changes. Unmanaged plugins retain Omarchy’s stock add, update, and remove behaviour.

Other devices receive managed plugins through the repository’s recursive submodule checkout. dot update and dot stow then restore the live plugin symlink and generated layout.

ChangeAction
shell.json layout or settings, existing modules onlyHot-reloads on save, nothing to run
New plugin addedomarchy-shell shell rescanPlugins, then the hot-reload picks it up
User plugin QML editedHot-reloads on save, nothing to run
Omarchy’s first-party shell QML edited, or hot-reload failsomarchy restart shell (full restart)

dot update bakes this in: it regenerates shell.json and reloads the running shell only when the rendered config changed. A standalone dot stow regenerates the file but does not reload.