This is a nightly release of Vibepollo and contains several bug fixes and may contain new features. Check the commit log for more information.
Windows installer · Linux (Arch x86_64)
Nightly Windows installers are unsigned and include self-signed gamepad components. They may cause false-positive antivirus alerts.
Source: 9316bf90e9f6
Since the preceding successfully published nightly Commit log
2.2.0: compare exact sources.
fix(macos): restore the macOS build
The macOS build had bit-rotted since CI stopped building it:
- nvenc: include <cstring> for the std::memcmp in equal_guids; libc++
doesn't pull it in transitively.
- macos: Boost.Process v2 renamed start_dir to process_start_dir.
- macos: DNSServiceRegister needs a C string, and SERVICE_TYPE is now
a string_view.
- process: include the header-only lossless scaling policy on every
platform.
- steam, lutris: libc++ counts file times in __int128, which
std::to_string has no overload for. Convert them to 64 bits first;
Windows and Linux already count in 64 bits, so their fingerprints
don't change.
- webrtc: declare the output override lease on macOS too. The
non-Windows branch already assigns it.
- cmake: let CMAKE_IGNORE_PREFIX_PATH drop /opt/local from the macOS
link directories, so a stale MacPorts can't shadow the SDK's curl
and iconv.
fix(macos): make the .app bundle run
- Anchor the working directory to Contents/MacOS at startup, as
upstream Sunshine does. Bundle assets are referenced relative to it
(../Resources/assets), so launching from Finder couldn't find
apps.json and exited.
- Copy the macOS assets (apps.json) into the build tree, so the
build-tree .app runs without installing, like the Homebrew build.
- Declare NSLocalNetworkUsageDescription, as upstream does. Without it
macOS can deny the Bonjour registration (kDNSServiceErr_PolicyDenied)
without asking, and Moonlight can't discover the host.
build(macos): package a signed DMG with the Vibepollo icon
- Replace the Sunshine icon with the Apollo star on the web UI's
indigo/violet accent, following the macOS icon grid.
src_assets/macos/build/make-icon.swift regenerates it.
- Copy the icon into the build-tree bundle too: MACOSX_BUNDLE_ICON_FILE
only writes the Info.plist key, so the build-tree app showed a blank
icon (and System Settings resolved to it).
- Fix the DMG: its volume icon pointed at sunshine.png, which no longer
exists, and the Finder layout script looked for Sunshine.app.
- Drop the versioned executable name inside the .app. codesign rejects
a symlinked main executable, and install() shipped both files.
- Only use hardened runtime and timestamps with a Developer ID. Ad-hoc
and self-signed identities have no Team ID, so library validation
refused to load the bundled dylibs and the app died at launch.
- Also sign the build-tree bundle when APPLE_CODESIGN_IDENTITY is set.
With a stable identity, macOS privacy permissions (Screen Recording,
Accessibility) survive rebuilds instead of silently resetting.
feat(macos): menu bar icon
The macOS tray was never implemented because AppKit only works on the
main thread. main() now runs the AppKit event loop there once startup
is done, and src/platform/macos/tray.mm implements the tray API on top
of it.
It replaces third-party/tray/src/tray_darwin.m, whose tray_exit() calls
[NSApp terminate:] (skipping Vibepollo's shutdown) and whose
tray_update() touches AppKit from whichever thread calls it. Updates
are copied and applied on the main thread, and tray_exit() just wakes
the event loop. System quit requests (logout, a permission prompt's
"Quit & Reopen") go through the normal SIGINT shutdown.
The icons are the web UI's SVGs used as template images, so they stay
crisp and follow light and dark menu bars.
feat(macos): request Accessibility for remote input
Remote mouse and keyboard input is posted with CGEventPost, which macOS
drops unless the app is allowed under Privacy & Security >
Accessibility. Nothing requested it, so no prompt ever appeared and
input from clients was silently ignored. Request it at startup, like
upstream Sunshine, and log why input is ignored until it's granted.
feat(macos): per-client virtual display
Brings Vibepollo's virtual display to macOS, driven by the same
settings as on Windows:
- virtual_display_mode: each stream gets a display at the client's
resolution and refresh rate, created before input and capture bind
to a display and removed when the stream ends. Concurrent streams
share it.
- virtual_display_layout: exclusive turns the physical displays off
while streaming; the primary layouts make it the main display.
macOS keeps displays adjacent, so the isolated layouts behave like
their plain versions.
- dd.virtual_display_scale_percent: resolution-based picks Retina (2x)
above 1920x1200, e.g. phones at native resolution, 1440p, and 4K.
Displays come from the private CGVirtualDisplay API, and physical
displays are turned off with CGSConfigureDisplayEnabled. Both are
looked up at runtime, so if macOS drops them, streams fall back to the
physical display.
macOS does not turn a display back on when the process that turned it
off dies, even with app-only configuration scope. So Vibepollo starts a
helper (itself, with --macos-display-restore-watchdog) that restores
the displays if the main process exits without doing so, and keeps a
marker file that the next launch also restores from.
AVCaptureScreenInput delivers no frames from virtual displays, so they
are captured with ScreenCaptureKit (SCVideo, an AVVideo subclass).
ScreenCaptureKit only sends images when the screen changes, so idle
ticks re-send the last one to keep a steady cadence for keyframe
requests. Physical displays keep using AVCaptureScreenInput.
Capture and input follow the virtual display while it exists.
Display configuration needs an AppKit session, which the main-thread
event loop provides.
fix(macos): keep virtual display streams alive on a static desktop
macOS streams use the sync encode path, which only encodes frames the
capture delivers and has no minimum frame rate of its own. The old
AVCaptureScreenInput delivered frames continuously, but ScreenCaptureKit
only sends images when the screen changes and can go silent on a static
desktop: tonight's sessions dipped to ~12 fps, below the configured
minimum_fps_target of 20, and a keyframe request after packet loss would
wait until something moved.
Deliver new images as soon as they arrive, and re-send the last one when
nothing new arrives within the minimum frame time, matching the async
encode path's minimum_fps_target handling.
Measured at 2442x1290 Retina @ 120 Hz on an M1 Pro (capture plus
hardware HEVC encode):
motion fps capture->encoded static fps static CPU
passthrough ~108 12.4 ms avg 18-49, can stall 3.6%
paced to 120 fps 120 23.7 ms avg 120 17%
this change ~108 12.2 ms avg 19.9 3.8%
Pacing every frame was rejected: at this size the hardware encoder is
near capacity, so repeats queued new images and added ~10 ms latency.
feat(macos): report GPU utilization in session stats
The stats dashboard showed no GPU usage on macOS. Apple GPUs publish
their utilization in the IORegistry (IOAccelerator's
PerformanceStatistics, "Device Utilization %"), readable without
entitlements, so sample it for host_gpu_percent. The hardware video
encoder is a separate engine and isn't included; GPU encoder
utilization, temperature, and VRAM stay at their sentinels.
fix(macos): stream from a sleeping Mac without hanging
A client connecting to a sleeping Mac only brings it into a dark wake,
with every display off. The encoder probe then waited forever for a
frame from the sleeping display (upstream's FIXME), and every later
connection queued behind it until Vibepollo was restarted.
- Wake the displays before capture, as the Windows capture powers its
display on, and keep them awake while capturing.
- Bound every wait for a frame: the probe gives up after 3 seconds, a
running capture re-sends the last image after a second without a new
one, and a ScreenCaptureKit stream that stops by itself is restarted.
- Keep frame callbacks from touching a capture that already returned,
and stop capture sessions before releasing their callback tables.
fix(macos): turn displays back on even if one has vanished
The exclusive layout turns off every other display, other apps' virtual
displays included. If one of those was removed while off, re-enabling
the list in a single configuration failed as a whole, so every screen
stayed off, and the restore helper failed the same way after a crash.
Turn each display back on in its own configuration, and count a display
that no longer exists as restored. Wake the Mac before restoring, since
a stream can end during a dark wake. Also wait for the turned-off
displays to be gone before capture starts: a capture set up while they
are still going away can get no frames.
feat(macos): bring the virtual display up when a client connects
Create the per-client virtual display at launch and resume, before the
encoder probe, as the Windows display helper applies the client's
display first. A Mac without a screen of its own, such as a headless
Mac mini, now has a display to probe and stream. The stream's session
takes the display over. A launch that fails removes it, and one that
never starts a stream has it removed after 30 seconds.
Capture setup on a display with no mode now fails cleanly instead of
crashing, so the startup probe on a Mac with no screen can't crash.
feat(macos): end streams cleanly when the Mac sleeps or its lid closes
When the Mac is about to sleep, as from Apple menu > Sleep or a low
battery, end active streams with the normal end-of-stream message
before letting it sleep. Clients previously just saw their stream
freeze. The app keeps running, so a client can resume once the Mac
wakes.
Closing a MacBook's lid mid-stream quits the app, which ends its streams
the same way and shows the app as closed. While the virtual display is
the only display, macOS would otherwise keep a plugged-in Mac streaming
with the lid shut, but only for streams started with it open, and a
closed Mac can't be woken back into a stream.
fix: build without warnings on macOS
The macOS CI builds with -Werror. Clear the warnings the shared code
raised there, mostly state only Windows, Linux, or the WebRTC
transport use:
- Compile the WebRTC-only constants only with WebRTC.
- Keep the RTX Video HDR keys and the startup probe's shutdown capture
inside their Windows blocks.
- Mark the display flags only Windows and Linux read [[maybe_unused]].
- Give non-Windows builds a no-op Vulkan HDR layer hook instead of
guarding each call to it.
- Drop lambda captures that were never used, and the constants and
cookie timestamp nothing read.
- Make kMaxDisplays an enum, so C code can size arrays with it.
fix(macos): fall back to PNG menu bar icons
If an SVG tray icon doesn't load, use its 45 px PNG rendering instead
of showing the tooltip text in the menu bar.
ci(macos): build Apple Silicon and Intel DMGs for macOS 14.2+
Run the macOS build from the main CI workflow, like the Windows one,
and allow running it on its own by hand. Without a Developer ID
identity, sign ad hoc, so the DMG launches on Apple Silicon. Smoke-test
the packaged app: its signature, the oldest macOS it supports, and that
it starts and serves its web UI and serverinfo.
fix(build): build the web UI with parallel make
web_ui depends on the two index.html pages, which the web build
declared as byproducts. The Makefile generators write no rules for
byproducts, so a clean parallel make (as on the macOS CI) failed with
"No rule to make target assets/web/index.html". Declare the pages as
outputs of the web build instead.
fix(macos): build with Xcode versions that lack std::jthread
Apple's libc++ has no std::jthread or std::stop_token before Xcode 16.3,
and only behind -fexperimental-library after that. macOS 14 build hosts
top out at Xcode 16.2, which building for macOS 14 with Homebrew's
Sonoma libraries needs.
Add util::jthread and util::stop_token: the standard types wherever
they exist, and small stand-ins otherwise, covering what the code uses
(passing a stop token, stopping and joining on destruction or
assignment). Use them in the places macOS builds compile.
fix(macos): build confighttp with Apple Clang 15
Capturing a function reference in nested lambdas trips up Apple Clang 15
(Xcode 15.4, the newest on macOS 14 build hosts). Take the blocking
route handler by value, so function handlers become plain pointers.
feat(macos): open at login from the menu bar
Add an "Open at Login" checkbox to the menu bar menu. It registers a
launchd agent shipped in the app bundle (Contents/Library/LaunchAgents)
with SMAppService, so launchd starts Vibepollo at login and restarts it
if it crashes. Quitting normally (exit status 0) leaves it stopped
until the next login. It's listed under Allow in the Background in
System Settings > General > Login Items.
The checkmark shows the state macOS reports, so it stays correct when
the setting changes in System Settings. If the user turned Vibepollo
off there, only they can turn it back on, so checking the box opens
Login Items for them.
Only launchd's copy runs while Open at Login is on:
- Opening Vibepollo any other way kickstarts the agent and exits once
the agent has taken over. launchd only starts the agent for the build
that registered it, so after an update this copy runs instead, and
turning Open at Login off and on again registers the new build.
- A lock file keeps a second copy from starting alongside the first.
The agent asks any other copy to quit and waits for it, which also
covers turning Open at Login on.
- Unregistering the agent makes launchd stop it, so turning Open at
Login off from the agent first opens a normal copy, which waits for
the agent to quit.
fix(tests): link Apple frameworks for FFmpeg in the artwork tests
The Steam and Lutris artwork tests link the static FFmpeg archives,
which include the VideoToolbox encoder and hardware context. On macOS
they need CoreFoundation, CoreMedia, CoreVideo, and VideoToolbox, as
Windows needs BCrypt.
fix(build): keep asset subdirectories in the build tree
The build-tree copy of the common assets flattened subdirectories, so
the remote-session icons landed in assets/ instead of
assets/remote-session/, where the host looks for them. Build-tree runs
showed no icons, and on macOS the packaged app got the icons installed
after it was signed, which broke its signature.
fix(macos): bundle libraries that linked dylibs load through @rpath
Homebrew's libwebp, linked for Steam artwork, loads libsharpyuv through
@rpath, which fixup_bundle can't resolve on its own, so packaging the
DMG failed. Pass it the directories of the linked dylibs to search.
fix(macos): say that Screen Recording is missing when no encoder works
Encoders are validated on a captured frame, so without the Screen
Recording permission every encoder fails at startup, and the log blamed
the GPU driver. Name the missing permission and where to allow it
instead, and point the startup warning at today's System Settings
instead of System Preferences.
ci(macos): build OpenSSL for the deployment target
The Intel DMG required macOS 15.0 instead of 14.2: the runner image's
OpenSSL targets the runner's own macOS 15, and Homebrew no longer
publishes Intel macOS bottles of OpenSSL to replace it with. Build a
static OpenSSL for MACOSX_DEPLOYMENT_TARGET on both architectures,
cached per version, and fail the smoke test when anything in the app
requires a newer macOS than the deployment target.
fix(macos): ask for Screen Recording through ScreenCaptureKit
On macOS 27, CGRequestScreenCaptureAccess() neither prompts nor lists
the app under Privacy & Security: tccd answers "does not allow
prompting", so a new user had no Vibepollo entry to switch on. Asking
ScreenCaptureKit for the shareable content still shows the system
prompt and lists the app, and does on every macOS Vibepollo supports.
feat(macos): Remote Monitor
Launching Remote Monitor from Moonlight on another device now gives it
an extra display beside the Mac's, as on Windows and Linux, instead of
being rejected. macOS had no remote display topology backend:
- Each Remote Monitor client gets its own virtual display, named after
the client and with a serial from its UUID so macOS remembers its
settings. The topology arranges it around the displays already
active, and captures it by its display ID.
- Capture uses ScreenCaptureKit for these displays, which
AVCaptureScreenInput can't capture, and the stream's own virtual
display no longer overrides them.
- Input sends a Remote Monitor stream's pointer to its display, found
by the origin its capture started at, and lets the pointer onto
Remote Monitor displays alongside the streamed one.
- The exclusive layout leaves Remote Monitor displays on, and Remote
Monitor and Remote Input streams no longer get the game's virtual
display.
fix(macos): keep the Mac's display scaling when adding a virtual display
macOS keeps display settings per combination of displays, so bringing
up a virtual display gave the Mac's own displays their default modes:
a Retina screen set to More Space went back to Default for as long as a
stream or Remote Monitor lasted. Turning the displays back on after the
exclusive layout, with a Remote Monitor still present, did the same.
Put back any mode that changed, for this process only, so the user's
own settings are what remain afterwards.
fix(macos): stream black video for Remote Input
Remote Input still needs a video stream, but macOS had no source of
black frames, so the session failed with "Synthetic black capture is
unavailable for the selected encoder backend". Add one that feeds a
black image to the VideoToolbox or software encoder. VideoToolbox also
emits asynchronously, so drain it after the input-only session's only
frame, as AMF is drained.
fix(macos): entitle Developer ID builds to capture audio
Signing with a Developer ID enables the hardened runtime, which blocks
audio input unless the app has the audio-input entitlement. Sign the app
with an entitlements file granting it, so audio device capture and the
system audio tap keep working in signed and notarized builds.
fix(macos): ask for one privacy permission per launch
macOS shows one privacy prompt at a time. Vibepollo asked for
Accessibility and Screen Recording together at launch, so one of the
prompts was dropped and needed an extra restart to come back.
Ask for Screen Recording first, and for Accessibility once Screen
Recording is allowed. Allowing Screen Recording needs a restart anyway,
which macOS offers, so each launch shows exactly one prompt.
feat(macos): stream HDR
When a client asks for HDR, make the per-client virtual display an HDR
display and capture it in HDR, as on Windows.
- The virtual display: macOS 27's private CGVirtualDisplayMode takes a
transfer function, and 1 makes the display HDR, with 5x EDR headroom
and a Display P3 gamut. Remote Monitor displays use it too. Where the
API is missing, displays stay SDR and so does the stream.
- Capture: ScreenCaptureKit's HDR capture (macOS 15) delivers 10-bit
frames already in BT.2020 with the PQ transfer function, so they reach
VideoToolbox without conversion. A physical HDR display, such as a
MacBook Pro's, is captured in HDR the same way.
- The display reports HDR and its mastering metadata, so the shared code
encodes HEVC Main10 HDR and sends Moonlight the HDR metadata. SDR white
measures about 140 nits in the captured frames, so the peak is the
display's EDR headroom times that, at most 1600 nits.
fix(remote): keep Remote Monitor and Remote Input when the game quits
Quitting the game from Moonlight ended every session, so a Remote
Monitor or Remote Input client lost its stream and couldn't reconnect
cleanly. While remote sessions are active, quitting now ends only the
game's sessions and displays, as the Terminate control does.
fix(remote): keep Remote Input listed after its session ends
Moonlight fetches the app list as it leaves a stream, while the host is
still tearing the Remote Input session down, and doesn't fetch it again
until the host is reopened. The list hid Remote Input from the client
that owned it, so the tile disappeared after every Remote Input session.
Keep the tile listed for its own client. Launching it again replaces
that client's previous Remote Input session, as Disconnect Input would,
instead of failing while the old one is still closing.
feat(macos): keep displays off while a session is paused
Hold the virtual display, and the physical displays the Exclusive layout
turned off, while a game keeps running after its stream ends, as on
Windows. Governed by dd_paused_virtual_display_timeout_secs, and
controlled by the new virtual_display_keep_while_paused option (on by
default).
fix(macos): end the paused display hold when the game is terminated
Quit App on a paused session has no stream left to finalize, so nothing
released the hold and the displays stayed off. Release it in
proc_t::terminate() where Windows removes its own virtual display, with
the same check: when another stream still owns the runtime, the final
stream's cleanup releases it instead.
fix(macos): give the displays back to Remote Input when a paused game's stream ends
The pause hold kept the virtual display, and so the physical displays the
Exclusive layout turned off, even while a Remote Input or Remote Monitor
stream was still connected, so that client drove a screen no one could see.
Skip the hold while one of those streams is live, end it when one starts,
and only let the stream that owns the virtual display take it.
feat(macos): keep streaming across lid transitions when the Mac stays awake
Closing a MacBook's lid always quit the app and ended its streams, even
when another utility kept the Mac awake. A new setting, "End streams when
the lid closes" (lid_close_ends_streams, on by default), keeps that
behavior; turned off, the lid alone ends nothing. Vibepollo still doesn't
keep the Mac awake or change power settings, and actual sleep still ends
streams through the existing sleep handler.
Reopening the lid brought the built-in display back next to the virtual
display despite the Exclusive layout. A debounced display reconfiguration
watcher now turns displays that come back on during an exclusive session
off again, on a background queue. It extends the marker file and restore
helper to cover them before turning them off, records their modes for the
end of the session, and gives up after three corrections a minute. It also
puts Vibepollo's own displays back in their created modes when a display
arrives or leaves. Displays that are offline when a session ends, such as
the built-in display with the lid closed, no longer block restoring the
rest; they stay in the marker in case they return turned off.
Capture now logs why ScreenCaptureKit stopped, sets itself up again within
the same session when its display stays offline or changes size, and ends
the stream after ten restarts in a minute instead of retrying forever.
The decisions live in display_policy.cpp, without Apple frameworks, and
have unit tests.
Document and translate virtual_display_keep_while_paused
The pause hold's setting had no entry in configuration.md or the v2 UI's
base locale, so the config catalog contract test failed, and the v2
settings page didn't offer it. Add the docs entry, the English strings,
and a macOS-only switch next to the other virtual display settings,
appended so the Everyday page's field picks stay the same.
The v2 page also hid the paused virtual display timeout on macOS, which
the pause hold uses as its limit; show it there too and assert that in
the parity test.
Turn the built-in display back on when a stream ends with the lid open
Restoring displays skipped any display CGDisplayIsOnline() reported as
offline, assuming that meant a closed lid. But macOS also reports a
display that's only turned off as offline, so ending an Exclusive stream
left the built-in display dark until Vibepollo restarted and used the
marker file.
Only the built-in display while the lid is actually closed is skipped
now, read from AppleClamshellState. Anything else is turned on as before.
Turn a display off as soon as it comes on during an Exclusive stream
Opening the lid made the built-in display the main one, with the stream's
display beside it, for as long as Vibepollo waited for the changes to
settle and then for the old restore helper to exit. A display coming on
now gets turned off right away, before the settled pass that fixes modes
and catches anything missed; the old helper is retired after the display
is off, since the new one already covers it. The correction budget is
six a minute, as one lid opening can take two.
Log when the lid opens and how long turning the display off took, to see
how much of macOS's own rearranging remains.
feat(macos): leave physical displays off after streaming
Add leave_displays_off_after_streaming (off by default). When it's on,
ending the last stream doesn't wake or light the Mac's displays just to
restore the desktop, so a panel another utility holds dark stays dark
until local input wakes it.
Turning a display back on with CGSConfigureDisplayEnabled lights its
panel, and that is what lit the built-in display at the end of an
Exclusive stream. So with the setting on, the Exclusive layout mirrors
the physical displays onto the virtual display instead of turning them
off. They leave the desktop but stay logically on, with their power
untouched, and the mirroring ends when the virtual display goes away,
including after a crash, so neither the restore helper nor the marker
file is needed. Displays that come on during the stream, such as when
the lid opens, are mirrored too, under the same retry budget.
Teardown no longer calls wake_displays() when the setting is on. Saved
modes are still put back, and a display that was turned off is still
turned back on (with a warning that it lights), so none is left
disabled. Windows and Linux log that the setting isn't supported yet.
Revert "feat(macos): leave physical displays off after streaming"
This reverts commit 6d556f65982b6641a6263aea4e8035dca2f59c8d.
Wait for the built-in display to wake before dropping the virtual display
When a stream ended, the virtual display was destroyed as soon as the
built-in display was active, even if it was still asleep. With nothing
else on, macOS marks the display stuck and its watchdog restarts
WindowServer. Count a physical display as back only once it is active
and awake.
Accept pen and touch input from Moonlight clients on macOS
Post the pen as a tablet device: proximity events as it enters, leaves, or
changes tool, and mouse events carrying pressure, tilt, rotation, and the
barrel buttons. The eraser is its own pointer type. Cancel and disconnect
lift every button and leave proximity.
macOS has no public multi-touch injection, so one finger acts as the mouse
(tap to click, move to drag) and two fingers scroll. Advertise pen_touch
unless native_pen_touch is off.
Post pen pressure changes while the pen holds still and fix touch double taps
Always post a pen move so pressure and tilt changes reach apps, give the
tertiary barrel button its own mouse button number, count double taps for
touch, carry fractional scroll movement between events, and skip the mouse
delta on the first pen event.
Set the mouse event pressure for pen events so NSEvent.pressure follows the pen
Scale pen and touch coordinates by the touch port on macOS
The shared input code passes pen and touch positions as 0..1 across the
stream, with the stream size in the touch port, so every event landed at the
top left corner.
Scroll with one finger and drag after a short hold on macOS touch
Merge branch 'feature/macos-pen-touch' into local/macos-all
Right-click with a two-finger tap or a long press on macOS touch
Indent touch_release consistently
Turn the built-in display back on when the lid opens
A stream that ended with the lid closed left the built-in display off,
on the assumption that macOS turns it back on by itself when the lid
opens. It didn't on macOS 27.0.1: the display stayed off with only a
leftover virtual display active.
When the lid opens, run the same recovery that runs at launch: turn on
the displays still listed in the marker. It does nothing while a
virtual display is up, so a paused session keeps the built-in display
off until it resumes or ends.
Merge branch 'feature/macos-pen-touch' into macos
Merge upstream 2.0.1 into macos
Conflicts were where both sides added a setting or code at the same
spot; kept both. Also adapts the macOS Remote Monitor release hook to
upstream's new bool result, and drops the deprecated
NSApplicationActivateIgnoringOtherApps from upstream's new macOS
managed-app focus code (it has no effect on macOS 14 and later, and
warnings are errors).
Use util::stop_token in the private display cleanup policy
Upstream 2.0.1's cleanup policy header takes a std::stop_token, which the
macOS CI's Xcode 15.4 and 16.4 libc++ don't have. The remote display
topology test includes it on every platform, so the macOS build failed.
util::stop_token is std::stop_token wherever that exists, so Linux is
unchanged.
merge: bring macos up to date with upstream master
Brings macos up to date with Nonary/Vibepollo master (1b3167870), which
now includes the build, Boost.Process, DNS-SD, and Remote Input
reconnection fixes taken from PRs 539 and 561. Where upstream's version
of one of those fixes overlaps ours (file-clock counts, SERVICE_TYPE,
Remote Input replacement), upstream's wins. Kept from macos: the
per-client virtual display in stream start, util::jthread for Apple
Clang 15, the macOS tray shutdown path, the lid setting strings, and
activation without the deprecated NSApplicationActivateIgnoringOtherApps.
fix(macos): restore the build after merging upstream master
Upstream code that hadn't been built on macOS failed with warnings as
errors: guard the RTX HDR metadata path in PyroWave encoding behind
SUNSHINE_ENABLE_NV_TRUEHDR like the other callers, format a file-clock
count with file_clock::count_string, drop a needless capture of a
static in the signal handlers, mark the stream-start ticket used where
no handler takes it, and route the menu bar library's log to ours.
chore(cache): drop a file-clock comment the upstream merge made redundant
Upstream formats the file-clock count with file_clock::count_string,
which explains itself, so the comment about libc++'s __int128 count no
longer describes the line below it.
fix(macos): use util::jthread in the audio role control and its tests
Upstream's audio role control and the audio context and power action
tests use std::jthread and std::stop_token, which the Xcode 15.4 and
16.4 toolchains on the macOS CI runners don't provide. Use the
util::jthread stand-in, as the rest of the code macOS compiles does. It
is std::jthread wherever that exists, so Windows' audio.cpp is
unchanged.
fix(macos): use util::jthread in the display helper's async dispatcher
The macOS build compiles the display helper's dispatcher and state
machine for their unit tests, and the dispatcher's std::jthread and
std::stop_token are missing from the Xcode 15.4 and 16.4 toolchains on
the CI runners. It only uses stop_requested and request_stop, which
util::jthread provides; on Windows util::jthread is std::jthread.
fix(macos): address the review of displays, lid close and remote input
Turn displays off only once both safeguards cover them. Writing the
marker now replaces it whole and reports failure, and
cover_and_turn_off() turns nothing off until the marker is written and
a new restore helper runs. The current helper stays until the displays
are off, and for good if anything fails, so a hotplug can no longer
trade a working helper for one that never started. Displays turned off
again while no helper runs get one first.
Keep Remote Monitors that are still being made out of the exclusive
layout. Vibepollo's own displays are recognized by their vendor and
product from the moment macOS makes them, and while one is being made
the layout leaves displays it hasn't turned off before alone, then
enforces itself again once the display is published.
Compose Remote Monitor placement in desktop points. The topology asks
the platform for a client display's footprint, and macOS answers with
the Retina display's size in points, so two 3840x2160 Retina displays
beside a 1920-point display sit at x=1920 and x=3840. Modes are still
reported in pixels.
End Remote Monitor and Remote Input streams when the lid-close policy
quits the app: its exit leaves them running on purpose. A client's own
Quit is unchanged.
Bind each stream's pointer, touch and pen input to its display by ID
instead of by where that display was when capture started. Capture
puts the display ID in the touch port, and the mouse viewport now
carries it too.
merge: bring the macOS port up to date with canary
fix(input): default the mouse viewport's output name
Existing callers build a viewport from its position and size alone, and
-Wmissing-field-initializers flags those once the viewport carries the
output name.
fix(crypto): build against OpenSSL 4
OpenSSL 4 makes ASN1_STRING opaque and returns a certificate's subject
name as const, so crypto.cpp and two test fixtures stopped compiling
once Homebrew linked openssl@4. Read the signature through
ASN1_STRING_get0_data and ASN1_STRING_get_length (ASN1_STRING_length is
deprecated in 4.1), and build the self-signed certificate's name with
X509_NAME_new before setting it as subject and issuer. Both paths still
build against OpenSSL 3.
merge: bring the macOS port up to date with canary
ci(macos): default the build version to 2.2.0
ci(macos): build Apple Silicon on the macos-15 runner
GitHub no longer accepts the macos-14 image, so the arm64 jobs were cancelled before they started. MACOSX_DEPLOYMENT_TARGET still pins the minimum to macOS 14.2, and the smoke test checks it.
fix(process): Declare the RTX HDR live keys for Linux too
Canary's native Linux RTX HDR reads RTX_HDR_LIVE_KEYS from code built on
Windows and Linux, but the macOS warning cleanup had kept the array in a
Windows-only block, so the Arch Linux build failed. Guard it the same way
as the code that uses it.
fix(cmake): Search OpenSSL's headers before Homebrew's prefix
include_directories(BEFORE) prepends each entry in turn, so Opus's
include directory was searched before OPENSSL_INCLUDE_DIR. On macOS Opus
resolves to /opt/homebrew/include, which on the macos-15 runner holds
OpenSSL 4's headers, while CI links a static OpenSSL 3.6 from
OPENSSL_ROOT_DIR. crypto.cpp then took the OpenSSL 4 path and failed to
link ASN1_STRING_get_length. List OpenSSL after Opus so the headers match
the library that's linked.
fix(tests): Name X509_name_st in the certificate fixtures
wincrypt.h defines X509_NAME as a macro, so the Windows build couldn't
use it as the unique_ptr's type in test_pairing_recovery. Name the
OpenSSL struct instead, which works with OpenSSL 3 and 4.
fix(macos): Build std::jthread with Xcode 16's libc++
The paused app monitor uses std::jthread and std::stop_token. The
GitHub macOS runners default to Xcode 16.4, whose libc++ still marks
both experimental, so the build stopped at paused_app_monitor.h. Probe
for std::jthread at configure time and, only when it's missing, build
and link with -fexperimental-library. Newer Xcodes pass the probe and
build without the flag.
Revert "fix(macos): Build std::jthread with Xcode 16's libc++"
This reverts commit b66466031. The branch already has util::jthread in
src/jthread.h for toolchains without std::jthread, so the paused app
monitor uses that instead of building everything with
-fexperimental-library.
fix(macos): Build the paused app monitor with util::jthread
Xcode 16's libc++ provides std::jthread and std::stop_token only behind
-fexperimental-library, so the macOS runners couldn't build
paused_app_monitor.h. Use util::jthread from jthread.h, as the rest of
the code does. The shim's stop_token can't interrupt a
condition_variable_any wait, so wait on a plain condition variable with
a predicate that checks the token, and take the lock in stop() before
notifying so a worker can't miss the wakeup.
ci(macos): require macOS 15
GitHub retired the macos-14 image, and on macos-15 Homebrew pours
Sequoia bottles. The bundled ICU, libjpeg-turbo, libpng, libwebp, and
miniupnpc then require macOS 15.0, so the smoke test rejected the app
against the 14.2 deployment target. Homebrew no longer publishes Sonoma
bottles for most of them, so raise the deployment target to 15.0 rather
than build each from source.
ci(macos): quote the deployment target
YAML reads an unquoted 15.0 as the number 15, so the smoke test compared
the app's 15.0 minimum against "15" and failed. Quote it so the
environment keeps "15.0".
ci(macos): version builds like Windows and Arch
Pass the release candidate's version and channel to the macOS job, and
fall back to 2.0.0-pr-validation and the legacy channel for pull
requests, as the Windows and Arch jobs do. A manual run resolves its
version the way Windows does: the requested version, else the tag, else
0.0.0. The hard-coded 2.2.0 default would have gone stale with the next
release.
Merge pull request #539 from benjweaver/feature/macos-port
feat(macos): restore the macOS host on 2.0
fix(main): run Windows teardown when startup returns early (#699)
A shutdown request that lands during startup (for example the service
stop Windows sends while powering off) makes main() take one of its
early returns, which skipped the ordered teardown at the end of main.
The virtual display lease thread started by proc::refresh() was then
still joinable when CRT on-exit handlers destroyed it, so the process
aborted (0xc0000409) on every such exit, and the Web UI reported a
crash after each reboot.
Run the same steps from a fail guard on early returns: stop the
deferred virtual-output and recovery workers, the display helper
watchdog, close the virtual display device, and stop the task pool.
The normal path still performs them in order and disables the guard.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
test: restore terminal composition HDR stub (#725)
fix(crypto): support OpenSSL 4 certificate APIs (#410)
(cherry picked from commit d0e5b0910ce42c303bb12e4aeb874a0ae0906476)
fix(display): defer shell refresh until after APPLY reply (#407)
* fix(display): defer shell refresh until after APPLY reply
* fix(display): serialize restore shell refreshes
* fix(display): join delayed refresh producer on shutdown
(cherry picked from commit fb8a7b4bfa7d2fcf6a693dc2ef242ae380cb03df)
fix(ci): use numeric release database ID for nightly publication
(cherry picked from commit 380a205403c567d8a249461728a6d7ded3898531)
fix(tests): keep extracted recovery fixtures aligned with production
Record shell-refresh requests in the boot restore fixture and assert notification behavior without invoking Windows APIs.
Include the production jthread compatibility types in shutdown replay fixtures.