I’ve been iterating on our home theater setup for a few years now. Recently it feels like it has stabilized somewhat so I think it’s time to share my work with the world. I am curious what people think and especially if people know of a better way to do things than I do.

Features

This home theater setup does a lot. Here is what I’ve integrated into one system:

  1. A receiver for controlling different HDMI inputs:
    • Xbox
    • Blu-Ray player (a PS3 at this time)
    • HTPC
    • Apple TV
  2. A home server for
    • Data backups
    • Media sharing
    • Hosting simple web apps
  3. Kodi, for playing local media
  4. Steam, for local multiplayer games
  5. Spotify, for music
  6. My gaming desktop PC
  7. All controlled primarily via smart phone

Here is how it all looks together:

Ideally there would be a line from the IR blaster to the Blu-Ray player but...

The easy part

Most of this is pretty low tech. If you ignore the HTPC and its attachments and focus on the HDMI-connected portion, this is a simple receiver-based setup. Each device has its own remote, you use the receiver’s remote to switch between inputs, and you use the TV’s remote to turn the TV on and off. Done.

HTPC

This is where it gets interesting. Inspired by Jeff Atwood’s HTPC builds, I built a low-power HTPC of my own. This was about 5 years ago so my hardware specs are probably irrelevant, but other than replacing a failed drive the thing has stood up very well over the years. I don’t have any plans to replace it any time soon. If I was to build another one, the general components I would look for are

  1. A mid-range Intel CPU with good integrated graphics
  2. Lotsa RAM
  3. An SSD
  4. A couple large NAS HDDs
  5. Splurge on a nice high-efficiency PSU, or listen to Jeff and get an external PSU if size is an issue for you.

The reasoning behind this is that this is primarily for streaming and network storage. When I built this, 1080p was still perfectly acceptable and Intel’s integrated graphics were perfectly cabaple of achieving 1080p video at 60fps. These days 4K is the goal to aim for and guess what? Intel’s integrated graphics are still keeping up. You just don’t need the cost, power, and clunkiness of a dedicated graphics card in your HTPC.

The SSD is for the OS because you want this baby to start up instantly. The HDDs are for media storage in RAID 1 because the best kind of backup is the one you don’t have to think about at all.

OS

My OS of choice is Arch Linux. I definitely don’t recommend this in general; you have to be a bit of Linux nerd to enjoy Arch. You probably could get by with Windows, but I think Linux (Arch, specifically) has benefits:

  1. Linux is the only OS I’ve used that runs as well on day 2000 as it did on day one. I don’t know what it is about Mac OS and Windows that causes them to sloooowly degrade over the years until you’re smashing your keyboard in frustration, but Linux doesn’t have it. This isn’t going to be your main PC, so you don’t want to have to do regular maintenance on it.
  2. Unless you have some very specific software in mind, Linux has the broadest software support. The main shortcoming used to be gaming, but with DXVK and Steam Play even that has virtually disappeared. So far Arch Linux has been able to satisfy all of my requirements.
  3. Every OS is hackable, but for an HTPC it’s especially important. It probably won’t have a mouse and keyboard for input and the traditional desktop metaphor makes no sense for it. If you want it to behave like a typical set-top device you want tight control over the startup process and you want to customize the UI. Again, it’s possible to do with other OSes but it’s easier with Linux, especially a bare-bones distribution like Arch.

Software

Here is the general software configuration of the HTPC.

  1. Arch installed in UEFI mode
  2. Motherboard boots Arch directly via EFISTUB. You don’t need GRUB for this box!
  3. systemd starts the usual boot stuff plus a few services:
    • sshd for admin work
    • NFS for accessing stored media on my other devices
    • lighttpd for webapps
  4. systemd automatically logs in to the unprivileged user account
  5. Bash is set up to immediately start X on login for that TTY.
  6. We don’t need a proper desktop environment, a window manager started via .xinitrc is sufficient. I like openbox for this because it gives you a very simple UI and is easy to set up
  7. The GUI-based applications have systemd user services set up for them:
    • Kodi
    • Steam
    • Spotify
    • A wake-on-LAN service that wakes up my Windows PC for streaming

    This is nice because all of their logs can be managed in one place (journalctl) and they can do stuff like auto-restarting on failure.

  8. On launch, openbox starts those services in its autostart script. This seems a bit weird but it’s actually the easiest way to manage them. X and systemd don’t play nice together, so having openbox do it guarantees that the X dependency is ready.
  9. Openbox rules make each application open fullscreen on its own desktop.

