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:
@@ -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
@@ -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.
|
||||
|
||||
@@ -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 ~"; }
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
+1
-1
@@ -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
|
||||
|
||||
+5
-4
@@ -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
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+1
-1
@@ -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
|
||||
@@ -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 {}
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user