Omarchy Taskbar
A window list and pinned launcher bar for Omarchy, per monitor.
Hyprland has no taskbar, and with more than one or two displays that leaves a real question unanswered: what is running, and which screen is it on? This widget answers it in the bar you are already looking at — and lets you pin the apps you always want on a given screen, so after a reboot they are one click away on the right display.

By default each bar lists the windows on its own screen as icons. Widen the
scope to all and every bar lists every window on the desk, grouped under
the output it lives on and ordered the way your monitors are physically
arranged — so one glance at one screen tells you where everything is.
| Input | On a running window | On a pinned app that is not running |
|---|---|---|
| Left click | Focus it, wherever it is — switching screen and workspace to get there | Launch it on that screen |
| Right click | Menu: pin/unpin · close | Menu: unpin · launch here |
| Middle click | Close it | — |
| Hover | A live thumbnail of the window, captioned with its title | — |
The active window is highlighted; a pinned app that is not running is dimmed; an urgent window is marked in the theme's urgent colour. Everything follows the current Omarchy theme, font, and bar position, and works on horizontal and vertical bars.
Window previews
Hovering a window's icon shows a live thumbnail of it, the way a Windows
taskbar does — which of four terminals is the one you want is a question a title
rarely answers and a picture always does. Hyprland exposes
hyprland_toplevel_export_v1, which Quickshell's ScreencopyView speaks, so it
is a real view of the window rather than a stale grab.
The preview waits ~400ms so sweeping the pointer along the bar does not strobe a card per button, disappears the moment you click — including the right click that opens the menu — and is click-through, so it can never eat the pointer. Capture only runs while a card is actually on screen; nothing is being recorded in the background.
Pinned apps that are not running have no window to preview, and get no card.
Pinning
Right-click any window's icon and choose Pin to <screen>. The app keeps a slot on that screen's taskbar whether or not it is running, and clicking it starts the app there — the widget focuses the output first, so the window opens on the right display.
Pinned slots hold the front of their monitor's group, in the order you pinned them. A pinned app that is running shows its real windows in that slot rather than a second icon, so nothing is ever listed twice. If a pinned app is already running on that screen, clicking focuses it instead of starting another copy.
Pins are made from a running window, so start the app once, then pin it.
Every window can be pinned, including ones that belong to no installed
application. A window with a .desktop entry is pinned as that entry. A window
without one — an Omarchy coding-agent terminal is ghostty --class=org.omarchy.agent -e claude, which is in no application database — is pinned as the exact command
line it is running, read from /proc/<pid>/cmdline at the moment you pin it.
That is literal, which is the point: the pin starts the same thing that window
was. If the window was started with a one-off argument (an agent launched with a
long prompt, say), the pin replays it. Edit the exec array in the pins file to
change what the icon runs — for a plain agent terminal, ["omarchy", "agent"].
Where pins live
~/.local/state/omarchy/taskbar-pins.json, written atomically and watched, so
every bar picks up a change immediately without a restart. It is plain JSON and
safe to hand-edit:
{
"version": 2,
"monitors": [
{
"name": "HDMI-A-1",
"description": "CVT CR240EU",
"apps": [
{ "appId": "vivaldi-stable", "desktop": "vivaldi-stable" },
{
"appId": "org.omarchy.agent",
"exec": ["/usr/bin/ghostty", "--class=org.omarchy.agent", "-e", "claude"]
}
]
}
]
}
appId is the Wayland app id — both the pin's identity and how running windows
are matched back to it, because it is the one name the compositor actually
reports. desktop is the desktop-entry id when the app has one, and is what
gets launched. exec is the recorded command line, used when there is no
desktop entry. Files written by 1.x (a plain list of desktop ids) are upgraded
on read.
Monitors are recorded by both connector name
and description: a connector name is what you see, but DRM assigns it by
enumeration order and it can move — a laptop eDP panel comes up as eDP-1 or
eDP-2 depending on which GPU's driver loaded first. Matching falls back to the
description so your pins survive that instead of silently emptying.
This deliberately does not live in shell.json. That file is Omarchy's, its
settings schema has no shape for a nested per-monitor map, and a plugin that
rewrites the shell's own config to record a click is a plugin that can corrupt
your bar.
Install
omarchy plugin add https://github.com/IntelligentWolf-Ltd/omarchy-taskbar.git
omarchy plugin enable intelligentwolf.taskbar --section left --after omarchy.workspaces
omarchy restart shell
Plugins land disabled so you can read the code before running it — this one is five QML files with no dependencies beyond Quickshell itself.
omarchy-shell shell rescanPluginsdoes not reliably pick up changes to a third-party widget's QML;omarchy restart shelldoes.
Update later with omarchy plugin update intelligentwolf.taskbar.
Remove
omarchy plugin remove intelligentwolf.taskbar
That disables the widget first, then deletes
~/.config/omarchy/plugins/intelligentwolf.taskbar/ and rescans. The upstream
git repo is untouched. If the widget is still drawn on the bar afterwards, run
omarchy restart shell.
To take it off the bar but keep the code installed, disable it instead:
omarchy plugin disable intelligentwolf.taskbar
Removal deliberately leaves your pins at
~/.local/state/omarchy/taskbar-pins.json, so reinstalling restores them.
Nothing else reads that file — delete it by hand if you want the state gone:
rm -f ~/.local/state/omarchy/taskbar-pins.json
Settings
Settings live under the widget's entry in ~/.config/omarchy/shell.json, or in
Setup → Plugins. Every key is optional.
| Key | Default | What it does |
|---|---|---|
scope |
monitor |
monitor — the windows on this bar's screen. workspace — only what that screen is showing right now. all — every window on every screen, grouped and labelled by output. Pins always show on their own screen whatever the scope. |
label |
none |
none is icons only. app adds the application name, title the window title. The full title is always in the tooltip. |
showIcons |
true |
Application icons, resolved from the desktop entry. |
showMonitorLabels |
true |
Output name above each group. Only applies to all scope — the other scopes show one screen, so a heading would say nothing. |
showEmptyMonitors |
false |
Give screens with no windows a place in the row. all scope only. |
maxLabelWidth |
120 |
Elide button text past this many pixels. |
maxWidthPercent |
35 |
See Fitting the bar below. 0 turns it off. |
groupSpacing |
12 |
Gap between monitor groups. |
monitorAliases |
"" |
Rename outputs in the group labels and pin menu: eDP-1=Laptop,DP-1=Left,HDMI-A-1=TV |
Example — every window on the desk, with application names:
{
"id": "intelligentwolf.taskbar",
"scope": "all",
"label": "app",
"monitorAliases": "eDP-1=Laptop,DP-2=Left,HDMI-A-1=TV"
}
Fitting the bar
A bar has to fit the menu, the workspaces, a centre-anchored clock and the status cluster as well as this widget. On a narrow output an all-screens window list with names will run straight into the clock.
So the widget gives up detail before it gives up windows. When the row would
need more than maxWidthPercent of the bar it drops the button text first, then
the monitor names. Windows are never hidden — every one keeps its button and
its tooltip, which is where the name, title, screen and workspace live anyway.
The budget is a share of the bar rather than a pixel count so one setting behaves the same across a mixed-resolution desk. It cannot be derived from the neighbouring widgets without reaching into the bar's internals, so it is a knob.
Requirements
Omarchy with the Quickshell-based omarchy-shell, and Hyprland. Developed and
tested against Hyprland 0.56.2 and Quickshell 0.3.1.
Focusing a window, and focusing an output before a launch, go through hyprctl dispatch, whose arguments Hyprland 0.56 parses as Lua — the same form Omarchy's
own workspace widget uses. Closing a window uses the Wayland toplevel close
request. Launching goes through uwsm-app, the same path Omarchy's own launcher
takes, which keeps the app in app-graphical.slice instead of letting it
inherit wayland-wm@.service; desktop-entry pins are handed to gtk-launch,
command-line pins are run as their recorded argv.
Everything variable reaches the shell as a positional parameter, which bash expands without re-tokenizing, so a recorded command line is never reparsed.
Window previews need a compositor that implements hyprland_toplevel_export_v1
(Hyprland does). Without it the card still appears and says "no preview".
Licence
MIT — see LICENSE. Copyright © 2026 IntelligentWolf Ltd.