// Power & Lock. // // hypridle has no IPC for reconfiguration, so these values reach it by // regenerating its config and restarting the daemon (services/IdleLock.qml). // That only happens when Panama manages the daemon, and the card below says // plainly which state you are in rather than presenting sliders that silently // do nothing. import QtQuick import Quickshell import qs.config import qs.services SettingsPage { id: root // Probing the power daemon is a D-Bus round trip, so it happens when the // page opens rather than at shell startup. Component.onCompleted: if (!PowerProfiles.scanned) PowerProfiles.refresh() title: "Power & Lock" lede: "When the screen turns off, when the session locks, and whether it ever sleeps." // Absent on a desktop, in full. `available` is false until a battery has // actually been read, so this is not an empty card claiming 0%. SettingsCard { visible: Battery.available title: "Battery" subtitle: Battery.acOnline ? "On wall power." : "On battery. The timings below switch to their battery values automatically." TextRow { label: "Charge" value: Math.round(Battery.percent) + "%" } TextRow { label: "State" value: { if (Battery.charging) return "Charging"; if (Battery.status === "Full") return "Full"; if (Battery.acOnline) return "Plugged in, not charging"; return "On battery"; } } // The two points at which the desktop starts telling you. These were // in the schema and reachable from settings search long before any // page rendered them -- search delivered people to this card and the // controls were not here. SliderRow { setting: "batteryLowPercent" } SliderRow { setting: "batteryCriticalPercent" divider: Battery.chargeLimitSupported } // Only where the firmware actually has a ceiling. A machine whose // kernel exposes nothing gets no control at all, rather than one that // would accept a value and change nothing. SliderRow { visible: Battery.chargeLimitSupported setting: "batteryChargeLimit" divider: false } } // The same profiles GNOME's Power panel offers. Not a stored preference -- // the daemon owns it, it survives Panama restarts, and anything else on the // system can change it, so a copy here would drift. SettingsCard { visible: PowerProfiles.available || PowerProfiles.lastError !== "" title: "Power profile" subtitle: PowerProfiles.degraded !== "" ? "Performance is limited right now: " + PowerProfiles.degraded : (PowerProfiles.available ? "Applies to the whole system and persists across sessions." : PowerProfiles.lastError) Repeater { model: PowerProfiles.profiles SettingRow { id: profileRow required property var modelData required property int index label: PowerProfiles.label(profileRow.modelData) detail: PowerProfiles.detail(profileRow.modelData) value: profileRow.modelData === PowerProfiles.active ? "Active" : "" divider: profileRow.index < PowerProfiles.profiles.length - 1 activatable: profileRow.modelData !== PowerProfiles.active && !PowerProfiles.busy onActivated: PowerProfiles.set(profileRow.modelData) } } } SettingsCard { // Named for the power source only on a machine that has two of them. // On a desktop this is simply "Idle behavior", as it always was. title: Battery.available ? "Idle behavior on wall power" : "Idle behavior" subtitle: IdleLock.managed ? (Battery.available && !Battery.acOnline ? "Managed here. These apply when the charger is connected; the battery timings below are what is in effect right now." : "Idle timings are managed here. Changes take effect immediately.") : "hypridle is running its shipped configuration. Turn on management below to make these adjustable." SliderRow { setting: "screenBlankMinutes"; zeroLabel: "Never" } SliderRow { setting: "lockMinutes"; zeroLabel: "Never" } SliderRow { setting: "suspendMinutes"; zeroLabel: "Never" } ToggleRow { setting: "lockOnSleep"; divider: false } } // Absent on a desktop. hypridle holds one set of timeouts at a time, so // these do not layer on top of the card above -- they replace it whenever // the charger is unplugged, and panama-idle rebuilds the config at that // moment. SettingsCard { visible: Battery.available title: "Idle behavior on battery" subtitle: Battery.acOnline ? "What will apply once the charger is unplugged." : "In effect right now." SliderRow { setting: "screenBlankMinutesBattery"; zeroLabel: "Never" } SliderRow { setting: "lockMinutesBattery"; zeroLabel: "Never" } SliderRow { setting: "suspendMinutesBattery"; zeroLabel: "Never"; divider: false } } // What the lid does. Informational by design: the decision follows what // is connected (see services/LidPolicy.qml), and the deliberate absence // of an override is part of the design -- a lid switch set to "never // suspend" is a laptop that cooks in a bag. Saying so here beats leaving // the behavior undiscoverable. SettingsCard { visible: Battery.available title: "When the lid closes" subtitle: "Decided by what is connected rather than by a setting: with an external display attached the machine is docked and keeps running; on its own it suspends, locking on the way down." TextRow { label: "Right now" value: LidPolicy.inhibited ? "Stays awake — an external display is connected" : "Suspends" divider: false } } // Only shown when the numbers are actually contradictory, rather than as a // permanent warning nobody reads. SettingsCard { visible: IdleLock.lockBeforeBlank title: "Lock happens before the screen turns off" subtitle: "The session will lock at " + IdleLock.lockMinutes + " minutes and the display will not blank until " + IdleLock.blankMinutes + ". That works, but the screen stays lit on the lock screen for the difference." } SettingsCard { title: "Management" subtitle: "hypridle's configuration is generated into your state directory and the service is pointed at it with a systemd drop-in. ~/.config/hypr is a symlink into the configuration repository, so the shipped file cannot be rewritten in place." SettingRow { label: "Manage idle timings here" detail: IdleLock.serviceState === "active" ? "hypridle is running" : "hypridle is " + IdleLock.serviceState controlWidth: 48 SettingsToggle { anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter checked: IdleLock.managed enabled: !IdleLock.busy onToggled: value => IdleLock.setManaged(value) } } ActionRow { label: "Lock the screen now" detail: "Same as the Super+L shortcut" action: "Lock" divider: false onTriggered: Quickshell.execDetached(["loginctl", "lock-session"]) } } SettingsCard { visible: IdleLock.lastError !== "" title: "Idle configuration problem" subtitle: IdleLock.lastError ActionRow { label: "Read the idle configuration again" action: "Retry" divider: false onTriggered: IdleLock.refresh() } } }