From 9b6fb29d4612faa9411ec582f441c568ad87e593 Mon Sep 17 00:00:00 2001 From: bbedward Date: Tue, 21 Oct 2025 14:27:42 -0400 Subject: [PATCH] nm: updates for NM agent in DMS API v7 --- DMSShell.qml | 18 +- Modals/WifiPasswordModal.qml | 140 +++++++++++-- .../ControlCenter/Details/NetworkDetail.qml | 16 +- Services/DMSService.qml | 3 + Services/NetworkManagerService.qml | 185 +++++++++++++++--- Services/NetworkService.qml | 24 +++ 6 files changed, 319 insertions(+), 67 deletions(-) diff --git a/DMSShell.qml b/DMSShell.qml index 4a80837b..7f9a56b3 100644 --- a/DMSShell.qml +++ b/DMSShell.qml @@ -197,17 +197,19 @@ Item { } } - LazyLoader { - id: wifiPasswordModalLoader + WifiPasswordModal { + id: wifiPasswordModal - active: false + Component.onCompleted: { + PopoutService.wifiPasswordModal = wifiPasswordModal + } + } - WifiPasswordModal { - id: wifiPasswordModal + Connections { + target: NetworkService - Component.onCompleted: { - PopoutService.wifiPasswordModal = wifiPasswordModal - } + function onCredentialsNeeded(token, ssid, setting, fields, hints, reason) { + wifiPasswordModal.showFromPrompt(token, ssid, setting, fields, hints, reason) } } diff --git a/Modals/WifiPasswordModal.qml b/Modals/WifiPasswordModal.qml index 4516fe3e..dcadbea1 100644 --- a/Modals/WifiPasswordModal.qml +++ b/Modals/WifiPasswordModal.qml @@ -15,12 +15,23 @@ DankModal { property string wifiAnonymousIdentityInput: "" property string wifiDomainInput: "" + property bool isPromptMode: false + property string promptToken: "" + property string promptReason: "" + property var promptFields: [] + property string promptSetting: "" + function show(ssid) { wifiPasswordSSID = ssid wifiPasswordInput = "" wifiUsernameInput = "" wifiAnonymousIdentityInput = "" wifiDomainInput = "" + isPromptMode = false + promptToken = "" + promptReason = "" + promptFields = [] + promptSetting = "" const network = NetworkService.wifiNetworks.find(n => n.ssid === ssid) requiresEnterprise = network?.enterprise || false @@ -37,6 +48,41 @@ DankModal { }) } + function showFromPrompt(token, ssid, setting, fields, hints, reason) { + wifiPasswordSSID = ssid + isPromptMode = true + promptToken = token + promptReason = reason + promptFields = fields || [] + promptSetting = setting || "802-11-wireless-security" + + requiresEnterprise = setting === "802-1x" + + if (reason === "wrong-password") { + wifiPasswordInput = "" + wifiUsernameInput = "" + } else { + wifiPasswordInput = "" + wifiUsernameInput = "" + wifiAnonymousIdentityInput = "" + wifiDomainInput = "" + } + + open() + Qt.callLater(() => { + if (contentLoader.item) { + if (reason === "wrong-password" && contentLoader.item.passwordInput) { + contentLoader.item.passwordInput.text = "" + contentLoader.item.passwordInput.forceActiveFocus() + } else if (requiresEnterprise && contentLoader.item.usernameInput) { + contentLoader.item.usernameInput.forceActiveFocus() + } else if (contentLoader.item.passwordInput) { + contentLoader.item.passwordInput.forceActiveFocus() + } + } + }) + } + shouldBeVisible: false width: 420 height: requiresEnterprise ? 430 : 230 @@ -60,6 +106,9 @@ DankModal { }) } onBackgroundClicked: () => { + if (isPromptMode) { + NetworkService.cancelCredentials(promptToken) + } close() wifiPasswordInput = "" wifiUsernameInput = "" @@ -90,6 +139,9 @@ DankModal { anchors.fill: parent focus: true Keys.onEscapePressed: event => { + if (isPromptMode) { + NetworkService.cancelCredentials(promptToken) + } close() wifiPasswordInput = "" wifiUsernameInput = "" @@ -117,12 +169,28 @@ DankModal { font.weight: Font.Medium } - StyledText { - text: requiresEnterprise ? I18n.tr("Enter credentials for ") + wifiPasswordSSID : I18n.tr("Enter password for ") + wifiPasswordSSID - font.pixelSize: Theme.fontSizeMedium - color: Theme.surfaceTextMedium + Column { width: parent.width - elide: Text.ElideRight + spacing: Theme.spacingXS + + StyledText { + text: { + const prefix = requiresEnterprise ? I18n.tr("Enter credentials for ") : I18n.tr("Enter password for ") + return prefix + wifiPasswordSSID + } + font.pixelSize: Theme.fontSizeMedium + color: Theme.surfaceTextMedium + width: parent.width + elide: Text.ElideRight + } + + StyledText { + visible: isPromptMode && promptReason === "wrong-password" + text: I18n.tr("Incorrect password") + font.pixelSize: Theme.fontSizeSmall + color: Theme.error + width: parent.width + } } } @@ -131,6 +199,9 @@ DankModal { iconSize: Theme.iconSize - 4 iconColor: Theme.surfaceText onClicked: () => { + if (isPromptMode) { + NetworkService.cancelCredentials(promptToken) + } close() wifiPasswordInput = "" wifiUsernameInput = "" @@ -208,14 +279,26 @@ DankModal { wifiPasswordInput = text } onAccepted: () => { - const username = requiresEnterprise ? usernameInput.text : "" - NetworkService.connectToWifi( - wifiPasswordSSID, - passwordInput.text, - username, - wifiAnonymousIdentityInput, - wifiDomainInput - ) + if (isPromptMode) { + const secrets = {} + if (promptSetting === "802-11-wireless-security") { + secrets["psk"] = passwordInput.text + } else if (promptSetting === "802-1x") { + if (usernameInput.text) secrets["identity"] = usernameInput.text + if (passwordInput.text) secrets["password"] = passwordInput.text + if (wifiAnonymousIdentityInput) secrets["anonymous-identity"] = wifiAnonymousIdentityInput + } + NetworkService.submitCredentials(promptToken, secrets, true) + } else { + const username = requiresEnterprise ? usernameInput.text : "" + NetworkService.connectToWifi( + wifiPasswordSSID, + passwordInput.text, + username, + wifiAnonymousIdentityInput, + wifiDomainInput + ) + } close() wifiPasswordInput = "" wifiUsernameInput = "" @@ -395,6 +478,9 @@ DankModal { hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: () => { + if (isPromptMode) { + NetworkService.cancelCredentials(promptToken) + } close() wifiPasswordInput = "" wifiUsernameInput = "" @@ -430,14 +516,26 @@ DankModal { cursorShape: Qt.PointingHandCursor enabled: parent.enabled onClicked: () => { - const username = requiresEnterprise ? usernameInput.text : "" - NetworkService.connectToWifi( - wifiPasswordSSID, - passwordInput.text, - username, - wifiAnonymousIdentityInput, - wifiDomainInput - ) + if (isPromptMode) { + const secrets = {} + if (promptSetting === "802-11-wireless-security") { + secrets["psk"] = passwordInput.text + } else if (promptSetting === "802-1x") { + if (usernameInput.text) secrets["identity"] = usernameInput.text + if (passwordInput.text) secrets["password"] = passwordInput.text + if (wifiAnonymousIdentityInput) secrets["anonymous-identity"] = wifiAnonymousIdentityInput + } + NetworkService.submitCredentials(promptToken, secrets, true) + } else { + const username = requiresEnterprise ? usernameInput.text : "" + NetworkService.connectToWifi( + wifiPasswordSSID, + passwordInput.text, + username, + wifiAnonymousIdentityInput, + wifiDomainInput + ) + } close() wifiPasswordInput = "" wifiUsernameInput = "" diff --git a/Modules/ControlCenter/Details/NetworkDetail.qml b/Modules/ControlCenter/Details/NetworkDetail.qml index 3e2188b7..43a1d752 100644 --- a/Modules/ControlCenter/Details/NetworkDetail.qml +++ b/Modules/ControlCenter/Details/NetworkDetail.qml @@ -509,7 +509,11 @@ Rectangle { onClicked: function(event) { if (modelData.ssid !== NetworkService.currentWifiSSID) { if (modelData.secured && !modelData.saved) { - wifiPasswordModal.show(modelData.ssid) + if (DMSService.apiVersion >= 7) { + NetworkService.connectToWifi(modelData.ssid) + } else if (PopoutService.wifiPasswordModal) { + PopoutService.wifiPasswordModal.show(modelData.ssid) + } } else { NetworkService.connectToWifi(modelData.ssid) } @@ -563,7 +567,11 @@ Rectangle { NetworkService.disconnectWifi() } else { if (networkContextMenu.currentSecured && !networkContextMenu.currentSaved) { - wifiPasswordModal.show(networkContextMenu.currentSSID) + if (DMSService.apiVersion >= 7) { + NetworkService.connectToWifi(networkContextMenu.currentSSID) + } else if (PopoutService.wifiPasswordModal) { + PopoutService.wifiPasswordModal.show(networkContextMenu.currentSSID) + } } else { NetworkService.connectToWifi(networkContextMenu.currentSSID) } @@ -618,10 +626,6 @@ Rectangle { } } - WifiPasswordModal { - id: wifiPasswordModal - } - NetworkInfoModal { id: networkInfoModal } diff --git a/Services/DMSService.qml b/Services/DMSService.qml index 559f883e..6e805445 100644 --- a/Services/DMSService.qml +++ b/Services/DMSService.qml @@ -41,6 +41,7 @@ Singleton { signal loginctlStateUpdate(var data) signal loginctlEvent(var event) signal capabilitiesReceived() + signal credentialsRequest(var data) Component.onCompleted: { if (socketPath && socketPath.length > 0) { @@ -261,6 +262,8 @@ Singleton { capabilitiesReceived() } else if (service === "network") { networkStateUpdate(data) + } else if (service === "network.credentials") { + credentialsRequest(data) } else if (service === "loginctl") { if (data.event) { loginctlEvent(data) diff --git a/Services/NetworkManagerService.qml b/Services/NetworkManagerService.qml index 6cb6d995..6589a7d4 100644 --- a/Services/NetworkManagerService.qml +++ b/Services/NetworkManagerService.qml @@ -79,8 +79,21 @@ Singleton { property int refCount: 0 property bool stateInitialized: false + property string credentialsToken: "" + property string credentialsSSID: "" + property string credentialsSetting: "" + property var credentialsFields: [] + property var credentialsHints: [] + property string credentialsReason: "" + property bool credentialsRequested: false + + property string pendingConnectionSSID: "" + property var pendingConnectionStartTime: 0 + property bool wasConnecting: false + signal networksUpdated signal connectionChanged + signal credentialsNeeded(string token, string ssid, string setting, var fields, var hints, string reason) readonly property string socketPath: Quickshell.env("DMS_SOCKET") @@ -120,6 +133,10 @@ Singleton { function onCapabilitiesChanged() { checkDMSCapabilities() } + + function onCredentialsRequest(data) { + handleCredentialsRequest(data) + } } function checkDMSCapabilities() { @@ -143,6 +160,18 @@ Singleton { } } + function handleCredentialsRequest(data) { + credentialsToken = data.token || "" + credentialsSSID = data.ssid || "" + credentialsSetting = data.setting || "802-11-wireless-security" + credentialsFields = data.fields || ["psk"] + credentialsHints = data.hints || [] + credentialsReason = data.reason || "Credentials required" + credentialsRequested = true + + credentialsNeeded(credentialsToken, credentialsSSID, credentialsSetting, credentialsFields, credentialsHints, credentialsReason) + } + function addRef() { refCount++ if (refCount === 1 && networkAvailable) { @@ -177,6 +206,9 @@ Singleton { } function updateState(state) { + const previousConnecting = isConnecting + const previousConnectingSSID = connectingSSID + networkStatus = state.networkStatus || "disconnected" primaryConnection = state.primaryConnection || "" @@ -225,6 +257,45 @@ Singleton { connectionError = state.lastError || "" lastConnectionError = state.lastError || "" + if (pendingConnectionSSID) { + if (wifiConnected && currentWifiSSID === pendingConnectionSSID && wifiIP) { + if (DMSService.verboseLogs) { + const elapsed = Date.now() - pendingConnectionStartTime + console.log("NetworkManagerService: Successfully connected to", pendingConnectionSSID, "in", elapsed, "ms") + } + ToastService.showInfo(`Connected to ${pendingConnectionSSID}`) + + if (userPreference === "wifi" || userPreference === "auto") { + setConnectionPriority("wifi") + } + + pendingConnectionSSID = "" + connectionStatus = "connected" + } else if (previousConnecting && !isConnecting && !wifiConnected) { + const elapsed = Date.now() - pendingConnectionStartTime + + if (elapsed < 5000) { + if (DMSService.verboseLogs) { + console.log("NetworkManagerService: Quick connection failure, likely authentication error") + } + connectionStatus = "invalid_password" + } else { + if (DMSService.verboseLogs) { + console.log("NetworkManagerService: Connection failed for", pendingConnectionSSID) + } + if (connectionError === "connection-failed") { + ToastService.showError(I18n.tr("Connection failed. Check password and try again.")) + } else if (connectionError) { + ToastService.showError(I18n.tr("Failed to connect to ") + pendingConnectionSSID) + } + connectionStatus = "failed" + pendingConnectionSSID = "" + } + } + } + + wasConnecting = isConnecting + connectionChanged() } @@ -242,11 +313,11 @@ Singleton { connectionError = response.error lastConnectionError = response.error connectionStatus = "failed" - ToastService.showError(`Failed to activate configuration`) + ToastService.showError(I18n.tr("Failed to activate configuration")) } else { connectionError = "" connectionStatus = "connected" - ToastService.showInfo(`Configuration activated`) + ToastService.showInfo(I18n.tr("Configuration activated")) } isConnecting = false @@ -280,42 +351,47 @@ Singleton { function connectToWifi(ssid, password = "", username = "", anonymousIdentity = "", domainSuffixMatch = "") { if (!networkAvailable || isConnecting) return - connectingSSID = ssid + pendingConnectionSSID = ssid + pendingConnectionStartTime = Date.now() connectionError = "" connectionStatus = "connecting" + credentialsRequested = false const params = { ssid: ssid } - if (password) params.password = password - if (username) params.username = username - if (anonymousIdentity) params.anonymousIdentity = anonymousIdentity - if (domainSuffixMatch) params.domainSuffixMatch = domainSuffixMatch + + if (DMSService.apiVersion >= 7) { + if (password || username) { + params.password = password + if (username) params.username = username + if (anonymousIdentity) params.anonymousIdentity = anonymousIdentity + if (domainSuffixMatch) params.domainSuffixMatch = domainSuffixMatch + params.interactive = false + } else { + params.interactive = true + } + } else { + if (password) params.password = password + if (username) params.username = username + if (anonymousIdentity) params.anonymousIdentity = anonymousIdentity + if (domainSuffixMatch) params.domainSuffixMatch = domainSuffixMatch + } DMSService.sendRequest("network.wifi.connect", params, response => { if (response.error) { + if (DMSService.verboseLogs) { + console.log("NetworkManagerService: Connection request failed:", response.error) + } + connectionError = response.error lastConnectionError = response.error - connectionStatus = response.error.includes("password") || response.error.includes("authentication") - ? "invalid_password" - : "failed" - - if (connectionStatus === "invalid_password") { - passwordDialogShouldReopen = true - ToastService.showError(`Invalid password for ${ssid}`) - } else { - ToastService.showError(`Failed to connect to ${ssid}`) - } + pendingConnectionSSID = "" + connectionStatus = "failed" + ToastService.showError(I18n.tr("Failed to start connection to ") + ssid) } else { - connectionError = "" - connectionStatus = "connected" - ToastService.showInfo(`Connected to ${ssid}`) - - if (userPreference === "wifi" || userPreference === "auto") { - setConnectionPriority("wifi") + if (DMSService.verboseLogs) { + console.log("NetworkManagerService: Connection request sent for", ssid) } } - - isConnecting = false - connectingSSID = "" }) } @@ -324,15 +400,60 @@ Singleton { DMSService.sendRequest("network.wifi.disconnect", null, response => { if (response.error) { - ToastService.showError("Failed to disconnect WiFi") + ToastService.showError(I18n.tr("Failed to disconnect WiFi")) } else { - ToastService.showInfo("Disconnected from WiFi") + ToastService.showInfo(I18n.tr("Disconnected from WiFi")) currentWifiSSID = "" connectionStatus = "" } }) } + function submitCredentials(token, secrets, save) { + if (!networkAvailable || DMSService.apiVersion < 7) return + + const params = { + token: token, + secrets: secrets, + save: save || false + } + + if (DMSService.verboseLogs) { + console.log("NetworkManagerService: Submitting credentials for token", token) + } + + credentialsRequested = false + + DMSService.sendRequest("network.credentials.submit", params, response => { + if (response.error) { + console.warn("NetworkManagerService: Failed to submit credentials:", response.error) + } + }) + } + + function cancelCredentials(token) { + if (!networkAvailable || DMSService.apiVersion < 7) return + + const params = { + token: token, + cancel: true + } + + if (DMSService.verboseLogs) { + console.log("NetworkManagerService: Cancelling credentials for token", token) + } + + credentialsRequested = false + pendingConnectionSSID = "" + connectionStatus = "cancelled" + + DMSService.sendRequest("network.credentials.submit", params, response => { + if (response.error) { + console.warn("NetworkManagerService: Failed to cancel credentials:", response.error) + } + }) + } + function forgetWifiNetwork(ssid) { if (!networkAvailable) return @@ -341,7 +462,7 @@ Singleton { if (response.error) { console.warn("Failed to forget network:", response.error) } else { - ToastService.showInfo(`Forgot network ${ssid}`) + ToastService.showInfo(I18n.tr("Forgot network ") + ssid) savedConnections = savedConnections.filter(s => s.ssid !== ssid) savedWifiNetworks = savedWifiNetworks.filter(s => s.ssid !== ssid) @@ -374,7 +495,7 @@ Singleton { console.warn("Failed to toggle WiFi:", response.error) } else if (response.result) { wifiEnabled = response.result.enabled - ToastService.showInfo(wifiEnabled ? "WiFi enabled" : "WiFi disabled") + ToastService.showInfo(wifiEnabled ? I18n.tr("WiFi enabled") : I18n.tr("WiFi disabled")) } }) } @@ -384,9 +505,9 @@ Singleton { DMSService.sendRequest("network.wifi.enable", null, response => { if (response.error) { - ToastService.showError("Failed to enable WiFi") + ToastService.showError(I18n.tr("Failed to enable WiFi")) } else { - ToastService.showInfo("WiFi enabled") + ToastService.showInfo(I18n.tr("WiFi enabled")) } }) } diff --git a/Services/NetworkService.qml b/Services/NetworkService.qml index 35fb492c..c8b8c3d2 100644 --- a/Services/NetworkService.qml +++ b/Services/NetworkService.qml @@ -69,8 +69,17 @@ Singleton { property bool subscriptionConnected: activeService?.subscriptionConnected ?? false + property string credentialsToken: activeService?.credentialsToken ?? "" + property string credentialsSSID: activeService?.credentialsSSID ?? "" + property string credentialsSetting: activeService?.credentialsSetting ?? "" + property var credentialsFields: activeService?.credentialsFields ?? [] + property var credentialsHints: activeService?.credentialsHints ?? [] + property string credentialsReason: activeService?.credentialsReason ?? "" + property bool credentialsRequested: activeService?.credentialsRequested ?? false + signal networksUpdated signal connectionChanged + signal credentialsNeeded(string token, string ssid, string setting, var fields, var hints, string reason) property bool usingLegacy: false property var activeService: null @@ -122,6 +131,9 @@ Singleton { if (activeService.connectionChanged) { activeService.connectionChanged.connect(root.connectionChanged) } + if (activeService.credentialsNeeded) { + activeService.credentialsNeeded.connect(root.credentialsNeeded) + } } } @@ -258,4 +270,16 @@ Singleton { activeService.connectToSpecificWiredConfig(uuid) } } + + function submitCredentials(token, secrets, save) { + if (activeService && activeService.submitCredentials) { + activeService.submitCredentials(token, secrets, save) + } + } + + function cancelCredentials(token) { + if (activeService && activeService.cancelCredentials) { + activeService.cancelCredentials(token) + } + } }