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

Merge branch 'master' of github.com:bbedward/DankMaterialShell

This commit is contained in:
purian23
2025-07-23 18:48:39 -04:00
22 changed files with 789 additions and 1039 deletions

View File

@@ -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()
}

View File

@@ -19,7 +19,7 @@ DankModal {
networkSSID = ssid; networkSSID = ssid;
networkData = data; networkData = data;
networkInfoModalVisible = true; networkInfoModalVisible = true;
WifiService.fetchNetworkInfo(ssid); NetworkService.fetchNetworkInfo(ssid);
} }
function hideDialog() { function hideDialog() {
@@ -117,7 +117,7 @@ DankModal {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingM 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 font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
wrapMode: Text.WordWrap wrapMode: Text.WordWrap

View File

@@ -27,6 +27,10 @@ DankModal {
function hide() { function hide() {
processListModal.visible = false; processListModal.visible = false;
SystemMonitorService.enableDetailedMonitoring(false); SystemMonitorService.enableDetailedMonitoring(false);
// Close any open context menus
if (processContextMenu.visible) {
processContextMenu.close();
}
} }
function toggle() { function toggle() {
@@ -276,7 +280,7 @@ DankModal {
Component { Component {
id: processesTabComponent id: processesTabComponent
ProcessesTab { ProcessesTab {
contextMenu: processContextMenuWindow contextMenu: processContextMenu
} }
} }
@@ -293,7 +297,7 @@ DankModal {
ProcessContextMenu { ProcessContextMenu {
id: processContextMenuWindow id: processContextMenu
} }
IpcHandler { IpcHandler {

View File

@@ -28,15 +28,15 @@ DankModal {
// Auto-reopen dialog on invalid password // Auto-reopen dialog on invalid password
Connections { Connections {
function onPasswordDialogShouldReopenChanged() { function onPasswordDialogShouldReopenChanged() {
if (WifiService.passwordDialogShouldReopen && WifiService.connectingSSID !== "") { if (NetworkService.passwordDialogShouldReopen && NetworkService.connectingSSID !== "") {
wifiPasswordSSID = WifiService.connectingSSID; wifiPasswordSSID = NetworkService.connectingSSID;
wifiPasswordInput = ""; wifiPasswordInput = "";
wifiPasswordModalVisible = true; wifiPasswordModalVisible = true;
WifiService.passwordDialogShouldReopen = false; NetworkService.passwordDialogShouldReopen = false;
} }
} }
target: WifiService target: NetworkService
} }
content: Component { content: Component {
@@ -111,7 +111,7 @@ DankModal {
wifiPasswordInput = text; wifiPasswordInput = text;
} }
onAccepted: { onAccepted: {
WifiService.connectToWifiWithPassword(wifiPasswordSSID, passwordInput.text); NetworkService.connectToWifiWithPassword(wifiPasswordSSID, passwordInput.text);
wifiPasswordModalVisible = false; wifiPasswordModalVisible = false;
wifiPasswordInput = ""; wifiPasswordInput = "";
passwordInput.text = ""; passwordInput.text = "";
@@ -245,7 +245,7 @@ DankModal {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
enabled: parent.enabled enabled: parent.enabled
onClicked: { onClicked: {
WifiService.connectToWifiWithPassword(wifiPasswordSSID, passwordInput.text); NetworkService.connectToWifiWithPassword(wifiPasswordSSID, passwordInput.text);
wifiPasswordModalVisible = false; wifiPasswordModalVisible = false;
wifiPasswordInput = ""; wifiPasswordInput = "";
passwordInput.text = ""; passwordInput.text = "";

View File

@@ -85,7 +85,6 @@ PanelWindow {
x: Theme.spacingL x: Theme.spacingL
y: Theme.barHeight + Theme.spacingXS y: Theme.barHeight + Theme.spacingXS
// GPU-accelerated scale + opacity animation
opacity: appDrawerPopout.isVisible ? 1 : 0 opacity: appDrawerPopout.isVisible ? 1 : 0
scale: appDrawerPopout.isVisible ? 1 : 0.9 scale: appDrawerPopout.isVisible ? 1 : 0.9
@@ -249,13 +248,22 @@ PanelWindow {
} }
} }
Connections { Component.onCompleted: {
function onVisibleChanged() { if (appDrawerPopout.isVisible) {
if (appDrawerPopout.visible)
searchField.forceActiveFocus(); searchField.forceActiveFocus();
else }
}
Connections {
function onIsVisibleChanged() {
if (appDrawerPopout.isVisible) {
Qt.callLater(function() {
searchField.forceActiveFocus();
});
} else {
searchField.clearFocus(); searchField.clearFocus();
} }
}
target: appDrawerPopout target: appDrawerPopout
} }

View File

@@ -23,7 +23,7 @@ PanelWindow {
visible: controlCenterVisible visible: controlCenterVisible
onVisibleChanged: { onVisibleChanged: {
// Enable/disable WiFi auto-refresh based on control center visibility // 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 // Stop bluetooth scanning when control center is closed
if (!visible && BluetoothService.adapter && BluetoothService.adapter.discovering) if (!visible && BluetoothService.adapter && BluetoothService.adapter.discovering)
BluetoothService.adapter.discovering = false; BluetoothService.adapter.discovering = false;

View File

@@ -49,8 +49,8 @@ Rectangle {
name: { name: {
if (!NetworkService.wifiEnabled) { if (!NetworkService.wifiEnabled) {
return "wifi_off"; return "wifi_off";
} else if (WifiService.currentWifiSSID !== "") { } else if (NetworkService.currentWifiSSID !== "") {
return getWiFiSignalIcon(WifiService.wifiSignalStrength); return getWiFiSignalIcon(NetworkService.wifiSignalStrength);
} else { } else {
return "wifi"; return "wifi";
} }
@@ -64,8 +64,8 @@ Rectangle {
text: { text: {
if (!NetworkService.wifiEnabled) { if (!NetworkService.wifiEnabled) {
return "WiFi is off"; return "WiFi is off";
} else if (NetworkService.wifiEnabled && WifiService.currentWifiSSID) { } else if (NetworkService.wifiEnabled && NetworkService.currentWifiSSID) {
return WifiService.currentWifiSSID || "Connected"; return NetworkService.currentWifiSSID || "Connected";
} else { } else {
return "Not Connected"; return "Not Connected";
} }
@@ -82,7 +82,7 @@ Rectangle {
text: { text: {
if (!NetworkService.wifiEnabled) { if (!NetworkService.wifiEnabled) {
return "Turn on WiFi to see networks"; return "Turn on WiFi to see networks";
} else if (NetworkService.wifiEnabled && WifiService.currentWifiSSID) { } else if (NetworkService.wifiEnabled && NetworkService.currentWifiSSID) {
return NetworkService.wifiIP || "Connected"; return NetworkService.wifiIP || "Connected";
} else { } else {
return "Select a network below"; return "Select a network below";
@@ -130,13 +130,13 @@ Rectangle {
onClicked: { onClicked: {
if (NetworkService.wifiEnabled) { if (NetworkService.wifiEnabled) {
// When turning WiFi off, clear all cached WiFi data // When turning WiFi off, clear all cached WiFi data
WifiService.currentWifiSSID = ""; NetworkService.currentWifiSSID = "";
WifiService.wifiSignalStrength = "excellent"; NetworkService.wifiSignalStrength = "excellent";
WifiService.wifiNetworks = []; NetworkService.wifiNetworks = [];
WifiService.savedWifiNetworks = []; NetworkService.savedWifiNetworks = [];
WifiService.connectionStatus = ""; NetworkService.connectionStatus = "";
WifiService.connectingSSID = ""; NetworkService.connectingSSID = "";
WifiService.isScanning = false; NetworkService.isScanning = false;
NetworkService.refreshNetworkStatus(); NetworkService.refreshNetworkStatus();
} }
NetworkService.toggleWifiRadio(); NetworkService.toggleWifiRadio();

View File

@@ -118,10 +118,10 @@ Rectangle {
onClicked: { onClicked: {
if (wifiContextMenuWindow.networkData) { if (wifiContextMenuWindow.networkData) {
if (wifiContextMenuWindow.networkData.connected) { if (wifiContextMenuWindow.networkData.connected) {
WifiService.disconnectWifi(); NetworkService.disconnectWifi();
} else { } else {
if (wifiContextMenuWindow.networkData.saved) { if (wifiContextMenuWindow.networkData.saved) {
WifiService.connectToWifi(wifiContextMenuWindow.networkData.ssid); NetworkService.connectToWifi(wifiContextMenuWindow.networkData.ssid);
} else if (wifiContextMenuWindow.networkData.secured) { } else if (wifiContextMenuWindow.networkData.secured) {
if (wifiPasswordModalRef) { if (wifiPasswordModalRef) {
wifiPasswordModalRef.wifiPasswordSSID = wifiContextMenuWindow.networkData.ssid; wifiPasswordModalRef.wifiPasswordSSID = wifiContextMenuWindow.networkData.ssid;
@@ -129,7 +129,7 @@ Rectangle {
wifiPasswordModalRef.wifiPasswordModalVisible = true; wifiPasswordModalRef.wifiPasswordModalVisible = true;
} }
} else { } else {
WifiService.connectToWifi(wifiContextMenuWindow.networkData.ssid); NetworkService.connectToWifi(wifiContextMenuWindow.networkData.ssid);
} }
} }
} }
@@ -198,7 +198,7 @@ Rectangle {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (wifiContextMenuWindow.networkData) { if (wifiContextMenuWindow.networkData) {
WifiService.forgetWifiNetwork(wifiContextMenuWindow.networkData.ssid); NetworkService.forgetWifiNetwork(wifiContextMenuWindow.networkData.ssid);
} }
wifiContextMenuWindow.hide(); wifiContextMenuWindow.hide();
} }

View File

@@ -55,7 +55,7 @@ Column {
width: 28 width: 28
height: 28 height: 28
radius: 14 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 { DankIcon {
id: refreshIconSpan id: refreshIconSpan
@@ -63,12 +63,12 @@ Column {
name: "refresh" name: "refresh"
size: Theme.iconSize - 6 size: Theme.iconSize - 6
color: refreshAreaSpan.containsMouse ? Theme.primary : Theme.surfaceText color: refreshAreaSpan.containsMouse ? Theme.primary : Theme.surfaceText
rotation: WifiService.isScanning ? refreshIconSpan.rotation : 0 rotation: NetworkService.isScanning ? refreshIconSpan.rotation : 0
RotationAnimation { RotationAnimation {
target: refreshIconSpan target: refreshIconSpan
property: "rotation" property: "rotation"
running: WifiService.isScanning running: NetworkService.isScanning
from: 0 from: 0
to: 360 to: 360
duration: 1000 duration: 1000
@@ -89,10 +89,10 @@ Column {
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (!WifiService.isScanning) { if (!NetworkService.isScanning) {
// Immediate visual feedback // Immediate visual feedback
refreshIconSpan.rotation += 30; refreshIconSpan.rotation += 30;
WifiService.scanWifi(); NetworkService.scanWifi();
} }
} }
} }
@@ -164,9 +164,9 @@ Column {
text: { text: {
if (modelData.connected) if (modelData.connected)
return "Connected"; return "Connected";
if (WifiService.connectionStatus === "connecting" && WifiService.connectingSSID === modelData.ssid) if (NetworkService.connectionStatus === "connecting" && NetworkService.connectingSSID === modelData.ssid)
return "Connecting..."; return "Connecting...";
if (WifiService.connectionStatus === "invalid_password" && WifiService.connectingSSID === modelData.ssid) if (NetworkService.connectionStatus === "invalid_password" && NetworkService.connectingSSID === modelData.ssid)
return "Invalid password"; return "Invalid password";
if (modelData.saved) if (modelData.saved)
return "Saved" + (modelData.secured ? " • Secured" : " • Open"); return "Saved" + (modelData.secured ? " • Secured" : " • Open");
@@ -174,9 +174,9 @@ Column {
} }
font.pixelSize: Theme.fontSizeSmall - 1 font.pixelSize: Theme.fontSizeSmall - 1
color: { color: {
if (WifiService.connectionStatus === "connecting" && WifiService.connectingSSID === modelData.ssid) if (NetworkService.connectionStatus === "connecting" && NetworkService.connectingSSID === modelData.ssid)
return Theme.primary; 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 Theme.error;
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7); return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7);
} }
@@ -253,7 +253,7 @@ Column {
return; return;
if (modelData.saved) { if (modelData.saved) {
WifiService.connectToWifi(modelData.ssid); NetworkService.connectToWifi(modelData.ssid);
} else if (modelData.secured) { } else if (modelData.secured) {
if (wifiPasswordModalRef) { if (wifiPasswordModalRef) {
wifiPasswordModalRef.wifiPasswordSSID = modelData.ssid; wifiPasswordModalRef.wifiPasswordSSID = modelData.ssid;
@@ -261,7 +261,7 @@ Column {
wifiPasswordModalRef.wifiPasswordModalVisible = true; wifiPasswordModalRef.wifiPasswordModalVisible = true;
} }
} else { } else {
WifiService.connectToWifi(modelData.ssid); NetworkService.connectToWifi(modelData.ssid);
} }
} }
} }

View File

@@ -21,10 +21,10 @@ Item {
} }
// Explicitly reference both arrays to ensure reactivity // Explicitly reference both arrays to ensure reactivity
var allNetworks = WifiService.wifiNetworks; var allNetworks = NetworkService.wifiNetworks;
var savedNetworks = WifiService.savedWifiNetworks; var savedNetworks = NetworkService.savedWifiNetworks;
var currentSSID = WifiService.currentWifiSSID; var currentSSID = NetworkService.currentWifiSSID;
var signalStrength = WifiService.wifiSignalStrength; var signalStrength = NetworkService.wifiSignalStrength;
var refreshTrigger = forceRefresh; // Force recalculation var refreshTrigger = forceRefresh; // Force recalculation
var networks = [...allNetworks]; var networks = [...allNetworks];
@@ -58,7 +58,7 @@ Item {
property int forceRefresh: 0 property int forceRefresh: 0
Connections { Connections {
target: WifiService target: NetworkService
function onNetworksUpdated() { function onNetworksUpdated() {
forceRefresh++; forceRefresh++;
} }
@@ -66,9 +66,9 @@ Item {
// 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; NetworkService.autoRefreshEnabled = true;
if (NetworkService.wifiEnabled) if (NetworkService.wifiEnabled)
WifiService.scanWifi(); NetworkService.scanWifi();
// Start smart monitoring // Start smart monitoring
wifiMonitorTimer.start(); wifiMonitorTimer.start();
} }
@@ -199,8 +199,8 @@ Item {
property bool triggered: false property bool triggered: false
onTriggered: { onTriggered: {
NetworkService.refreshNetworkStatus(); NetworkService.refreshNetworkStatus();
if (NetworkService.wifiEnabled && !WifiService.isScanning) { if (NetworkService.wifiEnabled && !NetworkService.isScanning) {
WifiService.scanWifi(); NetworkService.scanWifi();
} }
triggered = false; triggered = false;
} }
@@ -218,13 +218,13 @@ Item {
wifiMonitorTimer.start(); wifiMonitorTimer.start();
} else { } else {
// When WiFi is disabled, clear all cached WiFi data // When WiFi is disabled, clear all cached WiFi data
WifiService.currentWifiSSID = ""; NetworkService.currentWifiSSID = "";
WifiService.wifiSignalStrength = "excellent"; NetworkService.wifiSignalStrength = "excellent";
WifiService.wifiNetworks = []; NetworkService.wifiNetworks = [];
WifiService.savedWifiNetworks = []; NetworkService.savedWifiNetworks = [];
WifiService.connectionStatus = ""; NetworkService.connectionStatus = "";
WifiService.connectingSSID = ""; NetworkService.connectingSSID = "";
WifiService.isScanning = false; NetworkService.isScanning = false;
NetworkService.refreshNetworkStatus(); NetworkService.refreshNetworkStatus();
// Stop monitoring when WiFi is off // Stop monitoring when WiFi is off
wifiMonitorTimer.stop(); wifiMonitorTimer.stop();
@@ -240,8 +240,8 @@ Item {
repeat: false repeat: false
onTriggered: { onTriggered: {
if (NetworkService.wifiEnabled && visible) { if (NetworkService.wifiEnabled && visible) {
if (!WifiService.isScanning) { if (!NetworkService.isScanning) {
WifiService.scanWifi(); NetworkService.scanWifi();
} else { } else {
// If still scanning, try again in a bit // If still scanning, try again in a bit
wifiRetryTimer.start(); wifiRetryTimer.start();
@@ -257,9 +257,9 @@ Item {
running: false running: false
repeat: false repeat: false
onTriggered: { onTriggered: {
if (NetworkService.wifiEnabled && visible && WifiService.wifiNetworks.length === 0) { if (NetworkService.wifiEnabled && visible && NetworkService.wifiNetworks.length === 0) {
if (!WifiService.isScanning) { if (!NetworkService.isScanning) {
WifiService.scanWifi(); NetworkService.scanWifi();
} }
} }
} }
@@ -288,13 +288,13 @@ Item {
reason = "not connected to WiFi"; reason = "not connected to WiFi";
} }
// Also scan occasionally even when connected to keep networks fresh // Also scan occasionally even when connected to keep networks fresh
else if (WifiService.wifiNetworks.length === 0) { else if (NetworkService.wifiNetworks.length === 0) {
shouldScan = true; shouldScan = true;
reason = "no networks cached"; reason = "no networks cached";
} }
if (shouldScan && !WifiService.isScanning) { if (shouldScan && !NetworkService.isScanning) {
WifiService.scanWifi(); NetworkService.scanWifi();
} }
} }
} }

View File

@@ -1,80 +1,74 @@
import QtQuick import QtQuick
import Quickshell import QtQuick.Controls
import Quickshell.Io import Quickshell.Io
import Quickshell.Wayland
import qs.Common import qs.Common
import qs.Services import qs.Services
PanelWindow { Popup {
id: processContextMenuWindow id: processContextMenu
property var processData: null property var processData: null
property bool menuVisible: false
function show(x, y) { function show(x, y) {
if (!processContextMenu.parent && typeof Overlay !== "undefined" && Overlay.overlay) {
processContextMenu.parent = Overlay.overlay;
}
const menuWidth = 180; const menuWidth = 180;
const menuHeight = menuColumn.implicitHeight + Theme.spacingS * 2; const menuHeight = menuColumn.implicitHeight + Theme.spacingS * 2;
const screenWidth = processContextMenuWindow.screen ? processContextMenuWindow.screen.width : 1920; const screenWidth = Screen.width;
const screenHeight = processContextMenuWindow.screen ? processContextMenuWindow.screen.height : 1080; const screenHeight = Screen.height;
let finalX = x; let finalX = x;
let finalY = y; let finalY = y;
if (x + menuWidth > screenWidth - 20)
if (x + menuWidth > screenWidth - 20) {
finalX = x - menuWidth; finalX = x - menuWidth;
}
if (y + menuHeight > screenHeight - 20) if (y + menuHeight > screenHeight - 20) {
finalY = y - menuHeight; finalY = y - menuHeight;
finalX = Math.max(20, finalX);
finalY = Math.max(20, finalY);
processContextMenu.x = finalX;
processContextMenu.y = finalY;
processContextMenuWindow.menuVisible = true;
} }
function hide() { processContextMenu.x = Math.max(20, finalX);
processContextMenuWindow.menuVisible = false; processContextMenu.y = Math.max(20, finalY);
open();
} }
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 width: 180
height: menuColumn.implicitHeight + Theme.spacingS * 2 height: menuColumn.implicitHeight + Theme.spacingS * 2
radius: Theme.cornerRadiusLarge 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"
}
contentItem: Rectangle {
id: menuContent
color: Theme.popupBackground() color: Theme.popupBackground()
radius: Theme.cornerRadiusLarge
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1 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 { Column {
id: menuColumn id: menuColumn
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingS anchors.margins: Theme.spacingS
spacing: 1 spacing: 1
@@ -97,27 +91,17 @@ PanelWindow {
MouseArea { MouseArea {
id: copyPidArea id: copyPidArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (processContextMenuWindow.processData) { if (processContextMenu.processData) {
copyPidProcess.command = ["wl-copy", processContextMenuWindow.processData.pid.toString()]; copyPidProcess.command = ["wl-copy", processContextMenu.processData.pid.toString()];
copyPidProcess.running = true; copyPidProcess.running = true;
} }
processContextMenuWindow.hide(); processContextMenu.close();
} }
} }
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
} }
Rectangle { Rectangle {
@@ -138,28 +122,18 @@ PanelWindow {
MouseArea { MouseArea {
id: copyNameArea id: copyNameArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (processContextMenuWindow.processData) { if (processContextMenu.processData) {
let processName = processContextMenuWindow.processData.displayName || processContextMenuWindow.processData.command; let processName = processContextMenu.processData.displayName || processContextMenu.processData.command;
copyNameProcess.command = ["wl-copy", processName]; copyNameProcess.command = ["wl-copy", processName];
copyNameProcess.running = true; copyNameProcess.running = true;
} }
processContextMenuWindow.hide(); processContextMenu.close();
} }
} }
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
} }
Rectangle { Rectangle {
@@ -174,7 +148,6 @@ PanelWindow {
height: 1 height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2) color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
} }
} }
Rectangle { Rectangle {
@@ -182,7 +155,7 @@ PanelWindow {
height: 28 height: 28
radius: Theme.cornerRadiusSmall radius: Theme.cornerRadiusSmall
color: killArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent" 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 opacity: enabled ? 1 : 0.5
Text { Text {
@@ -197,28 +170,18 @@ PanelWindow {
MouseArea { MouseArea {
id: killArea id: killArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: parent.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor cursorShape: parent.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
enabled: parent.enabled enabled: parent.enabled
onClicked: { onClicked: {
if (processContextMenuWindow.processData) { if (processContextMenu.processData) {
killProcess.command = ["kill", processContextMenuWindow.processData.pid.toString()]; killProcess.command = ["kill", processContextMenu.processData.pid.toString()];
killProcess.running = true; killProcess.running = true;
} }
processContextMenuWindow.hide(); processContextMenu.close();
} }
} }
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
} }
Rectangle { Rectangle {
@@ -226,7 +189,7 @@ PanelWindow {
height: 28 height: 28
radius: Theme.cornerRadiusSmall radius: Theme.cornerRadiusSmall
color: forceKillArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent" 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 opacity: enabled ? 1 : 0.5
Text { Text {
@@ -241,79 +204,39 @@ PanelWindow {
MouseArea { MouseArea {
id: forceKillArea id: forceKillArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: parent.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor cursorShape: parent.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
enabled: parent.enabled enabled: parent.enabled
onClicked: { onClicked: {
if (processContextMenuWindow.processData) { if (processContextMenu.processData) {
forceKillProcess.command = ["kill", "-9", processContextMenuWindow.processData.pid.toString()]; forceKillProcess.command = ["kill", "-9", processContextMenu.processData.pid.toString()];
forceKillProcess.running = true; 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 { Process {
id: copyPidProcess id: copyPidProcess
running: false running: false
} }
Process { Process {
id: copyNameProcess id: copyNameProcess
running: false running: false
} }
Process { Process {
id: killProcess id: killProcess
running: false running: false
} }
Process { Process {
id: forceKillProcess id: forceKillProcess
running: false running: false
} }

View File

@@ -8,7 +8,6 @@ Rectangle {
property var process: null property var process: null
property var contextMenu: null property var contextMenu: null
property var processContextMenuWindow: null
width: parent.width width: parent.width
height: 40 height: 40
@@ -29,15 +28,16 @@ Rectangle {
if (process && process.pid > 0 && contextMenu) { if (process && process.pid > 0 && contextMenu) {
contextMenu.processData = process; contextMenu.processData = process;
let globalPos = processMouseArea.mapToGlobal(mouse.x, mouse.y); 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: { onPressAndHold: {
if (process && process.pid > 0 && processContextMenuWindow) { if (process && process.pid > 0 && contextMenu) {
processContextMenuWindow.processData = process; contextMenu.processData = process;
let globalPos = processMouseArea.mapToGlobal(processMouseArea.width / 2, processMouseArea.height / 2); 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 hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (process && process.pid > 0 && processContextMenuWindow) { if (process && process.pid > 0 && contextMenu) {
processContextMenuWindow.processData = process; contextMenu.processData = process;
let globalPos = menuButtonArea.mapToGlobal(menuButtonArea.width / 2, menuButtonArea.height); 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);
} }
} }
} }

View File

@@ -19,6 +19,10 @@ PanelWindow {
function hide() { function hide() {
isVisible = false; isVisible = false;
// Close any open context menus
if (processContextMenu.visible) {
processContextMenu.close();
}
} }
function show() { function show() {
@@ -127,13 +131,13 @@ PanelWindow {
ProcessListView { ProcessListView {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
contextMenu: processContextMenuWindow contextMenu: processContextMenu
} }
} }
} }
} }
ProcessContextMenu { ProcessContextMenu {
id: processContextMenuWindow id: processContextMenu
} }
} }

View File

@@ -5,7 +5,6 @@ import qs.Services
Column { Column {
id: root id: root
property var processContextMenuWindow: null
property var contextMenu: null property var contextMenu: null
Item { Item {
@@ -130,7 +129,6 @@ Column {
delegate: ProcessListItem { delegate: ProcessListItem {
process: modelData process: modelData
contextMenu: root.contextMenu contextMenu: root.contextMenu
processContextMenuWindow: root.contextMenu
} }
} }
} }

View File

@@ -2,6 +2,7 @@ import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import qs.Common import qs.Common
import qs.Services import qs.Services
import qs.Modules.ProcessList
ColumnLayout { ColumnLayout {
id: processesTab id: processesTab
@@ -17,6 +18,10 @@ ColumnLayout {
ProcessListView { ProcessListView {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
contextMenu: processesTab.contextMenu contextMenu: processesTab.contextMenu || localContextMenu
}
ProcessContextMenu {
id: localContextMenu
} }
} }

View File

@@ -8,7 +8,7 @@ import qs.Services
Item { Item {
id: root 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 MprisPlayer activePlayer: MprisController.activePlayer
readonly property bool hasActiveMedia: activePlayer !== null readonly property bool hasActiveMedia: activePlayer !== null
property bool cavaAvailable: false property bool cavaAvailable: false
@@ -41,10 +41,10 @@ Item {
id: cavaProcess id: cavaProcess
running: false 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: { onRunningChanged: {
if (!running) if (!running)
root.audioLevels = [0, 0, 0, 0]; root.audioLevels = [0, 0, 0, 0, 0, 0];
} }
@@ -57,8 +57,8 @@ Item {
}).filter((p) => { }).filter((p) => {
return !isNaN(p); return !isNaN(p);
}); });
if (points.length >= 4) if (points.length >= 6)
root.audioLevels = [points[0], points[1], points[2], points[3]]; root.audioLevels = [points[0], points[1], points[2], points[3], points[4], points[5]];
} }
} }
@@ -73,19 +73,19 @@ Item {
interval: 100 interval: 100
repeat: true repeat: true
onTriggered: { 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 { Row {
anchors.centerIn: parent anchors.centerIn: parent
spacing: 2 spacing: 1.5
Repeater { Repeater {
model: 4 model: 6
Rectangle { Rectangle {
width: 3 width: 2
height: { height: {
if (root.activePlayer && root.activePlayer.playbackState === MprisPlaybackState.Playing && root.audioLevels.length > index) { if (root.activePlayer && root.activePlayer.playbackState === MprisPlaybackState.Playing && root.audioLevels.length > index) {
const rawLevel = root.audioLevels[index] || 0; const rawLevel = root.audioLevels[index] || 0;

View File

@@ -43,7 +43,7 @@ Rectangle {
if (NetworkService.networkStatus === "ethernet") if (NetworkService.networkStatus === "ethernet")
return "lan"; return "lan";
else if (NetworkService.networkStatus === "wifi") else if (NetworkService.networkStatus === "wifi")
return getWiFiSignalIcon(WifiService.wifiSignalStrength); return getWiFiSignalIcon(NetworkService.wifiSignalStrength);
else else
return "wifi_off"; return "wifi_off";
} }

View File

@@ -85,8 +85,6 @@ Rectangle {
spacing: Theme.spacingXS spacing: Theme.spacingXS
AudioVisualization { AudioVisualization {
width: 20
height: Theme.iconSize
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }

View File

@@ -1,4 +1,3 @@
import "../../Common/Utilities.js" as Utils
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Effects import QtQuick.Effects
@@ -277,7 +276,7 @@ PanelWindow {
controlCenterPopout.controlCenterVisible = !controlCenterPopout.controlCenterVisible; controlCenterPopout.controlCenterVisible = !controlCenterPopout.controlCenterVisible;
if (controlCenterPopout.controlCenterVisible) { if (controlCenterPopout.controlCenterVisible) {
if (NetworkService.wifiEnabled) if (NetworkService.wifiEnabled)
WifiService.scanWifi(); NetworkService.scanWifi();
} }
} }

View File

@@ -21,6 +21,27 @@ Singleton {
property bool changingPreference: false property bool changingPreference: false
property string targetPreference: "" // Track what preference we're switching to 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 // Load saved preference on startup
Component.onCompleted: { Component.onCompleted: {
// Load preference from Prefs system // Load preference from Prefs system
@@ -29,7 +50,7 @@ Singleton {
// Trigger immediate WiFi info update if WiFi is connected and enabled // Trigger immediate WiFi info update if WiFi is connected and enabled
if (root.networkStatus === "wifi" && root.wifiEnabled) { if (root.networkStatus === "wifi" && root.wifiEnabled) {
WifiService.updateCurrentWifiInfo() updateCurrentWifiInfo()
} }
} }
@@ -68,7 +89,7 @@ Singleton {
root.networkStatus = "wifi" root.networkStatus = "wifi"
console.log("User prefers WiFi, setting status to wifi") console.log("User prefers WiFi, setting status to wifi")
if (root.wifiEnabled) { if (root.wifiEnabled) {
WifiService.updateCurrentWifiInfo() updateCurrentWifiInfo()
} }
} else if (root.userPreference === "ethernet") { } else if (root.userPreference === "ethernet") {
root.networkStatus = "ethernet" root.networkStatus = "ethernet"
@@ -82,7 +103,7 @@ Singleton {
console.log("Only WiFi connected, setting status to wifi") console.log("Only WiFi connected, setting status to wifi")
// Trigger WiFi SSID update // Trigger WiFi SSID update
if (root.wifiEnabled) { if (root.wifiEnabled) {
WifiService.updateCurrentWifiInfo() updateCurrentWifiInfo()
} }
} else if (hasEthernet || ethernetCableUp) { } else if (hasEthernet || ethernetCableUp) {
root.networkStatus = "ethernet" root.networkStatus = "ethernet"
@@ -145,7 +166,7 @@ Singleton {
console.log("WiFi interface has default route, setting status to wifi") console.log("WiFi interface has default route, setting status to wifi")
// Trigger WiFi SSID update // Trigger WiFi SSID update
if (root.wifiEnabled) { if (root.wifiEnabled) {
WifiService.updateCurrentWifiInfo() updateCurrentWifiInfo()
} }
} else if (defaultInterface.startsWith("en") || defaultInterface.includes("eth")) { } else if (defaultInterface.startsWith("en") || defaultInterface.includes("eth")) {
root.networkStatus = "ethernet" 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 { Process {
id: ethernetSwitcher id: ethernetSwitcher
@@ -381,26 +368,8 @@ Singleton {
console.log("Connecting ethernet...") console.log("Connecting ethernet...")
ethernetConnector.running = true 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() { function toggleWifiRadio() {
if (root.wifiToggling) return if (root.wifiToggling) return
@@ -436,15 +405,530 @@ Singleton {
} }
} }
// WiFi-specific functions
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
}
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) { function connectToWifiAndSetPreference(ssid, password) {
console.log("Connecting to WiFi and setting preference:", ssid) console.log("Connecting to WiFi and setting network preference:", ssid)
if (!root.wifiEnabled) { connectToWifiWithPassword(ssid, password)
console.log("WiFi is disabled, cannot connect to network") setNetworkPreference("wifi")
return
} }
root.userPreference = "wifi"
Prefs.setNetworkPreference("wifi") // WiFi Process Components
WifiService.connectToWifiWithPassword(ssid, password) 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)
}
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -1,7 +1,5 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import Quickshell
import Quickshell.Wayland
import qs.Common import qs.Common
import qs.Widgets import qs.Widgets
@@ -20,11 +18,11 @@ Rectangle {
height: 60 height: 60
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08) 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 { Column {
@@ -50,7 +48,6 @@ Rectangle {
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
width: parent.width width: parent.width
} }
} }
Rectangle { Rectangle {
@@ -66,17 +63,32 @@ Rectangle {
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: 1 border.width: 1
MouseArea {
id: dropdownArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (dropdownMenu.visible) {
dropdownMenu.close();
} else {
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 { Row {
id: contentRow
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Theme.spacingM anchors.leftMargin: Theme.spacingM
anchors.rightMargin: Theme.spacingS
Row {
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS spacing: Theme.spacingS
width: parent.width - 24
DankIcon { DankIcon {
name: { name: {
@@ -85,96 +97,47 @@ Rectangle {
} }
size: 18 size: 18
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter visible: name !== ""
visible: {
var currentIndex = root.options.indexOf(root.currentValue);
return root.optionIcons.length > currentIndex && currentIndex >= 0 && root.optionIcons[currentIndex] !== "";
}
} }
Text { Text {
text: root.currentValue text: root.currentValue
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter // Constrain width for proper eliding
width: parent.parent.width - (visible ? 24 + Theme.spacingS : 24) - (parent.children[0].visible ? 18 + Theme.spacingS : 0) width: dropdown.width - contentRow.x - expandIcon.width - Theme.spacingM - Theme.spacingS
elide: Text.ElideRight elide: Text.ElideRight
} }
} }
// Anchor the expand icon to the right, outside of the Row
DankIcon { DankIcon {
id: expandIcon
name: "expand_more" name: "expand_more"
size: 20 size: 20
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} anchors.rightMargin: Theme.spacingS
}
MouseArea {
id: dropdownArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: (mouse) => {
mouse.accepted = true;
if (!dropdownMenu.visible) {
dropdownMenu.updatePosition();
dropdownMenu.visible = true;
} else {
dropdownMenu.visible = false;
}
} }
} }
} Popup {
// Integrated dropdown menu with full-screen overlay
PanelWindow {
id: dropdownMenu id: dropdownMenu
parent: Overlay.overlay
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;
}
}
// Dropdown menu content
Rectangle {
x: dropdownMenu.targetX
y: dropdownMenu.targetY
width: 180 width: 180
height: Math.min(200, root.options.length * 36 + 16) height: Math.min(200, root.options.length * 36 + 16)
radius: Theme.cornerRadiusSmall
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) 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.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
border.width: 1 border.width: 1
radius: Theme.cornerRadiusSmall
ScrollView { ScrollView {
anchors.fill: parent anchors.fill: parent
@@ -201,8 +164,7 @@ Rectangle {
name: root.optionIcons.length > index ? root.optionIcons[index] : "" name: root.optionIcons.length > index ? root.optionIcons[index] : ""
size: 18 size: 18
color: root.currentValue === modelData ? Theme.primary : Theme.surfaceVariantText color: root.currentValue === modelData ? Theme.primary : Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter visible: name !== ""
visible: root.optionIcons.length > index && root.optionIcons[index] !== ""
} }
Text { Text {
@@ -216,25 +178,18 @@ Rectangle {
MouseArea { MouseArea {
id: optionArea id: optionArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: { onClicked: {
root.currentValue = modelData; root.currentValue = modelData;
root.valueChanged(modelData); root.valueChanged(modelData);
dropdownMenu.visible = false; dropdownMenu.close();
} }
} }
} }
} }
} }
} }
} }
} }