A lightweight system tray client for Home Assistant, built with Avalonia UI on .NET. It lives in your tray, connects to your HA instance over the WebSocket API, and gives you quick access to entities, cameras, media players, weather, and notifications without opening a browser tab.
- Quick-toggle tiles — pin lights, switches, and other entities to the tray flyout for one-click control, with custom labels, icons, and Small/Wide sizing.
- Visual tile layout editor — a drag-and-drop grid editor built into Settings → Tiles: reorder tiles by dragging, resize with a click, and drop one small tile squarely onto another to stack up to 4 entities into a single 2x2 Group tile (drag one back out to split it off again).
- Camera tiles — live snapshots with a detail flyout.
- Cover tiles — open/close/stop controls for blinds, garage doors, etc.
- Media player widget — playback controls for a chosen media player entity.
- Weather widget — current conditions and forecast.
- Sensor tiles & system sensor sharing — optionally publish this PC's metrics back to HA as
mobile_appsensors: CPU, memory, disk I/O activity, storage capacity used, disk throughput, GPU, battery, uptime, network throughput, active window, session lock state, system volume/mute, active audio output/input device + in-use state, active camera + in-use state, Wi-Fi SSID/BSSID/connection type, and display count + primary display resolution. Audio/camera/Wi-Fi/display sensors are currently Windows-only. - Remote volume control from HA — with the volume sensor enabled, an HA automation can mute, unmute, toggle mute, or set this PC's volume by sending a
notify.mobile_app_<device>call with acommand_volume_*message (see Remote commands). - Native notifications — subscribes to HA's mobile app push channel and shows notifications using the OS notification center, with an in-app history of the last 10.
- Secure login — signs in via HA's browser-based OAuth (loopback redirect, RFC 8252), the same flow HA's official mobile apps use. No long-lived access tokens are pasted in by hand.
- Persistent session — the OAuth refresh token is stored in the OS credential store (Windows Credential Manager, macOS Keychain, or the Linux equivalent) and access tokens are refreshed automatically before they expire.
- Autostart — optional launch on login, per-platform.
- Cross-platform — runs on Windows, macOS, and Linux (Linux tray support via D-Bus).
Most sensor data flows one way (PC → HA), but system volume is the exception: with the System volume sensor enabled (Settings → Sensors), HA can also control it. This isn't a separate entity or service — it piggybacks on the same notify.mobile_app_<device_slug> channel already used for push notifications, using a command_* message that the app recognizes and acts on locally instead of showing a notification.
Mute, unmute, or toggle mute:
service: notify.mobile_app_<device_slug>
data:
message: "command_volume_mute" # or command_volume_unmute / command_volume_toggle_muteSet an exact volume level (0-100):
service: notify.mobile_app_<device_slug>
data:
message: "command_volume_set"
data:
volume_level: 40<device_slug> is the device name set in Settings → Sensors, slugified (shown right below that field in the app). Currently implemented on Windows only.
The solution is split into two projects:
| Project | Purpose |
|---|---|
HaDesktop.Core |
Platform-agnostic library: HA WebSocket/REST client, OAuth login, mobile_app registration, credential storage, sensor collection, preferences persistence, notifications, and autostart — each with per-OS implementations behind an interface (*Manager/*Store/*Collector/*Notifier), selected at runtime via a .Current property. |
HaDesktop.Tray |
The Avalonia tray application: tray icon, flyout window, tiles, and settings UI. |
- .NET 10 SDK
- A running Home Assistant instance reachable from this machine
dotnet build HaDesktop.sln
dotnet run --project src/HaDesktop.TrayOn first run, click the tray icon and sign in with your Home Assistant URL — this opens your browser for HA's login page and completes the OAuth loopback flow automatically.
Tagged pushes (vX.Y.Z) trigger .github/workflows/release.yml, which builds and attaches to a GitHub Release:
- Windows —
HaDesktop-Setup-X.Y.Z.exe(Inno Setup installer, per-user install, no admin required) and aHaDesktop-X.Y.Z-win-x64-portable.zip(self-contained, no install needed). - macOS —
HaDesktop-X.Y.Z-osx-x64.zip/-osx-arm64.zip, each a self-contained.appbundle. - Linux —
HaDesktop-X.Y.Z-linux-x64-portable.tar.gz(self-contained single-file binary).
The workflow can also be run manually (workflow_dispatch) to sanity-check packaging without cutting a release.
To build the Windows installer locally, publish a self-contained win-x64 build to publish/win-x64 and run Inno Setup's ISCC.exe against installer/windows/setup.iss.
src/
HaDesktop.Core/
Autostart/ # Launch-on-login, per OS
Ha/ # HA WebSocket client, OAuth login, mobile_app API client
Notifications/ # Native OS notification wrappers
Sensors/ # System metrics collection (CPU/mem/disk/GPU/etc.), per OS
Storage/ # Credential store + local JSON preference stores
HaDesktop.Tray/
*.axaml(.cs) # Tray flyout, tiles, and settings windows (Avalonia)
AppSettings.cs # App-wide session/connection state and background timers
Program.cs # Entry point
installer/
windows/
setup.iss # Inno Setup script for the Windows installer
.github/workflows/
ci.yml # Build check on every push/PR
release.yml # Builds installer + portable packages and publishes a GitHub Release on tag push