Omahub
← All plugins
C

5bars

by Cinco Euzebio

Per-screen bar layouts. Every monitor renders the default bar until you give it one of its own, with its own left, center and right.

Security review

No obvious issues detected

Deterministic scan — not a security guarantee

None
Risk level
None
Analyzed commit
9cf9109
Scanned
3 days ago

No potentially dangerous behavior detected in the analyzed commit.

Automated analysis only — not a security guarantee.

AI advisory review

Review recommended

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

Low
AI risk level
Low
Recommendation
review
Model
~deepseek/deepseek-v4-flash-latest
Analyzed commit
9cf9109
Reviewed
3 days ago

No malicious behavior, obfuscation, destructive commands, credential access, or hidden persistence was found in the sampled code. The plugin's unusual runtime mechanism—patching Omarchy's installed Bar.qml in memory via edits.json—is transparent, documented, and fails safe to the stock bar, but it is a high-impact approach that warrants a human look at the full edits table before publishing.

  • Bar.qml dynamically reads, patches, compiles, and instantiates the installed Omarchy Bar.qml at runtime; this is an unusual and powerful mechanism that should be reviewed against the complete edits.json.
  • The full edits.json was only partially sampled; each edit modifies core bar behavior, and the complete table should be verified for anything beyond the documented per-screen and pills features.
  • The plugin depends on the exact installed Omarchy bar source and falls back to the stock bar when anchors do not match; this is safe but means behavior can change silently with upstream updates.
  • No install-time scripts or destructive shell commands were found; the only executed command is omarchy-notification-send, invoked as a process list, not a shell.
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/cinco/omarchy-5bars --enable
Desktop #bar #quickshell #workspaces

5bars

Per-screen bar layouts for Omarchy.

Omarchy builds one bar surface per monitor, but every one of them renders the same bar.layout. That is fine until the monitors stop being alike — a 2560px ultrawide and a 1080px screen turned on its side do not want the same eleven widgets on the right.

5bars replaces the bar engine so each output can render a layout of its own, while keeping everything else about the stock bar: the same widgets, the same panels, the same drag-to-reorder, the same omarchy bar commands.

<img src="preview.png" alt="The 5bars panel: the screen tabs, the settings that apply to every screen, and the section rows" width="420">

How it reads config

bar.layout is the default profile. Every output renders it until that output is given an entry under bar.screens:

{
  "bar": {
    "id": "cinco.5bars",
    "layout": {
      "left":   [ { "id": "omarchy.menu" }, { "id": "omarchy.workspaces" } ],
      "center": [ { "id": "omarchy.clock" } ],
      "right":  [ { "id": "omarchy.tray" }, { "id": "omarchy.power" } ]
    },
    "screens": {
      "HDMI-A-1": {
        "centerAnchor": "omarchy.clock",
        "layout": {
          "left":   [ { "id": "omarchy.menu" } ],
          "center": [ { "id": "omarchy.clock", "format": "HH:mm" } ],
          "right":  [ { "id": "omarchy.power" } ]
        }
      }
    }
  }
}

A screen profile has the same shape as the bar subtree itself — a layout plus an optional centerAnchor and enabled — so it is a bar config in miniature rather than a second schema to learn. Output names are Hyprland's: hyprctl monitors.

An output with no entry renders the default, and that is also what makes a renamed connector a non-event. Hyprland re-enumerates outputs on a replug or a DisplayPort link retrain, so the monitor that was DP-1 can come back as DP-2; under its new name it has no entry, so it renders the default while the old profile sits in shell.json doing nothing. The panel lists that profile as a screen which is not connected, so you can see what happened rather than wonder why a bar changed.

"enabled": false removes that screen's bar entirely. The surface is destroyed rather than parked off-screen the way the global bar-off hotkey does it, so the exclusion zone goes back and windows use the full height of that screen.

Pills

{
  "bar": {
    "pills": true,
    "layout": {
      "right": [ { "id": "omarchy.media", "pill": false } ]
    }
  }
}

"pills": true stops painting the strip and gives every widget a capsule of the theme bar background instead, so the wallpaper shows between widgets. It is a third look, not a flavour of transparent: a transparent strip has to sample the wallpaper to pick a readable text colour, while a capsule carries its own contrast and keeps the theme foreground. Pills therefore win — with both set, the strip is clear because of the pills, the sampled-colour path is never entered, and transparent keeps its value in the file for whenever pills are switched off. Toggling transparency while pills are on (double-click, the menu, omarchy bar transparent, the panel) flips that value and switches pills off, so the gesture always lands on the look it names.

<img src="assets/pills.png" alt="A bar with each widget in its own rounded capsule on a clear strip" width="760">

A widget that paints its own backdrop opts out of the capsule with "pill": false on its layout entry.

