Share a Wi-Fi network by QR code

GNOME's Wi-Fi panel has this and it is the most-used thing in it: the
alternative is reading a passphrase out loud. Network & Devices now shows
a scannable code for any saved network whose passphrase this user can
read.

The image contains the network password in machine-readable form, so
most of the care here is about that rather than about QR codes. It is
written under XDG_RUNTIME_DIR -- 0700, on tmpfs, gone at logout --
rather than /tmp, which is shared; the file is 0600; the passphrase is
piped to qrencode on stdin rather than passed as an argument, because
argv is world-readable through /proc for as long as the process runs;
and it is never printed or included in an error message. Generated on
demand, because producing a code for every saved network up front means
writing images of passwords nobody asked to see.

Enterprise networks are listed as not shareable rather than offered and
broken: there is no passphrase to encode, so the code could not work.

Two bugs the contract caught while being written. Semicolons in an SSID
were not escaped -- the sed replacement had one backslash where its four
neighbours have two, so sed dropped it, and an SSID containing a
semicolon would have produced a QR code describing a different network.
And nmcli's trailing newline landed inside the payload; it decoded here,
but a newline in the middle of a WIFI: URI is not something every phone
tolerates, and that failure would present as "the QR code just doesn't
work on my phone".

The contract stubs nmcli and qrencode, because the real ones would write
this machine's actual Wi-Fi password into a fixture directory. It
asserts the escaping, the absence of a newline, the file and directory
modes, that no temporary payload survives, and that the passphrase never
reaches argv.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
This commit is contained in:
Gabriel Brown
2026-08-18 10:56:01 -04:00
parent 54b8a82803
commit 4279da999a
4 changed files with 415 additions and 1 deletions
@@ -0,0 +1,99 @@
pragma Singleton
// A QR code for a saved Wi-Fi network, so a guest can join by pointing a phone
// at the screen. GNOME's Wi-Fi panel has this and it is the most-used thing in
// it; reading a passphrase aloud is the alternative.
//
// The generated image contains the network password in machine-readable form,
// so the helper writes it under XDG_RUNTIME_DIR -- 0700, on tmpfs, gone at
// logout -- rather than anywhere persistent. Nothing here ever holds the
// passphrase itself; this service only ever sees a file path.
//
// Generated on demand. Producing a QR for every saved network up front would
// mean writing images of passwords nobody asked to see.
import Quickshell
import Quickshell.Io
import QtQuick
Singleton {
id: root
readonly property string helperPath: Quickshell.shellDir + "/scripts/panama-wifi-qr"
// [{ name, ssid, shareable }]
property var networks: []
property bool scanned: false
property string lastError: ""
// The network whose code is on screen, and where its image is. Empty when
// nothing is being shared.
property string sharing: ""
property string imagePath: ""
readonly property var shareable: root.networks.filter(n => n.shareable)
function refresh(): void {
if (!list.running)
list.running = true;
}
function share(name: string): void {
if (generate.running)
return;
// Cache-bust: the helper reuses one file per network, so a QML Image
// pointed at the same path would keep showing the previous render.
root.imagePath = "";
root.sharing = name;
generate.command = [root.helperPath, "qr", name];
generate.running = true;
}
function stopSharing(): void {
root.sharing = "";
root.imagePath = "";
}
Process {
id: list
command: [root.helperPath, "list"]
stdout: StdioCollector {
onStreamFinished: {
try {
const parsed = JSON.parse(this.text);
root.networks = Array.isArray(parsed.networks) ? parsed.networks : [];
root.lastError = String(parsed.error ?? "");
} catch (error) {
root.networks = [];
root.lastError = "Could not read the Wi-Fi helper's output.";
console.warn("WifiShare: could not parse helper output:", error);
}
root.scanned = true;
}
}
}
Process {
id: generate
stdout: StdioCollector {
onStreamFinished: {
try {
const parsed = JSON.parse(this.text);
const path = String(parsed.path ?? "");
const error = String(parsed.error ?? "");
if (error !== "" || path === "") {
root.lastError = error !== "" ? error : "No QR code was produced.";
root.sharing = "";
return;
}
root.lastError = "";
root.imagePath = path;
} catch (error) {
root.lastError = "Could not read the generated QR code's path.";
root.sharing = "";
console.warn("WifiShare: could not parse helper output:", error);
}
}
}
}
}