Skip to content

Build and run basic_client on Windows - #214

Open
chrisuthe wants to merge 7 commits into
mainfrom
chrisuthe/windows-basic-client
Open

chrisuthe wants to merge 7 commits into
mainfrom
chrisuthe/windows-basic-client

Conversation

@chrisuthe

@chrisuthe chrisuthe commented Oct 27, 2026 •

Copy link
Copy Markdown
Member

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

Testing

  • 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.

@chrisuthe
chrisuthe marked this pull request as ready for review October 27, 2026 04:27
Base automatically changed from chrisuthe/winsock-init to main October 28, 2026 11:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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.

2 open issues
What changed in this PR

Adds Windows support to the basic_client host example, building on the Winsock initialization in #213. Other examples and tests remain POSIX-only.

Changes:

  • Fetches static PortAudio on Windows and adds a minimal getopt() replacement.
  • Adds console-key pairing controls and Windows file-persistence support.
  • Documents Windows build instructions, discovery limitations, and persistence permissions.
File Description
examples/​common/​win_getopt.h Adds short-option parsing for Windows.
examples/​common/​file_persistence_provider.cpp Adds Windows file writes and a %USERPROFILE% fallback.
examples/​basic_client/​README.md Documents Windows setup and platform differences.
examples/​basic_client/​main.cpp Adds Windows option parsing and p/c pairing controls.
examples/​basic_client/​CMakeLists.txt Fetches and links static PortAudio on Windows.

Review effort: Balanced

Comment thread examples/basic_client/README.md Resolved
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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Low severity Clarify Windows ACL behavior versus POSIX owner-only guarantee

examples/​common/​file_persistence_provider.cpp:72

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.

2 resolved since last review

Review effort: Balanced

@chrisuthe

Copy link
Copy Markdown
Member Author

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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

High severity 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.

Medium severity 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
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.

3 participants