refactor(quickshell)!: replace toggle-config with QML composition

The user-extension layer (moonarch.json + UserConfig singleton +
per-widget enabled() gates + UserWidgets slot) was a bespoke graft.
quickshell has no native settings/toggle/override concept — a config
is composable QML, and $XDG_CONFIG_HOME wins over $XDG_CONFIG_DIRS
(verified in the docs and src/launch/command.cpp).

Ship the bar as a named config (/etc/xdg/quickshell/moonarch/, launched
`-c moonarch`) of importable components; the composition (which widgets,
in what order) lives in shell.qml. Users customize by copying shell.qml
to ~/.config/quickshell/moonarch/ (wins by XDG precedence) and editing
it — reorder, add, and replace all fall out of plain composition, which
also retires the deferred reorder/user-popout/override items.

Also fixes the ModuleBox childrenRect binding loop (Item -> Row).

BREAKING CHANGE: ~/.config/quickshell/moonarch.json and UserWidgets.qml
are no longer read; per-widget toggles are gone in favor of composition.

Verified: qmllint clean (only quickshell C++ type-resolution artifacts),
quickshell loads the default config and a cross-dir composed config with
no binding loops or errors.
This commit is contained in:
2026-07-06 13:17:36 +02:00
parent 31af05f1b6
commit 450580aec1
54 changed files with 229 additions and 265 deletions
+17 -17
View File
@@ -43,35 +43,35 @@ Waybar is started via `moonarch-waybar` (not directly). The wrapper merges an op
## Quickshell Bar
quickshell is the **default bar and notification daemon**, spawned by niri as `quickshell` (`defaults/xdg/niri/config.kdl`); it loads `/etc/xdg/quickshell/` via XDG discovery (no `-p`, no wrapper). Both Waybar and swaync stay fully installed as **reserves** (no hard deletes) but are no longer autostarted: Waybar toggles via **Alt+W** (`pkill quickshell``moonarch-waybar`); swaync's `spawn-at-startup` is commented out so it releases `org.freedesktop.Notifications` for quickshell's own `NotificationServer`. To switch back: re-enable swaync's spawn and drop quickshell's `NotifToastHost` from `shell.qml`.
quickshell is the **default bar and notification daemon**, spawned by niri as `quickshell -c moonarch` (`defaults/xdg/niri/config.kdl`). It is shipped as a **named config** at `/etc/xdg/quickshell/moonarch/` — the officially recommended way to distribute a quickshell config as a distro package (`$XDG_CONFIG_DIRS/quickshell/<name>`). Both Waybar and swaync stay fully installed as **reserves** (no hard deletes) but are no longer autostarted: Waybar toggles via **Alt+W** (`pkill quickshell``moonarch-waybar`, restart is `quickshell -c moonarch`); swaync's `spawn-at-startup` is commented out so it releases `org.freedesktop.Notifications` for quickshell's own `NotificationServer`. To switch back: re-enable swaync's spawn and drop quickshell's `NotifToastHost` from `shell.qml`.
The bar (`defaults/xdg/quickshell/`) is system-provided but user-extensible without forking the whole config — quickshell has no native merge, so the mechanism is built in-repo:
**Customization = composition, not a toggle layer.** quickshell has no native settings/toggle/override/merge concept ([verified](https://quickshell.org/docs/configuration/intro/)) — a config is composable QML. moonarch ships the widgets as an importable component library; the composition lives in `shell.qml`.
- **Toggle widgets** — `~/.config/quickshell/moonarch.json`:
```json
{ "widgets": { "battery": false, "cpugov": false } }
```
Absent or broken file = every widget on. A widget shows unless its key is explicitly `false`. Read live (no restart) by the `UserConfig` singleton (`UserConfig.qml`, reusing the `NightlightService` FileView+JSON pattern). Keys: `clock`, `workspaces`, `windowTitle`, `mpris`, `tray`, `privacy`, `updates`, `notifications`, `network`, `bluetooth`, `audio`, `backlight`, `nightlight`, `cpugov`, `idleInhibitor`, `battery`.
- **Customize** — copy `/etc/xdg/quickshell/moonarch/shell.qml` to `~/.config/quickshell/moonarch/shell.qml` and edit it: add/remove/reorder widgets. The user copy **wins by XDG precedence** (`$XDG_CONFIG_HOME` is searched before `$XDG_CONFIG_DIRS`; verified in quickshell `src/launch/command.cpp``configBaseDirs()` prepends config-home, first `shell.qml` found wins). System component updates still flow via the import; structural updates to your own `shell.qml` do not.
- **Add own widgets** — `~/.config/quickshell/UserWidgets.qml` (loaded into a slot at the start of the right cluster when present):
- **`Bar.qml`** is a generic container exposing three composition slots — `leftContent` / `centerContent` / `rightContent` (assign a list of widgets; they reparent into the cluster) — plus a `popouts` alias so composed widgets bind `popouts: bar.popouts`. `shell.qml` fills the slots; that is the whole composition:
```qml
import QtQuick
import "file:///etc/xdg/quickshell" // exposes Theme + ModuleBox/Tooltip/… via the qmldir
Row {
spacing: 12
ModuleBox {
Text { text: "hi"; color: Theme.accent; font.family: Theme.fontFamily; font.pixelSize: Theme.fontSize }
import QtQuick.Layouts
import "file:///etc/xdg/quickshell/moonarch" // Theme + Bar/ModuleBox/… via the qmldir
ShellRoot {
Variants { model: Quickshell.screens
Bar { id: bar
rightContent: [ ModuleBox { Layout.alignment: Qt.AlignVCenter; Clock {} } ]
}
}
}
```
**Do not** anchor a direct child of `ModuleBox` with `anchors.centerIn: parent` — `ModuleBox` sizes to `childrenRect`, so a centered child creates a binding loop. Give the child its own size (as above). User widgets can't open bar popouts yet (the `Popouts.qml` registry is hardcoded).
`ModuleBox` wraps its content in a `Row`, so a direct child using `anchors.centerIn: parent` is harmless — `Row` manages horizontal position and ignores the anchor (no binding loop). Vertical centering via `anchors.verticalCenter` still works.
- **Why the user path works**: a `~/.config/quickshell/` dir without a `shell.qml` does not displace the system config (verified). Cross-dir access to our singletons/components needs the `qmldir` in the system dir — it is **generated** from the `.qml` files; regenerate it when adding components:
- **qmldir** exposes the singletons/components for the cross-dir `import`. It is **generated** from the `.qml` files; regenerate when adding components:
```sh
cd defaults/xdg/quickshell && for f in *.qml; do [ "$f" = shell.qml ] && continue; n=${f%.qml}; grep -q 'pragma Singleton' "$f" && echo "singleton $n $f" || echo "$n $f"; done | sort > qmldir
cd defaults/xdg/quickshell/moonarch && for f in *.qml; do [ "$f" = shell.qml ] && continue; n=${f%.qml}; grep -q 'pragma Singleton' "$f" && echo "singleton $n $f" || echo "$n $f"; done | sort > qmldir
```
Rationale + spike results: `DECISIONS.md` (2026-07-05).
- **Not yet composable:** `Popouts.qml` is still a hardcoded `switch` registry — user-added *popouts* (as opposed to bar widgets) remain a follow-on.
Rationale + spike results: `DECISIONS.md` (2026-07-05 pilot + user-extension, 2026-07-06 switch to composition).
## mpv + ModernZ OSC
+10 -4
View File
@@ -139,7 +139,7 @@
- **Who**: Dominik, ClaudeCode
- **Why**: System artifacts (XDG configs, helper scripts, zsh config, wallpaper) should be managed by pacman for clean deployment, versioning, rollback, and deinstallation
- **Tradeoffs**: /etc/xdg/ configs NOT in backup= (moonarch philosophy: system defaults flow through, users override in ~/.config/). /etc/greetd/ and /etc/moongreet/ NOT owned by package (owned by greetd/moongreet-git, overwritten via .install hook). Helper scripts move from /usr/local/bin/ to /usr/bin/ (FHS for package-managed files)
- **How**: moonarch-git PKGBUILD in moonarch-pkgbuilds repo. sweet-cursors-git as separate package. moonarch-update simplified (no git-sync, pacman handles file deployment). Installer scripts (post-install.sh, transform.sh) remain for orchestration, will be refactored in a follow-up to delegate file deployment to `paru -S moonarch-git`
- **How**: moonarch-git PKGBUILD in moonarch-pkgbuilds repo. sweet-cursors-git as separate package. moonarch-update simplified (no git-sync, pacman handles file deployment). Installer scripts (post-install.sh, transform.sh) remain for orchestration.
## 2026-03-30 Replace Rofi with Walker as application launcher
- **Who**: Dominik, ClaudeCode
@@ -180,11 +180,17 @@
## 2026-07-05 Quickshell bar: user extension mechanism (toggle + add widgets)
- **Who**: Dominik, ClaudeCode
- **Why**: quickshell has no native config merge/drop-in/plugin system — it picks a single config root by XDG precedence. We want a system-provided bar (`/etc/xdg/quickshell/`) that a user can *configure* (toggle widgets) and *extend* (own widgets) without forking the whole config and losing system updates for their part.
- **Tradeoffs**: Two behaviours were spiked against quickshell 0.3.0 first, not guessed: (1) a `~/.config/quickshell/` dir *without* a `shell.qml` does **not** displace the system config — so the user's files may live at the natural path. (2) User QML in a foreign dir cannot see our singletons implicitly, but a `qmldir` in the system dir + `import "file:///etc/xdg/quickshell"` exposes singletons **and** components natively (verified: `Theme.accent` resolves; property-injection via `Loader.setSource` is the fallback). Scope kept to **toggle + add** — no per-widget override (expensive: per-slot loader + the hardcoded `Popouts.qml` registry) and no reordering/user-popouts (follow-up). Toggles use JSON (Waybar-familiar, reuses the `NightlightService` FileView+JSON pattern); own widgets stay QML (unavoidable). A user widget rooted at/containing `ModuleBox` must not anchor its direct child with `centerIn: parent` — that hits a pre-existing `childrenRect` binding loop in `ModuleBox` (documented, not fixed here).
- **How**: New `UserConfig.qml` singleton reads `~/.config/quickshell/moonarch.json` (`{"widgets":{"<key>":false}}`, absent/broken = all on) and probes for `UserWidgets.qml`. New `qmldir` (generated from the `.qml` files) declares all singletons + components for cross-dir import. `Bar.qml` gates each of the 16 widgets on `UserConfig.enabled("<key>")` (self-hiding widgets combine it into their internal `visible`; group `ModuleBox`es hide when all their keys are off) and adds a `Loader` slot (right cluster) bound to `UserWidgets.qml` when present. Still a pilot — the `qmldir` must be shipped when the pilot is later packaged.
- **Tradeoffs**: Two behaviours were spiked against quickshell 0.3.0 first, not guessed: (1) a `~/.config/quickshell/` dir *without* a `shell.qml` does **not** displace the system config — so the user's files may live at the natural path. (2) User QML in a foreign dir cannot see our singletons implicitly, but a `qmldir` in the system dir + `import "file:///etc/xdg/quickshell"` exposes singletons **and** components natively (verified: `Theme.accent` resolves; property-injection via `Loader.setSource` is the fallback). Scope kept to **toggle + add** — no per-widget override (expensive: per-slot loader + the hardcoded `Popouts.qml` registry) and no reordering/user-popouts. Toggles use JSON (Waybar-familiar, reuses the `NightlightService` FileView+JSON pattern); own widgets stay QML (unavoidable). A user widget rooted at/containing `ModuleBox` must not anchor its direct child with `centerIn: parent` — that hits a pre-existing `childrenRect` binding loop in `ModuleBox` (documented in the bar's CLAUDE.md).
- **How**: New `UserConfig.qml` singleton reads `~/.config/quickshell/moonarch.json` (`{"widgets":{"<key>":false}}`, absent/broken = all on) and probes for `UserWidgets.qml`. New `qmldir` (generated from the `.qml` files) declares all singletons + components for cross-dir import. `Bar.qml` gates each of the 16 widgets on `UserConfig.enabled("<key>")` (self-hiding widgets combine it into their internal `visible`; group `ModuleBox`es hide when all their keys are off) and adds a `Loader` slot (right cluster) bound to `UserWidgets.qml` when present.
## 2026-07-05 Quickshell bar: promoted from pilot to default (Waybar + swaync kept as reserves)
- **Who**: Dominik, ClaudeCode
- **Why**: The pilot proved out in daily use. Make quickshell the session's default bar **and** notification daemon. "Remove nothing" means no hard deletes — Waybar and swaync stay installed, just no longer autostarted.
- **Tradeoffs**: Considered a `moonarch-bar` switch wrapper (config-driven bar selection) but rejected as over-engineering — Waybar is only a reserve, so a persistent setting isn't worth it; niri spawns `quickshell` directly and **Alt+W** toggles quickshell ⇄ Waybar. Notifications move to quickshell's own `NotificationServer`; swaync's `spawn-at-startup` is commented out (not deleted) so it releases `org.freedesktop.Notifications` — flip the comment back (and drop quickshell's `NotifToastHost`) to return to swaync. The quickshell config needs no PKGBUILD change (moonarch-git's generic `find` loop over `defaults/xdg/` already installs `/etc/xdg/quickshell/`, `qmldir` included); only the `quickshell` runtime package was added.
- **How**: `defaults/xdg/niri/config.kdl``spawn-at-startup "quickshell"` (was `moonarch-waybar`), swaync spawn commented out, Alt+W bar toggle. `quickshell` added to `packages/official.txt` and moonarch-git `PKGBUILD` `depends` (Waybar + swaync stay in both — reserves, not removed). quickshell's notification stack stays active (`NotifToastHost` in `shell.qml`, `NotificationIndicator` in `Bar.qml`, `notifications` popout). Verified: config loads via `qs -p` with zero binding loops; `niri validate` passes. Remaining manual step: end-to-end deploy (push → CI rebuilds moonarch-git → `pacman -Syu`) and the swaync→quickshell notification handover (test with swaync not running, since a running swaync holds the D-Bus name).
- **How**: `defaults/xdg/niri/config.kdl``spawn-at-startup "quickshell"` (was `moonarch-waybar`), swaync spawn commented out, Alt+W bar toggle. `quickshell` added to `packages/official.txt` and moonarch-git `PKGBUILD` `depends` (Waybar + swaync stay in both — reserves, not removed). quickshell's notification stack stays active (`NotifToastHost` in `shell.qml`, `NotificationIndicator` in `Bar.qml`, `notifications` popout). Verified: config loads via `qs -p` with zero binding loops; `niri validate` passes.
## 2026-07-06 Quickshell bar: toggle-layer → composition (named config)
- **Who**: Dominik, ClaudeCode
- **Why**: The 2026-07-05 user-extension mechanism (JSON `moonarch.json` + `UserConfig` singleton + per-widget `enabled()` gates + `UserWidgets.qml` slot) is **not quickshell-conforming**. Verified against docs + source: quickshell has **no** native settings/toggle/override/merge/drop-in concept — a config is composable QML, and `$XDG_CONFIG_HOME` is searched **before** `$XDG_CONFIG_DIRS` (`src/launch/command.cpp`: `configBaseDirs()` prepends config-home, first `shell.qml` found wins). The toggle layer was a bespoke graft mimicking composition on a system-owned config, built without sign-off.
- **Tradeoffs**: Hiding a widget now means owning + editing your own `shell.qml` (more friction than a one-line JSON edit) — but conform, and reorder / add-own / replace-a-system-widget all fall out for free (the three deferred "spekulativ" items become moot). Composer widgets bind `popouts: bar.popouts` explicitly (slightly verbose). Once a user owns their `shell.qml`, structural default updates no longer reach them (only imported component updates). Adopted the official **named-config** distribution pattern (`/etc/xdg/quickshell/moonarch/`, `-c moonarch`) over the unnamed default — one extra migration (niri spawn, Alt+W, file move) for the documented-correct form. `Popouts.qml` stays a hardcoded switch; user-added *popouts* remain a follow-on.
- **How**: All `defaults/xdg/quickshell/*` moved into a `moonarch/` subfolder (generic PKGBUILD find-loop installs to `/etc/xdg/quickshell/moonarch/`, no PKGBUILD edit). `Bar.qml` → generic container with `leftContent`/`centerContent`/`rightContent` slots + `popouts` alias. `shell.qml` → the default composition filling those slots. Deleted `UserConfig.qml` + `moonarch.json` handling + `UserWidgets` slot; stripped `&& UserConfig.enabled(...)` from the 7 self-hiding widgets (self-hide logic kept). `qmldir` regenerated (no `UserConfig`). `config.kdl` spawn + Alt+W → `quickshell -c moonarch`. Verified: `qmllint` clean (only quickshell C++ type-resolution artifacts), throwaway `quickshell -p …/moonarch``Configuration Loaded`, no binding loop/error.
+2 -2
View File
@@ -80,7 +80,7 @@ layout {
// xwayland-satellite is managed automatically since niri 25.08
// kanshi is managed via systemd user service (kanshi.service)
// Bar: quickshell is the default; Waybar stays installed as a reserve (toggle with Alt+W).
spawn-at-startup "quickshell"
spawn-at-startup "quickshell" "-c" "moonarch"
// swaync disabled — quickshell owns notifications now; package kept installed as a reserve.
// spawn-at-startup "swaync"
spawn-at-startup "/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1"
@@ -133,7 +133,7 @@ binds {
Super+C hotkey-overlay-title=null { spawn "walker" "-s" "clipboard"; }
Alt+W hotkey-overlay-title="Toggle bar: quickshell / Waybar" { spawn-sh "if pkill -x quickshell; then moonarch-waybar & else killall waybar; quickshell & fi"; }
Alt+W hotkey-overlay-title="Toggle bar: quickshell / Waybar" { spawn-sh "if pkill -x quickshell; then moonarch-waybar & else killall waybar; quickshell -c moonarch & fi"; }
Super+E { spawn-sh "xdg-open ~"; }
-159
View File
@@ -1,159 +0,0 @@
// ABOUTME: The moonarch quickshell top bar — one PanelWindow per screen.
// ABOUTME: Left: workspaces + focused-window title; right: tray, clock, battery. Widgets toggle via UserConfig.
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
PanelWindow {
id: bar
required property var modelData
screen: modelData
anchors {
top: true
left: true
right: true
}
implicitHeight: Theme.barHeight
exclusiveZone: Theme.barHeight
color: Theme.barBg
// Shared popout state + host for this screen (tray menu now, more popouts later).
PopoutState {
id: popouts
}
PopoutHost {
popouts: popouts
screen: bar.screen
}
// Holds a Wayland idle-inhibitor on this bar's surface while the shared toggle is on.
// One per screen, all bound to the same state — any single active inhibitor keeps the session awake.
IdleInhibitor {
window: bar
enabled: IdleInhibit.enabled
}
RowLayout {
anchors.fill: parent
anchors.leftMargin: Theme.spacing
anchors.rightMargin: Theme.spacing
spacing: Theme.spacing
// Left cluster.
ModuleBox {
Layout.alignment: Qt.AlignVCenter
visible: UserConfig.enabled("clock")
Clock {}
}
Workspaces {
Layout.alignment: Qt.AlignVCenter
visible: UserConfig.enabled("workspaces")
screenName: bar.screen ? bar.screen.name : ""
}
WindowTitle {
Layout.alignment: Qt.AlignVCenter
Layout.maximumWidth: 420
}
// Center spacer.
Item {
Layout.fillWidth: true
}
// User extension slot — loads ~/.config/quickshell/UserWidgets.qml if present (see CLAUDE.md).
Loader {
Layout.alignment: Qt.AlignVCenter
active: UserConfig.hasUserWidgets
source: active ? UserConfig.userWidgetsUrl : ""
}
// Right cluster — grouped by function.
// Media (auto-hides when nothing is playing).
MprisWidget {
Layout.alignment: Qt.AlignVCenter
popouts: popouts
}
// System tray.
ModuleBox {
Layout.alignment: Qt.AlignVCenter
visible: UserConfig.enabled("tray")
Tray {
popouts: popouts
}
}
// Attention / status — auto-hiding alerts + notifications.
ModuleBox {
Layout.alignment: Qt.AlignVCenter
visible: UserConfig.enabled("privacy") || UserConfig.enabled("updates") || UserConfig.enabled("notifications")
Row {
spacing: 12
PrivacyIndicator {}
UpdatesIndicator {}
NotificationIndicator {
visible: UserConfig.enabled("notifications")
popouts: popouts
}
}
}
// Connectivity.
ModuleBox {
Layout.alignment: Qt.AlignVCenter
visible: UserConfig.enabled("network") || UserConfig.enabled("bluetooth")
Row {
spacing: 12
NetworkIndicator {
visible: UserConfig.enabled("network")
popouts: popouts
}
BluetoothIndicator {
popouts: popouts
}
}
}
// Output levels — volume + brightness.
ModuleBox {
Layout.alignment: Qt.AlignVCenter
visible: UserConfig.enabled("audio") || UserConfig.enabled("backlight")
Row {
spacing: 12
AudioIndicator {
visible: UserConfig.enabled("audio")
popouts: popouts
}
BacklightIndicator {
popouts: popouts
}
}
}
// Session toggles / modes.
ModuleBox {
Layout.alignment: Qt.AlignVCenter
visible: UserConfig.enabled("nightlight") || UserConfig.enabled("cpugov") || UserConfig.enabled("idleInhibitor")
Row {
spacing: 12
NightlightIndicator {
visible: UserConfig.enabled("nightlight")
popouts: popouts
}
CpuGovIndicator {
visible: UserConfig.enabled("cpugov")
popouts: popouts
}
IdleInhibitorIndicator {
visible: UserConfig.enabled("idleInhibitor")
}
}
}
// Battery.
ModuleBox {
Layout.alignment: Qt.AlignVCenter
visible: batteryItem.visible
Battery {
id: batteryItem
popouts: popouts
}
}
}
}
-53
View File
@@ -1,53 +0,0 @@
// ABOUTME: User config service — reads ~/.config/quickshell/moonarch.json for per-widget toggles
// ABOUTME: and detects an optional UserWidgets.qml extension. Absent/broken = defaults (all on).
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
Singleton {
id: root
readonly property string configDir: (Quickshell.env("XDG_CONFIG_HOME") || (Quickshell.env("HOME") + "/.config")) + "/quickshell"
readonly property string configPath: root.configDir + "/moonarch.json"
readonly property string userWidgetsUrl: "file://" + root.configDir + "/UserWidgets.qml"
// Parsed { "<widgetKey>": bool } map. Empty object = no overrides = everything on.
property var widgets: ({})
// Whether ~/.config/quickshell/UserWidgets.qml exists (drives the extension slot).
property bool hasUserWidgets: false
// A widget is shown unless the user explicitly set its key to false.
function enabled(key) {
return root.widgets[key] !== false;
}
FileView {
id: cfgFile
path: root.configPath
blockLoading: true
printErrors: false
watchChanges: true
onLoaded: {
try {
const j = JSON.parse(cfgFile.text());
root.widgets = (j && typeof j.widgets === "object" && j.widgets !== null) ? j.widgets : ({});
} catch (e) {
root.widgets = ({});
}
}
// No config yet, or unreadable → no overrides.
onLoadFailed: root.widgets = ({})
}
// Existence probe for the extension file — Loader.source is bound to userWidgetsUrl only when present.
FileView {
id: userWidgetsProbe
path: root.configDir + "/UserWidgets.qml"
blockLoading: true
printErrors: false
watchChanges: true
onLoaded: root.hasUserWidgets = true
onLoadFailed: root.hasUserWidgets = false
}
}
@@ -6,7 +6,7 @@ Item {
id: root
property var popouts: null
visible: Backlight.supported && UserConfig.enabled("backlight")
visible: Backlight.supported
implicitWidth: visible ? row.implicitWidth : 0
implicitHeight: Theme.barHeight
+74
View File
@@ -0,0 +1,74 @@
// ABOUTME: Generic moonarch bar container — one PanelWindow per screen.
// ABOUTME: Composition (which widgets, order) lives in shell.qml, which fills the content slots.
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
PanelWindow {
id: bar
required property var modelData
screen: modelData
// Composition slots — the config's shell.qml fills these with widgets.
// Assigning a list reparents the items into the respective cluster RowLayout.
property alias leftContent: leftRow.data
property alias centerContent: centerRow.data
property alias rightContent: rightRow.data
// Shared popout state for this screen. Composer widgets bind `popouts: bar.popouts`.
readonly property alias popouts: popoutState
anchors {
top: true
left: true
right: true
}
implicitHeight: Theme.barHeight
exclusiveZone: Theme.barHeight
color: Theme.barBg
PopoutState {
id: popoutState
}
PopoutHost {
popouts: popoutState
screen: bar.screen
}
// Holds a Wayland idle-inhibitor on this bar's surface while the shared toggle is on.
// One per screen, all bound to the same state — any single active inhibitor keeps the session awake.
IdleInhibitor {
window: bar
enabled: IdleInhibit.enabled
}
RowLayout {
anchors.fill: parent
anchors.leftMargin: Theme.spacing
anchors.rightMargin: Theme.spacing
spacing: Theme.spacing
RowLayout {
id: leftRow
spacing: Theme.spacing
Layout.alignment: Qt.AlignVCenter
}
Item {
Layout.fillWidth: true
}
RowLayout {
id: centerRow
spacing: Theme.spacing
Layout.alignment: Qt.AlignVCenter
}
Item {
Layout.fillWidth: true
}
RowLayout {
id: rightRow
spacing: Theme.spacing
Layout.alignment: Qt.AlignVCenter
}
}
}
@@ -21,7 +21,7 @@ Row {
readonly property int level: Math.min(10, Math.max(0, Math.round(pct / 100 * 10)))
readonly property string icon: (plugged ? chargeIcons : dischargeIcons)[level]
visible: present && UserConfig.enabled("battery")
visible: present
spacing: 4
Text {
@@ -11,7 +11,7 @@ Item {
readonly property bool on: root.adapter ? root.adapter.enabled : false
readonly property bool connected: Bluetooth.devices ? Bluetooth.devices.values.some(d => d && d.connected) : false
visible: root.adapter !== null && UserConfig.enabled("bluetooth")
visible: root.adapter !== null
implicitWidth: visible ? iconText.implicitWidth : 0
implicitHeight: Theme.barHeight
@@ -6,15 +6,16 @@ Rectangle {
id: root
default property alias data: inner.data
implicitWidth: inner.width + Theme.modulePadH * 2
implicitWidth: inner.implicitWidth + Theme.modulePadH * 2
implicitHeight: Theme.barHeight - Theme.moduleMarginV * 2
color: Theme.moduleBg
radius: Theme.radius
Item {
// A Row derives its implicit size from the children's implicit sizes
// instead of childrenRect, and rejects horizontal child anchors so a
// child using anchors.centerIn can no longer feed back into the size.
Row {
id: inner
anchors.centerIn: parent
width: childrenRect.width
height: childrenRect.height
}
}
@@ -18,7 +18,7 @@ Item {
}
readonly property bool playing: root.player ? root.player.isPlaying : false
visible: root.player !== null && UserConfig.enabled("mpris")
visible: root.player !== null
implicitWidth: visible ? row.implicitWidth : 0
implicitHeight: Theme.barHeight
@@ -22,7 +22,7 @@ Item {
readonly property bool micActive: root.activeStream(PwNodeType.AudioInStream)
readonly property bool screenActive: root.activeStream(PwNodeType.Video)
visible: (root.micActive || root.screenActive) && UserConfig.enabled("privacy")
visible: (root.micActive || root.screenActive)
implicitWidth: visible ? row.implicitWidth : 0
implicitHeight: Theme.barHeight
@@ -9,7 +9,7 @@ Item {
property int count: 0
property string tip: ""
visible: root.count > 0 && UserConfig.enabled("updates")
visible: root.count > 0
implicitWidth: visible ? row.implicitWidth : 0
implicitHeight: Theme.barHeight
@@ -40,7 +40,7 @@ Row {
Image {
id: icon
anchors.verticalCenter: parent.verticalCenter
visible: root.iconName.length > 0 && root.raw.length > 0 && UserConfig.enabled("windowTitle")
visible: root.iconName.length > 0 && root.raw.length > 0
source: root.iconName.length > 0 ? Quickshell.iconPath(root.iconName, true) : ""
width: visible ? root.iconSize : 0
height: root.iconSize
@@ -36,7 +36,6 @@ singleton NightlightService NightlightService.qml
singleton Niri Niri.qml
singleton NotifService NotifService.qml
singleton Theme Theme.qml
singleton UserConfig UserConfig.qml
Toggle Toggle.qml
Tooltip Tooltip.qml
TrayMenu TrayMenu.qml
+114
View File
@@ -0,0 +1,114 @@
// ABOUTME: Default moonarch bar composition — one Bar per screen, slots filled with the default widget set.
// ABOUTME: Customize by copying this file to ~/.config/quickshell/moonarch/shell.qml and editing (add/remove/reorder widgets).
//@ pragma IconTheme Colloid-Grey-Catppuccin-Dark
import QtQuick
import QtQuick.Layouts
import Quickshell
ShellRoot {
Variants {
model: Quickshell.screens
Bar {
id: bar
// Left cluster.
leftContent: [
ModuleBox {
Layout.alignment: Qt.AlignVCenter
Clock {}
},
Workspaces {
Layout.alignment: Qt.AlignVCenter
screenName: bar.screen ? bar.screen.name : ""
},
WindowTitle {
Layout.alignment: Qt.AlignVCenter
Layout.maximumWidth: 420
}
]
// Right cluster — grouped by function.
rightContent: [
// Media (auto-hides when nothing is playing).
MprisWidget {
Layout.alignment: Qt.AlignVCenter
popouts: bar.popouts
},
// System tray.
ModuleBox {
Layout.alignment: Qt.AlignVCenter
Tray {
popouts: bar.popouts
}
},
// Attention / status — auto-hiding alerts + notifications.
ModuleBox {
Layout.alignment: Qt.AlignVCenter
Row {
spacing: 12
PrivacyIndicator {}
UpdatesIndicator {}
NotificationIndicator {
popouts: bar.popouts
}
}
},
// Connectivity.
ModuleBox {
Layout.alignment: Qt.AlignVCenter
Row {
spacing: 12
NetworkIndicator {
popouts: bar.popouts
}
BluetoothIndicator {
popouts: bar.popouts
}
}
},
// Output levels — volume + brightness.
ModuleBox {
Layout.alignment: Qt.AlignVCenter
Row {
spacing: 12
AudioIndicator {
popouts: bar.popouts
}
BacklightIndicator {
popouts: bar.popouts
}
}
},
// Session toggles / modes.
ModuleBox {
Layout.alignment: Qt.AlignVCenter
Row {
spacing: 12
NightlightIndicator {
popouts: bar.popouts
}
CpuGovIndicator {
popouts: bar.popouts
}
IdleInhibitorIndicator {}
}
},
// Battery.
ModuleBox {
Layout.alignment: Qt.AlignVCenter
Battery {
popouts: bar.popouts
}
}
]
}
}
// Transient notification toasts — one top-right layer-shell surface per screen.
Variants {
model: Quickshell.screens
NotifToastHost {}
}
}
-18
View File
@@ -1,18 +0,0 @@
// ABOUTME: Entry point for the moonarch quickshell bar pilot.
// ABOUTME: Instantiates one Bar per connected screen.
//@ pragma IconTheme Colloid-Grey-Catppuccin-Dark
import Quickshell
ShellRoot {
Variants {
model: Quickshell.screens
Bar {}
}
// Transient notification toasts — one top-right layer-shell surface per screen.
Variants {
model: Quickshell.screens
NotifToastHost {}
}
}