You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Depends on #213 (Winsock init): this branch is stacked on it, and basic_client cannot dial or listen on Windows without it. Review only the top commit; the base switches to main once #213 merges.
Summary
PortAudio on Windows. Windows has no pkg-config or system PortAudio, so examples/basic_client/CMakeLists.txt fetches PortAudio v19.7.0 on WIN32 and builds it as a static library. POSIX keeps the pkg-config lookup.
getopt() for Windows.examples/common/win_getopt.h is a minimal stand-in for the missing one. It covers grouped flags, -x val / -xval, and stops at the first non-option or --. Errors return ? without a message, since the example prints its usage on that.
The pairing-window gesture. Console keys p / c stand in for SIGUSR1 / SIGUSR2. A single #ifdef _WIN32 block defines the key poll (a no-op on POSIX) and the hint strings, so the usage text and the pairing-window prompt name the right gesture on each platform.
Persistence. The provider writes through _open / _write / _commit and replaces the file with std::filesystem::rename(), because std::rename() won't replace an existing file on Windows. It falls back to %USERPROFILE% when HOME is unset. Each write_file() variant has its own doc comment.
README.examples/basic_client/README.md gets a Windows section covering the build, no mDNS (so dial with -u), the p/c keys, and the persistence file's permissions.
The default build.tui_client includes POSIX <getopt.h>, so the top-level CMakeLists.txt skips it on WIN32 with a status message, the way source_client already skips itself without PortAudio. A standalone Windows build with the examples on (the default) now succeeds.
CRT warnings.basic_client defines _CRT_SECURE_NO_WARNINGS on WIN32, since the persistence provider's getenv() and _open() otherwise draw deprecation warnings pointing at _dupenv_s / _sopen_s.
Notes
The persistence file can't be created 0600 on Windows. It inherits its directory's ACL, which under %USERPROFILE% grants only the user, SYSTEM and Administrators.
tui_client, source_client and the tests remain POSIX-only. The top-level README's "macOS/Linux" wording is left alone, since Build the library on Windows with clang-cl #210 deferred the library's Windows support scope.
Windows 11, clang-cl 22.1.3 (Visual Studio 2026, Ninja, Release), from Fix what running on Windows turns up #211: sendspin and basic_client built with no warnings from the library's own sources. basic_client.exe -p 8929 -u ws://<music-assistant>:8927/sendspin completed the Noise handshake, was admitted (unpaired) and listened locally. Pairing and playback were not exercised.
Windows 11, clang-cl (Visual Studio 2026, Ninja), on this branch: a clean configure and build with the default BUILD_EXAMPLES succeeds, skipping tui_client and source_client, with no warnings from the repo's own sources. basic_client.exe -h names the p/c keys, an unknown option prints the usage, and basic_client.exe -p 8931 starts and listens on 8931.
The trimmed win_getopt.h was checked on Linux against basic_client's optstring: grouped flags, -pVAL, -p VAL, --, a lone -, an unknown option, a missing argument and -:.
Linux, GCC 16: host build including the examples is clean, and sendspin_tests passes 705/705. The sanitizer runs are this PR's CI.
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
The Windows persistence path reports success before the replacement is durable, so a power loss can discard an acknowledged pairing update and potentially restore a revoked record.
tui_client includes POSIX <getopt.h>, so a default Windows build (BUILD_EXAMPLES on when
standalone) failed compiling it. Skip it on WIN32 with a status message, as source_client
already skips itself without PortAudio.
The persistence provider's getenv() and _open() draw clang-cl's -Wdeprecated-declarations
for the CRT's _dupenv_s/_sopen_s variants. Define _CRT_SECURE_NO_WARNINGS for basic_client
on WIN32 so its build is warning-free.
commit() keeps its default for this write-through provider, so save_blob() returning true
promises the write is durable (client.h), and the provider's header asks a port to keep the
POSIX directory fsync's guarantee. The Windows write_file() flushed the temp file with
_commit() but left the rename itself unflushed, so a power loss could drop an acknowledged
write, including the zeroed blob that clears a revoked record.
Replace the file with MoveFileExA(MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH), which
does not return until the move is on disk.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
The header’s unconditional owner-only permissions guarantee conflicts with the Windows implementation’s reliance on unchanged directory ACLs and needs platform-specific clarification.
No open issues 🎉
1 previously missed issue
In code that hasn't changed since last review
Clarify Windows ACL behavior versus POSIX owner-only guarantee
The new Windows branch no longer satisfies the helper header's unconditional promise that files containing plaintext keys and PSKs are made owner-only with open()/fchmod() (examples/common/file_persistence_provider.h:26–32). Here access instead depends on the directory ACL, which this code does not tighten. Please update that header to distinguish the POSIX permissions guarantee from the Windows ACL behavior, especially since it explicitly encourages copying this provider into downstream projects.
Re the review's point on file_persistence_provider.h:26-32 (the unconditional owner-only promise): 73316d6 splits that paragraph by platform. On POSIX it keeps the 0600 open()/fchmod and the file and directory fsyncs. On Windows it states that the file inherits its directory's ACL, which write_file() does not tighten (under %USERPROFILE% that grants only the user, SYSTEM and Administrators; elsewhere, whatever the directory grants), and that the replace is made durable by _commit() and MoveFileExA(MOVEFILE_WRITE_THROUGH). Giving the file an explicit owner-only DACL on Windows is left out: under the default %USERPROFILE% path the inherited ACL already restricts it, and the README's Windows section notes the difference.
The header promised owner-only (0600) permissions and directory fsyncs unconditionally, and
invites copying the provider downstream. On Windows the file inherits its directory's ACL,
which write_file() does not tighten, and the replace is made durable by _commit() and
MoveFileExA(MOVEFILE_WRITE_THROUGH). Say which guarantee holds on which platform.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
The default Windows clang-cl build fails when installed PortAudio enables source_client, and extended-key scan codes can incorrectly authorize or cancel pairing.
No open issues 🎉
2 previously missed issues
In code that hasn't changed since last review
Skip source_client on Windows to avoid getopt.h build failure
CMakeLists.txt:277
On Windows with pkg-config and an installed PortAudio, examples/source_client/CMakeLists.txt:2–11 enables source_client rather than skipping it. The default clang-cl build then fails on its unconditional <getopt.h> include at examples/source_client/main.cpp:43. Explicitly skip source_client on Windows as well, rather than relying on PortAudio being absent.
Handle extended keys before processing pairing commands
examples/basic_client/main.cpp:168
_getch() returns extended keys as a 0/0xE0 prefix followed by a scan code, which this loop then treats as an ordinary character. Alt+F9 produces 0x70 (p) and Ctrl+F6 produces 0x63 (c), so unrelated key combinations can authorize or cancel pairing. Consume and discard the second value after an extended-key prefix before checking for p or c.
Review effort: Balanced
This branch has not been deployed
No deployments
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Depends on #213 (Winsock init): this branch is stacked on it, and
basic_clientcannot dial or listen on Windows without it. Review only the top commit; the base switches tomainonce #213 merges.Summary
examples/basic_client/CMakeLists.txtfetches PortAudio v19.7.0 onWIN32and builds it as a static library. POSIX keeps the pkg-config lookup.getopt()for Windows.examples/common/win_getopt.his a minimal stand-in for the missing one. It covers grouped flags,-x val/-xval, and stops at the first non-option or--. Errors return?without a message, since the example prints its usage on that.p/cstand in forSIGUSR1/SIGUSR2. A single#ifdef _WIN32block defines the key poll (a no-op on POSIX) and the hint strings, so the usage text and the pairing-window prompt name the right gesture on each platform._open/_write/_commitand replaces the file withstd::filesystem::rename(), becausestd::rename()won't replace an existing file on Windows. It falls back to%USERPROFILE%whenHOMEis unset. Eachwrite_file()variant has its own doc comment.examples/basic_client/README.mdgets a Windows section covering the build, no mDNS (so dial with-u), thep/ckeys, and the persistence file's permissions.tui_clientincludes POSIX<getopt.h>, so the top-levelCMakeLists.txtskips it onWIN32with a status message, the waysource_clientalready skips itself without PortAudio. A standalone Windows build with the examples on (the default) now succeeds.basic_clientdefines_CRT_SECURE_NO_WARNINGSonWIN32, since the persistence provider'sgetenv()and_open()otherwise draw deprecation warnings pointing at_dupenv_s/_sopen_s.Notes
%USERPROFILE%grants only the user, SYSTEM and Administrators.tui_client,source_clientand the tests remain POSIX-only. The top-level README's "macOS/Linux" wording is left alone, since Build the library on Windows with clang-cl #210 deferred the library's Windows support scope.Testing
sendspinandbasic_clientbuilt with no warnings from the library's own sources.basic_client.exe -p 8929 -u ws://<music-assistant>:8927/sendspincompleted the Noise handshake, was admitted (unpaired) and listened locally. Pairing and playback were not exercised.BUILD_EXAMPLESsucceeds, skippingtui_clientandsource_client, with no warnings from the repo's own sources.basic_client.exe -hnames thep/ckeys, an unknown option prints the usage, andbasic_client.exe -p 8931starts and listens on 8931.win_getopt.hwas checked on Linux againstbasic_client's optstring: grouped flags,-pVAL,-p VAL,--, a lone-, an unknown option, a missing argument and-:.sendspin_testspasses 705/705. The sanitizer runs are this PR's CI.