150 lines
5.4 KiB
QML
150 lines
5.4 KiB
QML
pragma Singleton
|
|
|
|
// Network and Bluetooth state for the settings page.
|
|
//
|
|
// The hard parts -- scanning, joining, pairing -- already work in the quick
|
|
// settings panel through Quickshell.Networking and Quickshell.Bluetooth, which
|
|
// speak to NetworkManager and BlueZ over DBus. Nothing here shells out to nmcli
|
|
// or bluetoothctl, and nothing should: the founding requirement for this
|
|
// desktop was never having to drop to a terminal to join a network.
|
|
//
|
|
// This exists so the page does not have to reach into those modules for the
|
|
// same derived values the panel already computes, and so scanning is driven by
|
|
// whether the page is actually on screen. Scanning while nobody is looking is
|
|
// radio time and battery spent on a list that is not being read.
|
|
|
|
import Quickshell
|
|
import Quickshell.Networking
|
|
import Quickshell.Bluetooth
|
|
import QtQuick
|
|
|
|
Singleton {
|
|
id: root
|
|
|
|
// Set by the page while it is visible; drives both scanners.
|
|
property bool active: false
|
|
|
|
readonly property var wifiDevice: {
|
|
for (const device of Networking.devices.values) {
|
|
if (device.type === DeviceType.Wifi)
|
|
return device;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
readonly property var wiredDevice: {
|
|
let fallback = null;
|
|
for (const device of Networking.devices.values) {
|
|
if (device.type !== DeviceType.Wired)
|
|
continue;
|
|
if (device.connected)
|
|
return device;
|
|
if (!fallback)
|
|
fallback = device;
|
|
}
|
|
return fallback;
|
|
}
|
|
|
|
readonly property var adapter: Bluetooth.defaultAdapter
|
|
|
|
readonly property bool wifiEnabled: Networking.wifiEnabled
|
|
readonly property bool wifiAvailable: Networking.wifiHardwareEnabled
|
|
|
|
// Current network, then saved, then by signal -- the order GNOME uses,
|
|
// which is the order you actually look for things in.
|
|
readonly property var networks: {
|
|
if (!root.wifiDevice || !root.wifiDevice.networks)
|
|
return [];
|
|
const list = root.wifiDevice.networks.values.slice();
|
|
list.sort((a, b) => {
|
|
if (a.connected !== b.connected)
|
|
return a.connected ? -1 : 1;
|
|
if (a.known !== b.known)
|
|
return a.known ? -1 : 1;
|
|
return b.signalStrength - a.signalStrength;
|
|
});
|
|
return list;
|
|
}
|
|
|
|
readonly property var savedNetworks: root.networks.filter(network => network.known)
|
|
|
|
readonly property var bluetoothDevices: {
|
|
if (!Bluetooth.devices)
|
|
return [];
|
|
const list = Bluetooth.devices.values.slice();
|
|
list.sort((a, b) => {
|
|
if (a.connected !== b.connected)
|
|
return a.connected ? -1 : 1;
|
|
if (a.paired !== b.paired)
|
|
return a.paired ? -1 : 1;
|
|
return String(a.name || "").localeCompare(String(b.name || ""));
|
|
});
|
|
return list;
|
|
}
|
|
|
|
readonly property var activeNetwork: root.networks.find(network => network.connected) ?? null
|
|
|
|
function isSecured(network: var): bool {
|
|
return network.security !== WifiSecurityType.Open
|
|
&& network.security !== WifiSecurityType.Owe
|
|
&& network.security !== WifiSecurityType.Unknown;
|
|
}
|
|
|
|
function securityLabel(network: var): string {
|
|
if (!root.isSecured(network))
|
|
return "Open";
|
|
switch (network.security) {
|
|
case WifiSecurityType.Wep: return "WEP";
|
|
case WifiSecurityType.Wpa: return "WPA";
|
|
case WifiSecurityType.Wpa2: return "WPA2";
|
|
case WifiSecurityType.Wpa3: return "WPA3";
|
|
case WifiSecurityType.Enterprise: return "Enterprise";
|
|
}
|
|
return "Secured";
|
|
}
|
|
|
|
// Four bars is what people read signal as, so bucket rather than showing a
|
|
// percentage that changes every scan and means nothing to anyone.
|
|
//
|
|
// signalStrength is 0.0-1.0, NOT a percentage. Treating it as 0-100 puts
|
|
// every network including the connected one in the bottom bucket, which is
|
|
// exactly as useless as showing nothing. Thresholds match the icon buckets
|
|
// in modules/quicksettings/WifiList.qml so the two never disagree.
|
|
function signalLabel(strength: real): string {
|
|
if (strength >= 0.8) return "Excellent";
|
|
if (strength >= 0.55) return "Good";
|
|
if (strength >= 0.3) return "Fair";
|
|
if (strength > 0.05) return "Weak";
|
|
return "No signal";
|
|
}
|
|
|
|
function connectionFailureText(reason: var): string {
|
|
switch (reason) {
|
|
case ConnectionFailReason.WifiAuthTimeout:
|
|
case ConnectionFailReason.Authentication:
|
|
return "Wrong password";
|
|
case ConnectionFailReason.WifiNetworkLost:
|
|
return "Network out of range";
|
|
}
|
|
return "Could not connect";
|
|
}
|
|
|
|
// Scanning follows visibility. NetworkManager keeps scanning as long as it
|
|
// is asked to, and Bluetooth discovery is worse -- it holds the radio.
|
|
function syncScanners(): void {
|
|
if (root.wifiDevice)
|
|
root.wifiDevice.scannerEnabled = root.active && root.wifiEnabled;
|
|
|
|
if (root.adapter && root.adapter.enabled) {
|
|
const shouldDiscover = root.active;
|
|
if (root.adapter.discovering !== shouldDiscover)
|
|
root.adapter.discovering = shouldDiscover;
|
|
}
|
|
}
|
|
|
|
onActiveChanged: root.syncScanners()
|
|
onWifiDeviceChanged: root.syncScanners()
|
|
onWifiEnabledChanged: root.syncScanners()
|
|
onAdapterChanged: root.syncScanners()
|
|
}
|