This simple process gets you most of the way there. You also have to do a little configuration in each application, like making sure that Kodi and Steam use good big-screen interfaces and work with whatever gamepads you have plugged in.

Once you have SSH set up, you can use x2x to use a laptop as a mouse and keyboard. This is handy for debugging or if you want to use your HTPC like a desktop PC occasionally.

The web server

You might have noticed the big missing piece here: there’s no way to control openbox itself! Gamepads work for Kodi and Steam, Spotify you can control through Spotify Connect, but how can you switch between the different desktops where these programs are running? How can you close and reopen an application if it gets into a weird state?

I first tried hacking this in by adding extra buttons to Kodi and Steam (since you can control both of those with a gamepad). Kodi has a plugin system that lets you add extra menu items that do whatever, and in Steam you can add external “games” that just run the commands you need. But this was really clunky to maintain, awkward to navigate in the UI, and didn’t play well with Steam because it got confused when the “game” it started didn’t open any windows and just switched to a different desktop.

I knew that a proper solution would have to be a totally separate piece of software. This was the first place where I couldn’t find any existing solution for what I needed. I knew all of the commands I needed to run as the HTPC user: wmctrl for issuing standard window manager commands like closing a window or changing desktop, systemctl --user for interacting directly with the services I defined, etc. Ideally I wanted an easy way to run these commands as needed from a remote device (e.g. a smartphone), and especially a way to do it securely.

That’s the real trick: custom, secure, remote control of an HTPC. Here’s the cheeky solution I came up with:

  1. systemd can monitor file paths for changes and start units in response, so
  2. Define a systemd user service for each command you want to be accessible remotely. This is nice because you can test each command by starting the services manually
  3. Create an empty file corresponding to each service as well as a systemd path unit to monitor each file and start the corresponding service when it changes. Again, you can test this by touching a file and verifying that its service runs. Place the empty files in a location that is only writable by your web server
  4. Make a simple web app with client-side buttons that make HTTP requests, and a server side that turns those request into touches on the corresponding empty files. Mine is just a static HTML page and a CGI script.

The cheeky bit is that I named these empty files “buttons”. So when you touch a button in my web app, the web server literally “touches a button” on the HTPC. systemd monitors those button touches and runs the services.

This is secure because

  1. The only commands that can be run are the ones you have defined services for (there’s no rm -rf / service)
  2. The only commands that the web app can trigger are the ones that systemd is looking for in the web server directory (if you wanted to have a “delete everything” service, just don’t set up a path unit monitoring the web server’s button for it)
  3. The only people who can access my web app are on my local network (I just don’t forward those ports)

Even if I exposed this to the Internet it wouldn’t be that bad. The worst I could get is a DOS attack with people repeatedly switching my desktop or restarting Kodi. Pretty limited threat model.

Because of the multiple pieces, I set up up a little project in Ruby that hooks it all up for me. I just create a data file saying which services I want exposed and how they should appear in the web app and it generates the path units, button files, and web app to match.

The final touch is that the wallpaper on my HTPC directs people how to access the web app on the local network. You only ever see the wallpaper if something goes wrong, so this works nicely.

The remote

All of the above is what we used successfully for several years, but my wife and I were still annoyed by the plethora of remotes:

  • Gamepads for Kodi/Steam
  • AppleTV remote
  • TV remote, on which we only ever use the power button
  • Receiver remote, our largest remote by far, on which we only use the buttons to switch input and raise/lower volume
  • Blu-Ray player remote (this was before we started using a PS3), on which we only use the usual play, pause, eject, select, etc. buttons but by tradition must include 20 other useless buttons

The first two you can’t really do without. Obviously you need the gamepads for gaming so no ditching those, and unless you’ve totally bought into the Apple ecosystem (we haven’t) there’s no way to control the Apple TV without the remote. But the rest should go!

The idea was simple: this HTPC is always sitting right next to the devices that we use remotes for, so just turn the HTPC into a master remote for all of them! My plan was to buy an IR emitter for it, set it up in a hidden location with line-of-sight to my devices, and extend my web app with more commands for issuing IR blasts.

