A full-screen, remote- and controller-driven home screen for a Mac mini in a living room. One calm screen for your streaming services, your Plex library, your own Switch-format games and your retro consoles, with macOS hidden underneath.
The goal is Apple TV-like simplicity on a Mac's power: pick up the remote, see what you were watching or playing, press Select. No windows, no cursor, no desktop. Because it is a real Mac underneath, it also runs things a streaming box cannot: Plex's own TV app, RetroArch, and Switch games built with the TVPort/NX2Apple engine.
Everything on the screens in this README is invented sample content (see Demo mode). No real viewing history, no real artwork.
- Streaming services: Netflix, Hulu, YouTube, Disney+, Prime Video, Max and Apple TV, each a tile on Home.
- Plex: the tile opens the Plex HTPC app. A Continue Watching row on Home opens a title there directly, on its own page, ready to resume.
- Games: Switch games built with the TVPort/NX2Apple engine, and retro
consoles through RetroArch (NES, SNES, N64, GameCube/Wii, Game Boy, DS,
PlayStation, PS2, PSP, Master System, Genesis, Sega CD, Saturn, Dreamcast,
TurboGrafx-16, Atari 2600, arcade). CouchMode ships no games, no BIOS
files and no emulator cores. It lists and launches the files you put in
~/Games/. - Settings: sound output, remotes and controllers (pairing), start at login, screensaver, interface sounds, sleep, restart and exit to desktop.
Netflix, Hulu, Disney+, Prime Video and Apple TV open in CouchMode's own TV screens: home rows with a
featured title, a details page with seasons and episodes, search with an
on-screen keyboard, and a profile picker. Those screens are the same for
every service (StreamHomeView, StreamDetailView, StreamSearchView),
and they only know a small, service-neutral vocabulary (StreamHome,
StreamTitle, StreamTitleDetails, in StreamCatalog.swift).
A small reader per service fills that vocabulary in. A reader is a
StreamSource (StreamSource.swift): home(), details(for:),
episodes(in:of:), search(_:), switchProfile(to:). Every real reader is a script package (apps/<id>/:
a manifest and a reader.js) run by one generic ScriptReader; Netflix, Hulu, Disney+, Prime Video and Apple TV
each have one. What a package returns is validated by PackageDecoder in CouchModeCore and tested against
synthetic fixtures and golden outputs. Services that don't have a package open their website with
remote-friendly navigation instead.
A reader is a proxy. Each thing a person does on CouchMode's screen is
the same single action on the real, signed-in website, read back:
open the service, search, open a title, switch season, switch profile.
Nothing is crawled, nothing is fetched ahead, nothing is cached beyond the
current screen. The shared PageDriver runs one action at a time in the
service's own web page, the newest action wins, and every step has a time
limit.
Each service's website is kept alive in a web view behind the CouchMode screen (the "backstage page"). That is how you stay signed in, how a reader has a page to act on, and how the site's own player is ready when you press Play. You never see the backstage page except while a title plays.
Playback is never drawn by CouchMode. Pressing Play hands off to the service's own web player, full screen, in its own page. CouchMode does not touch video, does not touch DRM, and does not try to. Back, Escape or leaving the player (the site's own back arrow, or the episode ending) returns to the CouchMode screen you came from.
The Plex tile launches the Plex HTPC app (the remote-friendly native Plex
client). CouchMode reads your Continue Watching from Plex using the
sign-in it finds in Plex Web inside CouchMode, keeps the token in this Mac's
Keychain, and opens a title in Plex HTPC on its own page. The details are in
PlexHTPC.swift.
Retro games are files in ~/Games/<console>/; RetroArch plays them using
cores in RetroArch's own folder. Switch games are Mac apps in
~/Games/switch/, built with the TVPort/NX2Apple engine; CouchMode reads an
optional catalog file for their names and art. Pressing Home (the Siri
Remote's TV button, a held Back, the Home key or Command-Delete) always ends
at CouchMode: a retro game saves its state and quits, a Switch game is
paused and shown first under Jump Back In.
- A Mac with Apple Silicon, macOS 14 or later.
- To install: nothing else. To build it yourself: Xcode (or the Swift 5.9+ toolchain).
- Optional: Plex HTPC, RetroArch with cores, NX2Apple game builds, and your own accounts for the streaming services.
- Permissions macOS will ask for: Accessibility (so Home works from other apps and the remote can drive them); Screen Recording only if you enable the debug channel's screenshot route.
Download the DMG from Releases, open it and drag CouchMode to Applications. The app is signed and notarized by Apple. Installed copies check for new releases themselves (Settings > Check for Updates, or Automatically Check).
scripts/check.sh # release build + all tests
scripts/build-app.sh # dist/CouchMode.app
scripts/deploy.sh # copy to the media Mac and relaunch it
Copy scripts/local.env.example to scripts/local.env (ignored by git)
and fill it in:
HTPC_HOST: the ssh destination of the Mac that runs CouchMode, fordeploy.sh. Key-based ssh must already work.HTPC_SIGN_IDENTITY: a code-signing identity fromsecurity find-identity -v -p codesigning. A stable identity keeps macOS's permission grants and Keychain access across rebuilds; without one the app is signed ad hoc and macOS asks again after each build.
More in agents/build-test-deploy.md.
| Input | Does |
|---|---|
| Arrows / d-pad / left stick / remote touch surface | Move focus |
| Return / A / remote click | Select |
| Escape / B / remote Back | Back |
I / remote Play-Pause |
Details (on Home and game tiles) |
| Command-Delete, Home key, remote TV button, held Back or Menu | Home, from anywhere |
On a streaming site's own page (while a title plays) the real keys go to the site's player; Back and Escape come back to CouchMode. Controllers need no setup beyond pairing; Settings has a Remotes & Controllers page for pairing.
On a TV Mac, turn off macOS's "Require password after screen saver begins or
display is turned off" (System Settings > Lock Screen > Never, or
sysadminctl -screenLock off in Terminal). Otherwise the Mac shows its lock
screen after it idles and a remote can't type the password. CouchMode can't
change this itself; Settings > Screen Lock shows the current state, and setup
mentions it when it is on.
Demo mode shows only invented titles with generated artwork, so screenshots and trials contain no personal data and no one else's art. It is off unless you turn it on:
defaults write local.brad.htpcshell DemoMode -bool true # then relaunch
defaults delete local.brad.htpcshell DemoMode # back to normal
In demo mode nothing is launched: no other app, no website, no player. See agents/demo-mode.md. The screenshots above were taken this way.
- No telemetry. CouchMode sends nothing about you anywhere.
- It talks only to the services you sign into (their websites, in their own web views) and to your own Plex servers, using the sign-in you gave those services. The Plex token is kept in this Mac's Keychain and is sent only to Plex.
- The optional debug channel is off by default and listens on 127.0.0.1 only.
- Logs never contain tokens.
- Only Netflix, Hulu, Disney+, Prime Video and Apple TV have CouchMode's own screens; the others open their website. Readers depend on each site's page, which can change without notice.
- "More in a row" (reaching the end of a row loads the next batch, one request each time, up to 40 titles a row) works on Disney+ and Prime Video, using the same paging request each site's own page makes. Hulu's home already returns a row's first 50 titles, more than a row holds, so there is nothing further to load. Netflix pages its rows with a persisted GraphQL query whose id is not in the page's data, so its rows stay at what the home page loaded. On Disney+ only the first rows carry titles (as on the site, later shelves load as they are scrolled to) and the profile list comes from the top bar's menu, so profiles are named, not numbered, and a name two profiles share can't be chosen.
- Disney+ may show its own "update this profile" or PIN step when a profile is chosen; CouchMode doesn't fill those in, so that switch is reported as failed and the profile is finished on Disney+'s own page.
- Prime Video's player opens over the title's page rather than as a page of its own, so CouchMode asks the page once a second whether the player is showing to know when you have left it (other services are detected by their address). Prime's home and details pages autoplay trailers; turn off "Autoplay video and live events" in Prime Video's playback settings (it is per profile), otherwise a trailer is started in the page behind CouchMode's screens (kept silent while it is out of view). Next-episode autoplay is a separate setting and is left alone.
- Prime Video lists titles you must rent, buy or get through a channel: they appear with their art and details, and Play opens Prime's own page for them. A season's episodes come from the season's own page, one page of them (Prime pages long seasons); a profile's id is its name, and a PIN or profile form is finished on Prime's own page.
- Apple TV's home is its Apple TV+ channel (Up Next with where you stopped, charts and rows); the reader works from the site's own signed-in API for the US storefront, and the page's tokens stay inside the page's scripts. Apple TV has no profiles on the web, so there is one. "More in a row" isn't read. The API answers a season's episodes in windows of about ten, so CouchMode asks for at most six windows per season and a very long season is cut short. Titles you must rent, buy or get on another service appear with their details, and Play opens Apple's own page for them. Apple's player opens as a dialog over the title's page, so CouchMode asks the page once a second whether it is showing. Apple TV's own pages autoplay previews, so the reader works from the search page, which plays nothing.
- Streaming picture quality is whatever each site gives a Mac web browser; for example some sites cap at a lower resolution than their TV apps.
- Switch games need NX2Apple builds you made yourself; retro games need RetroArch, its cores and (for some systems) BIOS files you provide.
- Not an App Store app; it is signed and run on your own Mac.
- Siri Remote and controller behavior was tested on a handful of devices.
Start with AGENTS.md: the ground rules, then an index into agents/.
MIT. See LICENSE.






