summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--modules/bar/popouts/Network.qml2
-rw-r--r--modules/controlcenter/network/Details.qml55
-rw-r--r--modules/controlcenter/network/NetworkList.qml5
-rw-r--r--modules/controlcenter/network/PasswordDialog.qml283
-rw-r--r--services/Network.qml316
5 files changed, 637 insertions, 24 deletions
diff --git a/modules/bar/popouts/Network.qml b/modules/bar/popouts/Network.qml
index f21a92d..f040b6a 100644
--- a/modules/bar/popouts/Network.qml
+++ b/modules/bar/popouts/Network.qml
@@ -118,7 +118,7 @@ ColumnLayout {
Network.disconnectFromNetwork();
} else {
root.connectingToSsid = networkItem.modelData.ssid;
- Network.connectToNetwork(networkItem.modelData.ssid, "");
+ Network.connectToNetwork(networkItem.modelData.ssid, "", networkItem.modelData.bssid, null);
}
}
}
diff --git a/modules/controlcenter/network/Details.qml b/modules/controlcenter/network/Details.qml
index a53f62e..5e636a2 100644
--- a/modules/controlcenter/network/Details.qml
+++ b/modules/controlcenter/network/Details.qml
@@ -95,10 +95,31 @@ Item {
// Callback: connection failed, show password dialog
root.session.network.showPasswordDialog = true;
root.session.network.pendingNetwork = root.network;
- }
+ },
+ root.network.bssid
);
} else {
- Network.connectToNetwork(root.network.ssid, "");
+ Network.connectToNetwork(root.network.ssid, "", root.network.bssid, null);
+ }
+ }
+ }
+
+ Button {
+ Layout.fillWidth: true
+ Layout.topMargin: Appearance.spacing.normal
+ visible: root.network && root.network.ssid && Network.savedConnections.includes(root.network.ssid)
+ color: Colours.palette.m3errorContainer
+ onColor: Colours.palette.m3onErrorContainer
+ text: qsTr("Forget Network")
+
+ onClicked: {
+ if (root.network && root.network.ssid) {
+ // Disconnect first if connected
+ if (root.network.active) {
+ Network.disconnectFromNetwork();
+ }
+ // Delete the connection profile
+ Network.forgetNetwork(root.network.ssid);
}
}
}
@@ -152,9 +173,39 @@ Item {
deviceDetails: Network.wirelessDeviceDetails
}
}
+
}
}
+ component Button: StyledRect {
+ property color onColor: Colours.palette.m3onSurface
+ property alias disabled: stateLayer.disabled
+ property alias text: label.text
+ property alias enabled: stateLayer.enabled
+
+ Layout.fillWidth: true
+ implicitHeight: label.implicitHeight + Appearance.padding.normal * 2
+ radius: Appearance.rounding.normal
+
+ StateLayer {
+ id: stateLayer
+ color: parent.onColor
+ function onClicked(): void {
+ if (parent.enabled !== false) {
+ parent.clicked();
+ }
+ }
+ }
+
+ StyledText {
+ id: label
+ anchors.centerIn: parent
+ color: parent.onColor
+ }
+
+ signal clicked
+ }
+
}
diff --git a/modules/controlcenter/network/NetworkList.qml b/modules/controlcenter/network/NetworkList.qml
index 09d7352..6c4158c 100644
--- a/modules/controlcenter/network/NetworkList.qml
+++ b/modules/controlcenter/network/NetworkList.qml
@@ -203,10 +203,11 @@ ColumnLayout {
// Callback: connection failed, show password dialog
root.session.network.showPasswordDialog = true;
root.session.network.pendingNetwork = modelData;
- }
+ },
+ modelData.bssid
);
} else {
- Network.connectToNetwork(modelData.ssid, "");
+ Network.connectToNetwork(modelData.ssid, "", modelData.bssid, null);
}
}
}
diff --git a/modules/controlcenter/network/PasswordDialog.qml b/modules/controlcenter/network/PasswordDialog.qml
index fa4788c..7aa698b 100644
--- a/modules/controlcenter/network/PasswordDialog.qml
+++ b/modules/controlcenter/network/PasswordDialog.qml
@@ -14,11 +14,37 @@ Item {
id: root
required property Session session
- readonly property var network: session.network.pendingNetwork
+ readonly property var network: {
+ // Try pendingNetwork first, then fall back to active network selection
+ if (session.network.pendingNetwork) {
+ return session.network.pendingNetwork;
+ }
+ // Fallback to active network if available
+ if (session.network.active) {
+ return session.network.active;
+ }
+ return null;
+ }
visible: session.network.showPasswordDialog
enabled: visible
focus: visible
+
+ // Ensure network is set when dialog opens
+ Component.onCompleted: {
+ if (visible && !session.network.pendingNetwork && session.network.active) {
+ session.network.pendingNetwork = session.network.active;
+ }
+ }
+
+ Connections {
+ target: root
+ function onVisibleChanged(): void {
+ if (visible && !session.network.pendingNetwork && session.network.active) {
+ session.network.pendingNetwork = session.network.active;
+ }
+ }
+ }
Keys.onEscapePressed: {
root.session.network.showPasswordDialog = false;
@@ -105,6 +131,35 @@ Item {
font.pointSize: Appearance.font.size.small
}
+ StyledText {
+ id: statusText
+
+ Layout.alignment: Qt.AlignHCenter
+ Layout.topMargin: Appearance.spacing.small
+ visible: Network.connectionStatus.length > 0 || connectButton.connecting
+ text: {
+ if (Network.connectionStatus.length > 0) {
+ return Network.connectionStatus;
+ } else if (connectButton.connecting) {
+ return qsTr("Connecting...");
+ }
+ return "";
+ }
+ color: {
+ const status = Network.connectionStatus;
+ if (status.includes("Error") || status.includes("error") || status.includes("failed")) {
+ return Colours.palette.m3error;
+ } else if (status.includes("successful") || status.includes("Connected") || status.includes("success")) {
+ return Colours.palette.m3primary;
+ }
+ return Colours.palette.m3onSurfaceVariant;
+ }
+ font.pointSize: Appearance.font.size.small
+ font.weight: (Network.connectionStatus.includes("Error") || Network.connectionStatus.includes("error")) ? 500 : 400
+ wrapMode: Text.WordWrap
+ Layout.maximumWidth: parent.width - Appearance.padding.large * 2
+ }
+
Item {
Layout.topMargin: Appearance.spacing.large
Layout.fillWidth: true
@@ -145,9 +200,17 @@ Item {
if (root.visible) {
passwordField.forceActiveFocus();
passwordField.text = "";
+ Network.connectionStatus = "";
}
}
}
+
+ Connections {
+ target: Network
+ function onConnectionStatusChanged(): void {
+ // Status updated, ensure it's visible
+ }
+ }
Keys.onReturnPressed: {
if (connectButton.enabled) {
@@ -190,12 +253,222 @@ Item {
text: qsTr("Connect")
enabled: passwordField.text.length > 0
+ property bool connecting: false
+
function onClicked(): void {
- if (root.network && passwordField.text.length > 0) {
- Network.connectToNetwork(root.network.ssid, passwordField.text);
- root.session.network.showPasswordDialog = false;
- passwordField.text = "";
+ Network.connectionStatus = "";
+
+ // Get password first
+ const password = passwordField.text;
+
+ // Try multiple ways to get the network
+ let networkToUse = null;
+
+ // Try 1: root.network (computed property)
+ if (root.network) {
+ networkToUse = root.network;
+ }
+
+ // Try 2: pendingNetwork
+ if (!networkToUse && root.session.network.pendingNetwork) {
+ networkToUse = root.session.network.pendingNetwork;
+ }
+
+ // Try 3: active network
+ if (!networkToUse && root.session.network.active) {
+ networkToUse = root.session.network.active;
+ root.session.network.pendingNetwork = networkToUse;
+ }
+
+ // Check all conditions
+ const hasNetwork = !!networkToUse;
+ const hasPassword = password && password.length > 0;
+ const notConnecting = !connecting;
+
+ if (hasNetwork && hasPassword && notConnecting) {
+ // Set status immediately
+ Network.connectionStatus = qsTr("Preparing to connect...");
+
+ // Keep dialog open and track connection
+ connecting = true;
+ connectButton.enabled = false;
+ connectButton.text = qsTr("Connecting...");
+
+ // Force immediate UI update
+ statusText.visible = true;
+
+ // Store target SSID for later comparison
+ const ssidToConnect = networkToUse.ssid || "";
+ const bssidToConnect = networkToUse.bssid || "";
+
+ // Store the SSID we're connecting to so we can compare later
+ // even if root.network changes
+ content.connectingToSsid = ssidToConnect;
+
+ // Execute connection immediately
+ Network.connectToNetwork(
+ ssidToConnect,
+ password,
+ bssidToConnect,
+ () => {
+ // Callback if connection fails - keep dialog open
+ connecting = false;
+ connectButton.enabled = true;
+ connectButton.text = qsTr("Connect");
+ content.connectingToSsid = ""; // Clear on failure
+ }
+ );
+
+ // Start connection check timer immediately
+ connectionCheckTimer.checkCount = 0;
+ connectionCheckTimer.start();
+
+ // Also check immediately after a short delay to catch quick connections
+ Qt.callLater(() => {
+ if (root.visible) {
+ closeDialogIfConnected();
+ }
+ });
+ } else {
+ // Show error in status
+ Network.connectionStatus = qsTr("Error: Cannot connect - missing network or password");
+ }
+ }
+ }
+ }
+
+ // Store the SSID we're connecting to when connection starts
+ property string connectingToSsid: ""
+
+ property string targetSsid: {
+ // Track the SSID we're trying to connect to
+ // Prefer explicitly stored connectingToSsid, then computed values
+ if (connectingToSsid && connectingToSsid.length > 0) {
+ return connectingToSsid;
+ }
+ if (root.network && root.network.ssid) {
+ return root.network.ssid;
+ }
+ if (root.session.network.pendingNetwork && root.session.network.pendingNetwork.ssid) {
+ return root.session.network.pendingNetwork.ssid;
+ }
+ return "";
+ }
+
+ function closeDialogIfConnected(): bool {
+ // Check if we're connected to the network we're trying to connect to
+ const ssid = targetSsid;
+
+ if (!ssid || ssid.length === 0) {
+ return false;
+ }
+
+ if (!Network.active) {
+ return false;
+ }
+
+ const activeSsid = Network.active.ssid || "";
+
+ if (activeSsid === ssid) {
+ // Connection succeeded - close dialog
+ connectionCheckTimer.stop();
+ aggressiveCheckTimer.stop();
+ connectionCheckTimer.checkCount = 0;
+ connectButton.connecting = false;
+ Network.connectionStatus = "";
+ root.session.network.showPasswordDialog = false;
+ passwordField.text = "";
+ content.connectingToSsid = ""; // Clear stored SSID
+ return true;
+ }
+ return false;
+ }
+
+ Timer {
+ id: connectionCheckTimer
+ interval: 1000 // Check every 1 second for faster response
+ repeat: true
+ triggeredOnStart: false
+ property int checkCount: 0
+
+ onTriggered: {
+ checkCount++;
+
+ // Try to close dialog if connected
+ const closed = content.closeDialogIfConnected();
+ if (closed) {
+ return;
+ }
+
+ if (connectButton.connecting) {
+ // Still connecting, check again
+ // Limit to 20 checks (20 seconds total)
+ if (checkCount >= 20) {
+ connectionCheckTimer.stop();
+ connectionCheckTimer.checkCount = 0;
+ connectButton.connecting = false;
+ connectButton.enabled = true;
+ connectButton.text = qsTr("Connect");
+ }
+ } else {
+ // Not connecting anymore, stop timer
+ connectionCheckTimer.stop();
+ connectionCheckTimer.checkCount = 0;
+ }
+ }
+ }
+
+ Connections {
+ target: Network
+ function onActiveChanged(): void {
+ // Check immediately when active network changes
+ if (root.visible) {
+ // Check immediately - if connected, close right away
+ if (content.closeDialogIfConnected()) {
+ return;
+ }
+
+ // Also check after a delay in case the active network isn't fully updated yet
+ Qt.callLater(() => {
+ if (root.visible) {
+ content.closeDialogIfConnected();
+ }
+ });
+ }
+ }
+ }
+
+ // Also check when dialog becomes visible
+ Connections {
+ target: root
+ function onVisibleChanged(): void {
+ if (root.visible) {
+ // Check immediately when dialog opens
+ Qt.callLater(() => {
+ if (root.visible) {
+ closeDialogIfConnected();
+ }
+ });
+ }
+ }
+ }
+
+ // Aggressive polling timer - checks every 500ms when dialog is visible and connecting
+ // This ensures we catch the connection even if signals are missed
+ Timer {
+ id: aggressiveCheckTimer
+ interval: 500
+ repeat: true
+ running: root.visible && connectButton.connecting
+ triggeredOnStart: true
+
+ onTriggered: {
+ if (root.visible && connectButton.connecting) {
+ if (content.closeDialogIfConnected()) {
+ stop();
}
+ } else {
+ stop();
}
}
}
diff --git a/services/Network.qml b/services/Network.qml
index c8ab264..ea5c3e7 100644
--- a/services/Network.qml
+++ b/services/Network.qml
@@ -12,6 +12,8 @@ Singleton {
Qt.callLater(() => {
getEthernetDevices();
});
+ // Load saved connections on startup
+ listConnectionsProc.running = true;
}
readonly property list<AccessPoint> networks: []
@@ -26,6 +28,34 @@ Singleton {
property bool ethernetProcessRunning: false
property var ethernetDeviceDetails: null
property var wirelessDeviceDetails: null
+ property string connectionStatus: ""
+ property string connectionDebug: ""
+
+ function clearConnectionStatus(): void {
+ connectionStatus = "";
+ // Don't clear debug - keep it for reference
+ // connectionDebug = "";
+ }
+
+ function setConnectionStatus(status: string): void {
+ connectionStatus = status;
+ }
+
+ function addDebugInfo(info: string): void {
+ const timestamp = new Date().toLocaleTimeString();
+ const newInfo = "[" + timestamp + "] " + info;
+ // CRITICAL: Always append - NEVER replace
+ // Get current value - NEVER allow it to be empty/cleared
+ let current = connectionDebug;
+ if (!current || current === undefined || current === null) {
+ current = "";
+ }
+ // ALWAYS append - never replace
+ // If current is empty, just use newInfo, otherwise append with newline
+ const updated = (current.length > 0) ? (current + "\n" + newInfo) : newInfo;
+ // CRITICAL: Only assign if we're appending, never replace
+ connectionDebug = updated;
+ }
function enableWifi(enabled: bool): void {
const cmd = enabled ? "on" : "off";
@@ -44,30 +74,118 @@ Singleton {
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
+ function connectToNetwork(ssid: string, password: string, bssid: string, callback: var): void {
+ // When password is provided, use BSSID for more reliable connection
+ // When no password, use SSID (will use saved password if available)
+ const hasBssid = bssid !== undefined && bssid !== null && bssid.length > 0;
+ let cmd = [];
+
+ // Set up pending connection tracking if callback provided
+ if (callback) {
+ root.pendingConnection = { ssid: ssid, bssid: hasBssid ? bssid : "", callback: callback };
+ }
+
if (password && password.length > 0) {
- connectProc.exec(["nmcli", "device", "wifi", "connect", ssid, "password", password]);
+ // When password is provided, try BSSID first if available, otherwise use SSID
+ if (hasBssid) {
+ // Use BSSID when password is provided - ensure BSSID is uppercase
+ const bssidUpper = bssid.toUpperCase();
+ // Create connection profile with all required properties for BSSID + password
+ // First remove any existing connection with this name
+ cmd = ["nmcli", "connection", "add",
+ "type", "wifi",
+ "con-name", ssid,
+ "ifname", "*",
+ "ssid", ssid,
+ "802-11-wireless.bssid", bssidUpper,
+ "802-11-wireless-security.key-mgmt", "wpa-psk",
+ "802-11-wireless-security.psk", password];
+ root.setConnectionStatus(qsTr("Connecting to %1 (BSSID: %2)...").arg(ssid).arg(bssidUpper));
+ root.addDebugInfo(qsTr("Using BSSID: %1 for SSID: %2").arg(bssidUpper).arg(ssid));
+ root.addDebugInfo(qsTr("Creating connection profile with password and key-mgmt"));
+ } else {
+ // Fallback to SSID if BSSID not available - use device wifi connect
+ cmd = ["nmcli", "device", "wifi", "connect", ssid, "password", password];
+ root.setConnectionStatus(qsTr("Connecting to %1...").arg(ssid));
+ root.addDebugInfo(qsTr("Using SSID only (no BSSID): %1").arg(ssid));
+ }
} else {
// Try to connect to existing connection first (will use saved password if available)
- connectProc.exec(["nmcli", "device", "wifi", "connect", ssid]);
+ cmd = ["nmcli", "device", "wifi", "connect", ssid];
+ root.setConnectionStatus(qsTr("Connecting to %1 (using saved password)...").arg(ssid));
+ root.addDebugInfo(qsTr("Using saved password for: %1").arg(ssid));
+ }
+
+ // Show the exact command being executed
+ const cmdStr = cmd.join(" ");
+ root.addDebugInfo(qsTr("=== COMMAND TO EXECUTE ==="));
+ root.addDebugInfo(qsTr("Command: %1").arg(cmdStr));
+ root.addDebugInfo(qsTr("Command array: [%1]").arg(cmd.map((arg, i) => `"${arg}"`).join(", ")));
+ root.addDebugInfo(qsTr("Command array length: %1").arg(cmd.length));
+ root.addDebugInfo(qsTr("==========================="));
+
+ // Set command and start process
+ root.addDebugInfo(qsTr("Setting command property..."));
+ connectProc.command = cmd;
+ const setCmdStr = connectProc.command ? connectProc.command.join(" ") : "null";
+ root.addDebugInfo(qsTr("Command property set, value: %1").arg(setCmdStr));
+ root.addDebugInfo(qsTr("Command property verified: %1").arg(setCmdStr === cmdStr ? "Match" : "MISMATCH"));
+
+ // If we're creating a connection profile, we need to activate it after creation
+ const isConnectionAdd = cmd.length > 0 && cmd[0] === "nmcli" && cmd[1] === "connection" && cmd[2] === "add";
+
+ // Wait a moment before starting to ensure command is set
+ Qt.callLater(() => {
+ root.addDebugInfo(qsTr("=== STARTING PROCESS ==="));
+ root.addDebugInfo(qsTr("Current running state: %1").arg(connectProc.running));
+ root.addDebugInfo(qsTr("Command to run: %1").arg(connectProc.command ? connectProc.command.join(" ") : "NOT SET"));
+ root.addDebugInfo(qsTr("Is connection add command: %1").arg(isConnectionAdd));
+ connectProc.running = true;
+ root.addDebugInfo(qsTr("Process running set to: %1").arg(connectProc.running));
+ root.addDebugInfo(qsTr("========================"));
+
+ // Check if process actually started after a short delay
+ Qt.callLater(() => {
+ root.addDebugInfo(qsTr("Process status check (100ms later):"));
+ root.addDebugInfo(qsTr(" Running: %1").arg(connectProc.running));
+ root.addDebugInfo(qsTr(" Command: %1").arg(connectProc.command ? connectProc.command.join(" ") : "null"));
+ if (!connectProc.running) {
+ root.addDebugInfo(qsTr("WARNING: Process did not start!"));
+ root.setConnectionStatus(qsTr("Error: Process failed to start"));
+ }
+ }, 100);
+ });
+
+ // Start connection check timer if we have a callback
+ if (callback) {
+ root.addDebugInfo(qsTr("Starting connection check timer (4 second interval)"));
+ connectionCheckTimer.start();
+ } else {
+ root.addDebugInfo(qsTr("No callback provided - not starting connection check timer"));
}
}
- function connectToNetworkWithPasswordCheck(ssid: string, isSecure: bool, callback: var): void {
+ function connectToNetworkWithPasswordCheck(ssid: string, isSecure: bool, callback: var, bssid: string): void {
+ root.addDebugInfo(qsTr("=== connectToNetworkWithPasswordCheck ==="));
+ root.addDebugInfo(qsTr("SSID: %1, isSecure: %2").arg(ssid).arg(isSecure));
+
// 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 };
+ const hasBssid = bssid !== undefined && bssid !== null && bssid.length > 0;
+ root.pendingConnection = { ssid: ssid, bssid: hasBssid ? bssid : "", callback: callback };
+ root.addDebugInfo(qsTr("Trying to connect without password (will use saved if available)"));
// Try connecting without password - will use saved password if available
connectProc.exec(["nmcli", "device", "wifi", "connect", ssid]);
// Start timer to check if connection succeeded
+ root.addDebugInfo(qsTr("Starting connection check timer"));
connectionCheckTimer.start();
} else {
- connectToNetwork(ssid, "");
+ root.addDebugInfo(qsTr("Network is not secure, connecting directly"));
+ connectToNetwork(ssid, "", bssid, null);
}
+ root.addDebugInfo(qsTr("========================================="));
}
function disconnectFromNetwork(): void {
@@ -80,6 +198,50 @@ Singleton {
disconnectProc.exec(["nmcli", "device", "disconnect", "wifi"]);
}
}
+
+ function forgetNetwork(ssid: string): void {
+ // Delete the connection profile for this network
+ // This will remove the saved password and connection settings
+ if (ssid && ssid.length > 0) {
+ deleteConnectionProc.exec(["nmcli", "connection", "delete", ssid]);
+ // Also refresh network list after deletion
+ Qt.callLater(() => {
+ getNetworks.running = true;
+ }, 500);
+ }
+ }
+
+ function hasConnectionProfile(ssid: string): bool {
+ // Check if a connection profile exists for this SSID
+ // This is synchronous check - returns true if connection exists
+ if (!ssid || ssid.length === 0) {
+ return false;
+ }
+ // Use nmcli to check if connection exists
+ // We'll use a Process to check, but for now return false
+ // The actual check will be done asynchronously
+ return false;
+ }
+
+ property list<string> savedConnections: []
+
+ Process {
+ id: listConnectionsProc
+ command: ["nmcli", "-t", "-f", "NAME", "connection", "show"]
+ onExited: {
+ if (exitCode === 0) {
+ // Parse connection names from output
+ const connections = stdout.text.trim().split("\n").filter(name => name.length > 0);
+ root.savedConnections = connections;
+ }
+ }
+ stdout: StdioCollector {
+ onStreamFinished: {
+ const connections = text.trim().split("\n").filter(name => name.length > 0);
+ root.savedConnections = connections;
+ }
+ }
+ }
function getWifiStatus(): void {
wifiStatusProc.running = true;
@@ -184,11 +346,17 @@ Singleton {
id: connectionCheckTimer
interval: 4000
onTriggered: {
+ root.addDebugInfo(qsTr("=== CONNECTION CHECK TIMER (4s) ==="));
if (root.pendingConnection) {
- // Final check - if connection still hasn't succeeded, show password dialog
const connected = root.active && root.active.ssid === root.pendingConnection.ssid;
+ root.addDebugInfo(qsTr("Checking connection status..."));
+ root.addDebugInfo(qsTr(" Pending SSID: %1").arg(root.pendingConnection.ssid));
+ root.addDebugInfo(qsTr(" Active SSID: %1").arg(root.active ? root.active.ssid : "None"));
+ root.addDebugInfo(qsTr(" Connected: %1").arg(connected));
+
if (!connected && root.pendingConnection.callback) {
// Connection didn't succeed after multiple checks, show password dialog
+ root.addDebugInfo(qsTr("Connection failed - calling password dialog callback"));
const pending = root.pendingConnection;
root.pendingConnection = null;
immediateCheckTimer.stop();
@@ -196,11 +364,19 @@ Singleton {
pending.callback();
} else if (connected) {
// Connection succeeded, clear pending
+ root.addDebugInfo(qsTr("Connection succeeded!"));
+ root.setConnectionStatus(qsTr("Connected successfully!"));
root.pendingConnection = null;
immediateCheckTimer.stop();
immediateCheckTimer.checkCount = 0;
+ } else {
+ root.addDebugInfo(qsTr("Still connecting..."));
+ root.setConnectionStatus(qsTr("Still connecting..."));
}
+ } else {
+ root.addDebugInfo(qsTr("No pending connection"));
}
+ root.addDebugInfo(qsTr("================================"));
}
}
@@ -210,23 +386,37 @@ Singleton {
repeat: true
triggeredOnStart: false
property int checkCount: 0
+
+ onRunningChanged: {
+ if (running) {
+ root.addDebugInfo(qsTr("Immediate check timer started (checks every 500ms)"));
+ }
+ }
+
onTriggered: {
if (root.pendingConnection) {
checkCount++;
const connected = root.active && root.active.ssid === root.pendingConnection.ssid;
+ root.addDebugInfo(qsTr("Immediate check #%1: Connected=%2").arg(checkCount).arg(connected));
+
if (connected) {
// Connection succeeded, stop timers and clear pending
+ root.addDebugInfo(qsTr("Connection succeeded on check #%1!").arg(checkCount));
+ root.setConnectionStatus(qsTr("Connected successfully!"));
connectionCheckTimer.stop();
immediateCheckTimer.stop();
immediateCheckTimer.checkCount = 0;
root.pendingConnection = null;
} else if (checkCount >= 6) {
+ root.addDebugInfo(qsTr("Checked %1 times (3 seconds) - connection taking longer").arg(checkCount));
+ root.setConnectionStatus(qsTr("Connection taking longer than expected..."));
// Checked 6 times (3 seconds total), connection likely failed
// Stop immediate check, let the main timer handle it
immediateCheckTimer.stop();
immediateCheckTimer.checkCount = 0;
}
} else {
+ root.addDebugInfo(qsTr("Immediate check: No pending connection, stopping timer"));
immediateCheckTimer.stop();
immediateCheckTimer.checkCount = 0;
}
@@ -236,22 +426,92 @@ Singleton {
Process {
id: connectProc
+ onRunningChanged: {
+ root.addDebugInfo(qsTr("Process running changed to: %1").arg(running));
+ }
+
+ onStarted: {
+ root.addDebugInfo(qsTr("Process started successfully"));
+ }
+
onExited: {
+ root.addDebugInfo(qsTr("=== PROCESS EXITED ==="));
+ root.addDebugInfo(qsTr("Exit code: %1").arg(exitCode));
+ root.addDebugInfo(qsTr("(Exit code 0 = success, non-zero = error)"));
+
+ // Check if this was a "connection add" command - if so, we need to activate it
+ const wasConnectionAdd = connectProc.command && connectProc.command.length > 0
+ && connectProc.command[0] === "nmcli"
+ && connectProc.command[1] === "connection"
+ && connectProc.command[2] === "add";
+
+ if (wasConnectionAdd && exitCode === 0 && root.pendingConnection) {
+ // Connection profile was created successfully, now activate it
+ const ssid = root.pendingConnection.ssid;
+ root.addDebugInfo(qsTr("Connection profile created successfully, now activating: %1").arg(ssid));
+ root.setConnectionStatus(qsTr("Activating connection..."));
+
+ // Update saved connections list since we just created one
+ listConnectionsProc.running = true;
+
+ // Activate the connection we just created
+ connectProc.command = ["nmcli", "connection", "up", ssid];
+ Qt.callLater(() => {
+ connectProc.running = true;
+ });
+ // Don't start timers yet - wait for activation to complete
+ return;
+ }
+
// 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();
+ if (exitCode === 0) {
+ // Process succeeded, start checking connection status
+ root.setConnectionStatus(qsTr("Connection command succeeded, verifying..."));
+ root.addDebugInfo(qsTr("Command succeeded, checking connection status..."));
+ root.addDebugInfo(qsTr("Starting immediate check timer (500ms intervals)"));
+ immediateCheckTimer.start();
+ } else {
+ // Process failed, but wait a moment to see if connection still works
+ root.setConnectionStatus(qsTr("Connection command exited with code %1, checking status...").arg(exitCode));
+ root.addDebugInfo(qsTr("Command exited with error code %1").arg(exitCode));
+ root.addDebugInfo(qsTr("This usually means the command failed"));
+ root.addDebugInfo(qsTr("Checking connection status anyway..."));
+ root.addDebugInfo(qsTr("Starting immediate check timer (500ms intervals)"));
+ immediateCheckTimer.start();
+ }
+ } else {
+ root.addDebugInfo(qsTr("No pending connection - not starting immediate check timer"));
}
+ root.addDebugInfo(qsTr("======================"));
}
stdout: SplitParser {
- onRead: getNetworks.running = true
+ onRead: {
+ getNetworks.running = true;
+ // Also log output for debugging
+ if (text && text.trim().length > 0) {
+ root.addDebugInfo(qsTr("STDOUT: %1").arg(text.trim()));
+ root.setConnectionStatus(qsTr("Status: %1").arg(text.trim()));
+ }
+ }
}
stderr: StdioCollector {
onStreamFinished: {
const error = text.trim();
+ root.addDebugInfo(qsTr("=== STDERR OUTPUT ==="));
if (error && error.length > 0) {
+ // Split error into lines and add each one
+ const errorLines = error.split("\n");
+ for (let i = 0; i < errorLines.length; i++) {
+ const line = errorLines[i].trim();
+ if (line.length > 0) {
+ root.addDebugInfo(qsTr("STDERR: %1").arg(line));
+ }
+ }
+
// Check for specific errors that indicate password is needed
// Be careful not to match success messages
const needsPassword = (error.includes("Secrets were required") ||
@@ -270,11 +530,19 @@ Singleton {
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);
+ } else if (error && error.length > 0 && !error.includes("Connection activated") && !error.includes("successfully")) {
+ // Log all errors (except success messages)
+ root.setConnectionStatus(qsTr("Error: %1").arg(errorLines[0] || error));
+ // Emit signal for UI to handle
+ root.connectionFailed(root.pendingConnection ? root.pendingConnection.ssid : "");
+ } else if (error && (error.includes("Connection activated") || error.includes("successfully"))) {
+ root.addDebugInfo(qsTr("Connection successful!"));
+ root.setConnectionStatus(qsTr("Connection successful!"));
}
+ } else {
+ root.addDebugInfo(qsTr("STDERR: (empty)"));
}
+ root.addDebugInfo(qsTr("===================="));
}
}
}
@@ -322,6 +590,26 @@ Singleton {
}
Process {
+ id: deleteConnectionProc
+
+ // Delete connection profile - refresh network list and saved connections after deletion
+ onExited: {
+ // Refresh network list and saved connections after deletion
+ getNetworks.running = true;
+ listConnectionsProc.running = true;
+ }
+ stderr: StdioCollector {
+ onStreamFinished: {
+ const error = text.trim();
+ if (error && error.length > 0) {
+ // Log error but don't fail - connection might not exist
+ console.warn("Network connection delete error:", error);
+ }
+ }
+ }
+ }
+
+ Process {
id: getNetworks
running: true