Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
e1361db
windows: native Windows support as a self-contained root folder
Aug 11, 2026
f9a1114
windows: drop iced-Windows switcher + prebuilt dist/ (trim the PR)
Aug 11, 2026
26d52f2
windows(daemon): fetch FFmpeg at build time instead of vendoring it
Aug 11, 2026
1bb6e35
ci(windows): ship FFmpeg DLLs + prebuilt drivers in the artifact
Aug 11, 2026
fce90b1
ci(windows): pin FFmpeg to an immutable BtbN dated tag (fix SHA misma…
Aug 11, 2026
bd1b12d
driver(mic): strip render/keyword/debug from the SYSVAD sample (captu…
Aug 11, 2026
2147ce1
driver(mic): drop ToneGenerator + WaveReader (capture is mic-pipe only)
Aug 11, 2026
bd556f8
windows: drop dev/test CLIs (windows/tools) from the product tree
Aug 11, 2026
fc5fade
windows: drop the Rust librepods-tray — the WinUI client IS the tray
Aug 11, 2026
2068ba2
winui: start hidden to the tray on autostart (--tray)
Aug 11, 2026
91db553
driver(mic): strip round 3a — dead CircuitHelper funcs + unused audio…
Aug 11, 2026
b986e21
driver(mic): strip round 3b — dead render/multicircuit declarations (…
Aug 11, 2026
1707776
docs(mic): fix prebuilt README — point to ../install.ps1 (windows/dis…
Aug 11, 2026
4ef2e32
daemon: trust Windows BT status for teardown + rebuild the mic uplink…
Aug 11, 2026
38f424f
driver(mic): refresh prebuilt with the stripped, runtime-validated .sys
Aug 11, 2026
35472ee
windows: restore + modernise the one-shot installer, wire CI releases
Aug 11, 2026
81cff80
windows(installer): track bundled devcon.exe (force past the *.exe ig…
Aug 11, 2026
b1b6c8f
winui(docs): refresh screenshots (new UI: light/dark, hearing aid, tr…
Aug 11, 2026
c2f2bb0
windows: release the AAP driver handle on shutdown; graceful single-i…
Aug 11, 2026
7b6bcd1
docs(windows/hr): identity ruled out on 3 DIDs + macOS; reframe as ho…
Aug 11, 2026
18a8b69
windows(driver): open the ATT (hearing-aid) L2CAP channel lazily, not…
Aug 12, 2026
e9ab54e
winui(settings): collapse Experimental behind an expander (hidden by …
Aug 12, 2026
f35daae
windows(daemon): re-arm mic uplink in place on stall, don't rebuild t…
Aug 12, 2026
89cc173
winui: gate all experimental cards behind one experimental toggle
Aug 12, 2026
54e84c0
windows(driver): document that bthport refuses an ATT server on the r…
Aug 12, 2026
b6570c9
windows(daemon): never collapse the audio/mic link for HR; gate the G…
Aug 12, 2026
f62b393
windows(daemon): connect only A2DP for audio, never HFP — keep stereo…
Aug 12, 2026
a9bf5bf
winui(settings): add a "Start LibrePods at Windows login" toggle
Aug 12, 2026
c7c4388
windows(daemon): self-healing virtual-mic pipe + "mic operational" no…
Aug 12, 2026
04286c1
Revert "connect only A2DP for audio" — enable both again (keep an aud…
Aug 16, 2026
bf81bcf
windows(daemon): heart-rate over service 84 (0x54), not 19 — new-firm…
Aug 16, 2026
96fc5ab
windows(daemon): pick HR service (84 vs 19) by firmware version, like…
Aug 16, 2026
34cdfc2
windows(daemon): HR set on both 19+84, decode reports on 19/20/84 (fi…
Aug 16, 2026
5e9e088
windows(docs): update README + docs to match the shipped stack
Aug 30, 2026
393c651
windows(ci): re-pin FFmpeg to a month-end BtbN build (the old tag was…
Aug 30, 2026
ca60a90
windows: STOP_AUDIO is what breaks stereo — never leave mic mode mid-…
Aug 30, 2026
5f2e2c5
windows(driver): tear down L2CAP/ATT while the I/O target is still up
Aug 31, 2026
47e1b5b
windows(daemon): recover the audio route when another device takes th…
Aug 31, 2026
f9de6ca
windows(daemon): read the AirPods' own view of who is connected
Aug 31, 2026
966ca9d
windows(app): show which devices share the AirPods, and a "Restore au…
Aug 31, 2026
68d9d0e
windows(driver): refresh the prebuilt package with the teardown fix
Aug 31, 2026
357b670
windows(installer): ship the Code 38 recovery, and make the dist repr…
Aug 31, 2026
4df888b
windows(ci): ship a complete, self-contained install folder + GitHub …
Sep 14, 2026
345ff69
windows(driver): restore the AAP INF the prebuilt catalog was built for
Sep 14, 2026
134a40b
windows(mic): stop the capture ring from turning into a 1.4 s delay
Sep 20, 2026
817ec1f
windows(driver): request a 1691-byte inbound MTU on the AAP channel —…
Sep 29, 2026
692b1db
windows(daemon): heart-rate decoder and enable sequence for the worki…
Sep 29, 2026
6fa7a1a
Merge pull request #9 from arctumn/windows/heart-rate-mtu
arctumn Sep 29, 2026
8b59487
windows(docs): heart rate works — document the MTU cause, the stream …
Sep 29, 2026
8d950ef
windows(ci): build both kernel drivers from source alongside the daem…
Sep 29, 2026
e3c8853
windows(winui): heart-rate mini graph, and the card no longer warns i…
Sep 29, 2026
b599677
windows(daemon): track heart rate in a dedicated SQLite database
Sep 29, 2026
1fe6932
windows(ci): publish the freshly built driver packages as their own a…
Sep 29, 2026
7e7801f
Merge pull request #10 from arctumn/windows/heart-rate-mtu
arctumn Sep 29, 2026
8e855af
windows(winui): full-width heart-rate graph with values, past session…
Sep 29, 2026
b40103b
windows(docs): README for heart rate, the release assets and CI-built…
Sep 29, 2026
1d52448
docs: add Windows to the main README
Sep 29, 2026
04fb953
windows(ci): one release asset — LibrePods-Windows.zip carries everyt…
Sep 29, 2026
c83e102
Merge pull request #11 from arctumn/windows/single-release-zip
arctumn Sep 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
226 changes: 226 additions & 0 deletions .github/workflows/ci-windows.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
name: Windows CI (frontend)

# Builds the native Windows frontend: the Rust daemon (librepodsd) and the WinUI 3
# app (librepods-winui, which carries its own tray), and packs them with
# everything else into ONE self-contained install folder. The end user gets only
# that folder, so nothing may rely on tools installed on their PC (no WDK, no
# Windows SDK, no VC++ redistributable).
#
# The two kernel drivers (AAP L2CAP + hi-res mic) are built here from source with
# the WDK (installed via winget — approach from the Gab4545/librepods fork), their
# catalogs are regenerated, and the result overwrites windows/drivers/*/prebuilt so
# the install folder always carries drivers that match the source. The committed
# prebuilt packages remain for local installs; install.ps1 test-signs them on the
# user's machine either way.

on:
push:
branches: [main, windows-native]
tags: ['windows-v*']
paths:
- 'windows/**'
- '.github/workflows/ci-windows.yml'
pull_request:
paths:
- 'windows/**'
- '.github/workflows/ci-windows.yml'
workflow_dispatch:

jobs:
build:
# Pinned to windows-2022: windows-latest moved to Windows Server 2025 + VS2026,
# whose WDK/MSBuild integration for the WindowsKernelModeDriver10.0 toolset
# isn't there yet (MSB4062 loading Microsoft.DriverKit.Build.Tasks). VS2022
# (17.x) builds both drivers. Revisit once WDK support for VS2026 lands.
runs-on: windows-2022
# Creating/updating GitHub Releases needs write access; the default
# GITHUB_TOKEN is read-only on newer repos.
permissions:
contents: write

steps:
- uses: actions/checkout@v4

# ---- Kernel drivers (WDK) ------------------------------------------------
# A source change under windows/drivers/* only reaches users if the shipped
# .sys is rebuilt — e.g. the AAP channel's 1691-byte MTU that heart rate needs.
# Build both drivers here with Microsoft's WinGet-based WDK install
# (learn.microsoft.com/windows-hardware/drivers/install-the-wdk-using-winget),
# regenerate their catalogs, and overwrite windows/drivers/*/prebuilt so the
# "Assemble install folder" step below bundles the fresh builds.
# windows-2022 doesn't ship winget — install it first.
- name: Install winget
uses: Cyberboss/install-winget@v1
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Install Windows SDK + WDK (winget)
shell: pwsh
run: |
foreach ($id in 'Microsoft.WindowsSDK.10.0.26100', 'Microsoft.WindowsWDK.10.0.26100') {
winget install --source winget --exact --id $id `
--accept-package-agreements --accept-source-agreements --disable-interactivity
# -1978335189 (0x8A15002B) = already installed / no applicable update.
if ($LASTEXITCODE -and $LASTEXITCODE -ne -1978335189) { throw "winget $id failed (exit $LASTEXITCODE)" }
}

- name: Build drivers (KMDF, x64 Release)
shell: pwsh
run: |
$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
$msbuild = & $vswhere -latest -version "[17.0,18.0)" -requires Microsoft.Component.MSBuild `
-find 'MSBuild\**\Bin\MSBuild.exe' | Select-Object -First 1
if (-not $msbuild) { throw "MSBuild.exe not found via vswhere for VS2022 (17.x)" }
Write-Host "Using MSBuild: $msbuild"
# AAP: the WDK's own post-build Inf2Cat runs before the .sys sits next to
# the .inf and fails the signability test — the catalog is made below.
& $msbuild windows/drivers/aap/LibrePodsAAP.vcxproj /p:Configuration=Release /p:Platform=x64 /p:EnableInf2cat=false /m
if ($LASTEXITCODE) { throw "AAP driver build failed (exit $LASTEXITCODE)" }
& $msbuild windows/drivers/mic/AudioCodec/Driver/AudioCodec.sln /p:Configuration=Release /p:Platform=x64 /m
if ($LASTEXITCODE) { throw "Mic driver build failed (exit $LASTEXITCODE)" }

- name: Regenerate driver catalogs + refresh the prebuilt packages
shell: pwsh
run: |
$inf2cat = Get-ChildItem "${env:ProgramFiles(x86)}\Windows Kits\10\bin" -Recurse -Filter Inf2Cat.exe |
Where-Object { $_.FullName -match '\\x86\\' } | Select-Object -First 1
if (-not $inf2cat) { throw "Inf2Cat.exe not found under Windows Kits" }

# name, freshly built .sys, the .inf to ship, catalog name, prebuilt dir
$drivers = @(
@{ Name = 'LibrePodsAAP'; Inf = 'windows/drivers/aap/LibrePodsAAP.inf'; Cat = 'librepodsaap.cat'
Sys = (Get-ChildItem windows/drivers/aap -Recurse -Filter LibrePodsAAP.sys |
Where-Object { $_.FullName -match '\\x64\\Release\\' -and $_.FullName -notmatch '\\prebuilt\\' } |
Select-Object -First 1).FullName
Out = 'windows/drivers/aap/prebuilt' },
@{ Name = 'AudioCodec'; Inf = 'windows/drivers/mic/AudioCodec/Driver/x64/Release/AudioCodec.inf'; Cat = 'audiocodec.cat'
Sys = 'windows/drivers/mic/AudioCodec/Driver/x64/Release/AudioCodec.sys'
Out = 'windows/drivers/mic/prebuilt' }
)
foreach ($d in $drivers) {
foreach ($f in @($d.Sys, $d.Inf)) { if (-not $f -or -not (Test-Path $f)) { throw "$($d.Name): missing '$f' after build" } }
$pkg = "wdk-pkg-$($d.Name)"
Remove-Item $pkg -Recurse -Force -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force -Path $pkg | Out-Null
Copy-Item $d.Sys, $d.Inf $pkg -Force
& $inf2cat.FullName /driver:$pkg /os:10_X64
$cat = Join-Path $pkg $d.Cat
if (-not (Test-Path $cat)) { throw "$($d.Name): Inf2Cat did not produce $($d.Cat)" }
Copy-Item (Join-Path $pkg '*') $d.Out -Force
Write-Host "==> Freshly built $($d.Name) copied into $($d.Out)"
}

# ---- Rust: daemon (native msvc target) ---------------------------------
- name: Install Rust
uses: dtolnay/rust-toolchain@stable

- name: Cache Cargo
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
windows/daemon/target
key: ${{ runner.os }}-cargo-${{ hashFiles('windows/**/Cargo.lock') }}

- name: Fetch FFmpeg (AAC-ELD decode libs — not vendored in git)
shell: bash
run: windows/daemon/fetch-ffmpeg.sh

- name: Build daemon (librepodsd)
working-directory: windows/daemon
# Static CRT: otherwise the msvc build imports VCRUNTIME140.dll, which a
# clean Windows does not have, and the daemon won't start. (The FFmpeg
# DLLs only need the UCRT, which ships with Windows 10+.)
env:
RUSTFLAGS: -C target-feature=+crt-static
run: cargo build --release

# ---- WinUI 3 app (unpackaged, self-contained) --------------------------
# WinUI resource generation runs the PRI MSBuild task
# (Microsoft.Build.Packaging.Pri.Tasks.dll), which is a .NET *Framework*
# assembly. `dotnet build` hosts MSBuild on .NET (Core) and cannot load it
# → MSB4062. Visual Studio's msbuild.exe is .NET Framework and loads it, so
# build the WinUI project with VS MSBuild. The .NET 10 SDK is still needed
# for the net10.0 target resolver.
- name: Setup .NET 10
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'

- name: Setup MSBuild
uses: microsoft/setup-msbuild@v2

- name: Restore WinUI app
working-directory: windows/winui
run: msbuild LibrePods.WinUI/LibrePods.WinUI.csproj -t:Restore -p:Configuration=Release -p:Platform=x64

- name: Build WinUI app
working-directory: windows/winui
run: msbuild LibrePods.WinUI/LibrePods.WinUI.csproj -t:Build -p:Configuration=Release -p:Platform=x64 -p:RestorePackages=false

# ---- Collect + upload artifacts ----------------------------------------
- name: Assemble install folder
shell: pwsh
run: |
# make-dist.ps1 is the one place that knows the installer's layout (the
# local dist uses it too), and it fails if any file install.ps1 needs is
# missing — so an incomplete bundle can't be uploaded again.
# Find the WinUI output dir by locating the exe (path varies with TFM/RID).
$exe = Get-ChildItem -Recurse -Filter librepods-winui.exe `
windows/winui/LibrePods.WinUI/bin | Select-Object -First 1
if (-not $exe) { throw "WinUI build output (librepods-winui.exe) not found" }
./windows/installer/make-dist.ps1 -Out "$env:GITHUB_WORKSPACE/dist" `
-DaemonExe windows/daemon/target/release/librepodsd.exe `
-WinUIDir $exe.Directory.FullName

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: librepods-windows-frontend
path: dist
if-no-files-found: error

# ---- Releases --------------------------------------------------------
# push to windows-native → rolling "windows-nightly" pre-release, rebuilt on
# every push and pointing at that commit.
# push a windows-v* tag → a versioned release.
# PRs and other branches only get the Actions artifact above.
- name: Package release zip
if: startsWith(github.ref, 'refs/tags/windows-v') || (github.event_name == 'push' && github.ref == 'refs/heads/windows-native')
shell: pwsh
run: Compress-Archive -Path dist/* -DestinationPath LibrePods-Windows.zip

- name: Publish nightly pre-release
if: github.event_name == 'push' && github.ref == 'refs/heads/windows-native'
shell: pwsh
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Recreate instead of editing, so the tag moves to this commit too.
gh release delete windows-nightly --cleanup-tag --yes 2>$null
$global:LASTEXITCODE = 0
$notes = @"
Automatic build of ``windows-native`` at ${{ github.sha }}.

Extract the zip, then in an **admin** PowerShell inside the folder:
``powershell -ExecutionPolicy Bypass -File .\install.ps1``
(Test Mode must be on first — see windows/README.md.)
The zip is everything: the app, the daemon and both drivers (``driver\``,
``driver-mic\``), all built from source in this run.
"@
gh release create windows-nightly LibrePods-Windows.zip `
--target ${{ github.sha }} --prerelease --latest=false `
--title "LibrePods for Windows (nightly)" --notes $notes
if ($LASTEXITCODE) { throw "gh release create failed ($LASTEXITCODE)" }

- name: Create versioned GitHub Release
if: startsWith(github.ref, 'refs/tags/windows-v')
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: LibrePods for Windows ${{ github.ref_name }}
files: LibrePods-Windows.zip
generate_release_notes: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Loading