From 4c9a51bf6a9720c3f0effd7d286119e628a67090 Mon Sep 17 00:00:00 2001 From: JPratama7 Date: Sun, 3 Aug 2025 06:29:57 +0700 Subject: [PATCH 01/10] chore: add Program --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index fed6799..b8d3939 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .qmlls.ini -notification_history.json \ No newline at end of file +notification_history.json +Programs \ No newline at end of file From d2ea87b46b2bf5f0ed132a9d07bd4cf9fd8c5ae8 Mon Sep 17 00:00:00 2001 From: JPratama7 Date: Sun, 3 Aug 2025 06:33:44 +0700 Subject: [PATCH 02/10] feat: add refresh button for wifi --- Widgets/Sidebar/Panel/WifiPanel.qml | 77 +++++++++++++++-------------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/Widgets/Sidebar/Panel/WifiPanel.qml b/Widgets/Sidebar/Panel/WifiPanel.qml index 008a3c1..3c115ef 100644 --- a/Widgets/Sidebar/Panel/WifiPanel.qml +++ b/Widgets/Sidebar/Panel/WifiPanel.qml @@ -34,42 +34,40 @@ Item { } stdout: StdioCollector { onStreamFinished: { - var lines = text.split("\n"); - var nets = []; - var seen = {}; - for (var i = 0; i < lines.length; ++i) { - var line = lines[i].trim(); + const lines = text.split("\n"); + const networksMap = {}; + + for (let i = 0; i < lines.length; ++i) { + const line = lines[i].trim(); if (!line) continue; - var parts = line.split(":"); - var ssid = parts[0]; - var security = parts[1]; - var signal = parseInt(parts[2]); - var inUse = parts[3] === "*"; + + const parts = line.split(":"); + if (parts.length < 4) { + console.warn("Malformed nmcli output line:", line); + continue; + } + + const ssid = parts[0]; + const security = parts[1]; + const signal = parseInt(parts[2]); + const inUse = parts[3] === "*"; + if (ssid) { - if (!seen[ssid]) { - // First time seeing this SSID - nets.push({ ssid: ssid, security: security, signal: signal, connected: inUse }); - seen[ssid] = true; + if (!networksMap[ssid]) { + networksMap[ssid] = { ssid: ssid, security: security, signal: signal, connected: inUse }; } else { - // SSID already exists, update if this entry has better signal or is connected - for (var j = 0; j < nets.length; ++j) { - if (nets[j].ssid === ssid) { - // Update connection status if this entry is connected - if (inUse) { - nets[j].connected = true; - } - // Update signal if this entry has better signal - if (signal > nets[j].signal) { - nets[j].signal = signal; - nets[j].security = security; - } - break; - } + const existingNet = networksMap[ssid]; + if (inUse) { + existingNet.connected = true; + } + if (signal > existingNet.signal) { + existingNet.signal = signal; + existingNet.security = security; } } } } - wifiLogic.networks = nets; + wifiLogic.networks = Object.values(networksMap); } } } @@ -115,17 +113,17 @@ Item { listConnectionsProcess.running = true; } function doConnect() { - var params = wifiLogic.pendingConnect; + const params = wifiLogic.pendingConnect; wifiLogic.connectingSsid = params.ssid; if (params.security && params.security !== "--") { getInterfaceProcess.running = true; - } else { - connectProcess.security = params.security; - connectProcess.ssid = params.ssid; - connectProcess.password = params.password; - connectProcess.running = true; - wifiLogic.pendingConnect = null; + return; } + connectProcess.security = params.security; + connectProcess.ssid = params.ssid; + connectProcess.password = params.password; + connectProcess.running = true; + wifiLogic.pendingConnect = null; } function isSecured(security) { return security && security.trim() !== "" && security.trim() !== "--"; @@ -400,8 +398,13 @@ Item { color: Theme.textPrimary Layout.fillWidth: true } + Item { Layout.fillWidth: true } + IconButton { + icon: "refresh" + onClicked: wifiLogic.refreshNetworks() + } Rectangle { - width: 36; height: 36; radius: 18 + implicitWidth: 36; implicitHeight: 36; radius: 18 color: closeButtonArea.containsMouse ? Theme.accentPrimary : "transparent" border.color: Theme.accentPrimary border.width: 1 From a419e56690aaee87dfd95e0ce75039dc51b404b8 Mon Sep 17 00:00:00 2001 From: JPratama7 Date: Sun, 3 Aug 2025 07:01:31 +0700 Subject: [PATCH 03/10] feat: add loading when scanning wifi --- Widgets/Sidebar/Panel/WifiPanel.qml | 41 ++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/Widgets/Sidebar/Panel/WifiPanel.qml b/Widgets/Sidebar/Panel/WifiPanel.qml index 3c115ef..127ad4f 100644 --- a/Widgets/Sidebar/Panel/WifiPanel.qml +++ b/Widgets/Sidebar/Panel/WifiPanel.qml @@ -30,7 +30,10 @@ Item { running: false command: ["nmcli", "-t", "-f", "SSID,SECURITY,SIGNAL,IN-USE", "device", "wifi", "list"] onRunningChanged: { - // Removed debug log + console.log("scanProcess.running changed: " + running); + // if (!running) { + // console.log("scanProcess finished."); + // } } stdout: StdioCollector { onStreamFinished: { @@ -399,10 +402,22 @@ Item { Layout.fillWidth: true } Item { Layout.fillWidth: true } + Spinner { + id: refreshIndicator + Layout.preferredWidth: 24 + Layout.preferredHeight: 24 + Layout.alignment: Qt.AlignVCenter + visible: scanProcess.running + running: scanProcess.running + color: Theme.accentPrimary // Assuming Spinner supports color property + size: 22 // Based on the existing Spinner usage + } IconButton { + id: refreshButton icon: "refresh" onClicked: wifiLogic.refreshNetworks() } + Rectangle { implicitWidth: 36; implicitHeight: 36; radius: 18 color: closeButtonArea.containsMouse ? Theme.accentPrimary : "transparent" @@ -557,18 +572,18 @@ Item { verticalAlignment: Text.AlignVCenter Layout.alignment: Qt.AlignVCenter } - Item { - Layout.alignment: Qt.AlignVCenter - Layout.preferredHeight: 22 - Layout.preferredWidth: 22 - Spinner { - visible: wifiLogic.connectingSsid === modelData.ssid - running: wifiLogic.connectingSsid === modelData.ssid - color: Theme.accentPrimary - anchors.centerIn: parent - size: 22 - } - } + Item { + Layout.alignment: Qt.AlignVCenter + Layout.preferredHeight: 22 + Layout.preferredWidth: 22 + Spinner { + visible: wifiLogic.connectingSsid === modelData.ssid + running: wifiLogic.connectingSsid === modelData.ssid + color: Theme.accentPrimary + anchors.centerIn: parent + size: 22 + } + } } MouseArea { id: networkMouseArea From fbea11ee9c10d7c4d698a0c73ac0d5c579c9fc07 Mon Sep 17 00:00:00 2001 From: JPratama7 Date: Sun, 3 Aug 2025 08:35:53 +0700 Subject: [PATCH 04/10] feat: add on refresh while still connecting --- Widgets/Sidebar/Panel/WifiPanel.qml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Widgets/Sidebar/Panel/WifiPanel.qml b/Widgets/Sidebar/Panel/WifiPanel.qml index 127ad4f..dc696c1 100644 --- a/Widgets/Sidebar/Panel/WifiPanel.qml +++ b/Widgets/Sidebar/Panel/WifiPanel.qml @@ -198,6 +198,12 @@ Item { property string password: "" property string security: "" running: false + onStarted: { + refreshIndicator.running = true; + } + onExited: (exitCode, exitStatus) => { + refreshIndicator.running = false; + } command: { if (password) { return ["nmcli", "device", "wifi", "connect", ssid, "password", password] From 5ad7d731c0a53e8e7634094acf4194dc5cf592f0 Mon Sep 17 00:00:00 2001 From: JPratama7 Date: Sun, 3 Aug 2025 19:39:55 +0700 Subject: [PATCH 05/10] feat: improve wifi connection handling with existing network detection and action panel --- Widgets/Sidebar/Panel/WifiPanel.qml | 258 ++++++++++++++++++++-------- 1 file changed, 188 insertions(+), 70 deletions(-) diff --git a/Widgets/Sidebar/Panel/WifiPanel.qml b/Widgets/Sidebar/Panel/WifiPanel.qml index dc696c1..6a2083d 100644 --- a/Widgets/Sidebar/Panel/WifiPanel.qml +++ b/Widgets/Sidebar/Panel/WifiPanel.qml @@ -17,6 +17,10 @@ Item { wifiLogic.refreshNetworks(); } + Component.onCompleted: { + existingNetwork.running = true; + } + function signalIcon(signal) { if (signal >= 80) return "network_wifi"; if (signal >= 60) return "network_wifi_3_bar"; @@ -25,16 +29,48 @@ Item { return "wifi_0_bar"; } + Process { + id: existingNetwork + running: false + command: ["nmcli", "-t", "-f", "NAME,TYPE", "connection", "show"] + stdout: StdioCollector { + onStreamFinished: { + const lines = text.split("\n"); + const networksMap = {}; + + refreshIndicator.running = true; + refreshIndicator.visible = true; + + for (let i = 0; i < lines.length; ++i) { + const line = lines[i].trim(); + if (!line) continue; + + const parts = line.split(":"); + if (parts.length < 2) { + console.warn("Malformed nmcli output line:", line); + continue; + } + + const ssid = parts[0]; + const type = parts[1]; + + if (ssid) { + networksMap[ssid] = { ssid: ssid, type: type }; + } + } + scanProcess.existingNetwork = networksMap; + scanProcess.running = true; + } + } + } + Process { id: scanProcess running: false command: ["nmcli", "-t", "-f", "SSID,SECURITY,SIGNAL,IN-USE", "device", "wifi", "list"] - onRunningChanged: { - console.log("scanProcess.running changed: " + running); - // if (!running) { - // console.log("scanProcess finished."); - // } - } + + property var existingNetwork + stdout: StdioCollector { onStreamFinished: { const lines = text.split("\n"); @@ -49,7 +85,6 @@ Item { console.warn("Malformed nmcli output line:", line); continue; } - const ssid = parts[0]; const security = parts[1]; const signal = parseInt(parts[2]); @@ -57,7 +92,7 @@ Item { if (ssid) { if (!networksMap[ssid]) { - networksMap[ssid] = { ssid: ssid, security: security, signal: signal, connected: inUse }; + networksMap[ssid] = { ssid: ssid, security: security, signal: signal, connected: inUse, existing: ssid in scanProcess.existingNetwork }; } else { const existingNet = networksMap[ssid]; if (inUse) { @@ -71,6 +106,9 @@ Item { } } wifiLogic.networks = Object.values(networksMap); + console.log(JSON.stringify(wifiLogic.networks)); + refreshIndicator.running = false; + refreshIndicator.visible = false; } } } @@ -91,17 +129,15 @@ Item { property string connectSecurity: "" property var pendingConnect: null property string detectedInterface: "" + property string actionPanelSsid: "" - function profileNameForSsid(ssid) { - return "quickshell-" + ssid.replace(/[^a-zA-Z0-9]/g, "_"); - } function disconnectNetwork(ssid) { - var profileName = wifiLogic.profileNameForSsid(ssid); + const profileName = ssid; disconnectProfileProcess.connectionName = profileName; disconnectProfileProcess.running = true; } function refreshNetworks() { - scanProcess.running = true; + existingNetwork.running = true; } function showAt() { wifiPanelModal.visible = true; @@ -109,15 +145,37 @@ Item { } function connectNetwork(ssid, security) { wifiLogic.pendingConnect = {ssid: ssid, security: security, password: ""}; - listConnectionsProcess.running = true; + wifiLogic.doConnect(); } function submitPassword() { wifiLogic.pendingConnect = {ssid: wifiLogic.passwordPromptSsid, security: wifiLogic.connectSecurity, password: wifiLogic.passwordInput}; - listConnectionsProcess.running = true; + wifiLogic.doConnect(); } function doConnect() { const params = wifiLogic.pendingConnect; + if (!params) return; + wifiLogic.connectingSsid = params.ssid; + + // Find the target network in our networks data + let targetNetwork = null; + for (let i = 0; i < wifiLogic.networks.length; ++i) { + if (wifiLogic.networks[i].ssid === params.ssid) { + targetNetwork = wifiLogic.networks[i]; + break; + } + } + + // Check if profile already exists using existing field + if (targetNetwork && targetNetwork.existing) { + // Profile exists, just bring it up (no password prompt) + upConnectionProcess.profileName = params.ssid; + upConnectionProcess.running = true; + wifiLogic.pendingConnect = null; + return; + } + + // No existing profile, proceed with normal connection flow if (params.security && params.security !== "--") { getInterfaceProcess.running = true; return; @@ -146,50 +204,7 @@ Item { } } - Process { - id: listConnectionsProcess - running: false - command: ["nmcli", "-t", "-f", "NAME", "connection", "show"] - stdout: StdioCollector { - onStreamFinished: { - var params = wifiLogic.pendingConnect; - var lines = text.split("\n"); - var expectedProfile = wifiLogic.profileNameForSsid(params.ssid); - var foundProfile = null; - for (var i = 0; i < lines.length; ++i) { - if (lines[i] === expectedProfile) { - foundProfile = lines[i]; - break; - } - } - if (foundProfile) { - // Profile exists, just bring it up (no password prompt) - upConnectionProcess.profileName = foundProfile; - upConnectionProcess.running = true; - } else { - // No profile: check if secured - if (wifiLogic.isSecured(params.security)) { - if (params.password && params.password.length > 0) { - // Password provided, proceed to connect - wifiLogic.doConnect(); - } else { - // No password yet, prompt for it - wifiLogic.passwordPromptSsid = params.ssid; - wifiLogic.passwordInput = ""; - wifiLogic.showPasswordPrompt = true; - wifiLogic.connectStatus = ""; - wifiLogic.connectStatusSsid = ""; - wifiLogic.connectError = ""; - wifiLogic.connectSecurity = params.security; - } - } else { - // Open, connect directly - wifiLogic.doConnect(); - } - } - } - } - } + // Handles connecting to a Wi-Fi network, with or without password Process { @@ -256,7 +271,7 @@ Item { addConnectionProcess.ifname = wifiLogic.detectedInterface; addConnectionProcess.ssid = params.ssid; addConnectionProcess.password = params.password; - addConnectionProcess.profileName = wifiLogic.profileNameForSsid(params.ssid); + addConnectionProcess.profileName = params.ssid; addConnectionProcess.security = params.security; addConnectionProcess.running = true; } else { @@ -413,8 +428,8 @@ Item { Layout.preferredWidth: 24 Layout.preferredHeight: 24 Layout.alignment: Qt.AlignVCenter - visible: scanProcess.running - running: scanProcess.running + visible: false + running: false color: Theme.accentPrimary // Assuming Spinner supports color property size: 22 // Based on the existing Spinner usage } @@ -490,8 +505,13 @@ Item { model: wifiLogic.networks delegate: Item { id: networkEntry + + required property var modelData + property var signalIcon: wifiPanel.signalIcon + width: parent.width - height: modelData.ssid === wifiLogic.passwordPromptSsid && wifiLogic.showPasswordPrompt ? 102 : 42 + height: (modelData.ssid === wifiLogic.passwordPromptSsid && wifiLogic.showPasswordPrompt ? 102 : 42) + + (modelData.ssid === wifiLogic.actionPanelSsid ? 60 : 0) ColumnLayout { anchors.fill: parent spacing: 0 @@ -596,10 +616,11 @@ Item { anchors.fill: parent hoverEnabled: true onClicked: { - if (modelData.connected) { - wifiLogic.disconnectNetwork(modelData.ssid); + // Toggle the action panel for this network + if (wifiLogic.actionPanelSsid === modelData.ssid) { + wifiLogic.actionPanelSsid = ""; // Close if already open } else { - wifiLogic.connectNetwork(modelData.ssid, modelData.security); + wifiLogic.actionPanelSsid = modelData.ssid; // Open for this network } } } @@ -610,8 +631,9 @@ Item { Layout.preferredHeight: 60 radius: 8 color: "transparent" - anchors.leftMargin: 32 - anchors.rightMargin: 32 + Layout.alignment: Qt.AlignLeft + Layout.leftMargin: 32 + Layout.rightMargin: 32 z: 2 RowLayout { anchors.fill: parent @@ -651,8 +673,8 @@ Item { } } Rectangle { - width: 80 - height: 36 + Layout.preferredWidth: 80 + Layout.preferredHeight: 36 radius: 18 color: Theme.accentPrimary border.color: Theme.accentPrimary @@ -677,6 +699,102 @@ Item { } } } + // Action panel for network connection controls + Rectangle { + visible: modelData.ssid === wifiLogic.actionPanelSsid + Layout.fillWidth: true + Layout.preferredHeight: 60 + radius: 8 + color: "transparent" + Layout.alignment: Qt.AlignLeft + Layout.leftMargin: 32 + Layout.rightMargin: 32 + z: 2 + RowLayout { + anchors.fill: parent + anchors.margins: 12 + spacing: 10 + // Password field for new secured networks + Item { + Layout.fillWidth: true + Layout.preferredHeight: 36 + visible: wifiLogic.isSecured(modelData.security) && !modelData.connected && !modelData.existing + Rectangle { + anchors.fill: parent + radius: 8 + color: "transparent" + border.color: actionPanelPasswordField.activeFocus ? Theme.accentPrimary : Theme.outline + border.width: 1 + TextInput { + id: actionPanelPasswordField + anchors.fill: parent + anchors.margins: 12 + font.pixelSize: 13 + color: Theme.textPrimary + verticalAlignment: TextInput.AlignVCenter + clip: true + selectByMouse: true + activeFocusOnTab: true + inputMethodHints: Qt.ImhNone + echoMode: TextInput.Password + onAccepted: { + // Connect with the entered password + wifiLogic.pendingConnect = {ssid: modelData.ssid, security: modelData.security, password: text}; + wifiLogic.doConnect(); + + wifiLogic.actionPanelSsid = ""; // Close the panel + } + } + } + } + // Connect/Disconnect button + Rectangle { + Layout.preferredWidth: 80 + Layout.preferredHeight: 36 + radius: 18 + color: modelData.connected ? Theme.error : Theme.accentPrimary + border.color: modelData.connected ? Theme.error : Theme.accentPrimary + border.width: 0 + opacity: 1.0 + Behavior on color { ColorAnimation { duration: 100 } } + MouseArea { + anchors.fill: parent + onClicked: { + if (modelData.connected) { + // Disconnect from network + wifiLogic.disconnectNetwork(modelData.ssid); + } else { + // For secured networks, check if we need password + if (wifiLogic.isSecured(modelData.security) && !modelData.existing) { + // If password field is visible and has content, use it + if (actionPanelPasswordField.text.length > 0) { + wifiLogic.pendingConnect = {ssid: modelData.ssid, security: modelData.security, password: actionPanelPasswordField.text}; + wifiLogic.doConnect(); + } + // For new networks without password entered, we might want to show an error or handle differently + // For now, we'll just close the panel + } else { + // Connect to open network + wifiLogic.connectNetwork(modelData.ssid, modelData.security); + } + } + wifiLogic.actionPanelSsid = ""; // Close the panel + } + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + onEntered: parent.color = modelData.connected ? Qt.darker(Theme.error, 1.1) : Qt.darker(Theme.accentPrimary, 1.1) + onExited: parent.color = modelData.connected ? Theme.error : Theme.accentPrimary + } + Text { + anchors.centerIn: parent + text: modelData.connected ? "wifi_off" : "check" + font.family: "Material Symbols Outlined" + font.pixelSize: 20 + color: Theme.backgroundPrimary + } + } + } + } } } } From 9347fd006dbd0dabdd18c73c799d2f55d2c4e06b Mon Sep 17 00:00:00 2001 From: JPratama7 Date: Sun, 3 Aug 2025 19:43:52 +0700 Subject: [PATCH 06/10] refactor: remove debug logging and reset network state after wifi scan --- Widgets/Sidebar/Panel/WifiPanel.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Widgets/Sidebar/Panel/WifiPanel.qml b/Widgets/Sidebar/Panel/WifiPanel.qml index 6a2083d..58956ab 100644 --- a/Widgets/Sidebar/Panel/WifiPanel.qml +++ b/Widgets/Sidebar/Panel/WifiPanel.qml @@ -106,7 +106,7 @@ Item { } } wifiLogic.networks = Object.values(networksMap); - console.log(JSON.stringify(wifiLogic.networks)); + scanProcess.existingNetwork = null; refreshIndicator.running = false; refreshIndicator.visible = false; } From 3cf3474957ada153297b7a55f8d43fcab5c39830 Mon Sep 17 00:00:00 2001 From: JPratama7 Date: Sun, 3 Aug 2025 20:56:16 +0700 Subject: [PATCH 07/10] style: format code with consistent indentation and object literals --- Widgets/Sidebar/Panel/WifiPanel.qml | 115 +++++++++++++++++++--------- 1 file changed, 78 insertions(+), 37 deletions(-) diff --git a/Widgets/Sidebar/Panel/WifiPanel.qml b/Widgets/Sidebar/Panel/WifiPanel.qml index 58956ab..6002551 100644 --- a/Widgets/Sidebar/Panel/WifiPanel.qml +++ b/Widgets/Sidebar/Panel/WifiPanel.qml @@ -11,7 +11,7 @@ import qs.Helpers Item { property alias panel: wifiPanelModal - + function showAt() { wifiPanelModal.visible = true; wifiLogic.refreshNetworks(); @@ -22,10 +22,14 @@ Item { } function signalIcon(signal) { - if (signal >= 80) return "network_wifi"; - if (signal >= 60) return "network_wifi_3_bar"; - if (signal >= 40) return "network_wifi_2_bar"; - if (signal >= 20) return "network_wifi_1_bar"; + if (signal >= 80) + return "network_wifi"; + if (signal >= 60) + return "network_wifi_3_bar"; + if (signal >= 40) + return "network_wifi_2_bar"; + if (signal >= 20) + return "network_wifi_1_bar"; return "wifi_0_bar"; } @@ -43,7 +47,8 @@ Item { for (let i = 0; i < lines.length; ++i) { const line = lines[i].trim(); - if (!line) continue; + if (!line) + continue; const parts = line.split(":"); if (parts.length < 2) { @@ -55,7 +60,10 @@ Item { const type = parts[1]; if (ssid) { - networksMap[ssid] = { ssid: ssid, type: type }; + networksMap[ssid] = { + ssid: ssid, + type: type + }; } } scanProcess.existingNetwork = networksMap; @@ -70,7 +78,7 @@ Item { command: ["nmcli", "-t", "-f", "SSID,SECURITY,SIGNAL,IN-USE", "device", "wifi", "list"] property var existingNetwork - + stdout: StdioCollector { onStreamFinished: { const lines = text.split("\n"); @@ -78,7 +86,8 @@ Item { for (let i = 0; i < lines.length; ++i) { const line = lines[i].trim(); - if (!line) continue; + if (!line) + continue; const parts = line.split(":"); if (parts.length < 4) { @@ -92,7 +101,13 @@ Item { if (ssid) { if (!networksMap[ssid]) { - networksMap[ssid] = { ssid: ssid, security: security, signal: signal, connected: inUse, existing: ssid in scanProcess.existingNetwork }; + networksMap[ssid] = { + ssid: ssid, + security: security, + signal: signal, + connected: inUse, + existing: ssid in scanProcess.existingNetwork + }; } else { const existingNet = networksMap[ssid]; if (inUse) { @@ -106,7 +121,7 @@ Item { } } wifiLogic.networks = Object.values(networksMap); - scanProcess.existingNetwork = null; + scanProcess.existingNetwork = {}; refreshIndicator.running = false; refreshIndicator.visible = false; } @@ -144,19 +159,28 @@ Item { wifiLogic.refreshNetworks(); } function connectNetwork(ssid, security) { - wifiLogic.pendingConnect = {ssid: ssid, security: security, password: ""}; + wifiLogic.pendingConnect = { + ssid: ssid, + security: security, + password: "" + }; wifiLogic.doConnect(); } function submitPassword() { - wifiLogic.pendingConnect = {ssid: wifiLogic.passwordPromptSsid, security: wifiLogic.connectSecurity, password: wifiLogic.passwordInput}; + wifiLogic.pendingConnect = { + ssid: wifiLogic.passwordPromptSsid, + security: wifiLogic.connectSecurity, + password: wifiLogic.passwordInput + }; wifiLogic.doConnect(); } function doConnect() { const params = wifiLogic.pendingConnect; - if (!params) return; - + if (!params) + return; + wifiLogic.connectingSsid = params.ssid; - + // Find the target network in our networks data let targetNetwork = null; for (let i = 0; i < wifiLogic.networks.length; ++i) { @@ -165,7 +189,7 @@ Item { break; } } - + // Check if profile already exists using existing field if (targetNetwork && targetNetwork.existing) { // Profile exists, just bring it up (no password prompt) @@ -174,7 +198,7 @@ Item { wifiLogic.pendingConnect = null; return; } - + // No existing profile, proceed with normal connection flow if (params.security && params.security !== "--") { getInterfaceProcess.running = true; @@ -204,8 +228,6 @@ Item { } } - - // Handles connecting to a Wi-Fi network, with or without password Process { id: connectProcess @@ -221,9 +243,9 @@ Item { } command: { if (password) { - return ["nmcli", "device", "wifi", "connect", ssid, "password", password] + return ["nmcli", "device", "wifi", "connect", ssid, "password", password]; } else { - return ["nmcli", "device", "wifi", "connect", ssid] + return ["nmcli", "device", "wifi", "connect", ssid]; } } stdout: StdioCollector { @@ -354,7 +376,8 @@ Item { // Wifi button (no background card) Rectangle { id: wifiButton - width: 36; height: 36 + width: 36 + height: 36 radius: 18 border.color: Theme.accentPrimary border.width: 1 @@ -365,9 +388,7 @@ Item { text: "wifi" font.family: "Material Symbols Outlined" font.pixelSize: 22 - color: wifiButtonArea.containsMouse - ? Theme.backgroundPrimary - : Theme.accentPrimary + color: wifiButtonArea.containsMouse ? Theme.backgroundPrimary : Theme.accentPrimary verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter } @@ -393,7 +414,7 @@ Item { margins.top: 0 WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand Component.onCompleted: { - wifiLogic.refreshNetworks() + wifiLogic.refreshNetworks(); } Rectangle { anchors.fill: parent @@ -422,7 +443,9 @@ Item { color: Theme.textPrimary Layout.fillWidth: true } - Item { Layout.fillWidth: true } + Item { + Layout.fillWidth: true + } Spinner { id: refreshIndicator Layout.preferredWidth: 24 @@ -440,7 +463,9 @@ Item { } Rectangle { - implicitWidth: 36; implicitHeight: 36; radius: 18 + implicitWidth: 36 + implicitHeight: 36 + radius: 18 color: closeButtonArea.containsMouse ? Theme.accentPrimary : "transparent" border.color: Theme.accentPrimary border.width: 1 @@ -510,8 +535,7 @@ Item { property var signalIcon: wifiPanel.signalIcon width: parent.width - height: (modelData.ssid === wifiLogic.passwordPromptSsid && wifiLogic.showPasswordPrompt ? 102 : 42) + - (modelData.ssid === wifiLogic.actionPanelSsid ? 60 : 0) + height: (modelData.ssid === wifiLogic.passwordPromptSsid && wifiLogic.showPasswordPrompt ? 102 : 42) + (modelData.ssid === wifiLogic.actionPanelSsid ? 60 : 0) ColumnLayout { anchors.fill: parent spacing: 0 @@ -548,7 +572,8 @@ Item { Layout.alignment: Qt.AlignVCenter } Item { - width: 22; height: 22 + width: 22 + height: 22 visible: wifiLogic.connectStatusSsid === modelData.ssid && wifiLogic.connectStatus !== "" RowLayout { anchors.fill: parent @@ -680,7 +705,11 @@ Item { border.color: Theme.accentPrimary border.width: 0 opacity: 1.0 - Behavior on color { ColorAnimation { duration: 100 } } + Behavior on color { + ColorAnimation { + duration: 100 + } + } MouseArea { anchors.fill: parent onClicked: wifiLogic.submitPassword() @@ -739,9 +768,13 @@ Item { echoMode: TextInput.Password onAccepted: { // Connect with the entered password - wifiLogic.pendingConnect = {ssid: modelData.ssid, security: modelData.security, password: text}; + wifiLogic.pendingConnect = { + ssid: modelData.ssid, + security: modelData.security, + password: text + }; wifiLogic.doConnect(); - + wifiLogic.actionPanelSsid = ""; // Close the panel } } @@ -756,7 +789,11 @@ Item { border.color: modelData.connected ? Theme.error : Theme.accentPrimary border.width: 0 opacity: 1.0 - Behavior on color { ColorAnimation { duration: 100 } } + Behavior on color { + ColorAnimation { + duration: 100 + } + } MouseArea { anchors.fill: parent onClicked: { @@ -768,7 +805,11 @@ Item { if (wifiLogic.isSecured(modelData.security) && !modelData.existing) { // If password field is visible and has content, use it if (actionPanelPasswordField.text.length > 0) { - wifiLogic.pendingConnect = {ssid: modelData.ssid, security: modelData.security, password: actionPanelPasswordField.text}; + wifiLogic.pendingConnect = { + ssid: modelData.ssid, + security: modelData.security, + password: actionPanelPasswordField.text + }; wifiLogic.doConnect(); } // For new networks without password entered, we might want to show an error or handle differently From 90e3deb589a9cad3fd4616f267e545c6d995fd81 Mon Sep 17 00:00:00 2001 From: JPratama7 Date: Sun, 3 Aug 2025 21:27:49 +0700 Subject: [PATCH 08/10] refactor: optimize wifi network lookup by using map instead of array iteration, change cli command for connect --- Widgets/Sidebar/Panel/WifiPanel.qml | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/Widgets/Sidebar/Panel/WifiPanel.qml b/Widgets/Sidebar/Panel/WifiPanel.qml index 6002551..4fc5b11 100644 --- a/Widgets/Sidebar/Panel/WifiPanel.qml +++ b/Widgets/Sidebar/Panel/WifiPanel.qml @@ -120,7 +120,7 @@ Item { } } } - wifiLogic.networks = Object.values(networksMap); + wifiLogic.networks = networksMap; scanProcess.existingNetwork = {}; refreshIndicator.running = false; refreshIndicator.visible = false; @@ -182,13 +182,7 @@ Item { wifiLogic.connectingSsid = params.ssid; // Find the target network in our networks data - let targetNetwork = null; - for (let i = 0; i < wifiLogic.networks.length; ++i) { - if (wifiLogic.networks[i].ssid === params.ssid) { - targetNetwork = wifiLogic.networks[i]; - break; - } - } + const targetNetwork = wifiLogic.networks[params.ssid]; // Check if profile already exists using existing field if (targetNetwork && targetNetwork.existing) { @@ -220,7 +214,7 @@ Item { id: disconnectProfileProcess property string connectionName: "" running: false - command: ["nmcli", "connection", "down", "id", connectionName] + command: ["nmcli", "connection", "down", connectionName] onRunningChanged: { if (!running) { wifiLogic.refreshNetworks(); @@ -527,7 +521,7 @@ Item { anchors.fill: parent spacing: 4 boundsBehavior: Flickable.StopAtBounds - model: wifiLogic.networks + model: Object.values(wifiLogic.networks) delegate: Item { id: networkEntry From a2b4f4b6b62f636743300a13e914bacf9c79c2d7 Mon Sep 17 00:00:00 2001 From: JPratama7 Date: Sun, 3 Aug 2025 22:09:08 +0700 Subject: [PATCH 09/10] feat: remove quickshell prefix for profilename --- Widgets/Sidebar/Panel/WifiPanel.qml | 73 +++++++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 4 deletions(-) diff --git a/Widgets/Sidebar/Panel/WifiPanel.qml b/Widgets/Sidebar/Panel/WifiPanel.qml index 4fc5b11..b02737b 100644 --- a/Widgets/Sidebar/Panel/WifiPanel.qml +++ b/Widgets/Sidebar/Panel/WifiPanel.qml @@ -56,7 +56,7 @@ Item { continue; } - const ssid = parts[0]; + const ssid = wifiLogic.replaceQuickshell(parts[0]); const type = parts[1]; if (ssid) { @@ -120,6 +120,8 @@ Item { } } } + + wifiLogic.networks = networksMap; scanProcess.existingNetwork = {}; refreshIndicator.running = false; @@ -130,7 +132,7 @@ Item { QtObject { id: wifiLogic - property var networks: [] + property var networks: {} property var anchorItem: null property real anchorX property real anchorY @@ -146,6 +148,28 @@ Item { property string detectedInterface: "" property string actionPanelSsid: "" + function replaceQuickshell(ssid: string): string { + const newName = ssid.replace("quickshell-", ""); + + if (!ssid.startsWith("quickshell-")){ + return newName; + } + + if (newName in wifiLogic.networks){ + console.log(`Quickshell ${newName} already exists, deleting old profile`) + deleteProfileProcess.connName = ssid; + deleteProfileProcess.running = true; + } + + + console.log(`Changing from ${ssid} to ${newName}`) + renameConnectionProcess.oldName = ssid; + renameConnectionProcess.newName = newName; + renameConnectionProcess.running = true; + + return newName; + } + function disconnectNetwork(ssid) { const profileName = ssid; disconnectProfileProcess.connectionName = profileName; @@ -222,6 +246,47 @@ Item { } } + // Process to rename a connection + Process { + id: renameConnectionProcess + running: false + property string oldName: "" + property string newName: "" + command: ["nmcli", "connection", "modify", oldName, "connection.id", newName] + + stdout: StdioCollector { + onStreamFinished: { + console.log("Renamed connection '" + renameConnectionProcess.oldName + "' to '" + renameConnectionProcess.newName + "'"); + } + } + stderr: StdioCollector { + onStreamFinished: { + console.error("Error renaming connection '" + renameConnectionProcess.oldName + "':", text); + } + } + } + + + // Process to rename a connection + Process { + id: deleteProfileProcess + running: false + property string connName: "" + command: ["nmcli", "connection", "delete", `'${connName}'`] + + stdout: StdioCollector { + onStreamFinished: { + console.log("Deleted connection '" + deleteProfileProcess.connName + "'"); + } + } + stderr: StdioCollector { + onStreamFinished: { + console.error("Error deleting connection '" + deleteProfileProcess.connName + "':", text); + } + } + } + + // Handles connecting to a Wi-Fi network, with or without password Process { id: connectProcess @@ -237,9 +302,9 @@ Item { } command: { if (password) { - return ["nmcli", "device", "wifi", "connect", ssid, "password", password]; + return ["nmcli", "device", "wifi", "connect", `'${ssid}'`, "password", password]; } else { - return ["nmcli", "device", "wifi", "connect", ssid]; + return ["nmcli", "device", "wifi", "connect", `'${ssid}'`]; } } stdout: StdioCollector { From 9fce72f05497698bca6b4c85fe951ed7c3aa9302 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Sun, 3 Aug 2025 18:08:48 +0200 Subject: [PATCH 10/10] Edit WifiPanel.qml --- Widgets/Sidebar/Panel/WifiPanel.qml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Widgets/Sidebar/Panel/WifiPanel.qml b/Widgets/Sidebar/Panel/WifiPanel.qml index b02737b..fc88e0c 100644 --- a/Widgets/Sidebar/Panel/WifiPanel.qml +++ b/Widgets/Sidebar/Panel/WifiPanel.qml @@ -151,17 +151,16 @@ Item { function replaceQuickshell(ssid: string): string { const newName = ssid.replace("quickshell-", ""); - if (!ssid.startsWith("quickshell-")){ + if (!ssid.startsWith("quickshell-")) { return newName; } - if (newName in wifiLogic.networks){ + if (wifiLogic.networks && newName in wifiLogic.networks) { console.log(`Quickshell ${newName} already exists, deleting old profile`) deleteProfileProcess.connName = ssid; deleteProfileProcess.running = true; } - console.log(`Changing from ${ssid} to ${newName}`) renameConnectionProcess.oldName = ssid; renameConnectionProcess.newName = newName; @@ -256,17 +255,22 @@ Item { stdout: StdioCollector { onStreamFinished: { - console.log("Renamed connection '" + renameConnectionProcess.oldName + "' to '" + renameConnectionProcess.newName + "'"); + console.log("Successfully renamed connection '" + + renameConnectionProcess.oldName + "' to '" + + renameConnectionProcess.newName + "'"); } } stderr: StdioCollector { onStreamFinished: { - console.error("Error renaming connection '" + renameConnectionProcess.oldName + "':", text); + if (text.trim() !== "" && !text.toLowerCase().includes("warning")) { + console.error("Error renaming connection:", text); + } } } } + // Process to rename a connection Process { id: deleteProfileProcess