summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--modules/controlcenter/network/Details.qml30
-rw-r--r--modules/controlcenter/network/NetworkList.qml29
-rw-r--r--modules/controlcenter/network/NetworkPane.qml6
-rw-r--r--modules/controlcenter/network/PasswordDialog.qml241
-rw-r--r--services/Network.qml158
5 files changed, 451 insertions, 13 deletions
diff --git a/modules/controlcenter/network/Details.qml b/modules/controlcenter/network/Details.qml
index 86164f1..19e011f 100644
--- a/modules/controlcenter/network/Details.qml
+++ b/modules/controlcenter/network/Details.qml
@@ -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, "");
+ }
+ }
}
}
}
diff --git a/modules/controlcenter/network/NetworkList.qml b/modules/controlcenter/network/NetworkList.qml
index 8dfebc7..df05de7 100644
--- a/modules/controlcenter/network/NetworkList.qml
+++ b/modules/controlcenter/network/NetworkList.qml
@@ -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 {
diff --git a/modules/controlcenter/network/NetworkPane.qml b/modules/controlcenter/network/NetworkPane.qml
index f37eedd..5e8a75a 100644
--- a/modules/controlcenter/network/NetworkPane.qml
+++ b/modules/controlcenter/network/NetworkPane.qml
@@ -76,4 +76,10 @@ RowLayout {
}
}
}
+
+ PasswordDialog {
+ anchors.fill: parent
+ session: root.session
+ z: 1000
+ }
}
diff --git a/modules/controlcenter/network/PasswordDialog.qml b/modules/controlcenter/network/PasswordDialog.qml
new file mode 100644
index 0000000..fa4788c
--- /dev/null
+++ b/modules/controlcenter/network/PasswordDialog.qml
@@ -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
+ }
+ }
+}
+
diff --git a/services/Network.qml b/services/Network.qml
index f2c403e..3ceadab 100644
--- a/services/Network.qml
+++ b/services/Network.qml
@@ -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;
+ }
+ });
+ }
}
}
}