Merge branch 'rebuild' of github.com:Ly-sec/Noctalia into rebuild

This commit is contained in:
quadbyte 2025-08-15 10:23:18 -04:00
commit 13df665f94
2 changed files with 405 additions and 204 deletions

View file

@ -41,7 +41,9 @@ NLoader {
// Also handle visibility changes from external sources
onVisibleChanged: {
if (!visible && wifiMenuRect.opacityValue > 0) {
if (visible && Settings.data.network.wifiEnabled) {
network.refreshNetworks()
} else if (wifiMenuRect.opacityValue > 0) {
// Start hide animation
wifiMenuRect.scaleValue = 0.8
wifiMenuRect.opacityValue = 0.0
@ -65,6 +67,22 @@ NLoader {
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
Network {
id: network
}
// Timer to refresh networks when WiFi is enabled while menu is open
Timer {
id: wifiEnableRefreshTimer
interval: 3000 // Wait 3 seconds for WiFi to be fully ready
repeat: false
onTriggered: {
if (Settings.data.network.wifiEnabled && wifiPanel.visible) {
network.refreshNetworks()
}
}
}
Rectangle {
id: wifiMenuRect
color: Colors.mSurface
@ -135,14 +153,19 @@ NLoader {
value: Settings.data.network.wifiEnabled
onToggled: function (value) {
Settings.data.network.wifiEnabled = value
// TBC: This should be done in a service
Quickshell.execDetached(["nmcli", "radio", "wifi", Settings.data.network.wifiEnabled ? "on" : "off"])
network.setWifiEnabled(value)
// If enabling WiFi while menu is open, refresh after a delay
if (value) {
wifiEnableRefreshTimer.start()
}
}
}
NIconButton {
icon: "refresh"
sizeMultiplier: 0.8
enabled: Settings.data.network.wifiEnabled && !network.isLoading
onClicked: {
network.refreshNetworks()
}
@ -159,226 +182,282 @@ NLoader {
NDivider {}
ListView {
id: networkList
Item {
Layout.fillWidth: true
Layout.fillHeight: true
model: Object.values(network.networks)
spacing: Style.marginMedium * scaling
clip: true
delegate: Item {
width: parent.width
height: modelData.ssid === passwordPromptSsid
&& showPasswordPrompt ? 108 * scaling : Style.baseWidgetSize * 1.5 * scaling
// Loading indicator
ColumnLayout {
anchors.centerIn: parent
visible: Settings.data.network.wifiEnabled && network.isLoading
spacing: Style.marginMedium * scaling
ColumnLayout {
anchors.fill: parent
spacing: 0
NBusyIndicator {
running: network.isLoading
color: Colors.mPrimary
size: Style.baseWidgetSize * scaling
Layout.alignment: Qt.AlignHCenter
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: Style.baseWidgetSize * 1.5 * scaling
radius: Style.radiusMedium * scaling
color: modelData.connected ? Colors.mPrimary : (networkMouseArea.containsMouse ? Colors.mTertiary : "transparent")
NText {
text: "Scanning for networks..."
font.pointSize: Style.fontSizeNormal * scaling
color: Colors.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
}
}
RowLayout {
anchors.fill: parent
anchors.margins: Style.marginSmall * scaling
spacing: Style.marginSmall * scaling
// WiFi disabled message
ColumnLayout {
anchors.centerIn: parent
visible: !Settings.data.network.wifiEnabled
spacing: Style.marginMedium * scaling
NText {
text: network.signalIcon(modelData.signal)
font.family: "Material Symbols Outlined"
font.pointSize: Style.fontSizeXL * scaling
color: modelData.connected ? Colors.mSurface : (networkMouseArea.containsMouse ? Colors.mSurface : Colors.mOnSurface)
}
NText {
text: "wifi_off"
font.family: "Material Symbols Outlined"
font.pointSize: Style.fontSizeXXL * scaling
color: Colors.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
}
ColumnLayout {
Layout.fillWidth: true
spacing: Style.marginTiny * scaling
NText {
text: "WiFi is disabled"
font.pointSize: Style.fontSizeLarge * scaling
color: Colors.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
}
NText {
text: "Enable WiFi to see available networks"
font.pointSize: Style.fontSizeNormal * scaling
color: Colors.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
}
}
// Network list
ListView {
id: networkList
anchors.fill: parent
visible: Settings.data.network.wifiEnabled && !network.isLoading
model: Object.values(network.networks)
spacing: Style.marginMedium * scaling
clip: true
delegate: Item {
width: parent.width
height: modelData.ssid === passwordPromptSsid
&& showPasswordPrompt ? 108 * scaling : Style.baseWidgetSize * 1.5 * scaling
ColumnLayout {
anchors.fill: parent
spacing: 0
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: Style.baseWidgetSize * 1.5 * scaling
radius: Style.radiusMedium * scaling
color: modelData.connected ? Colors.mPrimary : (networkMouseArea.containsMouse ? Colors.mTertiary : "transparent")
RowLayout {
anchors.fill: parent
anchors.margins: Style.marginSmall * scaling
spacing: Style.marginSmall * scaling
// SSID
NText {
text: modelData.ssid || "Unknown Network"
font.pointSize: Style.fontSizeNormal * scaling
elide: Text.ElideRight
Layout.fillWidth: true
text: network.signalIcon(modelData.signal)
font.family: "Material Symbols Outlined"
font.pointSize: Style.fontSizeXL * scaling
color: modelData.connected ? Colors.mSurface : (networkMouseArea.containsMouse ? Colors.mSurface : Colors.mOnSurface)
}
// Security Protocol
NText {
text: modelData.security && modelData.security !== "--" ? modelData.security : "Open"
font.pointSize: Style.fontSizeTiny * scaling
elide: Text.ElideRight
ColumnLayout {
Layout.fillWidth: true
spacing: Style.marginTiny * scaling
// SSID
NText {
text: modelData.ssid || "Unknown Network"
font.pointSize: Style.fontSizeNormal * scaling
elide: Text.ElideRight
Layout.fillWidth: true
color: modelData.connected ? Colors.mSurface : (networkMouseArea.containsMouse ? Colors.mSurface : Colors.mOnSurface)
}
// Security Protocol
NText {
text: modelData.security && modelData.security !== "--" ? modelData.security : "Open"
font.pointSize: Style.fontSizeTiny * scaling
elide: Text.ElideRight
Layout.fillWidth: true
color: modelData.connected ? Colors.mSurface : (networkMouseArea.containsMouse ? Colors.mSurface : Colors.mOnSurface)
}
NText {
visible: network.connectStatusSsid === modelData.ssid && network.connectStatus === "error"
&& network.connectError.length > 0
text: network.connectError
color: Colors.mError
font.pointSize: Style.fontSizeSmall * scaling
elide: Text.ElideRight
Layout.fillWidth: true
}
}
Item {
Layout.preferredWidth: 22
Layout.preferredHeight: 22
visible: network.connectStatusSsid === modelData.ssid
&& (network.connectStatus !== "" || network.connectingSsid === modelData.ssid)
NBusyIndicator {
visible: network.connectingSsid === modelData.ssid
running: network.connectingSsid === modelData.ssid
color: Colors.mPrimary
anchors.centerIn: parent
size: Style.baseWidgetSize * 0.7 * scaling
}
// TBC: Does nothing on my setup
NText {
visible: network.connectStatus === "success" && !network.connectingSsid
text: "check_circle"
font.family: "Material Symbols Outlined"
font.pointSize: 18 * scaling
color: "#43a047" // TBC: No!
anchors.centerIn: parent
}
// TBC: Does nothing on my setup
NText {
visible: network.connectStatus === "error" && !network.connectingSsid
text: "error"
font.family: "Material Symbols Outlined"
font.pointSize: Style.fontSizeSmall * scaling
color: Colors.mError
anchors.centerIn: parent
}
}
NText {
visible: modelData.connected
text: "connected"
font.pointSize: Style.fontSizeSmall * scaling
color: modelData.connected ? Colors.mSurface : (networkMouseArea.containsMouse ? Colors.mSurface : Colors.mOnSurface)
}
}
NText {
visible: network.connectStatusSsid === modelData.ssid && network.connectStatus === "error"
&& network.connectError.length > 0
text: network.connectError
color: Colors.mError
font.pointSize: Style.fontSizeSmall * scaling
elide: Text.ElideRight
MouseArea {
id: networkMouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
if (modelData.connected) {
network.disconnectNetwork(modelData.ssid)
} else if (network.isSecured(modelData.security) && !modelData.existing) {
passwordPromptSsid = modelData.ssid
showPasswordPrompt = true
passwordInput = "" // Clear previous input
Qt.callLater(function () {
passwordInputField.forceActiveFocus()
})
} else {
network.connectNetwork(modelData.ssid, modelData.security)
}
}
}
}
// Password prompt section
Rectangle {
id: passwordPromptSection
Layout.fillWidth: true
Layout.preferredHeight: modelData.ssid === passwordPromptSsid && showPasswordPrompt ? 60 : 0
Layout.margins: 8
visible: modelData.ssid === passwordPromptSsid && showPasswordPrompt
color: Colors.mSurfaceVariant
radius: Style.radiusSmall * scaling
RowLayout {
anchors.fill: parent
anchors.margins: Style.marginSmall * scaling
spacing: Style.marginSmall * scaling
Item {
Layout.fillWidth: true
Layout.preferredHeight: 36
Rectangle {
anchors.fill: parent
radius: 8
color: "transparent"
border.color: passwordInputField.activeFocus ? Colors.mPrimary : Colors.mOutline
border.width: 1
TextInput {
id: passwordInputField
anchors.fill: parent
anchors.margins: Style.marginMedium * scaling
text: passwordInput
font.pointSize: Style.fontSizeMedium * scaling
color: Colors.mOnSurface
verticalAlignment: TextInput.AlignVCenter
clip: true
focus: true
selectByMouse: true
activeFocusOnTab: true
inputMethodHints: Qt.ImhNone
echoMode: TextInput.Password
onTextChanged: passwordInput = text
onAccepted: {
network.submitPassword(passwordPromptSsid, passwordInput)
showPasswordPrompt = false
}
MouseArea {
id: passwordInputMouseArea
anchors.fill: parent
onClicked: passwordInputField.forceActiveFocus()
}
}
}
}
}
Item {
Layout.preferredWidth: 22
Layout.preferredHeight: 22
visible: network.connectStatusSsid === modelData.ssid
&& (network.connectStatus !== "" || network.connectingSsid === modelData.ssid)
NBusyIndicator {
visible: network.connectingSsid === modelData.ssid
running: network.connectingSsid === modelData.ssid
color: Colors.mPrimary
anchors.centerIn: parent
size: Style.baseWidgetSize * 0.7 * scaling
}
// TBC: Does nothing on my setup
NText {
visible: network.connectStatus === "success" && !network.connectingSsid
text: "check_circle"
font.family: "Material Symbols Outlined"
font.pointSize: 18 * scaling
color: "#43a047" // TBC: No!
anchors.centerIn: parent
}
// TBC: Does nothing on my setup
NText {
visible: network.connectStatus === "error" && !network.connectingSsid
text: "error"
font.family: "Material Symbols Outlined"
font.pointSize: Style.fontSizeSmall * scaling
color: Colors.mError
anchors.centerIn: parent
}
}
NText {
visible: modelData.connected
text: "connected"
font.pointSize: Style.fontSizeSmall * scaling
color: modelData.connected ? Colors.mSurface : (networkMouseArea.containsMouse ? Colors.mSurface : Colors.mOnSurface)
}
}
MouseArea {
id: networkMouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
if (modelData.connected) {
network.disconnectNetwork(modelData.ssid)
} else if (network.isSecured(modelData.security) && !modelData.existing) {
passwordPromptSsid = modelData.ssid
showPasswordPrompt = true
passwordInput = "" // Clear previous input
Qt.callLater(function () {
passwordInputField.forceActiveFocus()
})
} else {
network.connectNetwork(modelData.ssid, modelData.security)
}
}
}
}
// Password prompt section
Rectangle {
id: passwordPromptSection
Layout.fillWidth: true
Layout.preferredHeight: modelData.ssid === passwordPromptSsid && showPasswordPrompt ? 60 : 0
Layout.margins: 8
visible: modelData.ssid === passwordPromptSsid && showPasswordPrompt
color: Colors.mSurfaceVariant
radius: Style.radiusSmall * scaling
RowLayout {
anchors.fill: parent
anchors.margins: Style.marginSmall * scaling
spacing: Style.marginSmall * scaling
Item {
Layout.fillWidth: true
Layout.preferredHeight: 36
Rectangle {
anchors.fill: parent
radius: 8
color: "transparent"
border.color: passwordInputField.activeFocus ? Colors.mPrimary : Colors.mOutline
border.width: 1
Layout.preferredWidth: 80
Layout.preferredHeight: 36
radius: Style.radiusMedium * scaling
color: Colors.mPrimary
border.color: Colors.mPrimary
border.width: 0
TextInput {
id: passwordInputField
Behavior on color {
ColorAnimation {
duration: Style.animationFast
}
}
NText {
anchors.centerIn: parent
text: "Connect"
color: Colors.mSurface
font.pointSize: Style.fontSizeSmall * scaling
}
MouseArea {
anchors.fill: parent
anchors.margins: Style.marginMedium * scaling
text: passwordInput
font.pointSize: Style.fontSizeMedium * scaling
color: Colors.mOnSurface
verticalAlignment: TextInput.AlignVCenter
clip: true
focus: true
selectByMouse: true
activeFocusOnTab: true
inputMethodHints: Qt.ImhNone
echoMode: TextInput.Password
onTextChanged: passwordInput = text
onAccepted: {
onClicked: {
network.submitPassword(passwordPromptSsid, passwordInput)
showPasswordPrompt = false
}
MouseArea {
id: passwordInputMouseArea
anchors.fill: parent
onClicked: passwordInputField.forceActiveFocus()
}
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onEntered: parent.color = Qt.darker(Colors.mPrimary, 1.1)
onExited: parent.color = Colors.mPrimary
}
}
}
Rectangle {
Layout.preferredWidth: 80
Layout.preferredHeight: 36
radius: Style.radiusMedium * scaling
color: Colors.mPrimary
border.color: Colors.mPrimary
border.width: 0
Behavior on color {
ColorAnimation {
duration: Style.animationFast
}
}
NText {
anchors.centerIn: parent
text: "Connect"
color: Colors.mSurface
font.pointSize: Style.fontSizeSmall * scaling
}
MouseArea {
anchors.fill: parent
onClicked: {
network.submitPassword(passwordPromptSsid, passwordInput)
showPasswordPrompt = false
}
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onEntered: parent.color = Qt.darker(Colors.mPrimary, 1.1)
onExited: parent.color = Colors.mPrimary
}
}
}
}
}
@ -388,4 +467,4 @@ NLoader {
}
}
}
}
}

View file

@ -1,4 +1,5 @@
import QtQuick
import Quickshell
import Quickshell.Io
QtObject {
@ -10,6 +11,8 @@ QtObject {
property string connectStatusSsid: ""
property string connectError: ""
property string detectedInterface: ""
property string lastConnectedNetwork: ""
property bool isLoading: false
function signalIcon(signal) {
if (signal >= 80)
@ -28,9 +31,29 @@ QtObject {
}
function refreshNetworks() {
isLoading = true
existingNetwork.running = true
}
function setWifiEnabled(enabled) {
if (enabled) {
// Enable WiFi radio
isLoading = true
enableWifiProcess.running = true
} else {
// Store the currently connected network before disabling
for (const ssid in networks) {
if (networks[ssid].connected) {
lastConnectedNetwork = ssid
break
}
}
// Disable WiFi radio
disableWifiProcess.running = true
}
}
function connectNetwork(ssid, security) {
pendingConnect = {
"ssid": ssid,
@ -87,7 +110,7 @@ QtObject {
property int refreshInterval: 25000
// Only refresh when we have an active connection
// Only refresh when we have an active connection and WiFi is enabled
property bool hasActiveConnection: {
for (const net in networks) {
if (networks[net].connected) {
@ -99,18 +122,111 @@ QtObject {
property Timer refreshTimer: Timer {
interval: root.refreshInterval
// Only run timer when we're connected to a network
running: root.hasActiveConnection
// Only run timer when we're connected to a network and WiFi is enabled
running: root.hasActiveConnection && Settings.data.network.wifiEnabled
repeat: true
onTriggered: root.refreshNetworks()
}
// Force a refresh when menu is opened
function onMenuOpened() {
refreshNetworks()
if (Settings.data.network.wifiEnabled) {
refreshNetworks()
}
}
function onMenuClosed() {// No need to do anything special on close
function onMenuClosed() {
// No need to do anything special on close
}
// Process to enable WiFi radio
property Process enableWifiProcess: Process {
id: enableWifiProcess
running: false
command: ["nmcli", "radio", "wifi", "on"]
onRunningChanged: {
if (!running) {
// Wait a moment for the radio to be enabled, then refresh networks
enableWifiDelayTimer.start()
}
}
stderr: StdioCollector {
onStreamFinished: {
if (text.trim() !== "") {
console.warn("Error enabling WiFi:", text)
}
}
}
}
// Timer to delay network refresh after enabling WiFi
property Timer enableWifiDelayTimer: Timer {
id: enableWifiDelayTimer
interval: 2000 // Wait 2 seconds for radio to be ready
repeat: false
onTriggered: {
// Force refresh networks multiple times to ensure UI updates
root.refreshNetworks()
// Try to auto-reconnect to the last connected network if it exists
if (lastConnectedNetwork) {
autoReconnectTimer.start()
}
// Set up additional refresh to ensure UI is populated
postEnableRefreshTimer.start()
}
}
// Additional timer to ensure networks are populated after enabling
property Timer postEnableRefreshTimer: Timer {
id: postEnableRefreshTimer
interval: 1000
repeat: false
onTriggered: {
root.refreshNetworks()
}
}
// Timer to attempt auto-reconnection to the last connected network
property Timer autoReconnectTimer: Timer {
id: autoReconnectTimer
interval: 3000 // Wait 3 seconds after scan for networks to be available
repeat: false
onTriggered: {
if (lastConnectedNetwork && networks[lastConnectedNetwork]) {
const network = networks[lastConnectedNetwork]
if (network.existing && !network.connected) {
upConnectionProcess.profileName = lastConnectedNetwork
upConnectionProcess.running = true
}
}
}
}
// Process to disable WiFi radio
property Process disableWifiProcess: Process {
id: disableWifiProcess
running: false
command: ["nmcli", "radio", "wifi", "off"]
onRunningChanged: {
if (!running) {
// Clear networks when WiFi is disabled
root.networks = ({})
root.connectingSsid = ""
root.connectStatus = ""
root.connectStatusSsid = ""
root.connectError = ""
root.isLoading = false
}
}
stderr: StdioCollector {
onStreamFinished: {
if (text.trim() !== "") {
console.warn("Error disabling WiFi:", text)
}
}
}
}
property Process disconnectProfileProcess: Process {
@ -211,6 +327,7 @@ QtObject {
}
root.networks = networksMap
root.isLoading = false
scanProcess.existingNetwork = {}
}
}
@ -235,6 +352,7 @@ QtObject {
root.connectStatus = "success"
root.connectStatusSsid = connectProcess.ssid
root.connectError = ""
root.lastConnectedNetwork = connectProcess.ssid
root.refreshNetworks()
}
}
@ -322,8 +440,9 @@ QtObject {
onStreamFinished: {
root.connectingSsid = ""
root.connectStatus = "success"
root.connectStatusSsid = root.pendingConnect ? root.pendingConnect.ssid : ""
root.connectStatusSsid = root.pendingConnect ? root.pendingConnect.ssid : profileName
root.connectError = ""
root.lastConnectedNetwork = profileName
root.pendingConnect = null
root.refreshNetworks()
}
@ -332,7 +451,7 @@ QtObject {
onStreamFinished: {
root.connectingSsid = ""
root.connectStatus = "error"
root.connectStatusSsid = root.pendingConnect ? root.pendingConnect.ssid : ""
root.connectStatusSsid = root.pendingConnect ? root.pendingConnect.ssid : profileName
root.connectError = text
root.pendingConnect = null
}
@ -340,6 +459,9 @@ QtObject {
}
Component.onCompleted: {
refreshNetworks()
// Only refresh networks if WiFi is enabled
if (Settings.data.network.wifiEnabled) {
refreshNetworks()
}
}
}
}