Files
omarchycn/shell/plugins/panels/network/WifiQrPanel.qml
T
a79d1dc8da Add Wi-Fi QR sharing to network panel (#6463)
* Add Wi-Fi QR sharing to network panel

* Refine Wi-Fi QR sharing

* Use QR glyph for Wi-Fi sharing

* Add click-to-reveal password to the Wi-Fi share card

Scanning the QR is the fast path, but the person typing on a laptop needs
the actual password. A dimmed "Show password" hint under the QR toggles
the secret in place.

The password stays out of the shell until asked for: a click runs the new
omarchy-network-password helper (a private pipe, never an argument), and
closing the card drops it again. Open and enterprise networks never show
the control.

The card loses its Close button -- Escape and clicking outside already
cover it -- and now sizes itself to its content.

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

* Keep a dismissed Wi-Fi share card closed and make Escape reliable

Closing the card mid-generation killed the helper, but its buffered
stdout still arrived and repopulated the matrix, reopening the card the
user just closed. Both collectors now honor qrExpectedStop, and the flag
survives onExited because exit and stream-finished have no guaranteed
order. The password fetch gets the same treatment so a reveal in flight
during dismissal can't stash the secret into a closed card's state.

The content's focus was claimed while the window was still unmapped, so
Escape could land nowhere. Re-acquire it after mapping, the way
KeyboardPanel does.

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

* Share WEP networks correctly and restore the QR quiet zone

NetworkManager models WEP as key-mgmt "none" plus a wep-key, so the QR
helper encoded WEP networks as open -- a QR that scans fine and then
silently fails to join. Encode them as T:WEP and let the password helper
print the key.

Also widen qrencode's margin from 2 to the spec's 4-module quiet zone;
the card surround is dark, so that white border is all a scanner gets.

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

---------

Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 17:18:42 -04:00

172 lines
5.0 KiB
QML

import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
import qs.Commons
import qs.Ui
PanelWindow {
id: root
required property Item anchorItem
required property QtObject bar
required property var qrRows
required property int qrSize
required property bool loading
required property string error
required property string ssid
required property bool secured
required property string password
required property bool passwordVisible
required property string passwordError
property bool open: false
readonly property bool showingQr: qrSize > 0 && !loading && error === ""
signal closeRequested()
signal passwordToggleRequested()
visible: open
// The window is instantiated hidden, so the content's `focus: true` is
// evaluated before the surface is mapped and Escape would land nowhere.
// Re-acquire after mapping, as KeyboardPanel does.
onOpenChanged: {
if (open) Qt.callLater(function() {
if (root.open) keyCatcher.forceActiveFocus()
})
}
screen: anchorItem.QsWindow.window ? anchorItem.QsWindow.window.screen : null
anchors { top: true; bottom: true; left: true; right: true }
color: "transparent"
exclusionMode: ExclusionMode.Ignore
WlrLayershell.namespace: "omarchy-network-qr"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
Rectangle {
anchors.fill: parent
color: Qt.rgba(0, 0, 0, 0.45)
MouseArea {
anchors.fill: parent
onClicked: root.closeRequested()
}
}
BorderSurface {
width: Style.space(320)
height: content.implicitHeight + Style.space(48)
anchors.centerIn: parent
radius: Style.cornerRadius
color: Color.menu.background
borderSpec: Border.surfaceSpec("menu", "border", Color.popups.border, Math.max(1, Style.space(2)))
MouseArea { anchors.fill: parent; onClicked: {} }
Item {
id: keyCatcher
anchors.fill: parent
anchors.margins: Style.space(24)
focus: true
Keys.onEscapePressed: root.closeRequested()
ColumnLayout {
id: content
anchors.fill: parent
spacing: Style.space(12)
Text {
text: "Share " + (root.ssid || "Wi-Fi")
color: root.bar.foreground
font.family: root.bar.fontFamily
font.pixelSize: Style.font.title
font.bold: true
elide: Text.ElideRight
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
}
// Render every QR module as an integer-sized native rectangle. This
// stays crisp and avoids temporary images and file-cache races.
Rectangle {
id: qrCanvas
readonly property int moduleSize: root.qrSize > 0
? Math.max(4, Math.floor(Style.space(240) / root.qrSize))
: 0
visible: root.showingQr
width: root.qrSize * moduleSize
height: width
color: "white"
Layout.alignment: Qt.AlignHCenter
Grid {
anchors.fill: parent
columns: root.qrSize
Repeater {
model: root.qrSize * root.qrSize
Rectangle {
required property int index
readonly property int matrixRow: Math.floor(index / root.qrSize)
readonly property int matrixColumn: index % root.qrSize
width: qrCanvas.moduleSize
height: qrCanvas.moduleSize
color: root.qrRows[matrixRow].charAt(matrixColumn) === "1" ? "#111111" : "white"
}
}
}
}
Text {
visible: root.loading
text: "Generating QR code…"
color: root.bar.foreground
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
}
Text {
visible: root.error !== ""
text: root.error
color: root.bar.urgent
wrapMode: Text.Wrap
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
}
Text {
visible: root.showingQr
text: "Scan to join this network"
color: root.bar.foreground
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
}
Text {
visible: root.showingQr && root.secured
text: root.passwordError !== "" ? root.passwordError
: root.passwordVisible ? root.password
: "Show password"
color: root.passwordError !== "" ? root.bar.urgent : root.bar.foreground
opacity: root.passwordVisible || root.passwordError !== "" ? 1 : 0.6
font.family: root.bar.fontFamily
font.pixelSize: Style.font.bodySmall
wrapMode: Text.WrapAnywhere
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: root.passwordToggleRequested()
}
}
}
}
}
}