controlcenter: wifi passphrase input
fix: dependability issues with nmcli
This commit is contained in:
parent
81a8157b67
commit
d8de7c7e4b
5 changed files with 451 additions and 13 deletions
|
|
@ -79,17 +79,37 @@ Item {
|
|||
checked: root.network?.active ?? false
|
||||
toggle.onToggled: {
|
||||
if (checked) {
|
||||
if (root.network.isSecure) {
|
||||
// TODO: Show password dialog
|
||||
root.session.network.showPasswordDialog = true;
|
||||
root.session.network.pendingNetwork = root.network;
|
||||
// If already connected to a different network, disconnect first
|
||||
if (Network.active && Network.active.ssid !== root.network.ssid) {
|
||||
Network.disconnectFromNetwork();
|
||||
// Wait a moment before connecting to new network
|
||||
Qt.callLater(() => {
|
||||
connectToNetwork();
|
||||
});
|
||||
} else {
|
||||
Network.connectToNetwork(root.network.ssid, "");
|
||||
connectToNetwork();
|
||||
}
|
||||
} else {
|
||||
Network.disconnectFromNetwork();
|
||||
}
|
||||
}
|
||||
|
||||
function connectToNetwork(): void {
|
||||
if (root.network.isSecure) {
|
||||
// Try connecting without password first (in case it's saved)
|
||||
Network.connectToNetworkWithPasswordCheck(
|
||||
root.network.ssid,
|
||||
root.network.isSecure,
|
||||
() => {
|
||||
// Callback: connection failed, show password dialog
|
||||
root.session.network.showPasswordDialog = true;
|
||||
root.session.network.pendingNetwork = root.network;
|
||||
}
|
||||
);
|
||||
} else {
|
||||
Network.connectToNetwork(root.network.ssid, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -214,14 +214,35 @@ ColumnLayout {
|
|||
if (modelData.active) {
|
||||
Network.disconnectFromNetwork();
|
||||
} else {
|
||||
if (modelData.isSecure) {
|
||||
root.session.network.showPasswordDialog = true;
|
||||
root.session.network.pendingNetwork = modelData;
|
||||
// If already connected to a different network, disconnect first
|
||||
if (Network.active && Network.active.ssid !== modelData.ssid) {
|
||||
Network.disconnectFromNetwork();
|
||||
// Wait a moment before connecting to new network
|
||||
Qt.callLater(() => {
|
||||
connectToNetwork();
|
||||
});
|
||||
} else {
|
||||
Network.connectToNetwork(modelData.ssid, "");
|
||||
connectToNetwork();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function connectToNetwork(): void {
|
||||
if (modelData.isSecure) {
|
||||
// Try connecting without password first (in case it's saved)
|
||||
Network.connectToNetworkWithPasswordCheck(
|
||||
modelData.ssid,
|
||||
modelData.isSecure,
|
||||
() => {
|
||||
// Callback: connection failed, show password dialog
|
||||
root.session.network.showPasswordDialog = true;
|
||||
root.session.network.pendingNetwork = modelData;
|
||||
}
|
||||
);
|
||||
} else {
|
||||
Network.connectToNetwork(modelData.ssid, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
|
|
|
|||
|
|
@ -76,4 +76,10 @@ RowLayout {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
PasswordDialog {
|
||||
anchors.fill: parent
|
||||
session: root.session
|
||||
z: 1000
|
||||
}
|
||||
}
|
||||
|
|
|
|||
241
modules/controlcenter/network/PasswordDialog.qml
Normal file
241
modules/controlcenter/network/PasswordDialog.qml
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import ".."
|
||||
import qs.components
|
||||
import qs.components.controls
|
||||
import qs.components.effects
|
||||
import qs.components.containers
|
||||
import qs.services
|
||||
import qs.config
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property Session session
|
||||
readonly property var network: session.network.pendingNetwork
|
||||
|
||||
visible: session.network.showPasswordDialog
|
||||
enabled: visible
|
||||
focus: visible
|
||||
|
||||
Keys.onEscapePressed: {
|
||||
root.session.network.showPasswordDialog = false;
|
||||
passwordField.text = "";
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Qt.rgba(0, 0, 0, 0.5)
|
||||
opacity: root.visible ? 1 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: 200
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
root.session.network.showPasswordDialog = false;
|
||||
passwordField.text = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
id: dialog
|
||||
|
||||
anchors.centerIn: parent
|
||||
|
||||
implicitWidth: 400
|
||||
implicitHeight: content.implicitHeight + Appearance.padding.large * 2
|
||||
|
||||
radius: Appearance.rounding.normal
|
||||
color: Colours.tPalette.m3surface
|
||||
opacity: root.visible ? 1 : 0
|
||||
scale: root.visible ? 1 : 0.9
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: 200
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: 200
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onEscapePressed: {
|
||||
root.session.network.showPasswordDialog = false;
|
||||
passwordField.text = "";
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.margins: Appearance.padding.large
|
||||
|
||||
spacing: Appearance.spacing.normal
|
||||
|
||||
MaterialIcon {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: "lock"
|
||||
font.pointSize: Appearance.font.size.extraLarge * 2
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: qsTr("Enter password")
|
||||
font.pointSize: Appearance.font.size.large
|
||||
font.weight: 500
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: root.network ? qsTr("Network: %1").arg(root.network.ssid) : ""
|
||||
color: Colours.palette.m3outline
|
||||
font.pointSize: Appearance.font.size.small
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.topMargin: Appearance.spacing.large
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: passwordField.implicitHeight + Appearance.padding.normal * 2
|
||||
|
||||
StyledRect {
|
||||
anchors.fill: parent
|
||||
radius: Appearance.rounding.normal
|
||||
color: Colours.tPalette.m3surfaceContainer
|
||||
border.width: passwordField.activeFocus ? 2 : 1
|
||||
border.color: passwordField.activeFocus ? Colours.palette.m3primary : Colours.palette.m3outline
|
||||
|
||||
Behavior on border.color {
|
||||
CAnim {}
|
||||
}
|
||||
}
|
||||
|
||||
StyledTextField {
|
||||
id: passwordField
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.margins: Appearance.padding.normal
|
||||
|
||||
echoMode: TextField.Password
|
||||
placeholderText: qsTr("Password")
|
||||
|
||||
Component.onCompleted: {
|
||||
if (root.visible) {
|
||||
forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onVisibleChanged(): void {
|
||||
if (root.visible) {
|
||||
passwordField.forceActiveFocus();
|
||||
passwordField.text = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onReturnPressed: {
|
||||
if (connectButton.enabled) {
|
||||
connectButton.clicked();
|
||||
}
|
||||
}
|
||||
Keys.onEnterPressed: {
|
||||
if (connectButton.enabled) {
|
||||
connectButton.clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.topMargin: Appearance.spacing.normal
|
||||
Layout.fillWidth: true
|
||||
spacing: Appearance.spacing.normal
|
||||
|
||||
Button {
|
||||
id: cancelButton
|
||||
|
||||
Layout.fillWidth: true
|
||||
color: Colours.palette.m3secondaryContainer
|
||||
onColor: Colours.palette.m3onSecondaryContainer
|
||||
text: qsTr("Cancel")
|
||||
|
||||
function onClicked(): void {
|
||||
root.session.network.showPasswordDialog = false;
|
||||
passwordField.text = "";
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
id: connectButton
|
||||
|
||||
Layout.fillWidth: true
|
||||
color: Colours.palette.m3primary
|
||||
onColor: Colours.palette.m3onPrimary
|
||||
text: qsTr("Connect")
|
||||
enabled: passwordField.text.length > 0
|
||||
|
||||
function onClicked(): void {
|
||||
if (root.network && passwordField.text.length > 0) {
|
||||
Network.connectToNetwork(root.network.ssid, passwordField.text);
|
||||
root.session.network.showPasswordDialog = false;
|
||||
passwordField.text = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component Button: StyledRect {
|
||||
property color onColor: Colours.palette.m3onSurface
|
||||
property alias disabled: stateLayer.disabled
|
||||
property alias text: label.text
|
||||
property alias enabled: stateLayer.enabled
|
||||
|
||||
function onClicked(): void {
|
||||
}
|
||||
|
||||
radius: Appearance.rounding.small
|
||||
implicitHeight: label.implicitHeight + Appearance.padding.small * 2
|
||||
opacity: enabled ? 1 : 0.5
|
||||
|
||||
StateLayer {
|
||||
id: stateLayer
|
||||
|
||||
enabled: parent.enabled
|
||||
color: parent.onColor
|
||||
|
||||
function onClicked(): void {
|
||||
if (enabled) {
|
||||
parent.onClicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: label
|
||||
|
||||
anchors.centerIn: parent
|
||||
animate: true
|
||||
color: parent.onColor
|
||||
font.pointSize: Appearance.font.size.normal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -26,20 +26,42 @@ Singleton {
|
|||
rescanProc.running = true;
|
||||
}
|
||||
|
||||
property var pendingConnection: null
|
||||
signal connectionFailed(string ssid)
|
||||
|
||||
function connectToNetwork(ssid: string, password: string): void {
|
||||
// First try to connect to an existing connection
|
||||
// If that fails, create a new connection
|
||||
if (password && password.length > 0) {
|
||||
connectProc.exec(["nmcli", "device", "wifi", "connect", ssid, "password", password]);
|
||||
} else {
|
||||
// Try to connect to existing connection first
|
||||
// Try to connect to existing connection first (will use saved password if available)
|
||||
connectProc.exec(["nmcli", "device", "wifi", "connect", ssid]);
|
||||
}
|
||||
}
|
||||
|
||||
function connectToNetworkWithPasswordCheck(ssid: string, isSecure: bool, callback: var): void {
|
||||
// For secure networks, try connecting without password first
|
||||
// If connection succeeds (saved password exists), we're done
|
||||
// If it fails with password error, callback will be called to show password dialog
|
||||
if (isSecure) {
|
||||
root.pendingConnection = { ssid: ssid, callback: callback };
|
||||
// Try connecting without password - will use saved password if available
|
||||
connectProc.exec(["nmcli", "device", "wifi", "connect", ssid]);
|
||||
// Start timer to check if connection succeeded
|
||||
connectionCheckTimer.start();
|
||||
} else {
|
||||
connectToNetwork(ssid, "");
|
||||
}
|
||||
}
|
||||
|
||||
function disconnectFromNetwork(): void {
|
||||
if (active) {
|
||||
// Find the device name first, then disconnect
|
||||
// Try to disconnect - use connection name if available, otherwise use device
|
||||
if (active && active.ssid) {
|
||||
// First try to disconnect by connection name (more reliable)
|
||||
disconnectByConnectionProc.exec(["nmcli", "connection", "down", active.ssid]);
|
||||
} else {
|
||||
// Fallback: disconnect by device
|
||||
disconnectProc.exec(["nmcli", "device", "disconnect", "wifi"]);
|
||||
}
|
||||
}
|
||||
|
|
@ -90,18 +112,102 @@ Singleton {
|
|||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: connectionCheckTimer
|
||||
interval: 4000
|
||||
onTriggered: {
|
||||
if (root.pendingConnection) {
|
||||
// Final check - if connection still hasn't succeeded, show password dialog
|
||||
const connected = root.active && root.active.ssid === root.pendingConnection.ssid;
|
||||
if (!connected && root.pendingConnection.callback) {
|
||||
// Connection didn't succeed after multiple checks, show password dialog
|
||||
const pending = root.pendingConnection;
|
||||
root.pendingConnection = null;
|
||||
immediateCheckTimer.stop();
|
||||
immediateCheckTimer.checkCount = 0;
|
||||
pending.callback();
|
||||
} else if (connected) {
|
||||
// Connection succeeded, clear pending
|
||||
root.pendingConnection = null;
|
||||
immediateCheckTimer.stop();
|
||||
immediateCheckTimer.checkCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: immediateCheckTimer
|
||||
interval: 500
|
||||
repeat: true
|
||||
triggeredOnStart: false
|
||||
property int checkCount: 0
|
||||
onTriggered: {
|
||||
if (root.pendingConnection) {
|
||||
checkCount++;
|
||||
const connected = root.active && root.active.ssid === root.pendingConnection.ssid;
|
||||
if (connected) {
|
||||
// Connection succeeded, stop timers and clear pending
|
||||
connectionCheckTimer.stop();
|
||||
immediateCheckTimer.stop();
|
||||
immediateCheckTimer.checkCount = 0;
|
||||
root.pendingConnection = null;
|
||||
} else if (checkCount >= 6) {
|
||||
// Checked 6 times (3 seconds total), connection likely failed
|
||||
// Stop immediate check, let the main timer handle it
|
||||
immediateCheckTimer.stop();
|
||||
immediateCheckTimer.checkCount = 0;
|
||||
}
|
||||
} else {
|
||||
immediateCheckTimer.stop();
|
||||
immediateCheckTimer.checkCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: connectProc
|
||||
|
||||
onExited: {
|
||||
// Refresh network list after connection attempt
|
||||
getNetworks.running = true;
|
||||
|
||||
// Check if connection succeeded after a short delay (network list needs to update)
|
||||
if (root.pendingConnection) {
|
||||
immediateCheckTimer.start();
|
||||
}
|
||||
}
|
||||
stdout: SplitParser {
|
||||
onRead: getNetworks.running = true
|
||||
}
|
||||
stderr: StdioCollector {
|
||||
onStreamFinished: console.warn("Network connection error:", text)
|
||||
onStreamFinished: {
|
||||
const error = text.trim();
|
||||
if (error && error.length > 0) {
|
||||
// Check for specific errors that indicate password is needed
|
||||
// Be careful not to match success messages
|
||||
const needsPassword = (error.includes("Secrets were required") ||
|
||||
error.includes("No secrets provided") ||
|
||||
error.includes("802-11-wireless-security.psk") ||
|
||||
(error.includes("password") && !error.includes("Connection activated")) ||
|
||||
(error.includes("Secrets") && !error.includes("Connection activated")) ||
|
||||
(error.includes("802.11") && !error.includes("Connection activated"))) &&
|
||||
!error.includes("Connection activated") &&
|
||||
!error.includes("successfully");
|
||||
|
||||
if (needsPassword && root.pendingConnection && root.pendingConnection.callback) {
|
||||
// Connection failed because password is needed - show dialog immediately
|
||||
connectionCheckTimer.stop();
|
||||
immediateCheckTimer.stop();
|
||||
const pending = root.pendingConnection;
|
||||
root.pendingConnection = null;
|
||||
pending.callback();
|
||||
} else if (error && error.length > 0 && !error.includes("Connection activated")) {
|
||||
// Only log non-success messages
|
||||
console.warn("Network connection error:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -115,6 +221,36 @@ Singleton {
|
|||
stdout: SplitParser {
|
||||
onRead: getNetworks.running = true
|
||||
}
|
||||
stderr: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const error = text.trim();
|
||||
if (error && error.length > 0 && !error.includes("successfully") && !error.includes("disconnected")) {
|
||||
console.warn("Network device disconnect error:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: disconnectByConnectionProc
|
||||
|
||||
onExited: {
|
||||
// Refresh network list after disconnection
|
||||
getNetworks.running = true;
|
||||
}
|
||||
stdout: SplitParser {
|
||||
onRead: getNetworks.running = true
|
||||
}
|
||||
stderr: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const error = text.trim();
|
||||
if (error && error.length > 0 && !error.includes("successfully") && !error.includes("disconnected")) {
|
||||
console.warn("Network connection disconnect error:", error);
|
||||
// If connection down failed, try device disconnect as fallback
|
||||
disconnectProc.exec(["nmcli", "device", "disconnect", "wifi"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
|
|
@ -182,6 +318,20 @@ Singleton {
|
|||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// Check if pending connection succeeded after network list is fully updated
|
||||
if (root.pendingConnection) {
|
||||
Qt.callLater(() => {
|
||||
const connected = root.active && root.active.ssid === root.pendingConnection.ssid;
|
||||
if (connected) {
|
||||
// Connection succeeded, stop timers and clear pending
|
||||
connectionCheckTimer.stop();
|
||||
immediateCheckTimer.stop();
|
||||
immediateCheckTimer.checkCount = 0;
|
||||
root.pendingConnection = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue