Pipes Windows toast notifications directly into SteamVR, so you never miss a Discord message, email, or system alert while in VR.
Notifications appear as a floating card in your headset — fully positioned, opacity-controlled, and fade-animated. Includes a desktop control panel and an in-VR dashboard tab with clickable settings.
- Listens to the Windows notification listener (
UserNotificationListener) via thewinsdkWinRT projection - Renders each toast as a styled card and uploads it to a SteamVR overlay
- Queue-based display — one notification at a time, with a "+N more" indicator when backlog grows
- Word-wrapped body text (up to 3 lines) instead of hard truncation
- Appears as a clickable button in the SteamVR dashboard
- Tabbed interface with four sections:
- Home — DND toggle + Send Test Notification buttons
- Position — X/Y/Z +/- buttons (VR-friendly, no keyboard needed)
- Appearance — Width/Alpha/Display/Fade +/- buttons
- History — recent notifications list
- All clicks handled via controller laser pointer
- Changes apply live and sync back to the desktop control panel
- System tray app with optional window
- Tabbed interface mirroring the VR dashboard:
- Controls — DND checkbox, Test, Preview buttons
- Position — X/Y/Z sliders for fine control
- Appearance — Width, Alpha, Display duration, Fade duration sliders
- History — scrollable notification log
- Preview Overlay button — shows a static card in VR for adjusting position/alpha live
- Status indicator (connecting / running / DND active / error)
- Settings saved to
config.jsonautomatically
- Auto-generates and registers the
.vrmanifeston first launch - Auto-enables auto-launch with SteamVR — starts automatically when SteamVR starts
- Paths with spaces work correctly (URL-encoded in the manifest)
- No manual manifest editing required
- Background-thread SteamVR init (window never freezes during startup)
- Auto-restarting listener with exponential backoff if the event loop crashes
- WinRT collection access via index (not iteration) — avoids the
iter() returned non-iteratorcrash - Timeouts on all WinRT async calls — no more event-loop deadlocks
- Debounced dashboard rendering — rapid clicks won't crash SteamVR's overlay subsystem
- Thread-safe DND flag with proper Tk marshaling
- Windows 10 or later (uses WinRT notification APIs)
- SteamVR installed and a VR headset connected
- Notification listener access granted in Windows Settings
- All of the above, plus:
- Python 3.10+
- Dependencies listed in
requirements.txt
- Download the latest
WN2SVR.exefrom Releases - Place it anywhere on your PC (folders with spaces are fine)
- Run it once — it will auto-register with SteamVR
- Grant notification access (see below)
- Done — it will auto-start with SteamVR from now on
If you move the exe to a different folder:
- Run it once from the new location — it will re-register with SteamVR automatically
- Your settings (
config.json) stay in the old folder. Copyconfig.jsonto the new folder if you want to keep your saved position/alpha/appearance settings.
- Open Windows Settings
- Go to Privacy & security → Notifications
- Enable "Let apps access your notifications"
If you skip this step, the app will run but never receive any notifications.
- Start SteamVR
- WN2SVR starts automatically (if auto-launch is enabled)
- Put on your headset
- When a Windows notification arrives, it appears as a card in VR
- Press the system button on your controller to open the SteamVR dashboard
- Find the VR headset icon in the button list at the bottom
- Click it — the settings panel appears
- Use the tabs and +/- buttons to adjust everything without leaving VR
- Click the tray icon to show/hide the window
- Use the tabs to adjust position and appearance
- Click "Preview Overlay" to see a static card in VR while you drag sliders
- Right-click the tray icon for quick actions (Open, Quit)
- Toggle from VR: Click the DND button on the Home tab
- Toggle from desktop: Check the DND checkbox on the Controls tab
- When active, notifications are still logged to history but not shown in VR
All settings are stored in config.json next to the executable (not in a temp folder or user profile):
| Setting | Description | Default |
|---|---|---|
pos_x |
Overlay X position (-1.0 to 1.0) | 0.25 |
pos_y |
Overlay Y position (-1.0 to 1.0) | 0.20 |
pos_z |
Overlay Z distance (-2.0 to -0.1) | -0.75 |
overlay_width_m |
Overlay width in meters (0.1 to 1.5) | 0.45 |
overlay_alpha |
Overlay opacity (0.1 to 1.0) | 0.9 |
display_seconds |
Seconds each notification stays visible (1.0 to 15.0) | 5.0 |
fade_duration |
Fade in/out duration in seconds (0.0 to 1.0) | 0.25 |
dnd_enabled |
Do Not Disturb flag | false |
history_max |
Max notifications kept in history | 50 |
You can edit this file manually (while the app is closed) or use the GUI. The file is created automatically on first save.
WN2SVR runs as a background process that hooks into two Windows subsystems:
- Windows Notification Listener — polls for new toast notifications via the WinRT
UserNotificationListenerAPI - SteamVR Overlay API — renders each notification as a texture and displays it on a floating card in VR
It also registers a dashboard overlay with SteamVR, which gives you an in-VR settings panel with tabbed controls (no need to take off the headset to tweak things).
The desktop control panel and system tray icon provide an alternative way to configure everything from your PC.
SteamVR isn't running or the headset isn't connected. Start SteamVR and restart the app.
Grant notification access in Windows Settings (see Installation above).
Restart SteamVR once. The tab should appear after that. If not, run the app once from the desktop to re-register the manifest.
The app looks for config.json next to the executable. If you moved the exe, either copy your old config.json to the new location or reconfigure via the GUI.
If the exe was moved, SteamVR's registration points to the old path. Run the exe once from the new location — it will re-register automatically.
This was a bug in earlier versions caused by file corruption from concurrent read/write. It's fixed — if you're still seeing it, make sure you're on the latest version.
This was a bug in earlier versions (RGB vs RGBA mode for the icon). It's fixed in the latest version.
- Install Python 3.10+ (ensure it's in your PATH)
- Clone the repo:
git clone https://github.com/yourusername/WN2SVR.git cd WN2SVR - Install dependencies:
pip install -r requirements.txt
python WN2SVR.pyUseful for development — you can edit the code and re-run immediately.
-
Install PyInstaller:
pip install pyinstaller
-
Build the exe:
pyinstaller --noconsole --onefile --collect-all openvr WN2SVR.py
-
The exe will be in
dist/WN2SVR.exe -
Copy it anywhere and run it once to register with SteamVR
--onefilebundles everything into a single exe (~40-50MB)--noconsoleprevents a console window from appearing- The exe is built on Windows, for Windows — there's no cross-compilation path since
winsdkis Windows-only
MIT License — see LICENSE for details.
You're free to use, modify, distribute, and sell this software. Attribution appreciated but not required.
Built using:
- openvr — SteamVR Python bindings
- winsdk — Windows Runtime Python projection
- pystray — System tray support
- Pillow — Image rendering
Potential future features (not implemented):
- Text-to-speech read-aloud via Windows SAPI
- Per-app blocklist/allowlist
- Auto-DND when headset is taken off
- Customizable card themes
- Notification grouping for rapid-fire messages
- System tray quick actions

