From 71f8b8ce9a1b5f7928c0d754358eeaf4207184b7 Mon Sep 17 00:00:00 2001 From: bbedward Date: Wed, 23 Jul 2025 18:30:43 -0400 Subject: [PATCH] fix context menus and general stuff --- Common/Utilities.js | 101 --- Modals/NetworkInfoModal.qml | 4 +- Modals/ProcessListModal.qml | 8 +- Modals/WifiPasswordModal.qml | 12 +- Modules/AppDrawer/AppDrawerPopout.qml | 18 +- Modules/ControlCenter/ControlCenterPopout.qml | 2 +- Modules/ControlCenter/Network/WiFiCard.qml | 24 +- .../ControlCenter/Network/WiFiContextMenu.qml | 8 +- .../Network/WiFiNetworksList.qml | 22 +- Modules/ControlCenter/NetworkTab.qml | 48 +- Modules/ProcessList/ProcessContextMenu.qml | 199 ++---- Modules/ProcessList/ProcessListItem.qml | 19 +- Modules/ProcessList/ProcessListPopout.qml | 8 +- Modules/ProcessList/ProcessListView.qml | 4 +- Modules/ProcessList/ProcessesTab.qml | 11 +- Modules/TopBar/AudioVisualization.qml | 18 +- Modules/TopBar/ControlCenterButton.qml | 2 +- Modules/TopBar/Media.qml | 2 - Modules/TopBar/TopBar.qml | 3 +- Services/NetworkService.qml | 614 ++++++++++++++++-- Services/WifiService.qml | 528 --------------- Widgets/DankDropdown.qml | 173 ++--- 22 files changed, 789 insertions(+), 1039 deletions(-) delete mode 100644 Common/Utilities.js delete mode 100644 Services/WifiService.qml diff --git a/Common/Utilities.js b/Common/Utilities.js deleted file mode 100644 index 114e55ff..00000000 --- a/Common/Utilities.js +++ /dev/null @@ -1,101 +0,0 @@ -function parseWorkspaceOutput(data) { - const lines = data.split('\n') - let currentOutputName = "" - let focusedOutput = "" - let focusedWorkspace = 1 - let outputWorkspaces = {} - - - for (const line of lines) { - if (line.startsWith('Output "')) { - const outputMatch = line.match(/Output "(.+)"/) - if (outputMatch) { - currentOutputName = outputMatch[1] - outputWorkspaces[currentOutputName] = [] - } - continue - } - - if (line.trim() && line.match(/^\s*\*?\s*(\d+)$/)) { - const wsMatch = line.match(/^\s*(\*?)\s*(\d+)$/) - if (wsMatch) { - const isActive = wsMatch[1] === '*' - const wsNum = parseInt(wsMatch[2]) - - if (currentOutputName && outputWorkspaces[currentOutputName]) { - outputWorkspaces[currentOutputName].push(wsNum) - } - - if (isActive) { - focusedOutput = currentOutputName - focusedWorkspace = wsNum - } - } - } - } - - // Show workspaces for THIS screen only - if (topBar.screenName && outputWorkspaces[topBar.screenName]) { - workspaceList = outputWorkspaces[topBar.screenName] - - // Always track the active workspace for this display - // Parse all lines to find which workspace is active on this display - let thisDisplayActiveWorkspace = 1 - let inThisOutput = false - - for (const line of lines) { - if (line.startsWith('Output "')) { - const outputMatch = line.match(/Output "(.+)"/) - inThisOutput = outputMatch && outputMatch[1] === topBar.screenName - continue - } - - if (inThisOutput && line.trim() && line.match(/^\s*\*\s*(\d+)$/)) { - const wsMatch = line.match(/^\s*\*\s*(\d+)$/) - if (wsMatch) { - thisDisplayActiveWorkspace = parseInt(wsMatch[1]) - break - } - } - } - - currentWorkspace = thisDisplayActiveWorkspace - // console.log("Monitor", topBar.screenName, "active workspace:", thisDisplayActiveWorkspace) - } else { - // Fallback if screen name not found - workspaceList = [1, 2] - currentWorkspace = 1 - } -} - -function showMenu(x, y) { - root.currentTrayMenu = customTrayMenu - root.currentTrayItem = trayItem - - // Simple positioning: right side of screen, below the panel - root.trayMenuX = rightSection.x + rightSection.width - 180 - theme.spacingL - root.trayMenuY = theme.barHeight + theme.spacingS - - console.log("Showing menu at:", root.trayMenuX, root.trayMenuY) - menuVisible = true - root.showTrayMenu = true -} - -function hideMenu() { - menuVisible = false - root.showTrayMenu = false - root.currentTrayMenu = null - root.currentTrayItem = null -} - -function showNotificationPopup(notification) { - root.activeNotification = notification - root.showNotificationPopup = true - notificationTimer.restart() -} - -function hideNotificationPopup() { - root.showNotificationPopup = false - notificationTimer.stop() - clearNotificationTimer.restart() -} \ No newline at end of file diff --git a/Modals/NetworkInfoModal.qml b/Modals/NetworkInfoModal.qml index aee4f6c3..0145b5e0 100644 --- a/Modals/NetworkInfoModal.qml +++ b/Modals/NetworkInfoModal.qml @@ -19,7 +19,7 @@ DankModal { networkSSID = ssid; networkData = data; networkInfoModalVisible = true; - WifiService.fetchNetworkInfo(ssid); + NetworkService.fetchNetworkInfo(ssid); } function hideDialog() { @@ -117,7 +117,7 @@ DankModal { anchors.fill: parent anchors.margins: Theme.spacingM - text: WifiService.networkInfoDetails.replace(/\\n/g, '\n') || "No information available" + text: NetworkService.networkInfoDetails.replace(/\\n/g, '\n') || "No information available" font.pixelSize: Theme.fontSizeMedium color: Theme.surfaceText wrapMode: Text.WordWrap diff --git a/Modals/ProcessListModal.qml b/Modals/ProcessListModal.qml index 9d05e821..d22ea7a0 100644 --- a/Modals/ProcessListModal.qml +++ b/Modals/ProcessListModal.qml @@ -27,6 +27,10 @@ DankModal { function hide() { processListModal.visible = false; SystemMonitorService.enableDetailedMonitoring(false); + // Close any open context menus + if (processContextMenu.visible) { + processContextMenu.close(); + } } function toggle() { @@ -276,7 +280,7 @@ DankModal { Component { id: processesTabComponent ProcessesTab { - contextMenu: processContextMenuWindow + contextMenu: processContextMenu } } @@ -293,7 +297,7 @@ DankModal { ProcessContextMenu { - id: processContextMenuWindow + id: processContextMenu } IpcHandler { diff --git a/Modals/WifiPasswordModal.qml b/Modals/WifiPasswordModal.qml index f6d28a7b..452df6a2 100644 --- a/Modals/WifiPasswordModal.qml +++ b/Modals/WifiPasswordModal.qml @@ -28,15 +28,15 @@ DankModal { // Auto-reopen dialog on invalid password Connections { function onPasswordDialogShouldReopenChanged() { - if (WifiService.passwordDialogShouldReopen && WifiService.connectingSSID !== "") { - wifiPasswordSSID = WifiService.connectingSSID; + if (NetworkService.passwordDialogShouldReopen && NetworkService.connectingSSID !== "") { + wifiPasswordSSID = NetworkService.connectingSSID; wifiPasswordInput = ""; wifiPasswordModalVisible = true; - WifiService.passwordDialogShouldReopen = false; + NetworkService.passwordDialogShouldReopen = false; } } - target: WifiService + target: NetworkService } content: Component { @@ -111,7 +111,7 @@ DankModal { wifiPasswordInput = text; } onAccepted: { - WifiService.connectToWifiWithPassword(wifiPasswordSSID, passwordInput.text); + NetworkService.connectToWifiWithPassword(wifiPasswordSSID, passwordInput.text); wifiPasswordModalVisible = false; wifiPasswordInput = ""; passwordInput.text = ""; @@ -245,7 +245,7 @@ DankModal { cursorShape: Qt.PointingHandCursor enabled: parent.enabled onClicked: { - WifiService.connectToWifiWithPassword(wifiPasswordSSID, passwordInput.text); + NetworkService.connectToWifiWithPassword(wifiPasswordSSID, passwordInput.text); wifiPasswordModalVisible = false; wifiPasswordInput = ""; passwordInput.text = ""; diff --git a/Modules/AppDrawer/AppDrawerPopout.qml b/Modules/AppDrawer/AppDrawerPopout.qml index c0452f4f..42723f48 100644 --- a/Modules/AppDrawer/AppDrawerPopout.qml +++ b/Modules/AppDrawer/AppDrawerPopout.qml @@ -85,7 +85,6 @@ PanelWindow { x: Theme.spacingL y: Theme.barHeight + Theme.spacingXS - // GPU-accelerated scale + opacity animation opacity: appDrawerPopout.isVisible ? 1 : 0 scale: appDrawerPopout.isVisible ? 1 : 0.9 @@ -249,12 +248,21 @@ PanelWindow { } } + Component.onCompleted: { + if (appDrawerPopout.isVisible) { + searchField.forceActiveFocus(); + } + } + Connections { - function onVisibleChanged() { - if (appDrawerPopout.visible) - searchField.forceActiveFocus(); - else + function onIsVisibleChanged() { + if (appDrawerPopout.isVisible) { + Qt.callLater(function() { + searchField.forceActiveFocus(); + }); + } else { searchField.clearFocus(); + } } target: appDrawerPopout diff --git a/Modules/ControlCenter/ControlCenterPopout.qml b/Modules/ControlCenter/ControlCenterPopout.qml index 3ce9c27f..1eb9e62f 100644 --- a/Modules/ControlCenter/ControlCenterPopout.qml +++ b/Modules/ControlCenter/ControlCenterPopout.qml @@ -23,7 +23,7 @@ PanelWindow { visible: controlCenterVisible onVisibleChanged: { // Enable/disable WiFi auto-refresh based on control center visibility - WifiService.autoRefreshEnabled = visible && NetworkService.wifiEnabled; + NetworkService.autoRefreshEnabled = visible && NetworkService.wifiEnabled; // Stop bluetooth scanning when control center is closed if (!visible && BluetoothService.adapter && BluetoothService.adapter.discovering) BluetoothService.adapter.discovering = false; diff --git a/Modules/ControlCenter/Network/WiFiCard.qml b/Modules/ControlCenter/Network/WiFiCard.qml index a67e3560..4437105b 100644 --- a/Modules/ControlCenter/Network/WiFiCard.qml +++ b/Modules/ControlCenter/Network/WiFiCard.qml @@ -49,8 +49,8 @@ Rectangle { name: { if (!NetworkService.wifiEnabled) { return "wifi_off"; - } else if (WifiService.currentWifiSSID !== "") { - return getWiFiSignalIcon(WifiService.wifiSignalStrength); + } else if (NetworkService.currentWifiSSID !== "") { + return getWiFiSignalIcon(NetworkService.wifiSignalStrength); } else { return "wifi"; } @@ -64,8 +64,8 @@ Rectangle { text: { if (!NetworkService.wifiEnabled) { return "WiFi is off"; - } else if (NetworkService.wifiEnabled && WifiService.currentWifiSSID) { - return WifiService.currentWifiSSID || "Connected"; + } else if (NetworkService.wifiEnabled && NetworkService.currentWifiSSID) { + return NetworkService.currentWifiSSID || "Connected"; } else { return "Not Connected"; } @@ -82,7 +82,7 @@ Rectangle { text: { if (!NetworkService.wifiEnabled) { return "Turn on WiFi to see networks"; - } else if (NetworkService.wifiEnabled && WifiService.currentWifiSSID) { + } else if (NetworkService.wifiEnabled && NetworkService.currentWifiSSID) { return NetworkService.wifiIP || "Connected"; } else { return "Select a network below"; @@ -130,13 +130,13 @@ Rectangle { onClicked: { if (NetworkService.wifiEnabled) { // When turning WiFi off, clear all cached WiFi data - WifiService.currentWifiSSID = ""; - WifiService.wifiSignalStrength = "excellent"; - WifiService.wifiNetworks = []; - WifiService.savedWifiNetworks = []; - WifiService.connectionStatus = ""; - WifiService.connectingSSID = ""; - WifiService.isScanning = false; + NetworkService.currentWifiSSID = ""; + NetworkService.wifiSignalStrength = "excellent"; + NetworkService.wifiNetworks = []; + NetworkService.savedWifiNetworks = []; + NetworkService.connectionStatus = ""; + NetworkService.connectingSSID = ""; + NetworkService.isScanning = false; NetworkService.refreshNetworkStatus(); } NetworkService.toggleWifiRadio(); diff --git a/Modules/ControlCenter/Network/WiFiContextMenu.qml b/Modules/ControlCenter/Network/WiFiContextMenu.qml index 0d7001f0..143046b9 100644 --- a/Modules/ControlCenter/Network/WiFiContextMenu.qml +++ b/Modules/ControlCenter/Network/WiFiContextMenu.qml @@ -118,10 +118,10 @@ Rectangle { onClicked: { if (wifiContextMenuWindow.networkData) { if (wifiContextMenuWindow.networkData.connected) { - WifiService.disconnectWifi(); + NetworkService.disconnectWifi(); } else { if (wifiContextMenuWindow.networkData.saved) { - WifiService.connectToWifi(wifiContextMenuWindow.networkData.ssid); + NetworkService.connectToWifi(wifiContextMenuWindow.networkData.ssid); } else if (wifiContextMenuWindow.networkData.secured) { if (wifiPasswordModalRef) { wifiPasswordModalRef.wifiPasswordSSID = wifiContextMenuWindow.networkData.ssid; @@ -129,7 +129,7 @@ Rectangle { wifiPasswordModalRef.wifiPasswordModalVisible = true; } } else { - WifiService.connectToWifi(wifiContextMenuWindow.networkData.ssid); + NetworkService.connectToWifi(wifiContextMenuWindow.networkData.ssid); } } } @@ -198,7 +198,7 @@ Rectangle { cursorShape: Qt.PointingHandCursor onClicked: { if (wifiContextMenuWindow.networkData) { - WifiService.forgetWifiNetwork(wifiContextMenuWindow.networkData.ssid); + NetworkService.forgetWifiNetwork(wifiContextMenuWindow.networkData.ssid); } wifiContextMenuWindow.hide(); } diff --git a/Modules/ControlCenter/Network/WiFiNetworksList.qml b/Modules/ControlCenter/Network/WiFiNetworksList.qml index 8b710cb5..6caf7449 100644 --- a/Modules/ControlCenter/Network/WiFiNetworksList.qml +++ b/Modules/ControlCenter/Network/WiFiNetworksList.qml @@ -55,7 +55,7 @@ Column { width: 28 height: 28 radius: 14 - color: refreshAreaSpan.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : WifiService.isScanning ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.06) : "transparent" + color: refreshAreaSpan.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : NetworkService.isScanning ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.06) : "transparent" DankIcon { id: refreshIconSpan @@ -63,12 +63,12 @@ Column { name: "refresh" size: Theme.iconSize - 6 color: refreshAreaSpan.containsMouse ? Theme.primary : Theme.surfaceText - rotation: WifiService.isScanning ? refreshIconSpan.rotation : 0 + rotation: NetworkService.isScanning ? refreshIconSpan.rotation : 0 RotationAnimation { target: refreshIconSpan property: "rotation" - running: WifiService.isScanning + running: NetworkService.isScanning from: 0 to: 360 duration: 1000 @@ -89,10 +89,10 @@ Column { hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: { - if (!WifiService.isScanning) { + if (!NetworkService.isScanning) { // Immediate visual feedback refreshIconSpan.rotation += 30; - WifiService.scanWifi(); + NetworkService.scanWifi(); } } } @@ -164,9 +164,9 @@ Column { text: { if (modelData.connected) return "Connected"; - if (WifiService.connectionStatus === "connecting" && WifiService.connectingSSID === modelData.ssid) + if (NetworkService.connectionStatus === "connecting" && NetworkService.connectingSSID === modelData.ssid) return "Connecting..."; - if (WifiService.connectionStatus === "invalid_password" && WifiService.connectingSSID === modelData.ssid) + if (NetworkService.connectionStatus === "invalid_password" && NetworkService.connectingSSID === modelData.ssid) return "Invalid password"; if (modelData.saved) return "Saved" + (modelData.secured ? " • Secured" : " • Open"); @@ -174,9 +174,9 @@ Column { } font.pixelSize: Theme.fontSizeSmall - 1 color: { - if (WifiService.connectionStatus === "connecting" && WifiService.connectingSSID === modelData.ssid) + if (NetworkService.connectionStatus === "connecting" && NetworkService.connectingSSID === modelData.ssid) return Theme.primary; - if (WifiService.connectionStatus === "invalid_password" && WifiService.connectingSSID === modelData.ssid) + if (NetworkService.connectionStatus === "invalid_password" && NetworkService.connectingSSID === modelData.ssid) return Theme.error; return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7); } @@ -253,7 +253,7 @@ Column { return; if (modelData.saved) { - WifiService.connectToWifi(modelData.ssid); + NetworkService.connectToWifi(modelData.ssid); } else if (modelData.secured) { if (wifiPasswordModalRef) { wifiPasswordModalRef.wifiPasswordSSID = modelData.ssid; @@ -261,7 +261,7 @@ Column { wifiPasswordModalRef.wifiPasswordModalVisible = true; } } else { - WifiService.connectToWifi(modelData.ssid); + NetworkService.connectToWifi(modelData.ssid); } } } diff --git a/Modules/ControlCenter/NetworkTab.qml b/Modules/ControlCenter/NetworkTab.qml index ab9b8428..77d3a157 100644 --- a/Modules/ControlCenter/NetworkTab.qml +++ b/Modules/ControlCenter/NetworkTab.qml @@ -21,10 +21,10 @@ Item { } // Explicitly reference both arrays to ensure reactivity - var allNetworks = WifiService.wifiNetworks; - var savedNetworks = WifiService.savedWifiNetworks; - var currentSSID = WifiService.currentWifiSSID; - var signalStrength = WifiService.wifiSignalStrength; + var allNetworks = NetworkService.wifiNetworks; + var savedNetworks = NetworkService.savedWifiNetworks; + var currentSSID = NetworkService.currentWifiSSID; + var signalStrength = NetworkService.wifiSignalStrength; var refreshTrigger = forceRefresh; // Force recalculation var networks = [...allNetworks]; @@ -58,7 +58,7 @@ Item { property int forceRefresh: 0 Connections { - target: WifiService + target: NetworkService function onNetworksUpdated() { forceRefresh++; } @@ -66,9 +66,9 @@ Item { // Auto-enable WiFi auto-refresh when network tab is visible Component.onCompleted: { - WifiService.autoRefreshEnabled = true; + NetworkService.autoRefreshEnabled = true; if (NetworkService.wifiEnabled) - WifiService.scanWifi(); + NetworkService.scanWifi(); // Start smart monitoring wifiMonitorTimer.start(); } @@ -199,8 +199,8 @@ Item { property bool triggered: false onTriggered: { NetworkService.refreshNetworkStatus(); - if (NetworkService.wifiEnabled && !WifiService.isScanning) { - WifiService.scanWifi(); + if (NetworkService.wifiEnabled && !NetworkService.isScanning) { + NetworkService.scanWifi(); } triggered = false; } @@ -218,13 +218,13 @@ Item { wifiMonitorTimer.start(); } else { // When WiFi is disabled, clear all cached WiFi data - WifiService.currentWifiSSID = ""; - WifiService.wifiSignalStrength = "excellent"; - WifiService.wifiNetworks = []; - WifiService.savedWifiNetworks = []; - WifiService.connectionStatus = ""; - WifiService.connectingSSID = ""; - WifiService.isScanning = false; + NetworkService.currentWifiSSID = ""; + NetworkService.wifiSignalStrength = "excellent"; + NetworkService.wifiNetworks = []; + NetworkService.savedWifiNetworks = []; + NetworkService.connectionStatus = ""; + NetworkService.connectingSSID = ""; + NetworkService.isScanning = false; NetworkService.refreshNetworkStatus(); // Stop monitoring when WiFi is off wifiMonitorTimer.stop(); @@ -240,8 +240,8 @@ Item { repeat: false onTriggered: { if (NetworkService.wifiEnabled && visible) { - if (!WifiService.isScanning) { - WifiService.scanWifi(); + if (!NetworkService.isScanning) { + NetworkService.scanWifi(); } else { // If still scanning, try again in a bit wifiRetryTimer.start(); @@ -257,9 +257,9 @@ Item { running: false repeat: false onTriggered: { - if (NetworkService.wifiEnabled && visible && WifiService.wifiNetworks.length === 0) { - if (!WifiService.isScanning) { - WifiService.scanWifi(); + if (NetworkService.wifiEnabled && visible && NetworkService.wifiNetworks.length === 0) { + if (!NetworkService.isScanning) { + NetworkService.scanWifi(); } } } @@ -288,13 +288,13 @@ Item { reason = "not connected to WiFi"; } // Also scan occasionally even when connected to keep networks fresh - else if (WifiService.wifiNetworks.length === 0) { + else if (NetworkService.wifiNetworks.length === 0) { shouldScan = true; reason = "no networks cached"; } - if (shouldScan && !WifiService.isScanning) { - WifiService.scanWifi(); + if (shouldScan && !NetworkService.isScanning) { + NetworkService.scanWifi(); } } } diff --git a/Modules/ProcessList/ProcessContextMenu.qml b/Modules/ProcessList/ProcessContextMenu.qml index 373948f8..75ca4f6e 100644 --- a/Modules/ProcessList/ProcessContextMenu.qml +++ b/Modules/ProcessList/ProcessContextMenu.qml @@ -1,80 +1,74 @@ - import QtQuick -import Quickshell +import QtQuick.Controls import Quickshell.Io -import Quickshell.Wayland import qs.Common import qs.Services -PanelWindow { - id: processContextMenuWindow +Popup { + id: processContextMenu property var processData: null - property bool menuVisible: false function show(x, y) { + if (!processContextMenu.parent && typeof Overlay !== "undefined" && Overlay.overlay) { + processContextMenu.parent = Overlay.overlay; + } + const menuWidth = 180; const menuHeight = menuColumn.implicitHeight + Theme.spacingS * 2; - const screenWidth = processContextMenuWindow.screen ? processContextMenuWindow.screen.width : 1920; - const screenHeight = processContextMenuWindow.screen ? processContextMenuWindow.screen.height : 1080; + const screenWidth = Screen.width; + const screenHeight = Screen.height; + let finalX = x; let finalY = y; - if (x + menuWidth > screenWidth - 20) + + if (x + menuWidth > screenWidth - 20) { finalX = x - menuWidth; - - if (y + menuHeight > screenHeight - 20) + } + if (y + menuHeight > screenHeight - 20) { finalY = y - menuHeight; + } - finalX = Math.max(20, finalX); - finalY = Math.max(20, finalY); - processContextMenu.x = finalX; - processContextMenu.y = finalY; - processContextMenuWindow.menuVisible = true; + processContextMenu.x = Math.max(20, finalX); + processContextMenu.y = Math.max(20, finalY); + open(); } - function hide() { - processContextMenuWindow.menuVisible = false; + width: 180 + height: menuColumn.implicitHeight + Theme.spacingS * 2 + padding: 0 + modal: false + closePolicy: Popup.CloseOnEscape + + onClosed: { + closePolicy = Popup.CloseOnEscape; + } + + onOpened: { + outsideClickTimer.start(); + } + + Timer { + id: outsideClickTimer + interval: 100 + onTriggered: { + processContextMenu.closePolicy = Popup.CloseOnEscape | Popup.CloseOnPressOutside; + } + } + + background: Rectangle { + color: "transparent" } - visible: menuVisible - color: "transparent" - WlrLayershell.layer: WlrLayershell.Overlay - WlrLayershell.exclusiveZone: -1 - WlrLayershell.keyboardFocus: WlrKeyboardFocus.None - - anchors { - top: true - left: true - right: true - bottom: true - } - - Rectangle { - id: processContextMenu - - width: 180 - height: menuColumn.implicitHeight + Theme.spacingS * 2 - radius: Theme.cornerRadiusLarge + contentItem: Rectangle { + id: menuContent color: Theme.popupBackground() + radius: Theme.cornerRadiusLarge border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) border.width: 1 - opacity: processContextMenuWindow.menuVisible ? 1 : 0 - scale: processContextMenuWindow.menuVisible ? 1 : 0.85 - - 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: menuColumn - anchors.fill: parent anchors.margins: Theme.spacingS spacing: 1 @@ -97,27 +91,17 @@ PanelWindow { MouseArea { id: copyPidArea - anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: { - if (processContextMenuWindow.processData) { - copyPidProcess.command = ["wl-copy", processContextMenuWindow.processData.pid.toString()]; + if (processContextMenu.processData) { + copyPidProcess.command = ["wl-copy", processContextMenu.processData.pid.toString()]; copyPidProcess.running = true; } - processContextMenuWindow.hide(); + processContextMenu.close(); } } - - Behavior on color { - ColorAnimation { - duration: Theme.shortDuration - easing.type: Theme.standardEasing - } - - } - } Rectangle { @@ -138,28 +122,18 @@ PanelWindow { MouseArea { id: copyNameArea - anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: { - if (processContextMenuWindow.processData) { - let processName = processContextMenuWindow.processData.displayName || processContextMenuWindow.processData.command; + if (processContextMenu.processData) { + let processName = processContextMenu.processData.displayName || processContextMenu.processData.command; copyNameProcess.command = ["wl-copy", processName]; copyNameProcess.running = true; } - processContextMenuWindow.hide(); + processContextMenu.close(); } } - - Behavior on color { - ColorAnimation { - duration: Theme.shortDuration - easing.type: Theme.standardEasing - } - - } - } Rectangle { @@ -174,7 +148,6 @@ PanelWindow { height: 1 color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2) } - } Rectangle { @@ -182,7 +155,7 @@ PanelWindow { height: 28 radius: Theme.cornerRadiusSmall color: killArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent" - enabled: processContextMenuWindow.processData && processContextMenuWindow.processData.pid > 1000 + enabled: processContextMenu.processData && processContextMenu.processData.pid > 1000 opacity: enabled ? 1 : 0.5 Text { @@ -197,28 +170,18 @@ PanelWindow { MouseArea { id: killArea - anchors.fill: parent hoverEnabled: true cursorShape: parent.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor enabled: parent.enabled onClicked: { - if (processContextMenuWindow.processData) { - killProcess.command = ["kill", processContextMenuWindow.processData.pid.toString()]; + if (processContextMenu.processData) { + killProcess.command = ["kill", processContextMenu.processData.pid.toString()]; killProcess.running = true; } - processContextMenuWindow.hide(); + processContextMenu.close(); } } - - Behavior on color { - ColorAnimation { - duration: Theme.shortDuration - easing.type: Theme.standardEasing - } - - } - } Rectangle { @@ -226,7 +189,7 @@ PanelWindow { height: 28 radius: Theme.cornerRadiusSmall color: forceKillArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent" - enabled: processContextMenuWindow.processData && processContextMenuWindow.processData.pid > 1000 + enabled: processContextMenu.processData && processContextMenu.processData.pid > 1000 opacity: enabled ? 1 : 0.5 Text { @@ -241,80 +204,40 @@ PanelWindow { MouseArea { id: forceKillArea - anchors.fill: parent hoverEnabled: true cursorShape: parent.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor enabled: parent.enabled onClicked: { - if (processContextMenuWindow.processData) { - forceKillProcess.command = ["kill", "-9", processContextMenuWindow.processData.pid.toString()]; + if (processContextMenu.processData) { + forceKillProcess.command = ["kill", "-9", processContextMenu.processData.pid.toString()]; forceKillProcess.running = true; } - processContextMenuWindow.hide(); + processContextMenu.close(); } } - - 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 - } - - } - - } - - MouseArea { - anchors.fill: parent - z: -1 - onClicked: { - processContextMenuWindow.menuVisible = false; } } Process { id: copyPidProcess - running: false } Process { id: copyNameProcess - running: false } Process { id: killProcess - running: false } Process { id: forceKillProcess - running: false } - -} + +} \ No newline at end of file diff --git a/Modules/ProcessList/ProcessListItem.qml b/Modules/ProcessList/ProcessListItem.qml index 4f7fd017..66e05db7 100644 --- a/Modules/ProcessList/ProcessListItem.qml +++ b/Modules/ProcessList/ProcessListItem.qml @@ -8,7 +8,6 @@ Rectangle { property var process: null property var contextMenu: null - property var processContextMenuWindow: null width: parent.width height: 40 @@ -29,15 +28,16 @@ Rectangle { if (process && process.pid > 0 && contextMenu) { contextMenu.processData = process; let globalPos = processMouseArea.mapToGlobal(mouse.x, mouse.y); - contextMenu.show(globalPos.x, globalPos.y); + let localPos = contextMenu.parent ? contextMenu.parent.mapFromGlobal(globalPos.x, globalPos.y) : globalPos; + contextMenu.show(localPos.x, localPos.y); } } } onPressAndHold: { - if (process && process.pid > 0 && processContextMenuWindow) { - processContextMenuWindow.processData = process; + if (process && process.pid > 0 && contextMenu) { + contextMenu.processData = process; let globalPos = processMouseArea.mapToGlobal(processMouseArea.width / 2, processMouseArea.height / 2); - processContextMenuWindow.show(globalPos.x, globalPos.y); + contextMenu.show(globalPos.x, globalPos.y); } } } @@ -188,10 +188,11 @@ Rectangle { hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: { - if (process && process.pid > 0 && processContextMenuWindow) { - processContextMenuWindow.processData = process; + if (process && process.pid > 0 && contextMenu) { + contextMenu.processData = process; let globalPos = menuButtonArea.mapToGlobal(menuButtonArea.width / 2, menuButtonArea.height); - processContextMenuWindow.show(globalPos.x, globalPos.y); + let localPos = contextMenu.parent ? contextMenu.parent.mapFromGlobal(globalPos.x, globalPos.y) : globalPos; + contextMenu.show(localPos.x, localPos.y); } } } @@ -207,4 +208,4 @@ Rectangle { } -} +} \ No newline at end of file diff --git a/Modules/ProcessList/ProcessListPopout.qml b/Modules/ProcessList/ProcessListPopout.qml index deb67dd9..682b013d 100644 --- a/Modules/ProcessList/ProcessListPopout.qml +++ b/Modules/ProcessList/ProcessListPopout.qml @@ -19,6 +19,10 @@ PanelWindow { function hide() { isVisible = false; + // Close any open context menus + if (processContextMenu.visible) { + processContextMenu.close(); + } } function show() { @@ -127,13 +131,13 @@ PanelWindow { ProcessListView { Layout.fillWidth: true Layout.fillHeight: true - contextMenu: processContextMenuWindow + contextMenu: processContextMenu } } } } ProcessContextMenu { - id: processContextMenuWindow + id: processContextMenu } } \ No newline at end of file diff --git a/Modules/ProcessList/ProcessListView.qml b/Modules/ProcessList/ProcessListView.qml index 45770eda..90e3aec5 100644 --- a/Modules/ProcessList/ProcessListView.qml +++ b/Modules/ProcessList/ProcessListView.qml @@ -5,7 +5,6 @@ import qs.Services Column { id: root - property var processContextMenuWindow: null property var contextMenu: null Item { @@ -130,8 +129,7 @@ Column { delegate: ProcessListItem { process: modelData contextMenu: root.contextMenu - processContextMenuWindow: root.contextMenu } } } -} +} \ No newline at end of file diff --git a/Modules/ProcessList/ProcessesTab.qml b/Modules/ProcessList/ProcessesTab.qml index ae6a0fd0..db00534a 100644 --- a/Modules/ProcessList/ProcessesTab.qml +++ b/Modules/ProcessList/ProcessesTab.qml @@ -2,12 +2,13 @@ import QtQuick import QtQuick.Layouts import qs.Common import qs.Services +import qs.Modules.ProcessList ColumnLayout { id: processesTab anchors.fill: parent spacing: Theme.spacingM - + property var contextMenu: null SystemOverview { @@ -17,6 +18,10 @@ ColumnLayout { ProcessListView { Layout.fillWidth: true Layout.fillHeight: true - contextMenu: processesTab.contextMenu + contextMenu: processesTab.contextMenu || localContextMenu } -} + + ProcessContextMenu { + id: localContextMenu + } +} \ No newline at end of file diff --git a/Modules/TopBar/AudioVisualization.qml b/Modules/TopBar/AudioVisualization.qml index 1a489035..3cf8ea67 100644 --- a/Modules/TopBar/AudioVisualization.qml +++ b/Modules/TopBar/AudioVisualization.qml @@ -8,7 +8,7 @@ import qs.Services Item { id: root - property var audioLevels: [0, 0, 0, 0] + property var audioLevels: [0, 0, 0, 0, 0, 0] readonly property MprisPlayer activePlayer: MprisController.activePlayer readonly property bool hasActiveMedia: activePlayer !== null property bool cavaAvailable: false @@ -41,10 +41,10 @@ Item { id: cavaProcess running: false - command: ["sh", "-c", `printf '[general]\nmode=normal\nframerate=30\nautosens=0\nsensitivity=50\nbars=4\n[output]\nmethod=raw\nraw_target=/dev/stdout\ndata_format=ascii\nchannels=mono\nmono_option=average\n[smoothing]\nnoise_reduction=20' | cava -p /dev/stdin`] + command: ["sh", "-c", `printf '[general]\nmode=normal\nframerate=25\nautosens=0\nsensitivity=30\nbars=6\nlower_cutoff_freq=50\nhigher_cutoff_freq=12000\n[output]\nmethod=raw\nraw_target=/dev/stdout\ndata_format=ascii\nchannels=mono\nmono_option=average\n[smoothing]\nnoise_reduction=35\nintegral=90\ngravity=95\nignore=2\nmonstercat=1.5' | cava -p /dev/stdin`] onRunningChanged: { if (!running) - root.audioLevels = [0, 0, 0, 0]; + root.audioLevels = [0, 0, 0, 0, 0, 0]; } @@ -57,8 +57,8 @@ Item { }).filter((p) => { return !isNaN(p); }); - if (points.length >= 4) - root.audioLevels = [points[0], points[1], points[2], points[3]]; + if (points.length >= 6) + root.audioLevels = [points[0], points[1], points[2], points[3], points[4], points[5]]; } } @@ -73,19 +73,19 @@ Item { interval: 100 repeat: true onTriggered: { - root.audioLevels = [Math.random() * 40 + 10, Math.random() * 60 + 20, Math.random() * 50 + 15, Math.random() * 35 + 20]; + root.audioLevels = [Math.random() * 40 + 10, Math.random() * 60 + 20, Math.random() * 50 + 15, Math.random() * 35 + 20, Math.random() * 45 + 15, Math.random() * 55 + 25]; } } Row { anchors.centerIn: parent - spacing: 2 + spacing: 1.5 Repeater { - model: 4 + model: 6 Rectangle { - width: 3 + width: 2 height: { if (root.activePlayer && root.activePlayer.playbackState === MprisPlaybackState.Playing && root.audioLevels.length > index) { const rawLevel = root.audioLevels[index] || 0; diff --git a/Modules/TopBar/ControlCenterButton.qml b/Modules/TopBar/ControlCenterButton.qml index d4145267..b5801e19 100644 --- a/Modules/TopBar/ControlCenterButton.qml +++ b/Modules/TopBar/ControlCenterButton.qml @@ -43,7 +43,7 @@ Rectangle { if (NetworkService.networkStatus === "ethernet") return "lan"; else if (NetworkService.networkStatus === "wifi") - return getWiFiSignalIcon(WifiService.wifiSignalStrength); + return getWiFiSignalIcon(NetworkService.wifiSignalStrength); else return "wifi_off"; } diff --git a/Modules/TopBar/Media.qml b/Modules/TopBar/Media.qml index 37ddbb00..23c34568 100644 --- a/Modules/TopBar/Media.qml +++ b/Modules/TopBar/Media.qml @@ -85,8 +85,6 @@ Rectangle { spacing: Theme.spacingXS AudioVisualization { - width: 20 - height: Theme.iconSize anchors.verticalCenter: parent.verticalCenter } diff --git a/Modules/TopBar/TopBar.qml b/Modules/TopBar/TopBar.qml index 0cf4c86e..76630074 100644 --- a/Modules/TopBar/TopBar.qml +++ b/Modules/TopBar/TopBar.qml @@ -1,4 +1,3 @@ -import "../../Common/Utilities.js" as Utils import QtQuick import QtQuick.Controls import QtQuick.Effects @@ -277,7 +276,7 @@ PanelWindow { controlCenterPopout.controlCenterVisible = !controlCenterPopout.controlCenterVisible; if (controlCenterPopout.controlCenterVisible) { if (NetworkService.wifiEnabled) - WifiService.scanWifi(); + NetworkService.scanWifi(); } } diff --git a/Services/NetworkService.qml b/Services/NetworkService.qml index 94b08f1d..f93d1273 100644 --- a/Services/NetworkService.qml +++ b/Services/NetworkService.qml @@ -21,6 +21,27 @@ Singleton { property bool changingPreference: false property string targetPreference: "" // Track what preference we're switching to + // WiFi-specific properties + property string currentWifiSSID: "" + property string wifiSignalStrength: "excellent" // "excellent", "good", "fair", "poor" + property var wifiNetworks: [] + property var savedWifiNetworks: [] + property bool isScanning: false + property string connectionStatus: "" // "connecting", "connected", "failed", "invalid_password", "" + property string connectingSSID: "" + property string lastConnectionError: "" + property bool passwordDialogShouldReopen: false + property bool autoRefreshEnabled: false + property string wifiPassword: "" + property string forgetSSID: "" + + // Network info properties + property string networkInfoSSID: "" + property string networkInfoDetails: "" + property bool networkInfoLoading: false + + signal networksUpdated() + // Load saved preference on startup Component.onCompleted: { // Load preference from Prefs system @@ -29,7 +50,7 @@ Singleton { // Trigger immediate WiFi info update if WiFi is connected and enabled if (root.networkStatus === "wifi" && root.wifiEnabled) { - WifiService.updateCurrentWifiInfo() + updateCurrentWifiInfo() } } @@ -68,7 +89,7 @@ Singleton { root.networkStatus = "wifi" console.log("User prefers WiFi, setting status to wifi") if (root.wifiEnabled) { - WifiService.updateCurrentWifiInfo() + updateCurrentWifiInfo() } } else if (root.userPreference === "ethernet") { root.networkStatus = "ethernet" @@ -82,7 +103,7 @@ Singleton { console.log("Only WiFi connected, setting status to wifi") // Trigger WiFi SSID update if (root.wifiEnabled) { - WifiService.updateCurrentWifiInfo() + updateCurrentWifiInfo() } } else if (hasEthernet || ethernetCableUp) { root.networkStatus = "ethernet" @@ -145,7 +166,7 @@ Singleton { console.log("WiFi interface has default route, setting status to wifi") // Trigger WiFi SSID update if (root.wifiEnabled) { - WifiService.updateCurrentWifiInfo() + updateCurrentWifiInfo() } } else if (defaultInterface.startsWith("en") || defaultInterface.includes("eth")) { root.networkStatus = "ethernet" @@ -280,41 +301,7 @@ Singleton { } } - Process { - id: wifiDeviceConnector - 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) { - console.log("WiFi device connect result:", exitCode) - delayedRefreshNetworkStatus() - } - - stderr: SplitParser { - splitMarker: "\n" - onRead: function(data) { - console.log("WiFi device connect stderr:", data) - } - } - } - 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 @@ -381,27 +368,9 @@ Singleton { 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 - } } } - function switchToWifi() { - console.log("Switching to WiFi") - // Disconnect ethernet first, then try to connect to a known WiFi network - wifiSwitcher.running = true - } - - function switchToEthernet() { - console.log("Switching to Ethernet") - // Disconnect WiFi first, then connect ethernet - ethernetSwitcher.running = true - } - function toggleWifiRadio() { if (root.wifiToggling) return @@ -436,15 +405,530 @@ Singleton { } } - - function connectToWifiAndSetPreference(ssid, password) { - console.log("Connecting to WiFi and setting preference:", ssid) - if (!root.wifiEnabled) { - console.log("WiFi is disabled, cannot connect to network") + // WiFi-specific functions + function scanWifi() { + if (root.isScanning) return - } - root.userPreference = "wifi" - Prefs.setNetworkPreference("wifi") - WifiService.connectToWifiWithPassword(ssid, password) + + root.isScanning = true + wifiScanner.running = true + savedWifiScanner.running = true + currentWifiInfo.running = true + fallbackTimer.start() } + + function connectToWifi(ssid) { + console.log("Connecting to WiFi:", ssid) + root.connectionStatus = "connecting" + root.connectingSSID = ssid + ToastService.showInfo("Connecting to " + ssid + "...") + wifiConnector.running = true + } + + function connectToWifiWithPassword(ssid, password) { + console.log("Connecting to WiFi with password:", ssid) + root.connectionStatus = "connecting" + root.connectingSSID = ssid + root.wifiPassword = password + root.lastConnectionError = "" + root.passwordDialogShouldReopen = false + ToastService.showInfo("Connecting to " + ssid + "...") + wifiPasswordConnector.running = true + } + + function disconnectWifi() { + console.log("Disconnecting from current WiFi network") + wifiDisconnector.running = true + } + + function forgetWifiNetwork(ssid) { + console.log("Forgetting WiFi network:", ssid) + root.forgetSSID = ssid + wifiForget.running = true + } + + function fetchNetworkInfo(ssid) { + console.log("Fetching network info for:", ssid) + root.networkInfoSSID = ssid + root.networkInfoLoading = true + root.networkInfoDetails = "Loading network information..." + wifiInfoFetcher.running = true + } + + function updateCurrentWifiInfo() { + currentWifiInfo.running = true + } + + function enableWifiDevice() { + console.log("Enabling WiFi device...") + wifiDeviceEnabler.running = true + } + + function connectToWifiAndSetPreference(ssid, password) { + console.log("Connecting to WiFi and setting network preference:", ssid) + connectToWifiWithPassword(ssid, password) + setNetworkPreference("wifi") + } + + // WiFi Process Components + Process { + id: currentWifiInfo + command: ["bash", "-c", "nmcli -t -f ACTIVE,SSID,SIGNAL dev wifi | grep '^yes' | head -1"] + running: false + + stdout: SplitParser { + splitMarker: "\n" + onRead: (data) => { + if (data.trim()) { + let parts = data.split(":") + if (parts.length >= 3 && parts[1].trim() !== "") { + root.currentWifiSSID = parts[1].trim() + let signal = parseInt(parts[2]) || 100 + if (signal >= 75) + root.wifiSignalStrength = "excellent" + else if (signal >= 50) + root.wifiSignalStrength = "good" + else if (signal >= 25) + root.wifiSignalStrength = "fair" + else + root.wifiSignalStrength = "poor" + console.log("Active WiFi:", root.currentWifiSSID, "Signal:", signal + "%") + } + } + } + } + } + + Timer { + id: fallbackTimer + interval: 5000 + onTriggered: { + root.isScanning = false + } + } + + Timer { + id: statusResetTimer + interval: 3000 + onTriggered: { + root.connectionStatus = "" + root.connectingSSID = "" + } + } + + Timer { + id: autoRefreshTimer + interval: 20000 + running: root.autoRefreshEnabled + repeat: true + onTriggered: scanWifi() + } + + Process { + id: wifiScanner + + command: ["nmcli", "-t", "-f", "SSID,SIGNAL,SECURITY", "dev", "wifi"] + running: false + + stdout: StdioCollector { + onStreamFinished: { + if (text.trim()) { + let networks = []; + let lines = text.trim().split('\n'); + for (let line of lines) { + let parts = line.split(':'); + if (parts.length >= 3 && parts[0].trim() !== "") { + let ssid = parts[0].trim(); + let signal = parseInt(parts[1]) || 0; + let security = parts[2].trim(); + // Skip duplicates + if (!networks.find((n) => { + return n.ssid === ssid; + })) { + // Check if this network is saved + let isSaved = root.savedWifiNetworks.some((saved) => { + return saved.ssid === ssid; + }); + networks.push({ + "ssid": ssid, + "signal": signal, + "secured": security !== "", + "connected": ssid === root.currentWifiSSID, + "saved": isSaved, + "signalStrength": signal >= 75 ? "excellent" : signal >= 50 ? "good" : signal >= 25 ? "fair" : "poor" + }); + } + } + } + // Sort by signal strength + networks.sort((a, b) => { + return b.signal - a.signal; + }); + root.wifiNetworks = networks; + console.log("Found", networks.length, "WiFi networks"); + // Stop scanning once we have results + if (networks.length > 0) { + root.isScanning = false; + fallbackTimer.stop(); + } + } + } + } + + } + + Process { + id: savedWifiScanner + + command: ["nmcli", "-t", "-f", "NAME", "connection", "show"] + running: false + + stdout: StdioCollector { + onStreamFinished: { + if (text.trim()) { + let saved = []; + let lines = text.trim().split('\n'); + for (let line of lines) { + let connectionName = line.trim(); + if (connectionName && !connectionName.includes("ethernet") && !connectionName.includes("lo") && !connectionName.includes("Wired") && !connectionName.toLowerCase().includes("eth")) + saved.push({ + "ssid": connectionName, + "saved": true + }); + + } + root.savedWifiNetworks = saved; + console.log("Found", saved.length, "saved WiFi networks"); + } + } + } + + } + + // 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) + setNetworkPreference("wifi") + 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) + setNetworkPreference("wifi") + 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 = "" + 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 = ""; + 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); + } + } + } + + // WiFi Device Enabler Process + Process { + id: wifiDeviceEnabler + 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: (exitCode) => { + console.log("WiFi device enable result:", exitCode) + if (exitCode === 0) { + console.log("WiFi device enabled successfully") + ToastService.showInfo("WiFi enabled") + } else { + console.log("Failed to enable WiFi device") + ToastService.showError("Failed to enable WiFi") + } + delayedRefreshNetworkStatus() + } + + stderr: SplitParser { + splitMarker: "\\n" + onRead: (data) => { + console.log("WiFi device enable stderr:", data) + } + } + } + } \ No newline at end of file diff --git a/Services/WifiService.qml b/Services/WifiService.qml deleted file mode 100644 index 412ccf03..00000000 --- a/Services/WifiService.qml +++ /dev/null @@ -1,528 +0,0 @@ -pragma Singleton -pragma ComponentBehavior: Bound - -import QtQuick -import Quickshell -import Quickshell.Io - -Singleton { - id: root - - property string currentWifiSSID: "" - property string wifiSignalStrength: "excellent" // "excellent", "good", "fair", "poor" - property var wifiNetworks: [] - property var savedWifiNetworks: [] - property bool isScanning: false - property string connectionStatus: "" // "connecting", "connected", "failed", "invalid_password", "" - property string connectingSSID: "" - property string lastConnectionError: "" - property bool passwordDialogShouldReopen: false - // Auto-refresh timer for when control center is open - property bool autoRefreshEnabled: false - - signal networksUpdated() - - // Network info properties - property string networkInfoSSID: "" - property string networkInfoDetails: "" - property bool networkInfoLoading: false - - function scanWifi() { - if (root.isScanning) - return ; - - root.isScanning = true; - wifiScanner.running = true; - savedWifiScanner.running = true; - currentWifiInfo.running = true; - fallbackTimer.start(); - } - - function connectToWifi(ssid) { - console.log("Connecting to WiFi:", ssid); - root.connectionStatus = "connecting"; - root.connectingSSID = ssid; - ToastService.showInfo("Connecting to " + ssid + "..."); - wifiConnector.running = true; - } - - property string wifiPassword: "" - - function connectToWifiWithPassword(ssid, password) { - console.log("Connecting to WiFi with password:", ssid); - root.connectionStatus = "connecting"; - root.connectingSSID = ssid; - root.wifiPassword = password; - root.lastConnectionError = ""; - root.passwordDialogShouldReopen = false; - ToastService.showInfo("Connecting to " + ssid + "..."); - wifiPasswordConnector.running = true; - } - - function disconnectWifi() { - console.log("Disconnecting from current WiFi network"); - wifiDisconnector.running = true; - } - - property string forgetSSID: "" - - function forgetWifiNetwork(ssid) { - console.log("Forgetting WiFi network:", ssid); - root.forgetSSID = ssid; - wifiForget.running = true; - } - - function fetchNetworkInfo(ssid) { - console.log("Fetching network info for:", ssid); - root.networkInfoSSID = ssid; - root.networkInfoLoading = true; - root.networkInfoDetails = "Loading network information..."; - wifiInfoFetcher.running = true; - } - - function updateCurrentWifiInfo() { - currentWifiInfo.running = true; - } - - - Process { - id: currentWifiInfo - - command: ["bash", "-c", "nmcli -t -f ACTIVE,SSID,SIGNAL dev wifi | grep '^yes' | head -1"] - running: false - - stdout: SplitParser { - splitMarker: "\n" - onRead: (data) => { - if (data.trim()) { - let parts = data.split(":"); - if (parts.length >= 3 && parts[1].trim() !== "") { - root.currentWifiSSID = parts[1].trim(); - let signal = parseInt(parts[2]) || 100; - if (signal >= 75) - root.wifiSignalStrength = "excellent"; - else if (signal >= 50) - root.wifiSignalStrength = "good"; - else if (signal >= 25) - root.wifiSignalStrength = "fair"; - else - root.wifiSignalStrength = "poor"; - console.log("Active WiFi:", root.currentWifiSSID, "Signal:", signal + "%"); - } - } - } - } - - } - - Process { - id: wifiScanner - - command: ["nmcli", "-t", "-f", "SSID,SIGNAL,SECURITY", "dev", "wifi"] - running: false - - stdout: StdioCollector { - onStreamFinished: { - if (text.trim()) { - let networks = []; - let lines = text.trim().split('\n'); - for (let line of lines) { - let parts = line.split(':'); - if (parts.length >= 3 && parts[0].trim() !== "") { - let ssid = parts[0].trim(); - let signal = parseInt(parts[1]) || 0; - let security = parts[2].trim(); - // Skip duplicates - if (!networks.find((n) => { - return n.ssid === ssid; - })) { - // Check if this network is saved - let isSaved = root.savedWifiNetworks.some((saved) => { - return saved.ssid === ssid; - }); - networks.push({ - "ssid": ssid, - "signal": signal, - "secured": security !== "", - "connected": ssid === root.currentWifiSSID, - "saved": isSaved, - "signalStrength": signal >= 75 ? "excellent" : signal >= 50 ? "good" : signal >= 25 ? "fair" : "poor" - }); - } - } - } - // Sort by signal strength - networks.sort((a, b) => { - return b.signal - a.signal; - }); - root.wifiNetworks = networks; - console.log("Found", networks.length, "WiFi networks"); - // Stop scanning once we have results - if (networks.length > 0) { - root.isScanning = false; - fallbackTimer.stop(); - } - } - } - } - - } - - Process { - id: savedWifiScanner - - command: ["nmcli", "-t", "-f", "NAME", "connection", "show"] - running: false - - stdout: StdioCollector { - onStreamFinished: { - if (text.trim()) { - let saved = []; - let lines = text.trim().split('\n'); - for (let line of lines) { - let connectionName = line.trim(); - if (connectionName && !connectionName.includes("ethernet") && !connectionName.includes("lo") && !connectionName.includes("Wired") && !connectionName.toLowerCase().includes("eth")) - saved.push({ - "ssid": connectionName, - "saved": true - }); - - } - root.savedWifiNetworks = saved; - console.log("Found", saved.length, "saved WiFi networks"); - } - } - } - - } - - - Timer { - id: fallbackTimer - - interval: 5000 - onTriggered: { - root.isScanning = false; - } - } - - Timer { - id: statusResetTimer - - interval: 3000 - onTriggered: { - root.connectionStatus = ""; - root.connectingSSID = ""; - } - } - - Timer { - id: autoRefreshTimer - - interval: 20000 - running: root.autoRefreshEnabled - repeat: true - 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); - } - } - } - -} diff --git a/Widgets/DankDropdown.qml b/Widgets/DankDropdown.qml index df9ee662..7c2b47a1 100644 --- a/Widgets/DankDropdown.qml +++ b/Widgets/DankDropdown.qml @@ -1,7 +1,5 @@ import QtQuick import QtQuick.Controls -import Quickshell -import Quickshell.Wayland import qs.Common import qs.Widgets @@ -20,11 +18,11 @@ Rectangle { height: 60 radius: Theme.cornerRadius color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08) - // Global keyboard handler for escape key - Keys.onEscapePressed: { - if (dropdownMenu.visible) - dropdownMenu.visible = false; + onVisibleChanged: { + if (!visible && dropdownMenu.visible) { + dropdownMenu.close(); + } } Column { @@ -50,7 +48,6 @@ Rectangle { wrapMode: Text.WordWrap width: parent.width } - } Rectangle { @@ -66,115 +63,81 @@ Rectangle { border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2) border.width: 1 - Row { - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: Theme.spacingM - anchors.rightMargin: Theme.spacingS - - Row { - anchors.verticalCenter: parent.verticalCenter - spacing: Theme.spacingS - width: parent.width - 24 - - DankIcon { - name: { - var currentIndex = root.options.indexOf(root.currentValue); - return root.optionIcons.length > currentIndex && currentIndex >= 0 ? root.optionIcons[currentIndex] : ""; - } - size: 18 - color: Theme.surfaceVariantText - anchors.verticalCenter: parent.verticalCenter - visible: { - var currentIndex = root.options.indexOf(root.currentValue); - return root.optionIcons.length > currentIndex && currentIndex >= 0 && root.optionIcons[currentIndex] !== ""; - } - } - - Text { - text: root.currentValue - font.pixelSize: Theme.fontSizeMedium - color: Theme.surfaceText - anchors.verticalCenter: parent.verticalCenter - width: parent.parent.width - (visible ? 24 + Theme.spacingS : 24) - (parent.children[0].visible ? 18 + Theme.spacingS : 0) - elide: Text.ElideRight - } - } - - DankIcon { - name: "expand_more" - size: 20 - color: Theme.surfaceVariantText - anchors.verticalCenter: parent.verticalCenter - } - - } - MouseArea { id: dropdownArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor - onPressed: (mouse) => { - mouse.accepted = true; - if (!dropdownMenu.visible) { - dropdownMenu.updatePosition(); - dropdownMenu.visible = true; + + onClicked: { + if (dropdownMenu.visible) { + dropdownMenu.close(); } else { - dropdownMenu.visible = false; + var pos = dropdown.mapToItem(Overlay.overlay, 0, dropdown.height + 4); + dropdownMenu.x = pos.x; + dropdownMenu.y = pos.y; + dropdownMenu.open(); } } } - } + // Use a Row for the left-aligned content (icon + text) + Row { + id: contentRow + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: Theme.spacingM + spacing: Theme.spacingS - // Integrated dropdown menu with full-screen overlay - PanelWindow { - id: dropdownMenu + DankIcon { + name: { + var currentIndex = root.options.indexOf(root.currentValue); + return root.optionIcons.length > currentIndex && currentIndex >= 0 ? root.optionIcons[currentIndex] : ""; + } + size: 18 + color: Theme.surfaceVariantText + visible: name !== "" + } - property int targetX: 0 - property int targetY: 0 - - function updatePosition() { - var globalPos = dropdown.mapToGlobal(0, 0); - targetX = globalPos.x; - targetY = globalPos.y + dropdown.height + 4; - } - - visible: false - WlrLayershell.layer: WlrLayershell.Overlay - WlrLayershell.exclusiveZone: -1 - WlrLayershell.keyboardFocus: WlrKeyboardFocus.None - color: "transparent" - - anchors { - top: true - left: true - right: true - bottom: true - } - - // Background click interceptor (invisible) - MouseArea { - anchors.fill: parent - z: -1 - onPressed: { - dropdownMenu.visible = false; + Text { + text: root.currentValue + font.pixelSize: Theme.fontSizeMedium + color: Theme.surfaceText + // Constrain width for proper eliding + width: dropdown.width - contentRow.x - expandIcon.width - Theme.spacingM - Theme.spacingS + elide: Text.ElideRight } } - // Dropdown menu content - Rectangle { - x: dropdownMenu.targetX - y: dropdownMenu.targetY - width: 180 - height: Math.min(200, root.options.length * 36 + 16) - radius: Theme.cornerRadiusSmall + // Anchor the expand icon to the right, outside of the Row + DankIcon { + id: expandIcon + name: "expand_more" + size: 20 + color: Theme.surfaceVariantText + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.rightMargin: Theme.spacingS + } + } + + Popup { + id: dropdownMenu + parent: Overlay.overlay + + width: 180 + height: Math.min(200, root.options.length * 36 + 16) + + padding: 0 + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent + background: Rectangle { color: "transparent" } + + contentItem: Rectangle { color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 1) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3) border.width: 1 + radius: Theme.cornerRadiusSmall ScrollView { anchors.fill: parent @@ -201,8 +164,7 @@ Rectangle { name: root.optionIcons.length > index ? root.optionIcons[index] : "" size: 18 color: root.currentValue === modelData ? Theme.primary : Theme.surfaceVariantText - anchors.verticalCenter: parent.verticalCenter - visible: root.optionIcons.length > index && root.optionIcons[index] !== "" + visible: name !== "" } Text { @@ -216,25 +178,18 @@ Rectangle { MouseArea { id: optionArea - anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor - onPressed: { + onClicked: { root.currentValue = modelData; root.valueChanged(modelData); - dropdownMenu.visible = false; + dropdownMenu.close(); } } - } - } - } - } - } - -} +} \ No newline at end of file