Skip to content

Full Windows port - #221

Open
g00fle wants to merge 17 commits into
eklonofficial:mainfrom
g00fle:windows-port
Open

g00fle wants to merge 17 commits into
eklonofficial:mainfrom
g00fle:windows-port

Conversation

@g00fle

@g00fle g00fle commented Sep 23, 2026 •

Copy link
Copy Markdown

Full Windows Port

Adds Windows 10/11 support to Vice. The Linux code paths are unchanged. Windows code lives in new modules or in if IS_WINDOWS branches placed in front of the existing Linux code.

Recording

  • Capture: new recorder_win.py backend using ffmpeg's ddagrab (Desktop Duplication). The replay buffer is a rolling ring of 2-second segments, and saving a clip joins the newest ones.
  • Encoders: NVENC, Quick Sync and AMF, with x264 as a fallback. Each encoder is test-encoded once before use, so one that the driver rejects is skipped instead of breaking recording.
  • Audio: new win_audio.py records desktop audio through WASAPI loopback and the microphone through ffmpeg. Audio and video share a start time, so clips stay in sync.
  • Sessions and screenshots: use the same capture path.
  • OBS backend (optional): new recorder_obs.py drives OBS's replay buffer over obs-websocket v5. It's for exclusive-fullscreen games that ddagrab can't see. Clips from OBS go through the normal Vice library pipeline.

Desktop integration

  • Hotkeys: new hotkey_win.py uses a low-level keyboard hook that passes keys through. Keys map to the existing evdev names, so configs and the hotkey UI are unchanged. The combo and double-tap logic moved into a shared hotkey_dispatch.py,
  • Game detection: reads the foreground window's process, so the existing .exe entries in games.json match.
  • Discord presence: connects over named pipes.
  • Clipboard, file reveal and notification sounds: use native Windows APIs.
  • App window: uses WebView2, with Vice's icon and its own taskbar ent
  • Start at login: through the registry. vice autostart turns it on or off.

Daemon

  • Platform paths: new oscompat.py holds the platform paths (%APPDAP%) and the process helpers. On Linux it returns the same paths asbefore.
  • Control channel: IPC runs over localhost TCP with a token, since Wile locking and process-tree cleanup have Windows versions.
  • Crash recovery: a crashed daemon is detected, so Vice restarts cleanly.

Install and UI

  • Installer: install.ps1 installs Python and ffmpeg through winget, downloads cloudflared, sets up a venv under %LOCALAPPDATA%\Vice, and adds a Start Menu shortcut. It doesn't need admin rights and supports -Uninstall.
  • Settings: show the Windows encoders and the OBS options, and hide the Linux-only ones.
  • README: new Windows install, compatibility and troubleshooting sect
  • CI: a windows-latest job runs the platform-neutral test modules.

Also fixed

  • Python 3.12 test hang: test_share_server hung on Python 3.12 when ffmpeg was installed. The tests now stub the background ffmpeg work that triggered it.

Adds a platform layer (paths, subprocess flags, opening files, clipboard)
and Windows implementations beside the Linux ones:

- Capture: ffmpeg ddagrab into a rolling MPEG-TS segment ring, with a
  probed encoder chain (NVENC/QSV/AMF zero-copy, then any hardware
  encoder via hwdownload, then x264) and per-run segment directories.
- Audio: WASAPI loopback and microphones captured in Python (soundcard)
  and served to ffmpeg over loopback TCP, padded to the wall clock and
  anchored to the same epoch as the video timestamps, so clips stay in
  sync (measured ~0 ms offset, no drift).
- Hotkeys: low-level keyboard hook reporting evdev key names, sharing
  the combo/double-tap dispatcher with the evdev listener.
- Daemon IPC over token-authenticated loopback TCP, msvcrt file locks,
  a shutdown handler instead of self-SIGTERM, psutil tree kills.
- Window detection, pointer-follow display, DXGI output enumeration,
  Discord RPC over named pipes, winsound notifications, Win32 clipboard.

