Watch
1
0
Fork
You've already forked souveraine
0

hold power for a device menu; usb-c state shown, not faked

This commit is contained in:
Fimeg 2026-08-05 14:37:56 -04:00
commit f7eba9f658
6 changed files with 248 additions and 1 deletions

View file

@ -0,0 +1,187 @@
// The device's own verbs, on a held power button.
//
// The counterpart to WindowSheet: that sheet is about a window, this is about
// the machine. Casey, 2026-08-05 — "when we hold the power button ... we'd be
// given another context overlay", and "that whole power usb state probably
// belongs in the power switch options".
//
// ## Why the USB state lives here
//
// The USB-C port is not a preference, it is what the device currently *is*:
// host or device, sourcing power or sinking it, and what is on the other end.
// It changes the meaning of every peripheral attached, which makes it
// proprioception and puts it in sessiond beside panel, lock and idle. This
// surface is the view over that, not a second owner of it.
//
// ## What is honest here today
//
// The data role is real and readable — `/sys/class/usb_role/…/role` reports
// `device`, and `pmi8998-charger` reports `online=1`, so "attached, drawing
// power" is a measured fact rather than a guess. **Switching** the role is not
// offered yet: the sysfs node is root-owned, so the flip has to be a sessiond
// verb, and a button that cannot do the thing it names is the exact failure
// this project keeps writing down. It is shown as state, and it says so.
//
// There is no `/sys/class/typec/` on this device — the tcpm driver is not
// bound — so the *power* role genuinely is not knowable here yet. The row
// reports what the charger says and claims nothing more.
//
// ## Why it is not gated on the lock
//
// Powering off is something you do to a locked phone. A menu that disappeared
// when locked would send the user back to the hardware button they are already
// holding. It shows device verbs and never session content, so disclosure is
// unaffected.
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import qs
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import "." as SheetLocal
Scope {
id: scope
property bool menuOpen: false
// Measured, not assumed. Re-read each time the menu opens rather than
// polled: the port's state only matters while someone is looking at it,
// and a timer here would be a background reader of a file nobody needs.
property string dataRole: "unknown"
property bool chargerOnline: false
function refresh() {
roleProc.running = true;
chargerProc.running = true;
}
function open() {
scope.refresh();
scope.menuOpen = true;
}
function dismiss() {
scope.menuOpen = false;
}
IpcHandler {
target: "powerMenu"
function open(): void {
scope.open();
}
function close(): void {
scope.dismiss();
}
function state(): string {
return scope.dataRole + " charger_online=" + scope.chargerOnline;
}
}
Process {
id: roleProc
command: ["cat", "/sys/class/usb_role/a600000.usb-role-switch/role"]
stdout: StdioCollector {
onStreamFinished: scope.dataRole = text.trim() || "unknown"
}
}
Process {
id: chargerProc
command: ["cat", "/sys/class/power_supply/pmi8998-charger/online"]
stdout: StdioCollector {
onStreamFinished: scope.chargerOnline = text.trim() === "1"
}
}
Variants {
model: Quickshell.screens
PanelWindow {
id: win
required property var modelData
screen: win.modelData
anchors { top: true; left: true; right: true; bottom: true }
color: "transparent"
visible: scope.menuOpen
WlrLayershell.namespace: "souveraine:powermenu"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
exclusionMode: ExclusionMode.Ignore
Rectangle {
anchors.fill: parent
color: "#99000000"
MouseArea {
anchors.fill: parent
onClicked: scope.dismiss()
}
}
ColumnLayout {
anchors.centerIn: parent
spacing: 30
RowLayout {
Layout.alignment: Qt.AlignHCenter
spacing: 28
SheetLocal.SheetButton {
icon: "lock"
label: "Lock"
onTapped: {
scope.dismiss();
Session.lock();
}
}
SheetLocal.SheetButton {
icon: "restart_alt"
label: "Restart"
holdLabel: "hold to restart"
holdMs: 1000
// Hold, not tap. A restart reached by a single tap on a
// menu you opened by holding a button is one slip away
// from losing whatever was open.
onHeld: {
scope.dismiss();
Session.reboot();
}
}
SheetLocal.SheetButton {
icon: "power_settings_new"
label: "Power off"
holdLabel: "hold to power off"
holdMs: 1000
onHeld: {
scope.dismiss();
Session.poweroff();
}
}
}
// The port, as measured. Not a control yet — see the header.
StyledText {
Layout.alignment: Qt.AlignHCenter
Layout.maximumWidth: win.width * 0.8
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
color: "#c8ffffff"
font.pixelSize: Appearance.font.pixelSize.small
text: "USB-C: " + scope.dataRole
+ (scope.chargerOnline ? " · charger attached" : " · no charger")
}
}
}
}
}

View file

@ -1,2 +1,3 @@
PowerMenu 1.0 PowerMenu.qml
SheetButton 1.0 SheetButton.qml
WindowSheet 1.0 WindowSheet.qml