Stop closed network panels from leaving Wi-Fi scanning enabled (#6772)
* Stop closed network panels from leaving Wi-Fi scanning enabled refresh() defaults scanWifi to false and its no-scan branch enabled the scanner unconditionally. Five paths reach it with no panel on screen — Component.onCompleted, clearNetworkAction(), failNetworkAction(), the band-change actionProc exit, and the 30s actionTimeout — so the scanner stayed on and Quickshell kept re-arming RequestScan behind a closed panel. scanRestart had the mirror gap: it enabled the scanner 100ms after refresh(true) without re-checking that the panel was still open. Every sweep takes the radio off the operating channel, so this degraded the link it was scanning from: one sweep every 17s, gateway RTT rising from ~2ms to repeated 150ms+ spikes on an otherwise idle connection. Gate the scanner block on the panel being open, cancel a pending restart on close and re-check the panel when it fires, and track the WifiDevice this instance enabled so close, device replacement and destruction release the right object. Destruction matters on its own: a bar reload with the panel open would otherwise die with opened still true and never write scannerEnabled = false. * Cover the scanner ownership helper's own invariants The previous assertion only pinned that no write bypasses setScannerEnabled(); it said nothing about what the helper does. Dropping either the opened gate or the release-before-adopt from the helper still passed, while a closed instance could reclaim scanning and a device swap could leave the previous interface scanning. Run the helper's actual JavaScript against stand-in devices instead, following the extract-and-eval pattern the agents panel tests already use. Removing either invariant now fails its own assertion.
This commit is contained in:
@@ -292,6 +292,29 @@ Panel {
|
||||
readonly property color hoverFill: bar ? Style.hoverFillFor(bar.foreground, Color.accent) : "transparent"
|
||||
readonly property color selectedFill: bar ? Style.selectedFillFor(bar.foreground, Color.accent) : "transparent"
|
||||
|
||||
// scannerEnabled lives on the shared WifiDevice, which has no reference
|
||||
// counting, and a bar widget is instantiated once per monitor. Tracking the
|
||||
// device this instance turned scanning on for keeps the release correct when
|
||||
// the panel closes, the device is replaced, or the widget is destroyed —
|
||||
// without a closed instance ever claiming the scanner.
|
||||
property var scannerDevice: null
|
||||
|
||||
function setScannerEnabled(enabled) {
|
||||
var nextDevice = opened ? wifiDevice : null
|
||||
|
||||
if (scannerDevice && scannerDevice !== nextDevice)
|
||||
scannerDevice.scannerEnabled = false
|
||||
|
||||
scannerDevice = nextDevice
|
||||
|
||||
if (scannerDevice)
|
||||
scannerDevice.scannerEnabled = enabled
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
if (scannerDevice) scannerDevice.scannerEnabled = false
|
||||
}
|
||||
|
||||
// KeyboardPanel primes layer-shell focus whenever the panel opens. That's
|
||||
// what makes the SUPER+CTRL+W keybind land here with navigation ready.
|
||||
onOpenedChanged: {
|
||||
@@ -305,6 +328,10 @@ Panel {
|
||||
syncBandIndex()
|
||||
cursorActive = false
|
||||
} else {
|
||||
// Drop a restart armed by this open: without it a close/reopen inside
|
||||
// the 100ms window reuses the running timer and re-enables the scanner
|
||||
// almost immediately, undoing the deferral #6605 restored.
|
||||
scanRestart.stop()
|
||||
// Reset throughput tracking so the next open doesn't compute a fake
|
||||
// rate from a sample taken minutes ago.
|
||||
prevSampleTime = 0
|
||||
@@ -316,7 +343,7 @@ Panel {
|
||||
routerPingLatency = -1
|
||||
internetPingLatency = -1
|
||||
internetPingPacketLoss = 0
|
||||
if (wifiDevice) wifiDevice.scannerEnabled = false
|
||||
setScannerEnabled(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,7 +384,7 @@ Panel {
|
||||
}
|
||||
|
||||
onWifiDeviceChanged: {
|
||||
if (wifiDevice) wifiDevice.scannerEnabled = opened
|
||||
setScannerEnabled(true)
|
||||
syncWifiNetworks()
|
||||
}
|
||||
|
||||
@@ -452,13 +479,15 @@ Panel {
|
||||
bandProc.command = ["omarchy-network-band"]
|
||||
bandProc.running = true
|
||||
}
|
||||
if (wifiDevice) {
|
||||
// A closed panel has no nearby-network list to fill, and bare refresh()
|
||||
// reaches here from action completion, timeouts and construction.
|
||||
if (opened && wifiDevice) {
|
||||
if (scanWifi) {
|
||||
scanning = true
|
||||
wifiDevice.scannerEnabled = false
|
||||
setScannerEnabled(false)
|
||||
scanRestart.start()
|
||||
} else {
|
||||
wifiDevice.scannerEnabled = true
|
||||
setScannerEnabled(true)
|
||||
}
|
||||
}
|
||||
syncWifiNetworks()
|
||||
@@ -792,8 +821,10 @@ Panel {
|
||||
interval: 100
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (root.wifiDevice) root.wifiDevice.scannerEnabled = true
|
||||
scanDone.start()
|
||||
if (root.opened && root.wifiDevice) {
|
||||
root.setScannerEnabled(true)
|
||||
scanDone.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,60 @@ assert(barPress, 'network bar button has an onPressed handler')
|
||||
const barPressCode = barPress[0].replace(/\/\/.*$/gm, '')
|
||||
assert(!/refresh\(/.test(barPressCode), 'network bar click opens the panel without a second refresh that would undo the deferred scan')
|
||||
|
||||
// A closed panel has no nearby-network list to fill. Quickshell's scanner
|
||||
// re-arms RequestScan on its own timer, and every sweep takes the radio off
|
||||
// the operating channel, so a scanner left enabled behind a closed panel keeps
|
||||
// degrading the connection it is scanning from.
|
||||
const refreshFn = panelSource.match(/function refresh\(scanWifi\)[\s\S]*?\n {2}\}/)
|
||||
assert(refreshFn, 'network has a refresh() function')
|
||||
assert(/if \(opened && wifiDevice\)/.test(refreshFn[0]), 'network only touches the scanner from refresh() while its panel is open')
|
||||
|
||||
// The 100ms deferral can outlive the panel: closing inside the window would
|
||||
// otherwise re-enable scanning from a timer nobody is watching.
|
||||
const scanRestart = panelSource.match(/id: scanRestart[\s\S]*?onTriggered: \{[\s\S]*?\n {4}\}/)
|
||||
assert(scanRestart, 'network has the deferred scan restart timer')
|
||||
assert(/root\.opened/.test(scanRestart[0]), 'network re-checks the panel before the deferred restart re-enables scanning')
|
||||
assert(/scanRestart\.stop\(\)/.test(panelSource), 'network cancels a pending scan restart when the panel closes')
|
||||
|
||||
// scannerEnabled lives on a shared WifiDevice with no reference counting, so
|
||||
// the panel has to own what it enabled. Run the helper's own JavaScript against
|
||||
// stand-in devices: the two invariants it carries are that a closed panel never
|
||||
// takes a device, and that adopting a new one releases the previous.
|
||||
const scannerHelper = panelSource.match(/function setScannerEnabled\(enabled\) \{[\s\S]*?\n {2}\}/)
|
||||
assert(scannerHelper, 'network has a scanner ownership helper')
|
||||
|
||||
var opened = false
|
||||
var wifiDevice = { scannerEnabled: false }
|
||||
var scannerDevice = null
|
||||
eval(scannerHelper[0])
|
||||
|
||||
setScannerEnabled(true)
|
||||
assert(
|
||||
scannerDevice === null && wifiDevice.scannerEnabled === false,
|
||||
'network does not let a closed panel claim or enable a scanner device'
|
||||
)
|
||||
|
||||
var previousScannerDevice = { scannerEnabled: true }
|
||||
var replacementScannerDevice = { scannerEnabled: false }
|
||||
opened = true
|
||||
scannerDevice = previousScannerDevice
|
||||
wifiDevice = replacementScannerDevice
|
||||
setScannerEnabled(true)
|
||||
assert(
|
||||
previousScannerDevice.scannerEnabled === false &&
|
||||
scannerDevice === replacementScannerDevice &&
|
||||
replacementScannerDevice.scannerEnabled === true,
|
||||
'network releases the previous scanner device before enabling its replacement'
|
||||
)
|
||||
|
||||
// Destruction is the case a guard-only fix misses: the widget dies with the
|
||||
// panel still open, as a bar reload does, and nothing else would release it.
|
||||
assert(
|
||||
/Component\.onDestruction[\s\S]{0,140}scannerDevice\.scannerEnabled = false/.test(panelSource),
|
||||
'network releases the scanner it owns when the widget is destroyed'
|
||||
)
|
||||
assert(!/wifiDevice\.scannerEnabled\s*=/.test(panelSource), 'network writes scanner state through its owned device reference rather than the moving wifiDevice reference')
|
||||
|
||||
// A row is a primitive snapshot that can outlive its WifiNetwork, and
|
||||
// disconnect() falls back to the live connection when handed null, so row
|
||||
// activation must go through the guarded disconnectRow().
|
||||
|
||||
Reference in New Issue
Block a user