Linux code paths are unchanged; Windows branches sit in front of them.
The daemon reports its platform in status. On Windows, Settings offers the
ffmpeg and OBS backends, NVENC/QSV/AMF encoders, OBS connection fields and
an 'Extra ffmpeg arguments' label, and hides the wf-recorder microphone
mode. About, the hotkey banners, the codec-fallback notice and the
blocklist help get Windows wording instead of udev, systemd and X11
instructions. Linux keeps every existing string and option.

Also fixes tools/check-locales.mjs on Windows (URL.pathname -> fileURLToPath).
verify-custom-accent spawned npx, which Windows only has as a .cmd shim
that execFileSync will not start; run vite's entry with this node instead.
It also parsed accents.ts assuming LF, which an autocrlf checkout breaks.
The accent-parity vite config used URL.pathname, which is /C:/... on
Windows; use fileURLToPath.
recording.backend = "obs" drives OBS Studio's replay buffer over
obs-websocket v5 (Hello/Identify with SHA-256 auth, SaveReplayBuffer and
the ReplayBufferSaved event, StartRecord/StopRecord for sessions). The
saved file is moved into the library under Vice's name and goes through
the usual trim, volume and watermark steps. A buffer the user started
themselves is left running on exit.

tests/test_windows_port.py covers key mapping, the wall-clock audio
padding and epoch anchoring, encoder plan ordering, capture command
building, segment selection, filter-path escaping, the TCP IPC transport,
Win32 helpers, and the OBS protocol against a fake obs-websocket server.
Screen capture and injected-key tests run with VICE_LIVE_TESTS=1.

Also lock a byte far past the content on Windows: byte-range locks there
are mandatory, so locking byte 0 made the pid in the lock file unreadable.
install.ps1 installs Python, ffmpeg (with ddagrab) and optionally
cloudflared through winget, puts Vice in a private venv under
%LOCALAPPDATA%\Vice, adds a Start Menu shortcut and a vice command, and
can register recording at login; -Uninstall reverses all of it and never
touches clips. It is ASCII-only and routes native programs through a
helper, because Windows PowerShell 5.1 aborts on native stderr and eats
a loose -- argument.

evdev becomes Linux-only and soundcard Windows-only in the dependencies.
The README gains Windows install, compatibility and troubleshooting
sections. CI adds a windows-latest job that runs the modules covering
shared and Windows code; the Linux-mechanics modules stay Linux-only.

Settings also hides replay storage, colour depth and hardware decode on
Windows, where they do nothing, and doctor prints Windows locations.
A module named platform inside the package shadows the standard library's
the moment anything runs a file in vice/ directly, and uuid, among others,
imports platform. Also let the Windows port tests load on Linux, where
ctypes has no WINFUNCTYPE.
…eding admin

soundcard initialises COM only on the thread that first imports it. When
the Settings audio-source list imported it on a worker thread first, the
capture thread had no COM and every source failed with CO_E_NOTINITIALIZED,
leaving the recorder down. Each soundcard thread now initialises COM
itself, after the import, since soundcard treats S_FALSE at import as fatal.

winget's cloudflared package is a machine-wide MSI that prompts for
administrator rights. install.ps1 now downloads Cloudflare's standalone
exe into Vice's own bin folder instead, and the share server also looks
there, for a daemon started before the PATH change reached it.
Windows does not refuse a loopback connection to a closed port at once; it
retries for about two seconds. daemon_is_running() probed a crashed
daemon's port with a 0.2 s timeout, which timed out rather than being
refused, read as "a daemon may be busy", and so every start after a crash
or a kill failed with "Vice is already starting, running, or shutting
down" until the temp files were deleted by hand.

The endpoint file already names the daemon's pid, so a dead (or reused,
non-Python) owner now marks the endpoint stale without any probing, and
open_ipc_connection fails fast in the same case. Found by a soak test that
kills the daemon outright; the restart now also reaps the orphaned ffmpeg.
- install.ps1: a user PATH with a single entry came back from the pipeline
  as a string, and the Vice folder was glued onto it with no separator,
  breaking PATH on a fresh Windows install. Uninstall now also stops
  anything running from the venv and retries removal instead of aborting.
