Direct answer
A QR kiosk runs the QR Send sender on a laptop or small display and loops one file as a continuous stream of QR frames. Anyone in the room can open the receive page or point a camera at the screen: on the same network the file also arrives over WebRTC, otherwise it comes optically.
Key takeaways
- The sender loops a single file as a QRTP stream, so the screen is an always-open offer rather than a session tied to one receiver.
- Full frames carry explicit stream headers while squeezed frames imply
stream_id = 0; the 15-bittransfer_idhas one bit left to mark squeezed frames. - Optical packets are 368 B (large) or 40 B (small), a Wirehair block carries
packet_size − 4bytes, and the 4-byteblock_idis the only per-block overhead. - One fountain holds up to 64,000 blocks; a payload that fits a single packet is sent in direct mode with no coding at all.
- On a shared network the peer connection is advertised in the metadata side stream (
stream_id = 1), and the receiver keeps whichever path finishes first.
The kiosk setup
One unattended machine runs the sender against a fixed file: an onboarding pack, a price list, tonight's build artifact. It draws QR frames continuously and never asks a visitor to touch it.
./qrsend --fps 15 --cols 100 --rows 40 --ecc low \
--label "office-brochure.pdf" brochure.pdf
--cols and --rows override the detected terminal size, --fps sets the frame rate (default 15), --ecc low maximises the byte payload per symbol, and --label fixes the filename the receiver sees. Omit --loops and the fountain keeps cycling forever; --once is shorthand for a single pass. Anything pipeable can be kiosk-ed the same way:
git bundle create - --all | ./qrsend --label repo.bundle -
A - in place of a path means stdin, so a build job can publish its artifact to the lobby without writing a temporary file. A visitor opens the receive page, or scans with the embedded receiver, and the file appears when enough blocks have been collected. No account, no install, no pairing. The same flags are covered in the QR Send CLI guide.
Two paths, same bytes
| Optical path | WebRTC path | |
|---|---|---|
| Who uses it | Guest network, air-gapped or offline device | Visitor on the same LAN or WiFi as the kiosk |
| How it starts | Camera sees the QR frames; no network involved | Sender advertises a peer connection in the optical metadata side stream (stream_id = 1) |
| Throughput | Camera-limited — 15 fps of QR frames | Network-limited, far above camera speed |
| Failure mode | More scanning time | Falls back to the optical stream, which never stopped |
| Result | The exact bytes of the looped file | The exact bytes of the looped file |
The receiver does not have to choose correctly in advance: it starts collecting optically and accepts the WebRTC copy as soon as negotiation completes, keeping whichever finishes first. Nothing about the kiosk changes based on which path a visitor ends up on, which is what makes the screen safe to leave in a lobby. It also means no upload, no expiry, and no third-party copy of the document — the trade-offs are in QR Send vs WeTransfer and cloud sharing.
Why the sender must keep serving
A visitor who walks away mid-transfer must not degrade the offer for the next person. A session-based sender tracks per-peer acknowledgements and has to clean that state up when a peer vanishes; a naive one stalls waiting for someone who is never coming back.
A QRTP kiosk is not a session. The sender emits fountain-coded blocks in a cycle, statelessly with respect to receivers:
- Each cycle is a complete offer. A visitor arriving mid-cycle collects blocks and finishes on a later cycle — there is no handshake to miss.
- No per-peer state accumulates, so nothing needs releasing when someone leaves.
- The codes stay up for the next person whether the last one finished, gave up, or walked out of range.
- A restart is just a new
transfer_idwith the same file and label, so a nightly restart looks like an ordinary cycle boundary.
Nobody has to reset the kiosk, and the front-desk machine needs no owner. For a visitor on a machine with no network at all, this is exactly the loop described in how to transfer files to and from air-gapped machines.
Practical considerations
| Concern | Arm's length (phone, hand-held) | Across a room (mounted display) |
|---|---|---|
| Display | Laptop screen or small monitor at desk height | Large panel at eye level; portrait if the sheet is portrait |
| Brightness | Indoor ambient is enough; avoid a window behind the screen | Raise brightness; a glossy screen reflecting a ceiling light kills scanning |
| Code size | Two to four QR codes per frame reads well at 30–60 cm | One or two larger codes; cap --qrs-per-frame low for a phone 3–4 m away |
| Frame rate | 15 fps is comfortable for a hand-held camera | 8–10 fps; faster flips blur on a distant camera |
| Error correction | Default is fine indoors | Higher ECC costs payload per symbol but tolerates blur and poor focus |
| Several visitors | Fine — everyone scans the same codes | Fine optically; the crowd pressure lands on WiFi via the WebRTC path |
The sender's cost is pixel work, not per-receiver bookkeeping, so ten phones scanning one screen cost the kiosk nothing extra. Code size trades against payload: --packets-per-qr controls how much of the file each visible symbol carries, so a mounted display wants larger symbols with more packets packed in, not more symbols per frame.
Deployment checklist
- Freeze the file and version the name (
price-list-2026-09.pdf) instead of editing it in place. - Fix the label with
--labelso every visitor receives the correct filename. - Start at
--fps 15with the default layout, then tune--cols/--rowsor--qrs-per-frameuntil a phone at the intended distance locks on within a couple of seconds. - Verify once end to end, from both a networked phone and an offline laptop.
- Check the physical setup: brightness, glare, cables, a screen nobody bumps.
- Leave it running — no reset button, no per-visitor cleanup, no expiry.
FAQ
How many people can scan the same QR code at once?
There is no sender-side limit: the kiosk only draws frames, and every camera that captures them recovers the file independently. The real ceiling is how many visitors share a network when they take the WebRTC path.
Do visitors need to install an app?
No. They open the receive page in a browser and point a camera at the screen. A machine with no camera can borrow one from a second device.
What happens if someone walks away halfway through?
Nothing on the sender changes. The fountain keeps cycling, the codes stay on screen, and the next visitor starts collecting wherever the loop happens to be.
Can I leave it running for days?
Yes. With --loops omitted the sender keeps cycling until it is stopped and never needs input; add --verbose or --log-audit if you want a record of what it streamed.
Why run a kiosk instead of printing a short link?
A link proves nothing shipped, depends on a server staying up, and cannot serve an offline machine. A kiosk delivers the file itself over the room's air or the room's network — see QR Send vs WeTransfer and cloud sharing and, if you are scripting it, the QR Send CLI guide.
Related reading
- How to transfer files to and from air-gapped machines — Step-by-step: move a file into or out of a machine that has no network, using a camera and a stream of QR codes.
- QR Send vs WeTransfer and cloud sharing — Cloud sharing is fast and asynchronous, and leaves a copy on someone else's disk. QR Send keeps the file between the two devices in the room.
- QR Send CLI guide for power users — The qrsend binary renders QR streams straight in your terminal, with flags for frame rate, grid size, error correction, colours and auditing.