Conversation
Survey of the fork's Linux readiness: build system, packaging template gap, per-feature platform matrix, and an enhancement-porting roadmap.
Reintroduce the four UNIX configure_file inputs referenced by cmake/prep/special_package_configuration.cmake (desktop entries, appstream metainfo, systemd user service). Content taken verbatim from LizardByte/Sunshine at b2d44f5, the last commit before upstream renamed these files; all @var@ placeholders are defined by this fork's CMake.
- The pinned third-party/tray implements Linux with Qt + libnotify (src/tray_linux.cpp); the referenced tray_linux.c no longer exists. Link the submodule's tray::tray target like upstream does and gate on libnotify only (the appindicator defines were unused by src/tray). - Remove the dead src/platform/linux/input.cpp entry; input lives in src/platform/linux/input/inputtino*.cpp picked up by the existing glob.
Platform fixes needed to build the fork's Windows-centric sources on
Linux (GCC 16, C++23):
- vdd_ioctl.cpp / vdd_utils.cpp: guard the Windows ZakoVDD transport and
driver logic with _WIN32; add Linux stubs so the shared display
session staging code links (driver reported absent/unsupported).
- vdd_utils.h: drop the unconditional windows.h include and add Linux
no-op SessionEventListener / w_utils::is_any_rdp_session_active
helpers used by session.cpp.
- globals.{h,cpp}: unguard VDD_NAME/ZAKO_NAME/is_running_as_system_user
(plain constants; Linux reports the device as absent).
- entry_handler.cpp, session.cpp, system_tray.cpp: move Windows-only
includes under _WIN32.
- config.cpp: add the fork's QVBR/HQVBR AMF rate-control constants
(4/5/6, matching AMF 1.5.2 headers) to the non-Windows fallback table.
- video.cpp: use NVENC SDK numeric option values in the avcodec branch
(the branch never compiled anywhere before; values from nvEncodeAPI.h).
- linux/display_device.cpp: update settings_t::apply_config stub to the
fork's signature and stub the fork-added settings/device helpers.
- linux/misc.cpp: platf::adapter_names() stub (Windows-side concept).
- linux/vaapi.cpp: include libavutil/pixdesc.h explicitly.
- confighttp.cpp: include boost/regex.hpp explicitly and guard the
GetConsoleWindow branch of the boom endpoint.
- Boost_Sunshine.cmake: add the regex component (used by confighttp).
- tests: exclude tests/unit/platform/windows from the aggregate suite on
non-Windows hosts.
- packaging/arch-local/PKGBUILD packages the prebuilt build/ tree (the repo must be configured with CMAKE_INSTALL_PREFIX=/opt/sunshine so the compiled-in assets path matches) via cmake --install under DESTDIR. - Restore the tray status icons (sunshine-playing/pausing/locked.svg) from upstream; their install rule survived but the source files were lost in the WebUI redesign.
Adapt upstream's sunshine.install for the /opt layout: grant cap_sys_admin to the packaged binary on install and upgrade (so the cap survives package updates), reload udev rules and trigger uinput/uhid.
The Linux tray_menus layout omits the two Windows-only entries (VDD menu, advanced settings), so the fixed indices in update_menu_texts() walked past the array and dereferenced a NULL submenu on Linux - segfaulting at startup right after encoder probing. Give each platform its own index block instead.
Implement the vdd_utils surface on Linux on top of the external sunshineVD helper (sunshine-virt-display) instead of the Windows-only ZakoVDD driver: - create/destroy translate to the daemon's Unix socket commands (--connect with the cached session mode / --disconnect); - find_device_by_friendlyname reports the daemon's live virtual connector (state file + /sys status), which is what display session staging waits for; - set_vdd_session_mode / wait_for_mode_publication cache the client's requested mode and re-issue connect so the EDID matches the stream; - get_vdd_status maps the helper socket to the driver status surface. vdd_capability::query_state() now uses the same status mapping on every platform, so clients may request a virtual display on Linux whenever the helper daemon is running.
The serverinfo field and the display_control response hardcoded capability_version=0 on non-Windows, so clients never offered the virtual display option even though the Linux backend now reports ready. Send the protocol version unconditionally - the socket backend implements the same session semantics.
…n socket Replace the sunshineVD socket backend with a direct implementation of the fork's virtual display semantics: - vdd_edid: byte-for-byte port of sunshineVD's EDID generator (base block with the requested preferred timing + CEA extension carrying BT.2020 colorimetry and an HDR static metadata block); locked to the reference implementation with byte-equality unit tests. - vdd_utils (Linux): create/destroy override a spare disconnected connector's EDID via debugfs and force its status on, without touching physical displays; mode changes cycle the connector with a regenerated EDID; session teardown clears the override. - kmsgrab: when a virtual display session is live, capture follows the plane backing the virtual connector instead of the enumeration order. - packaging: grant cap_dac_read_search/cap_dac_override next to cap_sys_admin so the debugfs/sysfs writes succeed.
setcap grants permitted (not effective) capabilities, so the native virtual display backend hit EACCES on /sys/kernel/debug/dri and the filesystem_error took down the display_control endpoint. Raise the granted caps into the effective set with a libcap RAII guard (same pattern as the KMS capture path) around every privileged operation, and use the non-throwing filesystem overloads in get_vdd_status.
…d output When a virtual display session is live, kmsgrab must capture the plane backing the virtual connector even if the client supplied a stale or Windows-style display name (the previous index-fallthrough could select a physical plane first).
- kmsgrab: name displays after their DRM connector (eDP-1, DP-1, ...) and resolve the configured/client-supplied name back at init; virtual display sessions still take priority over any supplied name. - enum_available_devices (Linux): enumerate live connectors from sysfs so the headless-host guard stops skipping virtual display teardown after every stream, and stream display matching resolves by connector name. - vdd_prep: implement display_off (exclusive mode) by powering off connected physical connectors for the session and restoring them on virtual display destruction; extend-style modes are the compositor's natural state. - destroy on stream end now runs (unblocked by the enumeration fix), and create sweeps stale virtual connectors carrying our EDID signature. - toggle_display_power toggles create/destroy, which is what the tray and input command expect; tray Foundation Display menu is enabled on Linux. - main: SIGINT/SIGTERM wake the tray event loop so shutdown is graceful instead of hitting the 10s force-exit watchdog.
…uit wakes the loop - A virtual connector goes live before the compositor assigns its CRTC, so encoder probes 200ms after creation found no matching plane and failed every encoder. Poll the connector map for up to 3s when a target connector is set. - Tray Quit on Linux raised the shutdown event but left the main thread parked in the tray event loop, hanging the process forever (the 10s watchdog is only armed by signals). Wake the loop with end_tray().
…er CRTC wait - find_one_of_the_available_devices() only ever matched the virtual display, so a client selecting the physical display (e.g. eDP-1) was rejected with 'display does not exist'. Match device id, display name and friendly name against the live connector enumeration instead. - The compositor CRTC wait now bails out early when the target connector is no longer connected, instead of stalling every probe attempt.
NVIDIA does not emit a hotplug for a status-forced connector, so KWin lists the output but leaves it disabled and never assigns a CRTC - every encoder probe then failed with 'Couldn't find monitor'. After forcing the connector on, run 'kscreen-doctor output.<connector>.enable' (the session compositor on KDE) so KWin adopts and lights the display.
… compositor adoption observable - resolve_display_intent: the fork client sends the fixed placeholder '23172' for its virtual display option; treat it (and ZAKO_NAME) as an explicit VDD target on Linux, same as the Windows VDD_NAME alias. - create_vdd_monitor now retries 'kscreen-doctor output.<conn>.enable' and polls DRM until the compositor hands out a CRTC (up to 4s), with the command output captured into the log; mode switches re-run it. - kmsgrab probe wait reduced to fast-fail (the real wait lives in creation), so failed probes no longer stall for seconds.
…down restore - Port sunshineVD's force_crtc_assignment to the native backend: borrow the compositor's DRM master via pidfd_getfd (candidates found by scanning /proc fds for the card device), drop their master, create a dumb framebuffer in the connector's preferred mode and run the modeset, then restore compositor master. Replaces the KDE-specific kscreen-doctor enable (kept as a fallback). Needs cap_sys_ptrace in the packaging hook. - enum_available_devices reports connectors we forced off for an exclusive session as inactive devices, so the session teardown's headless-host guard no longer skips virtual display destruction - the physical screen is restored when the stream ends.
…ctors NVIDIA reports a status-forced-off connector as plain 'disconnected' in sysfs, so the display enumeration dropped it and the session teardown's headless-host guard concluded 'VDD is the only display' and skipped virtual display destruction - leaving the physical screen dark after exclusive streams and rejecting client selections of it afterwards. The backend now reports its offlined physical connectors (by name) and the Linux enumeration includes them as inactive devices; enumeration contents are logged for diagnosis.
The helper split the status file path after the last '/' - yielding 'status' instead of the connector name. That poisoned everything built on it: the offlined-physical list reported 'status' instead of 'eDP-1' (so the display enumeration missed the forced-off screen and teardown skipped restoring it), and the mode-switch kscreen-doctor call targeted 'output.status'. Take the parent directory name first.
The client's screen combination modes map through to_vdd_prep to vdd_as_primary / vdd_as_secondary / display_off. Windows realizes the primary/secondary distinction through topology order; KWin's equivalent is the kscreen output priority, so apply_vdd_prep now sets the virtual display to priority 1 (primary mode) or 2 behind the physical screens (secondary mode) via kscreen-doctor, and teardown hands priority 1 back to the physical screens. display_off keeps the power-off behavior. Compositors without kscreen-doctor degrade to 'virtual display enabled'.
Gap backlog: D17 leaves the open list and D18 becomes a recorded decision; §5.15 documents the EDID reader, the verified fallback and the warning-level degradation message. The report gains progress item 35.
Covers EDID-based display friendly names and the explicit compositor-unavailable warning.
GET /api/runtime/hdr answered "available: false" on Linux, so the WebUI could never show which dynamic metadata a stream carries even though the analyzer now runs there; Windows registers its pipeline from the capture device (audit item F11). The avcodec session now registers the same shared status on Linux: hdr_mode (pq/hlg/sdr), the configured analysis mode, whether this session actually has a statistics producer, and - only when analysis is active - the formats the stream can really carry (hdr10_plus, hdr_vivid), matching the Windows rule. scene_metadata_active flips on the first frame with usable statistics and the entry is removed when the session ends; SDR sessions register nothing, so an empty pipeline list means "no HDR session". conversion_path is left empty because Linux converts no HDR (KMS delivers PQ/HLG directly); the frontend hides that line for an empty value instead of labelling it as a D3D11 path. A failure reason is set when analysis was wanted but no producer exists. The endpoint's available flag is no longer platform-hardcoded. Windows is untouched: the registration block is Linux-only and its capture pipeline keeps registering as before. The shared registry already has coverage in test_video.cpp (an initially duplicated test of mine was removed).
Gap backlog: F11 leaves the open list and §5.16 documents what the Linux session publishes, why conversion_path stays empty and how Windows is unaffected. The report gains progress item 36.
Covers the Linux HDR runtime status publication.
is_topology_valid accepted multi-device groups while set_topology rejects them, so a caller could pass validation and then fail inside the setter with a different message (audit item D16). Reject them in the validator with the same reasoning: this backend cannot express a duplicated group at all until a compositor-side backend exists, whereas Windows accepts up to two devices per group. The VDD prep paths only build singleton groups, so behavior there is unchanged. Also hoist the virtual microphone's description into a named constant next to its sink name, and note that Windows has no counterpart to align with - its capture device is named by the VB-Cable driver (audit item A6).
Gap backlog: D16 becomes §5.17 R29 with the A6 constant, and the items that were re-checked and kept as-is move to §5.18 with their reasons (empty-container contract, microphone contract edges, log language, foreground exe semantics, histogram estimator, and the replication topology whose kscreen-doctor limitation was verified against the installed tool). The report gains progress item 37.
Covers the topology validator fix and the microphone naming constant.
D7, F3' and F9 are recorded decisions now (§5.18), so their entries in the §5.2 backlog were duplicates. §5.2 now lists only what is actually open: F4 (HLG analysis) at medium and C5 (cross-language constant sync) at low.
Add §5.19: every file in this workstream that compiles on Windows, with the evidence that its Windows behaviour is unchanged (verbatim-moved VDD helpers, the Windows capture device always setting `data` so the CPU analyzer paths are unreachable there, platform-gated hardware download / Vivid / status publication, numerically identical shared constants, byte-identical Windows tray statements and strings, additive shared headers) and the single deliberate exception (HDR10+ metadata is now attached on the first frame with real statistics instead of shipping fabricated placeholders). Add §5.20: the honest remaining list - HLG-domain analysis (F4), the niri / wlr-output-management output backend, the replication topology, and the recorded deliberate divergences in §5.18. C5 becomes a decision record (no cross-language codegen for five protocol constants; the C++ mirror is covered by the wire tests), which clears the low-priority section. The report gains progress item 38 with the final verification.
Creating a virtual display from the tray crashed with SIGSEGV on KDE. systemd-coredump put the top frame in display_device::vdd_utils::(anonymous)::enable_output_via_compositor with frames AlkaidLab#1-AlkaidLab#12 at one single address: the KDE branch of the new dispatcher called enable_output_via_compositor(connector) instead of running the command it replaced, so every VDD creation and live mode switch on Plasma overflowed the stack. The commit that introduced it (ff16fde) also claimed the KDE path was unchanged byte-for-byte; it was not. Both call sites (create_vdd_monitor, wait_for_mode_publication) keep using the same helper, but the desktop -> command mapping is now a pure function, platf::compositor_output::enable_command() in a new Linux-only module, and the dispatcher only reads the probes and hands the string to run_logged. The KDE command is the exact pre-ff16fdeb one ("kscreen-doctor output.<name>.enable") and stays ungated on tool_available, as before. The niri_session()/tool_available() probes move to that module too, so hint_primary_output() shares one implementation. tests/unit/test_compositor_output.cpp pins every branch (KDE/plasma, niri, wlr-randr, xrandr, unknown session) plus the probe semantics, so a mapping regression cannot hide behind the env-dependent dispatch again.
Both documents now describe the SIGSEGV, its root cause (a self-recursive KDE branch in the compositor-output dispatcher) and the fix, including the correction of the "KDE path unchanged" claim made when the dispatcher was introduced. LINUX_PORT_GAPS.md gains section 5.20 for this round and the remaining-work list moves to 5.21; the test baseline is updated to the current 528 cases / 515 passed / 0 assertion failures, with the one sandbox-caused network failure called out explicitly.
With a virtual display the session's mode and HDR maps were built for the physical screen and then dropped by filter_stale_devices(): config.device_id is resolved in make_parsed_config(), which runs *before* prepare_vdd() creates the VDD, so it still names the panel the client was looking at (or is empty). apply_config() therefore targeted eDP-1 while the VDD-only topology contained only DP-2, both maps came out empty, set_display_modes() no-opped on the empty map and the "guaranteed by the EDID" tolerance hid the rest: the resolution and HDR state the client asked for never reached the virtual display, while the panel kept whatever it had. In VDD mode the streamed display is the virtual one and the session's VDD stage already owns the topology, so resolve the target to the live VDD connector instead, and log the substitution when it differs from the requested id.
Around every debugfs probe the connector scan used the throwing std::filesystem overloads, so a caller without the file capabilities (a plain ./sunshine run, or a build whose capabilities were dropped) died with an uncaught std::filesystem_error as soon as a connector was connected and adopt_orphan_vdd_locked()/edid_override_path() looked for the override node. The aggregate test suite hit exactly that, aborting DisplayDeviceEnum.ActiveRequiresAnEnabledConnector. All four probes now use the error_code overloads and treat an unreadable directory as "no override node", which is what the capability-equipped service sees for a card without one.
The client's requested resolution could silently fail to take effect on the virtual display. Three assumptions caused it, all of them wrong: - is_mode_advertised() was a stub that answered "yes" whenever a VDD was live, so wait_for_mode_publication() never verified anything and the session proceeded no matter what the display advertised. It now asks the compositor (the Wayland analogue of EnumDisplaySettings) through a new platf::kscreen::advertised_modes() accessor that reuses the existing kscreen-doctor parser, matches with the shared +/-1 Hz semantics, polls for kModePublicationTimeout and, on failure, logs the modes that *are* published before reporting the session mode as unpublished. Non-KDE sessions keep the previous live-connector answer: an unavailable query is "unknown", never "not advertised". - The generator saturates the DTD's 16-bit pixel clock, so a mode above 655.35 MHz (3840x2160@120, 3440x1440@120, ...) was written as a *different* refresh (4K120 came out as 3840x2160@71.4). Such a mode is now rejected with an explicit error -- at creation, when the live list is updated, and in the publication check -- instead of replacing a valid EDID with a lying one. The limit is a named constant (vdd_edid::kMaxDtdPixelClockHz) because the DTD field size is a format property, not a tunable. - The EDID built at creation used the configured mode lists only, while Windows' SETMODES list carries the client's resolution and refresh rate too (cross product with every configured rate/resolution). The session's prepared settings now survive from set_vdd_session_mode() to create_vdd_monitor() for that purpose, and the stale cached_from_session flag is cleared on destroy so a later tray creation goes back to the configured preferred mode instead of reusing a finished session's mode. set_display_modes() failures on the virtual display are still tolerated, but only while the mode really is advertised; otherwise the session reports modes_fail like Windows does. Tests: EDID feasibility boundaries and the saturation failure mode (test_vdd_edid), plus the accessor contract (test_kscreen_modes, Linux-only).
New round section in LINUX_PORT_GAPS.md (5.21) and report item 40 describe the three defects behind the report, with the log evidence (empty "Changing display modes to:"/"Changing HDR states to:" for VDD sessions), the correction of the earlier "the blanking model is more conservative than CVT-R" claim -- cvt -r needs 658.25 MHz for 3440x1440@120, so those modes are simply not expressible in a base-block DTD -- and an honest DisplayID follow-up. The remaining-work list moves to 5.22 and the stale lock-granularity hazard is marked fixed. Test baseline updated to 532 cases / 519 passed / 0 assertion failures.
The client asked for 2400x1080@60 and got 2400x1080@144: the EDID's preferred timing carried the requested mode (so the resolution was right), but filter_stale_devices() erased every VDD entry from the mode and HDR maps by design -- the original backend assumed the EDID made compositor-side mode changes unnecessary. KDE therefore kept the mode it picked when the connector came up, which is the highest refresh rate the EDID offers. Windows applies the client's requested mode and HDR state to the VDD through CCD, so the maps now preserve the virtual display being configured while still dropping virtual displays that belong to another session or to no topology at all (the original/rollback bookkeeping keeps stripping them: a mode restore must not touch a VDD that is about to be destroyed). The "EDID guarantees the mode" tolerance after a failed set_display_modes() now checks the *current* mode instead of the advertised list: KDE lists every rate the EDID carries, so an unapplied 60 Hz request would otherwise pass while the display ran at 144 Hz. Only a display that already runs the session mode is tolerated.
Adds packaging/aur/sunshine-foundation-git (PKGBUILD, .SRCINFO, install
script, README) so the fork can be built from source with makepkg instead
of only via the local prebuilt-tree package in packaging/arch-local.
makepkg neither checks out gitlinks nor has network access in build(), so
every submodule the build consumes - including the nested ones under
third-party/{doxyconfig,moonlight-common-c,tray} - is declared as a pinned
source entry and restored into its gitlink path in prepare(). The two
oversized Windows/platform checkouts are sparse-checked out: third-party/AMF
(answer: none, Windows-only) and third-party/build-deps (this machine's
dist/Linux-<arch> slice only).
Boost is pinned to an exact version by Boost_Sunshine.cmake, so the package
depends on that exact boost/boost-libs and documents the coupling; without it
CMake silently fetches and builds Boost from GitHub inside build().
options=(!strip) keeps the file capabilities the install script sets
(same set as packaging/arch-local), which Sunshine needs for KMS capture,
connector EDID/status access and the DRM-master borrow used by the virtual
display.
…SCII name
`makepkg -si` died in package() with
CMake Error at build/cmake_install.cmake:118 (file):
file INSTALL cannot find "build/sunshine-2026.0914.211531.376057a2"
The executable name carries the configure timestamp plus the current commit
(cmake/prep/build_version.cmake) and cmake bakes it into cmake_install.cmake,
so re-running cmake after a commit without relinking -- the normal slip after
a pkgrel-only bump -- leaves the install rules naming a binary that was never
built. prepare() now detects that (install rules and the sunshine symlink
disagree) and finishes the job with cmake + ninja, or fails with the actual
configure command when build/ is not configured at all. package() no longer
hides the CMake error behind >/dev/null and explains the fix.
A dirty tree appends the "杂鱼" marker to the stamp, which libarchive cannot
translate ("bsdtar: Can't translate ... to UTF-8") and which leaves pacman
with a path it cannot reliably match; the staged binary is now renamed to an
ASCII stamp (…dirty) and the sunshine symlink re-pointed at it. The version
string compiled into the binary is unchanged.
The install hook also refuses to run setcap on a dangling symlink (a package
whose stamped binary is missing would otherwise install a capability-less
binary that cannot capture or create virtual displays).
A test box whose session is niri (Plasma available but niri starts) and a service started from SSH both blocked for a long time on client connection, with kscreen-doctor hitting the 10 s run_logged timeout. kscreen-doctor drives org.kde.KScreen over the session bus. Where there is no KScreen, asking anyway makes D-Bus activate a KScreen service that waits for a Plasma session, so the tool never returns by itself and only dies at the kill timeout; the previous code spawned it for every display query, and a session start issues dozens (enumeration, topology, modes, HDR). The "unavailable" static only suppressed the log line, not the spawn. platf::kscreen now owns both the availability decision and the runner: - session_supports_kscreen() reads NIRI_SOCKET first (niri has no KScreen even when XDG_CURRENT_DESKTOP claims KDE for toolkit compatibility) and otherwise requires a Plasma desktop string. It is evaluated per call, never cached, so a service that starts before the desktop still picks it up afterwards. A session that cannot have KScreen is never probed: 0 ms per query instead of 10 s. - queries use a 1.5 s probe timeout, and a failed probe arms a 20 s cooldown, so a broken KScreen costs at most one short probe per window rather than a 10 s hang per query. A successful query clears the cooldown. - change commands keep the 10 s bound and are only reached after a successful query (or a session that supports KScreen). Measured with the real code: KDE session 699 ms / 1 mode (unchanged), niri 0 ms, no desktop 0 ms, KDE-with-unreachable-compositor 807 ms once then 0 ms. The VDD layer's kscreen commands got the same guard: a niri session is routed to its own IPC instead of being sent a command that can only hang. tests/unit/test_kscreen_backend.cpp pins the gate (env combinations) and the cooldown transitions; it is Linux-only like the module.
package() could ship a binary whose version stamp names an older commit: the stamp is fixed at configure time, so a fresh commit does not change it, and the install-rules check alone only caught the case where cmake named a binary that was never linked. `makepkg` now compares the stamp's embedded commit (and its dirty marker) with the tree and re-configures plus relinks when they disagree, so the `Sunshine version:` line the user checks always identifies the tree that was packaged. The check is free when they already match.
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
No description provided.