Feature: Password input
This commit is contained in:
@@ -44,7 +44,13 @@ Item {
|
|||||||
return null
|
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 {
|
Binding {
|
||||||
target: root.wifiDevice
|
target: root.wifiDevice
|
||||||
property: "scannerEnabled"
|
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: ""
|
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
|
// Wired connections don't have a user-facing "name" on the device or
|
||||||
// Network object — those just expose the interface name (e.g. "eth0").
|
// 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 --------------------------------------------------
|
// --- Live stats --------------------------------------------------
|
||||||
// None of this comes from Quickshell's Networking API — it only knows
|
// None of this comes from Quickshell's Networking API — it only knows
|
||||||
// about devices/networks, not throughput, ping, or routing. So this
|
// about devices/networks, not throughput, ping, or routing. So this
|
||||||
@@ -529,13 +554,16 @@ Item {
|
|||||||
font.variableAxes: Appearance.iconFontAxes
|
font.variableAxes: Appearance.iconFontAxes
|
||||||
font.pixelSize: 16
|
font.pixelSize: 16
|
||||||
color: root.connectedDevice ? Colors.foreground : Colors.muted
|
color: root.connectedDevice ? Colors.foreground : Colors.muted
|
||||||
// Wired/disconnected use their own distinct glyphs; only the WiFi
|
// Only Wired renders its own distinct glyph; disconnected uses
|
||||||
// case renders one of wifiIcon()'s flat/wide wifi-fan glyphs, so
|
// "signal_wifi_off" — the actual off companion to the
|
||||||
// only that case needs the vertical-stretch correction below.
|
// network_wifi/signal_wifi fan family used below (not "wifi_off",
|
||||||
readonly property bool isWifiFan: root.connectedDevice !== null && root.connectedDevice.type !== DeviceType.Wired
|
// 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: {
|
text: {
|
||||||
if (!root.connectedDevice)
|
if (!root.connectedDevice)
|
||||||
return "wifi_off"
|
return "signal_wifi_off"
|
||||||
if (root.connectedDevice.type === DeviceType.Wired)
|
if (root.connectedDevice.type === DeviceType.Wired)
|
||||||
return "settings_ethernet"
|
return "settings_ethernet"
|
||||||
return root.wifiIcon(root.connectedWifiSignal)
|
return root.wifiIcon(root.connectedWifiSignal)
|
||||||
@@ -593,20 +621,17 @@ Item {
|
|||||||
id: netRow
|
id: netRow
|
||||||
required property var net
|
required property var net
|
||||||
signal activate()
|
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
|
readonly property bool passwordOpen: root.passwordSsid === netRow.net.ssid
|
||||||
width: parent ? parent.width : 0
|
width: parent ? parent.width : 0
|
||||||
radius: 0
|
radius: 0
|
||||||
implicitHeight: rowContent.implicitHeight + 12 + (passwordOpen ? passwordRow.implicitHeight + 6 : 0)
|
implicitHeight: rowContent.implicitHeight + 12
|
||||||
color: netRow.net.connected
|
color: (netRow.net.connected || netRow.passwordOpen)
|
||||||
? Qt.rgba(Colors.primary.r, Colors.primary.g, Colors.primary.b, 0.18)
|
? Qt.rgba(Colors.primary.r, Colors.primary.g, Colors.primary.b, 0.18)
|
||||||
: (rowMouse.containsMouse ? Colors.primary : "transparent")
|
: (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 {
|
Column {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 6
|
anchors.margins: 6
|
||||||
@@ -676,53 +701,6 @@ Item {
|
|||||||
color: rowMouse.containsMouse ? Colors.primaryText : Colors.muted
|
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 {
|
MouseArea {
|
||||||
@@ -1080,6 +1058,97 @@ Item {
|
|||||||
color: Colors.muted
|
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
|
// Known/Other Networks — capped height, scrolls once
|
||||||
// there are more networks than fit rather than growing
|
// there are more networks than fit rather than growing
|
||||||
// the popup indefinitely.
|
// the popup indefinitely.
|
||||||
|
|||||||
Reference in New Issue
Block a user