Files
dotfiles/.config/quickshell/backups/shell.qml.20261004_000531.bak
T
2026-10-15 21:17:12 +00:00

1948 lines
94 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Quickshell
import Quickshell.Io
import Quickshell.Hyprland
import Quickshell.Wayland
import Quickshell.Widgets
import Quickshell.Services.SystemTray
import Quickshell.Services.Pipewire
import Quickshell.Services.UPower
import Quickshell.Bluetooth
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
ShellRoot {
id: root
// ───────────── SETTINGS & THEMING (from ~/.config/foot/foot.ini) ─────────────
readonly property string mono: "Maple Mono, JetBrainsMono Nerd Font, monospace"
readonly property int barWidth: 42
readonly property int wsCount: 5
// Foot colorscheme palette: no harsh pure white, cohesive dark aesthetic
readonly property color cBar: "#151515" // background
readonly property color cPanel: "#181818" // opaque Foot-black popup surface
readonly property color cSurface: "#202020" // card / elevated item
readonly property color cSurfaceHover: "#282828" // hover state
readonly property color cBorder: "#2f2f2f" // subtle border
readonly property color cBorderFocus: "#454545" // active border
readonly property color cText: "#D0D0D0" // foreground text
readonly property color cDim: "#505050" // bright0 (dim / muted)
readonly property color cSubtle: "#757575" // secondary text
readonly property color cBright: "#F5F5F5" // selection-foreground (soft crisp white)
readonly property color cAccent: "#6C99BB" // selection-background / regular4 (steel blue)
readonly property color cSelected: "#303030" // neutral selected state, aligned with Foot's dark palette
readonly property color cSuccess: "#7B9246" // regular2 (olive green)
readonly property color cWarn: "#D3A04D" // regular3 (warm amber)
readonly property color cDanger: "#A53C23" // regular1 (crimson rust)
readonly property color cTeal: "#5EA8A2" // regular6 (teal cyan)
// ───────────── SYSTEM STATE ─────────────
property bool launcherOpen: false
property bool powerMenuOpen: false
property string powerConfirmAction: ""
property string activeApplet: "" // "wifi", "bluetooth", "sound", "calendar", or ""
function closeMenus(): void {
root.launcherOpen = false
root.activeApplet = ""
root.powerMenuOpen = false
root.powerConfirmAction = ""
}
function runPowerAction(action: string): void {
root.closeMenus()
if (action === "logout") Quickshell.execDetached(["hyprctl", "dispatch", "exit"])
else if (action === "reboot") Quickshell.execDetached(["foot", "-e", "doas", "/sbin/reboot"])
else if (action === "poweroff") Quickshell.execDetached(["foot", "-e", "doas", "/sbin/poweroff"])
}
function runBluetoothAction(action: string, address: string): void {
root.btActionPending = true
root.btActionAddress = address
root.btActionMessage = ""
btActionProc.action = action
btActionProc.address = address
btActionProc.running = true
}
property bool netUp: false
property bool wifiEnabled: true
property string activeSsid: ""
property string ipAddress: ""
property var wifiNetworks: []
property bool btOn: false
property bool btConnected: false
property bool btScanning: false
property bool btActionPending: false
property string btActionAddress: ""
property string btActionMessage: ""
property var btDevices: []
// Writable volume/mute values are only available after each PipeWire node
// is bound. Track the complete live node model, including app streams.
PwObjectTracker { objects: Pipewire.nodes.values }
IpcHandler {
target: "launcher"
function toggle(): void { root.launcherOpen = !root.launcherOpen }
function open(): void { root.launcherOpen = true }
function close(): void { root.launcherOpen = false }
}
IpcHandler {
target: "power"
function toggle(): void {
root.launcherOpen = false
root.activeApplet = ""
root.powerConfirmAction = ""
root.powerMenuOpen = !root.powerMenuOpen
}
function open(): void {
root.launcherOpen = false
root.activeApplet = ""
root.powerConfirmAction = ""
root.powerMenuOpen = true
}
function close(): void { root.closeMenus() }
}
SystemClock { id: clock; precision: SystemClock.Seconds }
// Periodic system status collector (Network + Bluetooth)
Process {
id: statusProc
command: ["python3", "-c", `
import subprocess, json
data = {
'net_up': False,
'wifi_enabled': False,
'active_ssid': '',
'ip': '',
'bt_on': False,
'bt_connected': False,
'bt_devices': []
}
# Network check
try:
state = subprocess.run(['nmcli', '-t', '-f', 'STATE', 'general'], capture_output=True, text=True, timeout=2).stdout.strip()
data['net_up'] = state.startswith('connected')
wifi_rad = subprocess.run(['nmcli', '-t', '-f', 'WIFI', 'general'], capture_output=True, text=True, timeout=2).stdout.strip()
data['wifi_enabled'] = 'enabled' in wifi_rad
conns = subprocess.run(['nmcli', '-t', '-f', 'NAME,TYPE,DEVICE,STATE', 'con', 'show', '--active'], capture_output=True, text=True, timeout=2).stdout.strip()
for line in conns.split('\\n'):
if line:
parts = line.split(':')
if len(parts) >= 2 and ('wireless' in parts[1] or 'ethernet' in parts[1]):
data['active_ssid'] = parts[0]
break
ip_out = subprocess.run(['sh', '-c', "ip -4 route get 1.1.1.1 2>/dev/null | awk '{print $7}'"], capture_output=True, text=True, timeout=2).stdout.strip()
data['ip'] = ip_out
except Exception:
pass
# Bluetooth check
try:
show = subprocess.run(['bluetoothctl', 'show'], capture_output=True, text=True, timeout=2).stdout
if 'Powered: yes' in show:
data['bt_on'] = True
devs = subprocess.run(['bluetoothctl', 'devices'], capture_output=True, text=True, timeout=2).stdout
for line in devs.strip().split('\\n'):
if line.startswith('Device '):
parts = line.split(' ', 2)
if len(parts) >= 3:
mac, name = parts[1], parts[2]
info = subprocess.run(['bluetoothctl', 'info', mac], capture_output=True, text=True, timeout=1).stdout
conn = 'Connected: yes' in info
if conn:
data['bt_connected'] = True
paired = 'Paired: yes' in info
trusted = 'Trusted: yes' in info
data['bt_devices'].append({'mac': mac, 'name': name, 'connected': conn, 'paired': paired, 'trusted': trusted})
data['bt_devices'].sort(key=lambda d: (not d['connected'], not d['paired'], d['name'].lower()))
except Exception:
pass
print(json.dumps(data))
`]
stdout: StdioCollector {
onStreamFinished: {
try {
const res = JSON.parse(text)
root.netUp = res.net_up
root.wifiEnabled = res.wifi_enabled
root.activeSsid = res.active_ssid
root.ipAddress = res.ip
root.btOn = res.bt_on
root.btConnected = res.bt_connected
root.btDevices = res.bt_devices
} catch (e) {}
}
}
}
// Discovery is started from the Bluetooth panel and is time limited.
Process {
id: btScanProc
command: ["sh", "-c", "bluetoothctl scan on && sleep 15 && bluetoothctl scan off"]
stdout: StdioCollector {
onStreamFinished: {
root.btScanning = false
statusProc.running = true
}
}
stderr: StdioCollector {
onStreamFinished: {
if (text.trim().length > 0)
console.warn("Bluetooth scan:", text.trim())
}
}
}
// Pair/connect operations run non-interactively from the menu; the user
// never has to open a bluetoothctl terminal.
Process {
id: btActionProc
property string action: ""
property string address: ""
command: action === "pair"
? ["bluetoothctl", "--agent", "NoInputNoOutput", "--timeout", "30", "pair", address]
: ["bluetoothctl", "--timeout", "20", action, address]
stdout: StdioCollector {
onStreamFinished: {
const lines = text.trim().split("\n").filter(line => line.length > 0)
if (lines.length > 0)
root.btActionMessage = lines[lines.length - 1]
statusProc.running = true
}
}
stderr: StdioCollector {
onStreamFinished: {
if (text.trim().length > 0) {
root.btActionMessage = text.trim().split("\n").pop()
console.warn("Bluetooth action:", text.trim())
}
statusProc.running = true
}
}
onExited: {
root.btActionPending = false
root.btActionAddress = ""
statusProc.running = true
}
}
Timer {
interval: 4000
running: true
repeat: true
triggeredOnStart: true
onTriggered: statusProc.running = true
}
// Wi-Fi network scanner
Process {
id: wifiScanProc
command: ["nmcli", "-t", "-f", "SSID,SIGNAL,SECURITY,IN-USE", "dev", "wifi"]
stdout: StdioCollector {
onStreamFinished: {
const lines = text.trim().split("\n")
let results = []
for (let l of lines) {
if (!l) continue
const parts = l.split(":")
if (parts[0] && !results.some(r => r.ssid === parts[0])) {
results.push({
ssid: parts[0],
signal: parseInt(parts[1]) || 0,
security: parts[2] || "Open",
active: parts[3] === "*"
})
}
}
results.sort((a, b) => (b.active ? 1 : 0) - (a.active ? 1 : 0) || b.signal - a.signal)
root.wifiNetworks = results
}
}
}
// ───────────── VERTICAL BAR ─────────────
Variants {
model: Quickshell.screens
PanelWindow {
id: bar
required property var modelData
screen: modelData
anchors { top: true; bottom: true; left: true }
implicitWidth: root.barWidth
color: root.cBar
Rectangle {
anchors { top: parent.top; bottom: parent.bottom; right: parent.right }
width: 1
color: root.cBorder
}
// Top: Workspaces
ColumnLayout {
anchors { top: parent.top; topMargin: 12; horizontalCenter: parent.horizontalCenter }
spacing: 6
// Workspaces
Repeater {
model: root.wsCount
delegate: Rectangle {
required property int index
readonly property int wsId: index + 1
readonly property bool active: Hyprland.focusedWorkspace?.id === wsId
readonly property bool occupied: Hyprland.workspaces.values.some(w => w.id === wsId)
Layout.alignment: Qt.AlignHCenter
implicitWidth: 32; implicitHeight: 32
radius: 0
color: active ? root.cSelected : (wsHover.containsMouse ? root.cSurface : "transparent")
border.color: active ? root.cBorderFocus : "transparent"
Text {
anchors.centerIn: parent
text: parent.wsId
font.family: root.mono
font.pixelSize: 12
font.bold: parent.active
color: parent.active ? root.cBright : (parent.occupied ? root.cText : root.cDim)
}
MouseArea {
id: wsHover
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: Hyprland.dispatch('hl.dsp.focus({ workspace = "' + parent.wsId + '" })')
}
}
}
}
// Center: Clock (Clickable, opens Calendar)
Rectangle {
anchors.centerIn: parent
implicitWidth: 34
implicitHeight: 52
radius: 0
color: root.activeApplet === "calendar" ? root.cSelected : (clockHover.containsMouse ? root.cSurface : "transparent")
border.color: root.activeApplet === "calendar" ? root.cBorderFocus : "transparent"
ColumnLayout {
anchors.centerIn: parent
spacing: 1
Text {
Layout.alignment: Qt.AlignHCenter
text: Qt.formatDateTime(clock.date, "HH")
font.family: root.mono
font.pixelSize: 13
font.bold: true
color: root.activeApplet === "calendar" ? root.cBright : (clockHover.containsMouse ? root.cBright : root.cText)
}
Rectangle {
Layout.alignment: Qt.AlignHCenter
width: 4; height: 1
color: root.activeApplet === "calendar" ? root.cBorderFocus : root.cBorder
}
Text {
Layout.alignment: Qt.AlignHCenter
text: Qt.formatDateTime(clock.date, "mm")
font.family: root.mono
font.pixelSize: 13
font.bold: true
color: root.activeApplet === "calendar" ? root.cBright : (clockHover.containsMouse ? root.cBright : root.cDim)
}
}
MouseArea {
id: clockHover
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.activeApplet = (root.activeApplet === "calendar" ? "" : "calendar")
}
}
// Bottom: Network, Bluetooth, Audio Triggers
ColumnLayout {
anchors { bottom: parent.bottom; bottomMargin: 12; horizontalCenter: parent.horizontalCenter }
spacing: 6
// Network Button
Rectangle {
Layout.alignment: Qt.AlignHCenter
implicitWidth: 32; implicitHeight: 32
radius: 0
color: root.activeApplet === "wifi" ? root.cSelected : (netHover.containsMouse ? root.cSurface : "transparent")
border.color: root.activeApplet === "wifi" ? root.cBorderFocus : "transparent"
Text {
anchors.centerIn: parent
text: root.netUp ? (root.activeSsid ? "" : "󰈀") : "󰤮"
font.family: root.mono; font.pixelSize: 14
color: root.activeApplet === "wifi" ? root.cBright : (root.netUp ? root.cText : root.cDim)
}
MouseArea {
id: netHover
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (root.activeApplet === "wifi") {
root.activeApplet = ""
} else {
root.activeApplet = "wifi"
wifiScanProc.running = true
}
}
}
}
Rectangle { Layout.alignment: Qt.AlignHCenter; width: 16; height: 1; color: root.cBorder }
// Bluetooth Button
Rectangle {
Layout.alignment: Qt.AlignHCenter
implicitWidth: 32; implicitHeight: 32
radius: 0
color: root.activeApplet === "bluetooth" ? root.cSelected : (btHover.containsMouse ? root.cSurface : "transparent")
border.color: root.activeApplet === "bluetooth" ? root.cBorderFocus : "transparent"
Text {
anchors.centerIn: parent
text: root.btConnected ? "󰂱" : (root.btOn ? "" : "󰂲")
font.family: root.mono; font.pixelSize: 14
color: root.activeApplet === "bluetooth" ? root.cBright : (root.btOn ? root.cText : root.cDim)
}
MouseArea {
id: btHover
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.activeApplet = (root.activeApplet === "bluetooth" ? "" : "bluetooth")
}
}
Rectangle { Layout.alignment: Qt.AlignHCenter; width: 16; height: 1; color: root.cBorder }
// Audio Button
Rectangle {
Layout.alignment: Qt.AlignHCenter
implicitWidth: 32; implicitHeight: 32
radius: 0
color: root.activeApplet === "sound" ? root.cSelected : (soundHover.containsMouse ? root.cSurface : "transparent")
border.color: root.activeApplet === "sound" ? root.cBorderFocus : "transparent"
Text {
anchors.centerIn: parent
text: {
if (Pipewire.defaultAudioSink?.audio?.muted) return "󰝟"
const vol = Pipewire.defaultAudioSink?.audio?.volume ?? 0
if (vol > 0.5) return ""
if (vol > 0) return ""
return ""
}
font.family: root.mono; font.pixelSize: 14
color: {
if (Pipewire.defaultAudioSink?.audio?.muted) return root.cDanger
if (root.activeApplet === "sound") return root.cBright
return soundHover.containsMouse ? root.cBright : root.cText
}
}
MouseArea {
id: soundHover
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.activeApplet = (root.activeApplet === "sound" ? "" : "sound")
}
}
}
}
}
// ───────────── MENU BACKDROP BLUR ─────────────
// Blur the active monitor behind open shell surfaces while preserving the bar.
Variants {
model: Quickshell.screens
PanelWindow {
id: menuBackdrop
required property var modelData
screen: modelData
visible: (root.activeApplet !== "" || root.launcherOpen || root.powerMenuOpen)
&& modelData.name === (Hyprland.focusedMonitor?.name ?? Quickshell.screens[0]?.name)
anchors { top: true; bottom: true; left: true; right: true }
color: "transparent"
mask: Region {}
WlrLayershell.layer: WlrLayer.Top
WlrLayershell.namespace: "menu-backdrop"
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
exclusionMode: ExclusionMode.Ignore
BackgroundEffect.blurRegion: Region {
item: menuBackdrop.contentItem
// The vertical bar occupies the leftmost 42 logical pixels.
Region {
x: 0
y: 0
width: root.barWidth
height: menuBackdrop.height
intersection: Intersection.Subtract
}
}
}
}
// ───────────── APP LAUNCHER OVERLAY ─────────────
PanelWindow {
id: launcherWindow
visible: root.launcherOpen
screen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name) ?? Quickshell.screens[0]
// Full-screen overlay surface; the launcher itself stays centered.
anchors { top: true; bottom: true; left: true; right: true }
color: "transparent"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.namespace: "launcher"
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
exclusionMode: ExclusionMode.Ignore
MouseArea {
anchors.fill: parent
onClicked: root.launcherOpen = false
}
Rectangle {
anchors.centerIn: parent
width: 340
height: 480
color: root.cPanel
border.color: root.cBorder
border.width: 1
radius: 0
MouseArea {
anchors.fill: parent
}
FocusScope {
id: launcherScope
anchors.fill: parent
focus: launcherWindow.visible
Keys.onEscapePressed: root.closeMenus()
ColumnLayout {
anchors.fill: parent
anchors.margins: 12
spacing: 10
// Search Input Box
Rectangle {
Layout.fillWidth: true
implicitHeight: 36
color: root.cSurface
border.color: root.cBorderFocus
border.width: 1
radius: 0
RowLayout {
anchors.fill: parent
anchors.leftMargin: 10
anchors.rightMargin: 10
spacing: 8
Text {
text: ""
font.family: root.mono
font.pixelSize: 13
color: root.cSubtle
}
TextInput {
id: searchInput
Layout.fillWidth: true
font.family: root.mono
font.pixelSize: 12
color: root.cBright
focus: true
selectByMouse: true
Text {
text: "Search applications..."
font.family: root.mono
font.pixelSize: 12
color: root.cDim
visible: searchInput.text === ""
}
Keys.onDownPressed: appListView.incrementCurrentIndex()
Keys.onUpPressed: appListView.decrementCurrentIndex()
Keys.onReturnPressed: {
if (appListView.currentItem) {
appListView.currentItem.launch()
}
}
}
}
}
// Application List
ListView {
id: appListView
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
spacing: 3
currentIndex: 0
model: appListProc.apps
delegate: Rectangle {
id: appItem
required property var modelData
required property int index
width: appListView.width
height: 38
radius: 0
color: ListView.isCurrentItem ? root.cSelected : (itemArea.containsMouse ? root.cSurfaceHover : "transparent")
border.color: ListView.isCurrentItem ? root.cBorderFocus : "transparent"
function launch() {
Quickshell.execDetached(["sh", "-c", modelData.exec])
root.launcherOpen = false
searchInput.text = ""
}
RowLayout {
anchors.fill: parent
anchors.leftMargin: 10
anchors.rightMargin: 10
spacing: 10
Text {
text: ""
font.family: root.mono
font.pixelSize: 13
color: ListView.isCurrentItem ? root.cAccent : root.cSubtle
}
Text {
text: modelData.name
font.family: root.mono
font.pixelSize: 12
color: ListView.isCurrentItem ? root.cBright : root.cText
Layout.fillWidth: true
elide: Text.ElideRight
}
}
MouseArea {
id: itemArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: appItem.launch()
}
}
// Empty search state
Item {
anchors.centerIn: parent
visible: appListView.count === 0
ColumnLayout {
anchors.centerIn: parent
spacing: 4
Text { Layout.alignment: Qt.AlignHCenter; text: "No applications found"; font.family: root.mono; font.pixelSize: 12; color: root.cDim }
}
}
}
}
}
// Desktop applications scanner with flatpak and standard fallbacks
Process {
id: appListProc
property var rawApps: []
property var apps: {
const query = searchInput.text.toLowerCase().trim()
if (!query) return rawApps
return rawApps.filter(a => a.name.toLowerCase().includes(query))
}
command: ["python3", "-c", `
import os, glob
dirs = [
'/usr/share/applications',
'/usr/local/share/applications',
os.path.expanduser('~/.local/share/applications'),
os.path.expanduser('~/.local/share/flatpak/exports/share/applications'),
'/var/lib/flatpak/exports/share/applications'
]
xdg = os.environ.get('XDG_DATA_DIRS', '')
if xdg:
for p in xdg.split(':'):
app_dir = os.path.join(p, 'applications')
if app_dir not in dirs:
dirs.append(app_dir)
apps = []
seen = set()
for d in dirs:
if not os.path.isdir(d):
continue
for path in glob.glob(os.path.join(d, '*.desktop')):
try:
with open(path, 'r', encoding='utf-8', errors='ignore') as f:
name, exec_cmd, nodisplay = '', '', False
for line in f:
line = line.strip()
if line.startswith('Name=') and not name:
name = line.split('=', 1)[1].strip()
elif line.startswith('Exec=') and not exec_cmd:
exec_cmd = line.split('=', 1)[1].strip().split('%')[0].strip()
elif line.startswith('NoDisplay=true'):
nodisplay = True
if name and exec_cmd and not nodisplay:
if name.lower() not in seen:
seen.add(name.lower())
apps.append((name, exec_cmd))
except:
pass
# Fallbacks for essential desktop tools
fallbacks = [
('Terminal (Foot)', 'foot'),
('Fish Shell', 'foot -e fish'),
]
for name, cmd in fallbacks:
if name.lower() not in seen:
apps.append((name, cmd))
apps.sort(key=lambda x: x[0].lower())
for name, cmd in apps:
print(f"{name}|||{cmd}")
`]
stdout: StdioCollector {
onStreamFinished: {
const lines = text.trim().split("\n")
let parsed = []
for (let line of lines) {
const parts = line.split("|||")
if (parts.length === 2 && parts[0]) {
parsed.push({ name: parts[0], exec: parts[1] })
}
}
appListProc.rawApps = parsed
}
}
}
Component.onCompleted: appListProc.running = true
onVisibleChanged: {
if (visible) {
searchInput.text = ""
searchInput.forceActiveFocus()
appListProc.running = true
}
}
}
}
// ───────────── CALENDAR POPUP WINDOW ─────────────
PanelWindow {
id: calendarWindow
visible: root.activeApplet === "calendar"
screen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name) ?? Quickshell.screens[0]
// Vertically centered beside the bar; this overlay reserves no space from app windows.
anchors { left: true; top: true }
margins.left: root.barWidth + 14
margins.top: Math.max(8, (screen.height - implicitHeight) / 2)
implicitWidth: 320
implicitHeight: 390
color: "transparent"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.namespace: "calendar"
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
aboveWindows: true
exclusionMode: ExclusionMode.Ignore
Rectangle {
anchors.fill: parent
color: root.cPanel
border.color: root.cBorder
border.width: 1
radius: 0
FocusScope {
id: calendarScope
anchors.fill: parent
focus: calendarWindow.visible
Keys.onEscapePressed: root.closeMenus()
property date viewDate: new Date()
function getCellData(index) {
const y = calendarScope.viewDate.getFullYear()
const m = calendarScope.viewDate.getMonth()
const firstDay = new Date(y, m, 1)
// Monday as 0, Sunday as 6
const startDay = (firstDay.getDay() + 6) % 7
const date = new Date(y, m, 1 - startDay + index)
const now = clock.date
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate())
const offset = Math.round((date.getTime() - today.getTime()) / 86400000)
return {
day: date.getDate(),
isCurrentMonth: date.getMonth() === m,
isToday: offset === 0,
isWeekend: index % 7 >= 5,
dayOffset: offset
}
}
ColumnLayout {
anchors.fill: parent
anchors.margins: 16
spacing: 12
// Header: Full Date & Clock
RowLayout {
Layout.fillWidth: true
ColumnLayout {
spacing: 2
Text {
text: Qt.formatDateTime(clock.date, "dddd, MMMM d, yyyy")
font.family: root.mono; font.pixelSize: 12; font.bold: true; color: root.cBright
}
Text {
text: Qt.formatDateTime(clock.date, "HH:mm:ss")
font.family: root.mono; font.pixelSize: 11; color: root.cAccent
}
}
Item { Layout.fillWidth: true }
}
Rectangle { Layout.fillWidth: true; height: 1; color: root.cBorder }
// Month Navigation Row
RowLayout {
Layout.fillWidth: true
Text {
text: Qt.formatDate(calendarScope.viewDate, "MMMM yyyy")
font.family: root.mono; font.pixelSize: 13; font.bold: true; color: root.cBright
Layout.fillWidth: true
}
// Prev Month Button
Rectangle {
width: 26; height: 26; radius: 0
color: prevHover.containsMouse ? root.cSurfaceHover : root.cSurface
border.color: root.cBorder
Text { anchors.centerIn: parent; text: ""; font.family: root.mono; font.pixelSize: 12; color: root.cText }
MouseArea {
id: prevHover
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
const v = calendarScope.viewDate
calendarScope.viewDate = new Date(v.getFullYear(), v.getMonth() - 1, 1)
}
}
}
// Today Button
Rectangle {
implicitWidth: 46; implicitHeight: 26; radius: 0
color: todayHover.containsMouse ? root.cSurfaceHover : root.cSurface
border.color: root.cBorder
Text { anchors.centerIn: parent; text: "Today"; font.family: root.mono; font.pixelSize: 10; color: root.cAccent }
MouseArea {
id: todayHover
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: calendarScope.viewDate = new Date()
}
}
// Next Month Button
Rectangle {
width: 26; height: 26; radius: 0
color: nextHover.containsMouse ? root.cSurfaceHover : root.cSurface
border.color: root.cBorder
Text { anchors.centerIn: parent; text: ""; font.family: root.mono; font.pixelSize: 12; color: root.cText }
MouseArea {
id: nextHover
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
const v = calendarScope.viewDate
calendarScope.viewDate = new Date(v.getFullYear(), v.getMonth() + 1, 1)
}
}
}
}
// Weekdays Header (Mon..Sun)
RowLayout {
Layout.fillWidth: true
spacing: 2
Repeater {
model: ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"]
delegate: Item {
Layout.fillWidth: true
height: 20
Text {
anchors.centerIn: parent
text: modelData
font.family: root.mono; font.pixelSize: 11; font.bold: true
color: (index >= 5) ? root.cDanger : root.cSubtle
}
}
}
}
// 42-day Calendar Grid
GridLayout {
id: calendarGrid
Layout.fillWidth: true
Layout.fillHeight: true
columns: 7
rows: 6
rowSpacing: 3
columnSpacing: 2
Repeater {
model: 42
delegate: Rectangle {
required property int index
readonly property var cell: calendarScope.getCellData(index)
readonly property string dayOffsetText: {
if (cell.dayOffset > 0)
return cell.dayOffset === 1 ? "In 1 day" : "In " + cell.dayOffset + " days"
const daysAgo = Math.abs(cell.dayOffset)
return daysAgo === 1 ? "1 day ago" : daysAgo + " days ago"
}
Layout.fillWidth: true
Layout.fillHeight: true
radius: 0
z: dayHover.containsMouse ? 2 : 0
color: cell.isToday ? root.cAccent : (dayHover.containsMouse ? root.cSurfaceHover : "transparent")
border.color: (cell.isToday) ? root.cAccent : "transparent"
Text {
anchors.centerIn: parent
text: parent.cell.day
font.family: root.mono
font.pixelSize: 11
font.bold: parent.cell.isToday
color: {
if (parent.cell.isToday) return root.cBar
if (parent.cell.isWeekend) return root.cDanger
if (parent.cell.isCurrentMonth) return root.cText
return root.cDim
}
}
Rectangle {
visible: dayHover.containsMouse && !cell.isToday
z: 3
anchors.bottom: parent.top
anchors.bottomMargin: 4
anchors.horizontalCenter: parent.horizontalCenter
implicitWidth: dayOffsetLabel.implicitWidth + 14
implicitHeight: 24
radius: 0
color: root.cSurface
border.color: root.cBorder
Text {
id: dayOffsetLabel
anchors.centerIn: parent
text: parent.parent.dayOffsetText
font.family: root.mono
font.pixelSize: 10
color: root.cText
}
}
MouseArea {
id: dayHover
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
}
}
}
}
}
}
}
}
// ───────────── APPLETS POPUP WINDOW (NETWORK / BLUETOOTH / AUDIO) ─────────────
PanelWindow {
id: appletWindow
visible: root.activeApplet !== "" && root.activeApplet !== "calendar"
screen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name) ?? Quickshell.screens[0]
// Keep a clear 14px gutter from the vertical bar, anchored to bottom
anchors { left: true; bottom: true }
margins.left: root.barWidth + 14
margins.bottom: 8
implicitWidth: 360
implicitHeight: 460
color: "transparent"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.namespace: "applet"
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
aboveWindows: true
exclusionMode: ExclusionMode.Ignore
Rectangle {
anchors.fill: parent
color: root.cPanel
border.color: root.cBorder
border.width: 1
radius: 0
FocusScope {
id: appletScope
anchors.fill: parent
focus: appletWindow.visible
Keys.onEscapePressed: root.closeMenus()
ColumnLayout {
anchors.fill: parent
anchors.margins: 16
spacing: 12
// Each bar control opens its own view; keep this header informational.
RowLayout {
Layout.fillWidth: true
Text {
text: root.activeApplet === "wifi" ? "NETWORK" : (root.activeApplet === "bluetooth" ? "BLUETOOTH" : "AUDIO")
font.family: root.mono
font.pixelSize: 12
font.bold: true
color: root.cBright
}
Item { Layout.fillWidth: true }
}
Rectangle { Layout.fillWidth: true; height: 1; color: root.cBorder }
// Container for the 3 Applet Views
Item {
Layout.fillWidth: true
Layout.fillHeight: true
// ══════════════════ NETWORK VIEW ══════════════════
ColumnLayout {
anchors.fill: parent
visible: root.activeApplet === "wifi"
spacing: 12
// Status & Toggle Card
Rectangle {
Layout.fillWidth: true
implicitHeight: 64
color: root.cSurface
border.color: root.cBorder
radius: 0
RowLayout {
anchors.fill: parent
anchors.margins: 12
spacing: 10
Text {
text: root.netUp ? (root.activeSsid ? "" : "󰈀") : "󰤮"
font.family: root.mono; font.pixelSize: 22
color: root.netUp ? root.cSuccess : root.cWarn
}
ColumnLayout {
spacing: 2
Text {
text: root.netUp ? (root.activeSsid ? root.activeSsid : "Wired Connection") : "Disconnected"
font.family: root.mono; font.pixelSize: 13; font.bold: true; color: root.cBright
elide: Text.ElideRight
}
Text {
text: root.ipAddress ? ("IP: " + root.ipAddress) : (root.wifiEnabled ? "Wi-Fi Enabled" : "Wi-Fi Disabled")
font.family: root.mono; font.pixelSize: 10; color: root.cSubtle
}
}
Item { Layout.fillWidth: true }
// Wi-Fi Toggle Pill
Rectangle {
implicitWidth: 36; implicitHeight: 20; radius: 0
color: root.wifiEnabled ? root.cAccent : root.cSurfaceHover
border.color: root.wifiEnabled ? root.cAccent : root.cBorder
border.width: 1
Rectangle {
x: root.wifiEnabled ? 18 : 2; y: 2; width: 16; height: 16; radius: 0
color: root.wifiEnabled ? root.cBar : root.cDim
Behavior on x { NumberAnimation { duration: 150 } }
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
const newState = !root.wifiEnabled
root.wifiEnabled = newState
Quickshell.execDetached(["nmcli", "radio", "wifi", newState ? "on" : "off"])
if (newState) wifiScanProc.running = true
}
}
}
}
}
// Keep the network action in the panel; no terminal launcher.
RowLayout {
Layout.fillWidth: true
spacing: 0
Rectangle {
Layout.fillWidth: true; implicitHeight: 28; radius: 0
color: rescanHover.containsMouse ? root.cSurfaceHover : root.cSurface
border.color: root.cBorder
RowLayout {
anchors.centerIn: parent; spacing: 6
Text { text: ""; font.family: root.mono; font.pixelSize: 11; color: root.cAccent }
Text { text: "Rescan"; font.family: root.mono; font.pixelSize: 11; color: root.cText }
}
MouseArea {
id: rescanHover
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: wifiScanProc.running = true
}
}
}
// Available Networks Header
Text {
text: "AVAILABLE NETWORKS"
font.family: root.mono; font.pixelSize: 10; font.bold: true; color: root.cSubtle
Layout.topMargin: 2
}
// Wi-Fi Networks List
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
spacing: 4
model: root.wifiNetworks
delegate: Rectangle {
width: ListView.view.width
height: 36
radius: 0
color: modelData.active ? root.cSelected : (wifiItemHover.containsMouse ? root.cSurfaceHover : root.cSurface)
border.color: modelData.active ? root.cAccent : "transparent"
RowLayout {
anchors.fill: parent
anchors.leftMargin: 10
anchors.rightMargin: 10
spacing: 10
Text {
text: ""
font.family: root.mono; font.pixelSize: 12
color: modelData.active ? root.cAccent : (modelData.signal > 50 ? root.cText : root.cDim)
}
Text {
text: modelData.ssid
font.family: root.mono; font.pixelSize: 12
color: modelData.active ? root.cBright : root.cText
Layout.fillWidth: true
elide: Text.ElideRight
}
// Security lock
Text {
visible: modelData.security && !modelData.security.includes("Open")
text: ""
font.family: root.mono; font.pixelSize: 10; color: root.cDim
}
// Signal percentage
Text {
text: modelData.signal + "%"
font.family: root.mono; font.pixelSize: 10; color: root.cSubtle
}
// Active badge
Rectangle {
visible: modelData.active
width: 8; height: 8; radius: 0
color: root.cSuccess
}
}
MouseArea {
id: wifiItemHover
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (modelData.active) {
Quickshell.execDetached(["nmcli", "connection", "down", "id", modelData.ssid])
} else {
Quickshell.execDetached(["foot", "-e", "nmtui-connect", modelData.ssid])
}
root.activeApplet = ""
}
}
}
// Empty state
Item {
anchors.centerIn: parent
visible: root.wifiNetworks.length === 0
ColumnLayout {
anchors.centerIn: parent
spacing: 4
Text { Layout.alignment: Qt.AlignHCenter; text: root.wifiEnabled ? "Scanning for networks..." : "Wi-Fi is currently disabled"; font.family: root.mono; font.pixelSize: 12; color: root.cDim }
}
}
}
}
// ══════════════════ BLUETOOTH VIEW ══════════════════
ColumnLayout {
anchors.fill: parent
visible: root.activeApplet === "bluetooth"
spacing: 12
// Status & Toggle Card
Rectangle {
Layout.fillWidth: true
implicitHeight: 64
color: root.cSurface
border.color: root.cBorder
radius: 0
RowLayout {
anchors.fill: parent
anchors.margins: 12
spacing: 10
Text {
text: root.btConnected ? "󰂱" : (root.btOn ? "" : "󰂲")
font.family: root.mono; font.pixelSize: 22
color: root.btOn ? (root.btConnected ? root.cSuccess : root.cAccent) : root.cDim
}
ColumnLayout {
spacing: 2
Text {
text: root.btOn ? (root.btConnected ? "Device Connected" : "Bluetooth Enabled") : "Bluetooth Disabled"
font.family: root.mono; font.pixelSize: 13; font.bold: true; color: root.cBright
}
Text {
text: root.btActionMessage || (root.btDevices.length > 0 ? (root.btDevices.length + " known devices") : "No devices")
font.family: root.mono; font.pixelSize: 10; color: root.cSubtle
elide: Text.ElideRight
Layout.maximumWidth: 190
}
}
Item { Layout.fillWidth: true }
// Bluetooth Power Toggle Pill
Rectangle {
implicitWidth: 36; implicitHeight: 20; radius: 0
color: root.btOn ? root.cAccent : root.cSurfaceHover
border.color: root.btOn ? root.cAccent : root.cBorder
border.width: 1
Rectangle {
x: root.btOn ? 18 : 2; y: 2; width: 16; height: 16; radius: 0
color: root.btOn ? root.cBar : root.cDim
Behavior on x { NumberAnimation { duration: 150 } }
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
const newState = !root.btOn
root.btOn = newState
if (Bluetooth.defaultAdapter)
Bluetooth.defaultAdapter.enabled = newState
statusProc.running = true
}
}
}
}
}
// Start a bounded discovery from this panel. Pairing and
// connect actions remain here, without opening a terminal.
Rectangle {
Layout.fillWidth: true
implicitHeight: 30
radius: 0
color: btScanHover.containsMouse ? root.cSurfaceHover : root.cSurface
border.color: root.cBorder
RowLayout {
anchors.centerIn: parent
spacing: 7
Text {
text: root.btScanning ? "◌" : (root.btOn ? "" : "")
font.family: root.mono; font.pixelSize: 11
color: root.cAccent
}
Text {
text: root.btScanning ? "Scanning nearby devices…" : (root.btOn ? "Scan nearby devices" : "Enable Bluetooth")
font.family: root.mono; font.pixelSize: 11
color: root.cText
}
}
MouseArea {
id: btScanHover
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
enabled: !root.btScanning
onClicked: {
if (!root.btOn) {
if (Bluetooth.defaultAdapter)
Bluetooth.defaultAdapter.enabled = true
statusProc.running = true
} else {
root.btScanning = true
btScanProc.running = true
}
}
}
}
// Paired Devices Header
Text {
text: "NEARBY & PAIRED DEVICES"
font.family: root.mono; font.pixelSize: 10; font.bold: true; color: root.cSubtle
Layout.topMargin: 2
}
// Bluetooth Devices List
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
spacing: 4
model: root.btDevices
delegate: Rectangle {
width: ListView.view.width
height: 50
radius: 0
color: modelData.connected ? root.cSelected : root.cSurface
border.color: modelData.connected ? root.cBorderFocus : "transparent"
RowLayout {
anchors.fill: parent
anchors.leftMargin: 10
anchors.rightMargin: 10
spacing: 10
Text {
text: ""
font.family: root.mono; font.pixelSize: 14
color: modelData.connected ? root.cSuccess : root.cSubtle
}
ColumnLayout {
Layout.fillWidth: true; spacing: 1
Text {
text: modelData.name
font.family: root.mono; font.pixelSize: 12
color: modelData.connected ? root.cBright : root.cText
elide: Text.ElideRight
}
Text {
text: modelData.mac
font.family: root.mono; font.pixelSize: 9
color: root.cDim
}
}
// Connect / Disconnect Action Button
Rectangle {
implicitWidth: modelData.connected ? 78 : (modelData.paired ? 62 : 48)
implicitHeight: 22
radius: 0
color: modelData.connected ? root.cDanger : (btActionHover.containsMouse ? root.cSelected : root.cSurfaceHover)
border.color: root.cBorder
Text {
anchors.centerIn: parent
text: root.btActionPending && root.btActionAddress === modelData.mac
? "Working…"
: (modelData.connected ? "Disconnect" : (modelData.paired ? "Connect" : "Pair"))
font.family: root.mono; font.pixelSize: 9
color: modelData.connected ? root.cBright : root.cText
}
MouseArea {
id: btActionHover
anchors.fill: parent
hoverEnabled: true
enabled: !root.btActionPending
cursorShape: Qt.PointingHandCursor
onClicked: {
const action = modelData.connected ? "disconnect" : (modelData.paired ? "connect" : "pair")
root.runBluetoothAction(action, modelData.mac)
}
}
}
}
}
// Empty state
Item {
anchors.centerIn: parent
visible: root.btDevices.length === 0
ColumnLayout {
anchors.centerIn: parent
spacing: 4
Text {
Layout.alignment: Qt.AlignHCenter
text: root.btOn ? (root.btScanning ? "Looking for nearby devices…" : "No devices found yet") : "Bluetooth is powered off"
font.family: root.mono; font.pixelSize: 12; color: root.cDim
}
Text {
Layout.alignment: Qt.AlignHCenter
text: "Scan nearby devices, then choose Pair or Connect"
font.family: root.mono; font.pixelSize: 10; color: root.cSubtle
visible: root.btOn && !root.btScanning
}
}
}
}
}
// ══════════════════ AUDIO VIEW ══════════════════
ScrollView {
anchors.fill: parent
contentWidth: availableWidth
clip: true
visible: root.activeApplet === "sound"
ColumnLayout {
width: parent.width
spacing: 10
RowLayout {
Layout.fillWidth: true
Text {
text: "OUTPUT"
font.family: root.mono; font.pixelSize: 10; font.bold: true
color: root.cSubtle
}
Item { Layout.fillWidth: true }
Text {
text: Pipewire.defaultAudioSink?.description || Pipewire.defaultAudioSink?.name || "No output device"
font.family: root.mono; font.pixelSize: 10
color: root.cDim
elide: Text.ElideRight
Layout.maximumWidth: 190
}
}
RowLayout {
Layout.fillWidth: true
spacing: 10
Text {
text: Pipewire.defaultAudioSink?.audio?.muted ? "󰝟" : ""
font.family: root.mono; font.pixelSize: 15
color: Pipewire.defaultAudioSink?.audio?.muted ? root.cDanger : root.cText
}
Slider {
id: masterVolSlider
Layout.fillWidth: true
from: 0; to: 1.0; stepSize: 0.01
value: Pipewire.defaultAudioSink?.audio?.volume ?? 0
onMoved: {
if (Pipewire.defaultAudioSink?.audio)
Pipewire.defaultAudioSink.audio.volume = value
}
background: Rectangle {
x: masterVolSlider.leftPadding
y: masterVolSlider.topPadding + masterVolSlider.availableHeight / 2 - height / 2
width: masterVolSlider.availableWidth; height: 3
color: root.cBorder
Rectangle {
width: masterVolSlider.visualPosition * parent.width; height: parent.height
color: Pipewire.defaultAudioSink?.audio?.muted ? root.cDim : root.cAccent
}
}
handle: Rectangle {
x: masterVolSlider.leftPadding + masterVolSlider.visualPosition * (masterVolSlider.availableWidth - width)
y: masterVolSlider.topPadding + masterVolSlider.availableHeight / 2 - height / 2
width: 10; height: 10; color: root.cBright
border.color: root.cBorderFocus; border.width: 1
}
}
Text {
text: Pipewire.defaultAudioSink?.audio?.muted ? "Muted" : Math.round((Pipewire.defaultAudioSink?.audio?.volume ?? 0) * 100) + "%"
font.family: root.mono; font.pixelSize: 10; color: root.cText
horizontalAlignment: Text.AlignRight
Layout.preferredWidth: 38
}
Rectangle {
implicitWidth: 30; implicitHeight: 28
color: muteHover.containsMouse ? root.cSelected : "transparent"
border.color: muteHover.containsMouse ? root.cBorderFocus : root.cBorder
Text {
anchors.centerIn: parent
text: Pipewire.defaultAudioSink?.audio?.muted ? "󰝟" : "󰕾"
font.family: root.mono; font.pixelSize: 12; color: root.cText
}
MouseArea {
id: muteHover
anchors.fill: parent; hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (Pipewire.defaultAudioSink?.audio)
Pipewire.defaultAudioSink.audio.muted = !Pipewire.defaultAudioSink.audio.muted
}
}
}
}
Rectangle { Layout.fillWidth: true; height: 1; color: root.cBorder }
Text {
text: "PLAYING NOW"
font.family: root.mono; font.pixelSize: 10; font.bold: true
color: root.cSubtle
}
Repeater {
model: Pipewire.nodes.values.filter(n => n.isStream && n.audio && n.type === "Stream/Output/Audio")
delegate: RowLayout {
required property var modelData
Layout.fillWidth: true
spacing: 8
ColumnLayout {
Layout.fillWidth: true
spacing: 2
Text {
text: modelData.properties["application.name"] || modelData.description || modelData.name || "Audio stream"
font.family: root.mono; font.pixelSize: 10
color: root.cText
elide: Text.ElideRight
Layout.fillWidth: true
}
Text {
text: modelData.properties["media.title"]
? modelData.properties["media.title"]
: (modelData.properties["media.name"] || "Active playback")
font.family: root.mono; font.pixelSize: 9
color: root.cDim
elide: Text.ElideRight
Layout.fillWidth: true
}
}
Slider {
id: playbackSlider
Layout.preferredWidth: 82
from: 0; to: 1.0; stepSize: 0.01
value: modelData.audio?.volume ?? 0
onMoved: if (modelData.audio) modelData.audio.volume = value
background: Rectangle {
x: playbackSlider.leftPadding
y: playbackSlider.topPadding + playbackSlider.availableHeight / 2 - height / 2
width: playbackSlider.availableWidth; height: 2; color: root.cBorder
Rectangle {
width: playbackSlider.visualPosition * parent.width
height: parent.height
color: root.cAccent
}
}
handle: Rectangle {
x: playbackSlider.leftPadding + playbackSlider.visualPosition * (playbackSlider.availableWidth - width)
y: playbackSlider.topPadding + playbackSlider.availableHeight / 2 - height / 2
width: 8; height: 8; color: root.cText
}
}
Text {
text: Math.round((modelData.audio?.volume ?? 0) * 100) + "%"
font.family: root.mono; font.pixelSize: 9
color: root.cSubtle
Layout.preferredWidth: 30
horizontalAlignment: Text.AlignRight
}
Rectangle {
implicitWidth: 26; implicitHeight: 24
color: streamMuteHover.containsMouse ? root.cSelected : "transparent"
border.color: streamMuteHover.containsMouse ? root.cBorderFocus : "transparent"
Text {
anchors.centerIn: parent
text: modelData.audio?.muted ? "󰝟" : "󰕾"
font.family: root.mono; font.pixelSize: 11
color: modelData.audio?.muted ? root.cDanger : root.cText
}
MouseArea {
id: streamMuteHover
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: if (modelData.audio) modelData.audio.muted = !modelData.audio.muted
}
}
}
}
Text {
text: "No active playback"
visible: Pipewire.nodes.values.filter(n => n.isStream && n.audio && n.type === "Stream/Output/Audio").length === 0
font.family: root.mono; font.pixelSize: 10
color: root.cDim
}
Text {
text: "AVAILABLE OUTPUTS"
font.family: root.mono; font.pixelSize: 10; font.bold: true; color: root.cSubtle
}
Repeater {
model: Pipewire.nodes.values.filter(n => n.type === "Audio/Sink")
delegate: ColumnLayout {
required property var modelData
readonly property bool isDefault: Pipewire.defaultAudioSink?.id === modelData.id
Layout.fillWidth: true
spacing: 3
RowLayout {
Layout.fillWidth: true
Text {
text: isDefault ? "●" : "○"
font.family: root.mono; font.pixelSize: 10
color: isDefault ? root.cAccent : root.cDim
}
Text {
text: modelData.description || modelData.name || "Output device"
font.family: root.mono; font.pixelSize: 10
color: isDefault ? root.cBright : root.cText
Layout.fillWidth: true; elide: Text.ElideRight
}
Text {
text: Math.round((modelData.audio?.volume ?? 0) * 100) + "%"
font.family: root.mono; font.pixelSize: 9; color: root.cSubtle
}
MouseArea {
anchors.fill: parent; cursorShape: Qt.PointingHandCursor
onClicked: Pipewire.preferredDefaultAudioSink = modelData
}
}
Slider {
id: sinkSlider
Layout.fillWidth: true
from: 0; to: 1.0; stepSize: 0.01
value: modelData.audio?.volume ?? 0
onMoved: if (modelData.audio) modelData.audio.volume = value
background: Rectangle {
x: sinkSlider.leftPadding
y: sinkSlider.topPadding + sinkSlider.availableHeight / 2 - height / 2
width: sinkSlider.availableWidth; height: 2; color: root.cBorder
Rectangle { width: sinkSlider.visualPosition * parent.width; height: parent.height; color: isDefault ? root.cAccent : root.cDim }
}
handle: Rectangle {
x: sinkSlider.leftPadding + sinkSlider.visualPosition * (sinkSlider.availableWidth - width)
y: sinkSlider.topPadding + sinkSlider.availableHeight / 2 - height / 2
width: 8; height: 8; color: root.cText
}
}
}
}
Rectangle { Layout.fillWidth: true; height: 1; color: root.cBorder; Layout.topMargin: 2 }
Text {
text: "INPUTS"
font.family: root.mono; font.pixelSize: 10; font.bold: true; color: root.cSubtle
}
Repeater {
model: Pipewire.nodes.values.filter(n => n.type === "Audio/Source" && !n.description.includes("Monitor"))
delegate: ColumnLayout {
required property var modelData
Layout.fillWidth: true
spacing: 3
RowLayout {
Layout.fillWidth: true
Text { text: ""; font.family: root.mono; font.pixelSize: 10; color: root.cSubtle }
Text {
text: modelData.description || modelData.name || "Microphone"
font.family: root.mono; font.pixelSize: 10; color: root.cText
Layout.fillWidth: true; elide: Text.ElideRight
}
Text {
text: Math.round((modelData.audio?.volume ?? 0) * 100) + "%"
font.family: root.mono; font.pixelSize: 9; color: root.cSubtle
}
}
Slider {
id: srcSlider
Layout.fillWidth: true
from: 0; to: 1.0; stepSize: 0.01
value: modelData.audio?.volume ?? 0
onMoved: if (modelData.audio) modelData.audio.volume = value
background: Rectangle {
x: srcSlider.leftPadding
y: srcSlider.topPadding + srcSlider.availableHeight / 2 - height / 2
width: srcSlider.availableWidth; height: 2; color: root.cBorder
Rectangle { width: srcSlider.visualPosition * parent.width; height: parent.height; color: root.cTeal }
}
handle: Rectangle {
x: srcSlider.leftPadding + srcSlider.visualPosition * (srcSlider.availableWidth - width)
y: srcSlider.topPadding + srcSlider.availableHeight / 2 - height / 2
width: 8; height: 8; color: root.cText
}
}
}
}
}
}
}
}
}
}
}
// ───────────── POWER MENU ─────────────
PanelWindow {
id: powerWindow
visible: root.powerMenuOpen
screen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name) ?? Quickshell.screens[0]
anchors { top: true; bottom: true; left: true; right: true }
color: "transparent"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.namespace: "power-menu"
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
exclusionMode: ExclusionMode.Ignore
MouseArea {
anchors.fill: parent
onClicked: root.closeMenus()
}
Rectangle {
anchors.centerIn: parent
width: 330
height: root.powerConfirmAction === "" ? 310 : 190
color: root.cPanel
border.color: root.cBorder
border.width: 1
radius: 0
MouseArea { anchors.fill: parent }
FocusScope {
anchors.fill: parent
focus: powerWindow.visible
Keys.onEscapePressed: {
if (root.powerConfirmAction !== "") root.powerConfirmAction = ""
else root.closeMenus()
}
ColumnLayout {
anchors.fill: parent
anchors.margins: 18
spacing: 8
Text {
text: root.powerConfirmAction === "" ? "POWER" : "CONFIRM ACTION"
font.family: root.mono
font.pixelSize: 11
font.bold: true
color: root.cBright
}
Text {
Layout.fillWidth: true
text: root.powerConfirmAction === "" ? "Choose what to do with this session." :
(root.powerConfirmAction === "reboot" ? "Restart the computer now?" : "Shut down the computer now?")
font.family: root.mono
font.pixelSize: 10
color: root.cSubtle
wrapMode: Text.WordWrap
}
Rectangle { Layout.fillWidth: true; height: 1; color: root.cBorder }
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 4
visible: root.powerConfirmAction === ""
Repeater {
model: [
{ label: "Log out", icon: "󰍃", action: "logout", confirm: false },
{ label: "Restart…", icon: "󰜉", action: "reboot", confirm: true },
{ label: "Shut down…", icon: "", action: "poweroff", confirm: true },
]
delegate: Rectangle {
required property var modelData
Layout.fillWidth: true
implicitHeight: 42
color: powerActionHover.containsMouse ? root.cSurfaceHover : "transparent"
border.color: powerActionHover.containsMouse ? root.cBorderFocus : "transparent"
border.width: 1
RowLayout {
anchors.fill: parent
anchors.leftMargin: 10
anchors.rightMargin: 10
spacing: 12
Text {
text: modelData.icon
font.family: root.mono
font.pixelSize: 14
color: root.cSubtle
}
Text {
Layout.fillWidth: true
text: modelData.label
font.family: root.mono
font.pixelSize: 11
color: root.cText
}
Text {
text: modelData.confirm ? "›" : ""
font.family: root.mono
font.pixelSize: 14
color: root.cSubtle
}
}
MouseArea {
id: powerActionHover
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (modelData.confirm) root.powerConfirmAction = modelData.action
else root.runPowerAction(modelData.action)
}
}
}
}
}
RowLayout {
Layout.fillWidth: true
visible: root.powerConfirmAction !== ""
Item { Layout.fillWidth: true }
Rectangle {
implicitWidth: 88
implicitHeight: 34
color: cancelPowerHover.containsMouse ? root.cSurfaceHover : "transparent"
border.color: root.cBorder
border.width: 1
Text { anchors.centerIn: parent; text: "Cancel"; font.family: root.mono; font.pixelSize: 10; color: root.cText }
MouseArea {
id: cancelPowerHover
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.powerConfirmAction = ""
}
}
Rectangle {
implicitWidth: 104
implicitHeight: 34
color: confirmPowerHover.containsMouse ? root.cSurfaceHover : root.cSelected
border.color: root.cBorderFocus
border.width: 1
Text {
anchors.centerIn: parent
text: root.powerConfirmAction === "reboot" ? "Restart" : "Shut down"
font.family: root.mono
font.pixelSize: 10
color: root.cBright
}
MouseArea {
id: confirmPowerHover
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.runPowerAction(root.powerConfirmAction)
}
}
}
}
}
}
}
}