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>
237 lines
8 KiB
QML
237 lines
8 KiB
QML
pragma ComponentBehavior: Bound
|
|
|
|
// NoSignal Network popout (off the Wi-Fi pill). Title + switch; list of networks
|
|
// (signal glyph accent if connected, SSID, Connected label, lock if secured);
|
|
// connected row = accent.soft. Footer "Network settings". Wired to Network/Nmcli.
|
|
//
|
|
// FIX 2026-06-20 (hardware test): the shipped panel was display-only — the row
|
|
// MouseArea had no onClicked and there was no password field, so a secured Wi-Fi
|
|
// network could not be joined from the GUI (user had to drop to nmcli/nmtui).
|
|
// This wires the existing Network service: click a row to connect; open or
|
|
// already-saved networks connect directly; a secured network with no saved
|
|
// profile expands an inline password field (StyledTextField, echoMode Password).
|
|
// NsOverlay grants OnDemand keyboard focus while visible, so the field is typable.
|
|
// NOTE: authored on the test machine but NOT yet built/cert'd — review in builder.
|
|
|
|
import QtQuick
|
|
import QtQuick.Layouts
|
|
import qs.services
|
|
import qs.utils
|
|
import qs.components
|
|
import qs.components.controls
|
|
|
|
NsPanel {
|
|
id: root
|
|
|
|
implicitWidth: 340
|
|
anchorMode: "right"
|
|
|
|
// ssid of the secured row currently showing its inline password field ("" = none)
|
|
property string expandedSsid: ""
|
|
|
|
// Decide what a click on a network row does.
|
|
function activate(m): void {
|
|
if (!m)
|
|
return;
|
|
if (m.active) {
|
|
// Already connected -> tapping disconnects.
|
|
Network.disconnectFromNetwork();
|
|
root.expandedSsid = "";
|
|
return;
|
|
}
|
|
if (!m.isSecure || Network.hasSavedProfile(m.ssid)) {
|
|
// Open network, or we already hold the PSK -> connect straight away.
|
|
Network.connectToNetworkWithPasswordCheck(m.ssid, m.isSecure, () => {}, m.bssid);
|
|
root.expandedSsid = "";
|
|
} else {
|
|
// Secured + no saved profile -> reveal the inline password field.
|
|
root.expandedSsid = (root.expandedSsid === m.ssid) ? "" : m.ssid;
|
|
}
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
|
|
StyledText {
|
|
text: "Wi-Fi"
|
|
color: Theme.text
|
|
font.family: Theme.font.family
|
|
font.pixelSize: Theme.font.panelTitle
|
|
font.weight: Font.DemiBold
|
|
}
|
|
|
|
Item {
|
|
Layout.fillWidth: true
|
|
}
|
|
|
|
NsSwitch {
|
|
on: Network.wifiEnabled
|
|
onToggled: Network.toggleWifi()
|
|
}
|
|
}
|
|
|
|
// network list
|
|
ColumnLayout {
|
|
Layout.fillWidth: true
|
|
spacing: 2
|
|
visible: Network.wifiEnabled
|
|
|
|
Repeater {
|
|
model: Network.networks
|
|
|
|
delegate: ColumnLayout {
|
|
id: rowWrap
|
|
|
|
required property var modelData
|
|
readonly property bool connected: modelData?.active ?? false
|
|
readonly property bool expanded: root.expandedSsid === (modelData?.ssid ?? " ")
|
|
|
|
Layout.fillWidth: true
|
|
spacing: 2
|
|
|
|
function doConnect(): void {
|
|
const m = rowWrap.modelData;
|
|
if (!m || pwField.text.length === 0)
|
|
return;
|
|
Network.connectToNetwork(m.ssid, pwField.text, m.bssid, () => {});
|
|
pwField.text = "";
|
|
root.expandedSsid = "";
|
|
}
|
|
|
|
Rectangle {
|
|
id: row
|
|
|
|
Layout.fillWidth: true
|
|
implicitHeight: 40
|
|
radius: Theme.radius.button
|
|
color: rowWrap.connected ? Theme.accentSoft : ma.containsMouse ? Theme.hover : "transparent"
|
|
|
|
RowLayout {
|
|
anchors.fill: parent
|
|
anchors.leftMargin: 10
|
|
anchors.rightMargin: 10
|
|
spacing: 10
|
|
|
|
NsIcon {
|
|
icon: Icons.getNetworkIcon(rowWrap.modelData?.strength ?? 0)
|
|
color: rowWrap.connected ? Theme.accent : Theme.text
|
|
}
|
|
|
|
ColumnLayout {
|
|
Layout.fillWidth: true
|
|
spacing: 0
|
|
|
|
StyledText {
|
|
text: rowWrap.modelData?.ssid ?? "?"
|
|
color: Theme.text
|
|
elide: Text.ElideRight
|
|
Layout.fillWidth: true
|
|
font.family: Theme.font.family
|
|
font.pixelSize: Theme.font.bodySmall
|
|
}
|
|
StyledText {
|
|
visible: rowWrap.connected
|
|
text: "Connected"
|
|
color: Theme.accent
|
|
font.family: Theme.font.family
|
|
font.pixelSize: Theme.font.meta
|
|
}
|
|
}
|
|
|
|
NsIcon {
|
|
visible: rowWrap.modelData?.isSecure ?? false
|
|
icon: "lock"
|
|
color: Theme.textFaint
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
id: ma
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: root.activate(rowWrap.modelData)
|
|
}
|
|
}
|
|
|
|
// inline password entry — only for a secured network with no saved profile
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: 10
|
|
Layout.rightMargin: 10
|
|
Layout.bottomMargin: 4
|
|
spacing: 8
|
|
visible: rowWrap.expanded
|
|
|
|
onVisibleChanged: {
|
|
if (visible)
|
|
pwField.forceActiveFocus();
|
|
}
|
|
|
|
StyledTextField {
|
|
id: pwField
|
|
|
|
Layout.fillWidth: true
|
|
echoMode: TextField.Password
|
|
placeholderText: "Password"
|
|
onAccepted: rowWrap.doConnect()
|
|
}
|
|
|
|
Rectangle {
|
|
implicitWidth: 72
|
|
implicitHeight: 28
|
|
radius: Theme.radius.button
|
|
color: cma.containsMouse ? Theme.accent : Theme.accentSoft
|
|
|
|
StyledText {
|
|
anchors.centerIn: parent
|
|
text: "Connect"
|
|
color: Theme.text
|
|
font.family: Theme.font.family
|
|
font.pixelSize: Theme.font.meta
|
|
}
|
|
|
|
MouseArea {
|
|
id: cma
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: rowWrap.doConnect()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
StyledText {
|
|
visible: Network.networks.length === 0
|
|
text: "No networks found"
|
|
color: Theme.textMuted
|
|
font.family: Theme.font.family
|
|
font.pixelSize: Theme.font.bodySmall
|
|
}
|
|
}
|
|
|
|
StyledText {
|
|
visible: !Network.wifiEnabled
|
|
text: "Wi-Fi is off"
|
|
color: Theme.textMuted
|
|
font.family: Theme.font.family
|
|
font.pixelSize: Theme.font.bodySmall
|
|
}
|
|
|
|
// footer
|
|
Rectangle {
|
|
Layout.fillWidth: true
|
|
Layout.topMargin: 4
|
|
implicitHeight: 1
|
|
color: Theme.barBorder
|
|
}
|
|
|
|
StyledText {
|
|
text: "Network settings"
|
|
color: Theme.textMuted
|
|
font.family: Theme.font.family
|
|
font.pixelSize: Theme.font.bodySmall
|
|
}
|
|
}
|