From the 2026-06-20 Acer muxless-Optimus hardware test:
- F-wifi (NsNetwork.qml): the Wi-Fi panel was display-only — no onClicked,
no password field — so a secured network could not be joined from the GUI.
Wire the existing Network service: click to connect; open/saved networks
connect directly; secured-without-profile expands an inline password field
(StyledTextField, echoMode Password). Also strip a stray NUL byte that the
on-box edit left at the `?? " "` sentinel. HW-verified on the test laptop.
- F-multimon (NsShell.qml + NsBar.qml + NsOverlay.qml): bar popouts mirrored
on every monitor. Add a `screen` identity to NsShell; pills pass their
output name; each NsOverlay only renders when it owns the open panel ("" =
unscoped, for the global power modal). HW-verified dual-monitor.
- F-sudo (SudoToggleRow.qml): the passwordless-sudo prompt used the shared
TUI.float class with no focus rule, so keystrokes missed it and three blank
tries tripped pam_faillock with no feedback. Use a dedicated `nosignal-sudo`
class (builder ships float/center/pin/stayfocused rules) and call the new
`nosignal-sudo-toggle enable-tui` wrapper, which keeps the terminal open on
failure so the reason is visible.
Add .gitattributes (*.qml/*.conf/*.json text) for clean diffs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
76 lines
2.9 KiB
QML
76 lines
2.9 KiB
QML
// SudoToggleRow.qml (NoSignal) — Settings -> Services toggle for time-boxed
|
|
// passwordless sudo. Reflects live state by polling `nosignal-sudo-toggle
|
|
// status` (no root needed); enabling opens a floating terminal for the ONE
|
|
// password prompt, disabling runs passwordless inside the active window.
|
|
//
|
|
// Untracked file under modules/nexus/common — auto-discovered as the type
|
|
// `SudoToggleRow` via `import qs.modules.nexus.common`. Survives caelestia
|
|
// upgrades; only the one-line insert in ServicesPage.qml is re-applied by hook.
|
|
import QtQuick
|
|
import Quickshell.Io
|
|
import qs.modules.nexus.common
|
|
|
|
ToggleRow {
|
|
id: root
|
|
|
|
property bool active: false
|
|
property int remaining: 0
|
|
|
|
text: qsTr("Passwordless sudo (15 min)")
|
|
subtext: active
|
|
? qsTr("On — %1 min left. Auto-reverts; a reboot also clears it.").arg(remaining)
|
|
: qsTr("Run sudo without a password for 15 minutes, then it reverts")
|
|
|
|
onToggled: {
|
|
if (checked)
|
|
enableProc.running = true; // needs a password -> floating terminal
|
|
else
|
|
disableProc.running = true; // no password inside the active window
|
|
reconcile.restart();
|
|
}
|
|
|
|
// --- live state -----------------------------------------------------------
|
|
Process {
|
|
id: statusProc
|
|
command: ["nosignal-sudo-toggle", "status"]
|
|
stdout: StdioCollector {
|
|
onStreamFinished: {
|
|
const p = text.trim().split(/\s+/);
|
|
root.active = p[0] === "active";
|
|
root.remaining = parseInt(p[1] || "0") || 0;
|
|
root.checked = root.active; // drive switch from real state
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- actions --------------------------------------------------------------
|
|
Process {
|
|
id: enableProc
|
|
// Dedicated window class (nosignal-sudo) so the shipped hypr windowrules
|
|
// (float/center/pin/stayfocused) force this prompt to GRAB keyboard focus.
|
|
// With the shared TUI.float class and no focus rule the prompt did not get
|
|
// keystrokes -> three blank tries tripped pam_faillock and the switch just
|
|
// snapped back with no feedback (hardware bug 2026-06-20). `enable-tui`
|
|
// runs the sudo prompt as the user and keeps the terminal open on failure
|
|
// so the reason is visible instead of vanishing.
|
|
command: ["kitty", "--class", "nosignal-sudo", "-e", "nosignal-sudo-toggle", "enable-tui"]
|
|
}
|
|
Process {
|
|
id: disableProc
|
|
command: ["sudo", "-n", "nosignal-sudo-toggle", "disable"]
|
|
}
|
|
|
|
// --- polling --------------------------------------------------------------
|
|
Timer {
|
|
interval: 5000
|
|
repeat: true
|
|
running: true
|
|
triggeredOnStart: true
|
|
onTriggered: statusProc.running = true
|
|
}
|
|
Timer {
|
|
id: reconcile
|
|
interval: 2000
|
|
onTriggered: statusProc.running = true
|
|
}
|
|
}
|