mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
Add detailed Wi‑Fi and Bluetooth panel improvements
- Introduce expanded info panels for connected Wi‑Fi networks and Bluetooth devices. - Display device details like IP, gateway, link speed, signal strength, and pairing status. - Add pairing and unpairing functionality with enhanced device deduplication. - Update translations to include new labels and messages for Wi‑Fi and Bluetooth. - Refactor services to support feature-rich info retrieval and device handling.
This commit is contained in:
@@ -453,10 +453,17 @@
|
||||
"disconnect": "Disconnect",
|
||||
"enable-message": "Enable Bluetooth to see available devices.",
|
||||
"known-devices": "Known devices",
|
||||
"paired-devices": "Paired devices",
|
||||
"pairing": "Pairing...",
|
||||
"pairing-mode": "Make sure your device is in pairing mode.",
|
||||
"scanning": "Scanning for devices...",
|
||||
"title": "Bluetooth"
|
||||
"title": "Bluetooth",
|
||||
"pair": "Pair",
|
||||
"unpair": "Unpair",
|
||||
"info": "Info",
|
||||
"device-address": "Device address",
|
||||
"paired": "Paired",
|
||||
"trusted": "Trusted"
|
||||
}
|
||||
},
|
||||
"calendar": {
|
||||
@@ -516,7 +523,9 @@
|
||||
"cancel": "Cancel",
|
||||
"check-settings": "Check Settings for details",
|
||||
"close": "Close",
|
||||
"save": "Save"
|
||||
"save": "Save",
|
||||
"yes": "Yes",
|
||||
"no": "No"
|
||||
},
|
||||
"context-menu": {
|
||||
"activate-app": "Activate {app}",
|
||||
@@ -2618,6 +2627,27 @@
|
||||
"wallpaper-selector": "Wallpaper selector",
|
||||
"widget-settings": "Widget settings"
|
||||
},
|
||||
"wifi": {
|
||||
"panel": {
|
||||
"title": "Wi‑Fi",
|
||||
"connect": "Connect",
|
||||
"disconnect": "Disconnect",
|
||||
"password": "Password",
|
||||
"enter-password": "Enter Wi‑Fi password",
|
||||
"connected": "Connected",
|
||||
"disconnecting": "Disconnecting…",
|
||||
"forgetting": "Forgetting…",
|
||||
"saved": "Saved",
|
||||
"forget-network": "Forget this network",
|
||||
"forget": "Forget",
|
||||
"info": "Info",
|
||||
"security": "Security",
|
||||
"internet-connected": "Internet connected",
|
||||
"internet-limited": "No internet",
|
||||
"link-speed": "Link speed",
|
||||
"gateway": "Gateway"
|
||||
}
|
||||
},
|
||||
"wallpaper": {
|
||||
"configure-directory": "Configure your wallpaper directory with images.",
|
||||
"fill-modes": {
|
||||
|
||||
@@ -14,6 +14,8 @@ NBox {
|
||||
property string label: ""
|
||||
property string tooltipText: ""
|
||||
property var model: {}
|
||||
// Per-list expanded details (by device key)
|
||||
property string expandedDeviceKey: ""
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: column.implicitHeight + Style.marginM * 2
|
||||
@@ -46,6 +48,7 @@ NBox {
|
||||
|
||||
readonly property bool canConnect: BluetoothService.canConnect(modelData)
|
||||
readonly property bool canDisconnect: BluetoothService.canDisconnect(modelData)
|
||||
readonly property bool canPair: BluetoothService.canPair(modelData)
|
||||
readonly property bool isBusy: BluetoothService.isDeviceBusy(modelData)
|
||||
|
||||
function getContentColor(defaultColor = Color.mOnSurface) {
|
||||
@@ -146,7 +149,7 @@ NBox {
|
||||
NButton {
|
||||
id: button
|
||||
visible: (modelData.state !== BluetoothDeviceState.Connecting)
|
||||
enabled: (canConnect || canDisconnect) && !isBusy
|
||||
enabled: (canConnect || canDisconnect || canPair) && !isBusy
|
||||
outlined: !button.hovered
|
||||
fontSize: Style.fontSizeXS
|
||||
fontWeight: Style.fontWeightMedium
|
||||
@@ -167,6 +170,9 @@ NBox {
|
||||
if (modelData.connected) {
|
||||
return I18n.tr("bluetooth.panel.disconnect");
|
||||
}
|
||||
if (device.canPair) {
|
||||
return I18n.tr("bluetooth.panel.pair");
|
||||
}
|
||||
return I18n.tr("bluetooth.panel.connect");
|
||||
}
|
||||
icon: (isBusy ? "busy" : null)
|
||||
@@ -174,13 +180,89 @@ NBox {
|
||||
if (modelData.connected) {
|
||||
BluetoothService.disconnectDevice(modelData);
|
||||
} else {
|
||||
BluetoothService.connectDeviceWithTrust(modelData);
|
||||
if (device.canPair) {
|
||||
BluetoothService.pairDevice(modelData);
|
||||
} else {
|
||||
BluetoothService.connectDeviceWithTrust(modelData);
|
||||
}
|
||||
}
|
||||
}
|
||||
onRightClicked: {
|
||||
BluetoothService.forgetDevice(modelData);
|
||||
}
|
||||
}
|
||||
|
||||
// Extra actions
|
||||
RowLayout {
|
||||
spacing: Style.marginXS
|
||||
|
||||
// Unpair for saved devices when not connected
|
||||
NIconButton {
|
||||
visible: (modelData.paired || modelData.trusted) && !modelData.connected && !isBusy && !modelData.blocked
|
||||
icon: "trash"
|
||||
tooltipText: I18n.tr("bluetooth.panel.unpair")
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: BluetoothService.unpairDevice(modelData)
|
||||
}
|
||||
|
||||
// Info for connected device
|
||||
NIconButton {
|
||||
visible: modelData.connected
|
||||
icon: "info-circle"
|
||||
tooltipText: I18n.tr("bluetooth.panel.info")
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: {
|
||||
const key = BluetoothService.deviceKey(modelData);
|
||||
root.expandedDeviceKey = (root.expandedDeviceKey === key) ? "" : key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Expanded info section
|
||||
Rectangle {
|
||||
visible: root.expandedDeviceKey === BluetoothService.deviceKey(modelData)
|
||||
Layout.fillWidth: true
|
||||
height: infoColumn.implicitHeight + Style.marginS * 2
|
||||
radius: Style.radiusS
|
||||
color: Color.mSurfaceVariant
|
||||
border.width: Style.borderS
|
||||
border.color: Color.mOutline
|
||||
|
||||
ColumnLayout {
|
||||
id: infoColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginS
|
||||
spacing: Style.marginXS
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginS
|
||||
NIcon { icon: BluetoothService.getSignalIcon(modelData); pointSize: Style.fontSizeM; color: Color.mOnSurface }
|
||||
NText { text: BluetoothService.getSignalStrength(modelData); pointSize: Style.fontSizeXS; color: Color.mOnSurface }
|
||||
NText { visible: modelData.signalStrength > 0; text: (modelData.signalStrength || 0) + "%"; pointSize: Style.fontSizeXS; color: Color.mOnSurfaceVariant }
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginS
|
||||
NIcon { icon: "hash"; pointSize: Style.fontSizeM; color: Color.mOnSurface }
|
||||
NText { text: I18n.tr("bluetooth.panel.device-address") + ": "; pointSize: Style.fontSizeXS; color: Color.mOnSurfaceVariant }
|
||||
NText { text: modelData.address || "-"; pointSize: Style.fontSizeXS; color: Color.mOnSurface }
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginS
|
||||
NIcon { icon: "shield-check"; pointSize: Style.fontSizeM; color: Color.mOnSurface }
|
||||
NText { text: I18n.tr("bluetooth.panel.paired") + ": " + (modelData.paired ? I18n.tr("common.yes") : I18n.tr("common.no")); pointSize: Style.fontSizeXS; color: Color.mOnSurface }
|
||||
NText { text: "•"; pointSize: Style.fontSizeXS; color: Color.mOnSurfaceVariant }
|
||||
NText { text: I18n.tr("bluetooth.panel.trusted") + ": " + (modelData.trusted ? I18n.tr("common.yes") : I18n.tr("common.no")); pointSize: Style.fontSizeXS; color: Color.mOnSurface }
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
visible: modelData.batteryAvailable
|
||||
spacing: Style.marginS
|
||||
NIcon { icon: "battery"; pointSize: Style.fontSizeM; color: Color.mOnSurface }
|
||||
NText { text: BluetoothService.getBattery(modelData); pointSize: Style.fontSizeXS; color: Color.mOnSurface }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,6 +151,7 @@ SmartPanel {
|
||||
if (!BluetoothService.adapter || !Bluetooth.devices)
|
||||
return [];
|
||||
var filtered = Bluetooth.devices.values.filter(dev => dev && !dev.blocked && dev.connected);
|
||||
filtered = BluetoothService.dedupeDevices(filtered);
|
||||
return BluetoothService.sortDevices(filtered);
|
||||
}
|
||||
model: items
|
||||
@@ -158,14 +159,15 @@ SmartPanel {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Known devices
|
||||
// Paired devices
|
||||
BluetoothDevicesList {
|
||||
label: I18n.tr("bluetooth.panel.known-devices")
|
||||
label: I18n.tr("bluetooth.panel.paired-devices")
|
||||
tooltipText: I18n.tr("tooltips.connect-disconnect-devices")
|
||||
property var items: {
|
||||
if (!BluetoothService.adapter || !Bluetooth.devices)
|
||||
return [];
|
||||
var filtered = Bluetooth.devices.values.filter(dev => dev && !dev.blocked && !dev.connected && (dev.paired || dev.trusted));
|
||||
filtered = BluetoothService.dedupeDevices(filtered);
|
||||
return BluetoothService.sortDevices(filtered);
|
||||
}
|
||||
model: items
|
||||
@@ -173,13 +175,14 @@ SmartPanel {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Available devices
|
||||
// Available devices (for pairing)
|
||||
BluetoothDevicesList {
|
||||
label: I18n.tr("bluetooth.panel.available-devices")
|
||||
property var items: {
|
||||
if (!BluetoothService.adapter || !Bluetooth.devices)
|
||||
return [];
|
||||
var filtered = Bluetooth.devices.values.filter(dev => dev && !dev.blocked && !dev.paired && !dev.trusted);
|
||||
filtered = BluetoothService.dedupeDevices(filtered);
|
||||
return BluetoothService.sortDevices(filtered);
|
||||
}
|
||||
model: items
|
||||
|
||||
@@ -13,6 +13,8 @@ NBox {
|
||||
property var model: []
|
||||
property string passwordSsid: ""
|
||||
property string expandedSsid: ""
|
||||
// Currently expanded info panel for a connected SSID
|
||||
property string infoSsid: ""
|
||||
|
||||
signal passwordRequested(string ssid)
|
||||
signal passwordSubmitted(string ssid, string password)
|
||||
@@ -203,6 +205,22 @@ NBox {
|
||||
size: Style.baseWidgetSize * 0.5
|
||||
}
|
||||
|
||||
// Info toggle for connected network
|
||||
NIconButton {
|
||||
visible: modelData.connected && NetworkService.disconnectingFrom !== modelData.ssid
|
||||
icon: "info-circle"
|
||||
tooltipText: I18n.tr("wifi.panel.info")
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: {
|
||||
if (root.infoSsid === modelData.ssid) {
|
||||
root.infoSsid = "";
|
||||
} else {
|
||||
root.infoSsid = modelData.ssid;
|
||||
NetworkService.refreshActiveWifiDetails();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
visible: (modelData.existing || modelData.cached) && !modelData.connected && NetworkService.connectingTo !== modelData.ssid && NetworkService.forgettingNetwork !== modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid
|
||||
icon: "trash"
|
||||
@@ -243,6 +261,60 @@ NBox {
|
||||
}
|
||||
}
|
||||
|
||||
// Connection info details
|
||||
Rectangle {
|
||||
visible: root.infoSsid === modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid && NetworkService.forgettingNetwork !== modelData.ssid
|
||||
Layout.fillWidth: true
|
||||
color: Color.mSurfaceVariant
|
||||
radius: Style.radiusS
|
||||
border.width: Style.borderS
|
||||
border.color: Color.mOutline
|
||||
height: infoColumn.implicitHeight + Style.marginS * 2
|
||||
|
||||
ColumnLayout {
|
||||
id: infoColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginS
|
||||
spacing: Style.marginXS
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginS
|
||||
NIcon { icon: NetworkService.signalIcon(modelData.signal, modelData.connected); pointSize: Style.fontSizeM; color: Color.mOnSurface }
|
||||
NText { text: I18n.tr("system.signal-strength", {"signal": modelData.signal}); pointSize: Style.fontSizeXS; color: Color.mOnSurface }
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginS
|
||||
NIcon { icon: "lock"; pointSize: Style.fontSizeM; color: Color.mOnSurface }
|
||||
NText { text: I18n.tr("wifi.panel.security") + ": "; pointSize: Style.fontSizeXS; color: Color.mOnSurfaceVariant }
|
||||
NText { text: NetworkService.isSecured(modelData.security) ? modelData.security : "Open"; pointSize: Style.fontSizeXS; color: Color.mOnSurface }
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginS
|
||||
NIcon { icon: NetworkService.internetConnectivity ? "world" : "world-off"; pointSize: Style.fontSizeM; color: NetworkService.internetConnectivity ? Color.mOnSurface : Color.mError }
|
||||
NText { text: NetworkService.internetConnectivity ? I18n.tr("wifi.panel.internet-connected") : I18n.tr("wifi.panel.internet-limited"); pointSize: Style.fontSizeXS; color: NetworkService.internetConnectivity ? Color.mOnSurface : Color.mError }
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginS
|
||||
NIcon { icon: "activity"; pointSize: Style.fontSizeM; color: Color.mOnSurface }
|
||||
NText { text: I18n.tr("wifi.panel.link-speed") + ": "; pointSize: Style.fontSizeXS; color: Color.mOnSurfaceVariant }
|
||||
NText { text: (NetworkService.activeWifiDetails.rate || "-"); pointSize: Style.fontSizeXS; color: Color.mOnSurface }
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginS
|
||||
NIcon { icon: "router"; pointSize: Style.fontSizeM; color: Color.mOnSurface }
|
||||
NText { text: "IPv4: "; pointSize: Style.fontSizeXS; color: Color.mOnSurfaceVariant }
|
||||
NText { text: (NetworkService.activeWifiDetails.ipv4 || "-"); pointSize: Style.fontSizeXS; color: Color.mOnSurface }
|
||||
NText { text: "•"; pointSize: Style.fontSizeXS; color: Color.mOnSurfaceVariant }
|
||||
NText { text: I18n.tr("wifi.panel.gateway") + ": "; pointSize: Style.fontSizeXS; color: Color.mOnSurfaceVariant }
|
||||
NText { text: (NetworkService.activeWifiDetails.gateway4 || "-"); pointSize: Style.fontSizeXS; color: Color.mOnSurface }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Password input
|
||||
Rectangle {
|
||||
visible: root.passwordSsid === modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid && NetworkService.forgettingNetwork !== modelData.ssid
|
||||
|
||||
@@ -152,7 +152,8 @@ Singleton {
|
||||
Example: once your headphones are paired, you don’t need to type a PIN every time.
|
||||
Hence, instead of !device.paired, should be device.connected
|
||||
*/
|
||||
return !device.connected && !device.pairing && !device.blocked;
|
||||
// Only allow connect if device is already paired or trusted
|
||||
return !device.connected && (device.paired || device.trusted) && !device.pairing && !device.blocked;
|
||||
}
|
||||
|
||||
function canDisconnect(device) {
|
||||
@@ -226,6 +227,74 @@ Singleton {
|
||||
return device.pairing || device.state === BluetoothDeviceState.Disconnecting || device.state === BluetoothDeviceState.Connecting;
|
||||
}
|
||||
|
||||
// Return a stable unique key for a device (prefer MAC address)
|
||||
function deviceKey(device) {
|
||||
if (!device)
|
||||
return "";
|
||||
if (device.address && device.address.length > 0)
|
||||
return device.address.toUpperCase();
|
||||
if (device.nativePath && device.nativePath.length > 0)
|
||||
return device.nativePath;
|
||||
if (device.devicePath && device.devicePath.length > 0)
|
||||
return device.devicePath;
|
||||
return (device.name || device.deviceName || "") + "|" + (device.icon || "");
|
||||
}
|
||||
|
||||
// Deduplicate a list of devices using the stable key
|
||||
function dedupeDevices(devList) {
|
||||
if (!devList || devList.length === 0)
|
||||
return [];
|
||||
const seen = ({});
|
||||
const out = [];
|
||||
for (let i = 0; i < devList.length; ++i) {
|
||||
const d = devList[i];
|
||||
if (!d)
|
||||
continue;
|
||||
const key = deviceKey(d);
|
||||
if (key && !seen[key]) {
|
||||
seen[key] = true;
|
||||
out.push(d);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Separate capability helpers
|
||||
function canPair(device) {
|
||||
if (!device)
|
||||
return false;
|
||||
return !device.connected && !device.paired && !device.trusted && !device.pairing && !device.blocked;
|
||||
}
|
||||
|
||||
// Pairing and unpairing helpers
|
||||
function pairDevice(device) {
|
||||
if (!device)
|
||||
return;
|
||||
try {
|
||||
if (typeof device.pair === 'function') {
|
||||
device.pair();
|
||||
} else {
|
||||
// Fallback: trust and connect (most stacks will pair during connect)
|
||||
device.trusted = true;
|
||||
device.connect();
|
||||
}
|
||||
} catch (e) {
|
||||
Logger.w("Bluetooth", "pairDevice failed", e);
|
||||
// Fallback to connect if pair not supported
|
||||
try {
|
||||
device.trusted = true;
|
||||
device.connect();
|
||||
} catch (e2) {
|
||||
Logger.w("Bluetooth", "pairDevice connect fallback failed", e2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function unpairDevice(device) {
|
||||
// Alias to forgetDevice for clarity in UI
|
||||
forgetDevice(device);
|
||||
}
|
||||
|
||||
function connectDeviceWithTrust(device) {
|
||||
if (!device) {
|
||||
return;
|
||||
|
||||
@@ -23,6 +23,10 @@ Singleton {
|
||||
property bool ignoreScanResults: false
|
||||
property bool scanPending: false
|
||||
|
||||
// Active Wi‑Fi connection details (for info panel)
|
||||
property var activeWifiDetails: ({})
|
||||
property string activeWifiIf: ""
|
||||
|
||||
// Persistent cache
|
||||
property string cacheFile: Settings.cacheDir + "network.json"
|
||||
readonly property string cachedLastConnected: cacheAdapter.lastConnected
|
||||
@@ -79,6 +83,13 @@ Singleton {
|
||||
onTriggered: cacheFileView.writeAdapter()
|
||||
}
|
||||
|
||||
// Refresh details for the currently active Wi‑Fi link
|
||||
function refreshActiveWifiDetails() {
|
||||
activeWifiDetails = ({})
|
||||
activeWifiIf = ""
|
||||
wifiDeviceListProcess.running = true;
|
||||
}
|
||||
|
||||
function saveCache() {
|
||||
saveDebounce.restart();
|
||||
}
|
||||
@@ -256,6 +267,99 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
// Discover connected Wi‑Fi interface
|
||||
Process {
|
||||
id: wifiDeviceListProcess
|
||||
running: false
|
||||
command: ["nmcli", "-t", "-f", "DEVICE,TYPE,STATE", "device"]
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
let ifname = "";
|
||||
const lines = text.split("\n");
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const parts = lines[i].trim().split(":");
|
||||
if (parts.length >= 3) {
|
||||
const dev = parts[0];
|
||||
const type = parts[1];
|
||||
const state = parts[2];
|
||||
if (type === "wifi" && state === "connected") {
|
||||
ifname = dev;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
root.activeWifiIf = ifname;
|
||||
if (ifname) {
|
||||
wifiDeviceShowProcess.ifname = ifname;
|
||||
wifiDeviceShowProcess.running = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch IPv4 and gateway for the interface
|
||||
Process {
|
||||
id: wifiDeviceShowProcess
|
||||
property string ifname: ""
|
||||
running: false
|
||||
command: ["nmcli", "-t", "-f", "IP4.ADDRESS,IP4.GATEWAY", "device", "show", ifname]
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const details = root.activeWifiDetails || ({});
|
||||
let ipv4 = "";
|
||||
let gw4 = "";
|
||||
const lines = text.split("\n");
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i].trim();
|
||||
if (!line) continue;
|
||||
const idx = line.indexOf(":");
|
||||
if (idx === -1) continue;
|
||||
const key = line.substring(0, idx);
|
||||
const val = line.substring(idx + 1);
|
||||
if (key.startsWith("IP4.ADDRESS")) {
|
||||
ipv4 = val.split("/")[0];
|
||||
} else if (key === "IP4.GATEWAY") {
|
||||
gw4 = val;
|
||||
}
|
||||
}
|
||||
details.ipv4 = ipv4;
|
||||
details.gateway4 = gw4;
|
||||
root.activeWifiDetails = details;
|
||||
|
||||
// Try to get link rate (best effort)
|
||||
wifiIwLinkProcess.ifname = wifiDeviceShowProcess.ifname;
|
||||
wifiIwLinkProcess.running = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Optional: query Wi‑Fi bitrate via iw if available
|
||||
Process {
|
||||
id: wifiIwLinkProcess
|
||||
property string ifname: ""
|
||||
running: false
|
||||
command: ["sh", "-c", "iw dev '" + ifname + "' link 2>/dev/null || true"]
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const details = root.activeWifiDetails || ({});
|
||||
let rate = "";
|
||||
const lines = text.split("\n");
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i].trim();
|
||||
if (line.toLowerCase().startsWith("tx bitrate:")) {
|
||||
rate = line.substring(11).trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
details.rate = rate;
|
||||
root.activeWifiDetails = details;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only check the state of the actual interface
|
||||
// and update our setting to be in sync.
|
||||
Process {
|
||||
|
||||
Reference in New Issue
Block a user