I ended up buying a USB-UIRT. It looks good on paper but I do not recommend it. I like that it can both send and receive, so I can use LIRC to learn my remotes’ blast patterns and have it essentially replay them. But I found that only the seller’s proprietary Windows software worked well. With LIRC it worked for some devices (TV, receiver) but not others (Blu-Ray). And even when it did work, it was slow, taking about twice as long (or longer) to raise the volume by a certain amount.

I spent a lot of time messing around with LIRC settings, trying to modify the remote definitions, tweaking the drivers, all to no avail. LIRC supports a ton of different drivers, but I also had a really hard time finding any other IR blaster to buy. Everything I found was either being sold from a website that hadn’t been updated since the 90s or was a homebrew kit that you had to build yourself. Do I suck at Googling? Is there a good USB IR blaster out there that “just works”?

Anyway, for now this works, just not as well as I would like. It’s less of an issue now that we use the PS3 which again has its own proprietary controller that we can’t get rid of.

Upgraded app

With LIRC and the IR blaster set up, I needed more buttons in the web app. This was a little tricky because the web app had been designed for the case of one command per button push, but now I wanted to be able to continuously raise and lower volume. I hacked this in by defining multiple buttons: start raise volume, stop lower volume, etc. Then in the HTML/JS I add extra buttons that press those buttons on press/release and in the CSS I hide the start/stop ones. It is a hack, but it works perfectly, is purely client-side, and lets me keep the stupid simple model of one command per button.

It was easy to add buttons to toggle the TV power and to switch to whatever input on the receiver because that worked just like the old buttons.

The other part that was easy but kinda magical was that this let me unify everything in the web app. Previously there was a distinction between multiple inputs (PC, Xbox, etc.) controlled through the receiver and multiple applications (Kodi, Steam, etc.) controlled through the app. But now that the app could control the receiver, everything fell in line. If you tap the Kodi button, it switches the receiver input and the openbox desktop. Pretty cool.

The other features I’ve found necessary over time are basic troubleshooting buttons. I have a generic “close window” button that closes whatever is on top in openbox. Sometimes something (usually a Steam window) somehow ends up on top and you can’t control the thing you want. Also I changed the application buttons such that a single tap does a normal “switch to” button touch, but a second tap does a “restart” button touch. I implemented this using the same client-side hack as the volume control. I’ve noticed that sometimes Spotify or Kodi get in a weird state where you’ll switch to them and they’re just a blank window; tap again and they’ll restart (using systemd, which is really easy).

Future work

Overall I really like this system. With my smartphone and possibly one other specific controller (Xbox, PS3, Apple TV) I can interact with whatever media I want. I didn’t have to replace any devices with “smart” devices and even better it works more or less exactly how I want.

But there’s always room for improvement.

  1. I obviously want a better IR blaster. The PS3 is a lender so one day we’ll be stuck with the normal Blu-Ray player and I really don’t want to use its remote. Blu-Ray support on Linux is still not great as far as I know, so IR is the only path I have at this point. Which IR blaster is easy to buy and has great lirc support?
  2. I wish I could easily track the power state of everything. The HTPC is really the only thing that needs to be always-on (because it operates as a server). If it could track the power state of every device, it could do stuff like turn off a device when you switch away from it and turn it on automatically when you switch back. I could do this manually for now, but there would always be the risk of the recorded state getting out of sync and I can’t think of a good UX for fixing that.
  3. Before I got the Apple TV, the weak point of this setup was streaming video. The closest I could get was using x2x to connect my laptop to the HTPC and then opening a browser to Netflix/Hulu/whatever, but that was a really crappy UX. Seeing as all of these streaming services run fine in a browser on Linux, I feel like there should be a Linux app that is essentially Chromium with a good gamepad interface and a stripped-down UI (for example, I don’t need a URL bar or tabs or anything, I just need to be able to load from a pre-configured set of bookmarks). I tried using Steam’s web browser in this capacity but it didn’t work very well.

Update 2019-08-18

I moved a few weeks ago and the movers broke my TV. Turns out you can’t buy dumb TVs anymore so now I have a smart one, which replaces the receiver and Apple TV. It also has a pretty good unified remote that can control my Blu-Ray player, so there’s no need for the IR blaster either. The house is also bigger than my apartment so I set up a dedicated music system with Spotify rather than routing it through the TV.

The only interesting part left is my HTPC and remote app, but it only controls the HTPC itself (just has buttons for Kodi, Steam, and wake-on-LAN).