The sidebar was a flat scan of thirty-one rows; now it reads like a settings app. Multi-subject categories (Input, Network & Sharing, Applications, Users & Accounts, Privacy & Security, System) carry an Appearance-style tab strip above the page, drawn by the shell so the leaf pages themselves are untouched. The taxonomy lives in one new file, services/SettingsRoutes.qml; the sidebar, the strip, route validation, search breadcrumbs, and both generators derive from it. ShellState.settingsPage still holds leaf ids, so every deep link, IPC call, and search result keeps working — and now lands on the exact tab. Dictation moves out of Sound onto its own page under Input, with a handoff back to Sound for the microphone. The strip scrolls when System's nine tabs outgrow a tiled window. All 161 contracts pass. Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
213 lines
7.9 KiB
QML
213 lines
7.9 KiB
QML
// Connections — wired, Wi-Fi, and Bluetooth.
|
|
//
|
|
// Wi-Fi and Bluetooth are handled here rather than delegated. Everything goes
|
|
// through Quickshell.Networking and Quickshell.Bluetooth -- NetworkManager and
|
|
// BlueZ over DBus -- and nothing shells out to nmcli or bluetoothctl. That was
|
|
// the founding requirement for this desktop: never having to drop to a terminal
|
|
// to join a network.
|
|
//
|
|
// Scanning follows this page being on screen. Wi-Fi scanning and especially
|
|
// Bluetooth discovery hold the radio, and doing either for a list nobody is
|
|
// looking at is battery and airtime spent on nothing.
|
|
|
|
import QtQuick
|
|
import Quickshell
|
|
import Quickshell.Networking
|
|
import qs.config
|
|
import qs.services
|
|
|
|
SettingsPage {
|
|
id: root
|
|
|
|
|
|
title: "Connections"
|
|
lede: Connectivity.activeNetwork
|
|
? "Connected to " + Connectivity.activeNetwork.name
|
|
: "Wi-Fi, Bluetooth, and the things Fedora owns."
|
|
|
|
// Drive the scanners only while this page is the one being shown.
|
|
Component.onCompleted: {
|
|
Connectivity.active = true;
|
|
if (!WifiShare.scanned)
|
|
WifiShare.refresh();
|
|
}
|
|
Component.onDestruction: Connectivity.active = false
|
|
|
|
SettingsCard {
|
|
title: "Wired"
|
|
visible: Connectivity.wiredDevice !== null
|
|
|
|
SwitchRow {
|
|
label: "Ethernet"
|
|
// Three states worth telling apart: on, off but plugged in, and
|
|
// nothing in the socket. "Not connected" covered all three and
|
|
// explained none of them.
|
|
detail: {
|
|
const device = Connectivity.wiredDevice;
|
|
if (!device)
|
|
return "";
|
|
if (device.connected)
|
|
return device.name + (device.linkSpeed > 0
|
|
? " · " + device.linkSpeed + " Mb/s" : "");
|
|
// Not "no cable": that cannot be told apart from "switched off"
|
|
// by anything reliable here, and guessing produced a switch
|
|
// that blamed the hardware for what it had just done itself.
|
|
return device.name + " · off";
|
|
}
|
|
checked: Connectivity.wiredOn
|
|
enabled: Connectivity.wiredAvailable
|
|
divider: false
|
|
onToggled: value => Connectivity.setWired(value)
|
|
}
|
|
}
|
|
|
|
SettingsCard {
|
|
title: "Wi-Fi"
|
|
// A Wi-Fi switch reading "On" above the words "No Wi-Fi adapter" is a
|
|
// contradiction; with no radio the card simply does not belong.
|
|
visible: Connectivity.wifiDevice !== null
|
|
subtitle: "Networks are re-scanned while this page is open."
|
|
|
|
SettingRow {
|
|
label: "Wi-Fi"
|
|
detail: Connectivity.wifiEnabled ? "On" : "Off"
|
|
controlWidth: 48
|
|
divider: Connectivity.wifiEnabled
|
|
|
|
SettingsToggle {
|
|
anchors.right: parent.right
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
checked: Connectivity.wifiEnabled
|
|
enabled: Connectivity.wifiAvailable
|
|
onToggled: value => Networking.wifiEnabled = value
|
|
}
|
|
}
|
|
|
|
WifiPanel {
|
|
width: parent.width
|
|
visible: Connectivity.wifiEnabled
|
|
}
|
|
}
|
|
|
|
// Sharing a network by QR, the way GNOME's Wi-Fi panel does. The
|
|
// alternative is reading a passphrase out loud.
|
|
//
|
|
// The image holds the password in machine-readable form, so it is generated
|
|
// on demand rather than up front, and the helper writes it to tmpfs under
|
|
// XDG_RUNTIME_DIR instead of anywhere persistent.
|
|
SettingsCard {
|
|
visible: Connectivity.wifiDevice !== null && WifiShare.shareable.length > 0
|
|
title: "Share a network"
|
|
subtitle: WifiShare.sharing !== ""
|
|
? "Point a phone's camera at the code to join " + WifiShare.sharing + "."
|
|
: "Shows a QR code a phone can scan to join, without reading the password out."
|
|
|
|
Repeater {
|
|
model: WifiShare.shareable
|
|
|
|
ActionRow {
|
|
id: shareRow
|
|
required property var modelData
|
|
required property int index
|
|
|
|
label: shareRow.modelData.ssid
|
|
detail: WifiShare.sharing === shareRow.modelData.name
|
|
? "Showing a code below — anyone who can see the screen can join"
|
|
: "Saved network"
|
|
action: WifiShare.sharing === shareRow.modelData.name ? "Hide" : "Show code"
|
|
divider: shareRow.index < WifiShare.shareable.length - 1 || WifiShare.sharing !== ""
|
|
onTriggered: WifiShare.sharing === shareRow.modelData.name
|
|
? WifiShare.stopSharing()
|
|
: WifiShare.share(shareRow.modelData.name)
|
|
}
|
|
}
|
|
|
|
// Drawn at its natural size on a white plate: a QR code inverted or
|
|
// tinted to match a dark theme is unreliable to scan, and this one has
|
|
// exactly one job.
|
|
Item {
|
|
width: parent.width
|
|
visible: WifiShare.sharing !== "" && WifiShare.imagePath !== ""
|
|
implicitHeight: visible ? plate.height + 20 : 0
|
|
|
|
Rectangle {
|
|
id: plate
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
y: 10
|
|
width: 208
|
|
height: 208
|
|
radius: 10
|
|
color: "white"
|
|
|
|
Image {
|
|
anchors.centerIn: parent
|
|
width: 184
|
|
height: 184
|
|
smooth: false
|
|
fillMode: Image.PreserveAspectFit
|
|
cache: false
|
|
source: WifiShare.imagePath !== "" ? "file://" + WifiShare.imagePath : ""
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SettingsCard {
|
|
title: "Bluetooth"
|
|
visible: Connectivity.adapter !== null
|
|
subtitle: "Discovery runs while this page is open."
|
|
|
|
SettingRow {
|
|
label: "Bluetooth"
|
|
detail: Connectivity.adapter
|
|
? (Connectivity.adapter.enabled ? "On" : "Off")
|
|
: "Unavailable"
|
|
controlWidth: 48
|
|
divider: !!(Connectivity.adapter && Connectivity.adapter.enabled)
|
|
|
|
SettingsToggle {
|
|
anchors.right: parent.right
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
checked: !!(Connectivity.adapter && Connectivity.adapter.enabled)
|
|
enabled: Connectivity.adapter !== null
|
|
onToggled: value => {
|
|
if (Connectivity.adapter)
|
|
Connectivity.adapter.enabled = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
BluetoothPanel {
|
|
width: parent.width
|
|
visible: !!(Connectivity.adapter && Connectivity.adapter.enabled)
|
|
}
|
|
}
|
|
|
|
SettingsCard {
|
|
title: "Owned by Fedora"
|
|
// These two panels drive NetworkManager over D-Bus, which is why they
|
|
// work in this session when most GNOME panels do not. They are split
|
|
// because GNOME splits them: "network" is wired, VPN and proxies, and
|
|
// does not contain Wi-Fi -- the one panel used to point everyone
|
|
// there, so the road to a hidden SSID or eduroam ended on a page
|
|
// without Wi-Fi on it.
|
|
subtitle: "Wired, VPN and Wi-Fi connection editing stay with GNOME's panels, which drive the same NetworkManager this page reads. Printers and online accounts have their own pages here."
|
|
|
|
ActionRow {
|
|
label: "Wi-Fi networks"
|
|
detail: "Hidden networks, enterprise (802.1X) logins, and per-network settings"
|
|
action: "Open"
|
|
onTriggered: SystemSettings.openGnomePanel("wifi")
|
|
}
|
|
|
|
ActionRow {
|
|
label: "Network connections"
|
|
detail: "VPN, proxies, and wired connection settings"
|
|
action: "Open"
|
|
divider: false
|
|
onTriggered: SystemSettings.openGnomePanel("network")
|
|
}
|
|
|
|
}
|
|
}
|