How Quickshell Desktop Integrates with Hyprland: A Deep Dive Into Omacom/Omarchy
Quickshell integrates with Hyprland through the Quickshell.Hyprland QML module, which exposes compositor state as reactive properties and forwards events via hyprctl and the compositor's socket.
The Omarchy project (hosted at omacom/omarchy) uses Quickshell as its core UI engine—a generic QML-based desktop shell that adapts to any Wayland compositor. This article explains how the Hyprland-specific integration works, covering state fetching, event handling, and real-world code patterns from the repository.
Importing the Hyprland Module in Quickshell
UI components that need compositor data import Quickshell.Hyprland:
import Quickshell.Hyprland
This exposes a JavaScript singleton object named Hyprland with properties that mirror Hyprland's runtime state:
Hyprland.workspaces— available workspaces with IDs and metadataHyprland.focusedMonitor— currently focused monitorHyprland.focusedWorkspace— active workspace- Configuration values like
general:gaps_outanddecoration:rounding
These properties update automatically when the compositor state changes, enabling reactive UI components without manual polling in application code.
Fetching Compositor State via hyprctl
The Quickshell.Hyprland module communicates with Hyprland through two mechanisms: the hyprctl command-line interface and Hyprland's dedicated socket.
For values that Hyprland does not push (such as general:gaps_out and decoration:rounding), the module periodically polls hyprctl get ... and caches results in the Hyprland singleton. The cache refreshes when underlying configuration files change.
This polling mechanism enables automatic UI synchronization. In shell/Commons/Style.qml, the desktop's visual properties derive directly from Hyprland configuration:
// shell/Commons/Style.qml
property real cornerRadius: Hyprland.decoration.rounding
property real gapsOut: Hyprland.general.gaps_out
When a user modifies their Hyprland config and reloads, Quickshell's bar, popovers, and widgets automatically adopt the new corner radius and gap sizes.
Reacting to Hyprland Events
Hyprland emits raw events for workspace switches, monitor changes, window focus transitions, and more. The Quickshell.Hyprland module registers a listener—HyprlandFocusGrab—that receives these events and updates singleton properties.
Downstream services consume these events for compositor-aware behavior. For example, in shell/plugins/services/idle/Service.qml, the idle service uses Hyprland events to determine when to trigger screen dimming or locking. Similarly, shell/plugins/notifications/Service.qml listens for focus changes to decide whether to show on-screen displays or refocus windows.
Bar-Level Hyprland Integration
The top bar (shell/plugins/bar/Bar.qml) imports the Hyprland module to read Hyprland.focusedMonitor and locate workspaces. Widgets access this data directly.
The Workspaces pill (shell/plugins/bar/widgets/Workspaces.qml) demonstrates the full pattern: reading workspace state, computing IDs, and dispatching commands:
// shell/plugins/bar/widgets/Workspaces.qml
import Quickshell.Hyprland
import qs.Ui
BarWidget {
id: ws
moduleName: "omarchy.workspaces"
function workspaceIds() {
var ids = [1,2,3,4,5];
var values = Hyprland.workspaces.values;
for (var i = 0; i < values.length; i++) {
var id = values[i].id;
if (id > 0 && ids.indexOf(id) === -1) ids.push(id);
}
ids.sort(function (a,b) { return a-b });
return ids;
}
function focusWorkspace(id) {
// Canonical command interface: hyprctl dispatch
root.bar.run(
"hyprctl dispatch " +
Util.shellQuote("hl.dsp.focus({ workspace = \"" + id + "\" })")
);
}
GridLayout {
columns: ws.workspaceIds().length
Repeater {
model: ws.workspaceIds()
WidgetButton {
readonly property var wsInfo:
Hyprland.workspaces.values.find(w => w.id === modelData)
readonly property bool focused:
Hyprland.focusedWorkspace?.id === modelData
text: focused ? "\uDB85\uDCFB" : String(modelData)
onPressed: ws.focusWorkspace(modelData)
}
}
}
}
The button text switches to a special icon when focused evaluates true, and clicking dispatches a hyprctl command to switch workspaces.
Monitor-Aware Layout
Components that need per-monitor behavior react to Hyprland.focusedMonitor changes:
import Quickshell.Hyprland
Bar {
onFocusedMonitorChanged: {
console.log("New monitor:", Hyprland.focusedMonitor.name);
// Re-layout widgets based on monitor geometry
}
}
This property updates automatically whenever the user moves focus between displays, enabling dynamic widget positioning without manual geometry calculations.
Session Lock Integration
The lock service (shell/plugins/lock/Service.qml) creates an ext-session-lock client tied to the Hyprland session lifecycle. The shell/shell.qml entry point contains comments explaining this interaction: when the lock releases, the client destroys itself, allowing Hyprland to clean up the session lock properly.
This integration ensures that Omarchy's lock screen follows the ext-session-lock-v1 protocol correctly, matching Hyprland's expectations for secure screen locking.
Visual Styling from Hyprland Configuration
The desktop maintains visual consistency with the compositor through three key mappings in shell/Commons/Style.qml:
Style.cornerRadius→Hyprland.decoration:roundingStyle.gapsOut→Hyprland.general:gaps_out- Bar colors →
shell/Commons/Color.qml(Hyprland-aware palette)
This approach eliminates configuration duplication—users set values once in Hyprland, and Quickshell renders matching UI elements.
Key Files in the Integration
| File | Purpose |
|---|---|
shell/plugins/bar/Bar.qml |
Core bar; reads Hyprland.focusedMonitor |
shell/plugins/bar/widgets/Workspaces.qml |
Workspace widget; queries Hyprland.workspaces and dispatches hyprctl |
shell/plugins/services/idle/Service.qml |
Registers Hyprland event listener for idle management |
shell/plugins/notifications/Service.qml |
Window focus and decoration integration |
shell/plugins/lock/Service.qml |
ext-session-lock client lifecycle |
shell/Commons/Style.qml |
Mirrors Hyprland gaps and rounding |
shell/Ui/PopupCard.qml |
Uses HyprlandFocusGrab for click-outside dismissal |
shell/shell.qml |
Entry point with lock/session documentation |
Summary
- Module import:
Quickshell.Hyprlandexposes compositor state as reactive QML properties - State fetching:
hyprctlpolling for config values, socket events for live updates - Event handling:
HyprlandFocusGrabforwards Hyprland events to QML signals - Command dispatch: Widgets call
hyprctl dispatchfor workspace changes and window management - Visual sync:
Style.qmlautomatically applies Hyprland gap and rounding settings - Session lock: Proper
ext-session-lockprotocol implementation inshell/plugins/lock/Service.qml
Frequently Asked Questions
What protocol does Quickshell use to communicate with Hyprland?
Quickshell uses Hyprland's native hyprctl command-line interface for state queries and the compositor's Unix socket for event streaming. The Quickshell.Hyprland module abstracts these into QML properties and signals.
Can Quickshell work with compositors other than Hyprland?
Yes. Quickshell is designed as a generic Wayland desktop shell. The Hyprland integration lives in the optional Quickshell.Hyprland module; other compositors would use their own integration modules or the base Wayland functionality.
How does the bar know when to update workspace indicators?
The Hyprland.workspaces property is reactive. When Hyprland emits a workspace change event, the module updates this property, triggering onChanged handlers in QML bindings. The Workspaces widget re-evaluates workspaceIds() and the Repeater updates automatically.
Where are Hyprland's gap and rounding values cached?
The Quickshell.Hyprland module caches polled configuration values internally. shell/Commons/Style.qml then exposes these as cornerRadius and gapsOut properties that the rest of the UI consumes.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →