adb-qr 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,22 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ jobs:
8
+ pypi:
9
+ runs-on: ubuntu-latest
10
+ environment: pypi
11
+ permissions:
12
+ id-token: write # PyPI trusted publishing (OIDC)
13
+ contents: write # gh release create
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: astral-sh/setup-uv@v5
17
+ - run: uv build
18
+ - run: uv publish --trusted-publishing always
19
+ - name: Create GitHub release
20
+ run: gh release create "$GITHUB_REF_NAME" dist/* --generate-notes
21
+ env:
22
+ GH_TOKEN: ${{ github.token }}
@@ -0,0 +1,4 @@
1
+ __pycache__/
2
+ *.egg-info/
3
+ dist/
4
+ .venv/
adb_qr-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Aleix (aleixrodriala)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
adb_qr-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,123 @@
1
+ Metadata-Version: 2.4
2
+ Name: adb-qr
3
+ Version: 0.1.0
4
+ Summary: Pair Android devices for wireless ADB by QR code -- discovery via adb's own mDNS, so it works even in WSL2, containers and VMs
5
+ Project-URL: Repository, https://github.com/aleixrodriala/adb-qr
6
+ Project-URL: Issues, https://github.com/aleixrodriala/adb-qr/issues
7
+ Author-email: Aleix <aleixrodriala@gmail.com>
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Keywords: adb,android,mdns,qr-code,wireless-debugging,wsl2
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Topic :: Software Development :: Debuggers
17
+ Classifier: Topic :: Utilities
18
+ Requires-Python: >=3.10
19
+ Requires-Dist: qrcode>=7
20
+ Description-Content-Type: text/markdown
21
+
22
+ # adb-qr
23
+
24
+ Pair your Android phone for wireless debugging by scanning a QR code in your
25
+ terminal — like Android Studio's *"Pair device with QR code"*, but for the
26
+ CLI. No typing pairing codes, no looking up IPs and ports.
27
+
28
+ ![adb-qr pairing session](docs/demo.png)
29
+
30
+ ## Why another QR-pairing tool?
31
+
32
+ Existing tools ([adb-wireless](https://github.com/teamclouday/adb-wireless),
33
+ [adb-qr](https://github.com/oosawy/adb-qr), and friends) run their **own**
34
+ mDNS listener to discover the phone after it scans the QR. That works on a
35
+ native host — and silently fails inside **WSL2, containers, and VMs**, where
36
+ LAN multicast never reaches the virtualized network. The QR renders, the
37
+ phone scans it, and then... nothing.
38
+
39
+ `adb-qr` doesn't listen for mDNS at all. It asks the **adb server itself**
40
+ what it sees (`adb mdns services`), so discovery happens wherever the server
41
+ runs. On WSL2, point it at the Windows `adb.exe` (it does this automatically)
42
+ and the Windows side — which *can* see your LAN — does the discovery. The
43
+ same trick means zero networking dependencies on any platform: if `adb`
44
+ works, `adb-qr` works.
45
+
46
+ ## Install
47
+
48
+ With [uv](https://docs.astral.sh/uv/) (recommended):
49
+
50
+ ```sh
51
+ uv tool install git+https://github.com/aleixrodriala/adb-qr
52
+ ```
53
+
54
+ With [pipx](https://pipx.pypa.io/):
55
+
56
+ ```sh
57
+ pipx install git+https://github.com/aleixrodriala/adb-qr
58
+ ```
59
+
60
+ Or run it once without installing anything:
61
+
62
+ ```sh
63
+ uvx --from git+https://github.com/aleixrodriala/adb-qr adb-qr
64
+ ```
65
+
66
+ ### Requirements
67
+
68
+ - Android **platform-tools 31+** on the host that has network visibility
69
+ (on WSL2: install them on **Windows**; `adb-qr` finds `adb.exe` by itself)
70
+ - A phone on **Android 11+**, on the same Wi-Fi as that host
71
+ - Python 3.10+ (handled for you by `uv`/`pipx`)
72
+
73
+ ## Usage
74
+
75
+ ```
76
+ adb-qr # show QR, wait for scan, pair, connect
77
+ adb-qr --pair-only # stop after pairing (adb usually auto-connects anyway)
78
+ adb-qr --timeout 300 # wait longer for the scan
79
+ adb-qr --no-invert # flip QR colors if your phone won't scan it
80
+ adb-qr --adb /path/to/adb # explicit adb binary (env var ADB works too)
81
+ ```
82
+
83
+ On the phone: **Settings → Developer options → Wireless debugging →
84
+ Pair device with QR code**, and point the camera at your terminal.
85
+
86
+ ## How it works
87
+
88
+ The QR encodes `WIFI:T:ADB;S:<name>;P:<password>;;` — the same payload
89
+ Android Studio generates. When the phone scans it:
90
+
91
+ 1. The phone starts advertising an `_adb-tls-pairing._tcp` mDNS service
92
+ whose instance name is the `<name>` from the QR.
93
+ 2. `adb-qr` polls `adb mdns services` until that service shows up, then runs
94
+ `adb pair <ip>:<port> <password>`.
95
+ 3. After pairing, the phone's `_adb-tls-connect._tcp` service is used to
96
+ `adb connect` (recent adb versions often auto-connect on their own —
97
+ `adb-qr` detects that too and gets out of the way).
98
+
99
+ ## Troubleshooting
100
+
101
+ - **QR won't scan** — try `--no-invert`; some terminal color schemes render
102
+ the QR with polarity a given camera dislikes.
103
+ - **Times out waiting for scan** — the phone must be on the same network as
104
+ the machine where the *adb server* runs. On WSL2 that machine is Windows,
105
+ not the WSL VM. Corporate/guest Wi-Fi often blocks mDNS entirely.
106
+ - **`adb mdns check` fails** — `adb-qr` force-enables the openscreen mDNS
107
+ backend (`ADB_MDNS_OPENSCREEN=1`) and restarts the server once; if it still
108
+ fails, update platform-tools.
109
+ - **Paired but not connected** — run `adb connect <ip>:<port>` with the port
110
+ shown on the phone's Wireless debugging screen (it differs from the
111
+ pairing port).
112
+
113
+ ## Prior art
114
+
115
+ The pair-by-QR flow was pioneered for the terminal by
116
+ [teamclouday/adb-wireless](https://github.com/teamclouday/adb-wireless).
117
+ `adb-qr` exists because none of the existing tools survive WSL2's NAT — and
118
+ delegating discovery to the adb server turned out to make the whole thing
119
+ simpler, too.
120
+
121
+ ## License
122
+
123
+ MIT
adb_qr-0.1.0/README.md ADDED
@@ -0,0 +1,102 @@
1
+ # adb-qr
2
+
3
+ Pair your Android phone for wireless debugging by scanning a QR code in your
4
+ terminal — like Android Studio's *"Pair device with QR code"*, but for the
5
+ CLI. No typing pairing codes, no looking up IPs and ports.
6
+
7
+ ![adb-qr pairing session](docs/demo.png)
8
+
9
+ ## Why another QR-pairing tool?
10
+
11
+ Existing tools ([adb-wireless](https://github.com/teamclouday/adb-wireless),
12
+ [adb-qr](https://github.com/oosawy/adb-qr), and friends) run their **own**
13
+ mDNS listener to discover the phone after it scans the QR. That works on a
14
+ native host — and silently fails inside **WSL2, containers, and VMs**, where
15
+ LAN multicast never reaches the virtualized network. The QR renders, the
16
+ phone scans it, and then... nothing.
17
+
18
+ `adb-qr` doesn't listen for mDNS at all. It asks the **adb server itself**
19
+ what it sees (`adb mdns services`), so discovery happens wherever the server
20
+ runs. On WSL2, point it at the Windows `adb.exe` (it does this automatically)
21
+ and the Windows side — which *can* see your LAN — does the discovery. The
22
+ same trick means zero networking dependencies on any platform: if `adb`
23
+ works, `adb-qr` works.
24
+
25
+ ## Install
26
+
27
+ With [uv](https://docs.astral.sh/uv/) (recommended):
28
+
29
+ ```sh
30
+ uv tool install git+https://github.com/aleixrodriala/adb-qr
31
+ ```
32
+
33
+ With [pipx](https://pipx.pypa.io/):
34
+
35
+ ```sh
36
+ pipx install git+https://github.com/aleixrodriala/adb-qr
37
+ ```
38
+
39
+ Or run it once without installing anything:
40
+
41
+ ```sh
42
+ uvx --from git+https://github.com/aleixrodriala/adb-qr adb-qr
43
+ ```
44
+
45
+ ### Requirements
46
+
47
+ - Android **platform-tools 31+** on the host that has network visibility
48
+ (on WSL2: install them on **Windows**; `adb-qr` finds `adb.exe` by itself)
49
+ - A phone on **Android 11+**, on the same Wi-Fi as that host
50
+ - Python 3.10+ (handled for you by `uv`/`pipx`)
51
+
52
+ ## Usage
53
+
54
+ ```
55
+ adb-qr # show QR, wait for scan, pair, connect
56
+ adb-qr --pair-only # stop after pairing (adb usually auto-connects anyway)
57
+ adb-qr --timeout 300 # wait longer for the scan
58
+ adb-qr --no-invert # flip QR colors if your phone won't scan it
59
+ adb-qr --adb /path/to/adb # explicit adb binary (env var ADB works too)
60
+ ```
61
+
62
+ On the phone: **Settings → Developer options → Wireless debugging →
63
+ Pair device with QR code**, and point the camera at your terminal.
64
+
65
+ ## How it works
66
+
67
+ The QR encodes `WIFI:T:ADB;S:<name>;P:<password>;;` — the same payload
68
+ Android Studio generates. When the phone scans it:
69
+
70
+ 1. The phone starts advertising an `_adb-tls-pairing._tcp` mDNS service
71
+ whose instance name is the `<name>` from the QR.
72
+ 2. `adb-qr` polls `adb mdns services` until that service shows up, then runs
73
+ `adb pair <ip>:<port> <password>`.
74
+ 3. After pairing, the phone's `_adb-tls-connect._tcp` service is used to
75
+ `adb connect` (recent adb versions often auto-connect on their own —
76
+ `adb-qr` detects that too and gets out of the way).
77
+
78
+ ## Troubleshooting
79
+
80
+ - **QR won't scan** — try `--no-invert`; some terminal color schemes render
81
+ the QR with polarity a given camera dislikes.
82
+ - **Times out waiting for scan** — the phone must be on the same network as
83
+ the machine where the *adb server* runs. On WSL2 that machine is Windows,
84
+ not the WSL VM. Corporate/guest Wi-Fi often blocks mDNS entirely.
85
+ - **`adb mdns check` fails** — `adb-qr` force-enables the openscreen mDNS
86
+ backend (`ADB_MDNS_OPENSCREEN=1`) and restarts the server once; if it still
87
+ fails, update platform-tools.
88
+ - **Paired but not connected** — run `adb connect <ip>:<port>` with the port
89
+ shown on the phone's Wireless debugging screen (it differs from the
90
+ pairing port).
91
+
92
+ ## Prior art
93
+
94
+ The pair-by-QR flow was pioneered for the terminal by
95
+ [teamclouday/adb-wireless](https://github.com/teamclouday/adb-wireless).
96
+ `adb-qr` exists because none of the existing tools survive WSL2's NAT — and
97
+ delegating discovery to the adb server turned out to make the whole thing
98
+ simpler, too.
99
+
100
+ ## License
101
+
102
+ MIT
adb_qr-0.1.0/adb_qr.py ADDED
@@ -0,0 +1,244 @@
1
+ #!/usr/bin/env -S uv run --script --quiet
2
+ # /// script
3
+ # requires-python = ">=3.10"
4
+ # dependencies = ["qrcode>=7"]
5
+ # ///
6
+ """Pair an Android device for wireless ADB by scanning a QR code in the terminal.
7
+
8
+ Like Android Studio's "Pair device with QR code", but for the CLI:
9
+
10
+ 1. Show a QR encoding ``WIFI:T:ADB;S:<name>;P:<password>;;``
11
+ 2. The phone scans it (Developer options > Wireless debugging > Pair device
12
+ with QR code) and starts advertising an ``_adb-tls-pairing._tcp`` mDNS
13
+ service named ``<name>``.
14
+ 3. We discover it and run ``adb pair ip:port password``.
15
+ 4. We find the device's ``_adb-tls-connect._tcp`` service (or let adb's
16
+ auto-connect kick in) and ``adb connect``.
17
+
18
+ Unlike other QR-pairing tools, discovery is delegated to the adb server
19
+ itself (``adb mdns services``) instead of an in-process zeroconf listener.
20
+ That makes it work in places where LAN multicast never reaches the tool --
21
+ WSL2, containers, VMs -- as long as the adb server runs somewhere with real
22
+ network visibility (on WSL2: the Windows adb.exe).
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ __version__ = "0.1.0"
28
+
29
+ import argparse
30
+ import glob
31
+ import os
32
+ import secrets
33
+ import shutil
34
+ import string
35
+ import subprocess
36
+ import sys
37
+ import time
38
+
39
+ import qrcode
40
+
41
+ PAIR_SVC = "_adb-tls-pairing"
42
+ CONNECT_SVC = "_adb-tls-connect"
43
+ WIN_ADB_GLOB = "/mnt/c/Users/*/AppData/Local/Android/Sdk/platform-tools/adb.exe"
44
+
45
+
46
+ def is_wsl() -> bool:
47
+ try:
48
+ with open("/proc/version") as f:
49
+ return "microsoft" in f.read().lower()
50
+ except OSError:
51
+ return False
52
+
53
+
54
+ def find_adb(override: str | None) -> str:
55
+ if override:
56
+ return override
57
+ if os.environ.get("ADB"):
58
+ return os.environ["ADB"]
59
+ if is_wsl():
60
+ # Prefer the Windows adb: its server runs on the Windows side, where
61
+ # LAN multicast is visible. A Linux adb inside WSL2 sits behind NAT
62
+ # and its mDNS discovery comes up empty.
63
+ exe = shutil.which("adb.exe") or next(iter(glob.glob(WIN_ADB_GLOB)), None)
64
+ if exe:
65
+ return exe
66
+ linux_adb = shutil.which("adb")
67
+ if linux_adb:
68
+ print(
69
+ "warning: no Windows adb.exe found, falling back to Linux adb -- "
70
+ "mDNS discovery will likely fail inside WSL2. Install "
71
+ "platform-tools on the Windows side for reliable pairing.",
72
+ file=sys.stderr,
73
+ )
74
+ return linux_adb
75
+ else:
76
+ found = shutil.which("adb")
77
+ if found:
78
+ return found
79
+ home = os.path.expanduser("~")
80
+ candidates = [
81
+ os.path.join(home, "Library", "Android", "sdk", "platform-tools", "adb"),
82
+ os.path.join(home, "Android", "Sdk", "platform-tools", "adb"),
83
+ ]
84
+ if os.environ.get("LOCALAPPDATA"):
85
+ candidates.append(
86
+ os.path.join(
87
+ os.environ["LOCALAPPDATA"],
88
+ "Android", "Sdk", "platform-tools", "adb.exe",
89
+ )
90
+ )
91
+ for p in candidates:
92
+ if os.path.exists(p):
93
+ return p
94
+ sys.exit("adb not found -- install Android platform-tools or pass --adb /path/to/adb")
95
+
96
+
97
+ def adb_env() -> dict[str, str]:
98
+ # ADB_MDNS_OPENSCREEN=1 forces the built-in mdns backend on older
99
+ # platform-tools; harmless on recent versions where it is the default.
100
+ env = {**os.environ, "ADB_MDNS_OPENSCREEN": "1"}
101
+ if is_wsl():
102
+ # WSLENV makes the variable cross the WSL->Windows interop boundary,
103
+ # so the adb *server* (a Windows process) actually sees it.
104
+ env["WSLENV"] = (os.environ.get("WSLENV", "") + ":ADB_MDNS_OPENSCREEN/u").lstrip(":")
105
+ return env
106
+
107
+
108
+ def run_adb(adb_path: str, *args: str, timeout: int = 60) -> subprocess.CompletedProcess:
109
+ return subprocess.run(
110
+ [adb_path, *args], capture_output=True, text=True, env=adb_env(), timeout=timeout
111
+ )
112
+
113
+
114
+ def mdns_services(adb_path: str) -> list[tuple[str, str, str, int]]:
115
+ """Parse `adb mdns services` into (instance, service_type, ip, port) rows."""
116
+ out = run_adb(adb_path, "mdns", "services").stdout
117
+ rows = []
118
+ for line in out.splitlines():
119
+ line = line.rstrip("\r")
120
+ parts = line.split("\t") if "\t" in line else line.split()
121
+ if len(parts) < 3 or not parts[1].startswith("_adb-tls-"):
122
+ continue
123
+ instance, svc, endpoint = parts[0], parts[1], parts[2]
124
+ ip, _, port = endpoint.rpartition(":")
125
+ if ip and port.isdigit():
126
+ rows.append((instance, svc, ip, int(port)))
127
+ return rows
128
+
129
+
130
+ def ensure_mdns(adb_path: str) -> None:
131
+ check = run_adb(adb_path, "mdns", "check")
132
+ if "daemon version" in check.stdout:
133
+ return
134
+ # The server may have been started without mdns enabled; restart it with
135
+ # our environment and try once more.
136
+ run_adb(adb_path, "kill-server")
137
+ check = run_adb(adb_path, "mdns", "check")
138
+ if "daemon version" in check.stdout:
139
+ return
140
+ msg = (check.stdout + check.stderr).strip()
141
+ if "unknown command" in msg or "usage:" in msg.lower():
142
+ sys.exit(
143
+ "this adb is too old for `adb mdns services` -- update Android "
144
+ "platform-tools (31+): https://developer.android.com/tools/releases/platform-tools"
145
+ )
146
+ sys.exit(f"adb mdns unavailable: {msg}")
147
+
148
+
149
+ def print_qr(text: str, invert: bool) -> None:
150
+ qr = qrcode.QRCode(border=2)
151
+ qr.add_data(text)
152
+ qr.print_ascii(invert=invert)
153
+
154
+
155
+ def main() -> int:
156
+ ap = argparse.ArgumentParser(
157
+ prog="adb-qr",
158
+ description="Pair (and connect) an Android device over wireless ADB by QR code.",
159
+ )
160
+ ap.add_argument("--adb", help="path to the adb binary (default: autodetect; env ADB also works)")
161
+ ap.add_argument("--timeout", type=int, default=120, metavar="SECONDS",
162
+ help="how long to wait for the phone to scan (default: 120)")
163
+ ap.add_argument("--pair-only", action="store_true",
164
+ help="stop after pairing; don't wait for a connection")
165
+ ap.add_argument("--no-invert", action="store_true",
166
+ help="flip QR colors if your scanner struggles on this terminal background")
167
+ ap.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
168
+ args = ap.parse_args()
169
+
170
+ adb_path = find_adb(args.adb)
171
+ ensure_mdns(adb_path)
172
+
173
+ name = f"adbqr-{secrets.token_hex(4)}"
174
+ alphabet = string.ascii_letters + string.digits
175
+ password = "".join(secrets.choice(alphabet) for _ in range(12))
176
+
177
+ print_qr(f"WIFI:T:ADB;S:{name};P:{password};;", invert=not args.no_invert)
178
+ print("On the phone: Developer options > Wireless debugging > Pair device with QR code")
179
+ print(f"Waiting for scan (up to {args.timeout}s)... ", end="", flush=True)
180
+
181
+ # Phase 1: after scanning, the phone advertises _adb-tls-pairing named
182
+ # after the QR's S: field. Match on that; if some device doesn't echo the
183
+ # name back, fall back to a lone pairing service (the password still
184
+ # guards against pairing with the wrong device).
185
+ pair_ip = pair_port = None
186
+ deadline = time.monotonic() + args.timeout
187
+ try:
188
+ while time.monotonic() < deadline:
189
+ pairing = [r for r in mdns_services(adb_path) if r[1].startswith(PAIR_SVC)]
190
+ match = [r for r in pairing if r[0] == name] or (
191
+ pairing if len(pairing) == 1 else []
192
+ )
193
+ if match:
194
+ _, _, pair_ip, pair_port = match[0]
195
+ break
196
+ time.sleep(1)
197
+ except KeyboardInterrupt:
198
+ print("\ninterrupted")
199
+ return 130
200
+ if not pair_ip:
201
+ print("\nTimed out: the phone never appeared in `adb mdns services`.")
202
+ print("Make sure it is on the same network as the machine running the adb")
203
+ print("server (on WSL2 that's Windows, not the WSL VM) and try again.")
204
+ return 1
205
+ print(f"found {pair_ip}:{pair_port}")
206
+
207
+ res = run_adb(adb_path, "pair", f"{pair_ip}:{pair_port}", password)
208
+ if "Successfully paired" not in res.stdout:
209
+ print(f"Pairing failed: {(res.stdout + res.stderr).strip()}", file=sys.stderr)
210
+ return 1
211
+ print(res.stdout.strip())
212
+ if args.pair_only:
213
+ return 0
214
+
215
+ # Phase 2: connect. Recent adb auto-connects to paired devices it sees
216
+ # via mDNS, so watch `adb devices` while also trying an explicit connect
217
+ # against the device's _adb-tls-connect service.
218
+ deadline = time.monotonic() + 30
219
+ try:
220
+ while time.monotonic() < deadline:
221
+ devices = run_adb(adb_path, "devices").stdout
222
+ connected = [
223
+ line for line in devices.splitlines()
224
+ if line.strip().endswith("device") and (pair_ip in line or CONNECT_SVC in line)
225
+ ]
226
+ if connected:
227
+ print(f"Connected: {connected[0].split()[0]}")
228
+ return 0
229
+ for _, svc, ip, port in mdns_services(adb_path):
230
+ if svc.startswith(CONNECT_SVC) and ip == pair_ip:
231
+ res = run_adb(adb_path, "connect", f"{ip}:{port}")
232
+ print(res.stdout.strip())
233
+ time.sleep(1)
234
+ except KeyboardInterrupt:
235
+ print("\ninterrupted (already paired -- `adb connect` manually if needed)")
236
+ return 130
237
+
238
+ print("Paired, but no connection after 30s -- run `adb connect <ip>:<port>` with")
239
+ print("the port shown on the phone's Wireless debugging screen.")
240
+ return 1
241
+
242
+
243
+ if __name__ == "__main__":
244
+ sys.exit(main())
Binary file
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env -S uv run --script --quiet
2
+ # /// script
3
+ # requires-python = ">=3.10"
4
+ # dependencies = ["qrcode>=7", "rich>=13"]
5
+ # ///
6
+ """Regenerate the README demo image.
7
+
8
+ This writes demo.svg; rasterize it with a real browser so the embedded
9
+ webfont renders the QR's half-block glyphs correctly (cairosvg mangles them):
10
+
11
+ chrome --headless=new --screenshot=demo.png --window-size=2038,1272 \
12
+ --hide-scrollbars --virtual-time-budget=10000 file://.../demo.svg
13
+ """
14
+
15
+ import io
16
+ import pathlib
17
+
18
+ import qrcode
19
+ from rich.console import Console
20
+
21
+ qr = qrcode.QRCode(border=2)
22
+ qr.add_data("WIFI:T:ADB;S:adbqr-3f9a17c2;P:kP4mW9xQz2Lr;;")
23
+ buf = io.StringIO()
24
+ qr.print_ascii(out=buf, invert=True)
25
+
26
+ console = Console(record=True, width=82)
27
+ console.print("[bold green]$[/] [bold]adb-qr[/]")
28
+ console.print(buf.getvalue().rstrip("\n"), markup=False, highlight=False)
29
+ console.print("On the phone: Developer options > Wireless debugging > Pair device with QR code")
30
+ console.print("Waiting for scan (up to 120s)... [cyan]found 192.168.1.42:40331[/]")
31
+ console.print("[green]Successfully paired to 192.168.1.42:40331 \\[guid=adb-1A2B3C4D-aBcDeF][/]")
32
+ console.print("[bold green]Connected: 192.168.1.42:44915[/]")
33
+ console.save_svg(str(pathlib.Path(__file__).parent / "demo.svg"), title="adb-qr")
@@ -0,0 +1,33 @@
1
+ [project]
2
+ name = "adb-qr"
3
+ version = "0.1.0"
4
+ description = "Pair Android devices for wireless ADB by QR code -- discovery via adb's own mDNS, so it works even in WSL2, containers and VMs"
5
+ readme = "README.md"
6
+ license = { text = "MIT" }
7
+ requires-python = ">=3.10"
8
+ dependencies = ["qrcode>=7"]
9
+ authors = [{ name = "Aleix", email = "aleixrodriala@gmail.com" }]
10
+ keywords = ["adb", "android", "wireless-debugging", "qr-code", "wsl2", "mdns"]
11
+ classifiers = [
12
+ "Environment :: Console",
13
+ "Intended Audience :: Developers",
14
+ "License :: OSI Approved :: MIT License",
15
+ "Operating System :: OS Independent",
16
+ "Programming Language :: Python :: 3",
17
+ "Topic :: Software Development :: Debuggers",
18
+ "Topic :: Utilities",
19
+ ]
20
+
21
+ [project.urls]
22
+ Repository = "https://github.com/aleixrodriala/adb-qr"
23
+ Issues = "https://github.com/aleixrodriala/adb-qr/issues"
24
+
25
+ [project.scripts]
26
+ adb-qr = "adb_qr:main"
27
+
28
+ [build-system]
29
+ requires = ["hatchling"]
30
+ build-backend = "hatchling.build"
31
+
32
+ [tool.hatch.build.targets.wheel]
33
+ include = ["adb_qr.py"]
adb_qr-0.1.0/uv.lock ADDED
@@ -0,0 +1,35 @@
1
+ version = 1
2
+ revision = 3
3
+ requires-python = ">=3.10"
4
+
5
+ [[package]]
6
+ name = "adb-qr"
7
+ version = "0.1.0"
8
+ source = { editable = "." }
9
+ dependencies = [
10
+ { name = "qrcode" },
11
+ ]
12
+
13
+ [package.metadata]
14
+ requires-dist = [{ name = "qrcode", specifier = ">=7" }]
15
+
16
+ [[package]]
17
+ name = "colorama"
18
+ version = "0.4.6"
19
+ source = { registry = "https://pypi.org/simple" }
20
+ sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
21
+ wheels = [
22
+ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
23
+ ]
24
+
25
+ [[package]]
26
+ name = "qrcode"
27
+ version = "8.2"
28
+ source = { registry = "https://pypi.org/simple" }
29
+ dependencies = [
30
+ { name = "colorama", marker = "sys_platform == 'win32'" },
31
+ ]
32
+ sdist = { url = "https://files.pythonhosted.org/packages/8f/b2/7fc2931bfae0af02d5f53b174e9cf701adbb35f39d69c2af63d4a39f81a9/qrcode-8.2.tar.gz", hash = "sha256:35c3f2a4172b33136ab9f6b3ef1c00260dd2f66f858f24d88418a015f446506c", size = 43317, upload-time = "2025-05-01T15:44:24.726Z" }
33
+ wheels = [
34
+ { url = "https://files.pythonhosted.org/packages/dd/b8/d2d6d731733f51684bbf76bf34dab3b70a9148e8f2cef2bb544fccec681a/qrcode-8.2-py3-none-any.whl", hash = "sha256:16e64e0716c14960108e85d853062c9e8bba5ca8252c0b4d0231b9df4060ff4f", size = 45986, upload-time = "2025-05-01T15:44:22.781Z" },
35
+ ]