feat: improve wifi connection handling with existing network detection and action panel

This commit is contained in:
JPratama7 2025-08-03 19:39:55 +07:00
parent fbea11ee9c
commit 5ad7d731c0
No known key found for this signature in database
GPG key ID: CD3EB7D0490C5F4B

View file

@ -17,6 +17,10 @@ Item {
wifiLogic.refreshNetworks(); wifiLogic.refreshNetworks();
} }
Component.onCompleted: {
existingNetwork.running = true;
}
function signalIcon(signal) { function signalIcon(signal) {
if (signal >= 80) return "network_wifi"; if (signal >= 80) return "network_wifi";
if (signal >= 60) return "network_wifi_3_bar"; if (signal >= 60) return "network_wifi_3_bar";
@ -25,16 +29,48 @@ Item {
return "wifi_0_bar"; 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 { Process {
id: scanProcess id: scanProcess
running: false running: false
command: ["nmcli", "-t", "-f", "SSID,SECURITY,SIGNAL,IN-USE", "device", "wifi", "list"] command: ["nmcli", "-t", "-f", "SSID,SECURITY,SIGNAL,IN-USE", "device", "wifi", "list"]
onRunningChanged: {
console.log("scanProcess.running changed: " + running); property var existingNetwork
// if (!running) {
// console.log("scanProcess finished.");
// }
}
stdout: StdioCollector { stdout: StdioCollector {
onStreamFinished: { onStreamFinished: {
const lines = text.split("\n"); const lines = text.split("\n");
@ -49,7 +85,6 @@ Item {
console.warn("Malformed nmcli output line:", line); console.warn("Malformed nmcli output line:", line);
continue; continue;
} }
const ssid = parts[0]; const ssid = parts[0];
const security = parts[1]; const security = parts[1];
const signal = parseInt(parts[2]); const signal = parseInt(parts[2]);
@ -57,7 +92,7 @@ Item {
if (ssid) { if (ssid) {
if (!networksMap[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 { } else {
const existingNet = networksMap[ssid]; const existingNet = networksMap[ssid];
if (inUse) { if (inUse) {
@ -71,6 +106,9 @@ Item {
} }
} }
wifiLogic.networks = Object.values(networksMap); 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 string connectSecurity: ""
property var pendingConnect: null property var pendingConnect: null
property string detectedInterface: "" property string detectedInterface: ""
property string actionPanelSsid: ""
function profileNameForSsid(ssid) {
return "quickshell-" + ssid.replace(/[^a-zA-Z0-9]/g, "_");
}
function disconnectNetwork(ssid) { function disconnectNetwork(ssid) {
var profileName = wifiLogic.profileNameForSsid(ssid); const profileName = ssid;
disconnectProfileProcess.connectionName = profileName; disconnectProfileProcess.connectionName = profileName;
disconnectProfileProcess.running = true; disconnectProfileProcess.running = true;
} }
function refreshNetworks() { function refreshNetworks() {
scanProcess.running = true; existingNetwork.running = true;
} }
function showAt() { function showAt() {
wifiPanelModal.visible = true; wifiPanelModal.visible = true;
@ -109,15 +145,37 @@ Item {
} }
function connectNetwork(ssid, security) { function connectNetwork(ssid, security) {
wifiLogic.pendingConnect = {ssid: ssid, security: security, password: ""}; wifiLogic.pendingConnect = {ssid: ssid, security: security, password: ""};
listConnectionsProcess.running = true; wifiLogic.doConnect();
} }
function submitPassword() { function submitPassword() {
wifiLogic.pendingConnect = {ssid: wifiLogic.passwordPromptSsid, security: wifiLogic.connectSecurity, password: wifiLogic.passwordInput}; wifiLogic.pendingConnect = {ssid: wifiLogic.passwordPromptSsid, security: wifiLogic.connectSecurity, password: wifiLogic.passwordInput};
listConnectionsProcess.running = true; wifiLogic.doConnect();
} }
function doConnect() { function doConnect() {
const params = wifiLogic.pendingConnect; const params = wifiLogic.pendingConnect;
if (!params) return;
wifiLogic.connectingSsid = params.ssid; 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 !== "--") { if (params.security && params.security !== "--") {
getInterfaceProcess.running = true; getInterfaceProcess.running = true;
return; 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 // Handles connecting to a Wi-Fi network, with or without password
Process { Process {
@ -256,7 +271,7 @@ Item {
addConnectionProcess.ifname = wifiLogic.detectedInterface; addConnectionProcess.ifname = wifiLogic.detectedInterface;
addConnectionProcess.ssid = params.ssid; addConnectionProcess.ssid = params.ssid;
addConnectionProcess.password = params.password; addConnectionProcess.password = params.password;
addConnectionProcess.profileName = wifiLogic.profileNameForSsid(params.ssid); addConnectionProcess.profileName = params.ssid;
addConnectionProcess.security = params.security; addConnectionProcess.security = params.security;
addConnectionProcess.running = true; addConnectionProcess.running = true;
} else { } else {
@ -413,8 +428,8 @@ Item {
Layout.preferredWidth: 24 Layout.preferredWidth: 24
Layout.preferredHeight: 24 Layout.preferredHeight: 24
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
visible: scanProcess.running visible: false
running: scanProcess.running running: false
color: Theme.accentPrimary // Assuming Spinner supports color property color: Theme.accentPrimary // Assuming Spinner supports color property
size: 22 // Based on the existing Spinner usage size: 22 // Based on the existing Spinner usage
} }
@ -490,8 +505,13 @@ Item {
model: wifiLogic.networks model: wifiLogic.networks
delegate: Item { delegate: Item {
id: networkEntry id: networkEntry
required property var modelData
property var signalIcon: wifiPanel.signalIcon
width: parent.width 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 { ColumnLayout {
anchors.fill: parent anchors.fill: parent
spacing: 0 spacing: 0
@ -596,10 +616,11 @@ Item {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
onClicked: { onClicked: {
if (modelData.connected) { // Toggle the action panel for this network
wifiLogic.disconnectNetwork(modelData.ssid); if (wifiLogic.actionPanelSsid === modelData.ssid) {
wifiLogic.actionPanelSsid = ""; // Close if already open
} else { } else {
wifiLogic.connectNetwork(modelData.ssid, modelData.security); wifiLogic.actionPanelSsid = modelData.ssid; // Open for this network
} }
} }
} }
@ -610,8 +631,9 @@ Item {
Layout.preferredHeight: 60 Layout.preferredHeight: 60
radius: 8 radius: 8
color: "transparent" color: "transparent"
anchors.leftMargin: 32 Layout.alignment: Qt.AlignLeft
anchors.rightMargin: 32 Layout.leftMargin: 32
Layout.rightMargin: 32
z: 2 z: 2
RowLayout { RowLayout {
anchors.fill: parent anchors.fill: parent
@ -651,8 +673,8 @@ Item {
} }
} }
Rectangle { Rectangle {
width: 80 Layout.preferredWidth: 80
height: 36 Layout.preferredHeight: 36
radius: 18 radius: 18
color: Theme.accentPrimary color: Theme.accentPrimary
border.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
}
}
}
}
} }
} }
} }