Files

170 lines
4.7 KiB
QML

// Replaces GNOME's power-off dialog. Opened by Ctrl+Alt+Delete and from the
// quick-settings footer.
//
// Log Out, Restart and Power Off arm on the first press and fire on the second
// (see PowerButton); Lock and Suspend are one press because neither loses work.
import Quickshell
import Quickshell.Wayland
import QtQuick
import qs.config
import qs.services
PanelWindow {
id: win
visible: ShellState.powerMenuOpen
color: "transparent"
anchors {
top: true
bottom: true
left: true
right: true
}
exclusiveZone: 0
WlrLayershell.namespace: "qs-popover-power"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
property int currentIndex: 0
// The session is uwsm-managed, so logging out means stopping the uwsm unit
// rather than killing the compositor; the hyprctl branch is only there for
// a session started without it.
readonly property string logoutScript: 'if command -v uwsm >/dev/null 2>&1; then exec uwsm stop; else exec hyprctl dispatch "hl.dsp.exit()"; fi'
readonly property var entries: [
{
glyph: "󰌾",
label: "Lock",
destructive: false,
cmd: ["loginctl", "lock-session"]
},
{
glyph: "󰗼",
label: "Log Out",
destructive: true,
cmd: ["sh", "-c", win.logoutScript]
},
{
glyph: "󰒲",
label: "Suspend",
destructive: false,
cmd: ["systemctl", "suspend"]
},
{
glyph: "󰜉",
label: "Restart",
destructive: true,
cmd: ["systemctl", "reboot"]
},
{
glyph: "󰐥",
label: "Power Off",
destructive: true,
cmd: ["systemctl", "poweroff"]
}
]
onVisibleChanged: {
if (!win.visible)
return;
// Never reopen with a destructive button still armed from last time.
win.currentIndex = 0;
for (let i = 0; i < rep.count; i++)
rep.itemAt(i).disarm();
keys.forceActiveFocus();
}
function run(index: int): void {
ShellState.close();
Quickshell.execDetached(win.entries[index].cmd);
}
function move(step: int): void {
// Moving away disarms, so Enter can never fire a button the user only
// meant to walk past.
rep.itemAt(win.currentIndex).disarm();
win.currentIndex = (win.currentIndex + step + win.entries.length) % win.entries.length;
}
FocusScope {
id: keys
anchors.fill: parent
focus: true
Keys.onPressed: event => {
switch (event.key) {
case Qt.Key_Escape:
ShellState.close();
break;
case Qt.Key_Left:
win.move(-1);
break;
case Qt.Key_Right:
win.move(1);
break;
case Qt.Key_Return:
case Qt.Key_Enter:
case Qt.Key_Space:
rep.itemAt(win.currentIndex).trigger();
break;
default:
return;
}
event.accepted = true;
}
Rectangle {
anchors.fill: parent
color: Theme.alpha(Theme.bgDark, Theme.overlayAlpha)
border.width: 0
MouseArea {
anchors.fill: parent
onClicked: ShellState.close()
}
}
Rectangle {
anchors.centerIn: parent
implicitWidth: row.implicitWidth + 2 * 24
implicitHeight: row.implicitHeight + 2 * 24
radius: Theme.popoverRadius + 8
border.width: 0
color: Theme.alpha(Theme.bgPopover, Theme.popoverAlpha)
// Swallow clicks that land on the panel but miss a tile, so the
// outside-click handler underneath doesn't close the menu.
MouseArea {
anchors.fill: parent
}
Row {
id: row
anchors.centerIn: parent
spacing: 14
Repeater {
id: rep
model: win.entries
PowerButton {
required property var modelData
required property int index
glyph: modelData.glyph
label: modelData.label
destructive: modelData.destructive
selected: index === win.currentIndex
onActivated: win.run(index)
}
}
}
}
}
}