Share files and text between a Windows laptop and a phone on the same Wi-Fi (typically a phone hotspot). No app to install on either device — both just open the same web page.
- Python standard library only. No
pip install, no virtualenv, no build step. - Any size of file. Uploads stream to disk in 256 KB blocks, so a 4 GB video costs the same memory as a 4 KB text file.
- Local network only. Nothing leaves your hotspot; nothing is uploaded to any cloud service.
Download the latest pocket-share.exe from
Releases and double-click it.
That is the whole setup — no Python required on your machine. The release page covers the PIN, the firewall prompt and everything else you need to know, so there is no reason to read the rest of this file unless you want to build it yourself.
Building from source
You are on a hotspot, your phone's battery is low, and you need to move a photo to the laptop — or a file you just finished on the laptop over to the phone. Normally that means a cable, an app, or a cloud upload. This is one command and one URL.
Python 3.9+ on Windows. Nothing else. (Only if building from source — the
release .exe needs neither.)
From source:
python teleport_share.py
It prints the address to open on each device:
On the LAPTOP:
http://localhost:8765
On the PHONE (same Wi-Fi / hotspot):
http://192.168.181.50:8765
Useful flags:
python teleport_share.py --port 9000 # different port
python teleport_share.py --host 192.168.181.50 # only listen on that adapter
Everything lives in SharedFiles/, next to the script:
SharedFiles/
FromPhone/ files received from the phone
FromLaptop/ files sent from the laptop
Texts/ timestamped .txt of each message sent
_inbox_tmp/ partial uploads (cleared on startup, never listed)
.teleport_index.json
Files you copy into SharedFiles\ by hand are picked up automatically — you do
not have to use the page to share something.
Anything a browser sends is untrusted, so incoming names are sanitised before they touch the disk:
| Sent | Stored as |
|---|---|
../../secret.txt |
secret.txt |
C:\Windows\System32\evil.dll |
evil.dll |
CON.txt |
_CON.txt (a reserved Windows device name) |
a<b>c:d.txt |
a_b_c_d.txt |
photo.jpg (already exists) |
photo (2).jpg |
Nothing is ever overwritten silently, and nothing can be written outside
SharedFiles\.
In order of likelihood:
- Windows Firewall — allow the app on Private networks when prompted.
If the Wi-Fi profile is set to Public, inbound connections are blocked
entirely. Fix from an Administrator prompt:
netsh advfirewall firewall add rule name="pocket-share" dir=in action=allow protocol=TCP localport=8765 profile=any - Wrong network — the laptop must be on the hotspot, not on office Wi-Fi. Windows only routes through one adapter at a time.
- AP isolation — some phone hotspots block device-to-device traffic entirely. There is no setting on the laptop that can fix this; you have to change it in the hotspot settings or use a different hotspot.
Built and working:
- Runs on both devices, prints the phone URL, detects the local IP
- Send files from the phone to the laptop, with progress bars
- Filename sanitising, collision handling, streaming for large files
- Live file list on both pages, auto-updating
- Download any listed file on either device, including files copied into
SharedFiles\by hand - Files the laptop shares are started automatically on the phone — the phone fetches them without you finding them in a list (Android Chrome still asks you to confirm with Keep, see below)
- Send text from the phone — an icon button opens a box, and the message lands on the laptop page and on your Windows clipboard
- Send text from the laptop, with a Copy button on the phone
- 6-digit PIN lock, with sessions and brute-force throttling
Next:
- QR code on the console, double-click launcher script
The page is locked behind a 6-digit PIN, which lives in a .env file next
to the script:
.env
PIN=641867
That file is in .gitignore and must never be committed or shared. It is
created automatically on first run with a random PIN, which is printed in
the console at startup — so there is no guessable default to forget about.
Edit it any time to change the PIN.
Set PIN= (empty) to switch the lock off.
Both devices ask for the PIN the first time. The correct PIN is exchanged for a random token that the browser keeps, so you only type it once per device (and again after the laptop restarts — sessions are deliberately not saved to disk). Guessing is slowed down: 10 wrong tries from one address locks it out for a minute.
Be honest about what this does and does not do:
- It stops anyone who opens the URL from browsing, downloading or deleting your files.
- Traffic is still plain HTTP, so it does not stop someone running a packet sniffer on the same network, who could capture the PIN or a token.
- There is no HTTPS, because Python's standard library can use a certificate but cannot generate one.
- Lock your hotspot with a password if the network is not just you.
Deleting also bypasses the Recycle Bin, so Delete all files is gone for good.
Each file has a Delete button. You have to tap it twice — the first tap
turns it red and asks Really delete?, and it disarms itself after 4 seconds
if you change your mind. Delete all files at the bottom works the same way
and then asks once more. Clear all messages in the Messages card empties
saved messages the same way, and leaves shared files alone.
Deletion goes through the same checks as a download, so a crafted request
cannot reach outside SharedFiles\. Internal bookkeeping files are never
deletable this way.
Two things worth knowing:
- There is no undo, and deleting bypasses the Recycle Bin.
Delete all filescannot be recovered from. - Saved messages under
Texts\are kept by Delete all files — only the transferred files are removed.
Tap the ✎ button in the header to type a message, on either device. Whatever you send shows up on the other screen within a couple of seconds, and every message has a Copy button so you can lift it onto that device's clipboard.
On the laptop there is one extra behaviour: text arriving from the phone is copied to the Windows clipboard automatically, so you can paste it straight into whatever you were working in with Ctrl+V. Messages that made it into the clipboard are tagged past ready in the history.
The clipboard switch only appears on the laptop page, because there is no Windows clipboard on the phone. Turn it off and the message is still saved and displayed — it just no longer takes over your clipboard.
Text typed on the laptop never touches the clipboard: it is your own text, and you may well still want to paste it elsewhere.
Every message is also written to SharedFiles/Texts/ as
YYYYmmdd-HHMMSS_from-phone.txt, so nothing is lost if you want to grep
through old notes later.
navigator.clipboard — the modern clipboard — only works in a secure
context. http://localhost counts as one, but http://192.168.181.50 does
not, so on the phone that API simply does not exist. The button therefore tries
it first and falls back to selecting a throwaway <textarea> and calling
document.execCommand('copy'), which still works over plain HTTP. If even that
is refused, the button says Press & hold so the text is at least easy to
select by hand.
The Windows clipboard is written through ctypes and Win32 — no pip
install, and no hidden tkinter window popping up.
The laptop runs the server, so every file passes through it — the laptop is the origin, not a relay it can bypass. The phone is what saves the file, by fetching it over HTTP; nothing else can write to the phone's storage without an app installed on it.
So when you share a file from the laptop, the phone's open page notices it within a couple of seconds and starts saving it by itself. Which files it takes:
- Anything the laptop shared or you copied in by hand
- Anything the phone itself sent — skipped, it already has those
Already-saved files are remembered in the browser, so reloading the page never re-downloads the history. If the phone's page was closed when something was shared, it is simply waiting in the list when you reopen it.
The page starts the download for you, so the file turns up on its own instead
of you hunting for it in the list. Android Chrome then shows a Discard /
Keep prompt, because the page is served over plain http:// and Chrome
does not consider an insecure download trustworthy. Tap Keep and it saves.
iOS Safari does something similar on some versions.
This is a browser security decision, not something the server can turn off.
If the prompt bothers you, chrome://flags/#allow-insecure-downloads in
Chrome removes it. The alternative — HTTPS — needs a certificate, and a
self-signed one is not trusted by Chrome either, so it may not help while
adding a "connection is not private" warning of its own.
The phone asks for files by name, so the same hostile-input rules apply in
reverse. A request is refused unless it survives three independent checks: no
.. segments, no absolute path, and — after resolving symlinks — the result
must still be inside SharedFiles\. Internal files (.teleport_index.json,
_inbox_tmp\, Texts\) are never listed and never served.
This is a tool for your own hotspot, and it is honest about what that means:
- Traffic is plain HTTP. The optional PIN stops someone who opens the URL, but it does not stop someone running a packet sniffer on the same network.
- There is no HTTPS, because Python's standard library can use a certificate but cannot generate one.
- Lock your hotspot with a password if the network is not just you.
The Python server runs unchanged on Android — the clipboard is the only Windows-specific part and it is already guarded. If you want a native app, ANDROID_APP_PROMPT.md is a ready-made brief for an AI coding tool, covering the stack (Chaquopy + WebView, so the script is embedded rather than rewritten) and the four Android traps that break this particular app.
A quick way to try it before writing any app: run the script in Termux
(F-Droid, not the Play Store), pkg install python, then
python teleport_share.py.
Double-click build.bat. It checks the inputs are there, installs
pyinstaller and pywebview if they are missing, stops any running copy,
builds, and reports the size. Keeping the flags in one script means they
cannot drift from what is written here.
Both tools are only needed to build. The resulting .exe needs neither
Python nor a browser installed. To run the same command by hand:
pip install pyinstaller pywebview
python -m PyInstaller --onefile --noconsole --name pocket-share ^
--icon logo.ico --add-data "logo.ico;." ^
--hidden-import webview teleport_share.py
Result: dist/pocket-share.exe (~35 MB). Note --noconsole — without it
you get a console window full of logs instead of an application.
The window is the web app. It embeds the same page the laptop and phone
get, via pywebview driving the Edge WebView2 runtime that Windows already
has. Nothing is reimplemented, so the two can't drift apart. The only addition
is a thin bar at the top with Start and Stop.
Because the UI lives in a webview, pywebview is the one build-time
dependency that has no standard-library equivalent. build/, dist/ and the
generated .spec are gitignored.
logo.ico is the only artwork in the project. It holds the logo at seven
sizes and is used for the browser tab, the page header, the window title bar
and the taskbar — browsers render .ico in an <img> just as well as any
raster, so there is no second copy to keep in step. Drop a logo.png or
logo.jpg beside the executable to override it for the web page without
rebuilding.
The QR code is drawn by qr.py, which is part of this project and uses only
the standard library: there is no pip install for it, and the app needs no
internet to produce one. The code encodes this machine's LAN address, so a
phone can be pointed at the laptop without anyone typing an IP address. It
carries a hole in the middle for the logo.
From source:
python teleport_share.py # text version, no dependencies at all
python teleport_share.py --gui # the window (needs pywebview)
MIT — see LICENSE.