Regenerated
This commit is contained in:
+1042
-400
File diff suppressed because it is too large
Load Diff
@@ -2964,6 +2964,7 @@ bind = , XF86Sleep, exec, systemctl suspend
|
|||||||
bind = , XF86PowerOff, exec, systemctl poweroff
|
bind = , XF86PowerOff, exec, systemctl poweroff
|
||||||
bind = , XF86WakeUp, exec, systemctl suspend
|
bind = , XF86WakeUp, exec, systemctl suspend
|
||||||
bind = , XF86ScreenSaver, exec, loginctl lock-session
|
bind = , XF86ScreenSaver, exec, loginctl lock-session
|
||||||
|
bind = $mainMod, P, exec, qs -c powermgmt
|
||||||
|
|
||||||
#########################
|
#########################
|
||||||
# Start apps
|
# Start apps
|
||||||
@@ -3657,6 +3658,640 @@ ShellRoot {
|
|||||||
}
|
}
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
|
** =generated/.config/quickshell/powermgmt/shell.qml=
|
||||||
|
Provides powermanagement settings
|
||||||
|
#+BEGIN_SRC qml :tangle generated/.config/quickshell/powermgmt/shell.qml :noweb yes :mkdirp yes :eval never
|
||||||
|
// --- Power Management Panel for NixOS + Hyprland ---
|
||||||
|
// Launch with: bind=$MAINMOD,p,exec,qs powermgmt
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
ShellRoot {
|
||||||
|
// ── Catppuccin Mocha — full palette from colors.css ───────────────────
|
||||||
|
QtObject {
|
||||||
|
id: colors
|
||||||
|
// Base layers
|
||||||
|
readonly property color crust: "#11111b"
|
||||||
|
readonly property color mantle: "#181825"
|
||||||
|
readonly property color base: "#1e1e2e"
|
||||||
|
// Surfaces
|
||||||
|
readonly property color surface0: "#313244"
|
||||||
|
readonly property color surface1: "#45475a"
|
||||||
|
readonly property color surface2: "#585b70"
|
||||||
|
// Overlays
|
||||||
|
readonly property color overlay0: "#6c7086"
|
||||||
|
readonly property color overlay1: "#7f849c"
|
||||||
|
readonly property color overlay2: "#9399b2"
|
||||||
|
// Text
|
||||||
|
readonly property color subtext0: "#a6adc8"
|
||||||
|
readonly property color subtext1: "#bac2de"
|
||||||
|
readonly property color text: "#cdd6f4"
|
||||||
|
// Accent colours
|
||||||
|
readonly property color border: "#96cdd2"
|
||||||
|
readonly property color rosewater: "#f5e0dc"
|
||||||
|
readonly property color flamingo: "#f2cdcd"
|
||||||
|
readonly property color pink: "#f5c2e7"
|
||||||
|
readonly property color mauve: "#cba6f7"
|
||||||
|
readonly property color red: "#f38ba8"
|
||||||
|
readonly property color maroon: "#eba0ac"
|
||||||
|
readonly property color peach: "#fab387"
|
||||||
|
readonly property color yellow: "#f9e2af"
|
||||||
|
readonly property color green: "#a6e3a1"
|
||||||
|
readonly property color teal: "#94e2d5"
|
||||||
|
readonly property color sapphire: "#74c7ec"
|
||||||
|
readonly property color blue: "#89b4fa"
|
||||||
|
readonly property color lavender: "#b4befe"
|
||||||
|
// Convenience: semi-transparent base
|
||||||
|
readonly property color baseAlpha: Qt.rgba(0.118, 0.118, 0.180, 0.8)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── State ──────────────────────────────────────────────────────────────
|
||||||
|
property string activeProfile: "balanced" // performance | balanced | powersave
|
||||||
|
property bool lidClose: true
|
||||||
|
property bool wifiPower: true
|
||||||
|
property bool bluetoothOff: false
|
||||||
|
property string statusMsg: ""
|
||||||
|
property bool statusOk: true
|
||||||
|
|
||||||
|
// Hypridle timeouts — initialised from current hypridle.conf values:
|
||||||
|
// lock @ 600s = 10 min
|
||||||
|
// dpms @ 900s = 15 min
|
||||||
|
// suspend @ 1200s = 20 min
|
||||||
|
property int lockTimeout: 10 // minutes (0 = disabled)
|
||||||
|
property int dpmsTimeout: 15 // minutes (0 = disabled)
|
||||||
|
property int suspendTimeout: 20 // minutes (0 = disabled)
|
||||||
|
|
||||||
|
property int brightnessLevel: 80 // percent
|
||||||
|
|
||||||
|
// ── hypridle config writer ─────────────────────────────────────────────
|
||||||
|
// Mirrors the structure of the original hypridle.conf exactly,
|
||||||
|
// preserving: after_sleep_cmd, ignore_dbus_inhibit, on-resume for dpms.
|
||||||
|
function buildHypridleConf() {
|
||||||
|
var s =
|
||||||
|
"# --- managed by quickshell powermgmt panel ---\n" +
|
||||||
|
"general {\n" +
|
||||||
|
" lock_cmd = hyprlock\n" +
|
||||||
|
" before_sleep_cmd = hyprlock\n" +
|
||||||
|
" after_sleep_cmd = hyprctl dispatch dpms on\n" +
|
||||||
|
" ignore_dbus_inhibit = false\n" +
|
||||||
|
"}\n"
|
||||||
|
|
||||||
|
if (lockTimeout > 0) {
|
||||||
|
s +=
|
||||||
|
"\nlistener {\n" +
|
||||||
|
" timeout = " + (lockTimeout * 60) + "\n" +
|
||||||
|
" on-timeout = hyprlock\n" +
|
||||||
|
"}\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dpmsTimeout > 0) {
|
||||||
|
s +=
|
||||||
|
"\nlistener {\n" +
|
||||||
|
" timeout = " + (dpmsTimeout * 60) + "\n" +
|
||||||
|
" on-timeout = hyprctl dispatch dpms off\n" +
|
||||||
|
" on-resume = hyprctl dispatch dpms on\n" +
|
||||||
|
"}\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (suspendTimeout > 0) {
|
||||||
|
s +=
|
||||||
|
"\nlistener {\n" +
|
||||||
|
" timeout = " + (suspendTimeout * 60) + "\n" +
|
||||||
|
" on-timeout = systemctl suspend\n" +
|
||||||
|
"}\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyHypridle() {
|
||||||
|
var conf = buildHypridleConf()
|
||||||
|
// Use printf to avoid shell interpretation of special chars in the conf
|
||||||
|
writeProc.command = ["bash", "-c",
|
||||||
|
"printf '%s' " + JSON.stringify(conf) +
|
||||||
|
" > ~/.config/hypr/hypridle.conf && pkill hypridle; hypridle &"]
|
||||||
|
writeProc.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Helpers ────────────────────────────────────────────────────────────
|
||||||
|
function run(cmd) {
|
||||||
|
runnerProc.command = cmd
|
||||||
|
runnerProc.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function showStatus(msg, ok) {
|
||||||
|
statusMsg = msg
|
||||||
|
statusOk = ok
|
||||||
|
statusTimer.restart()
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyProfile(p) {
|
||||||
|
activeProfile = p
|
||||||
|
if (p === "performance") run(["powerprofilesctl", "set", "performance"])
|
||||||
|
else if (p === "balanced") run(["powerprofilesctl", "set", "balanced"])
|
||||||
|
else run(["powerprofilesctl", "set", "power-saver"])
|
||||||
|
showStatus("Profile → " + p, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyBrightness(v) {
|
||||||
|
brightnessLevel = v
|
||||||
|
run(["brightnessctl", "set", v + "%"])
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLockTimeout(mins) {
|
||||||
|
lockTimeout = mins
|
||||||
|
applyHypridle()
|
||||||
|
showStatus(mins === 0 ? "Auto-lock disabled" : "Lock after " + mins + " min", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function setDpmsTimeout(mins) {
|
||||||
|
dpmsTimeout = mins
|
||||||
|
applyHypridle()
|
||||||
|
showStatus(mins === 0 ? "Screen timeout disabled" : "Screen off after " + mins + " min", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSuspendTimeout(mins) {
|
||||||
|
suspendTimeout = mins
|
||||||
|
applyHypridle()
|
||||||
|
showStatus(mins === 0 ? "Auto-suspend disabled" : "Suspend after " + mins + " min", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleWifiPower(v) {
|
||||||
|
wifiPower = v
|
||||||
|
// Replace wlan0 with your actual interface if different (check: ip link)
|
||||||
|
run(v ? ["iw", "dev", "wlan0", "set", "power_save", "on"]
|
||||||
|
: ["iw", "dev", "wlan0", "set", "power_save", "off"])
|
||||||
|
showStatus("WiFi power save " + (v ? "on" : "off"), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleBluetooth(v) {
|
||||||
|
bluetoothOff = v
|
||||||
|
run(v ? ["rfkill", "block", "bluetooth"]
|
||||||
|
: ["rfkill", "unblock", "bluetooth"])
|
||||||
|
showStatus("Bluetooth " + (v ? "disabled" : "enabled"), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleLid(v) {
|
||||||
|
lidClose = v
|
||||||
|
run(["bash", "-c",
|
||||||
|
"loginctl set-handle-lid-switch " + (v ? "suspend" : "ignore")])
|
||||||
|
showStatus("Lid close → " + (v ? "suspend" : "ignore"), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Processes ──────────────────────────────────────────────────────────
|
||||||
|
Process {
|
||||||
|
id: runnerProc
|
||||||
|
onExited: (code) => {
|
||||||
|
if (code !== 0)
|
||||||
|
showStatus("Command failed (exit " + code + ")", false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: writeProc
|
||||||
|
onExited: (code) => {
|
||||||
|
if (code !== 0)
|
||||||
|
showStatus("hypridle reload failed (exit " + code + ")", false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: statusTimer
|
||||||
|
interval: 3000
|
||||||
|
onTriggered: statusMsg = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Window ─────────────────────────────────────────────────────────────
|
||||||
|
FloatingWindow {
|
||||||
|
title: "quickshell-powermgmt"
|
||||||
|
visible: true
|
||||||
|
width: 540
|
||||||
|
height: mainCol.implicitHeight + 40
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
Shortcut { sequence: "Escape"; onActivated: Qt.quit() }
|
||||||
|
|
||||||
|
// Gradient border — uses border colour from colors.css as midpoint
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: -2
|
||||||
|
radius: 20
|
||||||
|
z: -1
|
||||||
|
gradient: Gradient {
|
||||||
|
orientation: Gradient.Horizontal
|
||||||
|
GradientStop { position: 0.0; color: colors.mauve }
|
||||||
|
GradientStop { position: 0.5; color: colors.border }
|
||||||
|
GradientStop { position: 1.0; color: colors.blue }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: 18
|
||||||
|
color: colors.base
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: mainCol
|
||||||
|
anchors {
|
||||||
|
top: parent.top
|
||||||
|
left: parent.left
|
||||||
|
right: parent.right
|
||||||
|
margins: 20
|
||||||
|
}
|
||||||
|
spacing: 14
|
||||||
|
|
||||||
|
// ── Header ─────────────────────────────────────────────
|
||||||
|
RowLayout {
|
||||||
|
Layout.topMargin: 4
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
Text { text: "⚡"; font.pixelSize: 18 }
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Power Management"
|
||||||
|
color: colors.text
|
||||||
|
font.pixelSize: 16
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
Item { Layout.fillWidth: true }
|
||||||
|
Text {
|
||||||
|
text: "✕"
|
||||||
|
color: colors.surface2
|
||||||
|
font.pixelSize: 14
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: Qt.quit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Power Profile ───────────────────────────────────────
|
||||||
|
SectionLabel { label: "Power Profile" }
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
ProfileButton {
|
||||||
|
label: "Performance"
|
||||||
|
icon: "🚀"
|
||||||
|
accent: colors.peach
|
||||||
|
active: activeProfile === "performance"
|
||||||
|
onClicked: applyProfile("performance")
|
||||||
|
}
|
||||||
|
ProfileButton {
|
||||||
|
label: "Balanced"
|
||||||
|
icon: "⚖"
|
||||||
|
accent: colors.blue
|
||||||
|
active: activeProfile === "balanced"
|
||||||
|
onClicked: applyProfile("balanced")
|
||||||
|
}
|
||||||
|
ProfileButton {
|
||||||
|
label: "Power Save"
|
||||||
|
icon: "🌿"
|
||||||
|
accent: colors.green
|
||||||
|
active: activeProfile === "powersave"
|
||||||
|
onClicked: applyProfile("powersave")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Brightness ──────────────────────────────────────────
|
||||||
|
SectionLabel { label: "Screen Brightness" }
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
Text { text: "🔅"; font.pixelSize: 13 }
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
height: 6
|
||||||
|
radius: 3
|
||||||
|
color: colors.surface0
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width * (brightnessLevel / 100)
|
||||||
|
height: parent.height
|
||||||
|
radius: parent.radius
|
||||||
|
gradient: Gradient {
|
||||||
|
orientation: Gradient.Horizontal
|
||||||
|
GradientStop { position: 0.0; color: colors.yellow }
|
||||||
|
GradientStop { position: 1.0; color: colors.peach }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: (mouse) => {
|
||||||
|
var v = Math.round((mouse.x / width) * 100)
|
||||||
|
applyBrightness(Math.max(5, Math.min(100, v)))
|
||||||
|
}
|
||||||
|
onPositionChanged: (mouse) => {
|
||||||
|
if (pressed) {
|
||||||
|
var v = Math.round((mouse.x / width) * 100)
|
||||||
|
applyBrightness(Math.max(5, Math.min(100, v)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text { text: "🔆"; font.pixelSize: 13 }
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: brightnessLevel + "%"
|
||||||
|
color: colors.subtext0
|
||||||
|
font.pixelSize: 12
|
||||||
|
width: 32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Hypridle Timeouts ───────────────────────────────────
|
||||||
|
// These three rows map 1-to-1 to the three listeners in
|
||||||
|
// hypridle.conf and rewrite + reload the daemon on change.
|
||||||
|
SectionLabel { label: "Idle Timeouts (hypridle)" }
|
||||||
|
|
||||||
|
TimeoutRow {
|
||||||
|
label: "Auto-lock"
|
||||||
|
icon: "🔒"
|
||||||
|
accent: colors.lavender
|
||||||
|
value: lockTimeout
|
||||||
|
options: [5, 10, 15, 30, 0]
|
||||||
|
onPicked: (v) => setLockTimeout(v)
|
||||||
|
}
|
||||||
|
TimeoutRow {
|
||||||
|
label: "Screen off"
|
||||||
|
icon: "🖥"
|
||||||
|
accent: colors.sapphire
|
||||||
|
value: dpmsTimeout
|
||||||
|
options: [5, 10, 15, 30, 0]
|
||||||
|
onPicked: (v) => setDpmsTimeout(v)
|
||||||
|
}
|
||||||
|
TimeoutRow {
|
||||||
|
label: "Suspend"
|
||||||
|
icon: "💤"
|
||||||
|
accent: colors.mauve
|
||||||
|
value: suspendTimeout
|
||||||
|
options: [15, 20, 30, 60, 0]
|
||||||
|
onPicked: (v) => setSuspendTimeout(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Toggles ─────────────────────────────────────────────
|
||||||
|
SectionLabel { label: "Settings" }
|
||||||
|
|
||||||
|
ToggleRow {
|
||||||
|
label: "Suspend on lid close"
|
||||||
|
icon: "💻"
|
||||||
|
checked: lidClose
|
||||||
|
onToggled: (v) => toggleLid(v)
|
||||||
|
}
|
||||||
|
ToggleRow {
|
||||||
|
label: "WiFi power saving"
|
||||||
|
icon: "📶"
|
||||||
|
checked: wifiPower
|
||||||
|
onToggled: (v) => toggleWifiPower(v)
|
||||||
|
}
|
||||||
|
ToggleRow {
|
||||||
|
label: "Bluetooth off"
|
||||||
|
icon: "🔵"
|
||||||
|
checked: bluetoothOff
|
||||||
|
onToggled: (v) => toggleBluetooth(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Status bar ──────────────────────────────────────────
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
height: visible ? 32 : 0
|
||||||
|
visible: statusMsg !== ""
|
||||||
|
radius: 8
|
||||||
|
color: statusOk
|
||||||
|
? Qt.rgba(colors.green.r, colors.green.g, colors.green.b, 0.12)
|
||||||
|
: Qt.rgba(colors.red.r, colors.red.g, colors.red.b, 0.12)
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: statusMsg
|
||||||
|
color: statusOk ? colors.green : colors.red
|
||||||
|
font.pixelSize: 12
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Actions ─────────────────────────────────────────────
|
||||||
|
SectionLabel { label: "Actions" }
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.bottomMargin: 8
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
ActionButton { label: "Lock"; icon: "🔒"; accent: colors.blue; onClicked: run(["hyprlock"]) }
|
||||||
|
ActionButton { label: "Suspend"; icon: "💤"; accent: colors.mauve; onClicked: run(["systemctl", "suspend"]) }
|
||||||
|
ActionButton { label: "Hibernate"; icon: "🌙"; accent: colors.sapphire; onClicked: run(["systemctl", "hibernate"]) }
|
||||||
|
ActionButton { label: "Reboot"; icon: "🔄"; accent: colors.peach; onClicked: run(["systemctl", "reboot"]) }
|
||||||
|
ActionButton { label: "Shutdown"; icon: "⏻"; accent: colors.red; onClicked: run(["systemctl", "poweroff"]) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Inline components ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
component SectionLabel: Text {
|
||||||
|
required property string label
|
||||||
|
text: label.toUpperCase()
|
||||||
|
color: colors.overlay0
|
||||||
|
font.pixelSize: 10
|
||||||
|
font.bold: true
|
||||||
|
letterSpacing: 1.2
|
||||||
|
}
|
||||||
|
|
||||||
|
component ProfileButton: Rectangle {
|
||||||
|
required property string label
|
||||||
|
required property string icon
|
||||||
|
required property color accent
|
||||||
|
required property bool active
|
||||||
|
signal clicked
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
height: 56
|
||||||
|
radius: 10
|
||||||
|
color: active ? Qt.rgba(accent.r, accent.g, accent.b, 0.15) : colors.surface0
|
||||||
|
border.color: active ? accent : "transparent"
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Behavior on color { ColorAnimation { duration: 120 } }
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 2
|
||||||
|
Text {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
text: icon
|
||||||
|
font.pixelSize: 18
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
text: label
|
||||||
|
color: active ? accent : colors.subtext0
|
||||||
|
font.pixelSize: 11
|
||||||
|
font.bold: active
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: parent.clicked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component TimeoutRow: RowLayout {
|
||||||
|
required property string label
|
||||||
|
required property string icon
|
||||||
|
required property color accent
|
||||||
|
required property int value
|
||||||
|
required property var options
|
||||||
|
signal picked(int v)
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
Text { text: icon; font.pixelSize: 14 }
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: label
|
||||||
|
color: colors.subtext1
|
||||||
|
font.pixelSize: 12
|
||||||
|
Layout.preferredWidth: 100
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { Layout.fillWidth: true }
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: options
|
||||||
|
delegate: Rectangle {
|
||||||
|
readonly property int mins: modelData
|
||||||
|
readonly property bool sel: value === mins
|
||||||
|
|
||||||
|
width: mins === 0 ? 40 : 36
|
||||||
|
height: 24
|
||||||
|
radius: 6
|
||||||
|
color: sel ? Qt.rgba(accent.r, accent.g, accent.b, 0.2) : colors.surface0
|
||||||
|
border.color: sel ? accent : "transparent"
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: mins === 0 ? "off" : mins + "m"
|
||||||
|
color: sel ? accent : colors.subtext0
|
||||||
|
font.pixelSize: 10
|
||||||
|
font.bold: sel
|
||||||
|
}
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: picked(mins)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component ToggleRow: RowLayout {
|
||||||
|
required property string label
|
||||||
|
required property string icon
|
||||||
|
required property bool checked
|
||||||
|
signal toggled(bool v)
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
Text { text: icon; font.pixelSize: 14 }
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: label
|
||||||
|
color: colors.subtext1
|
||||||
|
font.pixelSize: 12
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { Layout.fillWidth: true }
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 42
|
||||||
|
height: 22
|
||||||
|
radius: 11
|
||||||
|
color: checked ? Qt.rgba(colors.green.r, colors.green.g, colors.green.b, 0.3) : colors.surface1
|
||||||
|
border.color: checked ? colors.green : colors.surface2
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Behavior on color { ColorAnimation { duration: 150 } }
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
x: checked ? parent.width - width - 3 : 3
|
||||||
|
y: 3
|
||||||
|
width: 16; height: 16; radius: 8
|
||||||
|
color: checked ? colors.green : colors.surface2
|
||||||
|
|
||||||
|
Behavior on x { NumberAnimation { duration: 150; easing.type: Easing.OutCubic } }
|
||||||
|
Behavior on color { ColorAnimation { duration: 150 } }
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: toggled(!checked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component ActionButton: Rectangle {
|
||||||
|
required property string label
|
||||||
|
required property string icon
|
||||||
|
required property color accent
|
||||||
|
signal clicked
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
height: 52
|
||||||
|
radius: 10
|
||||||
|
color: colors.surface0
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 2
|
||||||
|
Text {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
text: icon
|
||||||
|
font.pixelSize: 16
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
text: label
|
||||||
|
color: colors.subtext0
|
||||||
|
font.pixelSize: 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: hoverBorder
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: parent.radius
|
||||||
|
color: "transparent"
|
||||||
|
border.color: accent
|
||||||
|
border.width: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
hoverEnabled: true
|
||||||
|
onEntered: hoverBorder.border.width = 1
|
||||||
|
onExited: hoverBorder.border.width = 0
|
||||||
|
onClicked: parent.clicked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
** =generated/.config/quickshell/powermenu/shell.qml=
|
** =generated/.config/quickshell/powermenu/shell.qml=
|
||||||
Provides a powermenu
|
Provides a powermenu
|
||||||
#+BEGIN_SRC qml :tangle generated/.config/quickshell/powermenu/shell.qml :noweb yes :mkdirp yes :eval never
|
#+BEGIN_SRC qml :tangle generated/.config/quickshell/powermenu/shell.qml :noweb yes :mkdirp yes :eval never
|
||||||
|
|||||||
@@ -138,6 +138,7 @@ bind = , XF86Sleep, exec, systemctl suspend
|
|||||||
bind = , XF86PowerOff, exec, systemctl poweroff
|
bind = , XF86PowerOff, exec, systemctl poweroff
|
||||||
bind = , XF86WakeUp, exec, systemctl suspend
|
bind = , XF86WakeUp, exec, systemctl suspend
|
||||||
bind = , XF86ScreenSaver, exec, loginctl lock-session
|
bind = , XF86ScreenSaver, exec, loginctl lock-session
|
||||||
|
bind = $mainMod, P, exec, qs -c powermgmt
|
||||||
|
|
||||||
#########################
|
#########################
|
||||||
# Start apps
|
# Start apps
|
||||||
|
|||||||
@@ -0,0 +1,630 @@
|
|||||||
|
// --- This file has been auto-generated. For permanent changes alter the appropriate block in the README.org. ---
|
||||||
|
// --- Power Management Panel for NixOS + Hyprland ---
|
||||||
|
// Launch with: bind=$MAINMOD,p,exec,qs powermgmt
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
ShellRoot {
|
||||||
|
// ── Catppuccin Mocha — full palette from colors.css ───────────────────
|
||||||
|
QtObject {
|
||||||
|
id: colors
|
||||||
|
// Base layers
|
||||||
|
readonly property color crust: "#11111b"
|
||||||
|
readonly property color mantle: "#181825"
|
||||||
|
readonly property color base: "#1e1e2e"
|
||||||
|
// Surfaces
|
||||||
|
readonly property color surface0: "#313244"
|
||||||
|
readonly property color surface1: "#45475a"
|
||||||
|
readonly property color surface2: "#585b70"
|
||||||
|
// Overlays
|
||||||
|
readonly property color overlay0: "#6c7086"
|
||||||
|
readonly property color overlay1: "#7f849c"
|
||||||
|
readonly property color overlay2: "#9399b2"
|
||||||
|
// Text
|
||||||
|
readonly property color subtext0: "#a6adc8"
|
||||||
|
readonly property color subtext1: "#bac2de"
|
||||||
|
readonly property color text: "#cdd6f4"
|
||||||
|
// Accent colours
|
||||||
|
readonly property color border: "#96cdd2"
|
||||||
|
readonly property color rosewater: "#f5e0dc"
|
||||||
|
readonly property color flamingo: "#f2cdcd"
|
||||||
|
readonly property color pink: "#f5c2e7"
|
||||||
|
readonly property color mauve: "#cba6f7"
|
||||||
|
readonly property color red: "#f38ba8"
|
||||||
|
readonly property color maroon: "#eba0ac"
|
||||||
|
readonly property color peach: "#fab387"
|
||||||
|
readonly property color yellow: "#f9e2af"
|
||||||
|
readonly property color green: "#a6e3a1"
|
||||||
|
readonly property color teal: "#94e2d5"
|
||||||
|
readonly property color sapphire: "#74c7ec"
|
||||||
|
readonly property color blue: "#89b4fa"
|
||||||
|
readonly property color lavender: "#b4befe"
|
||||||
|
// Convenience: semi-transparent base
|
||||||
|
readonly property color baseAlpha: Qt.rgba(0.118, 0.118, 0.180, 0.8)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── State ──────────────────────────────────────────────────────────────
|
||||||
|
property string activeProfile: "balanced" // performance | balanced | powersave
|
||||||
|
property bool lidClose: true
|
||||||
|
property bool wifiPower: true
|
||||||
|
property bool bluetoothOff: false
|
||||||
|
property string statusMsg: ""
|
||||||
|
property bool statusOk: true
|
||||||
|
|
||||||
|
// Hypridle timeouts — initialised from current hypridle.conf values:
|
||||||
|
// lock @ 600s = 10 min
|
||||||
|
// dpms @ 900s = 15 min
|
||||||
|
// suspend @ 1200s = 20 min
|
||||||
|
property int lockTimeout: 10 // minutes (0 = disabled)
|
||||||
|
property int dpmsTimeout: 15 // minutes (0 = disabled)
|
||||||
|
property int suspendTimeout: 20 // minutes (0 = disabled)
|
||||||
|
|
||||||
|
property int brightnessLevel: 80 // percent
|
||||||
|
|
||||||
|
// ── hypridle config writer ─────────────────────────────────────────────
|
||||||
|
// Mirrors the structure of the original hypridle.conf exactly,
|
||||||
|
// preserving: after_sleep_cmd, ignore_dbus_inhibit, on-resume for dpms.
|
||||||
|
function buildHypridleConf() {
|
||||||
|
var s =
|
||||||
|
"# --- managed by quickshell powermgmt panel ---\n" +
|
||||||
|
"general {\n" +
|
||||||
|
" lock_cmd = hyprlock\n" +
|
||||||
|
" before_sleep_cmd = hyprlock\n" +
|
||||||
|
" after_sleep_cmd = hyprctl dispatch dpms on\n" +
|
||||||
|
" ignore_dbus_inhibit = false\n" +
|
||||||
|
"}\n"
|
||||||
|
|
||||||
|
if (lockTimeout > 0) {
|
||||||
|
s +=
|
||||||
|
"\nlistener {\n" +
|
||||||
|
" timeout = " + (lockTimeout * 60) + "\n" +
|
||||||
|
" on-timeout = hyprlock\n" +
|
||||||
|
"}\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dpmsTimeout > 0) {
|
||||||
|
s +=
|
||||||
|
"\nlistener {\n" +
|
||||||
|
" timeout = " + (dpmsTimeout * 60) + "\n" +
|
||||||
|
" on-timeout = hyprctl dispatch dpms off\n" +
|
||||||
|
" on-resume = hyprctl dispatch dpms on\n" +
|
||||||
|
"}\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (suspendTimeout > 0) {
|
||||||
|
s +=
|
||||||
|
"\nlistener {\n" +
|
||||||
|
" timeout = " + (suspendTimeout * 60) + "\n" +
|
||||||
|
" on-timeout = systemctl suspend\n" +
|
||||||
|
"}\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyHypridle() {
|
||||||
|
var conf = buildHypridleConf()
|
||||||
|
// Use printf to avoid shell interpretation of special chars in the conf
|
||||||
|
writeProc.command = ["bash", "-c",
|
||||||
|
"printf '%s' " + JSON.stringify(conf) +
|
||||||
|
" > ~/.config/hypr/hypridle.conf && pkill hypridle; hypridle &"]
|
||||||
|
writeProc.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Helpers ────────────────────────────────────────────────────────────
|
||||||
|
function run(cmd) {
|
||||||
|
runnerProc.command = cmd
|
||||||
|
runnerProc.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function showStatus(msg, ok) {
|
||||||
|
statusMsg = msg
|
||||||
|
statusOk = ok
|
||||||
|
statusTimer.restart()
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyProfile(p) {
|
||||||
|
activeProfile = p
|
||||||
|
if (p === "performance") run(["powerprofilesctl", "set", "performance"])
|
||||||
|
else if (p === "balanced") run(["powerprofilesctl", "set", "balanced"])
|
||||||
|
else run(["powerprofilesctl", "set", "power-saver"])
|
||||||
|
showStatus("Profile → " + p, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyBrightness(v) {
|
||||||
|
brightnessLevel = v
|
||||||
|
run(["brightnessctl", "set", v + "%"])
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLockTimeout(mins) {
|
||||||
|
lockTimeout = mins
|
||||||
|
applyHypridle()
|
||||||
|
showStatus(mins === 0 ? "Auto-lock disabled" : "Lock after " + mins + " min", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function setDpmsTimeout(mins) {
|
||||||
|
dpmsTimeout = mins
|
||||||
|
applyHypridle()
|
||||||
|
showStatus(mins === 0 ? "Screen timeout disabled" : "Screen off after " + mins + " min", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSuspendTimeout(mins) {
|
||||||
|
suspendTimeout = mins
|
||||||
|
applyHypridle()
|
||||||
|
showStatus(mins === 0 ? "Auto-suspend disabled" : "Suspend after " + mins + " min", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleWifiPower(v) {
|
||||||
|
wifiPower = v
|
||||||
|
// Replace wlan0 with your actual interface if different (check: ip link)
|
||||||
|
run(v ? ["iw", "dev", "wlan0", "set", "power_save", "on"]
|
||||||
|
: ["iw", "dev", "wlan0", "set", "power_save", "off"])
|
||||||
|
showStatus("WiFi power save " + (v ? "on" : "off"), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleBluetooth(v) {
|
||||||
|
bluetoothOff = v
|
||||||
|
run(v ? ["rfkill", "block", "bluetooth"]
|
||||||
|
: ["rfkill", "unblock", "bluetooth"])
|
||||||
|
showStatus("Bluetooth " + (v ? "disabled" : "enabled"), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleLid(v) {
|
||||||
|
lidClose = v
|
||||||
|
run(["bash", "-c",
|
||||||
|
"loginctl set-handle-lid-switch " + (v ? "suspend" : "ignore")])
|
||||||
|
showStatus("Lid close → " + (v ? "suspend" : "ignore"), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Processes ──────────────────────────────────────────────────────────
|
||||||
|
Process {
|
||||||
|
id: runnerProc
|
||||||
|
onExited: (code) => {
|
||||||
|
if (code !== 0)
|
||||||
|
showStatus("Command failed (exit " + code + ")", false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: writeProc
|
||||||
|
onExited: (code) => {
|
||||||
|
if (code !== 0)
|
||||||
|
showStatus("hypridle reload failed (exit " + code + ")", false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: statusTimer
|
||||||
|
interval: 3000
|
||||||
|
onTriggered: statusMsg = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Window ─────────────────────────────────────────────────────────────
|
||||||
|
FloatingWindow {
|
||||||
|
title: "quickshell-powermgmt"
|
||||||
|
visible: true
|
||||||
|
width: 540
|
||||||
|
height: mainCol.implicitHeight + 40
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
Shortcut { sequence: "Escape"; onActivated: Qt.quit() }
|
||||||
|
|
||||||
|
// Gradient border — uses border colour from colors.css as midpoint
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: -2
|
||||||
|
radius: 20
|
||||||
|
z: -1
|
||||||
|
gradient: Gradient {
|
||||||
|
orientation: Gradient.Horizontal
|
||||||
|
GradientStop { position: 0.0; color: colors.mauve }
|
||||||
|
GradientStop { position: 0.5; color: colors.border }
|
||||||
|
GradientStop { position: 1.0; color: colors.blue }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: 18
|
||||||
|
color: colors.base
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: mainCol
|
||||||
|
anchors {
|
||||||
|
top: parent.top
|
||||||
|
left: parent.left
|
||||||
|
right: parent.right
|
||||||
|
margins: 20
|
||||||
|
}
|
||||||
|
spacing: 14
|
||||||
|
|
||||||
|
// ── Header ─────────────────────────────────────────────
|
||||||
|
RowLayout {
|
||||||
|
Layout.topMargin: 4
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
Text { text: "⚡"; font.pixelSize: 18 }
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Power Management"
|
||||||
|
color: colors.text
|
||||||
|
font.pixelSize: 16
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
Item { Layout.fillWidth: true }
|
||||||
|
Text {
|
||||||
|
text: "✕"
|
||||||
|
color: colors.surface2
|
||||||
|
font.pixelSize: 14
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: Qt.quit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Power Profile ───────────────────────────────────────
|
||||||
|
SectionLabel { label: "Power Profile" }
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
ProfileButton {
|
||||||
|
label: "Performance"
|
||||||
|
icon: "🚀"
|
||||||
|
accent: colors.peach
|
||||||
|
active: activeProfile === "performance"
|
||||||
|
onClicked: applyProfile("performance")
|
||||||
|
}
|
||||||
|
ProfileButton {
|
||||||
|
label: "Balanced"
|
||||||
|
icon: "⚖"
|
||||||
|
accent: colors.blue
|
||||||
|
active: activeProfile === "balanced"
|
||||||
|
onClicked: applyProfile("balanced")
|
||||||
|
}
|
||||||
|
ProfileButton {
|
||||||
|
label: "Power Save"
|
||||||
|
icon: "🌿"
|
||||||
|
accent: colors.green
|
||||||
|
active: activeProfile === "powersave"
|
||||||
|
onClicked: applyProfile("powersave")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Brightness ──────────────────────────────────────────
|
||||||
|
SectionLabel { label: "Screen Brightness" }
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
Text { text: "🔅"; font.pixelSize: 13 }
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
height: 6
|
||||||
|
radius: 3
|
||||||
|
color: colors.surface0
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width * (brightnessLevel / 100)
|
||||||
|
height: parent.height
|
||||||
|
radius: parent.radius
|
||||||
|
gradient: Gradient {
|
||||||
|
orientation: Gradient.Horizontal
|
||||||
|
GradientStop { position: 0.0; color: colors.yellow }
|
||||||
|
GradientStop { position: 1.0; color: colors.peach }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: (mouse) => {
|
||||||
|
var v = Math.round((mouse.x / width) * 100)
|
||||||
|
applyBrightness(Math.max(5, Math.min(100, v)))
|
||||||
|
}
|
||||||
|
onPositionChanged: (mouse) => {
|
||||||
|
if (pressed) {
|
||||||
|
var v = Math.round((mouse.x / width) * 100)
|
||||||
|
applyBrightness(Math.max(5, Math.min(100, v)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text { text: "🔆"; font.pixelSize: 13 }
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: brightnessLevel + "%"
|
||||||
|
color: colors.subtext0
|
||||||
|
font.pixelSize: 12
|
||||||
|
width: 32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Hypridle Timeouts ───────────────────────────────────
|
||||||
|
// These three rows map 1-to-1 to the three listeners in
|
||||||
|
// hypridle.conf and rewrite + reload the daemon on change.
|
||||||
|
SectionLabel { label: "Idle Timeouts (hypridle)" }
|
||||||
|
|
||||||
|
TimeoutRow {
|
||||||
|
label: "Auto-lock"
|
||||||
|
icon: "🔒"
|
||||||
|
accent: colors.lavender
|
||||||
|
value: lockTimeout
|
||||||
|
options: [5, 10, 15, 30, 0]
|
||||||
|
onPicked: (v) => setLockTimeout(v)
|
||||||
|
}
|
||||||
|
TimeoutRow {
|
||||||
|
label: "Screen off"
|
||||||
|
icon: "🖥"
|
||||||
|
accent: colors.sapphire
|
||||||
|
value: dpmsTimeout
|
||||||
|
options: [5, 10, 15, 30, 0]
|
||||||
|
onPicked: (v) => setDpmsTimeout(v)
|
||||||
|
}
|
||||||
|
TimeoutRow {
|
||||||
|
label: "Suspend"
|
||||||
|
icon: "💤"
|
||||||
|
accent: colors.mauve
|
||||||
|
value: suspendTimeout
|
||||||
|
options: [15, 20, 30, 60, 0]
|
||||||
|
onPicked: (v) => setSuspendTimeout(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Toggles ─────────────────────────────────────────────
|
||||||
|
SectionLabel { label: "Settings" }
|
||||||
|
|
||||||
|
ToggleRow {
|
||||||
|
label: "Suspend on lid close"
|
||||||
|
icon: "💻"
|
||||||
|
checked: lidClose
|
||||||
|
onToggled: (v) => toggleLid(v)
|
||||||
|
}
|
||||||
|
ToggleRow {
|
||||||
|
label: "WiFi power saving"
|
||||||
|
icon: "📶"
|
||||||
|
checked: wifiPower
|
||||||
|
onToggled: (v) => toggleWifiPower(v)
|
||||||
|
}
|
||||||
|
ToggleRow {
|
||||||
|
label: "Bluetooth off"
|
||||||
|
icon: "🔵"
|
||||||
|
checked: bluetoothOff
|
||||||
|
onToggled: (v) => toggleBluetooth(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Status bar ──────────────────────────────────────────
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
height: visible ? 32 : 0
|
||||||
|
visible: statusMsg !== ""
|
||||||
|
radius: 8
|
||||||
|
color: statusOk
|
||||||
|
? Qt.rgba(colors.green.r, colors.green.g, colors.green.b, 0.12)
|
||||||
|
: Qt.rgba(colors.red.r, colors.red.g, colors.red.b, 0.12)
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: statusMsg
|
||||||
|
color: statusOk ? colors.green : colors.red
|
||||||
|
font.pixelSize: 12
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Actions ─────────────────────────────────────────────
|
||||||
|
SectionLabel { label: "Actions" }
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.bottomMargin: 8
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
ActionButton { label: "Lock"; icon: "🔒"; accent: colors.blue; onClicked: run(["hyprlock"]) }
|
||||||
|
ActionButton { label: "Suspend"; icon: "💤"; accent: colors.mauve; onClicked: run(["systemctl", "suspend"]) }
|
||||||
|
ActionButton { label: "Hibernate"; icon: "🌙"; accent: colors.sapphire; onClicked: run(["systemctl", "hibernate"]) }
|
||||||
|
ActionButton { label: "Reboot"; icon: "🔄"; accent: colors.peach; onClicked: run(["systemctl", "reboot"]) }
|
||||||
|
ActionButton { label: "Shutdown"; icon: "⏻"; accent: colors.red; onClicked: run(["systemctl", "poweroff"]) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Inline components ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
component SectionLabel: Text {
|
||||||
|
required property string label
|
||||||
|
text: label.toUpperCase()
|
||||||
|
color: colors.overlay0
|
||||||
|
font.pixelSize: 10
|
||||||
|
font.bold: true
|
||||||
|
letterSpacing: 1.2
|
||||||
|
}
|
||||||
|
|
||||||
|
component ProfileButton: Rectangle {
|
||||||
|
required property string label
|
||||||
|
required property string icon
|
||||||
|
required property color accent
|
||||||
|
required property bool active
|
||||||
|
signal clicked
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
height: 56
|
||||||
|
radius: 10
|
||||||
|
color: active ? Qt.rgba(accent.r, accent.g, accent.b, 0.15) : colors.surface0
|
||||||
|
border.color: active ? accent : "transparent"
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Behavior on color { ColorAnimation { duration: 120 } }
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 2
|
||||||
|
Text {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
text: icon
|
||||||
|
font.pixelSize: 18
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
text: label
|
||||||
|
color: active ? accent : colors.subtext0
|
||||||
|
font.pixelSize: 11
|
||||||
|
font.bold: active
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: parent.clicked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component TimeoutRow: RowLayout {
|
||||||
|
required property string label
|
||||||
|
required property string icon
|
||||||
|
required property color accent
|
||||||
|
required property int value
|
||||||
|
required property var options
|
||||||
|
signal picked(int v)
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
Text { text: icon; font.pixelSize: 14 }
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: label
|
||||||
|
color: colors.subtext1
|
||||||
|
font.pixelSize: 12
|
||||||
|
Layout.preferredWidth: 100
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { Layout.fillWidth: true }
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: options
|
||||||
|
delegate: Rectangle {
|
||||||
|
readonly property int mins: modelData
|
||||||
|
readonly property bool sel: value === mins
|
||||||
|
|
||||||
|
width: mins === 0 ? 40 : 36
|
||||||
|
height: 24
|
||||||
|
radius: 6
|
||||||
|
color: sel ? Qt.rgba(accent.r, accent.g, accent.b, 0.2) : colors.surface0
|
||||||
|
border.color: sel ? accent : "transparent"
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: mins === 0 ? "off" : mins + "m"
|
||||||
|
color: sel ? accent : colors.subtext0
|
||||||
|
font.pixelSize: 10
|
||||||
|
font.bold: sel
|
||||||
|
}
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: picked(mins)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component ToggleRow: RowLayout {
|
||||||
|
required property string label
|
||||||
|
required property string icon
|
||||||
|
required property bool checked
|
||||||
|
signal toggled(bool v)
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
Text { text: icon; font.pixelSize: 14 }
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: label
|
||||||
|
color: colors.subtext1
|
||||||
|
font.pixelSize: 12
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { Layout.fillWidth: true }
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 42
|
||||||
|
height: 22
|
||||||
|
radius: 11
|
||||||
|
color: checked ? Qt.rgba(colors.green.r, colors.green.g, colors.green.b, 0.3) : colors.surface1
|
||||||
|
border.color: checked ? colors.green : colors.surface2
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Behavior on color { ColorAnimation { duration: 150 } }
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
x: checked ? parent.width - width - 3 : 3
|
||||||
|
y: 3
|
||||||
|
width: 16; height: 16; radius: 8
|
||||||
|
color: checked ? colors.green : colors.surface2
|
||||||
|
|
||||||
|
Behavior on x { NumberAnimation { duration: 150; easing.type: Easing.OutCubic } }
|
||||||
|
Behavior on color { ColorAnimation { duration: 150 } }
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: toggled(!checked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component ActionButton: Rectangle {
|
||||||
|
required property string label
|
||||||
|
required property string icon
|
||||||
|
required property color accent
|
||||||
|
signal clicked
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
height: 52
|
||||||
|
radius: 10
|
||||||
|
color: colors.surface0
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 2
|
||||||
|
Text {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
text: icon
|
||||||
|
font.pixelSize: 16
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
text: label
|
||||||
|
color: colors.subtext0
|
||||||
|
font.pixelSize: 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: hoverBorder
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: parent.radius
|
||||||
|
color: "transparent"
|
||||||
|
border.color: accent
|
||||||
|
border.width: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
hoverEnabled: true
|
||||||
|
onEntered: hoverBorder.border.width = 1
|
||||||
|
onExited: hoverBorder.border.width = 0
|
||||||
|
onClicked: parent.clicked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user