Pills are global. They and transparency share the bar root's one foreground, and mixing them across screens would ask that single colour to be two things — see The panel.

Colour

A capsule is the theme's bar background at 0.85, with a hairline of the bar text, and it follows the theme as it changes. What it is painted with is theme business, so it is set where the shell keeps its other surfaces — shell.toml, not shell.json:

[bar]
pill              = "background"
pill-alpha        = 0.85
pill-border       = "foreground"
pill-border-alpha = 0.10

A value is any colour the theme already ships, so nothing has to be added to a theme for this to be useful:

a name from the theme's colors.toml lighter_background, selection, dark_background, green, bright_blue, …
a shell role foreground, accent, urgent, muted, background, transparent
another shell.toml key bar.active, popups.background, …
a hex colour #1b2530

lighter_background is the shade every stock theme draws a card in, which is usually what a capsule wants: distinct from the strip, and still the theme's. It follows a theme switch like everything else here.

A theme that ships these swaps them with itself, and a named group can carry its own pair under its own name, falling back to the generic one for anything it leaves out:

pill-sys       = "#1b2530"
pill-sys-alpha = 0.95

The same four live in shell.json too, as pillColor, pillOpacity, pillBorderColor and pillBorderOpacity, and they win — the theme sets the look, you override it. That is the cascade the shell already runs on, and it is the half the panel can write: a bar plugin reaches config through mutateShellConfig, which is shell.json only.

shell.toml   [bar] pill = "lighter_background"      the theme's
     ↓
shell.json   "bar": { "pillColor": "accent" }       yours, and the panel's

Specificity comes first, source second — a group's own token beats the generic one either way — so a group's colour is a theme-level decision, which is also all the panel could express.

The panel has the two that matter, under the Pills toggle: a colour built from the shades this theme actually ships, and an opacity slider. Picking Theme default clears the key and hands the capsule back to the theme.

Grouping

Give pill a name instead of leaving it out, and neighbours carrying the same name share one capsule:

{
  "right": [
    { "id": "omarchy.tray" },
    { "id": "omarchy.bluetooth", "pill": "sys" },
    { "id": "omarchy.network",   "pill": "sys" },
    { "id": "omarchy.audio",     "pill": "sys" },
    { "id": "omarchy.power" }
  ]
}

Bluetooth, network and audio become one capsule; the tray and power keep their own. The name is only an identity — call it what you like — and it groups neighbours, not everything that shares it: an entry between two members that carries a different name, or none, splits the run in two.

Only widgets that are actually on the bar count. One that hides itself when it has nothing to say — media with no player — is skipped rather than splitting the capsule around a gap that is not there.

A group lives inside one section of one screen. The centre is split by centerAnchor, so the anchor is always a capsule of its own and a group cannot reach across it.

Width

A capsule is as wide as the widget paints, not as wide as the slot it sits in. The two differ for the tray: collapsed, it still holds the width its drawer needs to slide into, which is most of its slot and all of it empty. The bar reads that from the widget's own input mask -- the thing that keeps the empty part unhoverable is also the only description of the painted part a widget offers -- so the tray's capsule is chevron-sized, and grows as the drawer opens. A widget without a mask fills its slot, as before.

The panel

Writing screens blocks by hand gets old, so 5bars also ships a bar widget: a small editor for everything above. Click its icon and you get one tab per output, plus the default profile.

The default tab carries what belongs to the bar as a whole; a screen tab carries what belongs to that screen.

Default A screen with its own profile
<img src="assets/panel-default.png" alt="The default tab: bar position, transparency, and the pills controls" width="330"> <img src="assets/panel-screen.png" alt="A screen tab: the profile opt-in and this screen's bar toggle" width="330">
Position, Transparent bar and Pills live here, because they apply to every screen — and with pills on, the capsule's colour and opacity too. Own profile seeds a copy of the default; Show the bar here removes this screen's bar and gives its space back.

It sits in the bar as a single icon:

<img src="assets/widget.png" alt="The 5bars icon in the bar" width="52">

Getting it onto the bar the first time takes a config edit:

jq '.bar.layout.right = ([{"id":"cinco.5bars"}] + .bar.layout.right)' \
  ~/.config/omarchy/shell.json > /tmp/s.json \
  && install -m600 /tmp/s.json ~/.config/omarchy/shell.json

omarchy bar put cinco.5bars looks like the right command and is not: for a plugin declaring both a bar and a widget under one id, it means make this the active bar. setEnabled returns from its bar-option branch before reaching any widget placement (PluginRegistry.qml:486), and reports success either way — so the only sign is that nothing moved. After the first time, the panel and drag both work normally.

