Build the Panama Hyprland desktop
This commit is contained in:
@@ -0,0 +1,274 @@
|
||||
// The wifi picker. This is the reason the quick settings panel exists: the
|
||||
// user must never have to drop to nmcli to join a network.
|
||||
//
|
||||
// Everything here goes through Quickshell.Networking (NetworkManager over
|
||||
// DBus) — no shelling out.
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Networking
|
||||
import qs.config
|
||||
import qs.widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
// The WifiDevice, or null while NetworkManager is still enumerating.
|
||||
property var device: null
|
||||
|
||||
// True while the section is open. Drives the NM scan request so we are not
|
||||
// burning radio time scanning for a list nobody is looking at.
|
||||
property bool active: false
|
||||
|
||||
property alias maxHeight: list.maxHeight
|
||||
|
||||
// SSID whose inline password field is open, and the last failure.
|
||||
property string passwordFor: ""
|
||||
property string failedSsid: ""
|
||||
property string failedText: ""
|
||||
|
||||
implicitHeight: list.implicitHeight
|
||||
|
||||
// GNOME's ordering: the current network, then saved ones, then by signal.
|
||||
readonly property var networks: {
|
||||
if (!root.device || !root.device.networks)
|
||||
return [];
|
||||
const list = root.device.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;
|
||||
}
|
||||
|
||||
function syncScanner(): void {
|
||||
if (root.device)
|
||||
root.device.scannerEnabled = root.active;
|
||||
}
|
||||
|
||||
onActiveChanged: root.syncScanner()
|
||||
onDeviceChanged: root.syncScanner()
|
||||
Component.onCompleted: root.syncScanner()
|
||||
Component.onDestruction: {
|
||||
if (root.device)
|
||||
root.device.scannerEnabled = false;
|
||||
}
|
||||
|
||||
function signalIcon(strength: real): string {
|
||||
if (strength >= 0.8)
|
||||
return "network-wireless-signal-excellent-symbolic";
|
||||
if (strength >= 0.55)
|
||||
return "network-wireless-signal-good-symbolic";
|
||||
if (strength >= 0.3)
|
||||
return "network-wireless-signal-ok-symbolic";
|
||||
if (strength > 0.05)
|
||||
return "network-wireless-signal-weak-symbolic";
|
||||
return "network-wireless-signal-none-symbolic";
|
||||
}
|
||||
|
||||
// NM's failure reasons are useful but not English.
|
||||
function failureText(reason): string {
|
||||
switch (reason) {
|
||||
case ConnectionFailReason.NoSecrets:
|
||||
return "Wrong password";
|
||||
case ConnectionFailReason.WifiAuthTimeout:
|
||||
return "Authentication timed out";
|
||||
case ConnectionFailReason.WifiNetworkLost:
|
||||
return "Network out of range";
|
||||
default:
|
||||
return "Couldn't connect";
|
||||
}
|
||||
}
|
||||
|
||||
function isSecured(network): bool {
|
||||
return network.security !== WifiSecurityType.Open && network.security !== WifiSecurityType.Owe && network.security !== WifiSecurityType.Unknown;
|
||||
}
|
||||
|
||||
// Left click on a row. Saved and open networks connect immediately;
|
||||
// anything else reveals the password field rather than failing silently.
|
||||
function activate(network): void {
|
||||
root.failedSsid = "";
|
||||
if (network.connected)
|
||||
return;
|
||||
if (network.known || !root.isSecured(network)) {
|
||||
root.passwordFor = "";
|
||||
network.connect();
|
||||
return;
|
||||
}
|
||||
root.passwordFor = root.passwordFor === network.name ? "" : network.name;
|
||||
}
|
||||
|
||||
ScrollColumn {
|
||||
id: list
|
||||
anchors.fill: parent
|
||||
maxHeight: 340
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 30
|
||||
visible: root.networks.length > 0
|
||||
|
||||
Text {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 12
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "Available networks"
|
||||
color: Theme.fgMuted
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.DemiBold
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
visible: root.networks.length === 0
|
||||
topPadding: 12
|
||||
bottomPadding: 12
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: root.device ? (Networking.wifiEnabled ? "Scanning…" : "Wi-Fi is off") : "No Wi-Fi adapter"
|
||||
color: Theme.fgMuted
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSize
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.networks
|
||||
|
||||
Column {
|
||||
id: entry
|
||||
|
||||
required property var modelData
|
||||
|
||||
width: parent.width
|
||||
spacing: 0
|
||||
|
||||
readonly property bool secured: root.isSecured(entry.modelData)
|
||||
readonly property string ssid: entry.modelData.name
|
||||
|
||||
// Reported by NM when a connection attempt gives up. Bad
|
||||
// passwords land here, so re-open the field to retry.
|
||||
Connections {
|
||||
target: entry.modelData
|
||||
function onConnectionFailed(reason) {
|
||||
root.failedSsid = entry.ssid;
|
||||
root.failedText = root.failureText(reason);
|
||||
if (entry.secured && !entry.modelData.known)
|
||||
root.passwordFor = entry.ssid;
|
||||
}
|
||||
}
|
||||
|
||||
RowButton {
|
||||
width: parent.width
|
||||
icon: root.signalIcon(entry.modelData.signalStrength)
|
||||
iconFallback: "network-wireless-symbolic"
|
||||
label: entry.ssid !== "" ? entry.ssid : "Hidden network"
|
||||
selected: entry.modelData.connected
|
||||
sublabel: {
|
||||
if (entry.modelData.stateChanging)
|
||||
return "Connecting…";
|
||||
if (entry.modelData.connected)
|
||||
return "Connected";
|
||||
if (root.failedSsid === entry.ssid)
|
||||
return root.failedText;
|
||||
if (entry.modelData.known)
|
||||
return "Saved";
|
||||
return "";
|
||||
}
|
||||
onClicked: root.activate(entry.modelData)
|
||||
|
||||
ThemedIcon {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
size: 14
|
||||
visible: entry.secured
|
||||
tint: Theme.fgMuted
|
||||
icon: "network-wireless-encrypted-symbolic"
|
||||
iconFallback: "changes-prevent-symbolic"
|
||||
}
|
||||
|
||||
IconButton {
|
||||
visible: entry.modelData.connected
|
||||
size: 26
|
||||
iconSize: 13
|
||||
icon: "window-close-symbolic"
|
||||
onClicked: entry.modelData.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
Section {
|
||||
id: pskSection
|
||||
width: parent.width
|
||||
expanded: root.passwordFor === entry.ssid
|
||||
|
||||
onExpandedChanged: {
|
||||
if (pskSection.expanded)
|
||||
pskInput.forceActiveFocus();
|
||||
else
|
||||
pskInput.text = "";
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.leftMargin: 10
|
||||
anchors.rightMargin: 10
|
||||
height: 42
|
||||
y: 4
|
||||
radius: Theme.cardRadius - 2
|
||||
border.width: 1
|
||||
border.color: Theme.alpha(Theme.accent, 0.4)
|
||||
color: Theme.alpha(Theme.fg, 0.08)
|
||||
|
||||
TextInput {
|
||||
id: pskInput
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 12
|
||||
anchors.right: sendButton.left
|
||||
anchors.rightMargin: 6
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
echoMode: TextInput.Password
|
||||
color: Theme.fg
|
||||
selectionColor: Theme.alpha(Theme.accent, 0.5)
|
||||
selectedTextColor: Theme.fg
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSize
|
||||
clip: true
|
||||
|
||||
onAccepted: {
|
||||
if (pskInput.text.length > 0) {
|
||||
root.failedSsid = "";
|
||||
entry.modelData.connectWithPsk(pskInput.text);
|
||||
root.passwordFor = "";
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.fill: parent
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
visible: pskInput.text === ""
|
||||
text: "Password"
|
||||
color: Theme.fgMuted
|
||||
font: pskInput.font
|
||||
}
|
||||
}
|
||||
|
||||
IconButton {
|
||||
id: sendButton
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 4
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
size: 30
|
||||
iconSize: 15
|
||||
icon: "go-next-symbolic"
|
||||
onClicked: pskInput.accepted()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user