import Quickshell import Quickshell.Wayland import QtQuick import qs.config import qs.components import qs.services // The session menu: the actions from Config.powerActions, one per row. Same // full-screen layer surface as the launcher, so click-away and Escape work // everywhere. // // Running them is left to PowerMenu, which owns the confirmation dialog — this // surface is unmapped the moment something is picked. PanelWindow { id: root required property var bar signal triggered(var action) readonly property string screenName: root.bar.screen ? root.bar.screen.name : "" visible: Session.isOpen("power", root.screenName) screen: root.bar.screen WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.namespace: Config.namespace + "-power" WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive anchors { left: true right: true top: true bottom: true } exclusionMode: ExclusionMode.Ignore color: "transparent" readonly property var actions: Config.powerActions property int current: 0 function activate(index: int): void { const action = root.actions[index]; if (!action) return; Session.close(); root.triggered(action); } onVisibleChanged: { if (root.visible) { // Never start on a destructive action: Enter on open should not be // able to shut the machine down. root.current = root.actions.findIndex(a => !a.dangerous); if (root.current < 0) root.current = 0; keyHandler.forceActiveFocus(); } } // Click-away scrim. MouseArea { anchors.fill: parent onClicked: Session.close() } Item { id: keyHandler anchors.fill: parent focus: true Keys.onEscapePressed: Session.close() Keys.onDownPressed: root.current = Math.min(root.current + 1, root.actions.length - 1) Keys.onUpPressed: root.current = Math.max(root.current - 1, 0) Keys.onReturnPressed: root.activate(root.current) Keys.onEnterPressed: root.activate(root.current) } Rectangle { id: menu // Anchored to the panel's right edge, under the button that opens it. anchors { right: parent.right bottom: parent.bottom rightMargin: Theme.padding bottomMargin: Theme.barHeight + Theme.padding } width: Math.min(Config.powerMenuWidth, root.width - Theme.padding * 2) height: column.implicitHeight + Theme.padding * 2 color: Theme.surface radius: Theme.radius * 2 border.width: Theme.borderWidth border.color: Theme.border // Swallow clicks so they do not reach the scrim behind. MouseArea { anchors.fill: parent } Column { id: column anchors { left: parent.left right: parent.right verticalCenter: parent.verticalCenter margins: Theme.padding } spacing: 1 Repeater { model: root.actions Rectangle { id: row required property var modelData required property int index readonly property bool destructive: row.modelData.dangerous readonly property bool selected: root.current === row.index readonly property color tint: row.destructive && row.selected ? Theme.danger : Theme.text width: column.width implicitHeight: 34 radius: Theme.radius color: row.selected ? Theme.surfaceActive : "transparent" Behavior on color { ColorAnimation { duration: Theme.animDuration easing.type: Theme.animEasing } } AppIcon { id: icon anchors { left: parent.left leftMargin: Theme.padding * 2 verticalCenter: parent.verticalCenter } icon: row.modelData.icon fallbackGlyph: row.modelData.glyph glyphColor: row.tint size: 18 } Text { anchors { left: icon.right right: parent.right leftMargin: Theme.padding * 2 rightMargin: Theme.padding * 2 verticalCenter: parent.verticalCenter } elide: Text.ElideRight text: row.modelData.label color: row.tint font.family: Theme.fontFamily font.pixelSize: Theme.fontSize } MouseArea { anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onEntered: root.current = row.index onClicked: root.activate(row.index) } } } } } }