aboutsummaryrefslogtreecommitdiff
path: root/modules/system/quickshell/Wifi.qml
diff options
context:
space:
mode:
authorLeander Scherer <leander@schererleander.de>2026-05-18 21:48:24 +0200
committerLeander Scherer <leander@schererleander.de>2026-05-28 22:42:07 +0200
commit9a7cf1242d296dbdb9c03df48ab09054960295aa (patch)
treef1a2d5c77ef6bdb049c995afcc4c663c1ffd1373 /modules/system/quickshell/Wifi.qml
parent3ef8b4973bcae26445f99467d50ad75730d204b5 (diff)
feat(quickshell): basic bar, tray, notification
Diffstat (limited to 'modules/system/quickshell/Wifi.qml')
-rw-r--r--modules/system/quickshell/Wifi.qml397
1 files changed, 397 insertions, 0 deletions
diff --git a/modules/system/quickshell/Wifi.qml b/modules/system/quickshell/Wifi.qml
new file mode 100644
index 0000000..0ce57a0
--- /dev/null
+++ b/modules/system/quickshell/Wifi.qml
@@ -0,0 +1,397 @@
+import QtQuick
+import QtQuick.Layouts
+import Quickshell
+import Quickshell.Networking
+import Quickshell.Widgets
+
+Item {
+ id: root
+
+ Scope {
+ id: internal
+
+ readonly property var device: {
+ if (!Networking.devices) return null
+ for (const d of Networking.devices.values || []) {
+ if (d && d.scannerEnabled !== undefined) return d
+ }
+ return null
+ }
+
+ readonly property var allNetworks: device?.networks ? device.networks.values : []
+
+ property bool hasKnown: false
+ property bool hasOther: false
+ property var activeNetwork: null
+
+ function updateState() {
+ let known = false
+ let other = false
+ let active = null
+
+ for (const n of internal.allNetworks) {
+ if (n?.connected) active = n
+ if (n?.known) known = true
+ if (n && !n.known && n.name) other = true
+ }
+
+ internal.hasKnown = known
+ internal.hasOther = other
+ internal.activeNetwork = active
+ }
+
+ Connections {
+ target: internal.device ? internal.device.networks : null
+ function onValuesChanged() { internal.updateState() }
+ }
+ }
+
+ function _getWifiIcon(strength) {
+ if (!Networking.wifiEnabled) return "network-wireless-offline-symbolic"
+ if (!internal.activeNetwork && !internal.device?.enabled) return "network-wireless-offline-symbolic"
+
+ const s = strength ?? 0
+ if (s >= 0.75) return "network-wireless-signal-excellent-symbolic"
+ if (s >= 0.50) return "network-wireless-signal-good-symbolic"
+ if (s >= 0.25) return "network-wireless-signal-ok-symbolic"
+ if (s > 0) return "network-wireless-signal-weak-symbolic"
+ return "network-wireless-signal-none-symbolic"
+ }
+
+ function _onNetworkClick(net) {
+ if (!net) return
+ if (net.connected) { net.disconnect(); return }
+ if (net.stateChanging) return
+ if (net.known) {
+ net.connect()
+ } else if ((net.security ?? 0) === 0) {
+ net.connect()
+ } else {
+ pskPrompt.network = net
+ pskPrompt.open(net.name ?? "")
+ }
+ }
+
+ width: childrenRect.width
+ height: parent.height
+
+ Component.onCompleted: internal.updateState()
+
+ Row {
+ anchors {
+ verticalCenter: parent.verticalCenter
+ }
+ spacing: 4
+ Image {
+ width: 20
+ height: 20
+ source: Quickshell.iconPath(root._getWifiIcon(internal.activeNetwork?.signalStrength ?? 0))
+ sourceSize: Qt.size(width, height)
+ smooth: true
+ mipmap: true
+ opacity: !Networking.wifiEnabled ? 0.35 : 1.0
+ }
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ acceptedButtons: Qt.LeftButton | Qt.RightButton
+ onClicked: mouse => {
+ if (mouse.button === Qt.LeftButton) {
+ GlobalState.toggle("Wifi")
+ } else if (mouse.button === Qt.RightButton) {
+ Networking.wifiEnabled = !Networking.wifiEnabled
+ }
+ }
+ }
+
+ PopupWindow {
+ id: popup
+ visible: GlobalState.activePopup === "Wifi"
+ grabFocus: true
+ implicitWidth: card.width
+ implicitHeight: card.height
+
+ anchor {
+ window: barWindow
+ item: root
+ edges: Edges.Bottom
+ gravity: Edges.Bottom
+ margins.top: Theme.popupGap
+ }
+
+ color: Theme.transparent
+
+ onVisibleChanged: {
+ if (visible) {
+ anchor.updateAnchor()
+ if (internal.device) internal.device.scannerEnabled = true
+ } else if (internal.device?.scannerEnabled) {
+ internal.device.scannerEnabled = false
+ }
+ }
+
+ PopupCard {
+ id: card
+ margins: 16
+
+ // Wi-Fi { Toggle }
+ RowLayout {
+ Layout.fillWidth: true
+ Layout.topMargin: 4
+ Layout.bottomMargin: 4
+ Layout.leftMargin: 8
+ Layout.rightMargin: 8
+ spacing: 8
+
+ Text {
+ text: "Wi-Fi"
+ color: Theme.text
+ font {
+ family: Theme.mainFont
+ pixelSize: 14
+ weight: Font.DemiBold
+ }
+ }
+
+ Item { Layout.fillWidth: true }
+
+ Toggle {
+ checked: Networking.wifiEnabled
+ enabled: Networking.wifiHardwareEnabled
+ onToggled: Networking.wifiEnabled = !Networking.wifiEnabled
+ }
+ }
+
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: Theme.border
+ Layout.topMargin: 4
+ Layout.bottomMargin: 4
+ }
+
+ // Known Network Header
+ Text {
+ visible: internal.hasKnown
+ text: "Known Network"
+ color: Theme.textMuted
+ font {
+ family: Theme.mainFont
+ pixelSize: 12
+ weight: Font.DemiBold
+ }
+ Layout.leftMargin: 8
+ Layout.topMargin: 2
+ Layout.bottomMargin: 2
+ }
+
+ // Known networks
+ ColumnLayout {
+ Layout.fillWidth: true
+ spacing: 2
+ visible: internal.hasKnown
+
+ Repeater {
+ model: internal.device?.networks ?? null
+
+ delegate: Squircle {
+ id: knownItem
+ required property var modelData
+ readonly property bool isConnected: knownItem.modelData?.connected ?? false
+ visible: knownItem.modelData?.known ?? false
+ Layout.fillWidth: true
+ Layout.preferredHeight: visible ? 36 : 0
+ fillColor: knownArea.containsMouse ? Theme.surface : Theme.transparent
+ cornerRadius: 6
+
+ Connections {
+ target: knownItem.modelData
+ function onKnownChanged() { internal.updateState() }
+ function onConnectedChanged() { internal.updateState() }
+ function onNameChanged() { internal.updateState() }
+ function onSignalStrengthChanged() { internal.updateState() }
+ }
+
+ MouseArea {
+ id: knownArea
+ anchors.fill: parent
+ hoverEnabled: true
+ enabled: !(knownItem.modelData?.stateChanging ?? false)
+ onClicked: root._onNetworkClick(knownItem.modelData)
+ }
+
+ RowLayout {
+ anchors {
+ fill: parent
+ leftMargin: 8
+ rightMargin: 8
+ }
+ spacing: 12
+
+ IconCircle {
+ size: 24
+ source: root._getWifiIcon(knownItem.modelData?.signalStrength ?? 0)
+ active: knownItem.isConnected
+ }
+
+ Text {
+ Layout.fillWidth: true
+ text: knownItem.modelData?.name ?? ""
+ color: Theme.text
+ font {
+ family: Theme.mainFont
+ pixelSize: 13
+ }
+ elide: Text.ElideRight
+ }
+
+ IconImage {
+ visible: (knownItem.modelData?.security ?? 0) !== 0
+ Layout.preferredWidth: 14
+ Layout.preferredHeight: 14
+ source: Quickshell.iconPath("changes-prevent-symbolic")
+ }
+ }
+ }
+ }
+ }
+
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: Theme.border
+ Layout.topMargin: 4
+ Layout.bottomMargin: 4
+ }
+
+ // Other Networks Header
+ RowLayout {
+ Layout.fillWidth: true
+ Layout.leftMargin: 8
+ Layout.rightMargin: 8
+ Layout.topMargin: 2
+ Layout.bottomMargin: 2
+
+ Text {
+ text: "Other Networks"
+ color: Theme.textMuted
+ font {
+ family: Theme.mainFont
+ pixelSize: 12
+ weight: Font.DemiBold
+ }
+ Layout.fillWidth: true
+ }
+
+ Image {
+ id: refreshIcon
+ width: 14
+ height: 14
+ source: Quickshell.iconPath("view-refresh-symbolic")
+ sourceSize: Qt.size(width, height)
+ opacity: refreshMouse.containsMouse ? 1.0 : 0.6
+
+ RotationAnimation on rotation {
+ running: internal.device?.scannerEnabled ?? false
+ from: 0
+ to: 360
+ duration: 1000
+ loops: Animation.Infinite
+ }
+
+ MouseArea {
+ id: refreshMouse
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ onClicked: {
+ if (internal.device) {
+ internal.device.scannerEnabled = !internal.device.scannerEnabled
+ }
+ }
+ }
+ }
+ }
+
+ // Other networks list
+ ColumnLayout {
+ Layout.fillWidth: true
+ spacing: 2
+ visible: internal.hasOther
+
+ Repeater {
+ model: internal.device?.networks ?? null
+
+ delegate: Squircle {
+ id: otherItem
+ required property var modelData
+ visible: !(otherItem.modelData?.known ?? true) && (otherItem.modelData?.name ?? "") !== ""
+ Layout.fillWidth: true
+ Layout.preferredHeight: visible ? 36 : 0
+ fillColor: otherArea.containsMouse ? Theme.surface : Theme.transparent
+ cornerRadius: 6
+
+ Connections {
+ target: otherItem.modelData
+ function onKnownChanged() { internal.updateState() }
+ function onNameChanged() { internal.updateState() }
+ function onSignalStrengthChanged() { internal.updateState() }
+ }
+
+ MouseArea {
+ id: otherArea
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ enabled: !(otherItem.modelData?.stateChanging ?? false)
+ onClicked: root._onNetworkClick(otherItem.modelData)
+ }
+
+ RowLayout {
+ anchors {
+ fill: parent
+ leftMargin: 8
+ rightMargin: 8
+ }
+ spacing: 12
+
+ IconCircle {
+ size: 24
+ source: root._getWifiIcon(otherItem.modelData?.signalStrength ?? 0)
+ active: false
+ }
+
+ Text {
+ Layout.fillWidth: true
+ text: otherItem.modelData?.name ?? ""
+ color: Theme.text
+ opacity: 0.8
+ font {
+ family: Theme.mainFont
+ pixelSize: 13
+ }
+ elide: Text.ElideRight
+ }
+
+ IconImage {
+ visible: (otherItem.modelData?.security ?? 0) !== 0
+ Layout.preferredWidth: 14
+ Layout.preferredHeight: 14
+ source: Quickshell.iconPath("changes-prevent-symbolic")
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ WifiPasswordPrompt {
+ id: pskPrompt
+ property var network: null
+ onSubmitted: (text, remember) => {
+ if (network) network.connectWithPsk(text)
+ }
+ }
+}