github Nonary/Vibepollo 2.14.1
Vibepollo 2.14.1 (Nightly)

pre-release49 minutes ago

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.

Commit log

Source: 9316bf90e9f6

Since the preceding successfully published nightly 2.2.0: compare exact sources.

3f5b005f0370

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.

ac513f50b851

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.

0ebc8ace6d3c

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.

e14e18a87447

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.

7ffe635c49b0

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.

a0dcfd800dc3

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.

7ae36550494c

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.

8750c2554f8c

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.

11b0335eb1f3

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.

dd54af3c4ef9

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.

6de88b1659e6

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.

f6b27c29cf8e

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.

c7989ae563e2

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.

7b73e74b8fa8

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.

6b11e0ac5af9

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.

0bd1fbdeaf88

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.

22c0a146a5fd

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.

08ccf876b81c

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.

582aac074325

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.

1fe00a74f8de

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.

ca1883d78437

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.

91efc4d84f44

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.

3d3c2c61fe90

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.

0a2980d866db

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.

08b5fffc839e

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.

a7268bc2fcf2

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.

7a09243d89ce

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.

77f115677d5f

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.

711050ae9739

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.

cefd55e45c01

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.

6dd718d3f2c1

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.

56ae12b54dae

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.

52987daa893d

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.

e803b5370625

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

1f2a0aeb487a

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.

e320e08b2c72

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.

bafbb75f189f

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.

955b265f1390

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.

9bedcf84b489

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.

560ee5068c11

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.

6d556f65982b

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.

984e942f3314

Revert "feat(macos): leave physical displays off after streaming"

This reverts commit 6d556f65982b6641a6263aea4e8035dca2f59c8d.

843454ea6dc5

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.

1bc1b4479bc0

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.

3e21eb2a7202

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.

5ff6f9bf36fc

Set the mouse event pressure for pen events so NSEvent.pressure follows the pen

c31e381600e2

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.

d9e7d5931b79

Scroll with one finger and drag after a short hold on macOS touch

91f3bc841527

Merge branch 'feature/macos-pen-touch' into local/macos-all

ac2de5eee9b0

Right-click with a two-finger tap or a long press on macOS touch

40dadaef78cf

Indent touch_release consistently

e914ebc595f8

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.

053533de6988

Merge branch 'feature/macos-pen-touch' into macos

20515bc6c7c1

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

09869cfad687

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.

3cf36435246d

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.

89a5df4cc989

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.

0a813881fa9d

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.

b094e4195d97

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.

0d3ca84710bd

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.

2c72f9d97b2f

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.

6d31228d64ad

merge: bring the macOS port up to date with canary

19ed053a3cc2

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.

4e1d5d67ce34

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.

467096b0e915

merge: bring the macOS port up to date with canary

0db0d078d156

ci(macos): default the build version to 2.2.0

6b8a3fe5e495

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.

d08ee59c9208

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.

9d12ec6ae940

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.

589fd9f96cc6

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.

7e34abafc7b3

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.

2cb8116b3670

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.

c4afdc9c65d6

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.

6943ba49eea6

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.

14d96559de19

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

b23a48c99cb1

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.

4cf32b2374ea

Merge pull request #539 from benjweaver/feature/macos-port

feat(macos): restore the macOS host on 2.0

29e9797dce5e

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>

73bc025c46cf

test: restore terminal composition HDR stub (#725)

6d410b0bde72

fix(crypto): support OpenSSL 4 certificate APIs (#410)

(cherry picked from commit d0e5b0910ce42c303bb12e4aeb874a0ae0906476)

64de686f1df5

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)

8270201ca511

fix(ci): use numeric release database ID for nightly publication

(cherry picked from commit 380a205403c567d8a249461728a6d7ded3898531)

9316bf90e9f6

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.

Don't miss a new Vibepollo release

NewReleases is sending notifications on new releases.