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

1821 lines
87 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 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"])
}
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 var btDevices: []
PwObjectTracker { objects: [Pipewire] }
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
data['bt_devices'].append({'mac': mac, 'name': name, 'connected': conn})
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) {}
}
}
}
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'),
('Network Connections', 'foot -e nmtui'),
('Bluetooth Manager', 'foot -e bluetoothctl'),
('Audio Mixer', 'foot -e pulsemixer'),
]
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
}
}
}
}
}
// Action Buttons: Rescan & NMTUI
RowLayout {
Layout.fillWidth: true
spacing: 8
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
}
}
Rectangle {
Layout.fillWidth: true; implicitHeight: 28; radius: 0
color: nmtuiHover.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.cTeal }
Text { text: "Open NMTUI"; font.family: root.mono; font.pixelSize: 11; color: root.cText }
}
MouseArea {
id: nmtuiHover
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
Quickshell.execDetached(["foot", "-e", "nmtui"])
root.activeApplet = ""
}
}
}
}
// 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.btDevices.length > 0 ? (root.btDevices.length + " paired devices") : "No devices"
font.family: root.mono; font.pixelSize: 10; color: root.cSubtle
}
}
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
Quickshell.execDetached(["bluetoothctl", "power", newState ? "on" : "off"])
statusProc.running = true
}
}
}
}
}
// Action Buttons: Scan & Bluetoothctl
RowLayout {
Layout.fillWidth: true
spacing: 8
Rectangle {
Layout.fillWidth: true; implicitHeight: 28; radius: 0
color: btScanHover.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: "Refresh"; font.family: root.mono; font.pixelSize: 11; color: root.cText }
}
MouseArea {
id: btScanHover
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: statusProc.running = true
}
}
Rectangle {
Layout.fillWidth: true; implicitHeight: 28; radius: 0
color: btCliHover.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.cTeal }
Text { text: "Open Bluetoothctl"; font.family: root.mono; font.pixelSize: 11; color: root.cText }
}
MouseArea {
id: btCliHover
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
Quickshell.execDetached(["foot", "-e", "bluetoothctl"])
root.activeApplet = ""
}
}
}
}
// Paired Devices Header
Text {
text: "PAIRED & KNOWN 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: 42
radius: 0
color: modelData.connected ? root.cSelected : (btItemHover.containsMouse ? root.cSurfaceHover : root.cSurface)
border.color: modelData.connected ? root.cAccent : "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 ? 75 : 62
implicitHeight: 22
radius: 0
color: modelData.connected ? root.cDanger : root.cSurfaceHover
border.color: root.cBorder
Text {
anchors.centerIn: parent
text: modelData.connected ? "Disconnect" : "Connect"
font.family: root.mono; font.pixelSize: 9
color: modelData.connected ? root.cBright : root.cText
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
const action = modelData.connected ? "disconnect" : "connect"
Quickshell.execDetached(["bluetoothctl", action, modelData.mac])
statusProc.running = true
}
}
}
}
MouseArea {
id: btItemHover
anchors.fill: parent
hoverEnabled: true
onClicked: {
const action = modelData.connected ? "disconnect" : "connect"
Quickshell.execDetached(["bluetoothctl", action, modelData.mac])
statusProc.running = true
}
}
}
// 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 ? "No paired devices found" : "Bluetooth is powered off"
font.family: root.mono; font.pixelSize: 12; color: root.cDim
}
Text {
Layout.alignment: Qt.AlignHCenter
text: "Use 'Open Bluetoothctl' above to scan & pair"
font.family: root.mono; font.pixelSize: 10; color: root.cSubtle
visible: root.btOn
}
}
}
}
}
// ══════════════════ 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
Quickshell.execDetached(["wpctl", "set-volume", "@DEFAULT_AUDIO_SINK@", value.toFixed(2)])
}
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
Quickshell.execDetached(["wpctl", "set-mute", "@DEFAULT_AUDIO_SINK@", "toggle"])
}
}
}
}
Rectangle { Layout.fillWidth: true; height: 1; color: root.cBorder }
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.mediaClass === "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: Quickshell.execDetached(["wpctl", "set-default", String(modelData.id)])
}
}
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.mediaClass === "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)
}
}
}
}
}
}
}
}