- Audio: a capture thread that dies (device unplugged) now closes its
  ffmpeg inputs, so the watchdog sees an unhealthy recorder and restarts it
  instead of ffmpeg waiting forever on a silent input.
- Encoders: hevc_qsv/av1_qsv/hevc_amf/av1_amf kept H.264 as the codec
  family, so the H.264 plan won and the chosen codec was silently ignored.
  A chosen libx264 no longer raises the CPU-fallback banner. Failed probes
  are no longer cached (a locked screen made them permanent), nor is a
  missing ffmpeg. Start-up work that blocks runs off the event loop.
- Daemon: the Windows shutdown handler is registered before anything
  starts, so a stop during start-up is honoured instead of dropped.
- Hotkeys: modifiers are read from GetAsyncKeyState at each press, and a
  keydown long after the last event is a new press, so key-ups lost to the
  lock screen or a UAC prompt no longer leave Win/Alt stuck or F9 dead.
- gpu_vendors() counts every adapter, not only those driving a monitor, so
  NVENC is found on laptops using only their built-in panel.
- Raising the existing window only matches Vice's own window, and
  follow-the-pointer uses MonitorFromPoint instead of a DXGI walk each tick.
- vice uninstall on Windows reads the clips folder before removing the
  config, deletes only clip files, and never deletes clips under --yes.
- tests: stub AutoPlaylistToggleTests' background thumbnailing, which hung
  the whole suite at teardown on Python 3.12 when ffmpeg is installed (the
  hang predates the port; main hangs the same way).
Reading them on the event loop (the previous fix for modifiers stuck by
key-ups lost to the lock screen) raced a quick Ctrl+F9: by the time the loop
ran, Ctrl was already up and the combo read as plain F9. The hook now
samples GetAsyncKeyState when the key goes down and hands the set along
with the event. Caught by the live injected-key test.
When asyncio.run's teardown cancels a task mid-probe, Python 3.12 can close
the subprocess transport before the process exit is reported, and the
unbounded await in the cleanup never returned. That hung the Linux test
suite on 3.12 whenever ffmpeg was installed (on main too), and could hang
the daemon's own exit with a thumbnail or probe in flight.
Same Python 3.12 teardown hang as AutoPlaylistToggleTests: cancelling a
task while asyncio is still spawning ffprobe never returns on 3.12, so the
whole suite stalled whenever ffmpeg was installed. The hang is inside
CPython's create_subprocess_exec, so it is avoided in the tests rather than
worked around in Vice.
…tunnel

Share links were built from the public address the daemon had when the
library loaded. The app usually loads before cloudflared prints its URL,
so every listed clip carried the LAN fallback and only worked on the
local network. The store now moves those links onto the tunnel URL when
it arrives, and on load when the status already reports one.
pywebview's WinForms backend falls back to the interpreter's icon, and
without an AppUserModelID Windows groups the window under pythonw.exe.
# Conflicts:
#	vice/share.py
#	vice/ui/scripts/app.js
@eklonofficial

Copy link
Copy Markdown
Owner

Thank you for this, and I mean that. It's a serious piece of work: a ddagrab backend with a segment ring, encoder probing, WASAPI audio, a keyboard hook, and an OBS fallback for exclusive fullscreen. That's the whole shape of a port, not a sketch of one.

I'm not going to take it on right now, though, and I'd rather be straight about why than leave it sitting unanswered. Vice is still settling on Linux, and I'm fixing bugs there most weeks. Merging a port means every change from then on has to work on Windows too, and I can't test on Windows, so I'd be shipping a platform I can't stand behind. That wouldn't be fair to the people who'd install it.

I may well come back to Windows once Vice is more mature and the Linux side is quieter. I'll leave this open rather than close it, because it's the best starting point there could be for that, and if I do pick it up it'll be through your PR with your name on it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants