// --- 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() } } }