Extract the Wi-Fi QR share card into its own omarchy.wifiqr panel plugin (#6598)

* Extract the Wi-Fi QR share card into its own omarchy.wifiqr panel plugin

omarchy-network-qr now leads with an iface/security/ssid meta line, so a
bare summon self-detects the connection and the plugin owns the whole
share flow. The network panel loses its overlay lifecycle: with no
centered card left inside it, the shadowed open/close collapses back to
the stock panel behavior, and the QR button just summons the plugin --
which a clone or third-party plugin can replace, like the speed test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Keep canceled QR and password runs from leaking into their replacements

Copilot review: the cancellation guards dropped in onExited while the
canceled run's collectors were still allowed to fire, so a stale stderr
could shadow a successful regeneration and a stale password could be
revealed under a new network's card. The guards now stay up until the
next run launches, good output settles any earlier error, and a bare
re-summon no longer inherits the previous card's SSID.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
David Heinemeier Hansson
2026-08-07 12:50:04 +02:00
committed by GitHub
co-authored by Claude Fable 5
parent 474d9dd4eb
commit 018f881840
12 changed files with 519 additions and 389 deletions
+16 -156
View File
@@ -17,26 +17,9 @@ Panel {
manageIpc: false
// Centralized close so callers can't forget to drop the passphrase prompt.
readonly property bool overlayVisible: qrVisible
// Shadows the base open(): a summon or toggle while a centered card is up
// dismisses the card instead of opening the compact panel behind an
// exclusive overlay. The base toggle() dispatches here, so the keybind,
// the bar icon, and every IPC route all get this behavior.
function open() {
if (overlayVisible) {
hideWifiQr()
return
}
root.controller.show()
}
function close() {
root.controller.hide()
cancelPasswordPrompt()
// The centered card outlives the compact panel, but the widget's
// canonical close must not leave an overlay behind.
hideWifiQr()
}
function cancelPasswordPrompt() {
@@ -115,18 +98,6 @@ Panel {
property string passwordText: ""
property string identityText: ""
property var qrRows: []
property int qrSize: 0
property string qrError: ""
property bool qrLoading: false
property bool qrExpectedStop: false
property bool pendingQrShow: false
property bool pendingQrDetect: false
property string qrPassword: ""
property bool qrPasswordVisible: false
property string qrPasswordError: ""
readonly property bool qrVisible: qrLoading || qrSize > 0 || qrError !== ""
// True while any wifi action is mid-flight. Rows
// disable themselves on this so clicks on the other rows don't silently
// no-op against runNetworkAction's serialized guard.
@@ -235,16 +206,14 @@ Panel {
function hide() { root.close() }
function toggle() { root.toggle() }
function toggleNetwork() { root.toggleNetwork() }
// Menu routes: summon the centered cards directly, panel open or not.
function showQr() {
root.refresh()
root.showWifiQr(true)
}
// Compat routes for configs that summon the centered cards through the
// network target; both cards are their own plugins now.
function showQr() { root.summonWifiQr(true) }
function speedTest() { root.summonSpeedTest() }
}
function activateHeader() {
if (headerIndex === qrHeaderIndex) showWifiQr()
if (headerIndex === qrHeaderIndex) summonWifiQr()
else if (headerIndex === speedHeaderIndex) summonSpeedTest()
else if (headerIndex === toggleHeaderIndex) toggleNetwork()
}
@@ -443,63 +412,20 @@ Panel {
readonly property string icon: Model.connectionIcon(kind, signalStrength)
function showWifiQr(forceDetect) {
if (qrProc.running) {
// A dismissal's SIGTERM is still in flight; Process.running stays true
// until the child exits, so queue the reopen for onExited.
if (qrExpectedStop) {
pendingQrShow = true
pendingQrDetect = !!forceDetect
}
return
}
qrSize = 0
qrRows = []
qrError = ""
qrLoading = true
qrExpectedStop = false
// The panel's own button shares the interface it is showing. The IPC
// route forces self-detection instead: details polling stops while the
// panel is closed, so its cached interface can be stale.
qrProc.command = !forceDetect && info.type === "wifi" && info.iface
? ["omarchy-network-qr", info.iface]
: ["omarchy-network-qr"]
qrProc.running = true
// Leave the compact network panel behind while the centered share card is open.
// The share card is its own panel plugin (omarchy.wifiqr) so a replacement
// design can take it over; summon() routes to whichever implementation is
// enabled. The panel's own button pins the interface it is showing. The
// IPC route forces self-detection instead: details polling stops while the
// panel is closed, so its cached interface can be stale.
function summonWifiQr(forceDetect) {
controller.hide()
cancelPasswordPrompt()
}
function hideWifiQr() {
pendingQrShow = false
if (qrProc.running) {
qrExpectedStop = true
qrProc.running = false
var payload = {}
if (!forceDetect && info.type === "wifi" && info.iface) {
payload.iface = info.iface
if (info.ssid) payload.ssid = info.ssid
}
if (pwProc.running) pwProc.running = false
qrSize = 0
qrRows = []
qrError = ""
qrLoading = false
qrPassword = ""
qrPasswordVisible = false
qrPasswordError = ""
}
function updateQr(raw) {
var matrix = Model.parseQrMatrix(raw)
qrRows = matrix.rows
qrSize = matrix.size
}
function toggleQrPassword() {
if (qrPasswordVisible) { qrPasswordVisible = false; return }
if (qrPassword !== "") { qrPasswordVisible = true; return }
if (pwProc.running || !info.iface) return
qrPasswordError = ""
pwProc.command = ["omarchy-network-password", info.iface]
pwProc.running = true
bar.shell.summon("omarchy.wifiqr", JSON.stringify(payload))
}
function refresh(scanWifi) {
@@ -858,55 +784,6 @@ Panel {
onTriggered: root.syncWifiNetworks()
}
Process {
id: qrProc
// Both collectors check qrExpectedStop: a dismissal mid-generation kills
// the process, but buffered output still arrives afterwards and would
// repopulate qrSize -- reopening the card the user just closed. The flag
// stays set through onExited (showWifiQr resets it) because the exit and
// stream-finished signals have no guaranteed order.
stdout: StdioCollector {
waitForEnd: true
onStreamFinished: if (!root.qrExpectedStop) root.updateQr(text)
}
stderr: StdioCollector {
waitForEnd: true
onStreamFinished: if (!root.qrExpectedStop) root.qrError = String(text || "").trim()
}
onExited: function(exitCode) {
root.qrLoading = false
if (root.pendingQrShow) {
root.pendingQrShow = false
root.qrExpectedStop = false
Qt.callLater(function() { root.showWifiQr(root.pendingQrDetect) })
return
}
if (root.qrExpectedStop) return
if (exitCode !== 0 || root.qrSize === 0) {
root.qrSize = 0
root.qrRows = []
if (root.qrError === "") root.qrError = "Could not generate the Wi-Fi QR code"
}
}
}
// The Wi-Fi password only enters shell memory when the user clicks to
// reveal it, and hideWifiQr drops it again when the share card closes.
// Both handlers bail when the card is gone so a fetch that was in flight
// during dismissal can't stash the secret into a closed panel's state.
Process {
id: pwProc
stdout: StdioCollector {
waitForEnd: true
onStreamFinished: if (root.qrVisible) root.qrPassword = String(text || "").trim()
}
onExited: function(exitCode) {
if (!root.qrVisible) return
if (exitCode === 0 && root.qrPassword !== "") root.qrPasswordVisible = true
else root.qrPasswordError = "Could not read the Wi-Fi password"
}
}
Process {
id: dnsProc
stdout: StdioCollector {
@@ -1184,7 +1061,7 @@ Panel {
hasCursor: root.qrHeaderHasCursor
Layout.alignment: Qt.AlignVCenter
onHovered: function(on) { if (on) root.setHeaderCursor(root.qrHeaderIndex) }
onClicked: root.showWifiQr()
onClicked: root.summonWifiQr()
}
Button {
@@ -1587,23 +1464,6 @@ Panel {
}
}
WifiQrPanel {
anchorItem: button
bar: root.bar
qrRows: root.qrRows
qrSize: root.qrSize
loading: root.qrLoading
error: root.qrError
ssid: root.info.ssid || ""
secured: root.connectedWifiNetwork ? root.isProtected(root.connectedWifiNetwork.security) : false
password: root.qrPassword
passwordVisible: root.qrPasswordVisible
passwordError: root.qrPasswordError
open: root.qrVisible
onCloseRequested: root.hideWifiQr()
onPasswordToggleRequested: root.toggleQrPassword()
}
// One Wi-Fi band pill. `active` (fill) is the band actually in use and
// `selected` (bold) is the pinned choice; with Automatic on nothing is
// pinned, so only the live band lights up and the two can no longer read as