Add modular interactive widgets to the Quickshell bar

Introduces a widgets/ directory of first-party QML modules and a common/
helpers library. Widgets auto-load by name from
default/quickshell/bar/widgets/<name>.qml — no edits to shell.qml needed
for additions beyond the firstPartyWidgets registry.

New widgets (all orientation-aware, top/bottom/left/right):
- media: MPRIS now-playing with scrolling label, cover art popup,
  play/pause/skip controls
- audioPanel: master volume slider, output device picker, per-app mixer
- networkPanel: Wi-Fi scan + connect, current connection details
- bluetoothPanel: paired device list, connect/disconnect, battery
- calendar: month grid popup keyed to today
- notificationCenter: live notification feed, DND toggle, clear all
- brightness: slider popup with scroll-to-adjust
- powerProfile: power-saver/balanced/performance picker (UPower)
- systemStats: inline CPU + RAM sparklines, expanded popup with btop
- weatherFlyout: weather popup with refresh and full report
- workspacesPro: animated focus indicator that slides between workspaces
- powerMenu: lock/suspend/log out/reboot/shutdown popup
- idleInhibitor: coffee-cup toggle wired to omarchy-toggle-idle
- microphone: mic state + mute toggle + scroll volume

Common helpers (default/quickshell/bar/common/):
- WidgetButton: hover/press scale animation, tooltip integration
- PopupCard: orientation-aware anchor + slide-in opacity transition
- Slider: drag/wheel-able linear slider with knob hover bump
- PillButton: rounded action button with icon + label

Bar root gains requestPopout/releasePopout so only one popup is open at
a time across widgets.

Updates bar-defaults.json to ship the new widgets out of the box while
keeping the legacy modules available.
This commit is contained in:
Ryan Hughes
2026-05-14 02:21:47 -04:00
parent 30dc9fddde
commit 221e0e1fcc
21 changed files with 2829 additions and 26 deletions
+39 -4
View File
@@ -64,6 +64,17 @@ ShellRoot {
property var tooltipTarget: null
property string tooltipText: ""
property bool tooltipShown: false
property var activePopout: null
function requestPopout(owner) {
if (activePopout === owner) return
if (activePopout && "closePopout" in activePopout) activePopout.closePopout()
activePopout = owner
}
function releasePopout(owner) {
if (activePopout === owner) activePopout = null
}
readonly property bool vertical: position === "left" || position === "right"
readonly property int barSize: vertical ? 28 : 26
@@ -248,6 +259,28 @@ ShellRoot {
return source ? fileUrl(source) : ""
}
readonly property var firstPartyWidgets: ({
"media": true,
"audioPanel": true,
"networkPanel": true,
"bluetoothPanel": true,
"calendar": true,
"notificationCenter": true,
"brightness": true,
"powerProfile": true,
"systemStats": true,
"weatherFlyout": true,
"workspacesPro": true,
"powerMenu": true,
"idleInhibitor": true,
"microphone": true
})
function firstPartyWidgetSource(name) {
if (!firstPartyWidgets[String(name)]) return ""
return fileUrl(omarchyPath + "/default/quickshell/bar/widgets/" + String(name) + ".qml")
}
function networkCommand() {
return [
"route_device=$(ip route get 1.1.1.1 2>/dev/null | awk '{ for (i = 1; i <= NF; i++) if ($i == \"dev\") { print $(i + 1); exit } }')",
@@ -1048,9 +1081,11 @@ ShellRoot {
required property string moduleName
readonly property string customType: root.customModuleType(moduleName)
readonly property var builtinComponent: customType ? null : root.builtinModuleComponent(moduleName)
readonly property string firstPartySource: customType || builtinComponent ? "" : root.firstPartyWidgetSource(moduleName)
readonly property bool qmlCustom: customType === "qml"
readonly property bool commandCustom: customType === "command"
readonly property var activeItem: qmlCustom ? qmlLoader.item : componentLoader.item
readonly property bool firstParty: firstPartySource !== ""
readonly property var activeItem: qmlCustom || firstParty ? qmlLoader.item : componentLoader.item
implicitWidth: activeItem && activeItem.visible ? activeItem.implicitWidth : 0
implicitHeight: activeItem && activeItem.visible ? activeItem.implicitHeight : 0
@@ -1059,15 +1094,15 @@ ShellRoot {
Loader {
id: componentLoader
active: !slot.qmlCustom
active: !slot.qmlCustom && !slot.firstParty
sourceComponent: slot.builtinComponent || (slot.commandCustom ? customCommandModuleComponent : emptyModuleComponent)
anchors.fill: parent
}
Loader {
id: qmlLoader
active: slot.qmlCustom
source: slot.qmlCustom ? root.customModuleSource(slot.moduleName) : ""
active: slot.qmlCustom || slot.firstParty
source: slot.qmlCustom ? root.customModuleSource(slot.moduleName) : (slot.firstParty ? slot.firstPartySource : "")
anchors.fill: parent
onLoaded: {
if (item && "bar" in item) item.bar = root