windows-apple-remote 0.1.1__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,20 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+ workflow_call:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: windows-latest
11
+ strategy:
12
+ matrix:
13
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: ${{ matrix.python-version }}
19
+ - run: pip install -e ".[test]"
20
+ - run: pytest -v
@@ -0,0 +1,72 @@
1
+ name: Publish
2
+
3
+ # Publishing a GitHub release uploads to PyPI.
4
+ # Running this workflow by hand (Actions tab -> Publish -> Run workflow) uploads to
5
+ # TestPyPI instead, for a dry run.
6
+ on:
7
+ release:
8
+ types: [published]
9
+ workflow_dispatch:
10
+
11
+ jobs:
12
+ test:
13
+ uses: ./.github/workflows/ci.yml
14
+
15
+ build:
16
+ needs: test
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+ - uses: actions/setup-python@v5
21
+ with:
22
+ python-version: "3.12"
23
+ - run: pip install build twine
24
+ - name: Check release tag matches package version
25
+ if: github.event_name == 'release'
26
+ run: |
27
+ version=$(sed -n 's/^__version__ = "\(.*\)"/\1/p' src/atv_remote/__init__.py)
28
+ tag="${GITHUB_REF_NAME#v}"
29
+ if [ "$version" != "$tag" ]; then
30
+ echo "Release tag $GITHUB_REF_NAME does not match __version__ $version" >&2
31
+ exit 1
32
+ fi
33
+ - run: python -m build
34
+ - run: twine check --strict dist/*
35
+ - uses: actions/upload-artifact@v4
36
+ with:
37
+ name: dist
38
+ path: dist/
39
+
40
+ pypi:
41
+ if: github.event_name == 'release'
42
+ needs: build
43
+ runs-on: ubuntu-latest
44
+ environment:
45
+ name: pypi
46
+ url: https://pypi.org/p/windows-apple-remote
47
+ permissions:
48
+ id-token: write # PyPI trusted publishing, no API token needed
49
+ steps:
50
+ - uses: actions/download-artifact@v4
51
+ with:
52
+ name: dist
53
+ path: dist/
54
+ - uses: pypa/gh-action-pypi-publish@release/v1
55
+
56
+ testpypi:
57
+ if: github.event_name == 'workflow_dispatch'
58
+ needs: build
59
+ runs-on: ubuntu-latest
60
+ environment:
61
+ name: testpypi
62
+ url: https://test.pypi.org/p/windows-apple-remote
63
+ permissions:
64
+ id-token: write
65
+ steps:
66
+ - uses: actions/download-artifact@v4
67
+ with:
68
+ name: dist
69
+ path: dist/
70
+ - uses: pypa/gh-action-pypi-publish@release/v1
71
+ with:
72
+ repository-url: https://test.pypi.org/legacy/
@@ -0,0 +1,6 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ .pytest_cache/
@@ -0,0 +1,54 @@
1
+ # Changelog
2
+
3
+ <!--
4
+ Maintained for both humans and Claude Code to reference. Before
5
+ implementing a feature or fix, check the entries below first — it may
6
+ already be done, which saves searching the whole codebase.
7
+
8
+ Format (based on Keep a Changelog: https://keepachangelog.com/en/1.1.0/):
9
+ - New entries go under "## [Unreleased]" until a release is cut.
10
+ - Categories: Added, Changed, Fixed, Deprecated, Removed, Security.
11
+ Only add a category heading once it has an entry under it.
12
+ - One bullet per entry, imperative mood, one line where possible:
13
+ - Add CSV export to the reports page. (Refs: JIRA-482, #210)
14
+ - Include "(Refs: ...)" only when there's an external reference — a Jira
15
+ key, GitHub issue/PR number, Trello card title/URL, or similar tracker
16
+ ID. Comma-separate multiple references. Omit the parenthetical entirely
17
+ if there's nothing to reference.
18
+ - To cut a release: rename "## [Unreleased]" to "## [X.Y.Z] - YYYY-MM-DD"
19
+ and start a fresh, empty "## [Unreleased]" section above it.
20
+ - Never delete or rewrite past entries — only append.
21
+ -->
22
+
23
+ ## [Unreleased]
24
+
25
+ ## [0.1.1] - 2026-09-24
26
+
27
+ ### Changed
28
+ - Rename the PyPI package from `atv-remote-server` to `windows-apple-remote` to match the repository and the PyPI trusted publisher; the `atv-remote` command and `atv_remote` import are unchanged.
29
+
30
+ ### Fixed
31
+ - Fix the PyPI upload failing with `400 Non-user identities cannot create new projects` (package name didn't match the pending publisher); 0.1.0 was never uploaded.
32
+
33
+ ## [0.1.0] - 2026-09-24
34
+
35
+ ### Added
36
+ - Add `atv-remote` server that advertises the PC as an Apple TV (Companion protocol via pyatv) so the iOS Apple TV Remote can pair with a PIN and control media keys, volume, arrows, Enter and Escape on Windows.
37
+ - Add per-install identity, random pairing PIN per attempt, and rejection of unpaired devices on pair-verify.
38
+ - Add end-to-end tests that pair and send commands using pyatv's own client.
39
+ - Add GitHub Actions for Windows CI and PyPI trusted publishing on release.
40
+ - Add TestPyPI dry-run publishing (manual workflow run), a release-tag/version check and `twine check` to the publish workflow; run CI on every push.
41
+ - Add GitHub project links (aryakvn/windows-apple-remote) to the README and package metadata, with PyPI trusted-publisher setup steps.
42
+
43
+ ### Fixed
44
+ - Encrypt the connection right after pair-setup, since iOS keeps using it, and parse only OPACK frame types, which fixes the `TypeError: 0xc9` disconnect after pairing.
45
+ - Answer `_systemInfo` with device info, reply "No request handler" to unknown requests and drop `_i` from responses to match a real Apple TV; log outgoing messages with `-v`.
46
+ - Decode OPACK back-references the way Apple encodes them (skip 1-byte objects like `''`); pyatv's decoder was off by one, so iOS 27's `_systemInfo` arrived without its `_i` and was rejected.
47
+ - Keep the iOS remote session open: answer every message successfully (replacing the "No request handler" errors), return a touch session id from `_touchStart` and flags from `FetchMediaControlStatus`, and send a full Apple TV `_systemInfo` (`_lP`, `_stA` with `com.apple.tvremoteservices`, Siri peer data) with `rpFl=0xB6782` and model `AppleTV5,3`, matching thiccaxe/CompanionGames, a server known to work with the remote.
48
+
49
+ ### Changed
50
+ - Make the touch area a media controller: tap plays/pauses, swipe left/right changes track, swipe up/down changes volume (longer swipe = more steps); D-pad directions map the same way.
51
+ - Advertise volume control (`_mcF` Volume flag) so the iPhone's volume buttons control the PC, and turn `SetVolume` into volume key steps.
52
+
53
+ ### Removed
54
+ - Remove arrow-key, Enter and ±10s skip mappings, which the touch-only iOS remote can't reach.
@@ -0,0 +1,193 @@
1
+ # CLAUDE.md — windows-apple-remote
2
+
3
+ Makes a Windows PC appear as an Apple TV so the iOS **Apple TV Remote** (Control
4
+ Center) can pair with it and send media keys. PyPI name `windows-apple-remote`, import
5
+ `atv_remote`, CLI `atv-remote`. macOS support is planned; HomeKit is out of scope.
6
+
7
+ ## Status (2026-09-24)
8
+
9
+ - Session now stays open on the iPhone (CompanionGames-style replies fixed it).
10
+ The iOS 27 remote shows only a touch area, so control is gesture-based.
11
+
12
+ - Working against a real iPhone (iOS 27.0, iPhone12,1): mDNS discovery, pair-setup
13
+ with PIN, pair-verify on reconnect, encrypted session, `_systemInfo` decode.
14
+ - Earlier the iPhone sent `TVRCSessionStop` ~30 ms after setup; fixed by the replies
15
+ listed below. Gestures and hardware-volume routing (`_mcF` Volume bit 0x100) are
16
+ not yet confirmed on a device.
17
+
18
+ ## Layout
19
+
20
+ - `src/atv_remote/server.py` — `RemoteServer` (one asyncio.Protocol per connection,
21
+ subclasses pyatv's `CompanionServerAuth`), `Identity`, `unpack_opack`.
22
+ - `src/atv_remote/keys.py` — action name → Windows virtual key via `keybd_event`.
23
+ Swap/extend this for macOS. `SUPPORTED` gates the CLI.
24
+ - `src/atv_remote/cli.py` — argparse, TCP server, zeroconf `_companion-link._tcp`
25
+ registration, PIN printing.
26
+ - `tests/test_server.py` — pyatv's own Companion client pairs and presses buttons
27
+ end to end; wrong PIN / unpaired device rejected; OPACK regression; replay of the
28
+ iOS 27 request sequence.
29
+ - `.github/workflows/ci.yml` (Windows, py3.10–3.13), `publish.yml` (GitHub release →
30
+ PyPI trusted publishing, environment `pypi`). Version lives in
31
+ `src/atv_remote/__init__.py` (hatch dynamic version).
32
+
33
+ ## Reusable pieces
34
+
35
+ - `Identity(path)` — persistent server identity in JSON: `id` (UUID), `seed` (32 bytes,
36
+ Ed25519/X25519 key seed), `clients` (pairing id → client Ed25519 public key hex).
37
+ `txt_record()` gives the mDNS TXT; `system_info(name, port)` gives the `_systemInfo`
38
+ reply. All ids derived deterministically from the seed. Default path
39
+ `%APPDATA%\atv-remote\state.json`; delete it to forget all paired devices.
40
+ - `unpack_opack(data)` — correct OPACK decoder (wraps pyatv's `_unpack` with a fixed
41
+ back-reference table). Use this instead of `pyatv.support.opack.unpack` for anything
42
+ coming from iOS.
43
+ - `keys.press(action)` — actions: `play_pause next previous volume_up volume_down up
44
+ down left right select back`.
45
+
46
+ ## Protocol facts learned (Companion / "rapport")
47
+
48
+ Discovery and auth:
49
+ - Modern iOS Remote uses the **Companion** protocol (`_companion-link._tcp`), not MRP.
50
+ - TXT values known to work: `rpFl=0xB6782`, `rpMd=AppleTV5,3`, `rpMac=2`, plus
51
+ `rpHN rpHA rpAD rpHI` (12 hex chars each), `rpBA` (MAC), `rpMRtID` (server id).
52
+ - Frame: 1 byte type + 3 byte big-endian length + payload. Encrypted payloads use
53
+ ChaCha20-Poly1305, 12-byte nonce counter, AAD = the 4-byte header, +16 byte tag.
54
+ - **After pair-setup M6, iOS keeps the same connection and encrypts immediately**
55
+ with `hkdf("", "ServerEncrypt-main"/"ClientEncrypt-main", SRP session key)`. pyatv's
56
+ client disconnects after pairing, so its tests never exercise this.
57
+ - pyatv's `CompanionServerAuth` weaknesses we override: fixed PIN 1111 (its `pin`
58
+ arg is ignored), public fixed private key, and `_m3_verify` never checks the
59
+ client's signature (any device could connect unpaired). We use a random PIN per
60
+ pairing attempt, a per-install seed, verify the controller signature in M5, and
61
+ verify the M3 signature against the stored client key.
62
+
63
+ OPACK:
64
+ - **pyatv 0.18 `opack.unpack` bug:** it adds 1-byte objects (`''`, `b''`) to the
65
+ back-reference table and dedupes by `==`. Apple's encoder (and pyatv's own `pack`)
66
+ skips 1-byte objects, so any message containing `''` resolves later references one
67
+ slot off. iOS 27's `_systemInfo` has `'myriadTrialTreatment': ''`, which turned the
68
+ top-level `_i` into another value. Fixed by `_RefTable` in server.py. Worth
69
+ reporting upstream to pyatv.
70
+
71
+ Messages (`_t`: 1=event, 2=request, 3=response; responses match on `_x`, carry no `_i`):
72
+ - iOS 27 sequence after pair-verify: `_systemInfo`, `_sessionStart`
73
+ (`_srvT=com.apple.tvremoteservices`), `TVRCSessionStart` (`ProtocolVersionKey
74
+ 1.2`), `FetchAttentionState`, `FetchSiriRemoteInfo`, `_interest`
75
+ (PushSiriRemoteInfo, SupportedActions, NowPlayingInfo, TopShelfItems,
76
+ MediaControlStatus), `FetchSupportedActionsEvent`,
77
+ `FetchCurrentNowPlayingInfoEvent`, `FetchCurrentTopShelfItemsEvent`,
78
+ `FetchMediaControlStatus`, `_touchStart`, `_tiStart`.
79
+ - Replies that did **not** keep the session open: empty `{}` for everything, and
80
+ `No request handler` errors (code 58822, RPErrorDomain) for unknown requests.
81
+ - Replies now sent (from CompanionGames): a normal `{}` reply to every message, never
82
+ an error; `_touchStart` → `{"_i": 1}`; `FetchMediaControlStatus` →
83
+ `{"MediaControlFlags": flags}`; `FetchAttentionState` → `{"state": 3}`;
84
+ `_sessionStart` → `{"_sid": random32}`; `TVRCSessionStart` echoes its content;
85
+ `_systemInfo` → name, model, `_i`, `_idsID`, `_pubID`, `_mrID`, `_mRtID`,
86
+ `_lP` (listening port), `_stA` (must include `com.apple.tvremoteservices`),
87
+ `_sf 65536`, `_bf 1920`, `_cf 512`, `_clFl 128`, `_msSt/_msRo/_dCapF 1`, and
88
+ `_siriInfo.peerData` with `userInterfaceIdiom: "ZEUS"` (Apple TV).
89
+ - Input: `_hidC` with `_hBtS` 1=down / 2=up and `_hidC` = pyatv `HidCommand`; a tap
90
+ on the touchpad also arrives as `_hidC` Select. `_hidT` touch events: `_tPh`
91
+ 1=start, 3=move, 4=end, 5=click; `_cx/_cy` 0–1000. `_mcc` = pyatv
92
+ `MediaControlCommand` (`SkipBy` carries `_skpS`).
93
+ - Real `FetchSiriRemoteInfo` reply is `{"SiriRemoteInfoKey": <NSKeyedArchiver
94
+ bplist of TVRCSiriRemoteInfo>}`; `SupportedActions` event carries
95
+ `GuideSupportedKey`; `NowPlayingInfo` event carries `NowPlayingInfoKey` bplist
96
+ (`TVRCNowPlayingInfo`, `playbackRate`). Not implemented; see pyatv issues #2325
97
+ and #2461.
98
+
99
+ ## README format
100
+
101
+ Keep `README.md` in this shape so every release reads the same:
102
+
103
+ 1. `# windows-apple-remote` (repo name), then the CI and PyPI badges, then
104
+ `Package: [\`windows-apple-remote\`](pypi link) · Source: [github.com/aryakvn/windows-apple-remote](repo link)`.
105
+ 2. One short paragraph: what it does (iOS Apple TV Remote → Windows media keys, via pyatv).
106
+ 3. Mapping table with columns `Remote (touch area) | PC`, one row per gesture or
107
+ button. It must match `HID_ACTIONS`, `MCC_ACTIONS` and `_touch()` in `server.py`;
108
+ update it in the same commit as any mapping change.
109
+ 4. `## Install & run`: `pip install windows-apple-remote` + `atv-remote`, then the
110
+ `pip install git+https://github.com/aryakvn/windows-apple-remote.git` alternative,
111
+ the pairing steps (same Wi-Fi, Control Center → Apple TV Remote, PIN in terminal),
112
+ and an `Options:` block listing every `cli.py` flag with a one-line comment.
113
+ 5. `## Notes`: firewall, state file location, behaviour limits, platform support.
114
+ 6. `## Development`: clone, venv, `pip install -e ".[test]"`, `pytest`, and the
115
+ editable-install warning.
116
+ 7. `## Releasing to PyPI`: one-time trusted-publisher values and the per-release
117
+ steps (keep them in sync with the release process below).
118
+
119
+ Style: short sentences, second person, Windows commands (`.venv\Scripts\activate`).
120
+ Files must stay UTF-8. On Windows, Python's `read_text()`/`write_text()` default to
121
+ cp1252 and `"\a"` in a normal string becomes a bell character; use the Edit tool or
122
+ bytes with explicit `utf-8`.
123
+
124
+ ## Release process
125
+
126
+ Versioning is semver: patch for fixes, minor for features; while 0.x, a breaking
127
+ change bumps the minor. Tags are `vX.Y.Z`. Work reaches `main` only through a PR
128
+ from a feature branch; the release commit is the only thing pushed straight to `main`.
129
+
130
+ 1. `main` is up to date and its CI is green.
131
+ 2. Bump `__version__` in `src/atv_remote/__init__.py` (if not bumped yet).
132
+ 3. Cut the changelog (`changelog:release` skill): rename `## [Unreleased]` to
133
+ `## [X.Y.Z] - YYYY-MM-DD` and add an empty `## [Unreleased]` above it. Don't touch
134
+ any entry.
135
+ 4. Commit on `main` with the message `Release X.Y.Z` (plus the co-author trailer).
136
+ 5. Annotated tag: `git tag -a vX.Y.Z -m "windows-apple-remote X.Y.Z"`, then push `main`
137
+ and the tag.
138
+ 6. GitHub release: tag `vX.Y.Z`, title `vX.Y.Z`, body:
139
+
140
+ ````markdown
141
+ <one-line summary of the release>
142
+
143
+ ```
144
+ pip install windows-apple-remote
145
+ atv-remote
146
+ ```
147
+
148
+ <the X.Y.Z section of CHANGELOG.md, copied as is: ### Added / Changed / Fixed / Removed>
149
+ ````
150
+
151
+ If something in the release hasn't been tried on a real iPhone, add a
152
+ `### Known issues` section at the end saying what.
153
+ 7. This machine has no `gh` or GitHub login (git pushes over SSH), so build the URL
154
+ `https://github.com/aryakvn/windows-apple-remote/releases/new?tag=vX.Y.Z&title=vX.Y.Z&body=<url-encoded body>`
155
+ and open it with PowerShell `Start-Process`; the user clicks **Publish release**.
156
+ PRs work the same way via `.../compare/main...<branch>?expand=1&title=...&body=...`.
157
+ 8. Publishing the release runs `publish.yml`: tests, tag vs `__version__` check,
158
+ build, `twine check`, upload to PyPI. PyPI versions can't be reused, so offer a
159
+ TestPyPI dry run first (run the Publish workflow by hand) when a change is untested.
160
+ 9. Check the Actions run; if the upload failed because the trusted publisher wasn't
161
+ set up yet, re-run the failed job after the user adds it.
162
+
163
+ ## References
164
+
165
+ - pyatv protocol docs: docs/documentation/protocols.md in postlund/pyatv.
166
+ - pyatv issue #2325 "[Companion] Documentation Dumps" — real Apple TV message dumps.
167
+ - thiccaxe/CompanionGames — a working Companion server for the iOS remote.
168
+ **AGPL**: take protocol facts only, never copy code (this package is MIT).
169
+ - pyatv's `scripts/atvproxy.py companion` can MITM a real Apple TV to capture replies.
170
+
171
+ ## Gotchas
172
+
173
+ - Dev install must be editable: `pip install -e ".[test]"`. A plain `pip install .`
174
+ puts a stale copy in `site-packages` and source edits stop taking effect (this
175
+ happened repeatedly while debugging; check traceback paths).
176
+ - pyatv is pinned `>=0.18,<0.19` because we use internals (`CompanionServerAuth`,
177
+ `opack._unpack`, companion framing). Re-check the OPACK fix and auth overrides
178
+ before bumping.
179
+ - Windows Firewall must allow Python on private networks (TCP port + UDP 5353).
180
+ - Windows has one Play/Pause key, so Play and Pause both toggle.
181
+ - Without the Volume bit (0x100) in `_mcF`, iOS doesn't route the iPhone's volume
182
+ buttons to the device. `SetVolume` (absolute `_vol`) is turned into one relative
183
+ key step.
184
+ - `atv-remote.exe` running locks the venv: pip can't reinstall (WinError 32) and may
185
+ leave the package uninstalled. Stop the server before `pip install -e .`, or run
186
+ tests with `PYTHONPATH=src`.
187
+ - Run with `atv-remote -v` to log every received and sent message.
188
+ - The PyPI trusted publisher's project name must equal `name` in `pyproject.toml`
189
+ (`windows-apple-remote`). A mismatch still passes the token exchange but the upload
190
+ fails with `400 Non-user identities cannot create new projects`. That is why v0.1.0
191
+ (published as `atv-remote-server`) never reached PyPI and 0.1.1 is the first upload.
192
+ - Distribution name is `windows-apple-remote`, but the import package stays
193
+ `atv_remote` and the command stays `atv-remote`.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Arya Kavian
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.
@@ -0,0 +1,118 @@
1
+ Metadata-Version: 2.5
2
+ Name: windows-apple-remote
3
+ Version: 0.1.1
4
+ Summary: Control your PC's media with the iOS Apple TV Remote app (powered by pyatv).
5
+ Project-URL: Homepage, https://github.com/aryakvn/windows-apple-remote
6
+ Project-URL: Issues, https://github.com/aryakvn/windows-apple-remote/issues
7
+ Project-URL: Changelog, https://github.com/aryakvn/windows-apple-remote/blob/main/CHANGELOG.md
8
+ Author-email: Arya Kavian <aryakvn@gmail.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: apple tv,companion,media keys,pyatv,remote
12
+ Classifier: Environment :: Console
13
+ Classifier: Operating System :: Microsoft :: Windows
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Multimedia
16
+ Requires-Python: >=3.10
17
+ Requires-Dist: cryptography>=42
18
+ Requires-Dist: pyatv<0.19,>=0.18
19
+ Requires-Dist: zeroconf>=0.131
20
+ Provides-Extra: test
21
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'test'
22
+ Requires-Dist: pytest>=8; extra == 'test'
23
+ Description-Content-Type: text/markdown
24
+
25
+ # windows-apple-remote
26
+
27
+ [![CI](https://github.com/aryakvn/windows-apple-remote/actions/workflows/ci.yml/badge.svg)](https://github.com/aryakvn/windows-apple-remote/actions/workflows/ci.yml)
28
+ [![PyPI](https://img.shields.io/pypi/v/windows-apple-remote)](https://pypi.org/project/windows-apple-remote/)
29
+
30
+ Package: [`windows-apple-remote`](https://pypi.org/project/windows-apple-remote/) · Source:
31
+ [github.com/aryakvn/windows-apple-remote](https://github.com/aryakvn/windows-apple-remote)
32
+
33
+ Control your Windows PC's media with the **Apple TV Remote** on your iPhone or iPad
34
+ (Control Center → Apple TV Remote). The PC advertises itself as an Apple TV over the
35
+ Companion protocol using [pyatv](https://pyatv.dev), and turns remote buttons into
36
+ media keys.
37
+
38
+ | Remote (touch area) | PC |
39
+ |---------------------------------------|--------------------------------------|
40
+ | Tap | Play/Pause |
41
+ | Swipe right / left | Next / Previous track |
42
+ | Swipe up / down (longer = more) | Volume Up / Down |
43
+ | iPhone volume buttons | Volume Up / Down |
44
+ | Back (Menu) | Escape |
45
+
46
+ ## Install & run
47
+
48
+ ```sh
49
+ pip install windows-apple-remote
50
+ atv-remote
51
+ ```
52
+
53
+ Or the latest code from GitHub:
54
+
55
+ ```sh
56
+ pip install git+https://github.com/aryakvn/windows-apple-remote.git
57
+ ```
58
+
59
+ Then on the iPhone (same Wi-Fi network): Control Center → Apple TV Remote → pick your
60
+ PC's name. The first time, a 4-digit PIN is printed in the terminal; type it on the
61
+ phone. The phone is remembered afterwards.
62
+
63
+ Options:
64
+
65
+ ```
66
+ atv-remote --name "Living Room PC" # name shown on the phone (default: hostname)
67
+ atv-remote --address 192.168.1.20 # IP to advertise if auto-detect picks the wrong adapter
68
+ atv-remote --port 49200 # fixed TCP port (useful for firewall rules)
69
+ atv-remote -v # debug logging
70
+ ```
71
+
72
+ ## Notes
73
+
74
+ - **Windows Firewall:** allow Python on private networks when prompted, or the phone
75
+ can't connect. mDNS (UDP 5353) must be allowed too.
76
+ - **Forget all paired devices:** delete `%APPDATA%\atv-remote\state.json`. It also
77
+ holds this PC's private identity key, so keep it private.
78
+ - Media and volume keys are global; Back (Escape) goes to the focused window.
79
+ - Windows has a single Play/Pause key, so the remote's separate Play and Pause both toggle.
80
+ - Now-playing info and the volume slider are not supported (they need AirPlay/MRP).
81
+ - macOS support is planned. HomeKit is not supported.
82
+
83
+ ## Development
84
+
85
+ ```sh
86
+ git clone https://github.com/aryakvn/windows-apple-remote.git
87
+ cd windows-apple-remote
88
+ python -m venv .venv
89
+ .venv\Scripts\activate
90
+ pip install -e ".[test]"
91
+ pytest
92
+ ```
93
+
94
+ Use `pip install -e` (editable). A plain `pip install .` copies the code into the venv
95
+ and later edits stop taking effect. Stop a running `atv-remote` before reinstalling;
96
+ Windows locks its executable.
97
+
98
+ The tests use pyatv's own client to pair with the server and press buttons end to end.
99
+ Bugs and ideas: [issues](https://github.com/aryakvn/windows-apple-remote/issues).
100
+
101
+ ## Releasing to PyPI
102
+
103
+ Publishing uses [trusted publishing](https://docs.pypi.org/trusted-publishers/), so no
104
+ API token is stored in GitHub. One-time setup:
105
+
106
+ 1. On [pypi.org](https://pypi.org/manage/account/publishing/) add a *pending publisher*:
107
+ project `windows-apple-remote`, owner `aryakvn`, repository `windows-apple-remote`,
108
+ workflow `publish.yml`, environment `pypi`.
109
+ 2. Optional dry run: do the same on [test.pypi.org](https://test.pypi.org/manage/account/publishing/)
110
+ with environment `testpypi`.
111
+
112
+ Each release:
113
+
114
+ 1. Bump `__version__` in `src/atv_remote/__init__.py` and move the `Unreleased`
115
+ entries in `CHANGELOG.md` under the new version.
116
+ 2. Optional: run the **Publish** workflow by hand (Actions tab) to upload to TestPyPI.
117
+ 3. Publish a GitHub release tagged `vX.Y.Z` (matching `__version__`). The workflow runs
118
+ the tests, checks the tag against the version, builds, and uploads to PyPI.
@@ -0,0 +1,94 @@
1
+ # windows-apple-remote
2
+
3
+ [![CI](https://github.com/aryakvn/windows-apple-remote/actions/workflows/ci.yml/badge.svg)](https://github.com/aryakvn/windows-apple-remote/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/windows-apple-remote)](https://pypi.org/project/windows-apple-remote/)
5
+
6
+ Package: [`windows-apple-remote`](https://pypi.org/project/windows-apple-remote/) · Source:
7
+ [github.com/aryakvn/windows-apple-remote](https://github.com/aryakvn/windows-apple-remote)
8
+
9
+ Control your Windows PC's media with the **Apple TV Remote** on your iPhone or iPad
10
+ (Control Center → Apple TV Remote). The PC advertises itself as an Apple TV over the
11
+ Companion protocol using [pyatv](https://pyatv.dev), and turns remote buttons into
12
+ media keys.
13
+
14
+ | Remote (touch area) | PC |
15
+ |---------------------------------------|--------------------------------------|
16
+ | Tap | Play/Pause |
17
+ | Swipe right / left | Next / Previous track |
18
+ | Swipe up / down (longer = more) | Volume Up / Down |
19
+ | iPhone volume buttons | Volume Up / Down |
20
+ | Back (Menu) | Escape |
21
+
22
+ ## Install & run
23
+
24
+ ```sh
25
+ pip install windows-apple-remote
26
+ atv-remote
27
+ ```
28
+
29
+ Or the latest code from GitHub:
30
+
31
+ ```sh
32
+ pip install git+https://github.com/aryakvn/windows-apple-remote.git
33
+ ```
34
+
35
+ Then on the iPhone (same Wi-Fi network): Control Center → Apple TV Remote → pick your
36
+ PC's name. The first time, a 4-digit PIN is printed in the terminal; type it on the
37
+ phone. The phone is remembered afterwards.
38
+
39
+ Options:
40
+
41
+ ```
42
+ atv-remote --name "Living Room PC" # name shown on the phone (default: hostname)
43
+ atv-remote --address 192.168.1.20 # IP to advertise if auto-detect picks the wrong adapter
44
+ atv-remote --port 49200 # fixed TCP port (useful for firewall rules)
45
+ atv-remote -v # debug logging
46
+ ```
47
+
48
+ ## Notes
49
+
50
+ - **Windows Firewall:** allow Python on private networks when prompted, or the phone
51
+ can't connect. mDNS (UDP 5353) must be allowed too.
52
+ - **Forget all paired devices:** delete `%APPDATA%\atv-remote\state.json`. It also
53
+ holds this PC's private identity key, so keep it private.
54
+ - Media and volume keys are global; Back (Escape) goes to the focused window.
55
+ - Windows has a single Play/Pause key, so the remote's separate Play and Pause both toggle.
56
+ - Now-playing info and the volume slider are not supported (they need AirPlay/MRP).
57
+ - macOS support is planned. HomeKit is not supported.
58
+
59
+ ## Development
60
+
61
+ ```sh
62
+ git clone https://github.com/aryakvn/windows-apple-remote.git
63
+ cd windows-apple-remote
64
+ python -m venv .venv
65
+ .venv\Scripts\activate
66
+ pip install -e ".[test]"
67
+ pytest
68
+ ```
69
+
70
+ Use `pip install -e` (editable). A plain `pip install .` copies the code into the venv
71
+ and later edits stop taking effect. Stop a running `atv-remote` before reinstalling;
72
+ Windows locks its executable.
73
+
74
+ The tests use pyatv's own client to pair with the server and press buttons end to end.
75
+ Bugs and ideas: [issues](https://github.com/aryakvn/windows-apple-remote/issues).
76
+
77
+ ## Releasing to PyPI
78
+
79
+ Publishing uses [trusted publishing](https://docs.pypi.org/trusted-publishers/), so no
80
+ API token is stored in GitHub. One-time setup:
81
+
82
+ 1. On [pypi.org](https://pypi.org/manage/account/publishing/) add a *pending publisher*:
83
+ project `windows-apple-remote`, owner `aryakvn`, repository `windows-apple-remote`,
84
+ workflow `publish.yml`, environment `pypi`.
85
+ 2. Optional dry run: do the same on [test.pypi.org](https://test.pypi.org/manage/account/publishing/)
86
+ with environment `testpypi`.
87
+
88
+ Each release:
89
+
90
+ 1. Bump `__version__` in `src/atv_remote/__init__.py` and move the `Unreleased`
91
+ entries in `CHANGELOG.md` under the new version.
92
+ 2. Optional: run the **Publish** workflow by hand (Actions tab) to upload to TestPyPI.
93
+ 3. Publish a GitHub release tagged `vX.Y.Z` (matching `__version__`). The workflow runs
94
+ the tests, checks the tag against the version, builds, and uploads to PyPI.
@@ -0,0 +1,45 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "windows-apple-remote"
7
+ dynamic = ["version"]
8
+ description = "Control your PC's media with the iOS Apple TV Remote app (powered by pyatv)."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.10"
12
+ authors = [{ name = "Arya Kavian", email = "aryakvn@gmail.com" }]
13
+ keywords = ["apple tv", "remote", "pyatv", "media keys", "companion"]
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "Operating System :: Microsoft :: Windows",
17
+ "Topic :: Multimedia",
18
+ "Environment :: Console",
19
+ ]
20
+ dependencies = [
21
+ # Pinned to a minor: we build on pyatv internals (server auth, companion framing).
22
+ "pyatv>=0.18,<0.19",
23
+ "zeroconf>=0.131",
24
+ "cryptography>=42",
25
+ ]
26
+
27
+ [project.urls]
28
+ Homepage = "https://github.com/aryakvn/windows-apple-remote"
29
+ Issues = "https://github.com/aryakvn/windows-apple-remote/issues"
30
+ Changelog = "https://github.com/aryakvn/windows-apple-remote/blob/main/CHANGELOG.md"
31
+
32
+ [project.optional-dependencies]
33
+ test = ["pytest>=8", "pytest-asyncio>=0.23"]
34
+
35
+ [project.scripts]
36
+ atv-remote = "atv_remote.cli:main"
37
+
38
+ [tool.hatch.version]
39
+ path = "src/atv_remote/__init__.py"
40
+
41
+ [tool.hatch.build.targets.wheel]
42
+ packages = ["src/atv_remote"]
43
+
44
+ [tool.pytest.ini_options]
45
+ asyncio_mode = "auto"
@@ -0,0 +1,3 @@
1
+ """Control a PC's media with the iOS Apple TV Remote app, via pyatv."""
2
+
3
+ __version__ = "0.1.1"
@@ -0,0 +1,3 @@
1
+ from atv_remote.cli import main
2
+
3
+ main()