1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-07 05:55:37 -05:00

networking: improve wifi experience and bugs

This commit is contained in:
bbedward
2025-07-22 11:50:07 -04:00
parent a4da6921bd
commit 02bd9bbc72
7 changed files with 1166 additions and 395 deletions

View File

@@ -28,14 +28,25 @@ Item {
return []; return [];
} }
var networks = [...WifiService.wifiNetworks]; // Explicitly reference both arrays to ensure reactivity
var allNetworks = WifiService.wifiNetworks;
var savedNetworks = WifiService.savedWifiNetworks;
var currentSSID = WifiService.currentWifiSSID;
var signalStrength = WifiService.wifiSignalStrength;
var refreshTrigger = forceRefresh; // Force recalculation
// Update connected status and signal strength based on current WiFi SSID var networks = [...allNetworks];
// Update connected status, saved status and signal strength based on current state
networks.forEach(function(network) { networks.forEach(function(network) {
network.connected = (network.ssid === WifiService.currentWifiSSID); network.connected = (network.ssid === currentSSID);
// Update saved status based on savedWifiNetworks
network.saved = savedNetworks.some(function(saved) {
return saved.ssid === network.ssid;
});
// Use current connection's signal strength for connected network // Use current connection's signal strength for connected network
if (network.connected && WifiService.wifiSignalStrength) { if (network.connected && signalStrength) {
network.signalStrength = WifiService.wifiSignalStrength; network.signalStrength = signalStrength;
} }
}); });
@@ -51,6 +62,16 @@ Item {
return networks; return networks;
} }
// Force refresh of sortedWifiNetworks when networks are updated
property int forceRefresh: 0
Connections {
target: WifiService
function onNetworksUpdated() {
forceRefresh++;
}
}
// Auto-enable WiFi auto-refresh when network tab is visible // Auto-enable WiFi auto-refresh when network tab is visible
Component.onCompleted: { Component.onCompleted: {
WifiService.autoRefreshEnabled = true; WifiService.autoRefreshEnabled = true;
@@ -166,6 +187,29 @@ Item {
} }
// Loading spinner for preference changes
DankIcon {
id: wifiLoadingSpinner
name: "refresh"
size: Theme.iconSize - 4
color: Theme.primary
anchors.right: wifiToggle.left
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
visible: NetworkService.changingPreference && NetworkService.targetPreference === "wifi"
z: 10
RotationAnimation {
target: wifiLoadingSpinner
property: "rotation"
running: wifiLoadingSpinner.visible
from: 0
to: 360
duration: 1000
loops: Animation.Infinite
}
}
// WiFi toggle switch // WiFi toggle switch
DankToggle { DankToggle {
id: wifiToggle id: wifiToggle
@@ -219,111 +263,6 @@ Item {
} }
} }
// Connection status indicator
Rectangle {
width: parent.width
height: 32
radius: Theme.cornerRadius
color: {
if (WifiService.connectionStatus === "connecting")
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12);
else if (WifiService.connectionStatus === "failed")
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12);
else if (WifiService.connectionStatus === "connected")
return Qt.rgba(Theme.success.r, Theme.success.g, Theme.success.b, 0.12);
return "transparent";
}
border.color: {
if (WifiService.connectionStatus === "connecting")
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.3);
else if (WifiService.connectionStatus === "failed")
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.3);
else if (WifiService.connectionStatus === "connected")
return Qt.rgba(Theme.success.r, Theme.success.g, Theme.success.b, 0.3);
return "transparent";
}
border.width: WifiService.connectionStatus !== "" ? 1 : 0
visible: WifiService.connectionStatus !== ""
Row {
anchors.centerIn: parent
spacing: Theme.spacingS
DankIcon {
id: connectionIcon
name: {
if (WifiService.connectionStatus === "connecting")
return "sync";
if (WifiService.connectionStatus === "failed")
return "error";
if (WifiService.connectionStatus === "connected")
return "check_circle";
return "";
}
size: Theme.iconSize - 6
color: {
if (WifiService.connectionStatus === "connecting")
return Theme.warning;
if (WifiService.connectionStatus === "failed")
return Theme.error;
if (WifiService.connectionStatus === "connected")
return Theme.success;
return Theme.surfaceText;
}
anchors.verticalCenter: parent.verticalCenter
rotation: WifiService.connectionStatus === "connecting" ? connectionIcon.rotation : 0
RotationAnimation {
target: connectionIcon
property: "rotation"
running: WifiService.connectionStatus === "connecting"
from: 0
to: 360
duration: 1000
loops: Animation.Infinite
}
Behavior on rotation {
RotationAnimation {
duration: 200
easing.type: Easing.OutQuad
}
}
}
Text {
text: {
if (WifiService.connectionStatus === "connecting")
return "Connecting to " + WifiService.connectingSSID;
if (WifiService.connectionStatus === "failed")
return "Failed to connect to " + WifiService.connectingSSID;
if (WifiService.connectionStatus === "connected")
return "Connected to " + WifiService.connectingSSID;
return "";
}
font.pixelSize: Theme.fontSizeSmall
color: {
if (WifiService.connectionStatus === "connecting")
return Theme.warning;
if (WifiService.connectionStatus === "failed")
return Theme.error;
if (WifiService.connectionStatus === "connected")
return Theme.success;
return Theme.surfaceText;
}
anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
} }
@@ -410,6 +349,29 @@ Item {
} }
} }
// Loading spinner for preference changes
DankIcon {
id: ethernetLoadingSpinner
name: "refresh"
size: Theme.iconSize - 4
color: Theme.primary
anchors.right: ethernetToggle.left
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
visible: NetworkService.changingPreference && NetworkService.targetPreference === "ethernet"
z: 10
RotationAnimation {
target: ethernetLoadingSpinner
property: "rotation"
running: ethernetLoadingSpinner.visible
from: 0
to: 360
duration: 1000
loops: Animation.Infinite
}
}
// Ethernet toggle switch (matching WiFi style) // Ethernet toggle switch (matching WiFi style)
DankToggle { DankToggle {
id: ethernetToggle id: ethernetToggle
@@ -604,6 +566,7 @@ Item {
Item { Item {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingXS anchors.margins: Theme.spacingXS
anchors.rightMargin: Theme.spacingM // Extra right margin for scrollbar
// Signal strength icon // Signal strength icon
DankIcon { DankIcon {
@@ -638,12 +601,22 @@ Item {
text: { text: {
if (modelData.connected) if (modelData.connected)
return "Connected"; return "Connected";
if (WifiService.connectionStatus === "connecting" && WifiService.connectingSSID === modelData.ssid)
return "Connecting...";
if (WifiService.connectionStatus === "invalid_password" && WifiService.connectingSSID === modelData.ssid)
return "Invalid password";
if (modelData.saved) if (modelData.saved)
return "Saved" + (modelData.secured ? " • Secured" : " • Open"); return "Saved" + (modelData.secured ? " • Secured" : " • Open");
return modelData.secured ? "Secured" : "Open"; return modelData.secured ? "Secured" : "Open";
} }
font.pixelSize: Theme.fontSizeSmall - 1 font.pixelSize: Theme.fontSizeSmall - 1
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: {
if (WifiService.connectionStatus === "connecting" && WifiService.connectingSSID === modelData.ssid)
return Theme.primary;
if (WifiService.connectionStatus === "invalid_password" && WifiService.connectingSSID === modelData.ssid)
return Theme.error;
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7);
}
elide: Text.ElideRight elide: Text.ElideRight
} }
} }
@@ -664,28 +637,37 @@ Item {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
// Forget button (for saved networks) // Context menu button
Rectangle { Rectangle {
id: wifiMenuButton
width: 24 width: 24
height: 24 height: 24
radius: 12 radius: 12
color: forgetArea2.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent" color: wifiMenuButtonArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent"
visible: modelData.saved || modelData.connected
DankIcon { DankIcon {
anchors.centerIn: parent name: "more_vert"
name: "delete"
size: Theme.iconSize - 8 size: Theme.iconSize - 8
color: forgetArea2.containsMouse ? Theme.error : Theme.surfaceText color: Theme.surfaceText
opacity: 0.6
anchors.centerIn: parent
} }
MouseArea { MouseArea {
id: forgetArea2 id: wifiMenuButtonArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
WifiService.forgetWifiNetwork(modelData.ssid); wifiContextMenuWindow.networkData = modelData;
let localPos = wifiMenuButtonArea.mapToItem(networkTab, wifiMenuButtonArea.width / 2, wifiMenuButtonArea.height);
wifiContextMenuWindow.show(localPos.x, localPos.y);
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
} }
} }
} }
@@ -695,6 +677,7 @@ Item {
MouseArea { MouseArea {
id: networkArea2 id: networkArea2
anchors.fill: parent anchors.fill: parent
anchors.rightMargin: 32 // Exclude menu button area
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
@@ -841,4 +824,279 @@ Item {
wifiMonitorTimer.stop(); wifiMonitorTimer.stop();
} }
} }
// WiFi Context Menu Window
Rectangle {
id: wifiContextMenuWindow
property var networkData: null
property bool menuVisible: false
function show(x, y) {
const menuWidth = 160;
const menuHeight = wifiMenuColumn.implicitHeight + Theme.spacingS * 2;
let finalX = x - menuWidth / 2;
let finalY = y;
finalX = Math.max(0, Math.min(finalX, networkTab.width - menuWidth));
finalY = Math.max(0, Math.min(finalY, networkTab.height - menuHeight));
wifiContextMenuWindow.x = finalX;
wifiContextMenuWindow.y = finalY;
wifiContextMenuWindow.visible = true;
wifiContextMenuWindow.menuVisible = true;
}
function hide() {
wifiContextMenuWindow.menuVisible = false;
Qt.callLater(() => {
wifiContextMenuWindow.visible = false;
});
}
visible: false
width: 160
height: wifiMenuColumn.implicitHeight + Theme.spacingS * 2
radius: Theme.cornerRadiusLarge
color: Theme.popupBackground()
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1
z: 1000
opacity: menuVisible ? 1 : 0
scale: menuVisible ? 1 : 0.85
// Drop shadow
Rectangle {
anchors.fill: parent
anchors.topMargin: 4
anchors.leftMargin: 2
anchors.rightMargin: -2
anchors.bottomMargin: -4
radius: parent.radius
color: Qt.rgba(0, 0, 0, 0.15)
z: parent.z - 1
}
Column {
id: wifiMenuColumn
anchors.fill: parent
anchors.margins: Theme.spacingS
spacing: 1
// Connect/Disconnect option
Rectangle {
width: parent.width
height: 32
radius: Theme.cornerRadiusSmall
color: connectWifiArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
DankIcon {
name: wifiContextMenuWindow.networkData && wifiContextMenuWindow.networkData.connected ? "wifi_off" : "wifi"
size: Theme.iconSize - 2
color: Theme.surfaceText
opacity: 0.7
anchors.verticalCenter: parent.verticalCenter
}
Text {
text: wifiContextMenuWindow.networkData && wifiContextMenuWindow.networkData.connected ? "Disconnect" : "Connect"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Normal
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: connectWifiArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (wifiContextMenuWindow.networkData) {
if (wifiContextMenuWindow.networkData.connected) {
// Disconnect from current network
WifiService.disconnectWifi();
} else {
// Connect to selected network
if (wifiContextMenuWindow.networkData.saved) {
WifiService.connectToWifi(wifiContextMenuWindow.networkData.ssid);
} else if (wifiContextMenuWindow.networkData.secured) {
// Show password dialog for secured networks
wifiPasswordDialog.wifiPasswordSSID = wifiContextMenuWindow.networkData.ssid;
wifiPasswordDialog.wifiPasswordInput = "";
wifiPasswordDialog.wifiPasswordDialogVisible = true;
} else {
WifiService.connectToWifi(wifiContextMenuWindow.networkData.ssid);
}
}
}
wifiContextMenuWindow.hide();
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
// Separator
Rectangle {
width: parent.width - Theme.spacingS * 2
height: 5
anchors.horizontalCenter: parent.horizontalCenter
color: "transparent"
Rectangle {
anchors.centerIn: parent
width: parent.width
height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
}
}
// Forget Network option (only for saved networks)
Rectangle {
width: parent.width
height: 32
radius: Theme.cornerRadiusSmall
color: forgetWifiArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent"
visible: wifiContextMenuWindow.networkData && (wifiContextMenuWindow.networkData.saved || wifiContextMenuWindow.networkData.connected)
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
DankIcon {
name: "delete"
size: Theme.iconSize - 2
color: forgetWifiArea.containsMouse ? Theme.error : Theme.surfaceText
opacity: 0.7
anchors.verticalCenter: parent.verticalCenter
}
Text {
text: "Forget Network"
font.pixelSize: Theme.fontSizeSmall
color: forgetWifiArea.containsMouse ? Theme.error : Theme.surfaceText
font.weight: Font.Normal
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: forgetWifiArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (wifiContextMenuWindow.networkData) {
WifiService.forgetWifiNetwork(wifiContextMenuWindow.networkData.ssid);
}
wifiContextMenuWindow.hide();
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
// Network Info option
Rectangle {
width: parent.width
height: 32
radius: Theme.cornerRadiusSmall
color: infoWifiArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
DankIcon {
name: "info"
size: Theme.iconSize - 2
color: Theme.surfaceText
opacity: 0.7
anchors.verticalCenter: parent.verticalCenter
}
Text {
text: "Network Info"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Normal
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: infoWifiArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (wifiContextMenuWindow.networkData) {
networkInfoDialog.showNetworkInfo(wifiContextMenuWindow.networkData.ssid, wifiContextMenuWindow.networkData);
}
wifiContextMenuWindow.hide();
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
}
Behavior on opacity {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
Behavior on scale {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
}
// Background MouseArea to close the context menu
MouseArea {
anchors.fill: parent
visible: wifiContextMenuWindow.visible
onClicked: {
wifiContextMenuWindow.hide();
}
MouseArea {
x: wifiContextMenuWindow.x
y: wifiContextMenuWindow.y
width: wifiContextMenuWindow.width
height: wifiContextMenuWindow.height
onClicked: {
// Prevent clicks on menu from closing it
}
}
}
} }

View File

@@ -0,0 +1,208 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Wayland
import Quickshell.Widgets
import Quickshell.Io
import qs.Common
import qs.Services
import qs.Widgets
PanelWindow {
id: root
property bool networkInfoDialogVisible: false
property string networkSSID: ""
property var networkData: null
property string networkDetails: ""
visible: networkInfoDialogVisible
WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: networkInfoDialogVisible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
color: "transparent"
function showNetworkInfo(ssid, data) {
networkSSID = ssid;
networkData = data;
networkInfoDialogVisible = true;
WifiService.fetchNetworkInfo(ssid);
}
function hideDialog() {
networkInfoDialogVisible = false;
networkSSID = "";
networkData = null;
networkDetails = "";
}
anchors {
top: true
left: true
right: true
bottom: true
}
Rectangle {
anchors.fill: parent
color: Qt.rgba(0, 0, 0, 0.5)
opacity: networkInfoDialogVisible ? 1 : 0
MouseArea {
anchors.fill: parent
onClicked: {
root.hideDialog();
}
}
Behavior on opacity {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.standardEasing
}
}
}
Rectangle {
width: Math.min(600, parent.width - Theme.spacingL * 2)
height: Math.min(500, parent.height - Theme.spacingL * 2)
anchors.centerIn: parent
color: Theme.surfaceContainer
radius: Theme.cornerRadiusLarge
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: 1
opacity: networkInfoDialogVisible ? 1 : 0
scale: networkInfoDialogVisible ? 1 : 0.9
Column {
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingL
// Header
Row {
width: parent.width
Column {
width: parent.width - 40
spacing: Theme.spacingXS
Text {
text: "Network Information"
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
}
Text {
text: "Details for \"" + networkSSID + "\""
font.pixelSize: Theme.fontSizeMedium
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
width: parent.width
elide: Text.ElideRight
}
}
DankActionButton {
iconName: "close"
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
hoverColor: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12)
onClicked: {
root.hideDialog();
}
}
}
// Network Details
ScrollView {
width: parent.width
height: parent.height - 140
clip: true
ScrollBar.vertical.policy: ScrollBar.AsNeeded
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
Flickable {
contentWidth: parent.width
contentHeight: detailsRect.height
Rectangle {
id: detailsRect
width: parent.width
height: Math.max(parent.parent.height, detailsText.contentHeight + Theme.spacingM * 2)
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: 1
Text {
id: detailsText
anchors.fill: parent
anchors.margins: Theme.spacingM
text: WifiService.networkInfoDetails.replace(/\\n/g, '\n') || "No information available"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
wrapMode: Text.WordWrap
lineHeight: 1.5
}
}
}
}
// Close Button
Item {
width: parent.width
height: 40
Rectangle {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
width: Math.max(70, closeText.contentWidth + Theme.spacingM * 2)
height: 36
radius: Theme.cornerRadius
color: closeArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
Text {
id: closeText
anchors.centerIn: parent
text: "Close"
font.pixelSize: Theme.fontSizeMedium
color: Theme.background
font.weight: Font.Medium
}
MouseArea {
id: closeArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.hideDialog();
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
}
}
Behavior on opacity {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
Behavior on scale {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
}
}

View File

@@ -72,6 +72,14 @@ PanelWindow {
opacity: wifiPasswordDialogVisible ? 1 : 0 opacity: wifiPasswordDialogVisible ? 1 : 0
scale: wifiPasswordDialogVisible ? 1 : 0.9 scale: wifiPasswordDialogVisible ? 1 : 0.9
// Prevent clicks inside dialog from closing it
MouseArea {
anchors.fill: parent
onClicked: {
// Do nothing - prevent propagation to background
}
}
Column { Column {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingL anchors.margins: Theme.spacingL
@@ -131,6 +139,7 @@ PanelWindow {
anchors.fill: parent anchors.fill: parent
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
textColor: Theme.surfaceText textColor: Theme.surfaceText
text: wifiPasswordInput
echoMode: showPasswordCheckbox.checked ? TextInput.Normal : TextInput.Password echoMode: showPasswordCheckbox.checked ? TextInput.Normal : TextInput.Password
enabled: wifiPasswordDialogVisible enabled: wifiPasswordDialogVisible
placeholderText: "Enter password" placeholderText: "Enter password"
@@ -142,6 +151,10 @@ PanelWindow {
} }
onAccepted: { onAccepted: {
WifiService.connectToWifiWithPassword(wifiPasswordSSID, wifiPasswordInput); WifiService.connectToWifiWithPassword(wifiPasswordSSID, wifiPasswordInput);
// Close dialog immediately after pressing Enter
passwordInput.enabled = false;
wifiPasswordDialogVisible = false;
wifiPasswordInput = "";
} }
Component.onCompleted: { Component.onCompleted: {
if (wifiPasswordDialogVisible) if (wifiPasswordDialogVisible)
@@ -267,6 +280,10 @@ PanelWindow {
enabled: parent.enabled enabled: parent.enabled
onClicked: { onClicked: {
WifiService.connectToWifiWithPassword(wifiPasswordSSID, wifiPasswordInput); WifiService.connectToWifiWithPassword(wifiPasswordSSID, wifiPasswordInput);
// Close dialog immediately after clicking connect
passwordInput.enabled = false;
wifiPasswordDialogVisible = false;
wifiPasswordInput = "";
} }
} }
@@ -304,4 +321,17 @@ PanelWindow {
} }
// Auto-reopen dialog on invalid password
Connections {
target: WifiService
function onPasswordDialogShouldReopenChanged() {
if (WifiService.passwordDialogShouldReopen && WifiService.connectingSSID !== "") {
wifiPasswordSSID = WifiService.connectingSSID;
wifiPasswordInput = "";
wifiPasswordDialogVisible = true;
WifiService.passwordDialogShouldReopen = false;
}
}
}
} }

View File

@@ -75,35 +75,7 @@ Singleton {
console.log("User prefers Ethernet, setting status to ethernet") console.log("User prefers Ethernet, setting status to ethernet")
} else { } else {
// Auto mode - check which interface has the default route // Auto mode - check which interface has the default route
let priorityChecker = Qt.createQmlObject(` defaultRouteChecker.running = true
import Quickshell.Io
Process {
command: ["sh", "-c", "ip route show default | head -1 | cut -d\\\" \\\" -f5"]
running: true
stdout: SplitParser {
splitMarker: "\\n"
onRead: function(data) {
let defaultInterface = data.trim()
console.log("Default route interface:", defaultInterface)
// Check if the interface is wifi or ethernet
if (defaultInterface.startsWith("wl") || defaultInterface.includes("wifi")) {
root.networkStatus = "wifi"
console.log("WiFi interface has default route, setting status to wifi")
// Trigger WiFi SSID update
if (root.wifiEnabled) {
WifiService.updateCurrentWifiInfo()
}
} else if (defaultInterface.startsWith("en") || defaultInterface.includes("eth")) {
root.networkStatus = "ethernet"
console.log("Ethernet interface has default route, setting status to ethernet")
} else {
root.networkStatus = "disconnected"
console.log("Unknown interface type:", defaultInterface)
}
}
}
}
`, root)
} }
} else if (hasWifi) { } else if (hasWifi) {
root.networkStatus = "wifi" root.networkStatus = "wifi"
@@ -157,6 +129,35 @@ Singleton {
} }
} }
Process {
id: defaultRouteChecker
command: ["sh", "-c", "ip route show default | head -1 | cut -d' ' -f5"]
running: false
stdout: SplitParser {
splitMarker: "\n"
onRead: function(data) {
let defaultInterface = data.trim()
console.log("Default route interface:", defaultInterface)
// Check if the interface is wifi or ethernet
if (defaultInterface.startsWith("wl") || defaultInterface.includes("wifi")) {
root.networkStatus = "wifi"
console.log("WiFi interface has default route, setting status to wifi")
// Trigger WiFi SSID update
if (root.wifiEnabled) {
WifiService.updateCurrentWifiInfo()
}
} else if (defaultInterface.startsWith("en") || defaultInterface.includes("eth")) {
root.networkStatus = "ethernet"
console.log("Ethernet interface has default route, setting status to ethernet")
} else {
root.networkStatus = "disconnected"
console.log("Unknown interface type:", defaultInterface)
}
}
}
}
Process { Process {
id: wifiRadioChecker id: wifiRadioChecker
command: ["nmcli", "radio", "wifi"] command: ["nmcli", "radio", "wifi"]
@@ -186,13 +187,23 @@ Singleton {
console.log("Ethernet IP:", root.ethernetIP) console.log("Ethernet IP:", root.ethernetIP)
// Get the ethernet interface name // Get the ethernet interface name
let ethInterfaceProcess = Qt.createQmlObject(` ethernetInterfaceChecker.running = true
import Quickshell.Io } else {
console.log("No ethernet IP found")
root.ethernetIP = ""
root.ethernetInterface = ""
}
}
}
}
Process { Process {
id: ethernetInterfaceChecker
command: ["sh", "-c", "nmcli -t -f DEVICE,TYPE device | grep ethernet | grep connected | cut -d: -f1 | head -1"] command: ["sh", "-c", "nmcli -t -f DEVICE,TYPE device | grep ethernet | grep connected | cut -d: -f1 | head -1"]
running: true running: false
stdout: SplitParser { stdout: SplitParser {
splitMarker: "\\n" splitMarker: "\n"
onRead: function(interfaceData) { onRead: function(interfaceData) {
if (interfaceData.trim()) { if (interfaceData.trim()) {
root.ethernetInterface = interfaceData.trim() root.ethernetInterface = interfaceData.trim()
@@ -204,15 +215,6 @@ Singleton {
} }
} }
} }
`, root)
} else {
console.log("No ethernet IP found")
root.ethernetIP = ""
root.ethernetInterface = ""
}
}
}
}
Process { Process {
id: wifiIPChecker id: wifiIPChecker
@@ -236,37 +238,30 @@ Singleton {
} }
} }
function toggleNetworkConnection(type) { // Static processes for network operations
if (type === "ethernet") {
// Toggle ethernet connection
if (root.networkStatus === "ethernet") {
// Disconnect ethernet
console.log("Disconnecting ethernet...")
let disconnectProcess = Qt.createQmlObject(`
import Quickshell.Io
Process { Process {
id: ethernetDisconnector
command: ["sh", "-c", "nmcli device disconnect $(nmcli -t -f DEVICE,TYPE device | grep ethernet | cut -d: -f1 | head -1)"] command: ["sh", "-c", "nmcli device disconnect $(nmcli -t -f DEVICE,TYPE device | grep ethernet | cut -d: -f1 | head -1)"]
running: true running: false
onExited: function(exitCode) { onExited: function(exitCode) {
console.log("Ethernet disconnect result:", exitCode) console.log("Ethernet disconnect result:", exitCode)
delayedRefreshNetworkStatus() delayedRefreshNetworkStatus()
} }
stderr: SplitParser { stderr: SplitParser {
splitMarker: "\\n" splitMarker: "\n"
onRead: function(data) { onRead: function(data) {
console.log("Ethernet disconnect stderr:", data) console.log("Ethernet disconnect stderr:", data)
} }
} }
} }
`, root)
} else {
// Connect ethernet and set higher priority
console.log("Connecting ethernet...")
let connectProcess = Qt.createQmlObject(`
import Quickshell.Io
Process { Process {
command: ["sh", "-c", "ETH_DEV=$(nmcli -t -f DEVICE,TYPE device | grep ethernet | cut -d: -f1 | head -1); if [ -n \\"$ETH_DEV\\" ]; then nmcli device connect \\"$ETH_DEV\\"; ETH_CONN=$(nmcli -t -f NAME,DEVICE connection show --active | grep \\"$ETH_DEV\\" | cut -d: -f1); if [ -n \\"$ETH_CONN\\" ]; then nmcli connection modify \\"$ETH_CONN\\" connection.autoconnect-priority 100; nmcli connection down \\"$ETH_CONN\\"; nmcli connection up \\"$ETH_CONN\\"; fi; else echo \\"No ethernet device found\\"; exit 1; fi"] id: ethernetConnector
running: true command: ["sh", "-c", "ETH_DEV=$(nmcli -t -f DEVICE,TYPE device | grep ethernet | cut -d: -f1 | head -1); if [ -n \"$ETH_DEV\" ]; then nmcli device connect \"$ETH_DEV\"; ETH_CONN=$(nmcli -t -f NAME,DEVICE connection show --active | grep \"$ETH_DEV\" | cut -d: -f1); if [ -n \"$ETH_CONN\" ]; then nmcli connection modify \"$ETH_CONN\" connection.autoconnect-priority 100; nmcli connection down \"$ETH_CONN\"; nmcli connection up \"$ETH_CONN\"; fi; else echo \"No ethernet device found\"; exit 1; fi"]
running: false
onExited: function(exitCode) { onExited: function(exitCode) {
console.log("Ethernet connect result:", exitCode) console.log("Ethernet connect result:", exitCode)
if (exitCode === 0) { if (exitCode === 0) {
@@ -276,36 +271,121 @@ Singleton {
} }
delayedRefreshNetworkStatus() delayedRefreshNetworkStatus()
} }
stderr: SplitParser { stderr: SplitParser {
splitMarker: "\\n" splitMarker: "\n"
onRead: function(data) { onRead: function(data) {
console.log("Ethernet connect stderr:", data) console.log("Ethernet connect stderr:", data)
} }
} }
} }
`, root)
}
} else if (type === "wifi") {
// Connect to WiFi if disconnected
if (root.networkStatus !== "wifi" && root.wifiEnabled) {
console.log("Connecting to WiFi device...")
let connectProcess = Qt.createQmlObject(`
import Quickshell.Io
Process { Process {
command: ["sh", "-c", "WIFI_DEV=$(nmcli -t -f DEVICE,TYPE device | grep wifi | cut -d: -f1 | head -1); if [ -n \\"$WIFI_DEV\\" ]; then nmcli device connect \\"$WIFI_DEV\\"; else echo \\"No WiFi device found\\"; exit 1; fi"] id: wifiDeviceConnector
running: true command: ["sh", "-c", "WIFI_DEV=$(nmcli -t -f DEVICE,TYPE device | grep wifi | cut -d: -f1 | head -1); if [ -n \"$WIFI_DEV\" ]; then nmcli device connect \"$WIFI_DEV\"; else echo \"No WiFi device found\"; exit 1; fi"]
running: false
onExited: function(exitCode) { onExited: function(exitCode) {
console.log("WiFi device connect result:", exitCode) console.log("WiFi device connect result:", exitCode)
delayedRefreshNetworkStatus() delayedRefreshNetworkStatus()
} }
stderr: SplitParser { stderr: SplitParser {
splitMarker: "\\n" splitMarker: "\n"
onRead: function(data) { onRead: function(data) {
console.log("WiFi device connect stderr:", data) console.log("WiFi device connect stderr:", data)
} }
} }
} }
`, root)
Process {
id: wifiSwitcher
command: ["sh", "-c", "ETH_DEV=$(nmcli -t -f DEVICE,TYPE device | grep ethernet | cut -d: -f1 | head -1); WIFI_DEV=$(nmcli -t -f DEVICE,TYPE device | grep wifi | cut -d: -f1 | head -1); [ -n \"$ETH_DEV\" ] && nmcli device disconnect \"$ETH_DEV\" 2>/dev/null; [ -n \"$WIFI_DEV\" ] && nmcli device connect \"$WIFI_DEV\" 2>/dev/null || true"]
running: false
onExited: function(exitCode) {
console.log("Switch to wifi result:", exitCode)
delayedRefreshNetworkStatus()
}
stderr: SplitParser {
splitMarker: "\n"
onRead: function(data) {
console.log("Switch to wifi stderr:", data)
}
}
}
Process {
id: ethernetSwitcher
command: ["sh", "-c", "WIFI_DEV=$(nmcli -t -f DEVICE,TYPE device | grep wifi | cut -d: -f1 | head -1); ETH_DEV=$(nmcli -t -f DEVICE,TYPE device | grep ethernet | cut -d: -f1 | head -1); [ -n \"$WIFI_DEV\" ] && nmcli device disconnect \"$WIFI_DEV\" 2>/dev/null; [ -n \"$ETH_DEV\" ] && nmcli device connect \"$ETH_DEV\" 2>/dev/null || true"]
running: false
onExited: function(exitCode) {
console.log("Switch to ethernet result:", exitCode)
delayedRefreshNetworkStatus()
}
stderr: SplitParser {
splitMarker: "\n"
onRead: function(data) {
console.log("Switch to ethernet stderr:", data)
}
}
}
Process {
id: wifiRadioToggler
command: ["nmcli", "radio", "wifi", root.wifiEnabled ? "off" : "on"]
running: false
onExited: {
root.wifiToggling = false
networkStatusChecker.running = true
}
}
Process {
id: wifiPriorityChanger
command: ["sh", "-c", "nmcli -t -f NAME,TYPE connection show | grep 802-11-wireless | cut -d: -f1 | while read conn; do nmcli connection modify \"$conn\" ipv4.route-metric 50; done; nmcli -t -f NAME,TYPE connection show | grep 802-3-ethernet | cut -d: -f1 | while read conn; do nmcli connection modify \"$conn\" ipv4.route-metric 200; done; nmcli -t -f NAME,TYPE connection show --active | grep -E \"(802-11-wireless|802-3-ethernet)\" | cut -d: -f1 | while read conn; do nmcli connection down \"$conn\" && nmcli connection up \"$conn\"; done"]
running: false
onExited: function(exitCode) {
console.log("WiFi route metric set to 50, ethernet to 200, connections restarted, exit code:", exitCode)
// Don't reset changingPreference here - let network status check handle it
delayedRefreshNetworkStatus()
}
}
Process {
id: ethernetPriorityChanger
command: ["sh", "-c", "nmcli -t -f NAME,TYPE connection show | grep 802-3-ethernet | cut -d: -f1 | while read conn; do nmcli connection modify \"$conn\" ipv4.route-metric 50; done; nmcli -t -f NAME,TYPE connection show | grep 802-11-wireless | cut -d: -f1 | while read conn; do nmcli connection modify \"$conn\" ipv4.route-metric 200; done; nmcli -t -f NAME,TYPE connection show --active | grep -E \"(802-11-wireless|802-3-ethernet)\" | cut -d: -f1 | while read conn; do nmcli connection down \"$conn\" && nmcli connection up \"$conn\"; done"]
running: false
onExited: function(exitCode) {
console.log("Ethernet route metric set to 50, WiFi to 200, connections restarted, exit code:", exitCode)
// Don't reset changingPreference here - let network status check handle it
delayedRefreshNetworkStatus()
}
}
function toggleNetworkConnection(type) {
if (type === "ethernet") {
// Toggle ethernet connection
if (root.networkStatus === "ethernet") {
// Disconnect ethernet
console.log("Disconnecting ethernet...")
ethernetDisconnector.running = true
} else {
// Connect ethernet and set higher priority
console.log("Connecting ethernet...")
ethernetConnector.running = true
}
} else if (type === "wifi") {
// Connect to WiFi if disconnected
if (root.networkStatus !== "wifi" && root.wifiEnabled) {
console.log("Connecting to WiFi device...")
wifiDeviceConnector.running = true
} }
} }
} }
@@ -313,63 +393,21 @@ Singleton {
function switchToWifi() { function switchToWifi() {
console.log("Switching to WiFi") console.log("Switching to WiFi")
// Disconnect ethernet first, then try to connect to a known WiFi network // Disconnect ethernet first, then try to connect to a known WiFi network
let switchProcess = Qt.createQmlObject(` wifiSwitcher.running = true
import Quickshell.Io
Process {
command: ["sh", "-c", "ETH_DEV=$(nmcli -t -f DEVICE,TYPE device | grep ethernet | cut -d: -f1 | head -1); WIFI_DEV=$(nmcli -t -f DEVICE,TYPE device | grep wifi | cut -d: -f1 | head -1); [ -n \\"$ETH_DEV\\" ] && nmcli device disconnect \\"$ETH_DEV\\" 2>/dev/null; [ -n \\"$WIFI_DEV\\" ] && nmcli device connect \\"$WIFI_DEV\\" 2>/dev/null || true"]
running: true
onExited: function(exitCode) {
console.log("Switch to wifi result:", exitCode)
delayedRefreshNetworkStatus()
}
stderr: SplitParser {
splitMarker: "\\n"
onRead: function(data) {
console.log("Switch to wifi stderr:", data)
}
}
}
`, root)
} }
function switchToEthernet() { function switchToEthernet() {
console.log("Switching to Ethernet") console.log("Switching to Ethernet")
// Disconnect WiFi first, then connect ethernet // Disconnect WiFi first, then connect ethernet
let switchProcess = Qt.createQmlObject(` ethernetSwitcher.running = true
import Quickshell.Io
Process {
command: ["sh", "-c", "WIFI_DEV=$(nmcli -t -f DEVICE,TYPE device | grep wifi | cut -d: -f1 | head -1); ETH_DEV=$(nmcli -t -f DEVICE,TYPE device | grep ethernet | cut -d: -f1 | head -1); [ -n \\"$WIFI_DEV\\" ] && nmcli device disconnect \\"$WIFI_DEV\\" 2>/dev/null; [ -n \\"$ETH_DEV\\" ] && nmcli device connect \\"$ETH_DEV\\" 2>/dev/null || true"]
running: true
onExited: function(exitCode) {
console.log("Switch to ethernet result:", exitCode)
delayedRefreshNetworkStatus()
}
stderr: SplitParser {
splitMarker: "\\n"
onRead: function(data) {
console.log("Switch to ethernet stderr:", data)
}
}
}
`, root)
} }
function toggleWifiRadio() { function toggleWifiRadio() {
if (root.wifiToggling) return if (root.wifiToggling) return
root.wifiToggling = true root.wifiToggling = true
let action = root.wifiEnabled ? "off" : "on" wifiRadioToggler.command = ["nmcli", "radio", "wifi", root.wifiEnabled ? "off" : "on"]
let toggleProcess = Qt.createQmlObject(` wifiRadioToggler.running = true
import Quickshell.Io
Process {
command: ["nmcli", "radio", "wifi", "${action}"]
running: true
onExited: {
root.wifiToggling = false
networkStatusChecker.running = true
}
}
`, root)
} }
function refreshNetworkStatus() { function refreshNetworkStatus() {
@@ -391,32 +429,10 @@ Singleton {
if (preference === "wifi") { if (preference === "wifi") {
// Set WiFi to low route metric (high priority), ethernet to high route metric (low priority) // Set WiFi to low route metric (high priority), ethernet to high route metric (low priority)
let wifiPriorityProcess = Qt.createQmlObject(` wifiPriorityChanger.running = true
import Quickshell.Io
Process {
command: ["sh", "-c", "nmcli -t -f NAME,TYPE connection show | grep 802-11-wireless | cut -d: -f1 | while read conn; do nmcli connection modify \\\\\\"$conn\\\\\\" ipv4.route-metric 50; done; nmcli -t -f NAME,TYPE connection show | grep 802-3-ethernet | cut -d: -f1 | while read conn; do nmcli connection modify \\\\\\"$conn\\\\\\" ipv4.route-metric 200; done; nmcli -t -f NAME,TYPE connection show --active | grep -E \\\\\\"(802-11-wireless|802-3-ethernet)\\\\\\" | cut -d: -f1 | while read conn; do nmcli connection down \\\\\\"$conn\\\\\\" && nmcli connection up \\\\\\"$conn\\\\\\"; done"]
running: true
onExited: function(exitCode) {
console.log("WiFi route metric set to 50, ethernet to 200, connections restarted, exit code:", exitCode)
// Don't reset changingPreference here - let network status check handle it
delayedRefreshNetworkStatus()
}
}
`, root)
} else if (preference === "ethernet") { } else if (preference === "ethernet") {
// Set ethernet to low route metric (high priority), WiFi to high route metric (low priority) // Set ethernet to low route metric (high priority), WiFi to high route metric (low priority)
let ethernetPriorityProcess = Qt.createQmlObject(` ethernetPriorityChanger.running = true
import Quickshell.Io
Process {
command: ["sh", "-c", "nmcli -t -f NAME,TYPE connection show | grep 802-3-ethernet | cut -d: -f1 | while read conn; do nmcli connection modify \\\\\\"$conn\\\\\\" ipv4.route-metric 50; done; nmcli -t -f NAME,TYPE connection show | grep 802-11-wireless | cut -d: -f1 | while read conn; do nmcli connection modify \\\\\\"$conn\\\\\\" ipv4.route-metric 200; done; nmcli -t -f NAME,TYPE connection show --active | grep -E \\\\\\"(802-11-wireless|802-3-ethernet)\\\\\\" | cut -d: -f1 | while read conn; do nmcli connection down \\\\\\"$conn\\\\\\" && nmcli connection up \\\\\\"$conn\\\\\\"; done"]
running: true
onExited: function(exitCode) {
console.log("Ethernet route metric set to 50, WiFi to 200, connections restarted, exit code:", exitCode)
// Don't reset changingPreference here - let network status check handle it
delayedRefreshNetworkStatus()
}
}
`, root)
} }
} }

View File

@@ -119,25 +119,13 @@ Singleton {
updateProcessList(); updateProcessList();
} }
property int killPid: 0
function killProcess(pid) { function killProcess(pid) {
if (pid > 0) { if (pid > 0) {
const killCmd = ["bash", "-c", "kill " + pid]; root.killPid = pid
const killProcess = Qt.createQmlObject(` processKiller.command = ["bash", "-c", "kill " + pid]
import QtQuick processKiller.running = true
import Quickshell.Io
Process {
command: ${JSON.stringify(killCmd)}
running: true
onExited: (exitCode) => {
if (exitCode === 0) {
console.log("Process killed successfully:", ${pid})
} else {
console.warn("Failed to kill process:", ${pid}, "exit code:", exitCode)
}
destroy()
}
}
`, root);
} }
} }
@@ -438,6 +426,21 @@ Singleton {
} }
Process {
id: processKiller
command: ["bash", "-c", "kill " + root.killPid]
running: false
onExited: (exitCode) => {
if (exitCode === 0) {
console.log("Process killed successfully:", root.killPid)
} else {
console.warn("Failed to kill process:", root.killPid, "exit code:", exitCode)
}
root.killPid = 0
}
}
Timer { Timer {
id: processTimer id: processTimer

View File

@@ -13,11 +13,20 @@ Singleton {
property var wifiNetworks: [] property var wifiNetworks: []
property var savedWifiNetworks: [] property var savedWifiNetworks: []
property bool isScanning: false property bool isScanning: false
property string connectionStatus: "" // "cosnnecting", "connected", "failed", "" property string connectionStatus: "" // "connecting", "connected", "failed", "invalid_password", ""
property string connectingSSID: "" property string connectingSSID: ""
property string lastConnectionError: ""
property bool passwordDialogShouldReopen: false
// Auto-refresh timer for when control center is open // Auto-refresh timer for when control center is open
property bool autoRefreshEnabled: false property bool autoRefreshEnabled: false
signal networksUpdated()
// Network info properties
property string networkInfoSSID: ""
property string networkInfoDetails: ""
property bool networkInfoLoading: false
function scanWifi() { function scanWifi() {
if (root.isScanning) if (root.isScanning)
return ; return ;
@@ -33,107 +42,49 @@ Singleton {
console.log("Connecting to WiFi:", ssid); console.log("Connecting to WiFi:", ssid);
root.connectionStatus = "connecting"; root.connectionStatus = "connecting";
root.connectingSSID = ssid; root.connectingSSID = ssid;
let connectProcess = Qt.createQmlObject(` ToastService.showInfo("Connecting to " + ssid + "...");
import Quickshell.Io wifiConnector.running = true;
Process {
command: ["bash", "-c", "nmcli dev wifi connect \\"' + ssid + '\\" || nmcli connection up \\"' + ssid + '\\"; if [ $? -eq 0 ]; then nmcli connection modify \\"' + ssid + '\\" connection.autoconnect-priority 50; nmcli connection down \\"' + ssid + '\\"; nmcli connection up \\"' + ssid + '\\"; fi"]
running: true
onExited: (exitCode) => {
console.log("WiFi connection result:", exitCode)
if (exitCode === 0) {
root.connectionStatus = "connected"
console.log("Connected to WiFi successfully")
// Set user preference to WiFi when manually connecting
NetworkService.setNetworkPreference("wifi")
// Force network status refresh after successful connection
NetworkService.delayedRefreshNetworkStatus()
} else {
root.connectionStatus = "failed"
console.log("WiFi connection failed")
}
scanWifi()
statusResetTimer.start()
} }
stderr: SplitParser { property string wifiPassword: ""
splitMarker: "\\n"
onRead: (data) => {
console.log("WiFi connection stderr:", data)
}
}
}
`, root);
}
function connectToWifiWithPassword(ssid, password) { function connectToWifiWithPassword(ssid, password) {
console.log("Connecting to WiFi with password:", ssid); console.log("Connecting to WiFi with password:", ssid);
root.connectionStatus = "connecting"; root.connectionStatus = "connecting";
root.connectingSSID = ssid; root.connectingSSID = ssid;
let connectProcess = Qt.createQmlObject(` root.wifiPassword = password;
import Quickshell.Io root.lastConnectionError = "";
Process { root.passwordDialogShouldReopen = false;
command: ["bash", "-c", "nmcli dev wifi connect \\"' + ssid + '\\" password \\"' + password + '\\"; if [ $? -eq 0 ]; then nmcli connection modify \\"' + ssid + '\\" connection.autoconnect-priority 50; nmcli connection down \\"' + ssid + '\\"; nmcli connection up \\"' + ssid + '\\"; fi"] ToastService.showInfo("Connecting to " + ssid + "...");
running: true wifiPasswordConnector.running = true;
onExited: (exitCode) => {
console.log("WiFi connection with password result:", exitCode)
if (exitCode === 0) {
root.connectionStatus = "connected"
console.log("Connected to WiFi with password successfully")
// Set user preference to WiFi when manually connecting
NetworkService.setNetworkPreference("wifi")
// Force network status refresh after successful connection
NetworkService.delayedRefreshNetworkStatus()
} else {
root.connectionStatus = "failed"
console.log("WiFi connection with password failed")
}
scanWifi()
statusResetTimer.start()
} }
stderr: SplitParser { function disconnectWifi() {
splitMarker: "\\n" console.log("Disconnecting from current WiFi network");
onRead: (data) => { wifiDisconnector.running = true;
console.log("WiFi connection with password stderr:", data)
}
}
}
`, root);
} }
property string forgetSSID: ""
function forgetWifiNetwork(ssid) { function forgetWifiNetwork(ssid) {
console.log("Forgetting WiFi network:", ssid); console.log("Forgetting WiFi network:", ssid);
let forgetProcess = Qt.createQmlObject(` root.forgetSSID = ssid;
import Quickshell.Io wifiForget.running = true;
Process {
command: ["bash", "-c", "nmcli connection delete \\"' + ssid + '\\" || nmcli connection delete id \\"' + ssid + '\\""]
running: true
onExited: (exitCode) => {
console.log("WiFi forget result:", exitCode)
if (exitCode === 0) {
console.log("Successfully forgot WiFi network:", "' + ssid + '")
} else {
console.log("Failed to forget WiFi network:", "' + ssid + '")
}
scanWifi()
} }
stderr: SplitParser { function fetchNetworkInfo(ssid) {
splitMarker: "\\n" console.log("Fetching network info for:", ssid);
onRead: (data) => { root.networkInfoSSID = ssid;
console.log("WiFi forget stderr:", data) root.networkInfoLoading = true;
} root.networkInfoDetails = "Loading network information...";
} wifiInfoFetcher.running = true;
}
`, root);
} }
function updateCurrentWifiInfo() { function updateCurrentWifiInfo() {
currentWifiInfo.running = true; currentWifiInfo.running = true;
} }
Process { Process {
id: currentWifiInfo id: currentWifiInfo
@@ -245,6 +196,7 @@ Singleton {
} }
Timer { Timer {
id: fallbackTimer id: fallbackTimer
@@ -273,4 +225,304 @@ Singleton {
onTriggered: root.scanWifi() onTriggered: root.scanWifi()
} }
// WiFi Connection Process
Process {
id: wifiConnector
command: ["bash", "-c", "timeout 30 nmcli dev wifi connect \"" + root.connectingSSID + "\" || nmcli connection up \"" + root.connectingSSID + "\"; exit_code=$?; echo \"nmcli exit code: $exit_code\" >&2; if [ $exit_code -eq 0 ]; then nmcli connection modify \"" + root.connectingSSID + "\" connection.autoconnect-priority 50; sleep 2; if nmcli -t -f ACTIVE,SSID dev wifi | grep -q \"^yes:" + root.connectingSSID + "\"; then echo \"Connection verified\" >&2; exit 0; else echo \"Connection failed verification\" >&2; exit 4; fi; else exit $exit_code; fi"]
running: false
stderr: StdioCollector {
onStreamFinished: {
console.log("WiFi connection debug output:", text.trim())
}
}
onExited: (exitCode) => {
console.log("WiFi connection result:", exitCode)
if (exitCode === 0) {
root.connectionStatus = "connected"
root.passwordDialogShouldReopen = false
console.log("Connected to WiFi successfully")
ToastService.showInfo("Connected to " + root.connectingSSID)
NetworkService.setNetworkPreference("wifi")
NetworkService.delayedRefreshNetworkStatus()
// Immediately update savedWifiNetworks to include the new connection
if (!root.savedWifiNetworks.some((saved) => saved.ssid === root.connectingSSID)) {
let updatedSaved = [...root.savedWifiNetworks];
updatedSaved.push({"ssid": root.connectingSSID, "saved": true});
root.savedWifiNetworks = updatedSaved;
}
// Update wifiNetworks to reflect the change
let updatedNetworks = [...root.wifiNetworks];
for (let i = 0; i < updatedNetworks.length; i++) {
if (updatedNetworks[i].ssid === root.connectingSSID) {
updatedNetworks[i].saved = true;
updatedNetworks[i].connected = true;
break;
}
}
root.wifiNetworks = updatedNetworks;
} else if (exitCode === 4) {
// Connection failed - likely needs password for saved network
root.connectionStatus = "invalid_password"
root.passwordDialogShouldReopen = true
console.log("Saved network connection failed - password required")
ToastService.showError("Authentication failed for " + root.connectingSSID)
} else {
root.connectionStatus = "failed"
console.log("WiFi connection failed")
ToastService.showError("Failed to connect to " + root.connectingSSID)
}
scanWifi()
statusResetTimer.start()
}
}
// WiFi Connection with Password Process
Process {
id: wifiPasswordConnector
command: ["bash", "-c", "nmcli connection delete \"" + root.connectingSSID + "\" 2>/dev/null || true; timeout 30 nmcli dev wifi connect \"" + root.connectingSSID + "\" password \"" + root.wifiPassword + "\"; exit_code=$?; echo \"nmcli exit code: $exit_code\" >&2; if [ $exit_code -eq 0 ]; then nmcli connection modify \"" + root.connectingSSID + "\" connection.autoconnect-priority 50; sleep 2; if nmcli -t -f ACTIVE,SSID dev wifi | grep -q \"^yes:" + root.connectingSSID + "\"; then echo \"Connection verified\" >&2; exit 0; else echo \"Connection failed verification\" >&2; exit 4; fi; else exit $exit_code; fi"]
running: false
stdout: StdioCollector {
onStreamFinished: {
if (text.trim()) {
console.log("WiFi connection stdout:", text.trim())
}
}
}
stderr: StdioCollector {
onStreamFinished: {
root.lastConnectionError = text.trim()
console.log("WiFi connection debug output:", text.trim())
}
}
onExited: (exitCode) => {
console.log("WiFi connection with password result:", exitCode)
console.log("Error output:", root.lastConnectionError)
if (exitCode === 0) {
root.connectionStatus = "connected"
root.passwordDialogShouldReopen = false
console.log("Connected to WiFi with password successfully")
ToastService.showInfo("Connected to " + root.connectingSSID)
NetworkService.setNetworkPreference("wifi")
NetworkService.delayedRefreshNetworkStatus()
// Immediately update savedWifiNetworks to include the new connection
if (!root.savedWifiNetworks.some((saved) => saved.ssid === root.connectingSSID)) {
let updatedSaved = [...root.savedWifiNetworks];
updatedSaved.push({"ssid": root.connectingSSID, "saved": true});
root.savedWifiNetworks = updatedSaved;
}
// Update wifiNetworks to reflect the change
let updatedNetworks = [...root.wifiNetworks];
for (let i = 0; i < updatedNetworks.length; i++) {
if (updatedNetworks[i].ssid === root.connectingSSID) {
updatedNetworks[i].saved = true;
updatedNetworks[i].connected = true;
break;
}
}
root.wifiNetworks = updatedNetworks;
} else if (exitCode === 4) {
// Connection activation failed - likely invalid credentials
if (root.lastConnectionError.includes("Secrets were required") ||
root.lastConnectionError.includes("authentication") ||
root.lastConnectionError.includes("AUTH_TIMED_OUT")) {
root.connectionStatus = "invalid_password"
root.passwordDialogShouldReopen = true
console.log("Invalid password detected")
ToastService.showError("Invalid password for " + root.connectingSSID)
} else {
root.connectionStatus = "failed"
console.log("Connection failed - not password related")
ToastService.showError("Failed to connect to " + root.connectingSSID)
}
} else if (exitCode === 3 || exitCode === 124) {
root.connectionStatus = "failed"
console.log("Connection timed out")
ToastService.showError("Connection to " + root.connectingSSID + " timed out")
} else {
root.connectionStatus = "failed"
console.log("WiFi connection with password failed")
ToastService.showError("Failed to connect to " + root.connectingSSID)
}
root.wifiPassword = "" // Clear password
scanWifi()
statusResetTimer.start()
}
}
// WiFi Disconnect Process
Process {
id: wifiDisconnector
command: ["bash", "-c", "WIFI_DEV=$(nmcli -t -f DEVICE,TYPE device | grep wifi | cut -d: -f1 | head -1); [ -n \"$WIFI_DEV\" ] && nmcli device disconnect \"$WIFI_DEV\""]
running: false
onExited: (exitCode) => {
console.log("WiFi disconnect result:", exitCode)
if (exitCode === 0) {
console.log("Successfully disconnected from WiFi")
ToastService.showInfo("Disconnected from WiFi")
root.currentWifiSSID = ""
root.connectionStatus = ""
NetworkService.refreshNetworkStatus()
} else {
console.log("Failed to disconnect from WiFi")
ToastService.showError("Failed to disconnect from WiFi")
}
}
stderr: SplitParser {
splitMarker: "\\n"
onRead: (data) => {
console.log("WiFi disconnect stderr:", data)
}
}
}
// WiFi Forget Network Process
Process {
id: wifiForget
command: ["bash", "-c", "nmcli connection delete \"" + root.forgetSSID + "\" || nmcli connection delete id \"" + root.forgetSSID + "\""]
running: false
onExited: (exitCode) => {
console.log("WiFi forget result:", exitCode)
if (exitCode === 0) {
console.log("Successfully forgot WiFi network:", root.forgetSSID)
ToastService.showInfo("Forgot network \"" + root.forgetSSID + "\"")
// If we forgot the currently connected network, clear connection status
if (root.forgetSSID === root.currentWifiSSID) {
root.currentWifiSSID = "";
root.connectionStatus = "";
NetworkService.refreshNetworkStatus();
}
// Update savedWifiNetworks to remove the forgotten network
root.savedWifiNetworks = root.savedWifiNetworks.filter((saved) => {
return saved.ssid !== root.forgetSSID;
});
// Update wifiNetworks - create new array with updated objects
let updatedNetworks = [];
for (let i = 0; i < root.wifiNetworks.length; i++) {
let network = root.wifiNetworks[i];
if (network.ssid === root.forgetSSID) {
let updatedNetwork = Object.assign({}, network);
updatedNetwork.saved = false;
updatedNetwork.connected = false;
updatedNetworks.push(updatedNetwork);
} else {
updatedNetworks.push(network);
}
}
root.wifiNetworks = updatedNetworks;
root.networksUpdated();
} else {
console.log("Failed to forget WiFi network:", root.forgetSSID)
ToastService.showError("Failed to forget network \"" + root.forgetSSID + "\"")
}
root.forgetSSID = "" // Clear SSID
}
stderr: SplitParser {
splitMarker: "\\n"
onRead: (data) => {
console.log("WiFi forget stderr:", data)
}
}
}
// WiFi Network Info Fetcher Process - Using detailed nmcli output
Process {
id: wifiInfoFetcher
command: ["nmcli", "-t", "-f", "SSID,SIGNAL,SECURITY,FREQ,RATE,MODE,CHAN,WPA-FLAGS,RSN-FLAGS", "dev", "wifi", "list"]
running: false
stdout: StdioCollector {
onStreamFinished: {
let details = "";
if (text.trim()) {
let lines = text.trim().split('\n');
for (let line of lines) {
let parts = line.split(':');
if (parts.length >= 9 && parts[0] === root.networkInfoSSID) {
let ssid = parts[0] || "Unknown";
let signal = parts[1] || "0";
let security = parts[2] || "Open";
let freq = parts[3] || "Unknown";
let rate = parts[4] || "Unknown";
let mode = parts[5] || "Unknown";
let channel = parts[6] || "Unknown";
let wpaFlags = parts[7] || "";
let rsnFlags = parts[8] || "";
// Determine band from frequency
let band = "Unknown";
let freqNum = parseInt(freq);
if (freqNum >= 2400 && freqNum <= 2500) {
band = "2.4 GHz";
} else if (freqNum >= 5000 && freqNum <= 6000) {
band = "5 GHz";
} else if (freqNum >= 6000) {
band = "6 GHz";
}
details = "Network Name: " + ssid + "\\n";
details += "Signal Strength: " + signal + "%\\n";
details += "Security: " + (security === "" ? "Open" : security) + "\\n";
details += "Frequency: " + freq + " MHz\\n";
details += "Band: " + band + "\\n";
details += "Channel: " + channel + "\\n";
details += "Mode: " + mode + "\\n";
details += "Max Rate: " + rate + " Mbit/s\\n";
if (wpaFlags !== "") {
details += "WPA Flags: " + wpaFlags + "\\n";
}
if (rsnFlags !== "") {
details += "RSN Flags: " + rsnFlags + "\\n";
}
break;
}
}
}
if (details === "") {
details = "Network information not found or network not available.";
}
root.networkInfoDetails = details;
root.networkInfoLoading = false;
console.log("Network info fetched for:", root.networkInfoSSID);
}
}
onExited: (exitCode) => {
root.networkInfoLoading = false;
if (exitCode !== 0) {
console.log("Failed to fetch network info, exit code:", exitCode);
root.networkInfoDetails = "Failed to fetch network information";
}
}
stderr: SplitParser {
splitMarker: "\\n"
onRead: (data) => {
console.log("WiFi info stderr:", data);
}
}
}
} }

View File

@@ -43,6 +43,10 @@ ShellRoot {
id: wifiPasswordDialog id: wifiPasswordDialog
} }
NetworkInfoDialog {
id: networkInfoDialog
}
InputDialog { InputDialog {
id: globalInputDialog id: globalInputDialog
} }