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.
- windows_apple_remote-0.1.1/.github/workflows/ci.yml +20 -0
- windows_apple_remote-0.1.1/.github/workflows/publish.yml +72 -0
- windows_apple_remote-0.1.1/.gitignore +6 -0
- windows_apple_remote-0.1.1/CHANGELOG.md +54 -0
- windows_apple_remote-0.1.1/CLAUDE.md +193 -0
- windows_apple_remote-0.1.1/LICENSE +21 -0
- windows_apple_remote-0.1.1/PKG-INFO +118 -0
- windows_apple_remote-0.1.1/README.md +94 -0
- windows_apple_remote-0.1.1/pyproject.toml +45 -0
- windows_apple_remote-0.1.1/src/atv_remote/__init__.py +3 -0
- windows_apple_remote-0.1.1/src/atv_remote/__main__.py +3 -0
- windows_apple_remote-0.1.1/src/atv_remote/cli.py +91 -0
- windows_apple_remote-0.1.1/src/atv_remote/keys.py +28 -0
- windows_apple_remote-0.1.1/src/atv_remote/server.py +407 -0
- windows_apple_remote-0.1.1/tests/test_server.py +180 -0
|
@@ -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,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
|
+
[](https://github.com/aryakvn/windows-apple-remote/actions/workflows/ci.yml)
|
|
28
|
+
[](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
|
+
[](https://github.com/aryakvn/windows-apple-remote/actions/workflows/ci.yml)
|
|
4
|
+
[](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"
|