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 metadata
  • Hyprland.focusedMonitor — currently focused monitor
  • Hyprland.focusedWorkspace — active workspace
  • Configuration values like general:gaps_out and decoration: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:rounding
  • Style.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.Hyprland exposes compositor state as reactive QML properties
  • State fetching: hyprctl polling for config values, socket events for live updates
  • Event handling: HyprlandFocusGrab forwards Hyprland events to QML signals
  • Command dispatch: Widgets call hyprctl dispatch for workspace changes and window management
  • Visual sync: Style.qml automatically applies Hyprland gap and rounding settings
  • Session lock: Proper ext-session-lock protocol implementation in shell/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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →