Feature: Password input

This commit is contained in:
2026-08-27 09:18:57 +01:00
parent 00e4368abe
commit cede2a1690

View File

@@ -44,7 +44,13 @@ Item {
return null
}
// Only scan for networks while the panel is actually open.
// Only scan for networks while the panel is actually open. Deliberately
// NOT paused while a password prompt is up: scannerEnabled: false stops
// NetworkManager from refreshing its AP cache at all, so the existing
// list quietly ages out with nothing to replace it — the fix for typing
// being interrupted by scan-driven refreshes lives below instead (the
// password field itself is pulled out of the repeated row so refreshes
// can't touch it), not here.
Binding {
target: root.wifiDevice
property: "scannerEnabled"
@@ -135,8 +141,17 @@ Item {
}
}
// SSID of the row currently showing its inline password field, if any.
// SSID awaiting a password, if any. The input itself lives in a single
// persistent widget below the network list (see passwordEntryInput),
// not inside the per-row NetworkRow delegate — wifiRows is rebuilt from
// scratch on every scan result, and a Repeater over a plain array has no
// way to diff that, so it destroys and recreates every delegate each
// time. Keeping the field outside that churn is what lets typing survive
// a scan landing mid-keystroke.
property string passwordSsid: ""
// Cleared whenever the target SSID changes so stale text from a
// cancelled/previous prompt never carries over to the next one.
onPasswordSsidChanged: passwordEntryInput.text = ""
// Wired connections don't have a user-facing "name" on the device or
// Network object — those just expose the interface name (e.g. "eth0").
@@ -179,6 +194,16 @@ Item {
}
}
function submitPassword() {
const n = root.networkForSsid(root.passwordSsid)
if (n && passwordEntryInput.text.length > 0) n.connectWithPsk(passwordEntryInput.text)
root.passwordSsid = ""
}
function cancelPassword() {
root.passwordSsid = ""
}
// --- Live stats --------------------------------------------------
// None of this comes from Quickshell's Networking API — it only knows
// about devices/networks, not throughput, ping, or routing. So this
@@ -529,13 +554,16 @@ Item {
font.variableAxes: Appearance.iconFontAxes
font.pixelSize: 16
color: root.connectedDevice ? Colors.foreground : Colors.muted
// Wired/disconnected use their own distinct glyphs; only the WiFi
// case renders one of wifiIcon()'s flat/wide wifi-fan glyphs, so
// only that case needs the vertical-stretch correction below.
readonly property bool isWifiFan: root.connectedDevice !== null && root.connectedDevice.type !== DeviceType.Wired
// Only Wired renders its own distinct glyph; disconnected uses
// "signal_wifi_off" — the actual off companion to the
// network_wifi/signal_wifi fan family used below (not "wifi_off",
// which belongs to a visually different, newer glyph family and
// looked inconsistent next to the connected bars) — so it needs the
// same vertical-stretch correction as the rest of that family.
readonly property bool isWifiFan: root.connectedDevice === null || root.connectedDevice.type !== DeviceType.Wired
text: {
if (!root.connectedDevice)
return "wifi_off"
return "signal_wifi_off"
if (root.connectedDevice.type === DeviceType.Wired)
return "settings_ethernet"
return root.wifiIcon(root.connectedWifiSignal)
@@ -593,20 +621,17 @@ Item {
id: netRow
required property var net
signal activate()
// Still tracked for the highlight below — the actual input lives
// outside this (repeatedly rebuilt) delegate, in the persistent
// password entry widget under the network list.
readonly property bool passwordOpen: root.passwordSsid === netRow.net.ssid
width: parent ? parent.width : 0
radius: 0
implicitHeight: rowContent.implicitHeight + 12 + (passwordOpen ? passwordRow.implicitHeight + 6 : 0)
color: netRow.net.connected
implicitHeight: rowContent.implicitHeight + 12
color: (netRow.net.connected || netRow.passwordOpen)
? Qt.rgba(Colors.primary.r, Colors.primary.g, Colors.primary.b, 0.18)
: (rowMouse.containsMouse ? Colors.primary : "transparent")
function submit() {
const n = root.networkForSsid(netRow.net.ssid)
if (n && passwordInput.text.length > 0) n.connectWithPsk(passwordInput.text)
root.passwordSsid = ""
}
Column {
anchors.fill: parent
anchors.margins: 6
@@ -676,53 +701,6 @@ Item {
color: rowMouse.containsMouse ? Colors.primaryText : Colors.muted
}
}
RowLayout {
id: passwordRow
visible: netRow.passwordOpen
width: parent.width
spacing: 6
Rectangle {
Layout.fillWidth: true
implicitHeight: 24
radius: 0
color: Colors.background
border.color: Colors.muted
border.width: 1
TextInput {
id: passwordInput
anchors.fill: parent
anchors.margins: 6
echoMode: TextInput.Password
color: Colors.foreground
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSizeSmall
clip: true
focus: netRow.passwordOpen
onAccepted: netRow.submit()
// Explicit fallback alongside onAccepted, which only
// fires when acceptableInput is true — covers both
// the main Return key and numpad Enter directly.
Keys.onReturnPressed: netRow.submit()
Keys.onEnterPressed: netRow.submit()
}
}
Text {
text: "check"
font.family: Appearance.iconFontFamilyRounded
font.variableAxes: Appearance.iconFontAxes
font.pixelSize: 16
color: Colors.primary
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: netRow.submit()
}
}
}
}
MouseArea {
@@ -1080,6 +1058,97 @@ Item {
color: Colors.muted
}
// Password entry — a single persistent widget rather
// than living inside the NetworkRow delegate for
// root.passwordSsid, so a wifi scan landing mid-keystroke
// (which rebuilds the whole Known/Other Networks list
// below) can't destroy the focused TextInput and wipe
// out what's been typed. See passwordSsid's own comment.
Rectangle {
width: parent.width
visible: root.passwordSsid !== ""
radius: 0
color: Colors.background
border.color: Colors.muted
border.width: 1
implicitHeight: passwordEntryColumn.implicitHeight + 12
Column {
id: passwordEntryColumn
anchors.fill: parent
anchors.margins: 6
spacing: 6
Text {
width: parent.width
text: "Password for “" + root.passwordSsid + "”"
elide: Text.ElideRight
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSizeSmall
color: Colors.muted
}
RowLayout {
width: parent.width
spacing: 6
Rectangle {
Layout.fillWidth: true
implicitHeight: 24
radius: 0
color: Colors.surface
border.color: Colors.muted
border.width: 1
TextInput {
id: passwordEntryInput
anchors.fill: parent
anchors.margins: 6
echoMode: TextInput.Password
color: Colors.foreground
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSizeSmall
clip: true
focus: root.passwordSsid !== ""
onAccepted: root.submitPassword()
// Explicit fallback alongside onAccepted, which only
// fires when acceptableInput is true — covers both
// the main Return key and numpad Enter directly.
Keys.onReturnPressed: root.submitPassword()
Keys.onEnterPressed: root.submitPassword()
Keys.onEscapePressed: root.cancelPassword()
}
}
Text {
text: "check"
font.family: Appearance.iconFontFamilyRounded
font.variableAxes: Appearance.iconFontAxes
font.pixelSize: 16
color: Colors.primary
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: root.submitPassword()
}
}
Text {
text: "close"
font.family: Appearance.iconFontFamilyRounded
font.variableAxes: Appearance.iconFontAxes
font.pixelSize: 16
color: Colors.muted
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: root.cancelPassword()
}
}
}
}
}
// Known/Other Networks — capped height, scrolls once
// there are more networks than fit rather than growing
// the popup indefinitely.