A screen tab is opt-in. It opens showing that the screen inherits the default; switching it to Own profile seeds a copy of what that screen is already rendering. On the monitor that needs its own bar the work is nearly always taking things away, and seeding carries each widget's settings (clock format, tray pins) across with it. Going back to Use the default deletes the profile rather than emptying it — an empty bar is a different thing to mean.

Removing a widget from a profile never disables the plugin. The same widget is very likely still on another screen.

The add list scrolls, so everything installed stays reachable; the search box is a shortcut past a long catalogue, not the only way through it.

Two things the panel will not do, because both end with no way back short of hand-editing shell.json:

  • switch off the last screen still drawing a bar;
  • switch off the screen whose bar carries the only copy of the 5bars widget — there the widget is put back on the default profile first, so the control that turned a screen off is still around to turn it on.

Bar position, transparency and pills belong to the bar rather than to a screen, so they only appear on the default tab. For position and transparency that is a limit, and Not yet says why; for pills it is a decision. The two share the bar root's one foreground colour, and pills on one screen beside a transparent strip on another would ask that colour to be two things at once — the theme's, which reads against a capsule, and the wallpaper's, which reads against nothing.

Dragging

Drag-to-reorder still works, and it writes where you would expect: dragging on a screen that has its own profile edits that profile; dragging on a screen that is inheriting edits the default. The bar you are dragging on is the bar you are editing.

omarchy bar move, put and set write to the default profile.

Nothing is copied

5bars changes Omarchy's bar engine, but it does not ship a modified copy of it. At startup it reads the Bar.qml of the installed package, applies the edit table in edits.json in memory, and runs the result. Two things follow:

  • Upstream fixes arrive on their own. The base is whatever version is on your machine.
  • If an Omarchy update moves the ground under an edit, 5bars loads the stock bar of your own version and says why in a notification. It never runs a frozen copy against a shell that has moved on.

UPSTREAM.md has the details, including how to check a new Omarchy release.

Requirements

Omarchy 4.0.3 or newer (the Quickshell shell). No external dependencies, no daemons, no network access — everything happens inside omarchy-shell and shell.json.

4.0.3 reworked both halves of the plugin contract: it stopped handing a third-party bar the shell root, and it started handing third-party bar widgets a sandboxed bar facade. The edit table is anchored on that shell. On 4.0.0–4.0.2 an anchor no longer matches, so 5bars does what it always does in that case — loads the stock bar of your own version and tells you why. Nothing breaks; you just get the built-in bar until you update Omarchy.

Install

omarchy plugin add https://github.com/cinco/omarchy-5bars.git --enable --yes
omarchy bar use cinco.5bars

Uninstall

omarchy bar reset

That returns to the built-in bar, which ignores bar.screens and renders bar.layout — so nothing is stranded and no config needs unwinding.

Not yet

Per-screen bar position, and per-screen transparency. These are one problem wearing two hats, and the one thing here that is different in kind rather than merely more work.

On/off is a property of a surface: a bar exists, or it does not, and nothing outside it needs to know. Position is not. position, vertical and barSize live on the bar root, and that root is the object injected into every widget as bar — so a widget on a vertical screen and a widget on a horizontal one would read the same bar.vertical and lay themselves out the same way. The bar would move; its contents would not follow.

Transparency lands in the same place by a different road: a transparent bar draws with a colour sampled from the wallpaper, and widgets take it from the shared bar.barForeground. Let two screens disagree and that one colour is wrong for one of them — sampled-dark text vanishes on an opaque theme background, and forcing the theme's light text instead makes the transparent bar unreadable over a light wallpaper. There is no third answer while the colour is shared.

Making either per-screen means giving every widget a per-surface stand-in for the bar root. That root exposes 141 members, and any third-party widget reaching for one the stand-in does not forward breaks silently. It changes a contract this plugin does not own, so it needs its own release and its own testing.

Enabling a third-party widget from Omarchy's own plugin settings. Setup > Plugins, and omarchy plugin enable, read and write the default layout and nothing else. A widget you have placed on one screen shows up there as disabled, and switching it on puts it on every bar -- the opposite of what you asked for. Add and remove widgets from the 5bars panel instead: it writes the profile you are looking at.

The reason is one function. PluginRegistry.findBarLocation walks bar.layout and stops, so every enablement question upstream answers, it answers for the default bar. bar.screens is this plugin's idea and upstream has no reason to know it exists yet. Until it does, 5bars also records a third-party widget it places on a screen profile in the top-level plugins list -- that entry means "load this", not "put it on every bar", and it is what keeps a per-screen widget from being treated as switched off for the crime of not being in the default layout. Taking the widget off the last screen takes the entry back out.

Licence

MIT. The bar engine is derived from Omarchy's own (MIT, David Heinemeier Hansson) — see LICENSE and UPSTREAM.md.