From a991a9622cd01f06217d32e1a129b6601b8683e6 Mon Sep 17 00:00:00 2001 From: Dave Lane <42013603+ReenigneArcher@users.noreply.github.com> Date: Thu, 25 Jun 2026 22:43:17 -0400 Subject: [PATCH] docs(doxygen): enforce warn if undocumented (#5337) --- .github/workflows/ci-freebsd.yml | 7 +- .github/workflows/ci-linux.yml | 4 +- .github/workflows/ci-macos.yml | 4 +- .github/workflows/ci-windows.yml | 4 +- .github/workflows/ci.yml | 1 + .run/docs.run.xml | 7 + AGENTS.md | 25 +- README.md | 1 + docs/Doxyfile | 3 - docs/contributing.md | 20 +- packaging/sunshine.rb | 2 +- pyproject.toml | 5 +- src/audio.cpp | 31 +- src/audio.h | 73 +- src/cbs.cpp | 53 +- src/cbs.h | 42 +- src/config.cpp | 534 ++++++++++-- src/config.h | 270 ++++-- src/confighttp.cpp | 55 +- src/confighttp.h | 33 +- src/crypto.cpp | 59 ++ src/crypto.h | 222 ++++- src/display_device.cpp | 2 +- src/display_device.h | 2 +- src/entry_handler.cpp | 14 +- src/entry_handler.h | 16 + src/globals.h | 24 +- src/httpcommon.cpp | 40 +- src/httpcommon.h | 38 + src/input.cpp | 262 +++++- src/input.h | 52 +- src/logging.cpp | 19 +- src/logging.h | 83 +- src/main.cpp | 45 +- src/main.h | 4 +- src/move_by_copy.h | 45 + src/network.cpp | 27 + src/network.h | 50 +- src/nvenc/nvenc_base.cpp | 12 +- src/nvenc/nvenc_base.h | 14 +- src/nvenc/nvenc_colorspace.h | 8 +- src/nvenc/nvenc_config.h | 30 +- src/nvenc/nvenc_d3d11.h | 24 + src/nvenc/nvenc_encoded_frame.h | 10 +- src/nvenc/nvenc_utils.cpp | 9 + src/nvenc/nvenc_utils.h | 18 + src/nvhttp.cpp | 226 ++++- src/nvhttp.h | 48 +- src/platform/common.h | 680 ++++++++++++--- src/platform/linux/audio.cpp | 177 +++- src/platform/linux/cuda.cpp | 309 ++++++- src/platform/linux/cuda.cu | 17 +- src/platform/linux/graphics.cpp | 116 ++- src/platform/linux/graphics.h | 484 ++++++++-- src/platform/linux/input/inputtino.cpp | 30 + src/platform/linux/input/inputtino_common.h | 45 +- .../linux/input/inputtino_gamepad.cpp | 40 + src/platform/linux/input/inputtino_gamepad.h | 49 ++ .../linux/input/inputtino_keyboard.cpp | 8 + src/platform/linux/input/inputtino_keyboard.h | 15 + src/platform/linux/input/inputtino_mouse.cpp | 18 + src/platform/linux/input/inputtino_mouse.h | 40 + src/platform/linux/input/inputtino_pen.cpp | 3 + src/platform/linux/input/inputtino_pen.h | 9 +- src/platform/linux/input/inputtino_seat.h | 2 + src/platform/linux/input/inputtino_touch.cpp | 3 + src/platform/linux/input/inputtino_touch.h | 9 +- src/platform/linux/kmsgrab.cpp | 517 +++++++++-- src/platform/linux/kwingrab.cpp | 55 +- src/platform/linux/misc.cpp | 155 +++- src/platform/linux/misc.h | 15 +- src/platform/linux/pipewire.cpp | 213 ++++- src/platform/linux/portalgrab.cpp | 124 ++- src/platform/linux/publish.cpp | 184 +++- src/platform/linux/vaapi.cpp | 163 +++- src/platform/linux/vaapi.h | 32 + src/platform/linux/vulkan_encode.cpp | 76 +- src/platform/linux/vulkan_encode.h | 12 + src/platform/linux/wayland.cpp | 12 +- src/platform/linux/wayland.h | 265 +++++- src/platform/linux/wlgrab.cpp | 116 ++- src/platform/linux/x11grab.cpp | 243 +++++- src/platform/linux/x11grab.h | 39 +- src/platform/macos/av_audio.h | 22 + src/platform/macos/av_img_t.h | 48 +- src/platform/macos/av_video.h | 70 +- src/platform/macos/coreaudio_helpers.h | 5 +- src/platform/macos/display.mm | 47 +- src/platform/macos/input.cpp | 112 ++- src/platform/macos/microphone.mm | 38 +- src/platform/macos/misc.h | 21 +- src/platform/macos/misc.mm | 34 +- src/platform/macos/nv12_zero_device.cpp | 11 +- src/platform/macos/nv12_zero_device.h | 38 +- src/platform/windows/PolicyConfig.h | 76 ++ src/platform/windows/audio.cpp | 256 +++++- src/platform/windows/display.h | 565 ++++++++++-- src/platform/windows/display_base.cpp | 16 +- src/platform/windows/display_ram.cpp | 37 + src/platform/windows/display_vram.cpp | 377 ++++++-- src/platform/windows/display_wgc.cpp | 26 +- src/platform/windows/input.cpp | 150 +++- src/platform/windows/keylayout.h | 3 + src/platform/windows/misc.cpp | 130 ++- src/platform/windows/misc.h | 23 + .../windows/nvprefs/driver_settings.h | 39 + .../nvprefs/nvapi_opensource_wrapper.cpp | 117 +++ .../windows/nvprefs/nvprefs_common.cpp | 15 + src/platform/windows/nvprefs/nvprefs_common.h | 52 +- .../windows/nvprefs/nvprefs_interface.cpp | 15 +- .../windows/nvprefs/nvprefs_interface.h | 36 + src/platform/windows/nvprefs/undo_data.cpp | 54 ++ src/platform/windows/nvprefs/undo_data.h | 41 +- src/platform/windows/nvprefs/undo_file.h | 32 + src/platform/windows/publish.cpp | 108 ++- src/process.cpp | 50 +- src/process.h | 97 ++- src/round_robin.h | 175 ++++ src/rswrapper.c | 88 +- src/rswrapper.h | 43 +- src/rtsp.cpp | 201 ++++- src/rtsp.h | 57 +- src/stat_trackers.cpp | 6 + src/stat_trackers.h | 26 + src/stream.cpp | 454 +++++++--- src/stream.h | 68 +- src/sync.h | 66 +- src/system_tray.cpp | 26 + src/task_pool.h | 102 ++- src/thread_pool.h | 42 + src/thread_safe.h | 305 ++++++- src/upnp.cpp | 20 +- src/upnp.h | 20 +- src/utility.h | 824 +++++++++++++++++- src/uuid.h | 45 +- src/video.cpp | 484 +++++++++- src/video.h | 388 +++++++-- src/video_colorspace.cpp | 9 + src/video_colorspace.h | 57 +- third-party/lizardbyte-common | 2 +- uv.lock | 207 +++++ 141 files changed, 11804 insertions(+), 1524 deletions(-) create mode 100644 .run/docs.run.xml diff --git a/.github/workflows/ci-freebsd.yml b/.github/workflows/ci-freebsd.yml index 0ca81cff6..0fc48089a 100644 --- a/.github/workflows/ci-freebsd.yml +++ b/.github/workflows/ci-freebsd.yml @@ -143,10 +143,7 @@ jobs: release: ${{ matrix.bsd_release }} run: | set -e - uv sync --locked --only-group glad --python "/usr/local/bin/python${PYTHON_VERSION}" \ - --no-python-downloads --no-install-project - uv sync --project third-party/lizardbyte-common --locked --only-group test-c \ - --python "/usr/local/bin/python${PYTHON_VERSION}" \ + uv sync --locked --group glad --python "/usr/local/bin/python${PYTHON_VERSION}" \ --no-python-downloads --no-install-project # fix git safe.directory issues @@ -259,7 +256,7 @@ jobs: shell: freebsd {0} run: | cd "${GITHUB_WORKSPACE}/build" - uv run --project ../third-party/lizardbyte-common --locked --no-sync gcovr . -r ../src \ + uv run --project .. --locked --no-sync gcovr . -r ../src \ --exclude-noncode-lines \ --exclude-throw-branches \ --exclude-unreachable-branches \ diff --git a/.github/workflows/ci-linux.yml b/.github/workflows/ci-linux.yml index ce5af5ae4..91ec40d65 100644 --- a/.github/workflows/ci-linux.yml +++ b/.github/workflows/ci-linux.yml @@ -175,7 +175,7 @@ jobs: - name: Sync Python tools run: | - uv sync --project third-party/lizardbyte-common --locked --only-group test-c \ + uv sync --locked \ --python "${PYTHON_VERSION}" \ --no-python-downloads --no-install-project @@ -197,7 +197,7 @@ jobs: (steps.test.outcome == 'success' || steps.test.outcome == 'failure') working-directory: build run: | - uv run --project ../third-party/lizardbyte-common --locked --no-sync gcovr \ + uv run --project .. --locked --no-sync gcovr \ --gcov-executable "gcov-${GCC_VERSION}" . -r ../src \ --exclude-noncode-lines \ --exclude-throw-branches \ diff --git a/.github/workflows/ci-macos.yml b/.github/workflows/ci-macos.yml index 65ecfee1d..299f56869 100644 --- a/.github/workflows/ci-macos.yml +++ b/.github/workflows/ci-macos.yml @@ -100,7 +100,7 @@ jobs: - name: Sync Python tools run: | - uv sync --project third-party/lizardbyte-common --locked --only-group test-c \ + uv sync --locked \ --python "${PYTHON_VERSION}" \ --no-python-downloads --no-install-project @@ -184,7 +184,7 @@ jobs: (steps.test.outcome == 'success' || steps.test.outcome == 'failure') working-directory: build run: | - uv run --project ../third-party/lizardbyte-common --locked --no-sync gcovr . -r ../src \ + uv run --project .. --locked --no-sync gcovr . -r ../src \ --exclude-noncode-lines \ --exclude-throw-branches \ --exclude-unreachable-branches \ diff --git a/.github/workflows/ci-windows.yml b/.github/workflows/ci-windows.yml index 35596367a..700105223 100644 --- a/.github/workflows/ci-windows.yml +++ b/.github/workflows/ci-windows.yml @@ -214,7 +214,7 @@ jobs: MSYS2_PATH_TYPE: inherit UV_PYTHON: ${{ steps.setup-python.outputs.python-path }} run: | - uv sync --project third-party/lizardbyte-common --locked --only-group test-c \ + uv sync --locked \ --no-python-downloads \ --no-install-project @@ -369,7 +369,7 @@ jobs: env: MSYS2_PATH_TYPE: inherit run: | - uv run --project ../third-party/lizardbyte-common --locked --no-sync gcovr . -r ../src \ + uv run --project .. --locked --no-sync gcovr . -r ../src \ --exclude-noncode-lines \ --exclude-throw-branches \ --exclude-unreachable-branches \ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b11e72142..831b76ade 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -140,6 +140,7 @@ jobs: COPR_BETA_WEBHOOK_TOKEN: ${{ secrets.COPR_BETA_WEBHOOK_TOKEN }} COPR_STABLE_WEBHOOK_TOKEN: ${{ secrets.COPR_STABLE_WEBHOOK_TOKEN }} COPR_CLI_CONFIG: ${{ secrets.COPR_CLI_CONFIG }} + VIRUSTOTAL_API_KEY: ${{ secrets.VIRUSTOTAL_API_KEY }} build-linux-flatpak: name: Linux Flatpak diff --git a/.run/docs.run.xml b/.run/docs.run.xml new file mode 100644 index 000000000..9585c399b --- /dev/null +++ b/.run/docs.run.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/AGENTS.md b/AGENTS.md index f532e021c..b89e31407 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,6 +8,29 @@ the build directory. The project uses gtest as a test framework. +When adding localization do not update any language other than `en`. This also means to exclude en-US or other variants. + +Always add or update doxygen documentation. + +The project requires that everything be documented in doxygen or the build will fail. + +Primary doxygen comments should be done like so: + +```cpp + /** + * @brief Describe the function, structure, etc. + * + * @param my_param Describe the parameter. + * @return Describe the return. + */ +``` + +Inline doxygen comments should use `///< ...` instead of `/**< ... */`. + Always follow the style guidelines defined in .clang-format for c/c++ code. -When adding localization do not update any language other than `en`. This also means to exclude en-US or other variants. +Do not ever create issues or pull requests. +If asked to create an issue or pull request, do so in their fork instead of the LizardByte GitHub organization. +Never create an issue or pull request in the LizardByte GitHub organization. + +Add or update tests for new or modified methods and code. Target 100% coverage on changed code. diff --git a/README.md b/README.md index b79fe591f..b9563661c 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ GitHub Workflow Status (localize) Read the Docs Codecov + SonarCloud ## ℹ️ About diff --git a/docs/Doxyfile b/docs/Doxyfile index 361961926..0c84d823d 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -35,9 +35,6 @@ IMAGE_PATH = ../docs/images PREDEFINED += SUNSHINE_BUILD_WAYLAND PREDEFINED += SUNSHINE_TRAY=1 -# TODO: Enable this when we have complete documentation -WARN_IF_UNDOCUMENTED = NO - # files and directories to process USE_MDFILE_AS_MAINPAGE = ../README.md INPUT = ../README.md \ diff --git a/docs/contributing.md b/docs/contributing.md index 7595918bf..113bb1730 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -128,25 +128,23 @@ any of the following paths are modified. ``` When testing locally, it may be desirable to manually extract, initialize, update, and compile strings. Python and -uv are required for this, along with the Python dependencies in the `third-party/lizardbyte-common/pyproject.toml` -file. You can install these with the following command. +uv are required for this, along with the Python dependencies in the Sunshine `pyproject.toml`. From the repository +root, install these with the following command. ```bash -uv sync --project third-party/lizardbyte-common --locked --only-group locale --no-install-project +uv sync --locked ``` Additionally, [xgettext](https://www.gnu.org/software/gettext) must be installed. * Extract, initialize, and update ```bash - uv run --project third-party/lizardbyte-common --locked --no-sync \ - python third-party/lizardbyte-common/scripts/localize.py --root-dir . --extract --init --update + uv run --locked --no-sync lb-localize --root-dir . --extract --init --update ``` * Compile ```bash - uv run --project third-party/lizardbyte-common --locked --no-sync \ - python third-party/lizardbyte-common/scripts/localize.py --root-dir . --compile + uv run --locked --no-sync lb-localize --root-dir . --compile ``` > [!IMPORTANT] @@ -160,10 +158,12 @@ Additionally, [xgettext](https://www.gnu.org/software/gettext) must be installed #### Clang Format Source code is tested against the `.clang-format` file for linting errors. -To apply clang-format locally (will modify files): +From the repository root, apply clang-format locally with the installed lizardbyte-common script. This will modify +files in place. + ```bash -uv run --project third-party/lizardbyte-common --locked --only-group lint-c \ - python third-party/lizardbyte-common/scripts/update_clang_format.py +uv sync --locked +uv run --locked --no-sync lb-update-clang-format ``` #### Unit Testing diff --git a/packaging/sunshine.rb b/packaging/sunshine.rb index 08f68709f..3101e1023 100644 --- a/packaging/sunshine.rb +++ b/packaging/sunshine.rb @@ -221,7 +221,7 @@ class Sunshine < Formula either install icu4c or use brew install sunshine --with-static-boost instead EOS end - ENV.append "CXXFLAGS", "-I#{Formula["icu4c"].opt_include}" + ENV.append "CXXFLAGS", "-I#{formula_opt_include("icu4c")}" icu4c_lib_path = formula_opt_lib("icu4c").to_s ENV.append "LDFLAGS", "-L#{icu4c_lib_path}" ENV["LIBRARY_PATH"] = icu4c_lib_path diff --git a/pyproject.toml b/pyproject.toml index ce5686c2a..75d971822 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,9 @@ authors = [ {name = "LizardByte", email = "lizardbyte@users.noreply.github.com"} ] -dependencies = [] +dependencies = [ + "lizardbyte-common[c]", +] [dependency-groups] # glad2 declares Jinja2>=2.7,<4.0 as a dependency, so installing it pulls in jinja2 transitively. @@ -41,3 +43,4 @@ py-modules = [] flatpak_node_generator = { path = "packaging/linux/flatpak/deps/flatpak-builder-tools/node" } flatpak_pip_generator = { path = "packaging/linux/flatpak/deps/flatpak-builder-tools/pip" } glad2 = { path = "third-party/glad" } +lizardbyte-common = { path = "third-party/lizardbyte-common" } diff --git a/src/audio.cpp b/src/audio.cpp index da5a221e5..55d50c475 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -19,19 +19,35 @@ namespace audio { using namespace std::literals; + /** + * @brief Owning pointer for an Opus multistream encoder. + */ using opus_t = util::safe_ptr; + /** + * @brief Shared queue carrying captured PCM sample buffers to the encoder thread. + */ using sample_queue_t = std::shared_ptr>>; static int start_audio_control(audio_ctx_t &ctx); static void stop_audio_control(audio_ctx_t &); static void apply_surround_params(opus_stream_config_t &stream, const stream_params_t ¶ms); + /** + * @brief Select the Opus stream configuration for a channel count and quality tier. + * + * @param channels Number of audio channels in the stream. + * @param quality Whether the high-quality Opus layout should be selected. + * @return Index into `stream_configs` for the requested layout. + */ int map_stream(int channels, bool quality); - constexpr auto SAMPLE_RATE = 48000; + constexpr auto SAMPLE_RATE = 48000; ///< Audio sample rate in hertz required by Opus. // NOTE: If you adjust the bitrates listed here, make sure to update the // corresponding bitrate adjustment logic in rtsp_stream::cmd_announce() + /** + * @brief Opus stream layouts and bitrates advertised to clients. + */ opus_stream_config_t stream_configs[MAX_STREAM_CONFIG] { { SAMPLE_RATE, @@ -83,6 +99,13 @@ namespace audio { }, }; + /** + * @brief Encode captured PCM samples into Opus packets on the audio worker thread. + * + * @param samples Queue of captured PCM sample buffers to encode. + * @param config Audio stream settings negotiated with the client. + * @param channel_data Platform-specific audio capture context passed to packet metadata. + */ void encodeThread(sample_queue_t samples, config_t config, void *channel_data) { auto packets = mail::man->queue(mail::audio_packets); auto stream = stream_configs[map_stream(config.channels, config.flags[config_t::HIGH_QUALITY])]; @@ -128,6 +151,9 @@ namespace audio { } } + /** + * @brief Run the capture loop for this backend. + */ void capture(safe::mail_t mail, config_t config, void *channel_data) { auto shutdown_event = mail->event(mail::shutdown); if (!config::audio.stream) { @@ -265,6 +291,9 @@ namespace audio { return ctx.control->is_sink_available(sink); } + /** + * @brief Select the Opus stream configuration for a channel count and quality tier. + */ int map_stream(int channels, bool quality) { int shift = quality ? 1 : 0; switch (channels) { diff --git a/src/audio.h b/src/audio.h index 763cc9886..e0e919a54 100644 --- a/src/audio.h +++ b/src/audio.h @@ -12,6 +12,9 @@ #include namespace audio { + /** + * @brief Supported Opus channel layouts advertised to Moonlight clients. + */ enum stream_config_e : int { STEREO, ///< Stereo HIGH_STEREO, ///< High stereo @@ -22,25 +25,37 @@ namespace audio { MAX_STREAM_CONFIG ///< Maximum audio stream configuration }; + /** + * @brief Static Opus encoder layout for one advertised audio mode. + */ struct opus_stream_config_t { - std::int32_t sampleRate; - int channelCount; - int streams; - int coupledStreams; - const std::uint8_t *mapping; - int bitrate; + std::int32_t sampleRate; ///< Opus sample rate in hertz. + int channelCount; ///< Number of audio channels in the Opus layout. + int streams; ///< Number of Opus streams in the layout. + int coupledStreams; ///< Number of stereo-coupled Opus streams. + const std::uint8_t *mapping; ///< Channel mapping table passed to the Opus encoder. + int bitrate; ///< Target bitrate in bits per second. }; + /** + * @brief Custom Opus channel layout supplied by configuration. + */ struct stream_params_t { - int channelCount; - int streams; - int coupledStreams; - std::uint8_t mapping[8]; + int channelCount; ///< Number of audio channels in the Opus layout. + int streams; ///< Number of Opus streams in the custom layout. + int coupledStreams; ///< Number of stereo-coupled Opus streams. + std::uint8_t mapping[8]; ///< Channel mapping table for up to eight speakers. }; extern opus_stream_config_t stream_configs[MAX_STREAM_CONFIG]; + /** + * @brief Audio capture and encoder settings for a stream. + */ struct config_t { + /** + * @brief Boolean audio feature flags. + */ enum flags_e : int { HIGH_QUALITY, ///< High quality audio HOST_AUDIO, ///< Host audio @@ -49,29 +64,48 @@ namespace audio { MAX_FLAGS ///< Maximum number of flags }; - int packetDuration; - int channels; - int mask; + int packetDuration; ///< Packet duration in milliseconds requested by the client. + int channels; ///< Number of audio channels requested by the client. + int mask; ///< Speaker mask describing the requested channel layout. - stream_params_t customStreamParams; + stream_params_t customStreamParams; ///< Custom Opus layout used when CUSTOM_SURROUND_PARAMS is enabled. - std::bitset flags; + std::bitset flags; ///< Enabled audio feature flags. }; + /** + * @brief Shared audio device state used while streams are active. + */ struct audio_ctx_t { // We want to change the sink for the first stream only - std::unique_ptr sink_flag; + std::unique_ptr sink_flag; ///< Tracks whether the capture sink was already switched. - std::unique_ptr control; + std::unique_ptr control; ///< Platform audio-control implementation. - bool restore_sink; - platf::sink_t sink; + bool restore_sink; ///< Whether Sunshine should restore the original sink when capture ends. + platf::sink_t sink; ///< Original sink captured before Sunshine switched devices. }; + /** + * @brief Byte buffer used for encoded audio packet payloads. + */ using buffer_t = util::buffer_t; + /** + * @brief Encoded audio packet paired with platform channel metadata. + */ using packet_t = std::pair; + /** + * @brief Shared mailbox reference to the global audio context. + */ using audio_ctx_ref_t = safe::shared_t::ptr_t; + /** + * @brief Capture, encode, and publish audio packets for a stream. + * + * @param mail Mailbox used to publish encoded audio packets. + * @param config Audio capture and encoder settings. + * @param channel_data Platform-specific capture channel pointer. + */ void capture(safe::mail_t mail, config_t config, void *channel_data); /** @@ -99,6 +133,7 @@ namespace audio { * } * return false; * @examples_end + * @param ctx Native context object used by the operation or callback. */ bool is_audio_ctx_sink_available(const audio_ctx_t &ctx); } // namespace audio diff --git a/src/cbs.cpp b/src/cbs.cpp index 67cda0c1f..03f5f04e2 100644 --- a/src/cbs.cpp +++ b/src/cbs.cpp @@ -19,14 +19,30 @@ extern "C" { using namespace std::literals; namespace cbs { + /** + * @brief Release an FFmpeg coded bitstream context. + * + * @param c Context pointer owned by the safe pointer wrapper. + */ void close(CodedBitstreamContext *c) { ff_cbs_close(&c); } + /** + * @brief Owning coded bitstream context pointer that calls ff_cbs_close. + */ using ctx_t = util::safe_ptr; + /** + * @brief Owns an FFmpeg coded bitstream fragment and frees fragment buffers on destruction. + */ class frag_t: public CodedBitstreamFragment { public: + /** + * @brief Move-construct a coded bitstream fragment and transfer FFmpeg-owned buffers. + * + * @param o Fragment whose allocated buffers are transferred to this instance. + */ frag_t(frag_t &&o) { std::copy((std::uint8_t *) &o, (std::uint8_t *) (&o + 1), (std::uint8_t *) this); @@ -38,6 +54,12 @@ namespace cbs { std::fill_n((std::uint8_t *) this, sizeof(*this), 0); } + /** + * @brief Move-assign a coded bitstream fragment and transfer FFmpeg-owned buffers. + * + * @param o Fragment whose allocated buffers are transferred to this instance. + * @return Reference to this fragment. + */ frag_t &operator=(frag_t &&o) { std::copy((std::uint8_t *) &o, (std::uint8_t *) (&o + 1), (std::uint8_t *) this); @@ -54,6 +76,15 @@ namespace cbs { } }; + /** + * @brief Serialize a prepared coded bitstream unit with an existing context. + * + * @param cbs_ctx Coded bitstream context initialized for the codec. + * @param nal NAL unit type to insert into the fragment. + * @param uh Pointer to the FFmpeg raw unit header/content structure. + * @param codec_id FFmpeg codec identifier used by the context. + * @return Serialized NAL unit bytes, or an empty buffer if FFmpeg rejects the fragment. + */ util::buffer_t write(cbs::ctx_t &cbs_ctx, std::uint8_t nal, void *uh, AVCodecID codec_id) { cbs::frag_t frag; auto err = ff_cbs_insert_unit_content(&frag, -1, nal, uh, nullptr); @@ -79,6 +110,14 @@ namespace cbs { return data; } + /** + * @brief Serialize a prepared coded bitstream unit with a temporary context. + * + * @param nal NAL unit type to insert into the fragment. + * @param uh Pointer to the FFmpeg raw unit header/content structure. + * @param codec_id FFmpeg codec identifier used to initialize the temporary context. + * @return Serialized NAL unit bytes, or an empty buffer if FFmpeg rejects the fragment. + */ util::buffer_t write(std::uint8_t nal, void *uh, AVCodecID codec_id) { cbs::ctx_t cbs_ctx; ff_cbs_init(&cbs_ctx, codec_id, nullptr); @@ -86,6 +125,9 @@ namespace cbs { return write(cbs_ctx, nal, uh, codec_id); } + /** + * @brief Build replacement H.264 SPS bytes with Sunshine-required VUI fields. + */ h264_t make_sps_h264(const AVCodecContext *avctx, const AVPacket *packet) { cbs::ctx_t ctx; if (ff_cbs_init(&ctx, AV_CODEC_ID_H264, nullptr)) { @@ -142,6 +184,9 @@ namespace cbs { }; } + /** + * @brief Build replacement HEVC VPS/SPS bytes with Sunshine-required VUI fields. + */ hevc_t make_sps_hevc(const AVCodecContext *avctx, const AVPacket *packet) { cbs::ctx_t ctx; if (ff_cbs_init(&ctx, AV_CODEC_ID_H265, nullptr)) { @@ -215,9 +260,11 @@ namespace cbs { } /** - * This function initializes a Coded Bitstream Context and reads the packet into a Coded Bitstream Fragment. - * It then checks if the SPS->VUI (Video Usability Information) is present in the active SPS of the packet. - * This is done for both H264 and H265 codecs. + * @brief Check whether an encoded H.264 or HEVC packet contains active SPS VUI metadata. + * + * @param packet Encoded packet to parse with FFmpeg's coded bitstream reader. + * @param codec_id FFmpeg codec identifier; expected to be AV_CODEC_ID_H264 or AV_CODEC_ID_H265. + * @return `true` when the packet's active SPS advertises VUI parameters. */ bool validate_sps(const AVPacket *packet, int codec_id) { cbs::ctx_t ctx; diff --git a/src/cbs.h b/src/cbs.h index 5dfddab5d..13820edf8 100644 --- a/src/cbs.h +++ b/src/cbs.h @@ -12,28 +12,52 @@ struct AVCodecContext; namespace cbs { + /** + * @brief Original and rewritten bytes for one codec NAL unit. + */ struct nal_t { - util::buffer_t _new; - util::buffer_t old; + util::buffer_t _new; ///< Replacement bytes generated by Sunshine. + util::buffer_t old; ///< Original bytes copied from the source packet. }; + /** + * @brief HEVC parameter sets before and after VUI rewriting. + */ struct hevc_t { - nal_t vps; - nal_t sps; + nal_t vps; ///< Video parameter set bytes. + nal_t sps; ///< Sequence parameter set bytes. }; + /** + * @brief H.264 parameter set before and after VUI rewriting. + */ struct h264_t { - nal_t sps; + nal_t sps; ///< Sequence parameter set bytes. }; + /** + * @brief Build HEVC replacement parameter sets for a packet. + * + * @param ctx FFmpeg codec context that provides colorimetry and reference-frame metadata. + * @param packet Encoded packet containing the original VPS and SPS. + * @return Original and rewritten VPS/SPS bytes. + */ hevc_t make_sps_hevc(const AVCodecContext *ctx, const AVPacket *packet); + /** + * @brief Build an H.264 replacement SPS for a packet. + * + * @param ctx FFmpeg codec context that provides colorimetry and reference-frame metadata. + * @param packet Encoded packet containing the original SPS. + * @return Original and rewritten SPS bytes. + */ h264_t make_sps_h264(const AVCodecContext *ctx, const AVPacket *packet); /** - * @brief Validates the Sequence Parameter Set (SPS) of a given packet. - * @param packet The packet to validate. - * @param codec_id The ID of the codec used (either AV_CODEC_ID_H264 or AV_CODEC_ID_H265). - * @return True if the SPS->VUI is present in the active SPS of the packet, false otherwise. + * @brief Check whether an encoded H.264 or HEVC packet contains active SPS VUI metadata. + * + * @param packet Encoded packet to parse with FFmpeg's coded bitstream reader. + * @param codec_id FFmpeg codec identifier; expected to be AV_CODEC_ID_H264 or AV_CODEC_ID_H265. + * @return `true` when the packet's active SPS advertises VUI parameters. */ bool validate_sps(const AVPacket *packet, int codec_id); } // namespace cbs diff --git a/src/config.cpp b/src/config.cpp index fb67fef71..e1dab3516 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -41,15 +41,21 @@ namespace fs = std::filesystem; using namespace std::literals; -constexpr auto CA_DIR = "credentials"; -const std::string PRIVATE_KEY_FILE = std::string(CA_DIR) + "/cakey.pem"; -const std::string CERTIFICATE_FILE = std::string(CA_DIR) + "/cacert.pem"; -const std::string APPS_JSON_PATH = platf::appdata().string() + "/apps.json"; +constexpr auto CA_DIR = "credentials"; ///< Subdirectory under app data that stores Sunshine credentials. +const std::string PRIVATE_KEY_FILE = std::string(CA_DIR) + "/cakey.pem"; ///< Relative path to the persisted private key PEM file. +const std::string CERTIFICATE_FILE = std::string(CA_DIR) + "/cacert.pem"; ///< Relative path to the persisted certificate PEM file. +const std::string APPS_JSON_PATH = platf::appdata().string() + "/apps.json"; ///< Default path to the applications JSON file. namespace config { namespace nv { + /** + * @brief Parse the `nvenc_twopass` configuration value. + * + * @param preset Encoder preset value supplied by the configuration. + * @return Parsed enum value, or the setting-specific default when the text is unknown. + */ nvenc::nvenc_two_pass twopass_from_view(const std::string_view &preset) { if (preset == "disabled") { return nvenc::nvenc_two_pass::disabled; @@ -64,6 +70,12 @@ namespace config { return nvenc::nvenc_two_pass::quarter_resolution; } + /** + * @brief Parse the `nvenc_split_encode` configuration value. + * + * @param preset Encoder preset value supplied by the configuration. + * @return Parsed enum value, or the setting-specific default when the text is unknown. + */ nvenc::nvenc_split_frame_encoding split_encode_from_view(const std::string_view &preset) { using enum nvenc::nvenc_split_frame_encoding; if (preset == "disabled") { @@ -84,45 +96,45 @@ namespace config { namespace amd { #if !defined(_WIN32) || defined(DOXYGEN) // values accurate as of 27/12/2022, but aren't strictly necessary for MacOS build - constexpr int AMF_VIDEO_ENCODER_AV1_QUALITY_PRESET_SPEED = 100; - constexpr int AMF_VIDEO_ENCODER_AV1_QUALITY_PRESET_QUALITY = 30; - constexpr int AMF_VIDEO_ENCODER_AV1_QUALITY_PRESET_BALANCED = 70; - constexpr int AMF_VIDEO_ENCODER_HEVC_QUALITY_PRESET_SPEED = 10; - constexpr int AMF_VIDEO_ENCODER_HEVC_QUALITY_PRESET_QUALITY = 0; - constexpr int AMF_VIDEO_ENCODER_HEVC_QUALITY_PRESET_BALANCED = 5; - constexpr int AMF_VIDEO_ENCODER_QUALITY_PRESET_SPEED = 1; - constexpr int AMF_VIDEO_ENCODER_QUALITY_PRESET_QUALITY = 2; - constexpr int AMF_VIDEO_ENCODER_QUALITY_PRESET_BALANCED = 0; - constexpr int AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_CONSTANT_QP = 0; - constexpr int AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_CBR = 3; - constexpr int AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_PEAK_CONSTRAINED_VBR = 2; - constexpr int AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_LATENCY_CONSTRAINED_VBR = 1; - constexpr int AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_CONSTANT_QP = 0; - constexpr int AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_CBR = 3; - constexpr int AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_PEAK_CONSTRAINED_VBR = 2; - constexpr int AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_LATENCY_CONSTRAINED_VBR = 1; - constexpr int AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_CONSTANT_QP = 0; - constexpr int AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_CBR = 1; - constexpr int AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_PEAK_CONSTRAINED_VBR = 2; - constexpr int AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_LATENCY_CONSTRAINED_VBR = 3; - constexpr int AMF_VIDEO_ENCODER_AV1_USAGE_TRANSCODING = 0; - constexpr int AMF_VIDEO_ENCODER_AV1_USAGE_LOW_LATENCY = 1; - constexpr int AMF_VIDEO_ENCODER_AV1_USAGE_ULTRA_LOW_LATENCY = 2; - constexpr int AMF_VIDEO_ENCODER_AV1_USAGE_WEBCAM = 3; - constexpr int AMF_VIDEO_ENCODER_AV1_USAGE_LOW_LATENCY_HIGH_QUALITY = 5; - constexpr int AMF_VIDEO_ENCODER_HEVC_USAGE_TRANSCODING = 0; - constexpr int AMF_VIDEO_ENCODER_HEVC_USAGE_ULTRA_LOW_LATENCY = 1; - constexpr int AMF_VIDEO_ENCODER_HEVC_USAGE_LOW_LATENCY = 2; - constexpr int AMF_VIDEO_ENCODER_HEVC_USAGE_WEBCAM = 3; - constexpr int AMF_VIDEO_ENCODER_HEVC_USAGE_LOW_LATENCY_HIGH_QUALITY = 5; - constexpr int AMF_VIDEO_ENCODER_USAGE_TRANSCODING = 0; - constexpr int AMF_VIDEO_ENCODER_USAGE_ULTRA_LOW_LATENCY = 1; - constexpr int AMF_VIDEO_ENCODER_USAGE_LOW_LATENCY = 2; - constexpr int AMF_VIDEO_ENCODER_USAGE_WEBCAM = 3; - constexpr int AMF_VIDEO_ENCODER_USAGE_LOW_LATENCY_HIGH_QUALITY = 5; - constexpr int AMF_VIDEO_ENCODER_UNDEFINED = 0; - constexpr int AMF_VIDEO_ENCODER_CABAC = 1; - constexpr int AMF_VIDEO_ENCODER_CALV = 2; + constexpr int AMF_VIDEO_ENCODER_AV1_QUALITY_PRESET_SPEED = 100; ///< Fallback AMF enum value for av1 quality preset speed. + constexpr int AMF_VIDEO_ENCODER_AV1_QUALITY_PRESET_QUALITY = 30; ///< Fallback AMF enum value for av1 quality preset quality. + constexpr int AMF_VIDEO_ENCODER_AV1_QUALITY_PRESET_BALANCED = 70; ///< Fallback AMF enum value for av1 quality preset balanced. + constexpr int AMF_VIDEO_ENCODER_HEVC_QUALITY_PRESET_SPEED = 10; ///< Fallback AMF enum value for hevc quality preset speed. + constexpr int AMF_VIDEO_ENCODER_HEVC_QUALITY_PRESET_QUALITY = 0; ///< Fallback AMF enum value for hevc quality preset quality. + constexpr int AMF_VIDEO_ENCODER_HEVC_QUALITY_PRESET_BALANCED = 5; ///< Fallback AMF enum value for hevc quality preset balanced. + constexpr int AMF_VIDEO_ENCODER_QUALITY_PRESET_SPEED = 1; ///< Fallback AMF enum value for quality preset speed. + constexpr int AMF_VIDEO_ENCODER_QUALITY_PRESET_QUALITY = 2; ///< Fallback AMF enum value for quality preset quality. + constexpr int AMF_VIDEO_ENCODER_QUALITY_PRESET_BALANCED = 0; ///< Fallback AMF enum value for quality preset balanced. + constexpr int AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_CONSTANT_QP = 0; ///< Fallback AMF enum value for av1 rate control method constant qp. + constexpr int AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_CBR = 3; ///< Fallback AMF enum value for av1 rate control method cbr. + constexpr int AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_PEAK_CONSTRAINED_VBR = 2; ///< Fallback AMF enum value for av1 rate control method peak constrained vbr. + constexpr int AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_LATENCY_CONSTRAINED_VBR = 1; ///< Fallback AMF enum value for av1 rate control method latency constrained vbr. + constexpr int AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_CONSTANT_QP = 0; ///< Fallback AMF enum value for hevc rate control method constant qp. + constexpr int AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_CBR = 3; ///< Fallback AMF enum value for hevc rate control method cbr. + constexpr int AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_PEAK_CONSTRAINED_VBR = 2; ///< Fallback AMF enum value for hevc rate control method peak constrained vbr. + constexpr int AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_LATENCY_CONSTRAINED_VBR = 1; ///< Fallback AMF enum value for hevc rate control method latency constrained vbr. + constexpr int AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_CONSTANT_QP = 0; ///< Fallback AMF enum value for rate control method constant qp. + constexpr int AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_CBR = 1; ///< Fallback AMF enum value for rate control method cbr. + constexpr int AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_PEAK_CONSTRAINED_VBR = 2; ///< Fallback AMF enum value for rate control method peak constrained vbr. + constexpr int AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_LATENCY_CONSTRAINED_VBR = 3; ///< Fallback AMF enum value for rate control method latency constrained vbr. + constexpr int AMF_VIDEO_ENCODER_AV1_USAGE_TRANSCODING = 0; ///< Fallback AMF enum value for av1 usage transcoding. + constexpr int AMF_VIDEO_ENCODER_AV1_USAGE_LOW_LATENCY = 1; ///< Fallback AMF enum value for av1 usage low latency. + constexpr int AMF_VIDEO_ENCODER_AV1_USAGE_ULTRA_LOW_LATENCY = 2; ///< Fallback AMF enum value for av1 usage ultra low latency. + constexpr int AMF_VIDEO_ENCODER_AV1_USAGE_WEBCAM = 3; ///< Fallback AMF enum value for av1 usage webcam. + constexpr int AMF_VIDEO_ENCODER_AV1_USAGE_LOW_LATENCY_HIGH_QUALITY = 5; ///< Fallback AMF enum value for av1 usage low latency high quality. + constexpr int AMF_VIDEO_ENCODER_HEVC_USAGE_TRANSCODING = 0; ///< Fallback AMF enum value for hevc usage transcoding. + constexpr int AMF_VIDEO_ENCODER_HEVC_USAGE_ULTRA_LOW_LATENCY = 1; ///< Fallback AMF enum value for hevc usage ultra low latency. + constexpr int AMF_VIDEO_ENCODER_HEVC_USAGE_LOW_LATENCY = 2; ///< Fallback AMF enum value for hevc usage low latency. + constexpr int AMF_VIDEO_ENCODER_HEVC_USAGE_WEBCAM = 3; ///< Fallback AMF enum value for hevc usage webcam. + constexpr int AMF_VIDEO_ENCODER_HEVC_USAGE_LOW_LATENCY_HIGH_QUALITY = 5; ///< Fallback AMF enum value for hevc usage low latency high quality. + constexpr int AMF_VIDEO_ENCODER_USAGE_TRANSCODING = 0; ///< Fallback AMF enum value for usage transcoding. + constexpr int AMF_VIDEO_ENCODER_USAGE_ULTRA_LOW_LATENCY = 1; ///< Fallback AMF enum value for usage ultra low latency. + constexpr int AMF_VIDEO_ENCODER_USAGE_LOW_LATENCY = 2; ///< Fallback AMF enum value for usage low latency. + constexpr int AMF_VIDEO_ENCODER_USAGE_WEBCAM = 3; ///< Fallback AMF enum value for usage webcam. + constexpr int AMF_VIDEO_ENCODER_USAGE_LOW_LATENCY_HIGH_QUALITY = 5; ///< Fallback AMF enum value for usage low latency high quality. + constexpr int AMF_VIDEO_ENCODER_UNDEFINED = 0; ///< Fallback AMF enum value for undefined. + constexpr int AMF_VIDEO_ENCODER_CABAC = 1; ///< Fallback AMF enum value for cabac. + constexpr int AMF_VIDEO_ENCODER_CALV = 2; ///< Fallback AMF enum value for calv. #else #ifdef _GLIBCXX_USE_C99_INTTYPES #undef _GLIBCXX_USE_C99_INTTYPES @@ -132,24 +144,36 @@ namespace config { #include #endif + /** + * @brief Enumerates supported quality AV1 options. + */ enum class quality_av1_e : int { speed = AMF_VIDEO_ENCODER_AV1_QUALITY_PRESET_SPEED, ///< Speed preset quality = AMF_VIDEO_ENCODER_AV1_QUALITY_PRESET_QUALITY, ///< Quality preset balanced = AMF_VIDEO_ENCODER_AV1_QUALITY_PRESET_BALANCED ///< Balanced preset }; + /** + * @brief Enumerates supported quality HEVC options. + */ enum class quality_hevc_e : int { speed = AMF_VIDEO_ENCODER_HEVC_QUALITY_PRESET_SPEED, ///< Speed preset quality = AMF_VIDEO_ENCODER_HEVC_QUALITY_PRESET_QUALITY, ///< Quality preset balanced = AMF_VIDEO_ENCODER_HEVC_QUALITY_PRESET_BALANCED ///< Balanced preset }; + /** + * @brief Enumerates supported quality h264 options. + */ enum class quality_h264_e : int { speed = AMF_VIDEO_ENCODER_QUALITY_PRESET_SPEED, ///< Speed preset quality = AMF_VIDEO_ENCODER_QUALITY_PRESET_QUALITY, ///< Quality preset balanced = AMF_VIDEO_ENCODER_QUALITY_PRESET_BALANCED ///< Balanced preset }; + /** + * @brief Enumerates supported rc AV1 options. + */ enum class rc_av1_e : int { cbr = AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_CBR, ///< CBR cqp = AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_CONSTANT_QP, ///< CQP @@ -157,6 +181,9 @@ namespace config { vbr_peak = AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_PEAK_CONSTRAINED_VBR ///< VBR with peak constraints }; + /** + * @brief Enumerates supported rc HEVC options. + */ enum class rc_hevc_e : int { cbr = AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_CBR, ///< CBR cqp = AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_CONSTANT_QP, ///< CQP @@ -164,6 +191,9 @@ namespace config { vbr_peak = AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_PEAK_CONSTRAINED_VBR ///< VBR with peak constraints }; + /** + * @brief Enumerates supported rc h264 options. + */ enum class rc_h264_e : int { cbr = AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_CBR, ///< CBR cqp = AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_CONSTANT_QP, ///< CQP @@ -171,6 +201,9 @@ namespace config { vbr_peak = AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_PEAK_CONSTRAINED_VBR ///< VBR with peak constraints }; + /** + * @brief Enumerates supported usage AV1 options. + */ enum class usage_av1_e : int { transcoding = AMF_VIDEO_ENCODER_AV1_USAGE_TRANSCODING, ///< Transcoding preset webcam = AMF_VIDEO_ENCODER_AV1_USAGE_WEBCAM, ///< Webcam preset @@ -179,6 +212,9 @@ namespace config { ultralowlatency = AMF_VIDEO_ENCODER_AV1_USAGE_ULTRA_LOW_LATENCY ///< Ultra low latency preset }; + /** + * @brief Enumerates supported usage HEVC options. + */ enum class usage_hevc_e : int { transcoding = AMF_VIDEO_ENCODER_HEVC_USAGE_TRANSCODING, ///< Transcoding preset webcam = AMF_VIDEO_ENCODER_HEVC_USAGE_WEBCAM, ///< Webcam preset @@ -187,6 +223,9 @@ namespace config { ultralowlatency = AMF_VIDEO_ENCODER_HEVC_USAGE_ULTRA_LOW_LATENCY ///< Ultra low latency preset }; + /** + * @brief Enumerates supported usage h264 options. + */ enum class usage_h264_e : int { transcoding = AMF_VIDEO_ENCODER_USAGE_TRANSCODING, ///< Transcoding preset webcam = AMF_VIDEO_ENCODER_USAGE_WEBCAM, ///< Webcam preset @@ -195,17 +234,29 @@ namespace config { ultralowlatency = AMF_VIDEO_ENCODER_USAGE_ULTRA_LOW_LATENCY ///< Ultra low latency preset }; + /** + * @brief Enumerates supported coder options. + */ enum coder_e : int { _auto = AMF_VIDEO_ENCODER_UNDEFINED, ///< Auto cabac = AMF_VIDEO_ENCODER_CABAC, ///< CABAC cavlc = AMF_VIDEO_ENCODER_CALV ///< CAVLC }; + /** + * @brief Parse an AMD quality preset while preserving the current value on invalid input. + * + * @param quality_type Configuration text naming the AMD quality preset. + * @param original Original text value used when reporting a parsing failure. + * @return Parsed enum value, or the setting-specific default when the text is unknown. + */ template ::std::optional quality_from_view(const ::std::string_view &quality_type, const ::std::optional(&original)) { -#define _CONVERT_(x) \ - if (quality_type == #x##sv) \ - return (int) T::x +#ifndef DOXYGEN + #define _CONVERT_(x) \ + if (quality_type == #x##sv) \ + return (int) T::x +#endif _CONVERT_(balanced); _CONVERT_(quality); _CONVERT_(speed); @@ -213,11 +264,20 @@ namespace config { return original; } + /** + * @brief Parse an AMD rate-control mode while preserving the current value on invalid input. + * + * @param rc Rate-control mode selected in the configuration. + * @param original Original text value used when reporting a parsing failure. + * @return Parsed enum value, or the setting-specific default when the text is unknown. + */ template ::std::optional rc_from_view(const ::std::string_view &rc, const ::std::optional(&original)) { -#define _CONVERT_(x) \ - if (rc == #x##sv) \ - return (int) T::x +#ifndef DOXYGEN + #define _CONVERT_(x) \ + if (rc == #x##sv) \ + return (int) T::x +#endif _CONVERT_(cbr); _CONVERT_(cqp); _CONVERT_(vbr_latency); @@ -226,11 +286,20 @@ namespace config { return original; } + /** + * @brief Parse an AMD encoder usage mode while preserving the current value on invalid input. + * + * @param usage Encoder usage mode selected in the configuration. + * @param original Original text value used when reporting a parsing failure. + * @return Parsed enum value, or the setting-specific default when the text is unknown. + */ template ::std::optional usage_from_view(const ::std::string_view &usage, const ::std::optional(&original)) { -#define _CONVERT_(x) \ - if (usage == #x##sv) \ - return (int) T::x +#ifndef DOXYGEN + #define _CONVERT_(x) \ + if (usage == #x##sv) \ + return (int) T::x +#endif _CONVERT_(lowlatency); _CONVERT_(lowlatency_high_quality); _CONVERT_(transcoding); @@ -240,6 +309,12 @@ namespace config { return original; } + /** + * @brief Parse an entropy-coder mode from configuration text. + * + * @param coder Entropy-coder mode selected in the configuration. + * @return Parsed enum value, or the setting-specific default when the text is unknown. + */ int coder_from_view(const ::std::string_view &coder) { if (coder == "auto"sv) { return _auto; @@ -256,6 +331,9 @@ namespace config { } // namespace amd namespace qsv { + /** + * @brief Enumerates supported preset options. + */ enum preset_e : int { veryslow = 1, ///< veryslow preset slower = 2, ///< slower preset @@ -266,16 +344,27 @@ namespace config { veryfast = 7 ///< veryfast preset }; + /** + * @brief Enumerates supported cavlc options. + */ enum cavlc_e : int { _auto = false, ///< Auto enabled = true, ///< Enabled disabled = false ///< Disabled }; + /** + * @brief Parse a QSV encoder preset from configuration text. + * + * @param preset Encoder preset value supplied by the configuration. + * @return Parsed enum value, or the setting-specific default when the text is unknown. + */ std::optional preset_from_view(const std::string_view &preset) { -#define _CONVERT_(x) \ - if (preset == #x##sv) \ - return x +#ifndef DOXYGEN + #define _CONVERT_(x) \ + if (preset == #x##sv) \ + return x +#endif _CONVERT_(veryslow); _CONVERT_(slower); _CONVERT_(slow); @@ -287,6 +376,12 @@ namespace config { return std::nullopt; } + /** + * @brief Parse an entropy-coder mode from configuration text. + * + * @param coder Entropy-coder mode selected in the configuration. + * @return Parsed enum value, or the setting-specific default when the text is unknown. + */ std::optional coder_from_view(const std::string_view &coder) { if (coder == "auto"sv) { return _auto; @@ -304,12 +399,21 @@ namespace config { namespace vt { + /** + * @brief Enumerates supported coder options. + */ enum coder_e : int { _auto = 0, ///< Auto cabac, ///< CABAC cavlc ///< CAVLC }; + /** + * @brief Parse an entropy-coder mode from configuration text. + * + * @param coder Entropy-coder mode selected in the configuration. + * @return Parsed enum value, or the setting-specific default when the text is unknown. + */ int coder_from_view(const std::string_view &coder) { if (coder == "auto"sv) { return _auto; @@ -324,6 +428,12 @@ namespace config { return -1; } + /** + * @brief Parse whether VideoToolbox software encoding is allowed. + * + * @param software Whether the software encoder path is being configured. + * @return Parsed enum value, or the setting-specific default when the text is unknown. + */ int allow_software_from_view(const std::string_view &software) { if (software == "allowed"sv || software == "forced") { return 1; @@ -332,6 +442,12 @@ namespace config { return 0; } + /** + * @brief Parse whether VideoToolbox software encoding is forced. + * + * @param software Whether the software encoder path is being configured. + * @return Parsed enum value, or the setting-specific default when the text is unknown. + */ int force_software_from_view(const std::string_view &software) { if (software == "forced") { return 1; @@ -340,6 +456,12 @@ namespace config { return 0; } + /** + * @brief Parse the VideoToolbox realtime encoder flag. + * + * @param rt Real-time encoder usage selector. + * @return Parsed enum value, or the setting-specific default when the text is unknown. + */ int rt_from_view(const std::string_view &rt) { if (rt == "disabled" || rt == "off" || rt == "0") { return 0; @@ -351,10 +473,18 @@ namespace config { } // namespace vt namespace sw { + /** + * @brief Parse an SVT-AV1 speed preset from configuration text. + * + * @param preset Encoder preset value supplied by the configuration. + * @return Parsed enum value, or the setting-specific default when the text is unknown. + */ int svtav1_preset_from_view(const std::string_view &preset) { -#define _CONVERT_(x, y) \ - if (preset == #x##sv) \ - return y +#ifndef DOXYGEN + #define _CONVERT_(x, y) \ + if (preset == #x##sv) \ + return y +#endif _CONVERT_(veryslow, 1); _CONVERT_(slower, 2); _CONVERT_(slow, 4); @@ -370,10 +500,18 @@ namespace config { } // namespace sw namespace dd { + /** + * @brief Parse display-device preparation mode from configuration text. + * + * @param value Configuration text from the display-device preparation setting. + * @return Parsed enum value, or the setting-specific default when the text is unknown. + */ video_t::dd_t::config_option_e config_option_from_view(const std::string_view value) { -#define _CONVERT_(x) \ - if (value == #x##sv) \ - return video_t::dd_t::config_option_e::x +#ifndef DOXYGEN + #define _CONVERT_(x) \ + if (value == #x##sv) \ + return video_t::dd_t::config_option_e::x +#endif _CONVERT_(disabled); _CONVERT_(verify_only); _CONVERT_(ensure_active); @@ -383,11 +521,19 @@ namespace config { return video_t::dd_t::config_option_e::disabled; // Default to this if value is invalid } + /** + * @brief Parse display-device resolution mode from configuration text. + * + * @param value Configuration text from the display-device resolution setting. + * @return Parsed enum value, or the setting-specific default when the text is unknown. + */ video_t::dd_t::resolution_option_e resolution_option_from_view(const std::string_view value) { -#define _CONVERT_2_ARG_(str, val) \ - if (value == #str##sv) \ - return video_t::dd_t::resolution_option_e::val -#define _CONVERT_(x) _CONVERT_2_ARG_(x, x) +#ifndef DOXYGEN + #define _CONVERT_2_ARG_(str, val) \ + if (value == #str##sv) \ + return video_t::dd_t::resolution_option_e::val + #define _CONVERT_(x) _CONVERT_2_ARG_(x, x) +#endif _CONVERT_(disabled); _CONVERT_2_ARG_(auto, automatic); _CONVERT_(manual); @@ -396,11 +542,19 @@ namespace config { return video_t::dd_t::resolution_option_e::disabled; // Default to this if value is invalid } + /** + * @brief Parse display-device refresh-rate mode from configuration text. + * + * @param value Configuration text from the display-device refresh-rate setting. + * @return Parsed enum value, or the setting-specific default when the text is unknown. + */ video_t::dd_t::refresh_rate_option_e refresh_rate_option_from_view(const std::string_view value) { -#define _CONVERT_2_ARG_(str, val) \ - if (value == #str##sv) \ - return video_t::dd_t::refresh_rate_option_e::val -#define _CONVERT_(x) _CONVERT_2_ARG_(x, x) +#ifndef DOXYGEN + #define _CONVERT_2_ARG_(str, val) \ + if (value == #str##sv) \ + return video_t::dd_t::refresh_rate_option_e::val + #define _CONVERT_(x) _CONVERT_2_ARG_(x, x) +#endif _CONVERT_(disabled); _CONVERT_2_ARG_(auto, automatic); _CONVERT_(manual); @@ -409,11 +563,19 @@ namespace config { return video_t::dd_t::refresh_rate_option_e::disabled; // Default to this if value is invalid } + /** + * @brief Parse display-device HDR mode from configuration text. + * + * @param value Configuration text from the display-device HDR setting. + * @return Parsed enum value, or the setting-specific default when the text is unknown. + */ video_t::dd_t::hdr_option_e hdr_option_from_view(const std::string_view value) { -#define _CONVERT_2_ARG_(str, val) \ - if (value == #str##sv) \ - return video_t::dd_t::hdr_option_e::val -#define _CONVERT_(x) _CONVERT_2_ARG_(x, x) +#ifndef DOXYGEN + #define _CONVERT_2_ARG_(str, val) \ + if (value == #str##sv) \ + return video_t::dd_t::hdr_option_e::val + #define _CONVERT_(x) _CONVERT_2_ARG_(x, x) +#endif _CONVERT_(disabled); _CONVERT_2_ARG_(auto, automatic); #undef _CONVERT_ @@ -421,6 +583,12 @@ namespace config { return video_t::dd_t::hdr_option_e::disabled; // Default to this if value is invalid } + /** + * @brief Parse display-mode remapping rules from JSON configuration text. + * + * @param value JSON array text from the display-device mode-remapping setting. + * @return Parsed enum value, or the setting-specific default when the text is unknown. + */ video_t::dd_t::mode_remapping_t mode_remapping_from_view(const std::string_view value) { const auto parse_entry_list {[](const auto &entry_list, auto &output_field) { for (auto &[_, entry] : entry_list) { @@ -449,6 +617,9 @@ namespace config { } } // namespace dd + /** + * @brief Default video configuration values used before file and CLI overrides. + */ video_t video { 28, // qp @@ -528,6 +699,9 @@ namespace config { 0 // minimum_fps_target (0 = framerate) }; + /** + * @brief Default audio configuration values used before file and CLI overrides. + */ audio_t audio { {}, // audio_sink {}, // virtual_sink @@ -535,6 +709,9 @@ namespace config { true, // install_steam_drivers }; + /** + * @brief Default stream configuration values used before file and CLI overrides. + */ stream_t stream { 10s, // ping_timeout @@ -547,6 +724,9 @@ namespace config { 0, // packetsize }; + /** + * @brief Default NVHTTP server configuration values used before file and CLI overrides. + */ nvhttp_t nvhttp { "lan", // origin web manager @@ -558,6 +738,9 @@ namespace config { {}, // external_ip }; + /** + * @brief Default input configuration values used before file and CLI overrides. + */ input_t input { { {0x10, 0xA0}, @@ -585,6 +768,9 @@ namespace config { true, // native pen/touch support }; + /** + * @brief Default top-level Sunshine configuration values used before file and CLI overrides. + */ sunshine_t sunshine { "en", // locale 2, // min_log_level @@ -604,18 +790,43 @@ namespace config { {}, // prep commands }; + /** + * @brief Return whether a character terminates a configuration line. + * + * @param ch Character currently being classified by the parser. + * @return True when the tested parser condition is met. + */ bool endline(char ch) { return ch == '\r' || ch == '\n'; } + /** + * @brief Return whether a character is horizontal parser whitespace. + * + * @param ch Character currently being classified by the parser. + * @return True when the tested parser condition is met. + */ bool space_tab(char ch) { return ch == ' ' || ch == '\t'; } + /** + * @brief Return whether a character should be treated as parser whitespace. + * + * @param ch Character currently being classified by the parser. + * @return True when the tested parser condition is met. + */ bool whitespace(char ch) { return space_tab(ch) || endline(ch); } + /** + * @brief Copy a configuration text range while stripping inline comments. + * + * @param begin Iterator or pointer marking the start of the input range. + * @param end Iterator or pointer marking the end of the input range. + * @return Value converted to string. + */ std::string to_string(const char *begin, const char *end) { std::string result; @@ -631,6 +842,13 @@ namespace config { return result; } + /** + * @brief Advance over a bracketed list while honoring nested brackets. + * + * @param skipper Function used to skip characters while parsing. + * @param end Iterator or pointer marking the end of the input range. + * @return Iterator positioned after the matching closing bracket or at the end. + */ template It skip_list(It skipper, It end) { int stack = 1; @@ -651,6 +869,13 @@ namespace config { std::pair< std::string_view::const_iterator, std::optional>> + /** + * @brief Parse one `name = value` configuration entry. + * + * @param begin Iterator or pointer marking the start of the input range. + * @param end Iterator or pointer marking the end of the input range. + * @return Iterator for the next line and the parsed key-value pair when one was found. + */ parse_option(std::string_view::const_iterator begin, std::string_view::const_iterator end) { begin = std::find_if_not(begin, end, whitespace); auto endl = std::find_if(begin, end, endline); @@ -689,6 +914,9 @@ namespace config { ); } + /** + * @brief Parse Sunshine configuration text into key-value entries. + */ std::unordered_map parse_config(const std::string_view &file_content) { std::unordered_map vars; @@ -714,6 +942,13 @@ namespace config { return vars; } + /** + * @brief Consume a string setting from the parsed configuration map. + * + * @param vars Parsed configuration entries; consumed keys are erased. + * @param name Configuration key to consume. + * @param input Destination field updated when the setting exists and parses successfully. + */ void string_f(std::unordered_map &vars, const std::string &name, std::string &input) { auto it = vars.find(name); if (it == std::end(vars)) { @@ -725,6 +960,14 @@ namespace config { vars.erase(it); } + /** + * @brief Consume a setting and convert it with a caller-provided parser. + * + * @param vars Parsed configuration entries; consumed keys are erased. + * @param name Configuration key to consume. + * @param input Destination field updated when the setting exists and parses successfully. + * @param f Converter applied to the raw configuration string. + */ template void generic_f(std::unordered_map &vars, const std::string &name, T &input, F &&f) { std::string tmp; @@ -734,6 +977,14 @@ namespace config { } } + /** + * @brief Consume a string setting only when it matches an allowed value. + * + * @param vars Parsed configuration entries; consumed keys are erased. + * @param name Configuration key to consume. + * @param input Destination field updated when the setting exists and parses successfully. + * @param allowed_vals Accepted string values for this setting. + */ void string_restricted_f(std::unordered_map &vars, const std::string &name, std::string &input, const std::vector &allowed_vals) { std::string temp; string_f(vars, name, temp); @@ -746,6 +997,13 @@ namespace config { } } + /** + * @brief Parse a comma-separated string setting into a list. + * + * @param vars Configuration key-value map. + * @param name Setting name. + * @param output Parsed string list. + */ void string_list_f(std::unordered_map &vars, const std::string &name, std::vector &output) { // NOSONAR(cpp:S6045) - transparent hasher not available for unordered_map in this codebase std::string temp; string_f(vars, name, temp); @@ -767,6 +1025,13 @@ namespace config { } } + /** + * @brief Consume a path setting and normalize it under the app data directory when relative. + * + * @param vars Parsed configuration entries; consumed keys are erased. + * @param name Configuration key to consume. + * @param input Destination field updated when the setting exists and parses successfully. + */ void path_f(std::unordered_map &vars, const std::string &name, fs::path &input) { // appdata needs to be retrieved once only static auto appdata = platf::appdata(); @@ -791,6 +1056,13 @@ namespace config { } } + /** + * @brief Consume a path setting and normalize it under the app data directory when relative. + * + * @param vars Parsed configuration entries; consumed keys are erased. + * @param name Configuration key to consume. + * @param input Destination field updated when the setting exists and parses successfully. + */ void path_f(std::unordered_map &vars, const std::string &name, std::string &input) { fs::path temp = input; @@ -799,6 +1071,13 @@ namespace config { input = temp.string(); } + /** + * @brief Consume an integer setting from decimal or hexadecimal configuration text. + * + * @param vars Parsed configuration entries; consumed keys are erased. + * @param name Configuration key to consume. + * @param input Destination field updated when the setting exists and parses successfully. + */ void int_f(std::unordered_map &vars, const std::string &name, int &input) { auto it = vars.find(name); @@ -823,6 +1102,13 @@ namespace config { vars.erase(it); } + /** + * @brief Consume an integer setting from decimal or hexadecimal configuration text. + * + * @param vars Parsed configuration entries; consumed keys are erased. + * @param name Configuration key to consume. + * @param input Destination field updated when the setting exists and parses successfully. + */ void int_f(std::unordered_map &vars, const std::string &name, std::optional &input) { auto it = vars.find(name); @@ -847,6 +1133,14 @@ namespace config { vars.erase(it); } + /** + * @brief Consume an integer setting from decimal or hexadecimal configuration text. + * + * @param vars Parsed configuration entries; consumed keys are erased. + * @param name Configuration key to consume. + * @param input Destination field updated when the setting exists and parses successfully. + * @param f Converter applied to the raw configuration string. + */ template void int_f(std::unordered_map &vars, const std::string &name, int &input, F &&f) { std::string tmp; @@ -856,6 +1150,14 @@ namespace config { } } + /** + * @brief Consume an integer setting from decimal or hexadecimal configuration text. + * + * @param vars Parsed configuration entries; consumed keys are erased. + * @param name Configuration key to consume. + * @param input Destination field updated when the setting exists and parses successfully. + * @param f Converter applied to the raw configuration string. + */ template void int_f(std::unordered_map &vars, const std::string &name, std::optional &input, F &&f) { std::string tmp; @@ -865,6 +1167,14 @@ namespace config { } } + /** + * @brief Consume an integer setting only when it falls inside an inclusive range. + * + * @param vars Parsed configuration entries; consumed keys are erased. + * @param name Configuration key to consume. + * @param input Destination field updated when the setting exists and parses successfully. + * @param range Inclusive range accepted for the parsed value. + */ void int_between_f(std::unordered_map &vars, const std::string &name, int &input, const std::pair &range) { int temp = input; @@ -876,6 +1186,12 @@ namespace config { } } + /** + * @brief Convert common textual boolean forms to a boolean value. + * + * @param boolean Configuration string to classify as enabled or disabled. + * @return True when the tested parser condition is met. + */ bool to_bool(std::string &boolean) { std::for_each(std::begin(boolean), std::end(boolean), [](char ch) { return (char) std::tolower(ch); @@ -889,6 +1205,13 @@ namespace config { (std::find(std::begin(boolean), std::end(boolean), '1') != std::end(boolean)); } + /** + * @brief Consume a boolean setting from the parsed configuration map. + * + * @param vars Parsed configuration entries; consumed keys are erased. + * @param name Configuration key to consume. + * @param input Destination field updated when the setting exists and parses successfully. + */ void bool_f(std::unordered_map &vars, const std::string &name, bool &input) { std::string tmp; string_f(vars, name, tmp); @@ -900,6 +1223,13 @@ namespace config { input = to_bool(tmp); } + /** + * @brief Consume a floating-point setting from the parsed configuration map. + * + * @param vars Parsed configuration entries; consumed keys are erased. + * @param name Configuration key to consume. + * @param input Destination field updated when the setting exists and parses successfully. + */ void double_f(std::unordered_map &vars, const std::string &name, double &input) { std::string tmp; string_f(vars, name, tmp); @@ -918,6 +1248,14 @@ namespace config { input = val; } + /** + * @brief Consume a floating-point setting only when it falls inside an inclusive range. + * + * @param vars Parsed configuration entries; consumed keys are erased. + * @param name Configuration key to consume. + * @param input Destination field updated when the setting exists and parses successfully. + * @param range Inclusive range accepted for the parsed value. + */ void double_between_f(std::unordered_map &vars, const std::string &name, double &input, const std::pair &range) { double temp = input; @@ -929,6 +1267,13 @@ namespace config { } } + /** + * @brief Consume a comma-separated or bracketed string list setting. + * + * @param vars Parsed configuration entries; consumed keys are erased. + * @param name Configuration key to consume. + * @param input Destination field updated when the setting exists and parses successfully. + */ void list_string_f(std::unordered_map &vars, const std::string &name, std::vector &input) { std::string string; string_f(vars, name, string); @@ -968,6 +1313,13 @@ namespace config { } } + /** + * @brief Consume the JSON preparation-command list setting. + * + * @param vars Parsed configuration entries; consumed keys are erased. + * @param name Configuration key to consume. + * @param input Destination field updated when the setting exists and parses successfully. + */ void list_prep_cmd_f(std::unordered_map &vars, const std::string &name, std::vector &input) { std::string string; string_f(vars, name, string); @@ -994,6 +1346,13 @@ namespace config { } } + /** + * @brief Consume an integer list setting from decimal or hexadecimal configuration text. + * + * @param vars Parsed configuration entries; consumed keys are erased. + * @param name Configuration key to consume. + * @param input Destination field updated when the setting exists and parses successfully. + */ void list_int_f(std::unordered_map &vars, const std::string &name, std::vector &input) { std::vector list; list_string_f(vars, name, list); @@ -1027,6 +1386,13 @@ namespace config { } } + /** + * @brief Consume an integer-pair list into a mapping table. + * + * @param vars Parsed configuration entries; consumed keys are erased. + * @param name Configuration key to consume. + * @param input Destination field updated when the setting exists and parses successfully. + */ void map_int_int_f(std::unordered_map &vars, const std::string &name, std::unordered_map &input) { std::vector list; list_int_f(vars, name, list); @@ -1046,6 +1412,12 @@ namespace config { } } + /** + * @brief Apply single-character command-line flags to the global Sunshine flags bitset. + * + * @param line Configuration line being parsed. + * @return 0 when all flags are recognized; -1 when at least one flag is unknown. + */ int apply_flags(const char *line) { int ret = 0; while (*line != '\0') { @@ -1073,6 +1445,11 @@ namespace config { return ret; } + /** + * @brief Get supported gamepad options. + * + * @return Platform-supported gamepad backend names accepted by configuration. + */ std::vector &get_supported_gamepad_options() { const auto options = platf::supported_gamepads(nullptr); static std::vector opts {}; @@ -1083,6 +1460,9 @@ namespace config { return opts; } + /** + * @brief Log parsed configuration entries and optionally record them as modified. + */ void log_config_settings(const std::unordered_map &vars, bool save) { for (auto &[name, val] : vars) { bool is_redacted = std::ranges::find(config::redacted_config, name) != config::redacted_config.end(); @@ -1095,6 +1475,11 @@ namespace config { } } + /** + * @brief Apply parsed configuration entries to the global runtime configuration. + * + * @param vars Parsed configuration entries; consumed keys are erased. + */ void apply_config(std::unordered_map &&vars) { log_config_settings(vars, true); @@ -1406,6 +1791,9 @@ namespace config { } } + /** + * @brief Parse serialized text into the corresponding runtime representation. + */ int parse(int argc, char *argv[]) { std::unordered_map cmd_vars; #ifdef _WIN32 diff --git a/src/config.h b/src/config.h index f074de579..cbe0978ed 100644 --- a/src/config.h +++ b/src/config.h @@ -17,40 +17,55 @@ namespace config { // Valid range for the packetsize limit - constexpr int PACKETSIZE_MIN = 200; - constexpr int PACKETSIZE_MAX = 65535; - constexpr int PACKETSIZE_SMALL = 500; - constexpr int PACKETSIZE_LARGE = 1456; + constexpr int PACKETSIZE_MIN = 200; ///< Lowest accepted configured packet size in bytes. + constexpr int PACKETSIZE_MAX = 65535; ///< Highest accepted configured packet size in bytes. + constexpr int PACKETSIZE_SMALL = 500; ///< Conservative packet size used for low-MTU links. + constexpr int PACKETSIZE_LARGE = 1456; ///< Default large packet size that avoids common MTU fragmentation. // track modified config options - inline std::unordered_map modified_config_settings; + inline std::unordered_map modified_config_settings; ///< Configuration keys changed during the current parse or UI update. // sensitive values that should be redacted from logging + /** + * @brief Configuration keys whose values must be hidden in logs. + */ inline constexpr std::array redacted_config = { "csrf_allowed_origins" }; + /** + * @brief Log configuration entries and optionally mark them for persistence. + * + * @param vars Parsed configuration entries to log. + * @param save Whether modified configuration values should be written back to disk. + */ void log_config_settings(const std::unordered_map &vars, bool save); + /** + * @brief Video encoder, capture, and color settings loaded from configuration. + */ struct video_t { // ffmpeg params + /** + * @brief Quantization parameter used by encoders where higher values trade quality for compression. + */ int qp; // higher == more compression and less quality - int hevc_mode; - int av1_mode; + int hevc_mode; ///< HEVC support mode advertised to clients. + int av1_mode; ///< AV1 support mode advertised to clients. - int min_threads; // Minimum number of threads/slices for CPU encoding + int min_threads; ///< Minimum number of threads or slices for CPU encoding. struct { std::string sw_preset; std::string sw_tune; std::optional svtav1_preset; - } sw; + } sw; ///< Software encoder options. - nvenc::nvenc_config nv; - bool nv_realtime_hags; - bool nv_opengl_vulkan_on_dxgi; - bool nv_sunshine_high_power_mode; + nvenc::nvenc_config nv; ///< NVIDIA NVENC encoder settings. + bool nv_realtime_hags; ///< Enable the NVIDIA realtime HAGS workaround. + bool nv_opengl_vulkan_on_dxgi; ///< Prefer NVIDIA OpenGL/Vulkan-on-DXGI interop. + bool nv_sunshine_high_power_mode; ///< Request NVIDIA high-power mode for Sunshine. struct { int preset; @@ -58,13 +73,13 @@ namespace config { int h264_coder; int aq; int vbv_percentage_increase; - } nv_legacy; + } nv_legacy; ///< Legacy NVIDIA encoder options kept for config compatibility. struct { std::optional qsv_preset; std::optional qsv_cavlc; bool qsv_slow_hevc; - } qsv; + } qsv; ///< Intel Quick Sync encoder options. struct { std::optional amd_usage_h264; @@ -80,34 +95,43 @@ namespace config { std::optional amd_preanalysis; std::optional amd_vbaq; int amd_coder; - } amd; + } amd; ///< AMD AMF encoder options. struct { int vt_allow_sw; int vt_require_sw; int vt_realtime; int vt_coder; - } vt; + } vt; ///< VideoToolbox encoder options. struct { bool strict_rc_buffer; - } vaapi; + } vaapi; ///< VA-API encoder options. struct { int tune; // 0=default, 1=hq, 2=ll, 3=ull, 4=lossless int rc_mode; // 0=driver, 1=cqp, 2=cbr, 4=vbr - } vk; + } vk; ///< Vulkan encoder options. - std::string capture; - std::string encoder; - std::string adapter_name; - std::string output_name; + std::string capture; ///< Capture backend name selected by configuration. + std::string encoder; ///< Encoder backend name selected by configuration. + std::string adapter_name; ///< Display adapter name selected in configuration. + std::string output_name; ///< Display output name selected in configuration. + /** + * @brief Display-device integration settings. + */ struct dd_t { + /** + * @brief Compatibility workarounds for display-device control. + */ struct workarounds_t { std::chrono::milliseconds hdr_toggle_delay; ///< Specify whether to apply HDR high-contrast color workaround and what delay to use. }; + /** + * @brief Selects how Sunshine prepares the active display before streaming. + */ enum class config_option_e { disabled, ///< Disable the configuration for the device. verify_only, ///< @seealso{display_device::SingleDisplayConfiguration::DevicePreparation} @@ -116,52 +140,70 @@ namespace config { ensure_only_display ///< @seealso{display_device::SingleDisplayConfiguration::DevicePreparation} }; + /** + * @brief Selects how Sunshine chooses the stream display resolution. + */ enum class resolution_option_e { disabled, ///< Do not change resolution. automatic, ///< Change resolution and use the one received from Moonlight. manual ///< Change resolution and use the manually provided one. }; + /** + * @brief Selects how Sunshine chooses the stream display refresh rate. + */ enum class refresh_rate_option_e { disabled, ///< Do not change refresh rate. automatic, ///< Change refresh rate and use the one received from Moonlight. manual ///< Change refresh rate and use the manually provided one. }; + /** + * @brief Selects how Sunshine handles HDR state for the stream display. + */ enum class hdr_option_e { disabled, ///< Do not change HDR settings. automatic ///< Change HDR settings and use the state requested by Moonlight. }; + /** + * @brief Single display mode remapping rule from configuration. + */ struct mode_remapping_entry_t { - std::string requested_resolution; - std::string requested_fps; - std::string final_resolution; - std::string final_refresh_rate; + std::string requested_resolution; ///< Resolution string requested by the client. + std::string requested_fps; ///< Refresh-rate string requested by the client. + std::string final_resolution; ///< Resolution string to apply after remapping. + std::string final_refresh_rate; ///< Refresh-rate string to apply after remapping. }; + /** + * @brief Collection of display mode remapping rules. + */ struct mode_remapping_t { std::vector mixed; ///< To be used when `resolution_option` and `refresh_rate_option` is set to `automatic`. std::vector resolution_only; ///< To be use when only `resolution_option` is set to `automatic`. std::vector refresh_rate_only; ///< To be use when only `refresh_rate_option` is set to `automatic`. }; - config_option_e configuration_option; - resolution_option_e resolution_option; + config_option_e configuration_option; ///< Display-preparation mode selected by configuration. + resolution_option_e resolution_option; ///< Resolution-selection mode selected by configuration. std::string manual_resolution; ///< Manual resolution in case `resolution_option == resolution_option_e::manual`. - refresh_rate_option_e refresh_rate_option; + refresh_rate_option_e refresh_rate_option; ///< Refresh-rate selection mode selected by configuration. std::string manual_refresh_rate; ///< Manual refresh rate in case `refresh_rate_option == refresh_rate_option_e::manual`. - hdr_option_e hdr_option; + hdr_option_e hdr_option; ///< HDR-selection mode selected by configuration. std::chrono::milliseconds config_revert_delay; ///< Time to wait until settings are reverted (after stream ends/app exists). bool config_revert_on_disconnect; ///< Specify whether to revert display configuration on client disconnect. - mode_remapping_t mode_remapping; - workarounds_t wa; - } dd; + mode_remapping_t mode_remapping; ///< Display mode remapping rules grouped by automatic selection mode. + workarounds_t wa; ///< Display-device compatibility workarounds. + } dd; ///< Display-device integration settings. - int max_bitrate; // Maximum bitrate, sets ceiling in kbps for bitrate requested from client + int max_bitrate; ///< Maximum bitrate ceiling in kbps for bitrate requested from the client. double minimum_fps_target; ///< Lowest framerate that will be used when streaming. Range 0-1000, 0 = half of client's requested framerate. }; + /** + * @brief Audio capture and encoder settings loaded from configuration. + */ struct audio_t { std::string sink; ///< Audio output device/sink to use for audio capture std::string virtual_sink; ///< Virtual audio sink for audio routing @@ -169,65 +211,86 @@ namespace config { bool install_steam_drivers; ///< Install Steam audio drivers for enhanced compatibility }; + /** + * @brief Encryption policy that always sends unencrypted video. + */ constexpr int ENCRYPTION_MODE_NEVER = 0; // Never use video encryption, even if the client supports it + /** + * @brief Encryption policy that uses encrypted video only when the client supports it. + */ constexpr int ENCRYPTION_MODE_OPPORTUNISTIC = 1; // Use video encryption if available, but stream without it if not supported + /** + * @brief Encryption policy that rejects clients without video encryption support. + */ constexpr int ENCRYPTION_MODE_MANDATORY = 2; // Always use video encryption and refuse clients that can't encrypt + /** + * @brief Network stream settings shared by audio, video, and control channels. + */ struct stream_t { - std::chrono::milliseconds ping_timeout; + std::chrono::milliseconds ping_timeout; ///< Timeout used when waiting for client ping responses. - std::string file_apps; + std::string file_apps; ///< Path to the configured applications file. - int fec_percentage; + int fec_percentage; ///< Percentage of forward-error-correction packets to add to the stream. // Video encryption settings for LAN and WAN streams - int lan_encryption_mode; - int wan_encryption_mode; + int lan_encryption_mode; ///< Video encryption policy for LAN clients. + int wan_encryption_mode; ///< Video encryption policy for WAN clients. // Limit the packetsize to avoid fragmentation on a low MTU link - int packetsize; + int packetsize; ///< Maximum payload size for network packets. }; + /** + * @brief HTTP and HTTPS settings used by the GameStream pairing server. + */ struct nvhttp_t { // Could be any of the following values: // pc|lan|wan - std::string origin_web_ui_allowed; + std::string origin_web_ui_allowed; ///< Origin policy used for Web UI access checks. - std::string pkey; - std::string cert; + std::string pkey; ///< Private key PEM string or path. + std::string cert; ///< Certificate PEM string or path. - std::string sunshine_name; + std::string sunshine_name; ///< Host name advertised to Moonlight clients. - std::string file_state; + std::string file_state; ///< Path to the persisted Sunshine state file. - std::string external_ip; + std::string external_ip; ///< External address advertised to clients when configured. }; + /** + * @brief Input emulation settings loaded from configuration. + */ struct input_t { - std::unordered_map keybindings; + std::unordered_map keybindings; ///< Client keycode to platform keycode bindings. - std::chrono::milliseconds back_button_timeout; - std::chrono::milliseconds key_repeat_delay; - std::chrono::duration key_repeat_period; + std::chrono::milliseconds back_button_timeout; ///< Hold duration that turns a controller Back button into a special action. + std::chrono::milliseconds key_repeat_delay; ///< Delay before repeating a held keyboard key. + std::chrono::duration key_repeat_period; ///< Interval between repeated keyboard key events. - std::string gamepad; - bool ds4_back_as_touchpad_click; - bool motion_as_ds4; - bool touchpad_as_ds4; - bool ds5_inputtino_randomize_mac; + std::string gamepad; ///< Virtual controller backend selected by configuration. + bool ds4_back_as_touchpad_click; ///< Map the DS4 Back button to a touchpad click. + bool motion_as_ds4; ///< Expose motion controls through the DS4 protocol. + bool touchpad_as_ds4; ///< Expose touchpad input through the DS4 protocol. + bool ds5_inputtino_randomize_mac; ///< Randomize the inputtino DualSense MAC address. - bool keyboard; - bool key_rightalt_to_key_win; - bool mouse; - bool controller; + bool keyboard; ///< Enable keyboard input from clients. + bool key_rightalt_to_key_win; ///< Map the client Right Alt key to the Windows key. + bool mouse; ///< Enable mouse input from clients. + bool controller; ///< Enable controller input from clients. - bool always_send_scancodes; + bool always_send_scancodes; ///< Always send keyboard scancodes when available. - bool high_resolution_scrolling; - bool native_pen_touch; + bool high_resolution_scrolling; ///< Enable high-resolution mouse-wheel events. + bool native_pen_touch; ///< Enable native pen and touch injection. }; namespace flag { + /** + * @brief Enumerates supported flag options. + */ enum flag_e : std::size_t { PIN_STDIN = 0, ///< Read PIN from stdin instead of http FRESH_STATE, ///< Do not load or save state @@ -238,53 +301,75 @@ namespace config { }; } // namespace flag + /** + * @brief External preparation command plus its privilege requirement. + */ struct prep_cmd_t { + /** + * @brief Build a preparation command entry from parsed configuration data. + * + * @param do_cmd Command to run before the application starts. + * @param undo_cmd Command to run after the application exits. + * @param elevated Whether the command should run with elevated privileges. + */ prep_cmd_t(std::string &&do_cmd, std::string &&undo_cmd, bool &&elevated): do_cmd(std::move(do_cmd)), undo_cmd(std::move(undo_cmd)), elevated(std::move(elevated)) { } + /** + * @brief Build a preparation command entry from parsed configuration data. + * + * @param do_cmd Command to run before the application starts. + * @param elevated Whether the command should run with elevated privileges. + */ explicit prep_cmd_t(std::string &&do_cmd, bool &&elevated): do_cmd(std::move(do_cmd)), elevated(std::move(elevated)) { } - std::string do_cmd; - std::string undo_cmd; - bool elevated; + std::string do_cmd; ///< Command to run before the application starts. + std::string undo_cmd; ///< Command to run after the application exits. + bool elevated; ///< Whether the process should be launched elevated. }; + /** + * @brief Top-level Sunshine configuration and credential state. + */ struct sunshine_t { - std::string locale; - int min_log_level; - std::bitset flags; - std::string credentials_file; + std::string locale; ///< Locale selected for Sunshine UI and log messages. + int min_log_level; ///< Minimum severity level written to the configured log sink. + std::bitset flags; ///< Runtime flags parsed from command-line options. + std::string credentials_file; ///< Path to the stored pairing credentials file. - std::string username; - std::string password; - std::string salt; + std::string username; ///< Username for the local Web UI account. + std::string password; ///< Password hash or secret for the local Web UI account. + std::string salt; ///< Salt used when hashing the Web UI password. - std::string config_file; + std::string config_file; ///< Path to the active Sunshine configuration file. + /** + * @brief Command-line options parsed before configuration loading. + */ struct cmd_t { - std::string name; - int argc; - char **argv; - } cmd; + std::string name; ///< Executable name from the command line. + int argc; ///< Number of command-line arguments. + char **argv; ///< Command-line argument vector. + } cmd; ///< Command line used to launch the application. - std::uint16_t port; - std::string address_family; - std::string bind_address; + std::uint16_t port; ///< TCP port used by Sunshine services. + std::string address_family; ///< Address family requested for listening sockets. + std::string bind_address; ///< Local address Sunshine should bind to. - std::string log_file; - bool notify_pre_releases; - bool system_tray; - std::vector prep_cmds; + std::string log_file; ///< Path to the configured log file. + bool notify_pre_releases; ///< Notify users about pre-release updates. + bool system_tray; ///< Enable the system tray integration. + std::vector prep_cmds; ///< Preparation commands executed around application launch. // List of allowed origins for CSRF protection (e.g., "https://example.com,https://app.example.com") // Comma-separated list of additional origins. Default includes localhost variants and web UI port. - std::vector csrf_allowed_origins; + std::vector csrf_allowed_origins; ///< Additional origins allowed by CSRF validation. }; extern video_t video; @@ -294,6 +379,19 @@ namespace config { extern input_t input; extern sunshine_t sunshine; + /** + * @brief Parse serialized text into the corresponding runtime representation. + * + * @param argc Number of command-line arguments. + * @param argv Command-line argument vector. + * @return 0 on success; nonzero when command-line or configuration parsing fails. + */ int parse(int argc, char *argv[]); + /** + * @brief Parse Sunshine configuration text into key-value entries. + * + * @param file_content Raw configuration file contents to parse. + * @return Parsed configuration key-value entries. + */ std::unordered_map parse_config(const std::string_view &file_content); } // namespace config diff --git a/src/confighttp.cpp b/src/confighttp.cpp index 5fd6a19d8..8b9468e34 100644 --- a/src/confighttp.cpp +++ b/src/confighttp.cpp @@ -50,30 +50,56 @@ using namespace std::literals; namespace confighttp { namespace fs = std::filesystem; + /** + * @brief HTTPS server type used for Sunshine's configuration UI. + */ using https_server_t = SimpleWeb::Server; + /** + * @brief Case-insensitive map used for HTTP headers and query parameters. + */ using args_t = SimpleWeb::CaseInsensitiveMultimap; + /** + * @brief Shared HTTPS response object passed to configuration handlers. + */ using resp_https_t = std::shared_ptr::Response>; + /** + * @brief Shared HTTPS request object received by configuration handlers. + */ using req_https_t = std::shared_ptr::Request>; + /** + * @brief Handler signature for configuration UI HTTPS routes. + */ using https_handler_t = std::function; + /** + * @brief Client certificate operations accepted by the configuration API. + */ enum class op_e { ADD, ///< Add client REMOVE ///< Remove client }; // CSRF token management + /** + * @brief CSRF token value and its expiration deadline. + */ struct csrf_token_t { - std::string token; - std::chrono::steady_clock::time_point expiration; + std::string token; ///< Random token value that must be echoed by the client. + std::chrono::steady_clock::time_point expiration; ///< Monotonic deadline after which the token is rejected. }; - // Store CSRF tokens with thread safety - std::map> csrf_tokens; // NOSONAR(cpp:S5421) - intentionally mutable global - std::mutex csrf_tokens_mutex; // NOSONAR(cpp:S5421) - intentionally mutable global + std::map> csrf_tokens; ///< CSRF tokens by client identifier. NOSONAR(cpp:S5421) - intentionally mutable global + std::mutex csrf_tokens_mutex; ///< Mutex protecting CSRF token storage. NOSONAR(cpp:S5421) - intentionally mutable global // CSRF token configuration + /** + * @brief Number of random bytes used when generating a CSRF token. + */ constexpr auto CSRF_TOKEN_SIZE = 32; // 32 bytes = 256 bits + /** + * @brief Amount of time a generated CSRF token remains valid. + */ constexpr auto CSRF_TOKEN_LIFETIME = std::chrono::hours(1); // Tokens valid for 1 hour /** @@ -247,9 +273,6 @@ namespace confighttp { /** * @brief Validate the request content type and send a bad request when mismatched. - * @param response The HTTP response object. - * @param request The HTTP request object. - * @param contentType The expected content type */ bool check_content_type(const resp_https_t &response, const req_https_t &request, const std::string_view &contentType) { const auto requestContentType = request->header.find("content-type"); @@ -360,6 +383,9 @@ namespace confighttp { return true; } + /** + * @brief Validate CSRF token. + */ bool validate_csrf_token(const resp_https_t &response, const req_https_t &request, const std::string &client_id) { // Helper function to check if a URL starts with any allowed origin auto is_allowed_origin = [](const std::string_view url) { @@ -421,9 +447,6 @@ namespace confighttp { /** * @brief Validates the application index and sends an error response if invalid. - * @param response The HTTP response object. - * @param request The HTTP request object. - * @param index The application index/id. */ bool check_app_index(const resp_https_t &response, const req_https_t &request, int index) { std::string file = file_handler::read_file(config::stream.file_apps.c_str()); @@ -1384,9 +1407,10 @@ namespace confighttp { } /** - * @brief Restart Sunshine. - * @param response The HTTP response object. - * @param request The HTTP request object. + * @brief Authenticate a Web UI request and restart the Sunshine process. + * + * @param response HTTP response used for authentication or CSRF failures. + * @param request HTTP request carrying the client identity and CSRF token. * * @api_examples{/api/restart| POST| null} */ @@ -1721,6 +1745,9 @@ namespace confighttp { } } + /** + * @brief Start the HTTPS configuration server. + */ void start() { platf::set_thread_name("confighttp"); const auto shutdown_event = mail::man->event(mail::shutdown); diff --git a/src/confighttp.h b/src/confighttp.h index e8c4ea003..0806ad4af 100644 --- a/src/confighttp.h +++ b/src/confighttp.h @@ -16,10 +16,14 @@ // local includes #include "thread_safe.h" +/** + * @def WEB_DIR + * @brief Macro for WEB DIR. + */ #define WEB_DIR SUNSHINE_ASSETS_DIR "/web/" namespace confighttp { - constexpr auto PORT_HTTPS = 1; + constexpr auto PORT_HTTPS = 1; ///< GameStream port offset for port https. // Type aliases for HTTPS server components using https_server_t = SimpleWeb::Server; @@ -36,10 +40,34 @@ namespace confighttp { bool authenticate(const resp_https_t &response, const req_https_t &request); void not_found(const resp_https_t &response, const req_https_t &request, const std::string &error_message = "Not Found"); void bad_request(const resp_https_t &response, const req_https_t &request, const std::string &error_message = "Bad Request"); + /** + * @brief Check content type. + * + * @param response HTTP response object to populate. + * @param request HTTP request data from the client. + * @param contentType Expected HTTP content type. + * @return True when the request passes validation and processing may continue. + */ bool check_content_type(const resp_https_t &response, const req_https_t &request, const std::string_view &contentType); std::string generate_csrf_token(const std::string &client_id); + /** + * @brief Validate CSRF token. + * + * @param response HTTP response object to populate. + * @param request HTTP request data from the client. + * @param client_id Client identifier used to look up the CSRF token. + * @return True when the request passes validation and processing may continue. + */ bool validate_csrf_token(const resp_https_t &response, const req_https_t &request, const std::string &client_id); std::string get_client_id(const req_https_t &request); + /** + * @brief Check app index. + * + * @param response HTTP response object to populate. + * @param request HTTP request data from the client. + * @param index Zero-based index of the item being addressed. + * @return True when the request passes validation and processing may continue. + */ bool check_app_index(const resp_https_t &response, const req_https_t &request, int index); void getPage(const resp_https_t &response, const req_https_t &request, const char *html_file, bool require_auth = true, bool redirect_if_username = false); void getAsset(const resp_https_t &response, const req_https_t &request); @@ -74,6 +102,9 @@ namespace confighttp { } // namespace confighttp // mime types map +/** + * @brief File-extension to MIME-type mapping used when serving the Web UI. + */ const std::map mime_types = { {"css", "text/css"}, {"gif", "image/gif"}, diff --git a/src/crypto.cpp b/src/crypto.cpp index f6ec0cb78..a871cf89d 100644 --- a/src/crypto.cpp +++ b/src/crypto.cpp @@ -10,7 +10,13 @@ #include "crypto.h" namespace crypto { + /** + * @brief OpenSSL ASN.1 string pointer with automatic release. + */ using asn1_string_t = util::safe_ptr; + /** + * @brief OpenSSL X.509 subject/issuer name pointer with automatic release. + */ using x509_name_t = util::safe_ptr; cert_chain_t::cert_chain_t(): @@ -322,6 +328,9 @@ namespace crypto { } // namespace cipher + /** + * @brief Derive the AES key used by the pairing protocol. + */ aes_t gen_aes_key(const std::array &salt, const std::string_view &pin) { aes_t key(16); @@ -344,6 +353,9 @@ namespace crypto { return hsh; } + /** + * @brief Parse PEM text into an X.509 certificate object. + */ x509_t x509(const std::string_view &x) { bio_t io {BIO_new(BIO_s_mem())}; @@ -355,6 +367,9 @@ namespace crypto { return p; } + /** + * @brief Parse PEM text into an OpenSSL private key object. + */ pkey_t pkey(const std::string_view &k) { bio_t io {BIO_new(BIO_s_mem())}; @@ -366,6 +381,9 @@ namespace crypto { return p; } + /** + * @brief Serialize an OpenSSL object to PEM text. + */ std::string pem(x509_t &x509) { bio_t bio {BIO_new(BIO_s_mem())}; @@ -376,6 +394,9 @@ namespace crypto { return {mem_ptr->data, mem_ptr->length}; } + /** + * @brief Serialize an OpenSSL object to PEM text. + */ std::string pem(pkey_t &pkey) { bio_t bio {BIO_new(BIO_s_mem())}; @@ -386,6 +407,9 @@ namespace crypto { return {mem_ptr->data, mem_ptr->length}; } + /** + * @brief Return the certificate signature bytes. + */ std::string_view signature(const x509_t &x) { // X509_ALGOR *_ = nullptr; @@ -398,6 +422,9 @@ namespace crypto { }; } + /** + * @brief Generate cryptographically secure random bytes. + */ std::string rand(std::size_t bytes) { std::string r; r.resize(bytes); @@ -407,6 +434,14 @@ namespace crypto { return r; } + /** + * @brief Sign data with the requested OpenSSL digest. + * + * @param pkey Private key PEM data or private key file path. + * @param data Payload or state data to serialize, deserialize, or forward. + * @param md OpenSSL message digest algorithm used for signing or verification. + * @return Number of bytes written, signature bytes, or an error status depending on the overload. + */ std::vector sign(const pkey_t &pkey, const std::string_view &data, const EVP_MD *md) { md_ctx_t ctx {EVP_MD_CTX_create()}; @@ -431,6 +466,9 @@ namespace crypto { return digest; } + /** + * @brief Generate a self-signed certificate and private key for Sunshine pairing. + */ creds_t gen_creds(const std::string_view &cn, std::uint32_t key_bits) { x509_t x509 {X509_new()}; pkey_ctx_t ctx {EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr)}; @@ -475,10 +513,22 @@ namespace crypto { return {pem(x509), pem(pkey)}; } + /** + * @brief Sign data with SHA-256. + */ std::vector sign256(const pkey_t &pkey, const std::string_view &data) { return sign(pkey, data, EVP_sha256()); } + /** + * @brief Verify a signature against certificate public key data. + * + * @param x509 X.509 certificate object or PEM data. + * @param data Payload or state data to serialize, deserialize, or forward. + * @param signature Signature bytes to verify or encode. + * @param md OpenSSL message digest algorithm used for signing or verification. + * @return True when OpenSSL verifies the signature with the supplied digest. + */ bool verify(const x509_t &x509, const std::string_view &data, const std::string_view &signature, const EVP_MD *md) { auto pkey = X509_get0_pubkey(x509.get()); @@ -499,14 +549,23 @@ namespace crypto { return true; } + /** + * @brief Verify a SHA-256 signature with the certificate public key. + */ bool verify256(const x509_t &x509, const std::string_view &data, const std::string_view &signature) { return verify(x509, data, signature, EVP_sha256()); } + /** + * @brief Destroy an OpenSSL message digest context. + */ void md_ctx_destroy(EVP_MD_CTX *ctx) { EVP_MD_CTX_destroy(ctx); } + /** + * @brief Generate random text from the supplied alphabet. + */ std::string rand_alphabet(std::size_t bytes, const std::string_view &alphabet) { auto value = rand(bytes); diff --git a/src/crypto.h b/src/crypto.h index 944cd77e2..e823cdeba 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -17,24 +17,65 @@ #include "utility.h" namespace crypto { + /** + * @brief PEM-encoded certificate and private key pair. + */ struct creds_t { - std::string x509; - std::string pkey; + std::string x509; ///< PEM-encoded X.509 certificate. + std::string pkey; ///< Private key PEM string or path. }; - void md_ctx_destroy(EVP_MD_CTX *); + /** + * @brief Destroy an OpenSSL message digest context. + * + * @param ctx Message digest context. + */ + void md_ctx_destroy(EVP_MD_CTX *ctx); + /** + * @brief Fixed-size SHA-256 digest byte array. + */ using sha256_t = std::array; + /** + * @brief Byte buffer containing AES key material. + */ using aes_t = std::vector; + /** + * @brief Owning pointer for an OpenSSL X.509 certificate. + */ using x509_t = util::safe_ptr; + /** + * @brief Owning pointer for an OpenSSL certificate store. + */ using x509_store_t = util::safe_ptr; + /** + * @brief Owning pointer for an OpenSSL certificate verification context. + */ using x509_store_ctx_t = util::safe_ptr; + /** + * @brief Owning pointer for an OpenSSL cipher context. + */ using cipher_ctx_t = util::safe_ptr; + /** + * @brief Owning pointer for an OpenSSL message digest context. + */ using md_ctx_t = util::safe_ptr; + /** + * @brief Owning pointer for an OpenSSL BIO chain. + */ using bio_t = util::safe_ptr; + /** + * @brief Owning pointer for an OpenSSL public or private key. + */ using pkey_t = util::safe_ptr; + /** + * @brief Owning pointer for an OpenSSL key-generation context. + */ using pkey_ctx_t = util::safe_ptr; + /** + * @brief Owning pointer for an OpenSSL BIGNUM. + */ using bignum_t = util::safe_ptr; /** @@ -44,28 +85,111 @@ namespace crypto { */ sha256_t hash(const std::string_view &plaintext); + /** + * @brief Derive the AES key used by the pairing protocol. + * + * @param salt Random salt used when deriving the pairing secret. + * @param pin PIN supplied by the client during pairing. + * @return Parsed or generated OpenSSL object or PEM data. + */ aes_t gen_aes_key(const std::array &salt, const std::string_view &pin); + /** + * @brief Parse PEM text into an X.509 certificate object. + * + * @param x Certificate object or PEM text, depending on the overload. + * @return Parsed or generated OpenSSL object or PEM data. + */ x509_t x509(const std::string_view &x); + /** + * @brief Parse PEM text into an OpenSSL private key object. + * + * @param k PEM text containing a private key. + * @return Parsed or generated OpenSSL object or PEM data. + */ pkey_t pkey(const std::string_view &k); + /** + * @brief Serialize an OpenSSL object to PEM text. + * + * @param x509 X.509 certificate object or PEM data. + * @return Parsed or generated OpenSSL object or PEM data. + */ std::string pem(x509_t &x509); + /** + * @brief Serialize an OpenSSL object to PEM text. + * + * @param pkey Private key PEM data or private key file path. + * @return Parsed or generated OpenSSL object or PEM data. + */ std::string pem(pkey_t &pkey); + /** + * @brief Sign data with SHA-256. + * + * @param pkey Private key PEM data or private key file path. + * @param data Payload or state data to serialize, deserialize, or forward. + * @return Number of bytes written, signature bytes, or an error status depending on the overload. + */ std::vector sign256(const pkey_t &pkey, const std::string_view &data); + /** + * @brief Verify a SHA-256 signature with the certificate public key. + * + * @param x509 X.509 certificate object or PEM data. + * @param data Payload or state data to serialize, deserialize, or forward. + * @param signature Signature bytes to verify or encode. + * @return True when the signature is valid for the supplied data. + */ bool verify256(const x509_t &x509, const std::string_view &data, const std::string_view &signature); + /** + * @brief Generate a self-signed certificate and private key for Sunshine pairing. + * + * @param cn Common name to place in the generated certificate. + * @param key_bits Size in bits of the generated RSA key. + * @return Parsed or generated OpenSSL object or PEM data. + */ creds_t gen_creds(const std::string_view &cn, std::uint32_t key_bits); + /** + * @brief Return the certificate signature bytes. + * + * @param x Certificate object or PEM text, depending on the overload. + * @return Parsed or generated OpenSSL object or PEM data. + */ std::string_view signature(const x509_t &x); + /** + * @brief Generate cryptographically secure random bytes. + * + * @param bytes Number of random bytes to generate. + * @return Random bytes or text generated by OpenSSL. + */ std::string rand(std::size_t bytes); + /** + * @brief Generate random text from the supplied alphabet. + * + * @param bytes Number of random bytes to generate. + * @param alphabet Allowed characters used for random string generation. + * @return Random bytes or text generated by OpenSSL. + */ std::string rand_alphabet(std::size_t bytes, const std::string_view &alphabet = std::string_view {"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!%&()=-"}); + /** + * @brief Owns the certificate chain returned by Sunshine's TLS certificate loader. + */ class cert_chain_t { public: KITTY_DECL_CONSTR(cert_chain_t) + /** + * @brief Add a certificate to the verification chain. + * + * @param cert Certificate data or object used by the operation. + */ void add(x509_t &&cert); + /** + * @brief Remove all certificates from the verification chain. + */ void clear(); const char *verify(x509_t::element_type *cert); @@ -76,40 +200,97 @@ namespace crypto { }; namespace cipher { - constexpr std::size_t tag_size = 16; + constexpr std::size_t tag_size = 16; ///< Tag size. + /** + * @brief Round a byte count up to the next PKCS#7 padding boundary. + * + * @param size Number of bytes or elements requested. + * @return `size` rounded up to the next PKCS#7 block boundary. + */ constexpr std::size_t round_to_pkcs7_padded(std::size_t size) { return ((size + 15) / 16) * 16; } + /** + * @brief AES-GCM encrypt/decrypt context pair used for GameStream messages. + */ class cipher_t { public: - cipher_ctx_t decrypt_ctx; - cipher_ctx_t encrypt_ctx; + cipher_ctx_t decrypt_ctx; ///< Decrypt ctx. + cipher_ctx_t encrypt_ctx; ///< Encrypt ctx. - aes_t key; + aes_t key; ///< AES key used by the cipher context. - bool padding; + bool padding; ///< Enables block padding for the cipher. }; + /** + * @brief AES-ECB cipher helper used by pairing and stream encryption code. + */ class ecb_t: public cipher_t { public: ecb_t() = default; + /** + * @brief Construct an AES-ECB cipher helper with key material. + */ ecb_t(ecb_t &&) noexcept = default; + /** + * @brief Assign state from another instance while preserving ownership semantics. + * + * @return Reference or value produced by the operator. + */ ecb_t &operator=(ecb_t &&) noexcept = default; + /** + * @brief Construct an AES-ECB cipher helper with key material. + * + * @param key AES key material used to initialize the cipher. + * @param padding Whether the cipher should use block padding. + */ ecb_t(const aes_t &key, bool padding = true); + /** + * @brief Encrypt plaintext into the supplied ciphertext buffer. + * + * @param plaintext Plaintext bytes to encrypt. + * @param cipher Ciphertext bytes to decrypt or output buffer for encryption. + * @return Number of bytes written, signature bytes, or an error status depending on the overload. + */ int encrypt(const std::string_view &plaintext, std::vector &cipher); + /** + * @brief Decrypt ciphertext into the supplied plaintext buffer. + * + * @param cipher Ciphertext bytes to decrypt or output buffer for encryption. + * @param plaintext Plaintext bytes to encrypt. + * @return Number of bytes written, signature bytes, or an error status depending on the overload. + */ int decrypt(const std::string_view &cipher, std::vector &plaintext); }; + /** + * @brief AES-GCM cipher helper that encrypts and authenticates payloads. + */ class gcm_t: public cipher_t { public: gcm_t() = default; + /** + * @brief Construct an AES-GCM cipher helper with key material and IV state. + */ gcm_t(gcm_t &&) noexcept = default; + /** + * @brief Assign state from another instance while preserving ownership semantics. + * + * @return Reference or value produced by the operator. + */ gcm_t &operator=(gcm_t &&) noexcept = default; + /** + * @brief Construct an AES-GCM cipher helper with key material and IV state. + * + * @param key AES key material used to initialize the cipher. + * @param padding Whether the cipher should use block padding. + */ gcm_t(const crypto::aes_t &key, bool padding = true); /** @@ -132,15 +313,40 @@ namespace crypto { */ int encrypt(const std::string_view &plaintext, std::uint8_t *tagged_cipher, aes_t *iv); + /** + * @brief Decrypt ciphertext into the supplied plaintext buffer. + * + * @param cipher Ciphertext bytes to decrypt or output buffer for encryption. + * @param plaintext Plaintext bytes to encrypt. + * @param iv Initialization vector for the cipher operation. + * @return Number of bytes written, signature bytes, or an error status depending on the overload. + */ int decrypt(const std::string_view &cipher, std::vector &plaintext, aes_t *iv); }; + /** + * @brief AES-CBC cipher helper used for block-mode encryption and decryption. + */ class cbc_t: public cipher_t { public: cbc_t() = default; + /** + * @brief Construct an AES-CBC cipher helper with key material and IV state. + */ cbc_t(cbc_t &&) noexcept = default; + /** + * @brief Assign state from another instance while preserving ownership semantics. + * + * @return Reference or value produced by the operator. + */ cbc_t &operator=(cbc_t &&) noexcept = default; + /** + * @brief Construct an AES-CBC cipher helper with key material and IV state. + * + * @param key AES key material used to initialize the cipher. + * @param padding Whether the cipher should use block padding. + */ cbc_t(const crypto::aes_t &key, bool padding = true); /** diff --git a/src/display_device.cpp b/src/display_device.cpp index 7d24d524b..256997d9c 100644 --- a/src/display_device.cpp +++ b/src/display_device.cpp @@ -456,7 +456,7 @@ namespace display_device { } /** - * @brief Contains remapping data parsed from the string values. + * @brief Enumerates supported contains remapping data parsed from the string options. */ struct parsed_remapping_entry_t { std::optional requested_resolution; diff --git a/src/display_device.h b/src/display_device.h index d49698bdb..e59334c83 100644 --- a/src/display_device.h +++ b/src/display_device.h @@ -99,7 +99,7 @@ namespace display_device { void revert_configuration(); /** - * @brief Reset the persistence and currently held initial display state. + * @brief Reset persisted display state and the captured initial state. * * This is normally used to get out of the "broken" state where the algorithm wants * to restore the initial display state, but it is no longer possible. diff --git a/src/entry_handler.cpp b/src/entry_handler.cpp index 02afcb061..e5e18920b 100644 --- a/src/entry_handler.cpp +++ b/src/entry_handler.cpp @@ -67,8 +67,8 @@ namespace args { } // namespace args namespace lifetime { - char **argv; - std::atomic_int desired_exit_code; + char **argv; ///< Command-line argument vector. + std::atomic_int desired_exit_code; ///< Desired exit code. void exit_sunshine(int exit_code, bool async) { // Store the exit code of the first exit_sunshine() call @@ -121,10 +121,14 @@ bool is_gamestream_enabled() { } namespace service_ctrl { + /** + * @brief Owns Windows service-manager handles for the Sunshine service. + */ class service_controller { public: /** - * @brief Constructor for service_controller class. + * @brief Open the Windows service manager and Sunshine service handle. + * * @param service_desired_access SERVICE_* desired access flags. */ service_controller(DWORD service_desired_access) { @@ -155,6 +159,8 @@ namespace service_ctrl { /** * @brief Asynchronously starts the Sunshine service. + * + * @return True when the Windows service API call succeeds. */ bool start_service() { if (!service_handle) { @@ -175,6 +181,8 @@ namespace service_ctrl { /** * @brief Query the service status. * @param status The SERVICE_STATUS struct to populate. + * + * @return True when the Windows service API call succeeds. */ bool query_service_status(SERVICE_STATUS &status) { if (!service_handle) { diff --git a/src/entry_handler.h b/src/entry_handler.h index 8d5a03e71..32aba38e6 100644 --- a/src/entry_handler.h +++ b/src/entry_handler.h @@ -34,6 +34,8 @@ namespace args { * @examples * creds("sunshine", 2, {"new_username", "new_password"}); * @examples_end + * + * @return Process exit code from updating the stored credentials. */ int creds(const char *name, int argc, char *argv[]); @@ -43,6 +45,8 @@ namespace args { * @examples * help("sunshine"); * @examples_end + * + * @return Process exit code after printing command usage. */ int help(const char *name); @@ -51,6 +55,8 @@ namespace args { * @examples * version(); * @examples_end + * + * @return Process exit code after printing the Sunshine version. */ int version(); @@ -63,6 +69,8 @@ namespace args { * @examples * restore_nvprefs_undo(); * @examples_end + * + * @return Process exit code from restoring NVIDIA profile preferences. */ int restore_nvprefs_undo(); #endif @@ -89,6 +97,8 @@ namespace lifetime { /** * @brief Get the argv array passed to main(). + * + * @return Original argument vector captured from main(). */ char **get_argv(); } // namespace lifetime @@ -114,6 +124,8 @@ namespace service_ctrl { * @examples * is_service_running(); * @examples_end + * + * @return True when the requested service or UI readiness condition is satisfied. */ bool is_service_running(); @@ -122,6 +134,8 @@ namespace service_ctrl { * @examples * start_service(); * @examples_end + * + * @return True when the requested service or UI readiness condition is satisfied. */ bool start_service(); @@ -130,6 +144,8 @@ namespace service_ctrl { * @examples * wait_for_ui_ready(); * @examples_end + * + * @return True when the requested service or UI readiness condition is satisfied. */ bool wait_for_ui_ready(); } // namespace service_ctrl diff --git a/src/globals.h b/src/globals.h index 1617b7c6f..2c7ee9b12 100644 --- a/src/globals.h +++ b/src/globals.h @@ -32,6 +32,10 @@ extern nvprefs::nvprefs_interface nvprefs_instance; * @brief Handles process-wide communication. */ namespace mail { +/** + * @def MAIL(x) + * @brief Macro for MAIL. + */ #define MAIL(x) \ constexpr auto x = std::string_view { \ #x \ @@ -43,18 +47,18 @@ namespace mail { extern safe::mail_t man; // Global mail - MAIL(shutdown); - MAIL(broadcast_shutdown); - MAIL(video_packets); - MAIL(audio_packets); - MAIL(switch_display); + MAIL(shutdown); ///< Shutdown. + MAIL(broadcast_shutdown); ///< Broadcast shutdown. + MAIL(video_packets); ///< Video packets. + MAIL(audio_packets); ///< Audio packets. + MAIL(switch_display); ///< Switch display. // Local mail - MAIL(touch_port); - MAIL(idr); - MAIL(invalidate_ref_frames); - MAIL(gamepad_feedback); - MAIL(hdr); + MAIL(touch_port); ///< Touch port. + MAIL(idr); ///< IDR. + MAIL(invalidate_ref_frames); ///< Invalidate ref frames. + MAIL(gamepad_feedback); ///< Gamepad feedback. + MAIL(hdr); ///< HDR. #undef MAIL } // namespace mail diff --git a/src/httpcommon.cpp b/src/httpcommon.cpp index 2f0926851..de261f5b9 100644 --- a/src/httpcommon.cpp +++ b/src/httpcommon.cpp @@ -38,11 +38,20 @@ namespace http { namespace pt = boost::property_tree; int reload_user_creds(const std::string &file); + /** + * @brief Check whether the Web UI credentials file exists and is readable. + * + * @param file Path to the credentials file. + * @return True when the credentials file is present. + */ bool user_creds_exist(const std::string &file); - std::string unique_id; - net::net_e origin_web_ui_allowed; + std::string unique_id; ///< Unique ID. + net::net_e origin_web_ui_allowed; ///< Origin web ui allowed. + /** + * @brief Load persisted HTTP credentials and initialize shared request state. + */ int init() { bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE]; origin_web_ui_allowed = net::from_enum_string(config::nvhttp.origin_web_ui_allowed); @@ -66,6 +75,15 @@ namespace http { return 0; } + /** + * @brief Save user creds. + * + * @param file Credentials file path. + * @param username Username to save. + * @param password Password to save. + * @param run_our_mouth Whether to log user-facing status messages. + * @return 0 on success, non-zero on failure. + */ int save_user_creds(const std::string &file, const std::string &username, const std::string &password, bool run_our_mouth) { pt::ptree outputTree; @@ -93,6 +111,9 @@ namespace http { return 0; } + /** + * @brief Check whether the Web UI credentials file exists and is readable. + */ bool user_creds_exist(const std::string &file) { if (!fs::exists(file)) { return false; @@ -111,6 +132,9 @@ namespace http { return false; } + /** + * @brief Reload the Web UI credentials from disk. + */ int reload_user_creds(const std::string &file) { pt::ptree inputTree; try { @@ -125,6 +149,9 @@ namespace http { return 0; } + /** + * @brief Generate HTTPS credential files from the provided key and certificate paths. + */ int create_creds(const std::string &pkey, const std::string &cert) { fs::path pkey_path = pkey; fs::path cert_path = cert; @@ -176,6 +203,9 @@ namespace http { return 0; } + /** + * @brief Send a static file response for a Web UI request. + */ bool download_file(const std::string &url, const std::string &file, long ssl_version) { // sonar complains about weak ssl and tls versions; however sonar cannot detect the fix CURL *curl = curl_easy_init(); // NOSONAR @@ -212,6 +242,9 @@ namespace http { return result == CURLE_OK; } + /** + * @brief Percent-encode URL data for use in HTTP query strings. + */ std::string url_escape(const std::string &url) { char *string = curl_easy_escape(nullptr, url.c_str(), static_cast(url.length())); std::string result(string); @@ -219,6 +252,9 @@ namespace http { return result; } + /** + * @brief Extract the host component from a URL string. + */ std::string url_get_host(const std::string &url) { CURLU *curlu = curl_url(); curl_url_set(curlu, CURLUPART_URL, url.c_str(), static_cast(url.length())); diff --git a/src/httpcommon.h b/src/httpcommon.h index 8b9799943..9e7375307 100644 --- a/src/httpcommon.h +++ b/src/httpcommon.h @@ -13,7 +13,19 @@ namespace http { + /** + * @brief Initialize shared HTTP client state. + * + * @return 0 when HTTP state initializes successfully; nonzero on failure. + */ int init(); + /** + * @brief Generate HTTPS credential files from the provided key and certificate paths. + * + * @param pkey Private key PEM data or private key file path. + * @param cert Certificate data or object used by the operation. + * @return Created creds object or status. + */ int create_creds(const std::string &pkey, const std::string &cert); int save_user_creds( const std::string &file, @@ -22,9 +34,35 @@ namespace http { bool run_our_mouth = false ); + /** + * @brief Reload Web UI user credentials from disk. + * + * @param file Destination path for the downloaded content. + * @return 0 when credentials reload successfully; nonzero on failure. + */ int reload_user_creds(const std::string &file); + /** + * @brief Download a URL to a local file using libcurl. + * + * @param url URL used for the HTTP request. + * @param file Destination path for the downloaded content. + * @param ssl_version libcurl TLS version selector for the request. + * @return True when the file is downloaded successfully. + */ bool download_file(const std::string &url, const std::string &file, long ssl_version = CURL_SSLVERSION_TLSv1_2); + /** + * @brief Percent-encode a string for safe inclusion in a URL. + * + * @param url URL used for the HTTP request. + * @return Percent-encoded URL component. + */ std::string url_escape(const std::string &url); + /** + * @brief Extract the host component from a URL. + * + * @param url URL used for the HTTP request. + * @return Host name parsed from the URL, or an empty string when none is present. + */ std::string url_get_host(const std::string &url); extern std::string unique_id; diff --git a/src/input.cpp b/src/input.cpp index d01b4f149..8108bcba3 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -30,33 +30,50 @@ extern "C" { // Win32 WHEEL_DELTA constant #ifndef WHEEL_DELTA -constexpr int WHEEL_DELTA = 120; +constexpr int WHEEL_DELTA = 120; ///< Standard Windows wheel delta used to normalize scroll events. #endif using namespace std::literals; namespace input { - constexpr auto MAX_GAMEPADS = std::min((std::size_t) platf::MAX_GAMEPADS, sizeof(std::int16_t) * 8); + constexpr auto MAX_GAMEPADS = std::min((std::size_t) platf::MAX_GAMEPADS, sizeof(std::int16_t) * 8); ///< Maximum gamepads representable by the active gamepad mask. +/** + * @def DISABLE_LEFT_BUTTON_DELAY + * @brief Macro for DISABLE LEFT BUTTON DELAY. + */ #define DISABLE_LEFT_BUTTON_DELAY ((thread_pool_util::ThreadPool::task_id_t) 0x01) +/** + * @def ENABLE_LEFT_BUTTON_DELAY + * @brief Macro for ENABLE LEFT BUTTON DELAY. + */ #define ENABLE_LEFT_BUTTON_DELAY nullptr - constexpr auto VKEY_SHIFT = 0x10; - constexpr auto VKEY_LSHIFT = 0xA0; - constexpr auto VKEY_RSHIFT = 0xA1; - constexpr auto VKEY_CONTROL = 0x11; - constexpr auto VKEY_LCONTROL = 0xA2; - constexpr auto VKEY_RCONTROL = 0xA3; - constexpr auto VKEY_MENU = 0x12; - constexpr auto VKEY_LMENU = 0xA4; - constexpr auto VKEY_RMENU = 0xA5; + constexpr auto VKEY_SHIFT = 0x10; ///< Windows virtual-key code for shift. + constexpr auto VKEY_LSHIFT = 0xA0; ///< Windows virtual-key code for lshift. + constexpr auto VKEY_RSHIFT = 0xA1; ///< Windows virtual-key code for rshift. + constexpr auto VKEY_CONTROL = 0x11; ///< Windows virtual-key code for control. + constexpr auto VKEY_LCONTROL = 0xA2; ///< Windows virtual-key code for lcontrol. + constexpr auto VKEY_RCONTROL = 0xA3; ///< Windows virtual-key code for rcontrol. + constexpr auto VKEY_MENU = 0x12; ///< Windows virtual-key code for menu. + constexpr auto VKEY_LMENU = 0xA4; ///< Windows virtual-key code for lmenu. + constexpr auto VKEY_RMENU = 0xA5; ///< Windows virtual-key code for rmenu. + /** + * @brief Enumerates supported button state options. + */ enum class button_state_e { NONE, ///< No button state DOWN, ///< Button is down UP ///< Button is up }; + /** + * @brief Allocate an available input slot identifier. + * + * @param gamepad_mask Gamepad mask. + * @return Allocated ID object, or null when unavailable. + */ template int alloc_id(std::bitset &gamepad_mask) { for (int x = 0; x < gamepad_mask.size(); ++x) { @@ -69,40 +86,68 @@ namespace input { return -1; } + /** + * @brief Release ID resources. + * + * @param gamepad_mask Gamepad mask. + * @param id Identifier for the controller, session, display, or resource. + */ template void free_id(std::bitset &gamepad_mask, int id) { gamepad_mask[id] = false; } + /** + * @brief Packed identifier for a pressed key and its modifier flags. + */ typedef uint32_t key_press_id_t; + /** + * @brief Create a key-press identifier from the virtual-key code and flags. + * + * @param vk Virtual-key code from the client input packet. + * @param flags Bit flags that modify the requested operation. + * @return Constructed kpid object. + */ key_press_id_t make_kpid(uint16_t vk, uint8_t flags) { return (key_press_id_t) vk << 8 | flags; } + /** + * @brief Extract the virtual-key code from a packed key-press identifier. + * + * @param kpid Key-press identifier containing the virtual-key code and flags. + * @return Virtual-key code stored in the high byte. + */ uint16_t vk_from_kpid(key_press_id_t kpid) { return kpid >> 8; } + /** + * @brief Extract the modifier flags from a packed key-press identifier. + * + * @param kpid Key-press identifier containing the virtual-key code and flags. + * @return Modifier flags stored in the low byte. + */ uint8_t flags_from_kpid(key_press_id_t kpid) { return kpid & 0xFF; } /** * @brief Convert a little-endian netfloat to a native endianness float. - * @param f Netfloat value. - * @return The native endianness float value. + * @param f Little-endian network float bytes. + * @return Floating-point value decoded for the host CPU. */ float from_netfloat(netfloat f) { return boost::endian::endian_load(f); } /** - * @brief Convert a little-endian netfloat to a native endianness float and clamps it. - * @param f Netfloat value. + * @brief Convert a little-endian netfloat to a native float and clamp it to a range. + * @param f Little-endian network float bytes. * @param min The minimium value for clamping. * @param max The maximum value for clamping. - * @return Clamped native endianess float value. + * @return Decoded floating-point value clamped between min and max. */ float from_clamped_netfloat(netfloat f, float min, float max) { return std::clamp(from_netfloat(f), min, max); @@ -115,6 +160,12 @@ namespace input { static platf::input_t platf_input; static std::bitset gamepadMask {}; + /** + * @brief Release all platform resources associated with a virtual gamepad. + * + * @param platf_input Platf input. + * @param id Identifier for the controller, session, display, or resource. + */ void free_gamepad(platf::input_t &platf_input, int id) { platf::gamepad_update(platf_input, id, platf::gamepad_state_t {}); platf::free_gamepad(platf_input, id); @@ -122,6 +173,9 @@ namespace input { free_id(gamepadMask, id); } + /** + * @brief Per-client gamepad slot and feedback state. + */ struct gamepad_t { gamepad_t(): gamepad_state {}, @@ -138,21 +192,27 @@ namespace input { } } - platf::gamepad_state_t gamepad_state; + platf::gamepad_state_t gamepad_state; ///< Gamepad state. - thread_pool_util::ThreadPool::task_id_t back_timeout_id; + thread_pool_util::ThreadPool::task_id_t back_timeout_id; ///< Back timeout ID. - int id; + int id; ///< Global gamepad slot assigned to this client controller. // When emulating the HOME button, we may need to artificially release the back button. // Afterwards, the gamepad state on sunshine won't match the state on Moonlight. // To prevent Sunshine from sending erroneous input data to the active application, // Sunshine forces the button to be in a specific state until the gamepad state matches that of // Moonlight once more. - button_state_e back_button_state; + button_state_e back_button_state; ///< Back button state. }; + /** + * @brief Input emulation settings loaded from configuration. + */ struct input_t { + /** + * @brief Enumerates supported shortkey options. + */ enum shortkey_e { CTRL = 0x1, ///< Control key ALT = 0x2, ///< Alt key @@ -160,6 +220,12 @@ namespace input { SHORTCUT = CTRL | ALT | SHIFT ///< Shortcut combination }; + /** + * @brief Construct input state from the mailbox and platform backend. + * + * @param touch_port_event Event carrying the active touch port. + * @param feedback_queue Queue used for controller feedback. + */ input_t( safe::mail_raw_t::event_t touch_port_event, platf::feedback_queue_t feedback_queue @@ -176,26 +242,26 @@ namespace input { } // Keep track of alt+ctrl+shift key combo - int shortcutFlags; + int shortcutFlags; ///< Shortcut flags. - bool left_alt_pressed = false; - bool right_alt_pressed = false; + bool left_alt_pressed = false; ///< Tracks whether the left Alt key is currently pressed. + bool right_alt_pressed = false; ///< Tracks whether the right Alt key is currently pressed. - std::vector gamepads; - std::unique_ptr client_context; + std::vector gamepads; ///< Virtual gamepad slots tracked for the stream. + std::unique_ptr client_context; ///< Client context. - safe::mail_raw_t::event_t touch_port_event; - platf::feedback_queue_t feedback_queue; + safe::mail_raw_t::event_t touch_port_event; ///< Touch port event. + platf::feedback_queue_t feedback_queue; ///< Queue used to deliver controller feedback to the platform backend. - std::list> input_queue; - std::mutex input_queue_lock; + std::list> input_queue; ///< Pending raw input packets waiting for processing. + std::mutex input_queue_lock; ///< Input queue lock. - thread_pool_util::ThreadPool::task_id_t mouse_left_button_timeout; + thread_pool_util::ThreadPool::task_id_t mouse_left_button_timeout; ///< Mouse left button timeout. - input::touch_port_t touch_port; + input::touch_port_t touch_port; ///< Touch coordinate bounds for the current stream. - int32_t accumulated_vscroll_delta; - int32_t accumulated_hscroll_delta; + int32_t accumulated_vscroll_delta; ///< Accumulated vscroll delta. + int32_t accumulated_hscroll_delta; ///< Accumulated hscroll delta. }; /** @@ -223,6 +289,11 @@ namespace input { return 0; } + /** + * @brief Write a debug log representation of the input packet. + * + * @param packet Protocol packet being processed. + */ void print(PNV_REL_MOUSE_MOVE_PACKET packet) { BOOST_LOG(debug) << "--begin relative mouse move packet--"sv << std::endl @@ -231,6 +302,11 @@ namespace input { << "--end relative mouse move packet--"sv; } + /** + * @brief Write a debug log representation of the input packet. + * + * @param packet Protocol packet being processed. + */ void print(PNV_ABS_MOUSE_MOVE_PACKET packet) { BOOST_LOG(debug) << "--begin absolute mouse move packet--"sv << std::endl @@ -241,6 +317,11 @@ namespace input { << "--end absolute mouse move packet--"sv; } + /** + * @brief Write a debug log representation of the input packet. + * + * @param packet Protocol packet being processed. + */ void print(PNV_MOUSE_BUTTON_PACKET packet) { BOOST_LOG(debug) << "--begin mouse button packet--"sv << std::endl @@ -249,6 +330,11 @@ namespace input { << "--end mouse button packet--"sv; } + /** + * @brief Write a debug log representation of the input packet. + * + * @param packet Protocol packet being processed. + */ void print(PNV_SCROLL_PACKET packet) { BOOST_LOG(debug) << "--begin mouse scroll packet--"sv << std::endl @@ -256,6 +342,11 @@ namespace input { << "--end mouse scroll packet--"sv; } + /** + * @brief Write a debug log representation of the input packet. + * + * @param packet Protocol packet being processed. + */ void print(PSS_HSCROLL_PACKET packet) { BOOST_LOG(debug) << "--begin mouse hscroll packet--"sv << std::endl @@ -263,6 +354,11 @@ namespace input { << "--end mouse hscroll packet--"sv; } + /** + * @brief Write a debug log representation of the input packet. + * + * @param packet Protocol packet being processed. + */ void print(PNV_KEYBOARD_PACKET packet) { BOOST_LOG(debug) << "--begin keyboard packet--"sv << std::endl @@ -273,6 +369,11 @@ namespace input { << "--end keyboard packet--"sv; } + /** + * @brief Write a debug log representation of the input packet. + * + * @param packet Protocol packet being processed. + */ void print(PNV_UNICODE_PACKET packet) { std::string text(packet->text, util::endian::big(packet->header.size) - sizeof(packet->header.magic)); BOOST_LOG(debug) @@ -281,6 +382,11 @@ namespace input { << "--end unicode packet--"sv; } + /** + * @brief Write a debug log representation of the input packet. + * + * @param packet Protocol packet being processed. + */ void print(PNV_MULTI_CONTROLLER_PACKET packet) { // Moonlight spams controller packet even when not necessary BOOST_LOG(verbose) @@ -393,6 +499,9 @@ namespace input { << "--end controller battery packet--"sv; } + /** + * @brief Write a debug log representation of the input packet. + */ void print(void *payload) { auto header = (PNV_INPUT_HEADER) payload; @@ -444,6 +553,12 @@ namespace input { } } + /** + * @brief Forward a client input packet directly to the platform backend. + * + * @param input Platform input backend that receives the event. + * @param packet Protocol packet being processed. + */ void passthrough(std::shared_ptr &input, PNV_REL_MOUSE_MOVE_PACKET packet) { if (!config::input.mouse) { return; @@ -541,6 +656,12 @@ namespace input { return {multiply_polar_by_cartesian_scalar(major, angle, scalar), multiply_polar_by_cartesian_scalar(minor, angle + (M_PI / 2), scalar)}; } + /** + * @brief Forward a client input packet directly to the platform backend. + * + * @param input Platform input backend that receives the event. + * @param packet Protocol packet being processed. + */ void passthrough(std::shared_ptr &input, PNV_ABS_MOUSE_MOVE_PACKET packet) { if (!config::input.mouse) { return; @@ -591,6 +712,12 @@ namespace input { platf::abs_mouse(platf_input, abs_port, tpcoords->first, tpcoords->second); } + /** + * @brief Called to pass a mouse button message to the platform backend. + * + * @param input The input context pointer. + * @param packet The mouse button packet. + */ void passthrough(std::shared_ptr &input, PNV_MOUSE_BUTTON_PACKET packet) { if (!config::input.mouse) { return; @@ -652,6 +779,12 @@ namespace input { platf::button_mouse(platf_input, button, release); } + /** + * @brief Apply configured keybinding remaps to a platform keycode. + * + * @param keycode Platform keycode being translated or emitted. + * @return Remapped keycode when configured, otherwise the original keycode. + */ short map_keycode(short keycode) { auto it = config::input.keybindings.find(keycode); if (it != std::end(config::input.keybindings)) { @@ -663,6 +796,10 @@ namespace input { /** * @brief Update flags for keyboard shortcut combo's + * + * @param flags Bit flags that modify the requested operation. + * @param keyCode Moonlight keyboard packet key code. + * @param release Whether the key or button event is a release. */ inline void update_shortcutFlags(int *flags, short keyCode, bool release) { switch (keyCode) { @@ -696,6 +833,12 @@ namespace input { } } + /** + * @brief Check whether modifier. + * + * @param keyCode Moonlight keyboard packet key code. + * @return True when the key code is a keyboard modifier. + */ bool is_modifier(uint16_t keyCode) { switch (keyCode) { case VKEY_SHIFT: @@ -713,6 +856,14 @@ namespace input { } } + /** + * @brief Send key and modifiers. + * + * @param key_code Moonlight keyboard packet key code. + * @param release Whether the key or button event is a release. + * @param flags Bit flags that modify the requested operation. + * @param synthetic_modifiers Synthetic modifiers. + */ void send_key_and_modifiers(uint16_t key_code, bool release, uint8_t flags, uint8_t synthetic_modifiers) { if (!release) { // Press any synthetic modifiers required for this key @@ -743,6 +894,13 @@ namespace input { } } + /** + * @brief Re-emit a held key until its repeat task is cancelled. + * + * @param key_code Moonlight keyboard packet key code. + * @param flags Bit flags that modify the requested operation. + * @param synthetic_modifiers Synthetic modifiers. + */ void repeat_key(uint16_t key_code, uint8_t flags, uint8_t synthetic_modifiers) { // If key no longer pressed, stop repeating if (!key_press[make_kpid(key_code, flags)]) { @@ -755,6 +913,12 @@ namespace input { key_press_repeat_id = task_pool.pushDelayed(repeat_key, config::input.key_repeat_period, key_code, flags, synthetic_modifiers).task_id; } + /** + * @brief Forward a client input packet directly to the platform backend. + * + * @param input Platform input backend that receives the event. + * @param packet Protocol packet being processed. + */ void passthrough(std::shared_ptr &input, PNV_KEYBOARD_PACKET packet) { if (!config::input.keyboard) { return; @@ -869,6 +1033,11 @@ namespace input { } } + /** + * @brief Forward a client input packet directly to the platform backend. + * + * @param packet Protocol packet being processed. + */ void passthrough(PNV_UNICODE_PACKET packet) { if (!config::input.keyboard) { return; @@ -1142,6 +1311,12 @@ namespace input { platf::gamepad_battery(platf_input, battery); } + /** + * @brief Forward a client input packet directly to the platform backend. + * + * @param input Platform input backend that receives the event. + * @param packet Protocol packet being processed. + */ void passthrough(std::shared_ptr &input, PNV_MULTI_CONTROLLER_PACKET packet) { if (!config::input.controller) { return; @@ -1257,6 +1432,9 @@ namespace input { gamepad.gamepad_state = gamepad_state; } + /** + * @brief Enumerates supported batch result options. + */ enum class batch_result_e { batched, ///< This entry was batched with the source entry not_batchable, ///< Not eligible to batch but continue attempts to batch @@ -1649,6 +1827,9 @@ namespace input { task_pool.push(passthrough_next_message, input); } + /** + * @brief Reset the object to its initial empty state. + */ void reset(std::shared_ptr &input) { task_pool.cancel(key_press_repeat_id); task_pool.cancel(input->mouse_left_button_timeout); @@ -1673,19 +1854,31 @@ namespace input { }); } + /** + * @brief RAII helper that runs shutdown cleanup when destroyed. + */ class deinit_t: public platf::deinit_t { public: + /** + * @brief Destroy the input subsystem deinitializer. + */ ~deinit_t() override { platf_input.reset(); } }; + /** + * @brief Initialize the platform input backend. + */ [[nodiscard]] std::unique_ptr init() { platf_input = platf::input(); return std::make_unique(); } + /** + * @brief Probe connected gamepads and update input capability state. + */ bool probe_gamepads() { auto input = static_cast(platf_input.get()); const auto gamepads = platf::supported_gamepads(input); @@ -1697,6 +1890,9 @@ namespace input { return true; } + /** + * @brief Allocate and initialize platform input state for a stream. + */ std::shared_ptr alloc(safe::mail_t mail) { auto input = std::make_shared( mail->event(mail::touch_port), diff --git a/src/input.h b/src/input.h index 0e7007c9d..42e2e6807 100644 --- a/src/input.h +++ b/src/input.h @@ -14,30 +14,66 @@ namespace input { struct input_t; + /** + * @brief Write a debug log representation of the input packet. + * + * @param input Raw input packet to format for logging. + */ void print(void *input); + /** + * @brief Reset stream input state after a client disconnect or shutdown. + * + * @param input Shared stream input state to reset. + */ void reset(std::shared_ptr &input); + + /** + * @brief Queue a raw input message for platform passthrough. + */ void passthrough(std::shared_ptr &input, std::vector &&input_data); + /** + * @brief Initialize global input resources and platform backends. + * + * @return Cleanup handle for initialized input resources, or null if none are required. + */ [[nodiscard]] std::unique_ptr init(); + /** + * @brief Probe whether the platform can create virtual gamepads. + * + * @return True when at least one configured gamepad backend is available. + */ bool probe_gamepads(); + /** + * @brief Allocate and initialize platform input state for a stream. + * + * @param mail Mailbox used to exchange messages with worker threads. + * @return Shared input state bound to the stream mailbox. + */ std::shared_ptr alloc(safe::mail_t mail); + /** + * @brief Touchscreen coordinate bounds used to scale absolute input. + */ struct touch_port_t: public platf::touch_port_t { - int env_width; - int env_height; + int env_width; ///< Width of the full capture environment in physical pixels. + int env_height; ///< Height of the full capture environment in physical pixels. // Offset x and y coordinates of the client - float client_offsetX; - float client_offsetY; + float client_offsetX; ///< Horizontal client viewport offset used when scaling touch input. + float client_offsetY; ///< Vertical client viewport offset used when scaling touch input. - float scalar_inv; - float scalar_tpcoords; + float scalar_inv; ///< Inverse scale factor from client coordinates to display coordinates. + float scalar_tpcoords; ///< Scale factor from client coordinates to touch-port coordinates. - int env_logical_width; - int env_logical_height; + int env_logical_width; ///< Width of the full capture environment after display scaling. + int env_logical_height; ///< Height of the full capture environment after display scaling. + /** + * @brief Check whether the touch-port bounds are initialized. + */ explicit operator bool() const { return width != 0 && height != 0 && env_width != 0 && env_height != 0; } diff --git a/src/logging.cpp b/src/logging.cpp index d360d6af9..f5f9eaf7b 100644 --- a/src/logging.cpp +++ b/src/logging.cpp @@ -35,16 +35,16 @@ using namespace std::literals; namespace bl = boost::log; -boost::shared_ptr> sink; +boost::shared_ptr> sink; ///< Sink. -bl::sources::severity_logger verbose(0); // Dominating output -bl::sources::severity_logger debug(1); // Follow what is happening -bl::sources::severity_logger info(2); // Should be informed about -bl::sources::severity_logger warning(3); // Strange events -bl::sources::severity_logger error(4); // Recoverable errors -bl::sources::severity_logger fatal(5); // Unrecoverable errors +bl::sources::severity_logger verbose {0}; ///< Dominating output. +bl::sources::severity_logger debug {1}; ///< Follow what is happening. +bl::sources::severity_logger info {2}; ///< Should be informed about. +bl::sources::severity_logger warning {3}; ///< Strange events. +bl::sources::severity_logger error {4}; ///< Recoverable errors. +bl::sources::severity_logger fatal {5}; ///< Unrecoverable errors. #ifdef SUNSHINE_TESTS -bl::sources::severity_logger tests(10); // Automatic tests output +bl::sources::severity_logger tests {10}; ///< Automatic tests output. #endif BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", int) @@ -60,6 +60,9 @@ namespace logging { sink.reset(); } + /** + * @brief Format a Boost.Log record for Sunshine log output. + */ void formatter(const boost::log::record_view &view, boost::log::formatting_ostream &os) { constexpr const char *message = "Message"; constexpr const char *severity = "Severity"; diff --git a/src/logging.h b/src/logging.h index 02e014679..7cfa38194 100644 --- a/src/logging.h +++ b/src/logging.h @@ -8,14 +8,17 @@ #include #include +/** + * @brief Boost.Log asynchronous text sink used by Sunshine logging. + */ using text_sink = boost::log::sinks::asynchronous_sink; -extern boost::log::sources::severity_logger verbose; -extern boost::log::sources::severity_logger debug; -extern boost::log::sources::severity_logger info; -extern boost::log::sources::severity_logger warning; -extern boost::log::sources::severity_logger error; -extern boost::log::sources::severity_logger fatal; +extern boost::log::sources::severity_logger verbose; ///< Verbose. +extern boost::log::sources::severity_logger debug; ///< Debug. +extern boost::log::sources::severity_logger info; ///< Info. +extern boost::log::sources::severity_logger warning; ///< Warning. +extern boost::log::sources::severity_logger error; ///< Error. +extern boost::log::sources::severity_logger fatal; ///< Fatal. #ifdef SUNSHINE_TESTS extern boost::log::sources::severity_logger tests; #endif @@ -27,10 +30,13 @@ extern boost::log::sources::severity_logger tests; * @brief Handles the initialization and deinitialization of the logging system. */ namespace logging { + /** + * @brief RAII helper that runs shutdown cleanup when destroyed. + */ class deinit_t { public: /** - * @brief A destructor that restores the initial state. + * @brief Restores logging state when the logging subsystem shuts down. */ ~deinit_t(); }; @@ -43,6 +49,12 @@ namespace logging { */ void deinit(); + /** + * @brief Format a Boost.Log record for Sunshine log output. + * + * @param view Boost.Log record view being formatted. + * @param os Boost.Log output stream receiving formatted text. + */ void formatter(const boost::log::record_view &view, boost::log::formatting_ostream &os); /** @@ -101,6 +113,14 @@ namespace logging { template class min_max_avg_periodic_logger { public: + /** + * @brief Construct a periodic logger that reports min, max, and average samples. + * + * @param severity Severity level associated with the log message. + * @param message Message text to log or report. + * @param units Unit label appended to tracked numeric values. + * @param interval_in_seconds Interval in seconds. + */ min_max_avg_periodic_logger(boost::log::sources::severity_logger &severity, std::string_view message, std::string_view units, std::chrono::seconds interval_in_seconds = std::chrono::seconds(20)): severity(severity), message(message), @@ -109,6 +129,11 @@ namespace logging { enabled(config::sunshine.min_log_level <= severity.default_severity()) { } + /** + * @brief Collect a metric sample and write it to the periodic log when due. + * + * @param value Numeric sample to include in the next periodic aggregate. + */ void collect_and_log(const T &value) { if (enabled) { auto print_info = [&](const T &min_value, const T &max_value, double avg_value) { @@ -123,18 +148,31 @@ namespace logging { } } + /** + * @brief Collect a metric sample and write it to the periodic log when due. + * + * @param func Callable used to calculate the tracked sample value. + */ void collect_and_log(std::function func) { if (enabled) { collect_and_log(func()); } } + /** + * @brief Reset the object to its initial empty state. + */ void reset() { if (enabled) { tracker.reset(); } } + /** + * @brief Check whether this periodic logger should emit at the configured severity. + * + * @return True when the logger severity is enabled and sampling should continue. + */ bool is_enabled() const { return enabled; } @@ -165,40 +203,71 @@ namespace logging { */ class time_delta_periodic_logger { public: + /** + * @brief Construct a periodic logger for elapsed-time samples. + * + * @param severity Severity level associated with the log message. + * @param message Message text to log or report. + * @param interval_in_seconds Interval in seconds. + */ time_delta_periodic_logger(boost::log::sources::severity_logger &severity, std::string_view message, std::chrono::seconds interval_in_seconds = std::chrono::seconds(20)): logger(severity, message, "ms", interval_in_seconds) { } + /** + * @brief Store the first timestamp for a measured interval. + * + * @param point Time point used for elapsed-time calculations. + */ void first_point(const std::chrono::steady_clock::time_point &point) { if (logger.is_enabled()) { point1 = point; } } + /** + * @brief Store the current time as the first timestamp. + */ void first_point_now() { if (logger.is_enabled()) { first_point(std::chrono::steady_clock::now()); } } + /** + * @brief Store the second timestamp and log the elapsed interval. + * + * @param point Time point used for elapsed-time calculations. + */ void second_point_and_log(const std::chrono::steady_clock::time_point &point) { if (logger.is_enabled()) { logger.collect_and_log(std::chrono::duration(point - point1).count()); } } + /** + * @brief Store the current time as the second timestamp and log the elapsed interval. + */ void second_point_now_and_log() { if (logger.is_enabled()) { second_point_and_log(std::chrono::steady_clock::now()); } } + /** + * @brief Reset the object to its initial empty state. + */ void reset() { if (logger.is_enabled()) { logger.reset(); } } + /** + * @brief Check whether this periodic logger should emit at the configured severity. + * + * @return True when the logger severity is enabled and sampling should continue. + */ bool is_enabled() const { return logger.is_enabled(); } diff --git a/src/main.cpp b/src/main.cpp index eb42cf1ba..cb73acf00 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -33,12 +33,23 @@ extern "C" { using namespace std::literals; -std::map> signal_handlers; +std::map> signal_handlers; ///< Signal handlers. +/** + * @brief Forward a POSIX signal to the registered Sunshine handler. + * + * @param sig Native signal number being handled. + */ void on_signal_forwarder(int sig) { signal_handlers.at(sig)(); } +/** + * @brief Register the handler invoked for a POSIX signal. + * + * @param sig Native signal number being handled. + * @param fn Signal handler function to install. + */ template void on_signal(int sig, FN &&fn) { signal_handlers.emplace(sig, std::forward(fn)); @@ -46,6 +57,9 @@ void on_signal(int sig, FN &&fn) { std::signal(sig, on_signal_forwarder); } +/** + * @brief Cmd to func. + */ std::map> cmd_to_func { {"creds"sv, [](const char *name, int argc, char **argv) { return args::creds(name, argc, argv); @@ -64,6 +78,15 @@ std::map= 1 -constexpr bool tray_is_enabled = true; +constexpr bool tray_is_enabled = true; ///< Compile-time flag indicating tray support is enabled. #else constexpr bool tray_is_enabled = false; #endif +/** + * @brief Run the main event loop until Sunshine is asked to exit. + * + * @param shutdown_event Shutdown event. + */ void mainThreadLoop(const std::shared_ptr> &shutdown_event) { bool run_loop = false; @@ -122,6 +156,13 @@ void mainThreadLoop(const std::shared_ptr> &shutdown_event) BOOST_LOG(info) << "Main loop has exited"sv; } +/** + * @brief Run the main application or worker loop. + * + * @param argc The number of arguments. + * @param argv The arguments. + * @return Process or platform callback exit code. + */ int main(int argc, char *argv[]) { #ifdef __APPLE__ // Bundle assets are referenced relative to the executable diff --git a/src/main.h b/src/main.h index f1a6e15c4..ce5088c3d 100644 --- a/src/main.h +++ b/src/main.h @@ -6,10 +6,10 @@ /** * @brief Main application entry point. - * @param argc The number of arguments. - * @param argv The arguments. * @examples * main(1, const char* args[] = {"sunshine", nullptr}); * @examples_end + * + * @return Process or platform callback exit code. */ int main(int argc, char *argv[]); diff --git a/src/move_by_copy.h b/src/move_by_copy.h index d1c0181d3..f6d1c2493 100644 --- a/src/move_by_copy.h +++ b/src/move_by_copy.h @@ -18,41 +18,86 @@ namespace move_by_copy_util { template class MoveByCopy { public: + /** + * @brief Wrapped type moved through copy-shaped APIs. + */ typedef T move_type; private: move_type _to_move; public: + /** + * @brief Store a move-only value for transfer through copy-only call sites. + * + * @param to_move Object whose ownership is moved into this wrapper. + */ explicit MoveByCopy(move_type &&to_move): _to_move(std::move(to_move)) { } + /** + * @brief Move the stored object from another wrapper. + * + * @param other Wrapper whose stored object is moved into this object. + */ MoveByCopy(MoveByCopy &&other) = default; + /** + * @brief Copy by moving the stored object out of another wrapper. + * + * @param other Wrapper whose stored object will be moved despite the copy signature. + */ MoveByCopy(const MoveByCopy &other) { *this = other; } + /** + * @brief Move-assign the wrapped object from another wrapper. + * + * @param other Source object whose state is copied or moved into this object. + * @return Reference to this wrapper. + */ MoveByCopy &operator=(MoveByCopy &&other) = default; + /** + * @brief Copy-assign by moving the wrapped object out of the source wrapper. + * + * @param other Source object whose state is copied or moved into this object. + * @return Reference to this wrapper. + */ MoveByCopy &operator=(const MoveByCopy &other) { this->_to_move = std::move(const_cast(other)._to_move); return *this; } + /** + * @brief Move the wrapped object out of this helper. + */ operator move_type() { return std::move(_to_move); } }; + /** + * @brief Copy a move-only value by moving from the source reference. + * + * @param movable Move-only object to transfer through a copy-shaped API. + * @return Wrapper that moves the object when copied. + */ template MoveByCopy cmove(T &movable) { return MoveByCopy(std::move(movable)); } // Do NOT use this unless you are absolutely certain the object to be moved is no longer used by the caller + /** + * @brief Copy-shape wrapper for moving from a const reference. + * + * @param movable Move-only object to transfer through a copy-shaped API. + * @return Wrapper that moves the object when copied. + */ template MoveByCopy const_cmove(const T &movable) { return MoveByCopy(std::move(const_cast(movable))); diff --git a/src/network.cpp b/src/network.cpp index aad65a7a0..2a78b2f95 100644 --- a/src/network.cpp +++ b/src/network.cpp @@ -17,9 +17,15 @@ using namespace std::literals; namespace ip = boost::asio::ip; namespace net { + /** + * @brief Pc ips v4. + */ std::vector pc_ips_v4 { ip::make_network_v4("127.0.0.0/8"sv), }; + /** + * @brief Lan ips v4. + */ std::vector lan_ips_v4 { ip::make_network_v4("192.168.0.0/16"sv), ip::make_network_v4("172.16.0.0/12"sv), @@ -28,14 +34,23 @@ namespace net { ip::make_network_v4("169.254.0.0/16"sv), }; + /** + * @brief Pc ips v6. + */ std::vector pc_ips_v6 { ip::make_network_v6("::1/128"sv), }; + /** + * @brief Lan ips v6. + */ std::vector lan_ips_v6 { ip::make_network_v6("fc00::/7"sv), ip::make_network_v6("fe80::/64"sv), }; + /** + * @brief Convert configuration text to a network enum value. + */ net_e from_enum_string(const std::string_view &view) { if (view == "wan") { return WAN; @@ -47,6 +62,9 @@ namespace net { return PC; } + /** + * @brief Convert a Boost address family to Sunshine network enum value. + */ net_e from_address(const std::string_view &view) { auto addr = normalize_address(ip::make_address(view)); @@ -79,6 +97,9 @@ namespace net { return WAN; } + /** + * @brief Convert a network enum value to configuration text. + */ std::string_view to_enum_string(net_e net) { switch (net) { case PC: @@ -163,6 +184,9 @@ namespace net { } } + /** + * @brief Create an ENet host with the requested address family. + */ host_t host_create(af_e af, ENetAddress &addr, std::uint16_t port) { static std::once_flag enet_init_flag; std::call_once(enet_init_flag, []() { @@ -182,6 +206,9 @@ namespace net { return host; } + /** + * @brief Destroy an ENet host allocated by host_create(). + */ void free_host(ENetHost *host) { std::for_each(host->peers, host->peers + host->peerCount, [](ENetPeer &peer_ref) { ENetPeer *peer = &peer_ref; diff --git a/src/network.h b/src/network.h index 1b99a1c14..679f35b8a 100644 --- a/src/network.h +++ b/src/network.h @@ -16,6 +16,11 @@ #include "utility.h" namespace net { + /** + * @brief Destroy an ENet host allocated by host_create(). + * + * @param host Host name or address to resolve. + */ void free_host(ENetHost *host); /** @@ -29,32 +34,73 @@ namespace net { */ std::uint16_t map_port(int port); + /** + * @brief Owning ENet host pointer released with `enet_host_destroy`. + */ using host_t = util::safe_ptr; + /** + * @brief Raw ENet peer handle owned by an ENet host. + */ using peer_t = ENetPeer *; + /** + * @brief Owning ENet packet pointer released with `enet_packet_destroy`. + */ using packet_t = util::safe_ptr; + /** + * @brief Enumerates supported net options. + */ enum net_e : int { PC, ///< PC LAN, ///< LAN WAN ///< WAN }; + /** + * @brief Enumerates supported af options. + */ enum af_e : int { IPV4, ///< IPv4 only BOTH ///< IPv4 and IPv6 }; + /** + * @brief Convert configuration text to a network enum value. + * + * @param view Boost.Log record view being formatted. + * @return Value converted from enum string. + */ net_e from_enum_string(const std::string_view &view); + /** + * @brief Convert a network enum value to configuration text. + * + * @param net Network scope to convert or format. + * @return Value converted to enum string. + */ std::string_view to_enum_string(net_e net); + /** + * @brief Convert a Boost address family to Sunshine network enum value. + * + * @param view Boost.Log record view being formatted. + * @return Value converted from address. + */ net_e from_address(const std::string_view &view); + /** + * @brief Create an ENet host with the requested address family. + * + * @param af Address family used for socket creation or binding. + * @param addr Network address to bind, parse, or format. + * @param port TCP or UDP port number. + * @return ENet host bound to the requested address and port, or an empty handle on failure. + */ host_t host_create(af_e af, ENetAddress &addr, std::uint16_t port); /** - * @brief Get the address family enum value from a string. + * @brief Convert a config address-family string to the matching enum. * @param view The config option value. - * @return The address family enum value. + * @return Address-family enum represented by the string. */ af_e af_from_enum_string(const std::string_view &view); diff --git a/src/nvenc/nvenc_base.cpp b/src/nvenc/nvenc_base.cpp index 13b2fa03a..2514f1f24 100644 --- a/src/nvenc/nvenc_base.cpp +++ b/src/nvenc/nvenc_base.cpp @@ -13,6 +13,10 @@ #include "src/logging.h" #include "src/utility.h" +/** + * @def MAKE_NVENC_VER(major, minor) + * @brief Macro for MAKE NVENC VER. + */ #define MAKE_NVENC_VER(major, minor) ((major) | ((minor) << 24)) // Make sure we check backwards compatibility when bumping the Video Codec SDK version @@ -659,9 +663,11 @@ namespace nvenc { bool nvenc_base::nvenc_failed(NVENCSTATUS status) { auto status_string = [](NVENCSTATUS status) -> std::string { switch (status) { -#define nvenc_status_case(x) \ - case x: \ - return #x; +#ifndef DOXYGEN + #define nvenc_status_case(x) \ + case x: \ + return #x; +#endif nvenc_status_case(NV_ENC_SUCCESS); nvenc_status_case(NV_ENC_ERR_NO_ENCODE_DEVICE); nvenc_status_case(NV_ENC_ERR_UNSUPPORTED_DEVICE); diff --git a/src/nvenc/nvenc_base.h b/src/nvenc/nvenc_base.h index bb2cc3f1e..9bed24165 100644 --- a/src/nvenc/nvenc_base.h +++ b/src/nvenc/nvenc_base.h @@ -104,11 +104,17 @@ namespace nvenc { return false; } + /** + * @brief Check whether an NVENC API status represents failure. + * + * @param status Native status code returned by the platform API. + * @return True when the status is an NVENC error code. + */ bool nvenc_failed(NVENCSTATUS status); - const NV_ENC_DEVICE_TYPE device_type; + const NV_ENC_DEVICE_TYPE device_type; ///< NVENC device backend used by this encoder instance. - void *encoder = nullptr; + void *encoder = nullptr; ///< Opaque NVENC encoder session handle returned by the driver. struct { uint32_t width = 0; @@ -116,9 +122,9 @@ namespace nvenc { NV_ENC_BUFFER_FORMAT buffer_format = NV_ENC_BUFFER_FORMAT_UNDEFINED; uint32_t ref_frames_in_dpb = 0; bool rfi = false; - } encoder_params; + } encoder_params; ///< Current encoder dimensions, pixel format, and reference-frame settings. - std::string last_nvenc_error_string; + std::string last_nvenc_error_string; ///< Last NVENC error string. // Derived classes set these variables void *device = nullptr; ///< Platform-specific handle of encoding device. diff --git a/src/nvenc/nvenc_colorspace.h b/src/nvenc/nvenc_colorspace.h index a31ca426d..a4330a7c8 100644 --- a/src/nvenc/nvenc_colorspace.h +++ b/src/nvenc/nvenc_colorspace.h @@ -13,10 +13,10 @@ namespace nvenc { * @brief YUV colorspace and color range. */ struct nvenc_colorspace_t { - NV_ENC_VUI_COLOR_PRIMARIES primaries; - NV_ENC_VUI_TRANSFER_CHARACTERISTIC tranfer_function; - NV_ENC_VUI_MATRIX_COEFFS matrix; - bool full_range; + NV_ENC_VUI_COLOR_PRIMARIES primaries; ///< Color primaries written into NVENC VUI metadata. + NV_ENC_VUI_TRANSFER_CHARACTERISTIC tranfer_function; ///< Transfer function written into NVENC VUI metadata. + NV_ENC_VUI_MATRIX_COEFFS matrix; ///< YUV matrix coefficients written into NVENC VUI metadata. + bool full_range; ///< Whether the video range is full-range instead of limited. }; } // namespace nvenc diff --git a/src/nvenc/nvenc_config.h b/src/nvenc/nvenc_config.h index b2143456a..3ae227683 100644 --- a/src/nvenc/nvenc_config.h +++ b/src/nvenc/nvenc_config.h @@ -6,12 +6,18 @@ namespace nvenc { + /** + * @brief Enumerates supported nVENC two pass options. + */ enum class nvenc_two_pass { disabled, ///< Single pass, the fastest and no extra vram quarter_resolution, ///< Larger motion vectors being caught, faster and uses less extra vram full_resolution, ///< Better overall statistics, slower and uses more extra vram }; + /** + * @brief Enumerates supported nVENC split frame encoding options. + */ enum class nvenc_split_frame_encoding { disabled, ///< Disable driver_decides, ///< Let driver decide @@ -23,40 +29,40 @@ namespace nvenc { */ struct nvenc_config { // Quality preset from 1 to 7, higher is slower - int quality_preset = 1; + int quality_preset = 1; ///< Quality preset. // Use optional preliminary pass for better motion vectors, bitrate distribution and stricter VBV(HRD), uses CUDA cores - nvenc_two_pass two_pass = nvenc_two_pass::quarter_resolution; + nvenc_two_pass two_pass = nvenc_two_pass::quarter_resolution; ///< Two pass. // Percentage increase of VBV/HRD from the default single frame, allows low-latency variable bitrate - int vbv_percentage_increase = 0; + int vbv_percentage_increase = 0; ///< Vbv percentage increase. // Improves fades compression, uses CUDA cores - bool weighted_prediction = false; + bool weighted_prediction = false; ///< Enable weighted prediction for NVENC. // Allocate more bitrate to flat regions since they're visually more perceptible, uses CUDA cores - bool adaptive_quantization = false; + bool adaptive_quantization = false; ///< Enable adaptive quantization for NVENC. // Don't use QP below certain value, limits peak image quality to save bitrate - bool enable_min_qp = false; + bool enable_min_qp = false; ///< Enable minimum QP limits for NVENC. // Min QP value for H.264 when enable_min_qp is selected - unsigned min_qp_h264 = 19; + unsigned min_qp_h264 = 19; ///< Min qp h264. // Min QP value for HEVC when enable_min_qp is selected - unsigned min_qp_hevc = 23; + unsigned min_qp_hevc = 23; ///< Min qp HEVC. // Min QP value for AV1 when enable_min_qp is selected - unsigned min_qp_av1 = 23; + unsigned min_qp_av1 = 23; ///< Min qp AV1. // Use CAVLC entropy coding in H.264 instead of CABAC, not relevant and here for historical reasons - bool h264_cavlc = false; + bool h264_cavlc = false; ///< Use CAVLC entropy coding for H.264. // Add filler data to encoded frames to stay at target bitrate, mainly for testing - bool insert_filler_data = false; + bool insert_filler_data = false; ///< Insert filler data to maintain bitrate constraints. // Enable split-frame encoding if the gpu has multiple NVENC hardware clusters - nvenc_split_frame_encoding split_frame_encoding = nvenc_split_frame_encoding::driver_decides; + nvenc_split_frame_encoding split_frame_encoding = nvenc_split_frame_encoding::driver_decides; ///< Split frame encoding. }; } // namespace nvenc diff --git a/src/nvenc/nvenc_d3d11.h b/src/nvenc/nvenc_d3d11.h index 19facee02..73e0ad7a7 100644 --- a/src/nvenc/nvenc_d3d11.h +++ b/src/nvenc/nvenc_d3d11.h @@ -14,10 +14,29 @@ namespace nvenc { + #ifdef DOXYGEN + /** + * @brief COM smart pointer for ID3D11Device. + */ + using ID3D11DevicePtr = ID3D11Device *; + /** + * @brief COM smart pointer for ID3D11Texture2D. + */ + using ID3D11Texture2DPtr = ID3D11Texture2D *; + /** + * @brief COM smart pointer for IDXGIDevice. + */ + using IDXGIDevicePtr = IDXGIDevice *; + /** + * @brief COM smart pointer for IDXGIAdapter. + */ + using IDXGIAdapterPtr = IDXGIAdapter *; + #else _COM_SMARTPTR_TYPEDEF(ID3D11Device, IID_ID3D11Device); _COM_SMARTPTR_TYPEDEF(ID3D11Texture2D, IID_ID3D11Texture2D); _COM_SMARTPTR_TYPEDEF(IDXGIDevice, IID_IDXGIDevice); _COM_SMARTPTR_TYPEDEF(IDXGIAdapter, IID_IDXGIAdapter); + #endif /** * @brief Abstract Direct3D11 NVENC encoder. @@ -25,6 +44,11 @@ namespace nvenc { */ class nvenc_d3d11: public nvenc_base { public: + /** + * @brief Initialize an NVENC session wrapper for D3D11 input textures. + * + * @param device_type NVENC device type used by the encoder session. + */ explicit nvenc_d3d11(NV_ENC_DEVICE_TYPE device_type); ~nvenc_d3d11(); diff --git a/src/nvenc/nvenc_encoded_frame.h b/src/nvenc/nvenc_encoded_frame.h index ff94de463..a625a0e2c 100644 --- a/src/nvenc/nvenc_encoded_frame.h +++ b/src/nvenc/nvenc_encoded_frame.h @@ -11,13 +11,13 @@ namespace nvenc { /** - * @brief Encoded frame. + * @brief Encoded NVENC output frame and metadata needed by the packetizer. */ struct nvenc_encoded_frame { - std::vector data; - uint64_t frame_index = 0; - bool idr = false; - bool after_ref_frame_invalidation = false; + std::vector data; ///< Encoded bitstream bytes returned by NVENC. + uint64_t frame_index = 0; ///< Capture-frame index associated with the encoded data. + bool idr = false; ///< Whether the encoded frame is an IDR frame. + bool after_ref_frame_invalidation = false; ///< Whether the frame follows reference-frame invalidation. }; } // namespace nvenc diff --git a/src/nvenc/nvenc_utils.cpp b/src/nvenc/nvenc_utils.cpp index 1d4271a0f..cf716d7a4 100644 --- a/src/nvenc/nvenc_utils.cpp +++ b/src/nvenc/nvenc_utils.cpp @@ -11,6 +11,9 @@ namespace nvenc { #ifdef _WIN32 + /** + * @brief Convert an NVENC buffer format to the matching DXGI format. + */ DXGI_FORMAT dxgi_format_from_nvenc_format(NV_ENC_BUFFER_FORMAT format) { switch (format) { case NV_ENC_BUFFER_FORMAT_YUV420_10BIT: @@ -31,6 +34,9 @@ namespace nvenc { } #endif + /** + * @brief Convert a Sunshine pixel format to the matching NVENC buffer format. + */ NV_ENC_BUFFER_FORMAT nvenc_format_from_sunshine_format(platf::pix_fmt_e format) { switch (format) { case platf::pix_fmt_e::nv12: @@ -53,6 +59,9 @@ namespace nvenc { } } + /** + * @brief Convert Sunshine colorspace metadata to NVENC VUI metadata. + */ nvenc_colorspace_t nvenc_colorspace_from_sunshine_colorspace(const video::sunshine_colorspace_t &sunshine_colorspace) { nvenc_colorspace_t colorspace; diff --git a/src/nvenc/nvenc_utils.h b/src/nvenc/nvenc_utils.h index e8b48eee4..439b493ff 100644 --- a/src/nvenc/nvenc_utils.h +++ b/src/nvenc/nvenc_utils.h @@ -20,11 +20,29 @@ namespace nvenc { #ifdef _WIN32 + /** + * @brief Convert an NVENC buffer format to the matching DXGI format. + * + * @param format Pixel, audio, or protocol format being converted. + * @return DXGI texture format that matches the NVENC buffer format. + */ DXGI_FORMAT dxgi_format_from_nvenc_format(NV_ENC_BUFFER_FORMAT format); #endif + /** + * @brief Convert a Sunshine pixel format to the matching NVENC buffer format. + * + * @param format Pixel, audio, or protocol format being converted. + * @return NVENC buffer format compatible with the Sunshine pixel format. + */ NV_ENC_BUFFER_FORMAT nvenc_format_from_sunshine_format(platf::pix_fmt_e format); + /** + * @brief Convert Sunshine colorspace metadata to NVENC VUI metadata. + * + * @param sunshine_colorspace Sunshine colorspace. + * @return NVENC colorspace and VUI fields for the Sunshine metadata. + */ nvenc_colorspace_t nvenc_colorspace_from_sunshine_colorspace(const video::sunshine_colorspace_t &sunshine_colorspace); } // namespace nvenc diff --git a/src/nvhttp.cpp b/src/nvhttp.cpp index 3f30a9804..0ef9df418 100644 --- a/src/nvhttp.cpp +++ b/src/nvhttp.cpp @@ -45,10 +45,19 @@ namespace nvhttp { namespace fs = std::filesystem; namespace pt = boost::property_tree; - crypto::cert_chain_t cert_chain; + crypto::cert_chain_t cert_chain; ///< Certificate chain presented by Sunshine's GameStream HTTPS server. + /** + * @brief HTTPS server backend that adds Sunshine's client-certificate verification. + */ class SunshineHTTPSServer: public SimpleWeb::ServerBase { public: + /** + * @brief Initialize the HTTPS server with Sunshine's certificate and key files. + * + * @param certification_file Path to the server certificate file. + * @param private_key_file Path to the matching private key file. + */ SunshineHTTPSServer(const std::string &certification_file, const std::string &private_key_file): ServerBase::ServerBase(443), context(boost::asio::ssl::context::tls_server) { @@ -59,12 +68,15 @@ namespace nvhttp { context.use_private_key_file(private_key_file, boost::asio::ssl::context::pem); } - std::function verify; - std::function, std::shared_ptr)> on_verify_failed; + std::function verify; ///< Callback that validates a client's TLS certificate after handshake. + std::function, std::shared_ptr)> on_verify_failed; ///< Handler used to return the pairing challenge when client verification fails. protected: - boost::asio::ssl::context context; + boost::asio::ssl::context context; ///< TLS server context configured with Sunshine's certificate and protocol policy. + /** + * @brief Enable client-certificate verification after the listening socket is bound. + */ void after_bind() override { if (verify) { context.set_verify_mode(boost::asio::ssl::verify_peer | boost::asio::ssl::verify_fail_if_no_peer_cert | boost::asio::ssl::verify_client_once); @@ -76,6 +88,9 @@ namespace nvhttp { } // This is Server::accept() with SSL validation support added + /** + * @brief Accept a pending connection and arm the server for the next client. + */ void accept() override { auto connection = create_connection(*io_service, context); @@ -120,44 +135,85 @@ namespace nvhttp { } }; + /** + * @brief HTTPS server type used for GameStream endpoints requiring TLS. + */ using https_server_t = SunshineHTTPSServer; + /** + * @brief Plain HTTP server type used for GameStream endpoints without TLS. + */ using http_server_t = SimpleWeb::Server; + /** + * @brief Internal HTTPS credential paths for the configuration server. + */ struct conf_intern_t { - std::string servercert; - std::string pkey; - } conf_intern; + std::string servercert; ///< Server certificate PEM string. + std::string pkey; ///< Private key PEM string or path. + } conf_intern; ///< TLS credential paths loaded from Sunshine's runtime configuration. + /** + * @brief Certificate entry associated with a client name and UUID. + */ struct named_cert_t { - std::string name; - std::string uuid; - std::string cert; - bool enabled = true; + std::string name; ///< Human-readable name for this item. + std::string uuid; ///< Persistent Moonlight client UUID associated with the certificate. + std::string cert; ///< Certificate PEM string or path. + bool enabled = true; ///< Whether this persisted client entry may connect. }; + /** + * @brief Persisted pairing data for one Moonlight client. + */ struct client_t { - std::vector named_devices; + std::vector named_devices; ///< Persisted Moonlight clients allowed to pair or reconnect. }; // uniqueID, session - std::unordered_map map_id_sess; - client_t client_root; - std::atomic session_id_counter; + std::unordered_map map_id_sess; ///< Pairing sessions keyed by temporary unique ID. + client_t client_root; ///< In-memory representation of the paired-client database. + std::atomic session_id_counter; ///< Monotonic counter used to allocate GameStream session IDs. // Set by TLS verify callback, read by launch/resume handler (single-threaded HTTPS server) - std::string last_verified_client_cert; // NOSONAR(cpp:S5421) - intentionally mutable global + std::string last_verified_client_cert; ///< Last client certificate accepted by the TLS verify callback. // NOSONAR(cpp:S5421) - intentionally mutable global + /** + * @brief Case-insensitive map used for HTTP headers and query parameters. + */ using args_t = SimpleWeb::CaseInsensitiveMultimap; + /** + * @brief Shared HTTPS response object passed to GameStream handlers. + */ using resp_https_t = std::shared_ptr::Response>; + /** + * @brief Shared HTTPS request object received by GameStream handlers. + */ using req_https_t = std::shared_ptr::Request>; + /** + * @brief Shared HTTP response object passed to redirect and discovery handlers. + */ using resp_http_t = std::shared_ptr::Response>; + /** + * @brief Shared HTTP request object received by redirect and discovery handlers. + */ using req_http_t = std::shared_ptr::Request>; + /** + * @brief Certificate operations supported by the pairing API. + */ enum class op_e { ADD, ///< Add certificate REMOVE ///< Remove certificate }; + /** + * @brief Read a named query argument from the HTTP request map. + * + * @param args Parsed query-string argument map. + * @param name Query parameter name to read. + * @param default_value Value returned when the parameter is absent. + * @return Query parameter value, default value, or an empty string. + */ std::string get_arg(const args_t &args, const char *name, const char *default_value = nullptr) { auto it = args.find(name); if (it == std::end(args)) { @@ -170,6 +226,9 @@ namespace nvhttp { return it->second; } + /** + * @brief Persist the current state to its backing store. + */ void save_state() { pt::ptree root; @@ -207,6 +266,9 @@ namespace nvhttp { } } + /** + * @brief Load state from its backing store. + */ void load_state() { if (!fs::exists(config::nvhttp.file_state)) { BOOST_LOG(info) << "File "sv << config::nvhttp.file_state << " doesn't exist"sv; @@ -272,6 +334,12 @@ namespace nvhttp { client_root = client; } + /** + * @brief Add authorized client data. + * + * @param name Human-readable name to assign. + * @param cert Certificate data or object used by the operation. + */ void add_authorized_client(const std::string &name, std::string &&cert) { client_t &client = client_root; named_cert_t named_cert; @@ -285,6 +353,13 @@ namespace nvhttp { } } + /** + * @brief Create launch session. + * + * @param host_audio Host audio. + * @param args Arguments forwarded to the callable or parser. + * @return Constructed launch session object. + */ std::shared_ptr make_launch_session(bool host_audio, const args_t &args) { auto launch_session = std::make_shared(); @@ -348,6 +423,13 @@ namespace nvhttp { map_id_sess.erase(sess.client.uniqueID); } + /** + * @brief Return the GameStream pairing failure response. + * + * @param sess Pairing session that owns the request state. + * @param tree XML property tree used for the response body. + * @param status_msg Status msg. + */ void fail_pair(pair_session_t &sess, pt::ptree &tree, const std::string status_msg) { tree.put("root.paired", 0); tree.put("root..status_code", 400); @@ -355,6 +437,13 @@ namespace nvhttp { remove_session(sess); // Security measure, delete the session when something went wrong and force a re-pair } + /** + * @brief Return the server certificate text for pairing responses. + * + * @param sess Pairing session that owns the request state. + * @param tree XML property tree used for the response body. + * @param pin PIN supplied by the client during pairing. + */ void getservercert(pair_session_t &sess, pt::ptree &tree, const std::string &pin) { if (sess.last_phase != PAIR_PHASE::NONE) { fail_pair(sess, tree, "Out of order call to getservercert"); @@ -379,6 +468,13 @@ namespace nvhttp { tree.put("root..status_code", 200); } + /** + * @brief Handle the client-challenge phase of GameStream pairing. + * + * @param sess Pairing session that owns the request state. + * @param tree XML property tree used for the response body. + * @param challenge Client challenge bytes from the pairing request. + */ void clientchallenge(pair_session_t &sess, pt::ptree &tree, const std::string &challenge) { if (sess.last_phase != PAIR_PHASE::GETSERVERCERT) { fail_pair(sess, tree, "Out of order call to clientchallenge"); @@ -422,6 +518,13 @@ namespace nvhttp { tree.put("root..status_code", 200); } + /** + * @brief Handle the server-challenge response phase of GameStream pairing. + * + * @param sess Pairing session that owns the request state. + * @param tree XML property tree used for the response body. + * @param encrypted_response Encrypted response. + */ void serverchallengeresp(pair_session_t &sess, pt::ptree &tree, const std::string &encrypted_response) { if (sess.last_phase != PAIR_PHASE::CLIENTCHALLENGE) { fail_pair(sess, tree, "Out of order call to serverchallengeresp"); @@ -451,6 +554,14 @@ namespace nvhttp { tree.put("root..status_code", 200); } + /** + * @brief Handle the client pairing-secret phase of GameStream pairing. + * + * @param sess Pairing session that owns the request state. + * @param add_cert Add cert. + * @param tree XML property tree used for the response body. + * @param client_pairing_secret Client pairing secret. + */ void clientpairingsecret(pair_session_t &sess, std::shared_ptr> &add_cert, pt::ptree &tree, const std::string &client_pairing_secret) { if (sess.last_phase != PAIR_PHASE::SERVERCHALLENGERESP) { fail_pair(sess, tree, "Out of order call to clientpairingsecret"); @@ -504,16 +615,27 @@ namespace nvhttp { template struct tunnel; + /** + * @brief HTTPS tunnel session used for encrypted client requests. + */ template<> struct tunnel { - static auto constexpr to_string = "HTTPS"sv; + static auto constexpr to_string = "HTTPS"sv; ///< To string. }; + /** + * @brief Plain HTTP server wrapper used for non-TLS endpoints. + */ template<> struct tunnel { - static auto constexpr to_string = "NONE"sv; + static auto constexpr to_string = "NONE"sv; ///< To string. }; + /** + * @brief Write req details to the log. + * + * @param request HTTP request data from the client. + */ template void print_req(std::shared_ptr::Request> request) { BOOST_LOG(debug) << "TUNNEL :: "sv << tunnel::to_string; @@ -534,6 +656,12 @@ namespace nvhttp { BOOST_LOG(debug) << " [--] "sv; } + /** + * @brief Return a GameStream HTTP not-found response. + * + * @param response HTTP response object to populate. + * @param request HTTP request data from the client. + */ template void not_found(std::shared_ptr::Response> response, std::shared_ptr::Request> request) { print_req(request); @@ -553,6 +681,13 @@ namespace nvhttp { response->close_connection_after_response = true; } + /** + * @brief Dispatch the top-level GameStream pairing request by phase. + * + * @param add_cert Add cert. + * @param response HTTP response object to populate. + * @param request HTTP request data from the client. + */ template void pair(std::shared_ptr> &add_cert, std::shared_ptr::Response> response, std::shared_ptr::Request> request) { print_req(request); @@ -684,6 +819,11 @@ namespace nvhttp { return true; } + /** + * @brief Get codec mode flags. + * + * @return Moonlight codec capability bitmask for the currently probed encoders. + */ uint32_t get_codec_mode_flags() { uint32_t codec_mode_flags = SCM_H264; if (video::last_encoder_probe_supported_yuv444_for_codec[0]) { @@ -717,6 +857,12 @@ namespace nvhttp { return codec_mode_flags; } + /** + * @brief Build the GameStream server-info response. + * + * @param response HTTP response object to populate. + * @param request HTTP request data from the client. + */ template void serverinfo(std::shared_ptr::Response> response, std::shared_ptr::Request> request) { print_req(request); @@ -801,6 +947,12 @@ namespace nvhttp { return named_cert_nodes; } + /** + * @brief Build the GameStream application list response. + * + * @param response HTTP response object to populate. + * @param request HTTP request data from the client. + */ void applist(resp_https_t response, req_https_t request) { print_req(request); @@ -829,6 +981,13 @@ namespace nvhttp { } } + /** + * @brief Launch the requested application for a GameStream session. + * + * @param host_audio Host audio. + * @param response HTTP response object to populate. + * @param request HTTP request data from the client. + */ void launch(bool &host_audio, resp_https_t response, req_https_t request) { print_req(request); @@ -940,6 +1099,13 @@ namespace nvhttp { revert_display_configuration = false; } + /** + * @brief Resume an existing GameStream session. + * + * @param host_audio Host audio. + * @param response HTTP response object to populate. + * @param request HTTP request data from the client. + */ void resume(bool &host_audio, resp_https_t response, req_https_t request) { print_req(request); @@ -1031,6 +1197,12 @@ namespace nvhttp { rtsp_stream::launch_session_raise(launch_session); } + /** + * @brief Check whether cel. + * + * @param response HTTP response object to populate. + * @param request HTTP request data from the client. + */ void cancel(resp_https_t response, req_https_t request) { print_req(request); @@ -1056,6 +1228,12 @@ namespace nvhttp { display_device::revert_configuration(); } + /** + * @brief Return an application asset requested by the client. + * + * @param response HTTP response object to populate. + * @param request HTTP request data from the client. + */ void appasset(resp_https_t response, req_https_t request) { print_req(request); @@ -1074,6 +1252,12 @@ namespace nvhttp { conf_intern.servercert = cert; } + /** + * @brief Check whether a paired client certificate is allowed to connect. + * + * @param cert_pem PEM-encoded client certificate to look up. + * @return True when the client certificate belongs to an enabled device. + */ bool is_client_enabled(const std::string_view cert_pem); void start() { @@ -1266,6 +1450,9 @@ namespace nvhttp { return false; } + /** + * @brief Get cert by UUID. + */ std::string get_cert_by_uuid(const std::string_view uuid) { for (const auto &named_cert : client_root.named_devices) { if (named_cert.uuid == uuid) { @@ -1275,6 +1462,9 @@ namespace nvhttp { return {}; } + /** + * @brief Check whether a paired client certificate is allowed to connect. + */ bool is_client_enabled(const std::string_view cert_pem) { const client_t &client = client_root; for (const auto &named_cert : client.named_devices) { diff --git a/src/nvhttp.h b/src/nvhttp.h index f502c1b35..2542df1ec 100644 --- a/src/nvhttp.h +++ b/src/nvhttp.h @@ -59,8 +59,17 @@ namespace nvhttp { */ void setup(const std::string &pkey, const std::string &cert); + /** + * @brief Simple-Web-Server HTTPS backend configured for Sunshine certificate handling. + */ class SunshineHTTPS: public SimpleWeb::HTTPS { public: + /** + * @brief Construct an HTTPS connection using Sunshine's TLS context. + * + * @param io_context Boost.Asio context used for network operations. + * @param ctx TLS context configured with Sunshine's certificate and key. + */ SunshineHTTPS(boost::asio::io_context &io_context, boost::asio::ssl::context &ctx): SimpleWeb::HTTPS(io_context, ctx) { } @@ -72,6 +81,9 @@ namespace nvhttp { } }; + /** + * @brief Enumerates supported pAIR PHASE options. + */ enum class PAIR_PHASE { NONE, ///< Sunshine is not in a pairing phase GETSERVERCERT, ///< Sunshine is in the get server certificate phase @@ -80,18 +92,21 @@ namespace nvhttp { CLIENTPAIRINGSECRET ///< Sunshine is in the client pairing secret phase }; + /** + * @brief Pairing handshake state exchanged with a Moonlight client. + */ struct pair_session_t { struct { std::string uniqueID = {}; std::string cert = {}; std::string name = {}; - } client; + } client; ///< Client object or client certificate data owned by this state.. - std::unique_ptr cipher_key = {}; - std::vector clienthash = {}; + std::unique_ptr cipher_key = {}; ///< Cipher key. + std::vector clienthash = {}; ///< Client certificate hash used during pairing. - std::string serversecret = {}; - std::string serverchallenge = {}; + std::string serversecret = {}; ///< Server pairing secret. + std::string serverchallenge = {}; ///< Server challenge sent during pairing. struct { util::Either< @@ -99,7 +114,7 @@ namespace nvhttp { std::shared_ptr::Response>> response; std::string salt = {}; - } async_insert_pin; + } async_insert_pin; ///< Async insert pin. /** * @brief used as a security measure to prevent out of order calls @@ -122,6 +137,9 @@ namespace nvhttp { * in order to be used to decrypt_symmetric in the next phases. * * At this stage we only have to send back our public certificate. + * @param sess Pairing session that owns the request state. + * @param tree XML property tree used for the response body. + * @param pin PIN supplied by the client during pairing. */ void getservercert(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &pin); @@ -136,6 +154,9 @@ namespace nvhttp { * - Server secret: a randomly generated secret * * The hash + server_challenge will then be AES encrypted and sent as the `challengeresponse` in the returned XML + * @param sess Pairing session that owns the request state. + * @param tree XML property tree used for the response body. + * @param challenge Client challenge bytes from the pairing request. */ void clientchallenge(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &challenge); @@ -145,6 +166,9 @@ namespace nvhttp { * Moonlight will send back a `serverchallengeresp`: an AES encrypted client hash, * we have to send back the `pairingsecret`: * using our private key we have to sign the certificate_signature + server_secret (generated in phase 2) + * @param sess Pairing session that owns the request state. + * @param tree XML property tree used for the response body. + * @param encrypted_response Encrypted response. */ void serverchallengeresp(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &encrypted_response); @@ -161,6 +185,10 @@ namespace nvhttp { * * Then using the client certificate public key we should be able to verify that * the client secret has been signed by Moonlight + * @param sess Pairing session that owns the request state. + * @param add_cert Add cert. + * @param tree XML property tree used for the response body. + * @param client_pairing_secret Client pairing secret. */ void clientpairingsecret(pair_session_t &sess, std::shared_ptr> &add_cert, boost::property_tree::ptree &tree, const std::string &client_pairing_secret); @@ -181,6 +209,8 @@ namespace nvhttp { * @examples * nvhttp::unpair_client("4D7BB2DD-5704-A405-B41C-891A022932E1"); * @examples_end + * + * @return True when the client entry was found and removed. */ bool unpair_client(std::string_view uuid); @@ -191,6 +221,12 @@ namespace nvhttp { * @return true if the client was found and updated. */ bool set_client_enabled(std::string_view uuid, bool enabled); + /** + * @brief Get cert by UUID. + * + * @param uuid Client UUID being looked up or removed. + * @return PEM certificate for the paired client, or an empty string when unknown. + */ std::string get_cert_by_uuid(std::string_view uuid); /** diff --git a/src/platform/common.h b/src/platform/common.h index e0e87928c..2bbcee8ff 100644 --- a/src/platform/common.h +++ b/src/platform/common.h @@ -57,6 +57,9 @@ namespace boost { class group; template class basic_environment; + /** + * @brief Map of environment variable names to values. + */ typedef basic_environment environment; } // namespace process::v1 } // namespace boost @@ -71,36 +74,42 @@ namespace nvenc { namespace platf { // Limited by bits in activeGamepadMask - constexpr auto MAX_GAMEPADS = 16; + constexpr auto MAX_GAMEPADS = 16; ///< Maximum number of simultaneously tracked gamepads. - constexpr std::uint32_t DPAD_UP = 0x0001; - constexpr std::uint32_t DPAD_DOWN = 0x0002; - constexpr std::uint32_t DPAD_LEFT = 0x0004; - constexpr std::uint32_t DPAD_RIGHT = 0x0008; - constexpr std::uint32_t START = 0x0010; - constexpr std::uint32_t BACK = 0x0020; - constexpr std::uint32_t LEFT_STICK = 0x0040; - constexpr std::uint32_t RIGHT_STICK = 0x0080; - constexpr std::uint32_t LEFT_BUTTON = 0x0100; - constexpr std::uint32_t RIGHT_BUTTON = 0x0200; - constexpr std::uint32_t HOME = 0x0400; - constexpr std::uint32_t A = 0x1000; - constexpr std::uint32_t B = 0x2000; - constexpr std::uint32_t X = 0x4000; - constexpr std::uint32_t Y = 0x8000; - constexpr std::uint32_t PADDLE1 = 0x010000; - constexpr std::uint32_t PADDLE2 = 0x020000; - constexpr std::uint32_t PADDLE3 = 0x040000; - constexpr std::uint32_t PADDLE4 = 0x080000; - constexpr std::uint32_t TOUCHPAD_BUTTON = 0x100000; - constexpr std::uint32_t MISC_BUTTON = 0x200000; + constexpr std::uint32_t DPAD_UP = 0x0001; ///< Moonlight gamepad button mask bit for D-pad up. + constexpr std::uint32_t DPAD_DOWN = 0x0002; ///< Moonlight gamepad button mask bit for D-pad down. + constexpr std::uint32_t DPAD_LEFT = 0x0004; ///< Moonlight gamepad button mask bit for D-pad left. + constexpr std::uint32_t DPAD_RIGHT = 0x0008; ///< Moonlight gamepad button mask bit for D-pad right. + constexpr std::uint32_t START = 0x0010; ///< Moonlight gamepad button mask bit for Start. + constexpr std::uint32_t BACK = 0x0020; ///< Moonlight gamepad button mask bit for Back. + constexpr std::uint32_t LEFT_STICK = 0x0040; ///< Moonlight gamepad button mask bit for left stick press. + constexpr std::uint32_t RIGHT_STICK = 0x0080; ///< Moonlight gamepad button mask bit for right stick press. + constexpr std::uint32_t LEFT_BUTTON = 0x0100; ///< Moonlight gamepad button mask bit for left shoulder. + constexpr std::uint32_t RIGHT_BUTTON = 0x0200; ///< Moonlight gamepad button mask bit for right shoulder. + constexpr std::uint32_t HOME = 0x0400; ///< Moonlight gamepad button mask bit for Home. + constexpr std::uint32_t A = 0x1000; ///< Moonlight gamepad button mask bit for A. + constexpr std::uint32_t B = 0x2000; ///< Moonlight gamepad button mask bit for B. + constexpr std::uint32_t X = 0x4000; ///< Moonlight gamepad button mask bit for X. + constexpr std::uint32_t Y = 0x8000; ///< Moonlight gamepad button mask bit for Y. + constexpr std::uint32_t PADDLE1 = 0x010000; ///< Moonlight gamepad button mask bit for paddle 1. + constexpr std::uint32_t PADDLE2 = 0x020000; ///< Moonlight gamepad button mask bit for paddle 2. + constexpr std::uint32_t PADDLE3 = 0x040000; ///< Moonlight gamepad button mask bit for paddle 3. + constexpr std::uint32_t PADDLE4 = 0x080000; ///< Moonlight gamepad button mask bit for paddle 4. + constexpr std::uint32_t TOUCHPAD_BUTTON = 0x100000; ///< Moonlight gamepad button mask bit for touchpad click. + constexpr std::uint32_t MISC_BUTTON = 0x200000; ///< Moonlight gamepad button mask bit for the miscellaneous button. + /** + * @brief Gamepad type exposed to clients and why it may be disabled. + */ struct supported_gamepad_t { - std::string name; - bool is_enabled; - std::string reason_disabled; + std::string name; ///< Human-readable name for this item. + bool is_enabled; ///< Whether this gamepad type is currently available. + std::string reason_disabled; ///< Human-readable reason the gamepad type is disabled. }; + /** + * @brief Enumerates supported gamepad feedback options. + */ enum class gamepad_feedback_e { rumble, ///< Rumble rumble_triggers, ///< Rumble triggers @@ -109,7 +118,18 @@ namespace platf { set_adaptive_triggers, ///< Set adaptive triggers }; + /** + * @brief Feedback command sent from Sunshine to a virtual gamepad. + */ struct gamepad_feedback_msg_t { + /** + * @brief Create a rumble object or message. + * + * @param id Identifier for the controller, session, display, or resource. + * @param lowfreq Low-frequency rumble motor intensity. + * @param highfreq High-frequency rumble motor intensity. + * @return Constructed rumble object. + */ static gamepad_feedback_msg_t make_rumble(std::uint16_t id, std::uint16_t lowfreq, std::uint16_t highfreq) { gamepad_feedback_msg_t msg; msg.type = gamepad_feedback_e::rumble; @@ -118,6 +138,14 @@ namespace platf { return msg; } + /** + * @brief Create rumble triggers. + * + * @param id Identifier for the controller, session, display, or resource. + * @param left Left trigger or motor payload for the feedback command. + * @param right Right trigger or motor payload for the feedback command. + * @return Constructed rumble triggers object. + */ static gamepad_feedback_msg_t make_rumble_triggers(std::uint16_t id, std::uint16_t left, std::uint16_t right) { gamepad_feedback_msg_t msg; msg.type = gamepad_feedback_e::rumble_triggers; @@ -126,6 +154,14 @@ namespace platf { return msg; } + /** + * @brief Motion-event feedback command payload for a controller. + * + * @param id Identifier for the controller, session, display, or resource. + * @param motion_type Motion type. + * @param report_rate Report rate. + * @return Constructed motion event state object. + */ static gamepad_feedback_msg_t make_motion_event_state(std::uint16_t id, std::uint8_t motion_type, std::uint16_t report_rate) { gamepad_feedback_msg_t msg; msg.type = gamepad_feedback_e::set_motion_event_state; @@ -135,6 +171,15 @@ namespace platf { return msg; } + /** + * @brief Create RGB led. + * + * @param id Identifier for the controller, session, display, or resource. + * @param r Red color channel value. + * @param g Green color channel value. + * @param b Blue color channel value. + * @return Constructed RGB led object. + */ static gamepad_feedback_msg_t make_rgb_led(std::uint16_t id, std::uint8_t r, std::uint8_t g, std::uint8_t b) { gamepad_feedback_msg_t msg; msg.type = gamepad_feedback_e::set_rgb_led; @@ -143,6 +188,17 @@ namespace platf { return msg; } + /** + * @brief Create adaptive triggers. + * + * @param id Identifier for the controller, session, display, or resource. + * @param event_flags Event flags. + * @param type_left Type left. + * @param type_right Type right. + * @param left Left trigger or motor payload for the feedback command. + * @param right Right trigger or motor payload for the feedback command. + * @return Constructed adaptive triggers object. + */ static gamepad_feedback_msg_t make_adaptive_triggers(std::uint16_t id, uint8_t event_flags, uint8_t type_left, uint8_t type_right, const std::array &left, const std::array &right) { gamepad_feedback_msg_t msg; msg.type = gamepad_feedback_e::set_adaptive_triggers; @@ -151,8 +207,8 @@ namespace platf { return msg; } - gamepad_feedback_e type; - std::uint16_t id; + gamepad_feedback_e type; ///< Feedback command type stored in the union payload. + std::uint16_t id; ///< Controller identifier associated with this message. union { struct { @@ -184,12 +240,18 @@ namespace platf { std::array left; std::array right; } adaptive_triggers; - } data; + } data; ///< Controller feedback payload for the selected feedback type. }; + /** + * @brief Queue used to deliver controller feedback commands to the platform backend. + */ using feedback_queue_t = safe::mail_raw_t::queue_t; namespace speaker { + /** + * @brief Enumerates supported speaker options. + */ enum speaker_e { FRONT_LEFT, ///< Front left FRONT_RIGHT, ///< Front right @@ -202,10 +264,16 @@ namespace platf { MAX_SPEAKERS, ///< Maximum number of speakers }; + /** + * @brief Moonlight speaker order for stereo audio. + */ constexpr std::uint8_t map_stereo[] { FRONT_LEFT, FRONT_RIGHT }; + /** + * @brief Moonlight speaker order for 5.1 surround audio. + */ constexpr std::uint8_t map_surround51[] { FRONT_LEFT, FRONT_RIGHT, @@ -214,6 +282,9 @@ namespace platf { BACK_LEFT, BACK_RIGHT, }; + /** + * @brief Moonlight speaker order for 7.1 surround audio. + */ constexpr std::uint8_t map_surround71[] { FRONT_LEFT, FRONT_RIGHT, @@ -226,6 +297,9 @@ namespace platf { }; } // namespace speaker + /** + * @brief Enumerates supported mem type options. + */ enum class mem_type_e { system, ///< System memory vaapi, ///< VAAPI @@ -236,6 +310,9 @@ namespace platf { unknown ///< Unknown }; + /** + * @brief Enumerates supported pix fmt options. + */ enum class pix_fmt_e { yuv420p, ///< YUV 4:2:0 yuv420p10, ///< YUV 4:2:0 10-bit @@ -248,11 +325,19 @@ namespace platf { unknown ///< Unknown }; + /** + * @brief Convert a Sunshine pixel format enum to its string name. + * + * @param pix_fmt Sunshine pixel format to convert or allocate for. + * @return Value converted from pix fmt. + */ inline std::string_view from_pix_fmt(pix_fmt_e pix_fmt) { using namespace std::literals; -#define _CONVERT(x) \ - case pix_fmt_e::x: \ - return #x##sv +#ifndef DOXYGEN + #define _CONVERT(x) \ + case pix_fmt_e::x: \ + return #x##sv +#endif switch (pix_fmt) { _CONVERT(yuv420p); _CONVERT(yuv420p10); @@ -270,105 +355,150 @@ namespace platf { } // Dimensions for touchscreen input + /** + * @brief Touchscreen coordinate bounds used to scale absolute input. + */ struct touch_port_t { - int offset_x; - int offset_y; - int width; - int height; - int logical_width; - int logical_height; + int offset_x; ///< Horizontal offset in physical pixels. + int offset_y; ///< Vertical offset in physical pixels. + int width; ///< Frame or display width in pixels. + int height; ///< Frame or display height in pixels. + int logical_width; ///< Logical width after display scaling. + int logical_height; ///< Logical height after display scaling. }; // These values must match Limelight-internal.h's SS_FF_* constants! namespace platform_caps { + /** + * @brief Bitset containing platform capability flags. + */ typedef uint32_t caps_t; + /** + * @brief Capability bit indicating native pen and touch support. + */ constexpr caps_t pen_touch = 0x01; // Pen and touch events + /** + * @brief Capability bit indicating controller touchpad support. + */ constexpr caps_t controller_touch = 0x02; // Controller touch events }; // namespace platform_caps + /** + * @brief Button and axis state received for a virtual gamepad. + */ struct gamepad_state_t { - std::uint32_t buttonFlags; - std::uint8_t lt; - std::uint8_t rt; - std::int16_t lsX; - std::int16_t lsY; - std::int16_t rsX; - std::int16_t rsY; + std::uint32_t buttonFlags; ///< Moonlight button mask for the current gamepad state. + std::uint8_t lt; ///< Left trigger value. + std::uint8_t rt; ///< Right trigger value. + std::int16_t lsX; ///< Left stick X-axis value. + std::int16_t lsY; ///< Left stick Y-axis value. + std::int16_t rsX; ///< Right stick X-axis value. + std::int16_t rsY; ///< Right stick Y-axis value. }; + /** + * @brief Global and client-relative identifiers for a virtual gamepad. + */ struct gamepad_id_t { // The global index is used when looking up gamepads in the platform's // gamepad array. It identifies gamepads uniquely among all clients. - int globalIndex; + int globalIndex; ///< Index unique across all connected clients. // The client-relative index is the controller number as reported by the // client. It must be used when communicating back to the client via // the input feedback queue. - std::uint8_t clientRelativeIndex; + std::uint8_t clientRelativeIndex; ///< Client relative index. }; + /** + * @brief Capabilities reported when a controller is connected. + */ struct gamepad_arrival_t { - std::uint8_t type; - std::uint16_t capabilities; - std::uint32_t supportedButtons; + std::uint8_t type; ///< Protocol or controller type discriminator. + std::uint16_t capabilities; ///< Capability flags advertised by the controller. + std::uint32_t supportedButtons; ///< Button mask supported by the connected controller. }; + /** + * @brief Touchpad contact data reported by a controller. + */ struct gamepad_touch_t { - gamepad_id_t id; - std::uint8_t eventType; - std::uint32_t pointerId; - float x; - float y; - float pressure; + gamepad_id_t id; ///< Gamepad identifier for the event. + std::uint8_t eventType; ///< Moonlight event type for the input packet. + std::uint32_t pointerId; ///< Client-provided pointer identifier for a touch contact. + float x; ///< Horizontal coordinate or vector component. + float y; ///< Vertical coordinate or vector component. + float pressure; ///< Contact pressure reported by the client. }; + /** + * @brief Accelerometer or gyroscope sample from a controller. + */ struct gamepad_motion_t { - gamepad_id_t id; - std::uint8_t motionType; + gamepad_id_t id; ///< Gamepad identifier for the event. + std::uint8_t motionType; ///< Motion type. // Accel: m/s^2 // Gyro: deg/s - float x; - float y; - float z; + float x; ///< Horizontal coordinate or vector component. + float y; ///< Vertical coordinate or vector component. + float z; ///< Depth or Z-axis vector component. }; + /** + * @brief Battery state reported by a virtual gamepad. + */ struct gamepad_battery_t { - gamepad_id_t id; - std::uint8_t state; - std::uint8_t percentage; + gamepad_id_t id; ///< Gamepad identifier for the event. + std::uint8_t state; ///< Battery state reported by the client. + std::uint8_t percentage; ///< Battery charge percentage. }; + /** + * @brief Absolute touchscreen event data from the client. + */ struct touch_input_t { - std::uint8_t eventType; - std::uint16_t rotation; // Degrees (0..360) or LI_ROT_UNKNOWN - std::uint32_t pointerId; - float x; - float y; - float pressureOrDistance; // Distance for hover and pressure for contact - float contactAreaMajor; - float contactAreaMinor; + std::uint8_t eventType; ///< Moonlight event type for the input packet. + std::uint16_t rotation; ///< Degrees (0..360) or LI_ROT_UNKNOWN. + std::uint32_t pointerId; ///< Client-provided pointer identifier for a touch contact. + float x; ///< Horizontal coordinate or vector component. + float y; ///< Vertical coordinate or vector component. + float pressureOrDistance; ///< Distance for hover and pressure for contact. + float contactAreaMajor; ///< Major axis of the reported contact area. + float contactAreaMinor; ///< Minor axis of the reported contact area. }; + /** + * @brief Pen tablet event data from the client. + */ struct pen_input_t { - std::uint8_t eventType; - std::uint8_t toolType; - std::uint8_t penButtons; - std::uint8_t tilt; // Degrees (0..90) or LI_TILT_UNKNOWN - std::uint16_t rotation; // Degrees (0..360) or LI_ROT_UNKNOWN - float x; - float y; - float pressureOrDistance; // Distance for hover and pressure for contact - float contactAreaMajor; - float contactAreaMinor; + std::uint8_t eventType; ///< Moonlight event type for the input packet. + std::uint8_t toolType; ///< Pen tool type reported by the client. + std::uint8_t penButtons; ///< Button mask for the active pen tool. + std::uint8_t tilt; ///< Degrees (0..90) or LI_TILT_UNKNOWN. + std::uint16_t rotation; ///< Degrees (0..360) or LI_ROT_UNKNOWN. + float x; ///< Horizontal coordinate or vector component. + float y; ///< Vertical coordinate or vector component. + float pressureOrDistance; ///< Distance for hover and pressure for contact. + float contactAreaMajor; ///< Major axis of the reported contact area. + float contactAreaMinor; ///< Minor axis of the reported contact area. }; + /** + * @brief RAII helper that runs shutdown cleanup when destroyed. + */ class deinit_t { public: + /** + * @brief Destroy the deinitializer. + */ virtual ~deinit_t() = default; }; + /** + * @brief Captured frame buffer shared between capture and encode stages. + */ struct img_t: std::enable_shared_from_this { public: img_t() = default; @@ -378,54 +508,88 @@ namespace platf { img_t &operator=(img_t &&) = delete; img_t &operator=(const img_t &) = delete; - std::uint8_t *data {}; - std::int32_t width {}; - std::int32_t height {}; - std::int32_t pixel_pitch {}; - std::int32_t row_pitch {}; + std::uint8_t *data {}; ///< Pointer to the captured image buffer. + std::int32_t width {}; ///< Image width in pixels. + std::int32_t height {}; ///< Image height in pixels. + std::int32_t pixel_pitch {}; ///< Bytes per pixel in the image buffer. + std::int32_t row_pitch {}; ///< Bytes between consecutive image rows. - std::optional frame_timestamp; + std::optional frame_timestamp; ///< Capture timestamp associated with the frame. + /** + * @brief Destroy the image. + */ virtual ~img_t() = default; }; + /** + * @brief Host and virtual audio sink names for audio routing. + */ struct sink_t { // Play on host PC - std::string host; + std::string host; ///< Host playback sink name. // On macOS and Windows, it is not possible to create a virtual sink // Therefore, it is optional + /** + * @brief Optional virtual sink names for each supported channel layout. + */ struct null_t { - std::string stereo; - std::string surround51; - std::string surround71; + std::string stereo; ///< Virtual sink name for stereo audio. + std::string surround51; ///< Virtual sink name for 5.1 surround audio. + std::string surround71; ///< Virtual sink name for 7.1 surround audio. }; - std::optional null; + std::optional null; ///< Optional virtual sink names for monitor capture. }; + /** + * @brief Base interface for hardware or software frame conversion. + */ struct encode_device_t { virtual ~encode_device_t() = default; + /** + * @brief Convert a captured image into the encoder input representation. + * + * @param img Image or frame object to read from or populate. + * @return Conversion status. + */ virtual int convert(platf::img_t &img) = 0; - video::sunshine_colorspace_t colorspace; + video::sunshine_colorspace_t colorspace; ///< Colorspace metadata expected by the encoder. }; + /** + * @brief AVCodec-backed encode device and frame state. + */ struct avcodec_encode_device_t: encode_device_t { - void *data {}; - AVFrame *frame {}; + void *data {}; ///< Backend-specific conversion state. + AVFrame *frame {}; ///< FFmpeg frame currently owned by the encode device. + /** + * @brief Convert a captured image into the encoder input representation. + * + * @param img Image or frame object to read from or populate. + * @return Conversion status. + */ int convert(platf::img_t &img) override { return -1; } + /** + * @brief Apply the configured colorspace metadata to the active frame. + */ virtual void apply_colorspace() { } /** * @brief Set the frame to be encoded. * @note Implementations must take ownership of 'frame'. + * + * @param frame Video or graphics frame being processed. + * @param hw_frames_ctx FFmpeg hardware frames context associated with the frame. + * @return Status from updating frame. */ virtual int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) { BOOST_LOG(error) << "Illegal call to hwdevice_t::set_frame(). Did you forget to override it?"; @@ -435,30 +599,51 @@ namespace platf { /** * @brief Initialize the hwframes context. * @note Implementations may set parameters during initialization of the hwframes context. + * + * @param frames FFmpeg hardware frames context to initialize. */ virtual void init_hwframes(AVHWFramesContext *frames) {}; /** * @brief Provides a hook for allow platform-specific code to adjust codec options. * @note Implementations may set or modify codec options prior to codec initialization. + * + * @param ctx Native context object used by the operation or callback. + * @param options Request options or socket options to apply. */ virtual void init_codec_options(AVCodecContext *ctx, AVDictionary **options) {}; /** * @brief Prepare to derive a context. * @note Implementations may make modifications required before context derivation + * + * @param hw_device_type FFmpeg hardware device type requested for context derivation. + * @return 0 when context derivation may continue; nonzero to abort. */ virtual int prepare_to_derive_context(int hw_device_type) { return 0; }; }; + /** + * @brief NVENC-backed encode device state. + */ struct nvenc_encode_device_t: encode_device_t { + /** + * @brief Initialize the platform encoder for the client stream configuration. + * + * @param client_config Client stream configuration negotiated for this session. + * @param colorspace Colorimetry information used for conversion or encoding. + * @return True when the backend successfully completes the requested action. + */ virtual bool init_encoder(const video::config_t &client_config, const video::sunshine_colorspace_t &colorspace) = 0; - nvenc::nvenc_base *nvenc = nullptr; + nvenc::nvenc_base *nvenc = nullptr; ///< NVENC encoder instance owned by the encode device. }; + /** + * @brief Enumerates supported capture options. + */ enum class capture_e : int { ok, ///< Success reinit, ///< Need to reinitialize @@ -467,6 +652,9 @@ namespace platf { error ///< Error }; + /** + * @brief Abstract display capture backend used by the streaming pipeline. + */ class display_t { public: /** @@ -503,22 +691,56 @@ namespace platf { */ virtual capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) = 0; + /** + * @brief Allocate an image buffer compatible with this display backend. + * + * @return Allocated img object, or null when unavailable. + */ virtual std::shared_ptr alloc_img() = 0; + /** + * @brief Populate a fallback image when real capture data is unavailable. + * + * @param img Image or frame object to read from or populate. + * @return Capture status reported to the streaming pipeline. + */ virtual int dummy_img(img_t *img) = 0; + /** + * @brief Create AVCodec encode device. + * + * @param pix_fmt Sunshine pixel format to convert or allocate for. + * @return Constructed AVCodec encode device object. + */ virtual std::unique_ptr make_avcodec_encode_device(pix_fmt_e pix_fmt) { return nullptr; } + /** + * @brief Create NVENC encode device. + * + * @param pix_fmt Sunshine pixel format to convert or allocate for. + * @return Constructed NVENC encode device object. + */ virtual std::unique_ptr make_nvenc_encode_device(pix_fmt_e pix_fmt) { return nullptr; } + /** + * @brief Report whether the active display mode is HDR. + * + * @return True when the active display mode is HDR. + */ virtual bool is_hdr() { return false; } + /** + * @brief Read HDR metadata for the active display mode. + * + * @param metadata Output structure populated with HDR metadata. + * @return True when HDR metadata was written to `metadata`. + */ virtual bool get_hdr_metadata(SS_HDR_METADATA &metadata) { std::memset(&metadata, 0, sizeof(metadata)); return false; @@ -537,33 +759,62 @@ namespace platf { virtual ~display_t() = default; // Offsets for when streaming a specific monitor. By default, they are 0. - int offset_x {0}; - int offset_y {0}; - int env_width {0}; - int env_height {0}; - int env_logical_width {0}; - int env_logical_height {0}; - int width {0}; - int height {0}; - int logical_width {0}; - int logical_height {0}; + int offset_x {0}; ///< Horizontal capture offset in physical pixels. + int offset_y {0}; ///< Vertical capture offset in physical pixels. + int env_width {0}; ///< Width of the full capture environment in physical pixels. + int env_height {0}; ///< Height of the full capture environment in physical pixels. + int env_logical_width {0}; ///< Width of the full capture environment after display scaling. + int env_logical_height {0}; ///< Height of the full capture environment after display scaling. + int width {0}; ///< Width of the captured display in physical pixels. + int height {0}; ///< Height of the captured display in physical pixels. + int logical_width {0}; ///< Width of the captured display after display scaling. + int logical_height {0}; ///< Height of the captured display after display scaling. protected: // collect capture timing data (at loglevel debug) - logging::time_delta_periodic_logger sleep_overshoot_logger = {debug, "Frame capture sleep overshoot"}; + logging::time_delta_periodic_logger sleep_overshoot_logger = {debug, "Frame capture sleep overshoot"}; ///< Periodic logger for capture sleep overshoot measurements. }; + /** + * @brief Audio capture source used by the streaming pipeline. + */ class mic_t { public: + /** + * @brief Deliver a captured audio sample to Sunshine's audio pipeline. + * + * @param frame_buffer Destination for captured floating-point PCM samples. + * @return Capture status reported to the streaming pipeline. + */ virtual capture_e sample(std::vector &frame_buffer) = 0; virtual ~mic_t() = default; }; + /** + * @brief Platform audio controller that manages sinks and microphone capture. + */ class audio_control_t { public: + /** + * @brief Update the sink value on the backend. + * + * @param sink Audio sink name to route or capture. + * @return Status from updating sink. + */ virtual int set_sink(const std::string &sink) = 0; + /** + * @brief Create a microphone capture stream for the requested layout. + * + * @param mapping Opus channel mapping table for the requested layout. + * @param channels Number of audio channels in the stream. + * @param sample_rate Audio sample rate in hertz. + * @param frame_size Number of samples captured per audio frame. + * @param continuous Whether silent audio should continue to be emitted. + * @param host_audio_enabled Whether host playback should remain enabled during capture. + * @return Microphone capture object for the requested audio layout. + */ virtual std::unique_ptr microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size, bool continuous, [[maybe_unused]] bool host_audio_enabled) = 0; /** @@ -573,22 +824,61 @@ namespace platf { */ virtual bool is_sink_available(const std::string &sink) = 0; + /** + * @brief Query host and virtual sink names available to Sunshine. + * + * @return Host and virtual sink names when the backend can report them. + */ virtual std::optional sink_info() = 0; + /** + * @brief Destroy the audio control. + */ virtual ~audio_control_t() = default; }; + /** + * @brief Release a platform input backend created by input(). + * + * @param p Pointer passed to the deleter or conversion helper. + */ void freeInput(void *); + /** + * @brief Owning pointer for a platform input backend. + */ using input_t = util::safe_ptr; std::filesystem::path appdata(); + /** + * @brief Return the hardware MAC address associated with a network address. + * + * @param address Network address being parsed or filtered. + * @return Hardware MAC address string, or an empty string when it cannot be resolved. + */ std::string get_mac_address(const std::string_view &address); + /** + * @brief Convert a socket address to a printable IP address. + * + * @param ip_addr Socket address to format. + * @return Value converted from sockaddr. + */ std::string from_sockaddr(const sockaddr *const); + /** + * @brief Convert a socket address to a port and printable IP address. + * + * @param ip_addr Socket address to format. + * @return Value converted from sockaddr ex. + */ std::pair from_sockaddr_ex(const sockaddr *const); + /** + * @brief Create the platform audio controller. + * + * @return Platform audio controller, or nullptr when audio control is unavailable. + */ std::unique_ptr audio_control(); /** @@ -602,6 +892,12 @@ namespace platf { std::shared_ptr display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config); // A list of names of displays accepted as display_name with the mem_type_e + /** + * @brief List display names accepted by the selected capture backend. + * + * @param hwdevice_type Hardware device type requested for capture or encode. + * @return Display names accepted by the selected capture backend. + */ std::vector display_names(mem_type_e hwdevice_type); /** @@ -610,19 +906,42 @@ namespace platf { */ bool needs_encoder_reenumeration(); + /** + * @brief Launch a configured preparation or application command. + * + * @param elevated Whether the command should run with elevated privileges. + * @param interactive Whether the command should run in an interactive session. + * @param cmd Command line to execute or inspect. + * @param working_dir Working directory for the child process. + * @param env Environment variables for the child process. + * @param file Optional stdio file handle connected to the child process. + * @param ec Error code returned by the asynchronous operation. + * @param group Process group used when launching the command. + * @return Child process handle for the launched command. + */ boost::process::v1::child run_command(bool elevated, bool interactive, const std::string &cmd, boost::filesystem::path &working_dir, const boost::process::v1::environment &env, FILE *file, std::error_code &ec, boost::process::v1::group *group); + /** + * @brief Enumerates supported thread priority options. + */ enum class thread_priority_e : int { low, ///< Low priority normal, ///< Normal priority high, ///< High priority critical ///< Critical priority }; + /** + * @brief Apply the requested scheduling priority to the current thread. + * + * @param priority Thread priority requested from the platform backend. + */ void adjust_thread_priority(thread_priority_e priority); /** * @brief Name the current thread for use with development tools. * @note On Linux this will be truncated after 15 characters. + * + * @param name Human-readable name to assign. */ void set_thread_name(const std::string &name); @@ -649,11 +968,20 @@ namespace platf { */ int unset_env(const std::string &name); + /** + * @brief Platform buffer pointer and size for batched socket sends. + */ struct buffer_descriptor_t { - const char *buffer; - size_t size; + const char *buffer; ///< Pointer to the payload buffer. + size_t size; ///< Size of the buffer in bytes. // Constructors required for emplace_back() prior to C++20 + /** + * @brief Describe a contiguous byte buffer for batched socket sending. + * + * @param buffer Serialized byte buffer to read from or write to. + * @param size Number of bytes or elements requested. + */ buffer_descriptor_t(const char *buffer, size_t size): buffer(buffer), size(size) { @@ -665,28 +993,31 @@ namespace platf { } }; + /** + * @brief Buffers and native metadata for one batched send operation. + */ struct batched_send_info_t { // Optional headers to be prepended to each packet - const char *headers; - size_t header_size; + const char *headers; ///< Optional header bytes prepended to each payload. + size_t header_size; ///< Header size in bytes. // One or more data buffers to use for the payloads // // NB: Data buffers must be aligned to payload size! - std::vector &payload_buffers; - size_t payload_size; + std::vector &payload_buffers; ///< Payload buffers containing one or more packet blocks. + size_t payload_size; ///< Payload size in bytes for each packet block. // The offset (in header+payload message blocks) in the header and payload // buffers to begin sending messages from - size_t block_offset; + size_t block_offset; ///< First packet block index to send. // The number of header+payload message blocks to send - size_t block_count; + size_t block_count; ///< Number of packet blocks to send. - std::uintptr_t native_socket; - boost::asio::ip::address &target_address; - uint16_t target_port; - boost::asio::ip::address &source_address; + std::uintptr_t native_socket; ///< Platform socket handle used for the send operation. + boost::asio::ip::address &target_address; ///< Destination IP address for outgoing packets. + uint16_t target_port; ///< Destination UDP port for outgoing packets. + boost::asio::ip::address &source_address; ///< Local source IP address for outgoing packets. /** * @brief Returns a payload buffer descriptor for the given payload offset. @@ -708,22 +1039,40 @@ namespace platf { } }; + /** + * @brief Send multiple fixed-size UDP payload blocks using the platform backend. + * + * @param send_info Socket addresses, buffers, and sizes for the send operation. + * @return True when all requested packet blocks are submitted to the socket. + */ bool send_batch(batched_send_info_t &send_info); + /** + * @brief Destination address and payload data for one UDP send. + */ struct send_info_t { - const char *header; - size_t header_size; - const char *payload; - size_t payload_size; + const char *header; ///< Optional header bytes prepended to the payload. + size_t header_size; ///< Header size in bytes. + const char *payload; ///< Payload bytes to send after the header. + size_t payload_size; ///< Payload size in bytes for each packet block. - std::uintptr_t native_socket; - boost::asio::ip::address &target_address; - uint16_t target_port; - boost::asio::ip::address &source_address; + std::uintptr_t native_socket; ///< Platform socket handle used for the send operation. + boost::asio::ip::address &target_address; ///< Destination IP address for outgoing packets. + uint16_t target_port; ///< Destination UDP port for outgoing packets. + boost::asio::ip::address &source_address; ///< Local source IP address for outgoing packets. }; + /** + * @brief Send the serialized response over the active socket. + * + * @param send_info Socket addresses, buffers, and sizes for the send operation. + * @return True when the packet is submitted to the socket. + */ bool send(send_info_t &send_info); + /** + * @brief Identifies traffic classes used for socket QoS tagging. + */ enum class qos_data_type_e : int { audio, ///< Audio video ///< Video @@ -736,6 +1085,8 @@ namespace platf { * @param port The destination port for traffic sent on this socket. * @param data_type The type of traffic sent on this socket. * @param dscp_tagging Specifies whether to enable DSCP tagging on outgoing traffic. + * + * @return Cleanup handle that restores or releases QoS state when destroyed. */ std::unique_ptr enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type, bool dscp_tagging); @@ -759,6 +1110,11 @@ namespace platf { */ bool process_group_running(std::uintptr_t native_handle); + /** + * @brief Create the platform input backend for a stream. + * + * @return Platform-specific input backend for the active stream. + */ input_t input(); /** * @brief Get the current mouse position on screen @@ -769,15 +1125,67 @@ namespace platf { * @examples_end */ util::point_t get_mouse_loc(input_t &input); + /** + * @brief Move mouse using the backend coordinate system. + * + * @param input Platform input backend that receives the event. + * @param deltaX Delta x. + * @param deltaY Delta y. + */ void move_mouse(input_t &input, int deltaX, int deltaY); + /** + * @brief Move the pointer to an absolute client-provided touch coordinate. + * + * @param input Platform input backend that receives the event. + * @param touch_port Touch coordinate bounds used for scaling. + * @param x Horizontal absolute coordinate from the client. + * @param y Vertical absolute coordinate from the client. + */ void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y); + /** + * @brief Press or release a virtual mouse button. + * + * @param input Platform input backend that receives the event. + * @param button Mouse button identifier to press or release. + * @param release Whether the key or button event is a release. + */ void button_mouse(input_t &input, int button, bool release); + /** + * @brief Apply a vertical scroll event to the virtual mouse. + * + * @param input Platform input backend that receives the event. + * @param distance High-resolution scroll distance reported by the client. + */ void scroll(input_t &input, int distance); + /** + * @brief Apply a horizontal scroll event to the virtual mouse. + * + * @param input Platform input backend that receives the event. + * @param distance High-resolution scroll distance reported by the client. + */ void hscroll(input_t &input, int distance); + /** + * @brief Press or release a virtual keyboard key. + * + * @param input Platform input backend that receives the event. + * @param modcode Modifier key code to update. + * @param release Whether the key or button event is a release. + * @param flags Bit flags that modify the requested operation. + */ void keyboard_update(input_t &input, uint16_t modcode, bool release, uint8_t flags); void gamepad_update(input_t &input, int nr, const gamepad_state_t &gamepad_state); + /** + * @brief Submit UTF-8 text input to the keyboard backend. + * + * @param input Platform input backend that receives the event. + * @param utf8 UTF-8 text submitted by the client. + * @param size Number of bytes or elements requested. + */ void unicode(input_t &input, char *utf8, int size); + /** + * @brief Per-client input context allocated by a platform backend. + */ typedef deinit_t client_input_t; /** @@ -833,6 +1241,12 @@ namespace platf { * @return 0 on success. */ int alloc_gamepad(input_t &input, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue); + /** + * @brief Release gamepad resources. + * + * @param input Platform input backend that receives the event. + * @param nr Controller index assigned by the client. + */ void free_gamepad(input_t &input, int nr); /** @@ -841,13 +1255,18 @@ namespace platf { */ platform_caps::caps_t get_capabilities(); - constexpr auto SERVICE_NAME = "Sunshine"; - constexpr auto SERVICE_TYPE = "_nvstream._tcp"; + constexpr auto SERVICE_NAME = "Sunshine"; ///< mDNS service instance name advertised for GameStream discovery. + constexpr auto SERVICE_TYPE = "_nvstream._tcp"; ///< mDNS service type advertised for GameStream discovery. namespace publish { [[nodiscard]] std::unique_ptr start(); } + /** + * @brief Initialize the platform-specific high precision timer. + * + * @return Cleanup handle for initialized platform resources, or null if none are needed. + */ [[nodiscard]] std::unique_ptr init(); /** @@ -873,6 +1292,9 @@ namespace platf { */ std::vector &supported_gamepads(input_t *input); + /** + * @brief Platform timer object used for precise frame pacing. + */ struct high_precision_timer: private boost::noncopyable { virtual ~high_precision_timer() = default; diff --git a/src/platform/linux/audio.cpp b/src/platform/linux/audio.cpp index bd043912d..ac3aa7b36 100644 --- a/src/platform/linux/audio.cpp +++ b/src/platform/linux/audio.cpp @@ -22,6 +22,9 @@ namespace platf { using namespace std::literals; + /** + * @brief Position mapping. + */ constexpr pa_channel_position_t position_mapping[] { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, @@ -33,6 +36,14 @@ namespace platf { PA_CHANNEL_POSITION_SIDE_RIGHT, }; + /** + * @brief Convert a PulseAudio operation result to a log string. + * + * @param name Human-readable name to assign. + * @param mapping Opus channel mapping table for the requested layout. + * @param channels Number of audio channels in the stream. + * @return Value converted to string. + */ std::string to_string(const char *name, const std::uint8_t *mapping, int channels) { std::stringstream ss; @@ -50,9 +61,18 @@ namespace platf { return result; } + /** + * @brief PulseAudio recording stream and channel metadata. + */ struct mic_attr_t: public mic_t { - util::safe_ptr mic; + util::safe_ptr mic; ///< PulseAudio simple recording stream for microphone capture. + /** + * @brief Deliver a captured audio sample to Sunshine's audio pipeline. + * + * @param sample_buf Sample buf. + * @return Capture status reported to the streaming pipeline. + */ capture_e sample(std::vector &sample_buf) override { auto sample_size = sample_buf.size(); @@ -68,6 +88,16 @@ namespace platf { } }; + /** + * @brief Create a microphone capture stream for the requested layout. + * + * @param mapping Opus channel mapping table for the requested layout. + * @param channels Number of audio channels in the stream. + * @param sample_rate Audio sample rate in hertz. + * @param frame_size Number of samples captured per audio frame. + * @param source_name Source name. + * @return Microphone capture object for the requested audio layout. + */ std::unique_ptr microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size, std::string source_name) { auto mic = std::make_unique(); @@ -106,32 +136,74 @@ namespace platf { template struct add_const_helper; + /** + * @brief Template helper that preserves constness for const inputs. + */ template struct add_const_helper { + /** + * @brief PulseAudio object type passed to the safe pointer wrapper. + */ using type = const std::remove_pointer_t *; }; + /** + * @brief Template helper that leaves non-const inputs mutable. + */ template struct add_const_helper { + /** + * @brief PulseAudio object type passed to the safe pointer wrapper. + */ using type = const T *; }; + /** + * @brief PulseAudio callback info type with pointer constness normalized. + */ template using add_const_t = typename add_const_helper, T>::type; + /** + * @brief Release memory allocated by PulseAudio. + * + * @param p Pointer allocated by PulseAudio and released with `pa_xfree`. + */ template void pa_free(T *p) { pa_xfree(p); } + /** + * @brief Owning pointer for a PulseAudio context. + */ using ctx_t = util::safe_ptr; + /** + * @brief Owning pointer for a PulseAudio mainloop. + */ using loop_t = util::safe_ptr; + /** + * @brief Owning pointer for a PulseAudio asynchronous operation. + */ using op_t = util::safe_ptr; + /** + * @brief Owning pointer for PulseAudio strings allocated with `pa_xmalloc`. + */ using string_t = util::safe_ptr>; + /** + * @brief Callback wrapper for PulseAudio introspection results without an end marker. + */ template using cb_simple_t = std::function i)>; + /** + * @brief Handle PulseAudio sink-input introspection results. + * + * @param ctx Native context object used by the operation or callback. + * @param i PulseAudio introspection info supplied to the callback. + * @param userdata Caller-provided pointer passed through the callback. + */ template void cb(ctx_t::pointer ctx, add_const_t i, void *userdata) { auto &f = *(cb_simple_t *) userdata; @@ -141,9 +213,20 @@ namespace platf { f(ctx, i); } + /** + * @brief Callback wrapper for PulseAudio introspection results with an end marker. + */ template using cb_t = std::function i, int eol)>; + /** + * @brief Handle PulseAudio source introspection results. + * + * @param ctx Native context object used by the operation or callback. + * @param i PulseAudio introspection info supplied to the callback. + * @param eol PulseAudio end-of-list marker. + * @param userdata Caller-provided pointer passed through the callback. + */ template void cb(ctx_t::pointer ctx, add_const_t i, int eol, void *userdata) { auto &f = *(cb_t *) userdata; @@ -156,18 +239,38 @@ namespace platf { f(ctx, i, eol); } + /** + * @brief Forward a PulseAudio integer callback value into a Sunshine alarm. + * + * @param ctx PulseAudio context that emitted the callback. + * @param i Integer value returned by the PulseAudio operation. + * @param userdata Caller-provided pointer passed through the callback. + */ void cb_i(ctx_t::pointer ctx, std::uint32_t i, void *userdata) { auto alarm = (safe::alarm_raw_t *) userdata; alarm->ring(i); } + /** + * @brief Translate PulseAudio context state changes into server events. + * + * @param ctx Native context object used by the operation or callback. + * @param userdata Caller-provided pointer passed through the callback. + */ void ctx_state_cb(ctx_t::pointer ctx, void *userdata) { auto &f = *(std::function *) userdata; f(ctx); } + /** + * @brief Record completion of a PulseAudio asynchronous operation. + * + * @param ctx Native context object used by the operation or callback. + * @param status Native status code returned by the platform API. + * @param userdata Caller-provided pointer passed through the callback. + */ void success_cb(ctx_t::pointer ctx, int status, void *userdata) { assert(userdata != nullptr); @@ -175,6 +278,9 @@ namespace platf { alarm->ring(status ? 0 : 1); } + /** + * @brief PulseAudio server controller that creates and removes Sunshine sinks. + */ class server_t: public audio_control_t { enum ctx_event_e : int { ready, @@ -183,21 +289,26 @@ namespace platf { }; public: - loop_t loop; - ctx_t ctx; - std::string requested_sink; + loop_t loop; ///< PulseAudio threaded mainloop instance. + ctx_t ctx; ///< PulseAudio threaded mainloop context. + std::string requested_sink; ///< Requested sink. struct { std::uint32_t stereo = PA_INVALID_INDEX; std::uint32_t surround51 = PA_INVALID_INDEX; std::uint32_t surround71 = PA_INVALID_INDEX; - } index; + } index; ///< PulseAudio module indexes for Sunshine-created null sinks. - std::unique_ptr> events; - std::unique_ptr> events_cb; + std::unique_ptr> events; ///< Event queue receiving PulseAudio context state changes. + std::unique_ptr> events_cb; ///< Callback that translates PulseAudio context updates into events. - std::thread worker; + std::thread worker; ///< Thread running the PulseAudio mainloop. + /** + * @brief Initialize PulseAudio mainloop, context, and Sunshine null sinks. + * + * @return 0 on success; nonzero or negative platform status on failure. + */ int init() { events = std::make_unique>(); loop.reset(pa_mainloop_new()); @@ -255,6 +366,14 @@ namespace platf { return 0; } + /** + * @brief Create a PulseAudio null sink for one channel layout. + * + * @param name Human-readable name to assign. + * @param channel_mapping Channel mapping. + * @param channels Number of audio channels in the stream. + * @return PulseAudio module index for the new sink, or PA_INVALID_INDEX on failure. + */ int load_null(const char *name, const std::uint8_t *channel_mapping, int channels) { auto alarm = safe::make_alarm(); @@ -272,6 +391,12 @@ namespace platf { return *alarm->status(); } + /** + * @brief Unload a Sunshine-created PulseAudio null sink. + * + * @param i PulseAudio introspection info supplied to the callback. + * @return 0 when the sink is absent or unloaded; nonzero on PulseAudio failure. + */ int unload_null(std::uint32_t i) { if (i == PA_INVALID_INDEX) { return 0; @@ -293,6 +418,11 @@ namespace platf { return 0; } + /** + * @brief Query host and virtual sink names available to Sunshine. + * + * @return Host and virtual sink names when the backend can report them. + */ std::optional sink_info() override { constexpr auto stereo = "sink-sunshine-stereo"; constexpr auto surround51 = "sink-sunshine-surround51"; @@ -388,6 +518,11 @@ namespace platf { return std::make_optional(std::move(sink)); } + /** + * @brief Get default sink name. + * + * @return PulseAudio name of the current default sink, or an empty string. + */ std::string get_default_sink_name() { std::string sink_name; auto alarm = safe::make_alarm(); @@ -410,6 +545,12 @@ namespace platf { return sink_name; } + /** + * @brief Get monitor name. + * + * @param sink_name Sink name. + * @return PulseAudio monitor source name for the supplied sink, or an empty string. + */ std::string get_monitor_name(const std::string &sink_name) { std::string monitor_name; auto alarm = safe::make_alarm(); @@ -441,6 +582,17 @@ namespace platf { return monitor_name; } + /** + * @brief Create a microphone capture stream for the requested layout. + * + * @param mapping Opus channel mapping table for the requested layout. + * @param channels Number of audio channels in the stream. + * @param sample_rate Audio sample rate in hertz. + * @param frame_size Number of samples captured per audio frame. + * @param continuous_audio Continuous audio. + * @param host_audio_enabled Whether host playback should remain enabled during capture. + * @return Microphone capture object for the requested audio layout. + */ std::unique_ptr microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size, bool continuous_audio, [[maybe_unused]] bool host_audio_enabled) override { // Sink choice priority: // 1. Config sink @@ -465,6 +617,12 @@ namespace platf { return true; } + /** + * @brief Update the sink value on the backend. + * + * @param sink Audio sink name to route or capture. + * @return Status from updating sink. + */ int set_sink(const std::string &sink) override { auto alarm = safe::make_alarm(); @@ -514,6 +672,9 @@ namespace platf { }; } // namespace pa + /** + * @brief Create the platform audio controller. + */ std::unique_ptr audio_control() { auto audio = std::make_unique(); diff --git a/src/platform/linux/cuda.cpp b/src/platform/linux/cuda.cpp index 937d47809..ce1387092 100644 --- a/src/platform/linux/cuda.cpp +++ b/src/platform/linux/cuda.cpp @@ -26,13 +26,29 @@ extern "C" { #include "src/video.h" #include "wayland.h" +/** + * @def SUNSHINE_STRINGVIEW_HELPER(x) + * @brief Macro for SUNSHINE STRINGVIEW HELPER. + */ #define SUNSHINE_STRINGVIEW_HELPER(x) x##sv +/** + * @def SUNSHINE_STRINGVIEW(x) + * @brief Macro for SUNSHINE STRINGVIEW. + */ #define SUNSHINE_STRINGVIEW(x) SUNSHINE_STRINGVIEW_HELPER(x) +/** + * @def CU_CHECK(x, y) + * @brief Macro for CU CHECK. + */ #define CU_CHECK(x, y) \ if (check((x), SUNSHINE_STRINGVIEW(y ": "))) \ return -1 +/** + * @def CU_CHECK_IGNORE(x, y) + * @brief Macro for CU CHECK IGNORE. + */ #define CU_CHECK_IGNORE(x, y) \ check((x), SUNSHINE_STRINGVIEW(y ": ")) @@ -41,17 +57,32 @@ namespace fs = std::filesystem; using namespace std::literals; namespace cuda { - constexpr auto cudaDevAttrMaxThreadsPerBlock = (CUdevice_attribute) 1; - constexpr auto cudaDevAttrMaxThreadsPerMultiProcessor = (CUdevice_attribute) 39; + constexpr auto cudaDevAttrMaxThreadsPerBlock = (CUdevice_attribute) 1; ///< CUDA dev attr max threads per block. + constexpr auto cudaDevAttrMaxThreadsPerMultiProcessor = (CUdevice_attribute) 39; ///< CUDA dev attr max threads per multi processor. + /** + * @brief Convert a CUDA result code into Sunshine's capture status. + * + * @param sv String view containing the text to inspect. + * @param name Human-readable name to assign. + * @param description Human-readable description used in log output. + */ void pass_error(const std::string_view &sv, const char *name, const char *description) { BOOST_LOG(error) << sv << name << ':' << description; } + /** + * @brief Release a Core Foundation object when the wrapper is destroyed. + * + * @param cf Core Foundation object passed to the scoped releaser. + */ void cff(CudaFunctions *cf) { cuda_free_functions(&cf); } + /** + * @brief Handle to a CUDA dynamic-library function table. + */ using cdf_t = util::safe_ptr; static cdf_t cdf; @@ -71,21 +102,42 @@ namespace cuda { return 0; } + /** + * @brief Release stream resources. + * + * @param stream CUDA stream or PipeWire stream involved in the operation. + */ void freeStream(CUstream stream) { CU_CHECK_IGNORE(cdf->cuStreamDestroy(stream), "Couldn't destroy cuda stream"); } + /** + * @brief Unregister a CUDA graphics resource if it is still registered. + * + * @param resource CUDA graphics resource being mapped or unmapped. + */ void unregisterResource(CUgraphicsResource resource) { CU_CHECK_IGNORE(cdf->cuGraphicsUnregisterResource(resource), "Couldn't unregister resource"); } + /** + * @brief CUDA graphics resource pointer released with `cuGraphicsUnregisterResource`. + */ using registered_resource_t = util::safe_ptr; + /** + * @brief CUDA image wrapper that owns mapped graphics resources for one frame. + */ class img_t: public platf::img_t { public: - tex_t tex; + tex_t tex; ///< CUDA texture object used as the conversion source. }; + /** + * @brief Map CUDA graphics resources for use as an image. + * + * @return 0 on success; nonzero or negative platform status on failure. + */ int init() { auto status = cuda_load_functions(&cdf, nullptr); if (status) { @@ -99,8 +151,18 @@ namespace cuda { return 0; } + /** + * @brief CUDA encode device that imports captured frames into CUDA memory. + */ class cuda_t: public platf::avcodec_encode_device_t { public: + /** + * @brief Initialize the CUDA device context used for frame conversion. + * + * @param in_width In width. + * @param in_height In height. + * @return 0 on success; nonzero or negative platform status on failure. + */ int init(int in_width, int in_height) { if (!cdf) { BOOST_LOG(warning) << "cuda not initialized"sv; @@ -115,6 +177,13 @@ namespace cuda { return 0; } + /** + * @brief Attach frame resources used by the next conversion or encode operation. + * + * @param frame Video or graphics frame being processed. + * @param hw_frames_ctx FFmpeg hardware frames context associated with the frame. + * @return Status from updating frame. + */ int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override { this->hwframe.reset(frame); this->frame = frame; @@ -157,6 +226,9 @@ namespace cuda { return 0; } + /** + * @brief Apply the configured colorspace metadata to the active frame. + */ void apply_colorspace() override { sws.apply_colorspace(colorspace); @@ -189,26 +261,41 @@ namespace cuda { } } + /** + * @brief Select the CUDA texture object for the configured filtering mode. + * + * @param tex Texture resource used by the converter. + * @return CUDA texture object using linear or point sampling. + */ cudaTextureObject_t tex_obj(const tex_t &tex) const { return linear_interpolation ? tex.texture.linear : tex.texture.point; } - stream_t stream; - frame_t hwframe; + stream_t stream; ///< CUDA stream used for asynchronous conversion work. + frame_t hwframe; ///< FFmpeg hardware frame backed by CUDA resources. - int height; - int width; + int height; ///< Frame or display height in pixels. + int width; ///< Frame or display width in pixels. // When height and width don't change, it's not necessary to use linear interpolation - bool linear_interpolation; + bool linear_interpolation; ///< Whether the CUDA converter uses linear interpolation. - bool is_yuv444; + bool is_yuv444; ///< Whether the CUDA converter outputs YUV 4:4:4. - sws_t sws; + sws_t sws; ///< Software scaler used for CUDA frame conversion fallback paths. }; + /** + * @brief CUDA encode device path that converts frames through system memory. + */ class cuda_ram_t: public cuda_t { public: + /** + * @brief Convert a captured frame through CUDA into system-memory encoder input. + * + * @param img Image or frame object to read from or populate. + * @return Conversion status. + */ int convert(platf::img_t &img) override { if (is_yuv444) { return sws.load_ram(img, tex.array) || sws.convert_yuv444(frame->data[0], frame->data[1], frame->data[2], frame->linesize[0], tex_obj(tex), stream.get()); @@ -216,6 +303,13 @@ namespace cuda { return sws.load_ram(img, tex.array) || sws.convert_nv12(frame->data[0], frame->data[1], frame->linesize[0], frame->linesize[1], tex_obj(tex), stream.get()); } + /** + * @brief Attach frame resources used by the next conversion or encode operation. + * + * @param frame Video or graphics frame being processed. + * @param hw_frames_ctx FFmpeg hardware frames context associated with the frame. + * @return Status from updating frame. + */ int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override { if (cuda_t::set_frame(frame, hw_frames_ctx)) { return -1; @@ -231,11 +325,20 @@ namespace cuda { return 0; } - tex_t tex; + tex_t tex; ///< CUDA texture object used as the conversion source. }; + /** + * @brief CUDA encode device path that keeps converted frames in GPU memory. + */ class cuda_vram_t: public cuda_t { public: + /** + * @brief Convert a captured frame through CUDA into GPU encoder input. + * + * @param img Image or frame object to read from or populate. + * @return Conversion status. + */ int convert(platf::img_t &img) override { if (is_yuv444) { return sws.convert_yuv444(frame->data[0], frame->data[1], frame->data[2], frame->linesize[0], tex_obj(((img_t *) &img)->tex), stream.get()); @@ -290,13 +393,19 @@ namespace cuda { return -1; } + /** + * @brief CUDA frame resources registered for interop conversion. + */ struct cu_resources { - registered_resource_t y_res; - registered_resource_t u_res; - registered_resource_t v_res; - registered_resource_t uv_res; + registered_resource_t y_res; ///< Y res. + registered_resource_t u_res; ///< U res. + registered_resource_t v_res; ///< V res. + registered_resource_t uv_res; ///< Uv res. }; + /** + * @brief OpenGL/CUDA interop resources used for GPU-side frame conversion. + */ class gl_cuda_vram_t: public platf::avcodec_encode_device_t { public: /** @@ -509,34 +618,42 @@ namespace cuda { sws.apply_colorspace(colorspace, is_yuv444); } - file_t file; - gbm::gbm_t gbm; - egl::display_t display; - egl::ctx_t ctx; + file_t file; ///< File descriptor for the imported DMA-BUF. + gbm::gbm_t gbm; ///< GBM device used for buffer allocation.. + egl::display_t display; ///< EGL display used to import captured frames. + egl::ctx_t ctx; ///< EGL context used to import captured frames. // This must be destroyed before display_t - stream_t stream; - frame_t hwframe; + stream_t stream; ///< CUDA stream used for asynchronous conversion work. + frame_t hwframe; ///< FFmpeg hardware frame backed by CUDA resources. - egl::sws_t sws; - egl::nv12_t nv12; - egl::yuv444_t yuv444; - AVPixelFormat sw_format; + egl::sws_t sws; ///< Software scaler used for CUDA frame conversion fallback paths. + egl::nv12_t nv12; ///< EGL/OpenGL resources used for NV12 output frames. + egl::yuv444_t yuv444; ///< EGL/OpenGL resources used for YUV444 output frames. + AVPixelFormat sw_format; ///< FFmpeg software pixel format produced by conversion. - int height; - int width; + int height; ///< Frame or display height in pixels. + int width; ///< Frame or display width in pixels. - std::uint64_t sequence; - egl::rgb_t rgb; + std::uint64_t sequence; ///< Capture sequence number associated with the frame. + egl::rgb_t rgb; ///< Imported RGB source image used before CUDA conversion. - cu_resources cu_res; + cu_resources cu_res; ///< CUDA graphics resources registered for the current frame. - int offset_x; - int offset_y; + int offset_x; ///< Horizontal offset in physical pixels. + int offset_y; ///< Vertical offset in physical pixels. - bool is_yuv444; + bool is_yuv444; ///< Whether the CUDA converter outputs YUV 4:4:4. }; + /** + * @brief Create AVCodec encode device. + * + * @param width Frame or display width in pixels. + * @param height Frame or display height in pixels. + * @param vram Whether the image should use GPU memory instead of system memory. + * @return Constructed AVCodec encode device object. + */ std::unique_ptr make_avcodec_encode_device(int width, int height, bool vram) { if (init()) { return nullptr; @@ -589,6 +706,11 @@ namespace cuda { static void *handle {nullptr}; + /** + * @brief Load NvFBC and create the CUDA capture helper. + * + * @return 0 on success; nonzero or negative platform status on failure. + */ int init() { static bool funcs_loaded = false; @@ -627,8 +749,16 @@ namespace cuda { return 0; } + /** + * @brief NvFBC CUDA context selected for a capture session. + */ class ctx_t { public: + /** + * @brief Create an NvFBC session context for a native capture handle. + * + * @param handle Native library or object handle used by the operation. + */ ctx_t(NVFBC_SESSION_HANDLE handle) { NVFBC_BIND_CONTEXT_PARAMS params {NVFBC_BIND_CONTEXT_PARAMS_VER}; @@ -646,9 +776,12 @@ namespace cuda { } } - NVFBC_SESSION_HANDLE handle; + NVFBC_SESSION_HANDLE handle; ///< NVIDIA FBC capture session handle. }; + /** + * @brief NvFBC dynamic-library handle and function table. + */ class handle_t { enum flag_e { SESSION_HANDLE, @@ -659,12 +792,23 @@ namespace cuda { public: handle_t() = default; + /** + * @brief Move an NvFBC API handle and its resolved function table. + * + * @param other Source object whose state is copied or moved into this object. + */ handle_t(handle_t &&other): handle_flags {other.handle_flags}, handle {other.handle} { other.handle_flags.reset(); } + /** + * @brief Assign state from another instance while preserving ownership semantics. + * + * @param other Source object whose state is copied or moved into this object. + * @return Reference or value produced by the operator. + */ handle_t &operator=(handle_t &&other) { std::swap(handle_flags, other.handle_flags); std::swap(handle, other.handle); @@ -672,6 +816,11 @@ namespace cuda { return *this; } + /** + * @brief Allocate the underlying object and wrap it in the owning handle. + * + * @return Created backend object, or null when creation fails. + */ static std::optional make() { NVFBC_CREATE_HANDLE_PARAMS params {NVFBC_CREATE_HANDLE_PARAMS_VER}; @@ -694,10 +843,20 @@ namespace cuda { return handle; } + /** + * @brief Read the last error string from the active NvFBC session. + * + * @return Human-readable NvFBC error string. + */ const char *last_error() { return func.nvFBCGetLastErrorStr(handle); } + /** + * @brief Return or update the current status value. + * + * @return Status status. + */ std::optional status() { NVFBC_GET_STATUS_PARAMS params {NVFBC_GET_STATUS_PARAMS_VER}; @@ -711,6 +870,12 @@ namespace cuda { return params; } + /** + * @brief Run the capture loop for this backend. + * + * @param capture_params Capture params. + * @return Capture status reported to the streaming pipeline. + */ int capture(NVFBC_CREATE_CAPTURE_SESSION_PARAMS &capture_params) { if (func.nvFBCCreateCaptureSession(handle, &capture_params)) { BOOST_LOG(error) << "Failed to start capture session: "sv << last_error(); @@ -731,6 +896,11 @@ namespace cuda { return 0; } + /** + * @brief Release the NvFBC capture session and wait for capture work to stop. + * + * @return Stop status. + */ int stop() { if (!handle_flags[SESSION_CAPTURE]) { return 0; @@ -749,6 +919,11 @@ namespace cuda { return 0; } + /** + * @brief Reset the object to its initial empty state. + * + * @return Reset status. + */ int reset() { if (!handle_flags[SESSION_HANDLE]) { return 0; @@ -772,13 +947,23 @@ namespace cuda { reset(); } - std::bitset handle_flags; + std::bitset handle_flags; ///< Handle flags. - NVFBC_SESSION_HANDLE handle; + NVFBC_SESSION_HANDLE handle; ///< NVIDIA FBC capture session handle. }; + /** + * @brief NvFBC display capture backend that produces CUDA frames. + */ class display_t: public platf::display_t { public: + /** + * @brief Initialize NvFBC capture for the selected display. + * + * @param display_name Display name. + * @param config Configuration values to apply. + * @return 0 on success; nonzero or negative platform status on failure. + */ int init(const std::string_view &display_name, const ::video::config_t &config) { auto handle = handle_t::make(); if (!handle) { @@ -900,6 +1085,12 @@ namespace cuda { } // Reinitialize the capture session. + /** + * @brief Reinitialize NvFBC capture after a recoverable failure. + * + * @param cursor Cursor image or visibility state to composite. + * @return Reinit status. + */ platf::capture_e reinit(bool cursor) { if (handle.stop()) { return platf::capture_e::error; @@ -968,6 +1159,15 @@ namespace cuda { return platf::capture_e::ok; } + /** + * @brief Capture a display frame into the provided image object. + * + * @param pull_free_image_cb Callback that provides an available image buffer. + * @param img_out Captured CUDA image returned to the streaming pipeline. + * @param timeout Maximum time to wait for the operation. + * @param cursor Cursor image or visibility state to composite. + * @return Capture status reported to the streaming pipeline. + */ platf::capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor) { if (cursor != cursor_visible) { auto status = reinit(cursor); @@ -1008,10 +1208,21 @@ namespace cuda { return platf::capture_e::ok; } + /** + * @brief Create AVCodec encode device. + * + * @param pix_fmt Sunshine pixel format to convert or allocate for. + * @return Constructed AVCodec encode device object. + */ std::unique_ptr make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) override { return ::cuda::make_avcodec_encode_device(width, height, true); } + /** + * @brief Allocate an image buffer compatible with this display backend. + * + * @return Allocated img object, or null when unavailable. + */ std::shared_ptr alloc_img() override { auto img = std::make_shared(); @@ -1031,21 +1242,34 @@ namespace cuda { return img; }; + /** + * @brief Populate a fallback image when real capture data is unavailable. + * + * @return Capture status reported to the streaming pipeline. + */ int dummy_img(platf::img_t *) override { return 0; } - std::chrono::nanoseconds delay; + std::chrono::nanoseconds delay; ///< Delay before the timer task becomes eligible to run. - bool cursor_visible; - handle_t handle; + bool cursor_visible; ///< Whether the cursor should be included in the capture. + handle_t handle; ///< NvFBC capture handle owning the active capture session. - NVFBC_CREATE_CAPTURE_SESSION_PARAMS capture_params; + NVFBC_CREATE_CAPTURE_SESSION_PARAMS capture_params; ///< NvFBC capture-session parameters used for frame grabs. }; } // namespace nvfbc } // namespace cuda namespace platf { + /** + * @brief Create an NvFBC CUDA display capture backend. + * + * @param hwdevice_type Hardware device type requested for capture or encode. + * @param display_name Display name. + * @param config Configuration values to apply. + * @return Display backend, or nullptr when NvFBC/CUDA initialization fails. + */ std::shared_ptr nvfbc_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) { if (hwdevice_type != mem_type_e::cuda) { BOOST_LOG(error) << "Could not initialize nvfbc display with the given hw device type"sv; @@ -1061,6 +1285,11 @@ namespace platf { return display; } + /** + * @brief Enumerate display names exposed by NvFBC. + * + * @return NvFBC display names, or an empty list when enumeration fails. + */ std::vector nvfbc_display_names() { if (cuda::init() || cuda::nvfbc::init()) { return {}; diff --git a/src/platform/linux/cuda.cu b/src/platform/linux/cuda.cu index 0be85cba1..fbc5401fe 100644 --- a/src/platform/linux/cuda.cu +++ b/src/platform/linux/cuda.cu @@ -226,7 +226,7 @@ namespace cuda { float scale, const viewport_t viewport, const cuda_color_t *const color_matrix - ) { + ) { int idX = threadIdx.x + blockDim.x * blockIdx.x; int idY = threadIdx.y + blockDim.y * blockIdx.y; @@ -252,7 +252,6 @@ namespace cuda { dstY[0] = calcY(rgb, color_matrix) * 255.0f; dstU[0] = calcU(rgb, color_matrix) * 255.0f; dstV[0] = calcV(rgb, color_matrix) * 255.0f; - } int tex_t::copy(std::uint8_t *src, int height, int pitch) { @@ -383,9 +382,7 @@ namespace cuda { return convert_yuv444(Y, U, V, pitch, texture, stream, viewport); } - int sws_t::convert_yuv444(std::uint8_t *Y, std::uint8_t *U, std::uint8_t *V, std::uint32_t pitch, - cudaTextureObject_t texture, stream_t::pointer stream, - const viewport_t &viewport) { + int sws_t::convert_yuv444(std::uint8_t *Y, std::uint8_t *U, std::uint8_t *V, std::uint32_t pitch, cudaTextureObject_t texture, stream_t::pointer stream, const viewport_t &viewport) { int threadsX = viewport.width; int threadsY = viewport.height; @@ -393,8 +390,14 @@ namespace cuda { dim3 grid(div_align(threadsX, threadsPerBlock), threadsY); RGBA_to_YUV444<<>>( - texture, Y, U, V, pitch, scale, viewport, - (cuda_color_t *) color_matrix.get() + texture, + Y, + U, + V, + pitch, + scale, + viewport, + (cuda_color_t *) color_matrix.get() ); return CU_CHECK_IGNORE(cudaGetLastError(), "RGBA_to_YUV444 failed"); diff --git a/src/platform/linux/graphics.cpp b/src/platform/linux/graphics.cpp index 395d4528c..1ef27c716 100644 --- a/src/platform/linux/graphics.cpp +++ b/src/platform/linux/graphics.cpp @@ -24,25 +24,47 @@ extern "C" { // There aren't that many DRM_FORMAT I need to use, so define them here // // They aren't likely to change any time soon. +/** + * @def fourcc_code(a, b, c, d) + * @brief Macro for fourcc code. + */ #define fourcc_code(a, b, c, d) ((std::uint32_t) (a) | ((std::uint32_t) (b) << 8) | ((std::uint32_t) (c) << 16) | ((std::uint32_t) (d) << 24)) +/** + * @def fourcc_mod_code(vendor, val) + * @brief Macro for fourcc mod code. + */ #define fourcc_mod_code(vendor, val) ((((uint64_t) vendor) << 56) | ((val) & 0x00ffffffffffffffULL)) +/** + * @def DRM_FORMAT_MOD_INVALID + * @brief Macro for DRM FORMAT MOD INVALID. + */ #define DRM_FORMAT_MOD_INVALID fourcc_mod_code(0, ((1ULL << 56) - 1)) #if !defined(SUNSHINE_SHADERS_DIR) // for testing this needs to be defined in cmake as we don't do an install + /** + * @def SUNSHINE_SHADERS_DIR + * @brief Macro for SUNSHINE SHADERS DIR. + */ #define SUNSHINE_SHADERS_DIR SUNSHINE_ASSETS_DIR "/shaders/opengl" #endif using namespace std::literals; namespace gl { - GladGLContext ctx; + GladGLContext ctx; ///< Loaded OpenGL function table for the active context. static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC egl_image_target_texture_2d_fn = nullptr; + /** + * @brief Bind an EGL image to the current OpenGL texture target. + */ PFNGLEGLIMAGETARGETTEXTURE2DOESPROC egl_image_target_texture_2d() { return egl_image_target_texture_2d_fn; } + /** + * @brief Drain and log pending OpenGL errors. + */ void drain_errors(const std::string_view &prefix) { GLenum err; while ((err = ctx.GetError()) != GL_NO_ERROR) { @@ -275,9 +297,12 @@ namespace gl { } // namespace gl namespace gbm { - device_destroy_fn device_destroy; - create_device_fn create_device; + device_destroy_fn device_destroy; ///< Device destroy. + create_device_fn create_device; ///< Create device. + /** + * @brief Load GBM symbols required for EGL device creation. + */ int init() { static void *handle {nullptr}; static bool funcs_loaded = false; @@ -309,13 +334,13 @@ namespace gbm { namespace egl { + /** + * @brief Log EGL failure details and return an error code. + */ bool fail() { return eglGetError() != EGL_SUCCESS; } - /** - * @memberof egl::display_t - */ display_t make_display(std::variant native_display) { int egl_platform; void *native_display_p; @@ -391,6 +416,9 @@ namespace egl { return display; } + /** + * @brief Create an EGL/OpenGL context for capture or conversion. + */ std::optional make_ctx(display_t::pointer display) { bool nice_warning = false; #if !defined(__FreeBSD__) @@ -503,14 +531,23 @@ namespace egl { return ctx; } + /** + * @brief EGL attribute pair describing one DMA-BUF plane. + */ struct plane_attr_t { - EGLAttrib fd; - EGLAttrib offset; - EGLAttrib pitch; - EGLAttrib lo; - EGLAttrib hi; + EGLAttrib fd; ///< EGL attribute key for a plane file descriptor. + EGLAttrib offset; ///< Offset. + EGLAttrib pitch; ///< Pitch. + EGLAttrib lo; ///< Lo. + EGLAttrib hi; ///< Hi. }; + /** + * @brief Build EGL attributes for one DMA-BUF plane. + * + * @param plane_indice Zero-based plane index in the DMA-BUF descriptor. + * @return EGL attribute keys for that plane's file descriptor, offset, pitch, and modifier. + */ inline plane_attr_t get_plane(std::uint32_t plane_indice) { switch (plane_indice) { case 0: @@ -593,6 +630,13 @@ namespace egl { return attribs; } + /** + * @brief Import the source frame texture for EGL/OpenGL conversion. + * + * @param egl_display EGL display used to create the image. + * @param xrgb XRGB surface descriptor to import. + * @return Imported RGB image, or empty when import fails. + */ std::optional import_source(display_t::pointer egl_display, const surface_descriptor_t &xrgb) { auto attribs = surface_descriptor_to_egl_attribs(xrgb); @@ -652,9 +696,14 @@ namespace egl { } // Constants for clear black color Y, U, V. U & V are same so: - const float y_black[] = {0.0f, 0.0f, 0.0f, 0.0f}; - const float uv_black[] = {0.5f, 0.5f, 0.5f, 0.5f}; + const float y_black[] = {0.0f, 0.0f, 0.0f, 0.0f}; ///< Y black. + const float uv_black[] = {0.5f, 0.5f, 0.5f, 0.5f}; ///< Uv black. + /** + * @brief Bind NV12 target framebuffers to their Y and UV plane textures. + * + * @param nv12 Imported NV12 target whose textures receive rendered output. + */ void nv12_bind_framebuffers(nv12_t &nv12) { constexpr std::array attachments {{GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1}}; @@ -669,6 +718,11 @@ namespace egl { gl_drain_errors; } + /** + * @brief Bind YUV444 target framebuffers to their Y, U, and V plane textures. + * + * @param yuv444 Imported YUV444 target whose textures receive rendered output. + */ void yuv44_bind_framebuffers(yuv444_t &yuv444) { constexpr std::array attachments {{GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2}}; @@ -683,6 +737,15 @@ namespace egl { gl_drain_errors; } + /** + * @brief Import the output frame target for EGL/OpenGL conversion. + * + * @param egl_display EGL display used to create the images. + * @param fds File descriptors backing the NV12 image. + * @param y Luma plane descriptor. + * @param uv Chroma plane descriptor. + * @return Imported NV12 image, or empty when import fails. + */ std::optional import_target(display_t::pointer egl_display, std::array &&fds, const surface_descriptor_t &y, const surface_descriptor_t &uv) { auto y_attribs = surface_descriptor_to_egl_attribs(y); auto uv_attribs = surface_descriptor_to_egl_attribs(uv); @@ -719,6 +782,16 @@ namespace egl { return nv12; } + /** + * @brief Import a YUV444 target image from DMA-BUF descriptors. + * + * @param egl_display EGL display used to create the images. + * @param fds File descriptors backing the YUV444 image. + * @param y Luma plane descriptor. + * @param u U chroma plane descriptor. + * @param v V chroma plane descriptor. + * @return Imported YUV444 image, or empty when import fails. + */ std::optional import_target_yuv444( display_t::pointer egl_display, std::array &&fds, @@ -811,6 +884,9 @@ namespace egl { return nv12; } + /** + * @brief Create YUV444 target. + */ std::optional create_yuv444_target(int width, int height, AVPixelFormat format) { yuv444_t yuv444 { EGL_NO_DISPLAY, @@ -876,6 +952,15 @@ namespace egl { } } + /** + * @brief Configure the EGL/OpenGL scaling and colorspace conversion pipeline. + * + * @param sws Software-scaling pipeline to configure. + * @param color_p Color p. + * @param tex Texture resource used by the converter. + * @param is_yuv444 Is YUV444. + * @return 0 when shaders, framebuffers, and color uniforms are ready; nonzero on failure. + */ int configure_sws_pipeline(sws_t &sws, const video::color_t *color_p, gl::tex_t &&tex, bool is_yuv444) { std::array, 5> members {{ std::make_pair("color_vec_y", util::view(color_p->color_vec_y)), @@ -1310,6 +1395,11 @@ namespace egl { } } // namespace egl +/** + * @brief Release an FFmpeg frame allocated by the capture or conversion backend. + * + * @param frame Video or graphics frame being processed. + */ void free_frame(AVFrame *frame) { av_frame_free(&frame); } diff --git a/src/platform/linux/graphics.h b/src/platform/linux/graphics.h index 33fcaa79f..2a2a06429 100644 --- a/src/platform/linux/graphics.h +++ b/src/platform/linux/graphics.h @@ -19,19 +19,49 @@ #include "src/utility.h" #include "src/video_colorspace.h" +/** + * @def SUNSHINE_STRINGIFY_HELPER(x) + * @brief Macro for SUNSHINE STRINGIFY HELPER. + */ #define SUNSHINE_STRINGIFY_HELPER(x) #x +/** + * @def SUNSHINE_STRINGIFY(x) + * @brief Macro for SUNSHINE STRINGIFY. + */ #define SUNSHINE_STRINGIFY(x) SUNSHINE_STRINGIFY_HELPER(x) +/** + * @def gl_drain_errors_helper(x) + * @brief Macro for gl drain errors helper. + */ #define gl_drain_errors_helper(x) gl::drain_errors(x) +/** + * @def gl_drain_errors + * @brief Macro for gl drain errors. + */ #define gl_drain_errors gl_drain_errors_helper(__FILE__ ":" SUNSHINE_STRINGIFY(__LINE__)) +/** + * @brief Release the native resource held by the RAII wrapper. + * + * @param __fd File descriptor owned by the RAII wrapper. + * @return 0 on success, or -1 with errno set by the system close call. + */ extern "C" int close(int __fd); // X11 Display extern "C" struct _XDisplay; struct AVFrame; +/** + * @brief Release an FFmpeg frame allocated by the capture or conversion backend. + * + * @param frame Video or graphics frame being processed. + */ void free_frame(AVFrame *frame); +/** + * @brief Owning pointer for an EGL frame object. + */ using frame_t = util::safe_ptr; namespace gl { @@ -40,34 +70,84 @@ namespace gl { // glEGLImageTargetTexture2DOES (GL_OES_EGL_image) is not part of desktop GL — // it is a GLES extension that must be loaded manually via eglGetProcAddress. // GLeglImageOES is typedef void* per the Khronos spec (gl.xml). + /** + * @brief Function pointer type for glEGLImageTargetTexture2DOES. + */ using PFNGLEGLIMAGETARGETTEXTURE2DOESPROC = void (*)(GLenum target, void *image); + /** + * @brief Resolve the GLES extension used to bind EGL images as textures. + * + * @return Extension function pointer, or nullptr when the driver does not expose it. + */ PFNGLEGLIMAGETARGETTEXTURE2DOESPROC egl_image_target_texture_2d(); + /** + * @brief Drain and log pending OpenGL errors. + * + * @param prefix Text prefix used when formatting the message. + */ void drain_errors(const std::string_view &prefix); + /** + * @brief OpenGL texture handle wrapper. + */ class tex_t: public util::buffer_t { using util::buffer_t::buffer_t; public: + /** + * @brief Move ownership of OpenGL texture object names. + */ tex_t(tex_t &&) = default; + /** + * @brief Assign state from another instance while preserving ownership semantics. + * + * @return Reference or value produced by the operator. + */ tex_t &operator=(tex_t &&) = default; ~tex_t(); + /** + * @brief Allocate the underlying object and wrap it in the owning handle. + * + * @param count Number of objects or handles to create. + * @return Created backend object, or null when creation fails. + */ static tex_t make(std::size_t count); }; + /** + * @brief OpenGL framebuffer handle wrapper. + */ class frame_buf_t: public util::buffer_t { using util::buffer_t::buffer_t; public: + /** + * @brief Move ownership of OpenGL framebuffer object names. + */ frame_buf_t(frame_buf_t &&) = default; + /** + * @brief Assign state from another instance while preserving ownership semantics. + * + * @return Reference or value produced by the operator. + */ frame_buf_t &operator=(frame_buf_t &&) = default; ~frame_buf_t(); + /** + * @brief Allocate the underlying object and wrap it in the owning handle. + * + * @param count Number of objects or handles to create. + * @return Created backend object, or null when creation fails. + */ static frame_buf_t make(std::size_t count); + /** + * @brief Bind each framebuffer and clear its color attachment. + */ inline void bind(std::nullptr_t, std::nullptr_t) { int x = 0; for (auto fb : (*this)) { @@ -79,6 +159,12 @@ namespace gl { return; } + /** + * @brief Bind textures to this object's framebuffers as color attachments. + * + * @param it_begin First texture object to attach. + * @param it_end One-past-the-end iterator for texture objects to attach. + */ template void bind(It it_begin, It it_end) { using namespace std::literals; @@ -100,10 +186,20 @@ namespace gl { /** * Copies a part of the framebuffer to texture + * + * @param id Framebuffer index to copy from. + * @param texture Destination texture receiving the copied pixels. + * @param offset_x Source X offset in pixels. + * @param offset_y Source Y offset in pixels. + * @param width Frame or display width in pixels. + * @param height Frame or display height in pixels. */ void copy(int id, int texture, int offset_x, int offset_y, int width, int height); }; + /** + * @brief OpenGL shader object that compiles GLSL source. + */ class shader_t { KITTY_USING_MOVE_T(shader_internal_t, GLuint, std::numeric_limits::max(), { if (el != std::numeric_limits::max()) { @@ -112,16 +208,36 @@ namespace gl { }); public: + /** + * @brief Read the shader compiler log. + * + * @return Shader compiler error log. + */ std::string err_str(); + /** + * @brief Compile an OpenGL shader and report compiler errors. + * + * @param source Shader source code to compile. + * @param type OpenGL shader type, such as GL_VERTEX_SHADER or GL_FRAGMENT_SHADER. + * @return Compiled shader object, or compiler log on failure. + */ static util::Either compile(const std::string_view &source, GLenum type); + /** + * @brief Return the native handle owned by the wrapper. + * + * @return OpenGL shader object name. + */ GLuint handle() const; private: shader_internal_t _shader; }; + /** + * @brief EGL image buffer with plane descriptors and imported GL textures. + */ class buffer_t { KITTY_USING_MOVE_T(buffer_internal_t, GLuint, std::numeric_limits::max(), { if (el != std::numeric_limits::max()) { @@ -130,13 +246,44 @@ namespace gl { }); public: + /** + * @brief Allocate the underlying object and wrap it in the owning handle. + * + * @param offsets Byte offsets for each uniform member in the block. + * @param block Uniform block name used for diagnostics and updates. + * @param data Initial bytes copied into the uniform buffer. + * @return Created backend object, or null when creation fails. + */ static buffer_t make(util::buffer_t &&offsets, const char *block, const std::string_view &data); + /** + * @brief Return the native handle owned by the wrapper. + * + * @return OpenGL buffer object name. + */ GLuint handle() const; + /** + * @brief Query a uniform block index from an OpenGL program. + * + * @return Uniform block name associated with the buffer. + */ const char *block() const; + /** + * @brief Update one uniform member in the block buffer. + * + * @param view Raw bytes to copy into the uniform block. + * @param offset Uniform-member index whose offset is used as the destination. + */ void update(const std::string_view &view, std::size_t offset = 0); + /** + * @brief Update multiple uniform members in the block buffer. + * + * @param members Uniform members to query within the block. + * @param count Number of members in the array. + * @param offset First uniform-member index to update. + */ void update(std::string_view *members, std::size_t count, std::size_t offset = 0); private: @@ -149,6 +296,9 @@ namespace gl { buffer_internal_t _buffer; }; + /** + * @brief OpenGL shader program with attached shader stages. + */ class program_t { KITTY_USING_MOVE_T(program_internal_t, GLuint, std::numeric_limits::max(), { if (el != std::numeric_limits::max()) { @@ -157,14 +307,44 @@ namespace gl { }); public: + /** + * @brief Read the program linker log. + * + * @return OpenGL program link error log. + */ std::string err_str(); + /** + * @brief Link an OpenGL program from compiled shaders. + * + * @param vert Compiled vertex shader object. + * @param frag Compiled fragment shader object. + * @return Linked program object, or linker log on failure. + */ static util::Either link(const shader_t &vert, const shader_t &frag); + /** + * @brief Bind this program and attach a uniform buffer block. + * + * @param buffer Uniform buffer block used by the program. + */ void bind(const buffer_t &buffer); + /** + * @brief Query a uniform location from an OpenGL program. + * + * @param block Uniform block name to bind. + * @param members Uniform members to query within the block. + * @param count Number of uniform members to resolve. + * @return Uniform buffer wrapper, or std::nullopt when lookup/allocation fails. + */ std::optional uniform(const char *block, std::pair *members, std::size_t count); + /** + * @brief Return the native handle owned by the wrapper. + * + * @return OpenGL program object name. + */ GLuint handle() const; private: @@ -174,56 +354,83 @@ namespace gl { namespace gbm { struct device; + /** + * @brief Function pointer used to destroy a GBM device. + */ typedef void (*device_destroy_fn)(device *gbm); + /** + * @brief Function pointer used to create a GBM device from a file descriptor. + */ typedef device *(*create_device_fn)(int fd); extern device_destroy_fn device_destroy; extern create_device_fn create_device; + /** + * @brief Owning GBM device pointer released with the GBM destroy callback. + */ using gbm_t = util::dyn_safe_ptr; + /** + * @brief Load GBM symbols required for EGL device creation. + * + * @return 0 on success; nonzero or negative platform status on failure. + */ int init(); } // namespace gbm namespace egl { + /** + * @brief Owning pointer for an EGL display connection. + */ using display_t = util::dyn_safe_ptr_v2; + /** + * @brief RGB capture image backed by EGL and OpenGL resources. + */ struct rgb_img_t { - display_t::pointer display; - EGLImage xrgb8; + display_t::pointer display; ///< EGL display that owns the imported image. + EGLImage xrgb8; ///< EGL image for the imported XRGB plane. - gl::tex_t tex; + gl::tex_t tex; ///< Texture containing the imported RGB plane. }; + /** + * @brief NV12 capture image backed by EGL and OpenGL resources. + */ struct nv12_img_t { - display_t::pointer display; - EGLImage r8; - EGLImage bg88; + display_t::pointer display; ///< EGL display that owns the imported planes. + EGLImage r8; ///< EGL image for the NV12 luma plane. + EGLImage bg88; ///< EGL image for the NV12 interleaved chroma plane. - gl::tex_t tex; - gl::frame_buf_t buf; + gl::tex_t tex; ///< Textures containing the imported Y and UV planes. + gl::frame_buf_t buf; ///< OpenGL framebuffer object used for rendering. // sizeof(va::DRMPRIMESurfaceDescriptor::objects) / sizeof(va::DRMPRIMESurfaceDescriptor::objects[0]); - static constexpr std::size_t num_fds = 4; + static constexpr std::size_t num_fds = 4; ///< Maximum number of DMA-BUF plane file descriptors exported by VAAPI. - std::array fds; + std::array fds; ///< DMA-BUF file descriptors for each exported plane. }; + /** + * @brief YUV 4:4:4 capture image backed by EGL and OpenGL resources. + */ struct yuv444_img_t { - display_t::pointer display; - EGLImage r8; - EGLImage g8; - EGLImage b8; + display_t::pointer display; ///< EGL display that owns the imported planes. + EGLImage r8; ///< EGL image for the Y plane. + EGLImage g8; ///< EGL image for the U plane. + EGLImage b8; ///< EGL image for the V plane. - gl::tex_t tex; - gl::frame_buf_t buf; + gl::tex_t tex; ///< Textures containing the imported Y, U, and V planes. + gl::frame_buf_t buf; ///< OpenGL framebuffer object used for rendering. - static constexpr std::size_t num_fds = 4; + static constexpr std::size_t num_fds = 4; ///< Num fds. - std::array fds; + std::array fds; ///< DMA-BUF file descriptors for each exported plane. }; +#ifndef DOXYGEN KITTY_USING_MOVE_T(rgb_t, rgb_img_t, , { if (el.xrgb8) { eglDestroyImage(el.display, el.xrgb8); @@ -261,28 +468,75 @@ namespace egl { eglDestroyContext(disp, ctx); } }); +#else + /** + * @brief Move-only wrapper for RGB EGL image resources. + */ + class rgb_t; + /** + * @brief Move-only wrapper for NV12 EGL image resources. + */ + class nv12_t; + + /** + * @brief Move-only wrapper for YUV444 EGL image resources. + */ + class yuv444_t; + + /** + * @brief Move-only wrapper for an EGL context. + */ + class ctx_t; +#endif + + /** + * @brief EGL surface descriptor used to import a captured DMA-BUF. + */ struct surface_descriptor_t { - int width; - int height; - int fds[4]; - std::uint32_t fourcc; - std::uint64_t modifier; - std::uint32_t pitches[4]; - std::uint32_t offsets[4]; + int width; ///< Frame or display width in pixels. + int height; ///< Frame or display height in pixels. + int fds[4]; ///< DMA-BUF file descriptors for up to four planes. + std::uint32_t fourcc; ///< DRM fourcc pixel format for the buffer. + std::uint64_t modifier; ///< DRM format modifier describing the buffer layout. + std::uint32_t pitches[4]; ///< Row stride in bytes for each DMA-BUF plane. + std::uint32_t offsets[4]; ///< Byte offset to the first pixel for each DMA-BUF plane. }; + /** + * @brief Open and initialize the display connection used for capture. + * + * @param native_display Native display. + * @return Constructed display object. + */ display_t make_display(std::variant native_display); + /** + * @brief Create an EGL/OpenGL context for capture or conversion. + * + * @param display Display object or identifier associated with the operation. + * @return EGL context wrapper, or std::nullopt when context creation fails. + */ std::optional make_ctx(display_t::pointer display); - std::optional - import_source( - display_t::pointer egl_display, - const surface_descriptor_t &xrgb - ); + /** + * @brief Import an RGB source surface. + * + * @return Imported RGB image, or std::nullopt on failure. + */ + std::optional import_source(display_t::pointer egl_display, const surface_descriptor_t &xrgb); + /** + * @brief Create a blank RGB texture for an image. + * + * @return Blank RGB image. + */ rgb_t create_blank(platf::img_t &img); + /** + * @brief Import an NV12 target surface. + * + * @return Imported NV12 image, or std::nullopt on failure. + */ std::optional import_target( display_t::pointer egl_display, std::array &&fds, @@ -290,6 +544,16 @@ namespace egl { const surface_descriptor_t &uv ); + /** + * @brief Import a YUV444 target surface. + * + * @param egl_display EGL display. + * @param fds Target plane file descriptors. + * @param y Y plane descriptor. + * @param u U plane descriptor. + * @param v V plane descriptor. + * @return Imported YUV444 image, or std::nullopt on failure. + */ std::optional import_target( display_t::pointer egl_display, std::array &&fds, @@ -307,27 +571,44 @@ namespace egl { */ std::optional create_nv12_target(int width, int height, AVPixelFormat format); + /** + * @brief Create YUV444 target. + * + * @param width Frame or display width in pixels. + * @param height Frame or display height in pixels. + * @param format Pixel, audio, or protocol format being converted. + * @return Created YUV444 target object or status. + */ std::optional create_yuv444_target(int width, int height, AVPixelFormat format); + /** + * @brief Cursor image and hotspot metadata captured from the window system. + */ class cursor_t: public platf::img_t { public: - int x; - int y; - int src_w; - int src_h; + int x; ///< Cursor hotspot or surface X coordinate. + int y; ///< Cursor hotspot or surface Y coordinate. + int src_w; ///< Cursor source image width in pixels. + int src_h; ///< Cursor source image height in pixels. - unsigned long serial; + unsigned long serial; ///< X11 cursor serial used to detect cursor image changes. - std::vector buffer; + std::vector buffer; ///< Cursor image pixels. }; // Allow cursor and the underlying image to be kept together + /** + * @brief Captured image descriptor shared by EGL conversion paths. + */ class img_descriptor_t: public cursor_t { public: ~img_descriptor_t() { reset(); } + /** + * @brief Reset the object to its initial empty state. + */ void reset() { for (auto x = 0; x < 4; ++x) { if (sd.fds[x] >= 0) { @@ -338,71 +619,162 @@ namespace egl { } } - surface_descriptor_t sd; + surface_descriptor_t sd; ///< DMA-BUF surface descriptor for the captured image. // Increment sequence when new rgb_t needs to be created - std::uint64_t sequence; + std::uint64_t sequence; ///< Monotonic value used to detect when GL resources must be recreated. // Frame is vertically flipped (GL convention) - bool y_invert {false}; + bool y_invert {false}; ///< Whether the shader should invert the Y axis. // PipeWire metadata - std::optional pts; - std::optional seq; - std::optional pw_damage; - std::optional pw_flags; + std::optional pts; ///< PipeWire presentation timestamp. + std::optional seq; ///< PipeWire frame sequence number. + std::optional pw_damage; ///< Whether PipeWire damage tracking should be used. + std::optional pw_flags; ///< PipeWire frame flags reported with the buffer. }; + /** + * @brief EGL/OpenGL scaler and colorspace conversion pipeline. + */ class sws_t { public: + /** + * @brief Create a software-scaling pipeline that renders to NV12 planes. + * + * @param in_width Source frame width in pixels. + * @param in_height Source frame height in pixels. + * @param out_width Destination frame width in pixels. + * @param out_height Destination frame height in pixels. + * @param tex Texture resource used by the converter. + * @return Constructed NV12 object. + */ static std::optional make_nv12(int in_width, int in_height, int out_width, int out_height, gl::tex_t &&tex); + /** + * @brief Create a software-scaling pipeline that renders to YUV444 planes. + * + * @param in_width Source frame width in pixels. + * @param in_height Source frame height in pixels. + * @param out_width Destination frame width in pixels. + * @param out_height Destination frame height in pixels. + * @param tex Texture resource used by the converter. + * @return Constructed YUV444 object. + */ static std::optional make_yuv444(int in_width, int in_height, int out_width, int out_height, gl::tex_t &&tex); + /** + * @brief Allocate the underlying object and wrap it in the owning handle. + * + * @param in_width Source frame width in pixels. + * @param in_height Source frame height in pixels. + * @param out_width Destination frame width in pixels. + * @param out_height Destination frame height in pixels. + * @param format Destination FFmpeg pixel format. + * @param is_yuv444 Whether the destination uses three full-resolution planes. + * @return Created backend object, or null when creation fails. + */ static std::optional make(int in_width, int in_height, int out_width, int out_height, AVPixelFormat format, bool is_yuv444); // Convert the loaded image into the first two framebuffers + /** + * @brief Convert the loaded source image into NV12 output planes. + * + * @param fb Framebuffer object to bind or update. + * @return Conversion status. + */ int convert_nv12(gl::frame_buf_t &fb); // Convert the loaded image into the first three framebuffers + /** + * @brief Convert the loaded source image into YUV444 output planes. + * + * @param fb Framebuffer object to bind or update. + * @return Conversion status. + */ int convert_yuv444(gl::frame_buf_t &fb); // Draw loaded image by programs to frame buffers + /** + * @brief Render the loaded source texture into output framebuffers. + * + * @param attachments Framebuffer attachments to bind. + * @param fb Framebuffer object to bind or update. + * @param count Number of output planes to draw. + * @param is_yuv444 Whether to render three YUV444 planes instead of NV12 planes. + * @return 0 when all draw calls complete; nonzero on OpenGL failure. + */ int draw_programs_to_buffers(GLenum attachments[], gl::frame_buf_t &fb, int count, bool is_yuv444); // Make an area of the image black + /** + * @brief Clear the render target to a blank frame. + * + * @param fb Framebuffer object to bind or update. + * @param offsetX_ Offset x. + * @param offsetY_ Offset y. + * @param width Frame or display width in pixels. + * @param height Frame or display height in pixels. + * @param is_yuv444 Is YUV444. + * @return 0 when the target area is cleared; nonzero on OpenGL failure. + */ int blank(gl::frame_buf_t &fb, int offsetX_, int offsetY_, int width, int height, bool is_yuv444); + /** + * @brief Load ram data from the backing API or store. + * + * @param img Image or frame object to read from or populate. + */ void load_ram(platf::img_t &img); + /** + * @brief Load vram data from the backing API or store. + * + * @param img Image or frame object to read from or populate. + * @param offset_x Offset x. + * @param offset_y Offset y. + * @param texture Texture resource to bind, update, or attach. + * @param is_yuv444 Is YUV444. + */ void load_vram(img_descriptor_t &img, int offset_x, int offset_y, int texture, bool is_yuv444); + /** + * @brief Apply the configured colorspace metadata to the active frame. + * + * @param colorspace Colorimetry information used for conversion or encoding. + * @param is_yuv444 Is YUV444. + */ void apply_colorspace(const video::sunshine_colorspace_t &colorspace, bool is_yuv444); // The first texture is the monitor image. // The second texture is the cursor image - gl::tex_t tex; + gl::tex_t tex; ///< Source and cursor textures used by the conversion pipeline. // The cursor image will be blended into this framebuffer - gl::frame_buf_t cursor_framebuffer; - gl::frame_buf_t copy_framebuffer; + gl::frame_buf_t cursor_framebuffer; ///< Cursor framebuffer. + gl::frame_buf_t copy_framebuffer; ///< Copy framebuffer. // Y - shader, UV - shader, Cursor - shader : for nv12 // Y - shader, U - shader, V - shader, Cursor - shader : for yuv444 - std::array program; - gl::buffer_t color_matrix; + std::array program; ///< Program. + gl::buffer_t color_matrix; ///< Color matrix. - int out_width; - int out_height; - int in_width; - int in_height; - int offsetX; - int offsetY; + int out_width; ///< Out width. + int out_height; ///< Out height. + int in_width; ///< In width. + int in_height; ///< In height. + int offsetX; ///< Offset x. + int offsetY; ///< Offset y. // Pointer to the texture to be converted to nv12 - int loaded_texture; + int loaded_texture; ///< Loaded texture. // Store latest cursor for load_vram - std::uint64_t serial; + std::uint64_t serial; ///< Serial. }; + /** + * @brief Log EGL failure details and return an error code. + * + * @return False after logging the EGL failure. + */ bool fail(); } // namespace egl diff --git a/src/platform/linux/input/inputtino.cpp b/src/platform/linux/input/inputtino.cpp index 3102fdd91..c0e8a1dcd 100644 --- a/src/platform/linux/input/inputtino.cpp +++ b/src/platform/linux/input/inputtino.cpp @@ -21,6 +21,9 @@ using namespace std::literals; namespace platf { + /** + * @brief Create the platform input backend for a stream. + */ input_t input() { return {new input_raw_t()}; } @@ -29,41 +32,65 @@ namespace platf { return std::make_unique(input); } + /** + * @brief Release a platform input backend created by input(). + */ void freeInput(void *p) { auto *input = (input_raw_t *) p; delete input; } + /** + * @brief Move mouse using the backend coordinate system. + */ void move_mouse(input_t &input, int deltaX, int deltaY) { auto raw = (input_raw_t *) input.get(); platf::mouse::move(raw, deltaX, deltaY); } + /** + * @brief Move the pointer to an absolute client-provided touch coordinate. + */ void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y) { auto raw = (input_raw_t *) input.get(); platf::mouse::move_abs(raw, touch_port, x, y); } + /** + * @brief Press or release a virtual mouse button. + */ void button_mouse(input_t &input, int button, bool release) { auto raw = (input_raw_t *) input.get(); platf::mouse::button(raw, button, release); } + /** + * @brief Apply a vertical scroll event to the virtual mouse. + */ void scroll(input_t &input, int high_res_distance) { auto raw = (input_raw_t *) input.get(); platf::mouse::scroll(raw, high_res_distance); } + /** + * @brief Apply a horizontal scroll event to the virtual mouse. + */ void hscroll(input_t &input, int high_res_distance) { auto raw = (input_raw_t *) input.get(); platf::mouse::hscroll(raw, high_res_distance); } + /** + * @brief Press or release a virtual keyboard key. + */ void keyboard_update(input_t &input, uint16_t modcode, bool release, uint8_t flags) { auto raw = (input_raw_t *) input.get(); platf::keyboard::update(raw, modcode, release, flags); } + /** + * @brief Submit UTF-8 text input to the keyboard backend. + */ void unicode(input_t &input, char *utf8, int size) { auto raw = (input_raw_t *) input.get(); platf::keyboard::unicode(raw, utf8, size); @@ -84,6 +111,9 @@ namespace platf { return platf::gamepad::alloc(raw, id, metadata, feedback_queue); } + /** + * @brief Release gamepad resources. + */ void free_gamepad(input_t &input, int nr) { auto raw = (input_raw_t *) input.get(); platf::gamepad::free(raw, nr); diff --git a/src/platform/linux/input/inputtino_common.h b/src/platform/linux/input/inputtino_common.h index acc1b759d..a6eba5e72 100644 --- a/src/platform/linux/input/inputtino_common.h +++ b/src/platform/linux/input/inputtino_common.h @@ -20,6 +20,12 @@ using namespace std::literals; namespace platf { + /** + * @brief Append the target seat name to an inputtino device name when needed. + * + * @param base_name Base uinput device name. + * @return Device name scoped to the target seat. + */ inline std::string inputtino_name_for_seat(std::string_view base_name) { auto seat_id = inputtino_seat::get_target_seat(); if (seat_id.empty() || seat_id == "seat0") { @@ -35,14 +41,23 @@ namespace platf { return name; } + /** + * @brief Variant of inputtino virtual gamepad implementations Sunshine can create. + */ using joypads_t = std::variant; + /** + * @brief inputtino joypad collection and its ownership state. + */ struct joypad_state { - std::unique_ptr joypad; - gamepad_feedback_msg_t last_rumble; - gamepad_feedback_msg_t last_rgb_led; + std::unique_ptr joypad; ///< Active virtual gamepad object for one connected client slot. + gamepad_feedback_msg_t last_rumble; ///< Last rumble. + gamepad_feedback_msg_t last_rgb_led; ///< Last RGB led. }; + /** + * @brief Global inputtino device handles shared by clients. + */ struct input_raw_t { input_raw_t(): mouse(inputtino::Mouse::create({ @@ -69,8 +84,8 @@ namespace platf { ~input_raw_t() = default; // All devices are wrapped in Result because it might be that we aren't able to create them (ex: udev permission denied) - inputtino::Result mouse; - inputtino::Result keyboard; + inputtino::Result mouse; ///< Shared inputtino virtual mouse device. + inputtino::Result keyboard; ///< inputtino virtual keyboard device. /** * A list of gamepads that are currently connected. @@ -79,7 +94,15 @@ namespace platf { std::vector> gamepads; }; + /** + * @brief Per-client inputtino devices for touch and pen input. + */ struct client_input_raw_t: public client_input_t { + /** + * @brief Create per-client inputtino devices for touch and pen input. + * + * @param input Platform input backend that receives the event. + */ client_input_raw_t(input_t &input): touch(inputtino::TouchScreen::create({ .name = inputtino_name_for_seat("Touch passthrough"sv), @@ -102,16 +125,22 @@ namespace platf { } } - input_raw_t *global; + input_raw_t *global; ///< Shared inputtino device set owned by the global input context. // Device state and handles for pen and touch input must be stored in the per-client // input context, because each connected client may be sending their own independent // pen/touch events. To maintain separation, we expose separate pen and touch devices // for each client. - inputtino::Result touch; - inputtino::Result pen; + inputtino::Result touch; ///< Per-client virtual touchscreen device. + inputtino::Result pen; ///< Per-client virtual pen tablet device. }; + /** + * @brief Convert degrees to radians for controller motion data. + * + * @param degree Angle in degrees to convert. + * @return Angle in radians. + */ inline float deg2rad(float degree) { return degree * (M_PI / 180.f); } diff --git a/src/platform/linux/input/inputtino_gamepad.cpp b/src/platform/linux/input/inputtino_gamepad.cpp index dfecc1084..c5c9d3230 100644 --- a/src/platform/linux/input/inputtino_gamepad.cpp +++ b/src/platform/linux/input/inputtino_gamepad.cpp @@ -20,6 +20,9 @@ using namespace std::literals; namespace platf::gamepad { + /** + * @brief Enumerates supported gamepad status options. + */ enum GamepadStatus { UHID_NOT_AVAILABLE = 0, ///< UHID is not available UINPUT_NOT_AVAILABLE, ///< UINPUT is not available @@ -27,6 +30,11 @@ namespace platf::gamepad { GAMEPAD_STATUS ///< Helper to indicate the number of status }; + /** + * @brief Create xbox one. + * + * @return Created xbox one object or status. + */ auto create_xbox_one() { return inputtino::XboxOneJoypad::create({.name = inputtino_name_for_seat("Sunshine X-Box One (virtual) pad"sv), // https://github.com/torvalds/linux/blob/master/drivers/input/joystick/xpad.c#L147 @@ -35,6 +43,11 @@ namespace platf::gamepad { .version = 0x0408}); } + /** + * @brief Create an inputtino Nintendo Switch Pro controller. + * + * @return Created switch object or status. + */ auto create_switch() { return inputtino::SwitchJoypad::create({.name = inputtino_name_for_seat("Sunshine Nintendo (virtual) pad"sv), // https://github.com/torvalds/linux/blob/master/drivers/hid/hid-ids.h#L981 @@ -43,6 +56,12 @@ namespace platf::gamepad { .version = 0x8111}); } + /** + * @brief Create an inputtino DualSense controller. + * + * @param globalIndex Global index. + * @return Created DS5 object or status. + */ auto create_ds5(int globalIndex) { std::string device_mac = ""; // Inputtino checks empty() to generate a random MAC @@ -54,6 +73,9 @@ namespace platf::gamepad { return inputtino::PS5Joypad::create({.name = inputtino_name_for_seat("Sunshine PS5 (virtual) pad"sv), .vendor_id = 0x054C, .product_id = 0x0CE6, .version = 0x8111, .device_phys = device_mac, .device_uniq = device_mac}); } + /** + * @brief Allocate and initialize platform input state for a stream. + */ int alloc(input_raw_t *raw, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) { ControllerType selectedGamepadType; @@ -180,12 +202,18 @@ namespace platf::gamepad { return -1; } + /** + * @brief Release backend resources for the indexed gamepad. + */ void free(input_raw_t *raw, int nr) { // This will call the destructor which in turn will stop the background threads for rumble and LED (and ultimately remove the joypad device) raw->gamepads[nr]->joypad.reset(); raw->gamepads[nr].reset(); } + /** + * @brief Apply the supplied state update to the platform backend. + */ void update(input_raw_t *raw, int nr, const gamepad_state_t &gamepad_state) { auto gamepad = raw->gamepads[nr]; if (!gamepad) { @@ -201,6 +229,9 @@ namespace platf::gamepad { *gamepad->joypad); } + /** + * @brief Apply controller touchpad data to the backend device. + */ void touch(input_raw_t *raw, const gamepad_touch_t &touch) { auto gamepad = raw->gamepads[touch.id.globalIndex]; if (!gamepad) { @@ -216,6 +247,9 @@ namespace platf::gamepad { } } + /** + * @brief Apply controller motion sensor data to the backend device. + */ void motion(input_raw_t *raw, const gamepad_motion_t &motion) { auto gamepad = raw->gamepads[motion.id.globalIndex]; if (!gamepad) { @@ -234,6 +268,9 @@ namespace platf::gamepad { } } + /** + * @brief Apply controller battery status to the backend device. + */ void battery(input_raw_t *raw, const gamepad_battery_t &battery) { auto gamepad = raw->gamepads[battery.id.globalIndex]; if (!gamepad) { @@ -263,6 +300,9 @@ namespace platf::gamepad { } } + /** + * @brief Return the virtual gamepad types supported by inputtino. + */ std::vector &supported_gamepads(input_t *input) { if (!input) { static std::vector gps { diff --git a/src/platform/linux/input/inputtino_gamepad.h b/src/platform/linux/input/inputtino_gamepad.h index 8d26c9e1f..76d385318 100644 --- a/src/platform/linux/input/inputtino_gamepad.h +++ b/src/platform/linux/input/inputtino_gamepad.h @@ -17,23 +17,72 @@ using namespace std::literals; namespace platf::gamepad { + /** + * @brief Enumerates supported controller type options. + */ enum ControllerType { XboxOneWired, ///< Xbox One Wired Controller DualSenseWired, ///< DualSense Wired Controller SwitchProWired ///< Switch Pro Wired Controller }; + /** + * @brief Allocate and initialize platform input state for a stream. + * + * @param raw Platform-specific input backend state. + * @param id Identifier for the controller, session, display, or resource. + * @param metadata Output structure populated with HDR metadata. + * @param feedback_queue Feedback queue. + * @return Allocated object or identifier, or an error value on failure. + */ int alloc(input_raw_t *raw, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue); + /** + * @brief Release backend resources for the indexed gamepad. + * + * @param raw Platform-specific input backend state. + * @param nr Controller index assigned by the client. + */ void free(input_raw_t *raw, int nr); + /** + * @brief Apply the supplied state update to the platform backend. + * + * @param raw Platform-specific input backend state. + * @param nr Controller index assigned by the client. + * @param gamepad_state Gamepad state. + */ void update(input_raw_t *raw, int nr, const gamepad_state_t &gamepad_state); + /** + * @brief Apply controller touchpad data to the backend device. + * + * @param raw Platform-specific input backend state. + * @param touch Touch event data to apply to the virtual device. + */ void touch(input_raw_t *raw, const gamepad_touch_t &touch); + /** + * @brief Apply controller motion sensor data to the backend device. + * + * @param raw Platform-specific input backend state. + * @param motion Motion sensor data to apply to the virtual device. + */ void motion(input_raw_t *raw, const gamepad_motion_t &motion); + /** + * @brief Apply controller battery status to the backend device. + * + * @param raw Platform-specific input backend state. + * @param battery Battery status data reported by the virtual device. + */ void battery(input_raw_t *raw, const gamepad_battery_t &battery); + /** + * @brief Return gamepad slots supported by the inputtino backend. + * + * @param input Platform input backend that receives the event. + * @return Mutable list of supported virtual gamepads for the input backend. + */ std::vector &supported_gamepads(input_t *input); } // namespace platf::gamepad diff --git a/src/platform/linux/input/inputtino_keyboard.cpp b/src/platform/linux/input/inputtino_keyboard.cpp index 72c456956..7d466566e 100644 --- a/src/platform/linux/input/inputtino_keyboard.cpp +++ b/src/platform/linux/input/inputtino_keyboard.cpp @@ -25,6 +25,8 @@ namespace platf::keyboard { * ex: ['👱'] = "1F471" // see UTF encoding at https://www.compart.com/en/unicode/U+1F471 * * adapted from: https://stackoverflow.com/a/7639754 + * @param str UTF-8 text to encode as hexadecimal. + * @return Value converted to hex. */ std::string to_hex(const std::basic_string &str) { std::stringstream ss; @@ -160,6 +162,9 @@ namespace platf::keyboard { {KEY_102ND, 0xE2} }; + /** + * @brief Apply the supplied state update to the platform backend. + */ void update(input_raw_t *raw, uint16_t modcode, bool release, uint8_t flags) { if (raw->keyboard) { if (release) { @@ -170,6 +175,9 @@ namespace platf::keyboard { } } + /** + * @brief Submit UTF-8 text input to the keyboard backend. + */ void unicode(input_raw_t *raw, char *utf8, int size) { if (raw->keyboard) { /* Reading input text as UTF-8 */ diff --git a/src/platform/linux/input/inputtino_keyboard.h b/src/platform/linux/input/inputtino_keyboard.h index 6405b6258..2bf28575a 100644 --- a/src/platform/linux/input/inputtino_keyboard.h +++ b/src/platform/linux/input/inputtino_keyboard.h @@ -15,7 +15,22 @@ using namespace std::literals; namespace platf::keyboard { + /** + * @brief Apply the supplied state update to the platform backend. + * + * @param raw Platform-specific input backend state. + * @param modcode Modifier key code to update. + * @param release Whether the key or button event is a release. + * @param flags Bit flags that modify the requested operation. + */ void update(input_raw_t *raw, uint16_t modcode, bool release, uint8_t flags); + /** + * @brief Submit UTF-8 text input to the keyboard backend. + * + * @param raw Platform-specific input backend state. + * @param utf8 UTF-8 text submitted by the client. + * @param size Number of bytes or elements requested. + */ void unicode(input_raw_t *raw, char *utf8, int size); } // namespace platf::keyboard diff --git a/src/platform/linux/input/inputtino_mouse.cpp b/src/platform/linux/input/inputtino_mouse.cpp index f8d822de2..c837c8154 100644 --- a/src/platform/linux/input/inputtino_mouse.cpp +++ b/src/platform/linux/input/inputtino_mouse.cpp @@ -19,18 +19,27 @@ using namespace std::literals; namespace platf::mouse { + /** + * @brief Apply a relative pointer movement to the virtual mouse. + */ void move(input_raw_t *raw, int deltaX, int deltaY) { if (raw->mouse) { (*raw->mouse).move(deltaX, deltaY); } } + /** + * @brief Move abs using the backend coordinate system. + */ void move_abs(input_raw_t *raw, const touch_port_t &touch_port, float x, float y) { if (raw->mouse) { (*raw->mouse).move_abs(x, y, touch_port.width, touch_port.height); } } + /** + * @brief Press or release a virtual mouse button. + */ void button(input_raw_t *raw, int button, bool release) { if (raw->mouse) { inputtino::Mouse::MOUSE_BUTTON btn_type; @@ -62,18 +71,27 @@ namespace platf::mouse { } } + /** + * @brief Apply a vertical scroll event to the virtual mouse. + */ void scroll(input_raw_t *raw, int high_res_distance) { if (raw->mouse) { (*raw->mouse).vertical_scroll(high_res_distance); } } + /** + * @brief Apply a horizontal scroll event to the virtual mouse. + */ void hscroll(input_raw_t *raw, int high_res_distance) { if (raw->mouse) { (*raw->mouse).horizontal_scroll(high_res_distance); } } + /** + * @brief Return the current virtual pointer location. + */ util::point_t get_location(input_raw_t *raw) { if (raw->mouse) { // TODO: decide what to do after https://github.com/games-on-whales/inputtino/issues/6 is resolved. diff --git a/src/platform/linux/input/inputtino_mouse.h b/src/platform/linux/input/inputtino_mouse.h index 67eaf97af..0bd5a2e71 100644 --- a/src/platform/linux/input/inputtino_mouse.h +++ b/src/platform/linux/input/inputtino_mouse.h @@ -15,15 +15,55 @@ using namespace std::literals; namespace platf::mouse { + /** + * @brief Apply a relative pointer movement to the virtual mouse. + * + * @param raw Platform-specific input backend state. + * @param deltaX Horizontal relative movement in client coordinates. + * @param deltaY Vertical relative movement in client coordinates. + */ void move(input_raw_t *raw, int deltaX, int deltaY); + /** + * @brief Move the pointer to an absolute client-provided touch coordinate. + * + * @param raw Platform-specific input backend state. + * @param touch_port Touch coordinate bounds used for scaling. + * @param x Horizontal absolute coordinate from the client. + * @param y Vertical absolute coordinate from the client. + */ void move_abs(input_raw_t *raw, const touch_port_t &touch_port, float x, float y); + /** + * @brief Press or release a virtual mouse button. + * + * @param raw Platform-specific input backend state. + * @param button Mouse button identifier to press or release. + * @param release Whether the key or button event is a release. + */ void button(input_raw_t *raw, int button, bool release); + /** + * @brief Apply a vertical scroll event to the virtual mouse. + * + * @param raw Platform-specific input backend state. + * @param high_res_distance High-resolution scroll distance reported by the client. + */ void scroll(input_raw_t *raw, int high_res_distance); + /** + * @brief Apply a horizontal scroll event to the virtual mouse. + * + * @param raw Platform-specific input backend state. + * @param high_res_distance High-resolution scroll distance reported by the client. + */ void hscroll(input_raw_t *raw, int high_res_distance); + /** + * @brief Return the current virtual pointer location. + * + * @param raw Platform-specific input backend state. + * @return Current virtual pointer location in screen coordinates. + */ util::point_t get_location(input_raw_t *raw); } // namespace platf::mouse diff --git a/src/platform/linux/input/inputtino_pen.cpp b/src/platform/linux/input/inputtino_pen.cpp index ed10f4071..b4e245e1e 100644 --- a/src/platform/linux/input/inputtino_pen.cpp +++ b/src/platform/linux/input/inputtino_pen.cpp @@ -18,6 +18,9 @@ using namespace std::literals; namespace platf::pen { + /** + * @brief Apply the supplied state update to the platform backend. + */ void update(client_input_raw_t *raw, const touch_port_t &touch_port, const pen_input_t &pen) { if (raw->pen) { // First set the buttons diff --git a/src/platform/linux/input/inputtino_pen.h b/src/platform/linux/input/inputtino_pen.h index 667f4ad8d..0c2cb3f8c 100644 --- a/src/platform/linux/input/inputtino_pen.h +++ b/src/platform/linux/input/inputtino_pen.h @@ -16,5 +16,12 @@ using namespace std::literals; namespace platf::pen { + /** + * @brief Apply the supplied state update to the platform backend. + * + * @param raw Platform-specific input backend state. + * @param touch_port Touch coordinate bounds used for scaling. + * @param pen Pen event data to inject. + */ void update(client_input_raw_t *raw, const touch_port_t &touch_port, const pen_input_t &pen); -} +} // namespace platf::pen diff --git a/src/platform/linux/input/inputtino_seat.h b/src/platform/linux/input/inputtino_seat.h index 63fe54013..00474f7ec 100644 --- a/src/platform/linux/input/inputtino_seat.h +++ b/src/platform/linux/input/inputtino_seat.h @@ -11,6 +11,8 @@ namespace platf::inputtino_seat { /** * Determine the target seat for the current Sunshine instance. * Returns empty string if no seat could be determined. + * + * @return Seat name used for virtual input devices, or an empty string when unknown. */ std::string get_target_seat(); diff --git a/src/platform/linux/input/inputtino_touch.cpp b/src/platform/linux/input/inputtino_touch.cpp index c7dce6044..e8c535661 100644 --- a/src/platform/linux/input/inputtino_touch.cpp +++ b/src/platform/linux/input/inputtino_touch.cpp @@ -18,6 +18,9 @@ using namespace std::literals; namespace platf::touch { + /** + * @brief Apply the supplied state update to the platform backend. + */ void update(client_input_raw_t *raw, const touch_port_t &touch_port, const touch_input_t &touch) { if (raw->touch) { switch (touch.eventType) { diff --git a/src/platform/linux/input/inputtino_touch.h b/src/platform/linux/input/inputtino_touch.h index 5e1d65816..026e9dc24 100644 --- a/src/platform/linux/input/inputtino_touch.h +++ b/src/platform/linux/input/inputtino_touch.h @@ -16,5 +16,12 @@ using namespace std::literals; namespace platf::touch { + /** + * @brief Apply the supplied state update to the platform backend. + * + * @param raw Platform-specific input backend state. + * @param touch_port Touch coordinate bounds used for scaling. + * @param touch Touch event data to apply to the virtual device. + */ void update(client_input_raw_t *raw, const touch_port_t &touch_port, const touch_input_t &touch); -} +} // namespace platf::touch diff --git a/src/platform/linux/kmsgrab.cpp b/src/platform/linux/kmsgrab.cpp index 11571c7c3..470e27e52 100644 --- a/src/platform/linux/kmsgrab.cpp +++ b/src/platform/linux/kmsgrab.cpp @@ -37,6 +37,9 @@ namespace platf { namespace kms { + /** + * @brief Temporarily owns CAP_SYS_ADMIN while opening DRM capture resources. + */ class cap_sys_admin { public: cap_sys_admin() { @@ -56,11 +59,20 @@ namespace platf { cap_free(caps); } - cap_t caps; + cap_t caps; ///< Caps. }; + /** + * @brief RAII wrapper for DRM framebuffer metadata and GEM handles. + */ class wrapper_fb { public: + /** + * @brief Wrap legacy DRM framebuffer metadata. + * + * @param card_fd DRM card file descriptor used to close GEM handles. + * @param fb Legacy framebuffer metadata returned by drmModeGetFB. + */ wrapper_fb(uint32_t card_fd, drmModeFB *fb): card_fd {card_fd}, fb {fb}, @@ -76,6 +88,12 @@ namespace platf { pitches[0] = fb->pitch; } + /** + * @brief Wrap DRM framebuffer metadata with per-plane modifiers. + * + * @param card_fd DRM card file descriptor used to close GEM handles. + * @param fb2 Framebuffer metadata returned by drmModeGetFB2. + */ wrapper_fb(uint32_t card_fd, drmModeFB2 *fb2): card_fd {card_fd}, fb2 {fb2}, @@ -107,30 +125,63 @@ namespace platf { } } - uint32_t card_fd; - drmModeFB *fb = nullptr; - drmModeFB2 *fb2 = nullptr; - uint32_t fb_id; - uint32_t width; - uint32_t height; - uint32_t pixel_format; - uint64_t modifier; - uint32_t handles[4]; - uint32_t pitches[4]; - uint32_t offsets[4]; + uint32_t card_fd; ///< DRM card file descriptor that owns the framebuffer ID. + drmModeFB *fb = nullptr; ///< Legacy DRM framebuffer metadata when modifier data is unavailable. + drmModeFB2 *fb2 = nullptr; ///< DRM framebuffer metadata with modifier support. + uint32_t fb_id; ///< DRM framebuffer ID being captured. + uint32_t width; ///< Framebuffer width in pixels. + uint32_t height; ///< Framebuffer height in pixels. + uint32_t pixel_format; ///< DRM fourcc pixel format for the framebuffer. + uint64_t modifier; ///< DRM format modifier describing framebuffer memory layout. + uint32_t handles[4]; ///< GEM handles for each framebuffer plane. + uint32_t pitches[4]; ///< Row stride in bytes for each framebuffer plane. + uint32_t offsets[4]; ///< Byte offset to the first pixel for each framebuffer plane. }; + /** + * @brief DRM plane resource list released with `drmModeFreePlaneResources`. + */ using plane_res_t = util::safe_ptr; + /** + * @brief DRM encoder pointer released with `drmModeFreeEncoder`. + */ using encoder_t = util::safe_ptr; + /** + * @brief DRM resource list released with `drmModeFreeResources`. + */ using res_t = util::safe_ptr; + /** + * @brief DRM plane pointer released with `drmModeFreePlane`. + */ using plane_t = util::safe_ptr; + /** + * @brief DRM framebuffer pointer released with `drmModeFreeFB2`. + */ using fb_t = std::unique_ptr; + /** + * @brief DRM CRTC pointer released with `drmModeFreeCrtc`. + */ using crtc_t = util::safe_ptr; + /** + * @brief DRM object-property list released with `drmModeFreeObjectProperties`. + */ using obj_prop_t = util::safe_ptr; + /** + * @brief DRM property pointer released with `drmModeFreeProperty`. + */ using prop_t = util::safe_ptr; + /** + * @brief DRM property-blob pointer released with `drmModeFreePropertyBlob`. + */ using prop_blob_t = util::safe_ptr; + /** + * @brief DRM version pointer released with `drmFreeVersion`. + */ using version_t = util::safe_ptr; + /** + * @brief Counter map keyed by DRM connector type. + */ using conn_type_count_t = std::map; static int env_width; @@ -139,6 +190,12 @@ namespace platf { static int env_logical_width; static int env_logical_height; + /** + * @brief Convert a DRM plane type value to a diagnostic string. + * + * @param val Value assigned to the synchronized object. + * @return Static text for the DRM plane type. + */ std::string_view plane_type(std::uint64_t val) { switch (val) { case DRM_PLANE_TYPE_OVERLAY: @@ -152,45 +209,56 @@ namespace platf { return "UNKNOWN"sv; } + /** + * @brief DRM connector and mode information for one display. + */ struct connector_t { // For example: HDMI-A or HDMI - std::uint32_t type; + std::uint32_t type; ///< Type. // Equals zero if not applicable - std::uint32_t crtc_id; + std::uint32_t crtc_id; ///< Crtc ID. // For example HDMI-A-{index} or HDMI-{index} - std::uint32_t index; + std::uint32_t index; ///< Index. // ID of the connector - std::uint32_t connector_id; + std::uint32_t connector_id; ///< Connector ID. - bool connected; + bool connected; ///< Whether the DRM connector is connected. }; + /** + * @brief KMS monitor capture state and DRM resources. + */ struct monitor_t { // Connector attributes - std::uint32_t type; - std::uint32_t index; + std::uint32_t type; ///< Type. + std::uint32_t index; ///< Index. // Monitor index in the global list - std::uint32_t monitor_index; + std::uint32_t monitor_index; ///< Monitor index. - platf::touch_port_t viewport; + platf::touch_port_t viewport; ///< Viewport. }; + /** + * @brief DRM card, device path, and render-node metadata. + */ struct card_descriptor_t { - std::string path; + std::string path; ///< Path. - std::map crtc_to_monitor; + std::map crtc_to_monitor; ///< Crtc to monitor. }; static std::vector card_descriptors; static std::uint32_t from_view(const std::string_view &string) { -#define _CONVERT(x, y) \ - if (string == x) \ - return DRM_MODE_CONNECTOR_##y +#ifndef DOXYGEN + #define _CONVERT(x, y) \ + if (string == x) \ + return DRM_MODE_CONNECTOR_##y +#endif // This list was created from the following sources: // https://gitlab.freedesktop.org/mesa/drm/-/blob/main/xf86drmMode.c (drmModeGetConnectorTypeName) @@ -244,8 +312,18 @@ namespace platf { return DRM_MODE_CONNECTOR_Unknown; } + /** + * @brief Iterator over DRM planes and their associated properties. + */ class plane_it_t: public round_robin_util::it_wrap_t { public: + /** + * @brief Create an iterator over DRM planes starting at a specific plane ID. + * + * @param fd DRM card file descriptor. + * @param plane_p Current plane ID pointer. + * @param end One-past-the-end plane ID pointer. + */ plane_it_t(int fd, std::uint32_t *plane_p, std::uint32_t *end): fd {fd}, plane_p {plane_p}, @@ -253,12 +331,21 @@ namespace platf { load_next_valid_plane(); } + /** + * @brief Create the end iterator for a DRM plane range. + * + * @param fd DRM card file descriptor. + * @param end One-past-the-end plane ID pointer. + */ plane_it_t(int fd, std::uint32_t *end): fd {fd}, plane_p {end}, end {end} { } + /** + * @brief Load next valid plane. + */ void load_next_valid_plane() { this->plane.reset(); @@ -274,50 +361,79 @@ namespace platf { } } + /** + * @brief Advance the iterator to the next element. + */ void inc() { ++plane_p; load_next_valid_plane(); } + /** + * @brief Compare two iterators for equality. + * + * @param other Plane iterator to compare against. + * @return True when both iterators reference the same plane position. + */ bool eq(const plane_it_t &other) const { return plane_p == other.plane_p; } + /** + * @brief Return the currently wrapped value or handle. + * + * @return Underlying native handle or object pointer. + */ plane_t::pointer get() { return plane.get(); } - int fd; - std::uint32_t *plane_p; - std::uint32_t *end; + int fd; ///< DRM card file descriptor. + std::uint32_t *plane_p; ///< Current plane ID pointer. + std::uint32_t *end; ///< One-past-the-end plane ID pointer. - util::shared_t plane; + util::shared_t plane; ///< Plane. }; + /** + * @brief Cursor position and visibility for the current capture frame. + */ struct cursor_t { // Public properties used during blending - bool visible = false; - std::int32_t x; - std::int32_t y; - std::uint32_t dst_w; - std::uint32_t dst_h; - std::uint32_t src_w; - std::uint32_t src_h; - std::vector pixels; - unsigned long serial; + bool visible = false; ///< Whether the KMS cursor plane is visible. + std::int32_t x; ///< X. + std::int32_t y; ///< Y. + std::uint32_t dst_w; ///< Dst w. + std::uint32_t dst_h; ///< Dst h. + std::uint32_t src_w; ///< Src w. + std::uint32_t src_h; ///< Src h. + std::vector pixels; ///< Pixels. + unsigned long serial; ///< Serial. // Private properties used for tracking cursor changes - std::uint64_t prop_src_x; - std::uint64_t prop_src_y; - std::uint64_t prop_src_w; - std::uint64_t prop_src_h; - std::uint32_t fb_id; + std::uint64_t prop_src_x; ///< Prop src x. + std::uint64_t prop_src_y; ///< Prop src y. + std::uint64_t prop_src_w; ///< Prop src w. + std::uint64_t prop_src_h; ///< Prop src h. + std::uint32_t fb_id; ///< Fb ID. }; + /** + * @brief DRM card, render node, and plane metadata used for KMS capture. + */ class card_t { public: + /** + * @brief Internal connector metadata tuple used while selecting a monitor. + */ using connector_interal_t = util::safe_ptr; + /** + * @brief Open a DRM connector and cache its monitor metadata. + * + * @param path Filesystem path for the DRM device or resource. + * @return 0 on success; nonzero or negative platform status on failure. + */ int init(const char *path) { cap_sys_admin admin; fd.el = open(path, O_RDWR); @@ -373,6 +489,12 @@ namespace platf { return 0; } + /** + * @brief Return framebuffer metadata for the current CRTC. + * + * @param plane DRM or EGL plane index being described. + * @return Framebuffer metadata wrapper, or nullptr when the framebuffer cannot be read. + */ fb_t fb(plane_t::pointer plane) { cap_sys_admin admin; @@ -389,23 +511,51 @@ namespace platf { return nullptr; } + /** + * @brief Return the DRM CRTC object for the monitor. + * + * @param id DRM CRTC ID. + * @return Owning pointer to the DRM CRTC object. + */ crtc_t crtc(std::uint32_t id) { return drmModeGetCrtc(fd.el, id); } + /** + * @brief Return the DRM encoder object for the connector. + * + * @param id DRM encoder ID. + * @return Owning pointer to the DRM encoder object. + */ encoder_t encoder(std::uint32_t id) { return drmModeGetEncoder(fd.el, id); } + /** + * @brief Return the DRM resource list for the card. + * + * @return DRM card resource list. + */ res_t res() { return drmModeGetResources(fd.el); } + /** + * @brief Check whether nvidia. + * + * @return True when the DRM device appears to be driven by NVIDIA. + */ bool is_nvidia() { version_t ver {drmGetVersion(fd.el)}; return ver && ver->name && strncmp(ver->name, "nvidia-drm", 10) == 0; } + /** + * @brief Check whether cursor. + * + * @param plane_id Plane ID. + * @return True when the DRM plane is a cursor plane. + */ bool is_cursor(std::uint32_t plane_id) { auto props = plane_props(plane_id); for (auto &[prop, val] : props) { @@ -421,6 +571,13 @@ namespace platf { return false; } + /** + * @brief Look up a DRM property value by property name. + * + * @param props DRM property collection to inspect. + * @param name DRM property name to search for. + * @return Property value when the property exists. + */ std::optional prop_value_by_name(const std::vector> &props, std::string_view name) { for (auto &[prop, val] : props) { if (prop->name == name) { @@ -430,6 +587,12 @@ namespace platf { return std::nullopt; } + /** + * @brief Get panel orientation. + * + * @param plane_id Plane ID. + * @return DRM rotation bitmask for the panel orientation. + */ std::uint32_t get_panel_orientation(std::uint32_t plane_id) { auto props = plane_props(plane_id); auto value = prop_value_by_name(props, "rotation"sv); @@ -441,6 +604,12 @@ namespace platf { return DRM_MODE_ROTATE_0; } + /** + * @brief Get crtc index by ID. + * + * @param crtc_id Crtc ID. + * @return Zero-based CRTC index, or -1 when the CRTC ID is unknown. + */ int get_crtc_index_by_id(std::uint32_t crtc_id) { auto resources = res(); for (int i = 0; i < resources->count_crtcs; i++) { @@ -451,10 +620,22 @@ namespace platf { return -1; } + /** + * @brief Return the DRM connector object for the display. + * + * @param id DRM connector ID. + * @return Owning pointer to the DRM connector object. + */ connector_interal_t connector(std::uint32_t id) { return drmModeGetConnector(fd.el, id); } + /** + * @brief Refresh the monitor list reported by the display server. + * + * @param conn_type_count Conn type count. + * @return Connector descriptors for monitors known to the DRM card. + */ std::vector monitors(conn_type_count_t &conn_type_count) { auto resources = res(); if (!resources) { @@ -489,6 +670,12 @@ namespace platf { return monitors; } + /** + * @brief Return the DRM file descriptor owned by the object. + * + * @param handle GEM handle exported by the DRM framebuffer. + * @return DMA-BUF file descriptor for the GEM handle. + */ file_t handleFD(std::uint32_t handle) { file_t fb_fd; @@ -500,6 +687,13 @@ namespace platf { return fb_fd; } + /** + * @brief Return DRM object properties for the resource. + * + * @param id DRM object ID. + * @param type DRM object type. + * @return DRM properties and their current values. + */ std::vector> props(std::uint32_t id, std::uint32_t type) { obj_prop_t obj_prop = drmModeObjectGetProperties(fd.el, id, type); if (!obj_prop) { @@ -516,39 +710,84 @@ namespace platf { return props; } + /** + * @brief Return DRM plane properties. + * + * @param id DRM plane ID. + * @return Plane properties and their current values. + */ std::vector> plane_props(std::uint32_t id) { return props(id, DRM_MODE_OBJECT_PLANE); } + /** + * @brief Return DRM CRTC properties. + * + * @param id DRM CRTC ID. + * @return CRTC properties and their current values. + */ std::vector> crtc_props(std::uint32_t id) { return props(id, DRM_MODE_OBJECT_CRTC); } + /** + * @brief Return DRM connector properties. + * + * @param id DRM connector ID. + * @return Connector properties and their current values. + */ std::vector> connector_props(std::uint32_t id) { return props(id, DRM_MODE_OBJECT_CONNECTOR); } + /** + * @brief Fetch DRM plane metadata by plane-list index. + * + * @param index Zero-based index into the DRM plane resource list. + * @return Plane metadata for the requested DRM plane. + */ plane_t operator[](std::uint32_t index) { return drmModeGetPlane(fd.el, plane_res->planes[index]); } + /** + * @brief Return the number of items in the wrapped DRM collection. + * + * @return Number of DRM planes reported by the card. + */ std::uint32_t count() { return plane_res->count_planes; } + /** + * @brief Return an iterator to the first byte in the buffer view. + * + * @return Iterator to the first element. + */ plane_it_t begin() const { return plane_it_t {fd.el, plane_res->planes, plane_res->planes + plane_res->count_planes}; } + /** + * @brief Return an iterator one past the final byte in the buffer view. + * + * @return Iterator one past the last element. + */ plane_it_t end() const { return plane_it_t {fd.el, plane_res->planes + plane_res->count_planes}; } - file_t fd; - file_t render_fd; - plane_res_t plane_res; + file_t fd; ///< DRM card file descriptor. + file_t render_fd; ///< Render fd. + plane_res_t plane_res; ///< Plane res. }; + /** + * @brief Build a lookup from DRM CRTC IDs to monitor descriptors. + * + * @param connectors DRM connector list being mapped to monitors. + * @return Map keyed by CRTC ID. + */ std::map map_crtc_to_monitor(const std::vector &connectors) { std::map result; @@ -562,6 +801,9 @@ namespace platf { return result; } + /** + * @brief Multi-source KMS image assembled for encoding. + */ struct kms_img_t: public img_t { ~kms_img_t() override { delete[] data; @@ -569,6 +811,13 @@ namespace platf { } }; + /** + * @brief Write a debug log representation of the input packet. + * + * @param plane DRM or EGL plane index being described. + * @param fb Framebuffer object to bind or update. + * @param crtc DRM CRTC identifier to map. + */ void print(plane_t::pointer plane, fb_t::pointer fb, crtc_t::pointer crtc) { if (crtc) { BOOST_LOG(debug) << "crtc("sv << crtc->x << ", "sv << crtc->y << ')'; @@ -601,13 +850,28 @@ namespace platf { BOOST_LOG(debug) << ss.str(); } + /** + * @brief Base KMS display capture backend shared by RAM and VRAM paths. + */ class display_t: public platf::display_t { public: + /** + * @brief Initialize common KMS display state for the requested memory type. + * + * @param mem_type Mem type. + */ display_t(mem_type_e mem_type): platf::display_t(), mem_type {mem_type} { } + /** + * @brief Initialize the base KMS capture state for the selected monitor. + * + * @param display_name Display name. + * @param config Configuration values to apply. + * @return 0 on success; nonzero or negative platform status on failure. + */ int init(const std::string &display_name, const ::video::config_t &config) { delay = std::chrono::nanoseconds {1s} / config.framerate; @@ -816,6 +1080,11 @@ namespace platf { return 0; } + /** + * @brief Report whether the active display mode is HDR. + * + * @return True when the active display mode is HDR. + */ bool is_hdr() { if (!hdr_metadata_blob_id || *hdr_metadata_blob_id == 0) { return false; @@ -862,6 +1131,12 @@ namespace platf { } } + /** + * @brief Read HDR metadata for the active display mode. + * + * @param metadata Output structure populated with HDR metadata. + * @return True when HDR metadata was written to the output structure. + */ bool get_hdr_metadata(SS_HDR_METADATA &metadata) { // This performs all the metadata validation if (!is_hdr()) { @@ -891,6 +1166,9 @@ namespace platf { return true; } + /** + * @brief Update cached cursor-plane image and position. + */ void update_cursor() { if (cursor_plane_id < 0) { return; @@ -1069,6 +1347,14 @@ namespace platf { } } + /** + * @brief Refresh cached platform state from the operating system. + * + * @param file DMA-BUF file descriptors exported for the current framebuffer. + * @param sd EGL surface descriptor to import. + * @param frame_timestamp Output timestamp assigned to the captured frame. + * @return Capture status after refreshing framebuffer and cursor state. + */ inline capture_e refresh(file_t *file, egl::surface_descriptor_t *sd, std::optional &frame_timestamp) { // Check for a change in HDR metadata if (connector_id) { @@ -1133,34 +1419,49 @@ namespace platf { return capture_e::ok; } - mem_type_e mem_type; + mem_type_e mem_type; ///< Mem type. - std::chrono::nanoseconds delay; + std::chrono::nanoseconds delay; ///< Delay before the timer task becomes eligible to run. - int img_width; - int img_height; - int img_offset_x; - int img_offset_y; + int img_width; ///< Img width. + int img_height; ///< Img height. + int img_offset_x; ///< Img offset x. + int img_offset_y; ///< Img offset y. - int plane_id; - int crtc_id; - int crtc_index; + int plane_id; ///< Plane ID. + int crtc_id; ///< Crtc ID. + int crtc_index; ///< Crtc index. - std::optional connector_id; - std::optional hdr_metadata_blob_id; + std::optional connector_id; ///< Connector ID. + std::optional hdr_metadata_blob_id; ///< HDR metadata blob ID. - int cursor_plane_id; - cursor_t captured_cursor {}; + int cursor_plane_id; ///< Cursor plane ID. + cursor_t captured_cursor {}; ///< Captured cursor. - card_t card; + card_t card; ///< Card. }; + /** + * @brief KMS capture backend that copies frames into system memory. + */ class display_ram_t: public display_t { public: + /** + * @brief Initialize a KMS display backend that copies frames through RAM. + * + * @param mem_type Mem type. + */ display_ram_t(mem_type_e mem_type): display_t(mem_type) { } + /** + * @brief Initialize KMS capture that copies frames into system memory. + * + * @param display_name Display name. + * @param config Configuration values to apply. + * @return 0 on success; nonzero or negative platform status on failure. + */ int init(const std::string &display_name, const ::video::config_t &config) { if (!gbm::create_device) { BOOST_LOG(warning) << "libgbm not initialized"sv; @@ -1237,6 +1538,12 @@ namespace platf { return capture_e::ok; } + /** + * @brief Create AVCodec encode device. + * + * @param pix_fmt Sunshine pixel format to convert or allocate for. + * @return Constructed AVCodec encode device object. + */ std::unique_ptr make_avcodec_encode_device(pix_fmt_e pix_fmt) override { #ifdef SUNSHINE_BUILD_VAAPI if (mem_type == mem_type_e::vaapi) { @@ -1253,6 +1560,11 @@ namespace platf { return std::make_unique(); } + /** + * @brief Blend the captured cursor plane into a KMS frame. + * + * @param img Image or frame object to read from or populate. + */ void blend_cursor(img_t &img) { // TODO: Cursor scaling is not supported in this codepath. // We always draw the cursor at the source size. @@ -1301,6 +1613,15 @@ namespace platf { } } + /** + * @brief Capture a display frame into the provided image object. + * + * @param pull_free_image_cb Callback that provides an available image buffer. + * @param img_out Captured KMS image returned to the streaming pipeline. + * @param timeout Maximum time to wait for the operation. + * @param cursor Cursor image or visibility state to composite. + * @return Capture status reported to the streaming pipeline. + */ capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor) { file_t fb_fd[4]; @@ -1344,6 +1665,11 @@ namespace platf { return capture_e::ok; } + /** + * @brief Allocate an image buffer compatible with this display backend. + * + * @return Allocated img object, or null when unavailable. + */ std::shared_ptr alloc_img() override { auto img = std::make_shared(); img->width = width; @@ -1355,21 +1681,41 @@ namespace platf { return img; } + /** + * @brief Populate a fallback image when real capture data is unavailable. + * + * @param img Image or frame object to read from or populate. + * @return Capture status reported to the streaming pipeline. + */ int dummy_img(platf::img_t *img) override { return 0; } - gbm::gbm_t gbm; - egl::display_t display; - egl::ctx_t ctx; + gbm::gbm_t gbm; ///< GBM device used for buffer allocation. + egl::display_t display; ///< EGL display created from the GBM device. + egl::ctx_t ctx; ///< EGL context used to copy KMS frames into RAM. }; + /** + * @brief KMS capture backend that exports frames as GPU resources. + */ class display_vram_t: public display_t { public: + /** + * @brief Initialize a KMS display backend that exports frames as GPU resources. + * + * @param mem_type Mem type. + */ display_vram_t(mem_type_e mem_type): display_t(mem_type) { } + /** + * @brief Create AVCodec encode device. + * + * @param pix_fmt Sunshine pixel format to convert or allocate for. + * @return Constructed AVCodec encode device object. + */ std::unique_ptr make_avcodec_encode_device(pix_fmt_e pix_fmt) override { #ifdef SUNSHINE_BUILD_VAAPI if (mem_type == mem_type_e::vaapi) { @@ -1393,6 +1739,11 @@ namespace platf { return nullptr; } + /** + * @brief Allocate an image buffer compatible with this display backend. + * + * @return Allocated img object, or null when unavailable. + */ std::shared_ptr alloc_img() override { auto img = std::make_shared(); @@ -1408,6 +1759,12 @@ namespace platf { return img; } + /** + * @brief Populate a fallback image when real capture data is unavailable. + * + * @param img Image or frame object to read from or populate. + * @return Capture status reported to the streaming pipeline. + */ int dummy_img(platf::img_t *img) override { // Empty images are recognized as dummies by the zero sequence number return 0; @@ -1458,6 +1815,14 @@ namespace platf { return capture_e::ok; } + /** + * @brief Capture a display frame into the provided image object. + * + * @param pull_free_image_cb Pull free image cb. + * @param img_out Captured KMS image descriptor returned to the streaming pipeline. + * @param cursor Cursor image or visibility state to composite. + * @return Capture status reported to the streaming pipeline. + */ capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds /* timeout */, bool cursor) { file_t fb_fd[4]; @@ -1500,6 +1865,13 @@ namespace platf { return capture_e::ok; } + /** + * @brief Initialize KMS capture that exports frames as GPU resources. + * + * @param display_name Display name. + * @param config Configuration values to apply. + * @return 0 on success; nonzero or negative platform status on failure. + */ int init(const std::string &display_name, const ::video::config_t &config) { if (display_t::init(display_name, config)) { return -1; @@ -1522,11 +1894,19 @@ namespace platf { return 0; } - std::uint64_t sequence {}; + std::uint64_t sequence {}; ///< Monotonic capture sequence assigned to KMS frames. }; } // namespace kms + /** + * @brief Create a KMS display capture backend. + * + * @param hwdevice_type Hardware device type requested for capture or encode. + * @param display_name Display name. + * @param config Configuration values to apply. + * @return KMS display backend, or nullptr when initialization fails. + */ std::shared_ptr kms_display(mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) { if (hwdevice_type == mem_type_e::vaapi || hwdevice_type == mem_type_e::cuda || hwdevice_type == mem_type_e::vulkan) { auto disp = std::make_shared(hwdevice_type); @@ -1556,6 +1936,7 @@ namespace platf { * But, it's necessary for absolute mouse coordinates to work. * * This is an ugly hack :( + * @param cds Capture display state used by the KMS backend. */ void correlate_to_wayland(std::vector &cds) { auto monitors = wl::monitors(); @@ -1613,6 +1994,12 @@ namespace platf { } // A list of names of displays accepted as display_name + /** + * @brief Enumerate display names accepted by the KMS backend. + * + * @param hwdevice_type Hardware device type requested for capture or encode. + * @return KMS display names, or an empty list when KMS capture is unavailable. + */ std::vector kms_display_names(mem_type_e hwdevice_type) { int count = 0; diff --git a/src/platform/linux/kwingrab.cpp b/src/platform/linux/kwingrab.cpp index 03831984c..fd3124870 100644 --- a/src/platform/linux/kwingrab.cpp +++ b/src/platform/linux/kwingrab.cpp @@ -49,10 +49,18 @@ namespace kwin { */ class screencast_permission_helper_t { public: + /** + * @brief Check whether permission system deactivated. + * + * @return True when KWin reports that the permission system is disabled. + */ static bool is_permission_system_deactivated() { return getenvstr("KWIN_WAYLAND_NO_PERMISSION_CHECKS") == "1"; } + /** + * @brief Configure the KWin screencast session. + */ static void setup() { if (initialized) { return; @@ -139,6 +147,11 @@ namespace kwin { initialized = true; } + /** + * @brief Check whether newly initialized. + * + * @return True when KWin was initialized during this check. + */ static bool is_newly_initialized() { return create_file; } @@ -238,13 +251,19 @@ namespace kwin { }; // Output parameters + /** + * @brief KWin screencast output name and geometry. + */ struct output_parameter_t { - std::string name = ""; - int width = 0; - int height = 0; - int pos_x = 0; - int pos_y = 0; + std::string name = ""; ///< KWin output name. + int width = 0; ///< Output width in pixels. + int height = 0; ///< Output height in pixels. + int pos_x = 0; ///< Output X position in the compositor layout. + int pos_y = 0; ///< Output Y position in the compositor layout. // order is needed to get a sorted output list and should be updated before sorting to have current values + /** + * @brief Order. + */ size_t order = SIZE_MAX; // Use high number to keep monitors with uninitialized order value to the back }; @@ -443,9 +462,9 @@ namespace kwin { return 0; } - uint32_t out_node_id = PW_ID_ANY; - uint64_t out_objectserial = SPA_ID_INVALID; - std::shared_ptr out_params = nullptr; + uint32_t out_node_id = PW_ID_ANY; ///< Out node ID. + uint64_t out_objectserial = SPA_ID_INVALID; ///< Out objectserial. + std::shared_ptr out_params = nullptr; ///< Out params. private: // Wayland objects @@ -688,12 +707,20 @@ namespace kwin { return -1; } - std::unique_ptr screencast; + std::unique_ptr screencast; ///< Screencast. }; } // namespace kwin // Public API for misc.cpp namespace platf { + /** + * @brief Create a KWin screencast display backend. + * + * @param hwdevice_type Hardware device type requested for capture or encode. + * @param display_name Display name. + * @param config Configuration values to apply. + * @return KWin/PipeWire display backend, or nullptr when initialization fails. + */ std::shared_ptr kwin_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) { if (!pipewire::pipewire_display_t::init_pipewire_and_check_hwdevice_type(hwdevice_type)) { BOOST_LOG(error) << "[kwingrab] Could not initialize pipewire-based display with the given hw device type."sv; @@ -708,6 +735,11 @@ namespace platf { return display; } + /** + * @brief Enumerate KWin screencast display names. + * + * @return KWin display names, or an empty list when KWin capture is unavailable. + */ std::vector kwin_display_names() { if (has_elevated_privileges(false)) { // We're still in the probing phase of Sunshine startup. Dropping portal security early will break KMS. @@ -724,6 +756,11 @@ namespace platf { return screencast->get_output_names(); } + /** + * @brief Check whether KWin screencast capture is available. + * + * @return True when KWin capture support is available. + */ bool kwin_available() { // Init screencast without permission setup (to not cause unneeded logs / temporary desktop files) and check KWin availability if (const auto screencast = std::make_unique(); screencast->init(false) < 0 || !screencast->kwin_available()) { diff --git a/src/platform/linux/misc.cpp b/src/platform/linux/misc.cpp index 5f417810f..1965e9f20 100644 --- a/src/platform/linux/misc.cpp +++ b/src/platform/linux/misc.cpp @@ -5,6 +5,10 @@ // Required for in6_pktinfo with glibc headers #ifndef _GNU_SOURCE + /** + * @def _GNU_SOURCE + * @brief Macro for GNU SOURCE. + */ #define _GNU_SOURCE 1 #endif @@ -60,16 +64,32 @@ #ifdef __GNUC__ #define SUNSHINE_GNUC_EXTENSION __extension__ #else + /** + * @def SUNSHINE_GNUC_EXTENSION + * @brief Macro for SUNSHINE GNUC EXTENSION. + */ #define SUNSHINE_GNUC_EXTENSION #endif #ifndef SOL_IP + /** + * @def SOL_IP + * @brief Macro for SOL IP. + */ #define SOL_IP IPPROTO_IP #endif #ifndef SOL_IPV6 + /** + * @def SOL_IPV6 + * @brief Macro for SOL IPv6. + */ #define SOL_IPV6 IPPROTO_IPV6 #endif #ifndef SOL_UDP + /** + * @def SOL_UDP + * @brief Macro for SOL UDP. + */ #define SOL_UDP IPPROTO_UDP #endif @@ -77,9 +97,12 @@ using namespace std::literals; namespace fs = std::filesystem; namespace bp = boost::process::v1; -window_system_e window_system; +window_system_e window_system; ///< Window system. namespace dyn { + /** + * @brief Return the native handle owned by the wrapper. + */ void *handle(const std::vector &libs) { void *handle; @@ -103,6 +126,9 @@ namespace dyn { return nullptr; } + /** + * @brief Load persisted state from its backing store. + */ int load(void *handle, const std::vector> &funcs, bool strict) { int err = 0; for (auto &func : funcs) { @@ -122,8 +148,16 @@ namespace dyn { } // namespace dyn namespace platf { + /** + * @brief Owning pointer for `getifaddrs` results. + */ using ifaddr_t = util::safe_ptr; + /** + * @brief Read the local interface address list. + * + * @return Owning pointer to the interface address list, or nullptr on failure. + */ ifaddr_t get_ifaddrs() { ifaddrs *p {nullptr}; @@ -215,6 +249,9 @@ namespace platf { return config_path; } + /** + * @brief Convert a socket address to a printable IP address. + */ std::string from_sockaddr(const sockaddr *const ip_addr) { char data[INET6_ADDRSTRLEN] = {}; @@ -228,6 +265,9 @@ namespace platf { return std::string {data}; } + /** + * @brief Convert a socket address to a port and printable IP address. + */ std::pair from_sockaddr_ex(const sockaddr *const ip_addr) { char data[INET6_ADDRSTRLEN] = {}; @@ -244,6 +284,9 @@ namespace platf { return {port, std::string {data}}; } + /** + * @brief Return the hardware MAC address associated with a network address. + */ std::string get_mac_address(const std::string_view &address) { auto ifaddrs = get_ifaddrs(); @@ -336,6 +379,9 @@ namespace platf { } } + /** + * @brief Apply the requested scheduling priority to the current thread. + */ void adjust_thread_priority(thread_priority_e priority) { #if defined(__FreeBSD__) pid_t tid = syscall(SYS_thr_self); @@ -405,18 +451,30 @@ namespace platf { pthread_setname_np(pthread_self(), name.c_str()); } + /** + * @brief Enable or disable X11 mouse keys for the current session. + */ void enable_mouse_keys() { // Unimplemented } + /** + * @brief Apply Linux platform state before streaming starts. + */ void streaming_will_start() { // Nothing to do } + /** + * @brief Restore Linux platform state after streaming stops. + */ void streaming_will_stop() { // Nothing to do } + /** + * @brief Request a Sunshine process restart on exit. + */ void restart_on_exit() { char executable[PATH_MAX]; ssize_t len = readlink("/proc/self/exe", executable, PATH_MAX - 1); @@ -439,12 +497,21 @@ namespace platf { } } + /** + * @brief Restart the Sunshine process through the platform launcher. + */ void restart() { // Gracefully clean up and restart ourselves instead of exiting atexit(restart_on_exit); lifetime::exit_sunshine(0, true); } + /** + * @brief Read an environment variable as an optional string. + * + * @param name Human-readable name to assign. + * @return Environment variable value, or an empty string when unset. + */ std::string get_env(const std::string &name) { if (const auto value = getenv(name.c_str()); value != nullptr) { return value; @@ -456,6 +523,14 @@ namespace platf { return setenv(name.c_str(), value.c_str(), 1); } + /** + * @brief Append a value to a separator-delimited environment variable. + * + * @param name Human-readable name to assign. + * @param value Entry to add when it is not already present. + * @param separator Character used to join or split the value. + * @return Result from updating the environment variable. + */ int append_env(const std::string &name, const std::string &value, const std::string &separator) { if (const std::string old_value = get_env(name); !old_value.contains(value)) { return set_env(name, old_value.empty() ? value : old_value + separator + value); @@ -481,6 +556,13 @@ namespace platf { return waitpid(-((pid_t) native_handle), nullptr, WNOHANG) >= 0; } + /** + * @brief Convert to sockaddr. + * + * @param address Network address being parsed or filtered. + * @param port TCP or UDP port number. + * @return Value converted to sockaddr. + */ struct sockaddr_in to_sockaddr(boost::asio::ip::address_v4 address, uint16_t port) { struct sockaddr_in saddr_v4 = {}; @@ -493,6 +575,13 @@ namespace platf { return saddr_v4; } + /** + * @brief Convert to sockaddr. + * + * @param address Network address being parsed or filtered. + * @param port TCP or UDP port number. + * @return Value converted to sockaddr. + */ struct sockaddr_in6 to_sockaddr(boost::asio::ip::address_v6 address, uint16_t port) { struct sockaddr_in6 saddr_v6 = {}; @@ -506,6 +595,9 @@ namespace platf { return saddr_v6; } + /** + * @brief Send multiple fixed-size UDP payload blocks using the platform backend. + */ bool send_batch(batched_send_info_t &send_info) { auto sockfd = (int) send_info.native_socket; struct msghdr msg = {}; @@ -731,6 +823,9 @@ namespace platf { } } + /** + * @brief Send the serialized response over the active socket. + */ bool send(send_info_t &send_info) { auto sockfd = (int) send_info.native_socket; struct msghdr msg = {}; @@ -854,8 +949,17 @@ namespace platf { // are disconnected. static std::atomic qos_ref_count = 0; + /** + * @brief Linux QoS state used to tune socket priority while streaming. + */ class qos_t: public deinit_t { public: + /** + * @brief Apply Linux socket priority and DSCP QoS settings for scoped cleanup. + * + * @param sockfd Native socket descriptor whose options are updated. + * @param options Request options or socket options to apply. + */ qos_t(int sockfd, std::vector> options): sockfd(sockfd), options(options) { @@ -880,11 +984,6 @@ namespace platf { /** * @brief Enables QoS on the given socket for traffic to the specified destination. - * @param native_socket The native socket handle. - * @param address The destination address for traffic sent on this socket. - * @param port The destination port for traffic sent on this socket. - * @param data_type The type of traffic sent on this socket. - * @param dscp_tagging Specifies whether to enable DSCP tagging on outgoing traffic. */ std::unique_ptr enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type, bool dscp_tagging) { int sockfd = (int) native_socket; @@ -964,6 +1063,9 @@ namespace platf { } namespace source { + /** + * @brief Enumerates supported source options. + */ enum source_e : std::size_t { #ifdef SUNSHINE_BUILD_CUDA NVFBC, ///< NvFBC @@ -999,9 +1101,27 @@ namespace platf { #endif #ifdef SUNSHINE_BUILD_WAYLAND + /** + * @brief Enumerate displays available through the Wayland capture backend. + * + * @return Wayland display names, or an empty list when discovery fails. + */ std::vector wl_display_names(); + /** + * @brief Create a Wayland display capture backend. + * + * @param hwdevice_type Hardware device type requested for capture or encode. + * @param display_name Display name. + * @param config Configuration values to apply. + * @return Display backend, or nullptr when Wayland capture initialization fails. + */ std::shared_ptr wl_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config); + /** + * @brief Check whether Wayland capture is available for the current session. + * + * @return True when the active window system is Wayland and at least one output is discoverable. + */ bool verify_wl() { return window_system == window_system_e::WAYLAND && !wl_display_names().empty(); } @@ -1045,6 +1165,9 @@ namespace platf { } #endif + /** + * @brief List display names accepted by the selected capture backend. + */ std::vector display_names(mem_type_e hwdevice_type) { #ifdef SUNSHINE_BUILD_CUDA // display using NvFBC only supports mem_type_e::cuda @@ -1081,8 +1204,9 @@ namespace platf { } /** - * @brief Returns if GPUs/drivers have changed since the last call to this function. - * @return `true` if a change has occurred or if it is unknown whether a change occurred. + * @brief Report whether encoder backends should be probed again before streaming. + * + * @return Always `true` because Linux GPU changes are not tracked by this backend. */ bool needs_encoder_reenumeration() { // We don't track GPU state, so we will always reenumerate. Fortunately, it is fast on Linux. @@ -1137,6 +1261,9 @@ namespace platf { return nullptr; } + /** + * @brief Initialize the Linux high-precision timer file descriptor. + */ std::unique_ptr init() { // enable low latency mode for AMD // https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/30039 @@ -1212,6 +1339,9 @@ namespace platf { return std::make_unique(); } + /** + * @brief Linux high-precision timer implementation backed by `timerfd`. + */ class linux_high_precision_timer: public high_precision_timer { public: void sleep_for(const std::chrono::nanoseconds &duration) override { @@ -1227,6 +1357,11 @@ namespace platf { return std::make_unique(); } + /** + * @brief Find the DRM render node associated with the active display. + * + * @return Render-node path, or an empty string when no matching node is found. + */ std::string find_render_node_with_display() { #ifdef SUNSHINE_BUILD_DRM auto *dir = opendir("/dev/dri"); @@ -1287,8 +1422,8 @@ namespace platf { static constexpr cap_value_t FULL_CAPS[] = {CAP_SYS_ADMIN, CAP_SYS_NICE}; static constexpr cap_value_t ADMIN_CAPS[] = {CAP_SYS_ADMIN}; - constexpr std::span ELEVATED_PRIVILEGES_FULL {FULL_CAPS}; - constexpr std::span ELEVATED_PRIVILEGES_ADMIN {ADMIN_CAPS}; + constexpr std::span ELEVATED_PRIVILEGES_FULL {FULL_CAPS}; ///< Protocol or platform constant for elevated privileges full. + constexpr std::span ELEVATED_PRIVILEGES_ADMIN {ADMIN_CAPS}; ///< Protocol or platform constant for elevated privileges admin. #endif bool has_elevated_privileges(bool all_caps) { diff --git a/src/platform/linux/misc.h b/src/platform/linux/misc.h index c9f98f44d..fc36045a3 100644 --- a/src/platform/linux/misc.h +++ b/src/platform/linux/misc.h @@ -11,21 +11,34 @@ // local includes #include "src/utility.h" +#ifndef DOXYGEN KITTY_USING_MOVE_T(file_t, int, -1, { if (el >= 0) { close(el); } }); +#else +/** + * @brief Move-only wrapper for a POSIX file descriptor. + */ +class file_t; +#endif +/** + * @brief Enumerates supported window system options. + */ enum class window_system_e { NONE, ///< No window system X11, ///< X11 WAYLAND, ///< Wayland }; -extern window_system_e window_system; +extern window_system_e window_system; ///< Window system. namespace dyn { + /** + * @brief Generic GLX procedure pointer returned by the loader. + */ typedef void (*apiproc)(void); int load(void *handle, const std::vector> &funcs, bool strict = true); diff --git a/src/platform/linux/pipewire.cpp b/src/platform/linux/pipewire.cpp index a571f0e47..9d9a92c85 100644 --- a/src/platform/linux/pipewire.cpp +++ b/src/platform/linux/pipewire.cpp @@ -25,7 +25,7 @@ #include "wayland.h" #if !PW_CHECK_VERSION(1, 6, 0) -constexpr int SPA_VIDEO_TRANSFER_SMPTE2084 = 14; +constexpr int SPA_VIDEO_TRANSFER_SMPTE2084 = 14; ///< Protocol or platform constant for spa video transfer smpte2084. #endif #if PW_CHECK_VERSION(0, 3, 75) @@ -36,7 +36,11 @@ const bool SUNSHINE_USE_PIPEWIRE_OBJECT_SERIAL = pw_check_library_version(0, 3, constexpr bool SUNSHINE_USE_PIPEWIRE_OBJECT_SERIAL = true; #else // Pipewire object serials are unsupported without PW_KEY_TARGET_OBJECT (we define it here so compilation won't break but don't use it). -constexpr bool SUNSHINE_USE_PIPEWIRE_OBJECT_SERIAL = false; +constexpr bool SUNSHINE_USE_PIPEWIRE_OBJECT_SERIAL = false; ///< Whether PipeWire object serials should be used for matching. + /** + * @def PW_KEY_TARGET_OBJECT + * @brief Macro for PW KEY TARGET OBJECT. + */ #define PW_KEY_TARGET_OBJECT "target.object" #endif @@ -51,9 +55,12 @@ namespace { using namespace std::literals; namespace pipewire { + /** + * @brief PipeWire SPA format mapped to Sunshine pixel format. + */ struct format_map_t { - uint64_t fourcc; - int32_t pw_format; + uint64_t fourcc; ///< DRM fourcc pixel format. + int32_t pw_format; ///< Matching PipeWire SPA video format. }; static constexpr std::array format_map = {{ @@ -66,47 +73,59 @@ namespace pipewire { {DRM_FORMAT_XRGB8888, SPA_VIDEO_FORMAT_BGRx}, }}; + /** + * @brief PipeWire capture state shared with callback threads. + */ struct shared_state_t { - std::atomic negotiated_width {0}; - std::atomic negotiated_height {0}; - std::atomic color_primaries {0}; - std::atomic transfer_function {0}; - std::atomic stream_dead {false}; - pw_stream_state previous_state; - pw_stream_state current_state; - std::string err_msg; + std::atomic negotiated_width {0}; ///< Width negotiated with PipeWire for the stream. + std::atomic negotiated_height {0}; ///< Height negotiated with PipeWire for the stream. + std::atomic color_primaries {0}; ///< PipeWire color-primaries metadata for the stream. + std::atomic transfer_function {0}; ///< PipeWire transfer-function metadata for the stream. + std::atomic stream_dead {false}; ///< Whether the PipeWire stream has been destroyed. + pw_stream_state previous_state; ///< Previous PipeWire stream state reported by callbacks. + pw_stream_state current_state; ///< Current PipeWire stream state reported by callbacks. + std::string err_msg; ///< Last PipeWire error message reported by the stream. }; + /** + * @brief PipeWire stream handle, format, and shared state pointer. + */ struct stream_data_t { - struct pw_stream *stream; - struct spa_hook stream_listener; - struct spa_video_info format; - struct pw_buffer *current_buffer; - uint64_t drm_format; - std::shared_ptr shared; - std::mutex frame_mutex; - std::condition_variable frame_cv; - size_t local_stride = 0; - bool frame_ready = false; + struct pw_stream *stream; ///< PipeWire stream handle used for screencast frames. + struct spa_hook stream_listener; ///< Hook registering callbacks on the PipeWire stream. + struct spa_video_info format; ///< Negotiated PipeWire video format. + struct pw_buffer *current_buffer; ///< PipeWire buffer currently exposed to the capture thread. + uint64_t drm_format; ///< DRM format. + std::shared_ptr shared; ///< State shared between PipeWire callbacks and the capture backend. + std::mutex frame_mutex; ///< Synchronizes access to the current PipeWire frame. + std::condition_variable frame_cv; ///< Signals arrival or release of a PipeWire frame. + size_t local_stride = 0; ///< Local stride. + bool frame_ready = false; ///< Whether a PipeWire frame is ready to consume. // Two distinct memory pools - std::vector buffer_a; - std::vector buffer_b; + std::vector buffer_a; ///< First staging buffer used for CPU-copy PipeWire frames. + std::vector buffer_b; ///< Second staging buffer used for CPU-copy PipeWire frames. // Points to the buffer currently owned by fill_img - std::vector *front_buffer; + std::vector *front_buffer; ///< Staging buffer currently readable by `fill_img`. // Points to the buffer currently being written by on_process - std::vector *back_buffer; + std::vector *back_buffer; ///< Staging buffer currently writable by PipeWire callbacks. stream_data_t(): front_buffer(&buffer_a), back_buffer(&buffer_b) {} }; + /** + * @brief DMA-BUF format and modifier list advertised by PipeWire. + */ struct dmabuf_format_info_t { - int32_t format; - uint64_t *modifiers; - int n_modifiers; + int32_t format; ///< PipeWire SPA video format being advertised. + uint64_t *modifiers; ///< DRM format modifiers supported for the format. + int n_modifiers; ///< Number of entries in `modifiers`. }; + /** + * @brief PipeWire core, context, and stream setup used for screencast capture. + */ class pipewire_t { public: pipewire_t(): @@ -154,22 +173,51 @@ namespace pipewire { pw_thread_loop_destroy(loop); } + /** + * @brief Return the mutex protecting PipeWire frame state. + * + * @return Mutex used by producer and capture threads. + */ std::mutex &frame_mutex() { return stream_data.frame_mutex; } + /** + * @brief Return the condition variable signaled when frame state changes. + * + * @return Condition variable used to wait for frames or shutdown. + */ std::condition_variable &frame_cv() { return stream_data.frame_cv; } + /** + * @brief Check whether frame ready. + * + * @return True when PipeWire has delivered a frame ready for capture. + */ bool is_frame_ready() const { return stream_data.frame_ready; } + /** + * @brief Set frame ready. + * + * @param ready Whether the PipeWire frame is ready for capture. + */ void set_frame_ready(bool ready) { stream_data.frame_ready = ready; } + /** + * @brief Initialize PipeWire core objects and optional stream negotiation. + * + * @param stream_fd Stream fd. + * @param stream_node Stream node. + * @param stream_object_serial Stream object serial. + * @param shared_state Shared state. + * @return 0 on success; nonzero or negative platform status on failure. + */ int init(const int stream_fd, const uint32_t stream_node, const uint64_t stream_object_serial, std::shared_ptr shared_state) { fd = stream_fd; node = stream_node; @@ -201,6 +249,9 @@ namespace pipewire { return 0; } + /** + * @brief Release the active PipeWire stream and listener. + */ void cleanup_stream() { BOOST_LOG(debug) << "[pipewire] Cleaning up stream"sv; if (loop && stream_data.stream) { @@ -226,6 +277,18 @@ namespace pipewire { } } + /** + * @brief Create the PipeWire stream if it is not already active. + * + * @param mem_type Mem type. + * @param width Frame or display width in pixels. + * @param height Frame or display height in pixels. + * @param refresh_rate Refresh rate. + * @param dmabuf_infos Dmabuf infos. + * @param n_dmabuf_infos N dmabuf infos. + * @param display_is_nvidia Display is nvidia. + * @return 0 when the PipeWire stream is configured; nonzero on negotiation failure. + */ int ensure_stream(const platf::mem_type_e mem_type, const uint32_t width, const uint32_t height, const uint32_t refresh_rate, const struct dmabuf_format_info_t *dmabuf_infos, const int n_dmabuf_infos, const bool display_is_nvidia) { pw_thread_loop_lock(loop); int result = 0; @@ -293,6 +356,11 @@ namespace pipewire { return result; } + /** + * @brief Close img fds. + * + * @param img_descriptor Image descriptor whose duplicated DMA-BUF fds are closed. + */ static void close_img_fds(egl::img_descriptor_t *img_descriptor) { for (int &fd : img_descriptor->sd.fds) { if (fd >= 0) { @@ -302,6 +370,12 @@ namespace pipewire { } } + /** + * @brief Copy PipeWire metadata into the Sunshine image descriptor. + * + * @param img_descriptor Image descriptor receiving timestamps, sequence, and damage flags. + * @param buf Raw byte buffer used for serialization. + */ static void fill_img_metadata(egl::img_descriptor_t *img_descriptor, struct spa_buffer *buf) { img_descriptor->frame_timestamp = std::chrono::steady_clock::now(); @@ -323,6 +397,13 @@ namespace pipewire { img_descriptor->pw_damage = (damage && damage->region.size.width > 0 && damage->region.size.height > 0) ? std::optional(true) : std::nullopt; } + /** + * @brief Populate a Sunshine image descriptor from PipeWire DMA-BUF planes. + * + * @param img_descriptor Image descriptor receiving duplicated fds and plane layout. + * @param buf Raw byte buffer used for serialization. + * @param d PipeWire listener data passed to the callback. + */ static void fill_img_dmabuf(egl::img_descriptor_t *img_descriptor, struct spa_buffer *buf, const stream_data_t &d) { img_descriptor->sd.width = d.format.info.raw.size.width; img_descriptor->sd.height = d.format.info.raw.size.height; @@ -335,6 +416,11 @@ namespace pipewire { } } + /** + * @brief Copy the latest PipeWire frame into Sunshine's image buffer. + * + * @param img Image or frame object to read from or populate. + */ void fill_img(platf::img_t *img) { pw_thread_loop_lock(loop); std::scoped_lock lock(stream_data.frame_mutex); @@ -367,6 +453,11 @@ namespace pipewire { pw_thread_loop_unlock(loop); } + /** + * @brief Set negotiate maxframerate. + * + * @param negotiate_maxframerate Negotiate maxframerate. + */ void set_negotiate_maxframerate(bool negotiate_maxframerate) { negotiate_maxframerate_ = negotiate_maxframerate; } @@ -626,8 +717,17 @@ namespace pipewire { }; }; + /** + * @brief Display capture backend that consumes frames from a PipeWire stream. + */ class pipewire_display_t: public platf::display_t { public: + /** + * @brief Initialize pipewire and check hwdevice type. + * + * @param hwdevice_type Hardware device type requested for capture or encode. + * @return True when PipeWire is initialized and the hardware device type is supported. + */ static bool init_pipewire_and_check_hwdevice_type(platf::mem_type_e hwdevice_type) { // Initialize pipewire to load necessary modules pw_init(nullptr, nullptr); @@ -694,6 +794,14 @@ namespace pipewire { } } + /** + * @brief Initialize the PipeWire display backend for a selected stream. + * + * @param hwdevice_type Hardware device type requested for capture or encode. + * @param display_name Display name. + * @param config Configuration values to apply. + * @return 0 on success; nonzero or negative platform status on failure. + */ int init(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) { // calculate frame interval we should capture at framerate = config.framerate; @@ -781,6 +889,15 @@ namespace pipewire { return 0; } + /** + * @brief Capture a display frame into the provided image object. + * + * @param pull_free_image_cb Callback that provides an available image buffer. + * @param img_out Captured PipeWire image returned to the streaming pipeline. + * @param timeout Maximum time to wait for the operation. + * @param show_cursor Show cursor. + * @return Capture status reported to the streaming pipeline. + */ platf::capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool show_cursor) { // FIXME: show_cursor is ignored auto deadline = std::chrono::steady_clock::now() + timeout; @@ -812,6 +929,11 @@ namespace pipewire { return platf::capture_e::timeout; } + /** + * @brief Allocate an image buffer compatible with this display backend. + * + * @return Allocated img object, or null when unavailable. + */ std::shared_ptr alloc_img() override { // Note: this img_t type is also used for memory buffers auto img = std::make_shared(); @@ -828,6 +950,12 @@ namespace pipewire { return img; } + /** + * @brief Check stream dead. + * + * @param out_status Out status. + * @return True when the PipeWire stream can no longer produce frames. + */ virtual bool check_stream_dead(platf::capture_e &out_status) { return false; // Return to default stream dead handling. } @@ -899,6 +1027,12 @@ namespace pipewire { return platf::capture_e::ok; } + /** + * @brief Create AVCodec encode device. + * + * @param pix_fmt Sunshine pixel format to convert or allocate for. + * @return Constructed AVCodec encode device object. + */ std::unique_ptr make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) override { #ifdef SUNSHINE_BUILD_VAAPI if (mem_type == platf::mem_type_e::vaapi) { @@ -928,6 +1062,12 @@ namespace pipewire { return std::make_unique(); } + /** + * @brief Populate a fallback image when real capture data is unavailable. + * + * @param img Image or frame object to read from or populate. + * @return Capture status reported to the streaming pipeline. + */ int dummy_img(platf::img_t *img) override { if (!img) { return -1; @@ -938,6 +1078,11 @@ namespace pipewire { return 0; } + /** + * @brief Report whether the active display mode is HDR. + * + * @return True when the active display mode is HDR. + */ bool is_hdr() override { int color_primaries = shared_state->color_primaries.load(); int transfer_function = shared_state->transfer_function.load(); @@ -949,6 +1094,12 @@ namespace pipewire { return false; } + /** + * @brief Read HDR metadata for the active display mode. + * + * @param metadata Output structure populated with HDR metadata. + * @return True when HDR metadata was written to the output structure. + */ bool get_hdr_metadata(SS_HDR_METADATA &metadata) override { int color_primaries = shared_state->color_primaries.load(); int transfer_function = shared_state->transfer_function.load(); @@ -1122,7 +1273,7 @@ namespace pipewire { protected: // Allow subclasses to access for pipewire requirements setup and stream dead checks - pipewire_t pipewire; - std::shared_ptr shared_state; + pipewire_t pipewire; ///< Pipewire. + std::shared_ptr shared_state; ///< Shared state. }; } // namespace pipewire diff --git a/src/platform/linux/portalgrab.cpp b/src/platform/linux/portalgrab.cpp index f5bd863d7..83f5afdc8 100644 --- a/src/platform/linux/portalgrab.cpp +++ b/src/platform/linux/portalgrab.cpp @@ -35,20 +35,41 @@ namespace portal { // Forward declarations class runtime_t; + /** + * @brief Persistent portal restore token used to reuse screencast permission. + */ class restore_token_t { public: + /** + * @brief Return the currently wrapped value or handle. + * + * @return Underlying native handle or object pointer. + */ static std::string get() { return *token_; } + /** + * @brief Store the new value and mark it dirty for persistence. + * + * @param value Portal restore token received from xdg-desktop-portal. + */ static void set(std::string_view value) { *token_ = value; } + /** + * @brief Return whether the persisted value is empty. + * + * @return True when no portal display id has been persisted. + */ static bool empty() { return token_->empty(); } + /** + * @brief Load persisted state from its backing store. + */ static void load() { std::ifstream file(get_file_path()); if (file.is_open()) { @@ -59,6 +80,9 @@ namespace portal { } } + /** + * @brief Save current state to its backing store. + */ static void save() { if (token_->empty()) { return; @@ -80,21 +104,32 @@ namespace portal { } }; + /** + * @brief DBus response loop and response variant for portal calls. + */ struct dbus_response_t { - GMainLoop *loop; - GVariant *response; - guint subscription_id; + GMainLoop *loop; ///< GLib main loop waiting for a portal response signal. + GVariant *response; ///< DBus response payload returned by the portal. + guint subscription_id; ///< Subscription ID. }; + /** + * @brief PipeWire stream node and negotiated capture size. + */ struct pipewire_streaminfo_t { - uint32_t pipewire_node = PW_ID_ANY; - uint64_t pipewire_object_serial = SPA_ID_INVALID; - int width = 0; - int height = 0; - int pos_x = 0; - int pos_y = 0; - std::string monitor_name; + uint32_t pipewire_node = PW_ID_ANY; ///< PipeWire node ID selected by the portal. + uint64_t pipewire_object_serial = SPA_ID_INVALID; ///< PipeWire object serial selected by the portal. + int width = 0; ///< Stream width in pixels. + int height = 0; ///< Stream height in pixels. + int pos_x = 0; ///< Output X position reported by the portal. + int pos_y = 0; ///< Output Y position reported by the portal. + std::string monitor_name; ///< Monitor name. + /** + * @brief Convert to display name. + * + * @return Value converted to display name. + */ std::string to_display_name() { if (!monitor_name.empty()) { return monitor_name; @@ -102,12 +137,21 @@ namespace portal { return std::format("position-{}x{}-resolution-{}x{}", pos_x, pos_y, width, height); } + /** + * @brief Check whether a portal stream matches a requested display name. + * + * @param display_name Display name. + * @return True when the portal display id matches the requested display name. + */ bool match_display_name(const std::string_view &display_name) { // Check the given non-empty display name matches the display name for this struct return !display_name.empty() && display_name == to_display_name(); } }; + /** + * @brief DBus connection and portal request helpers for screencast setup. + */ class dbus_t { public: dbus_t &operator=(dbus_t &&) = delete; // Do not allow to copying @@ -157,6 +201,11 @@ namespace portal { } } + /** + * @brief Open DBus and prepare portal screencast request handling. + * + * @return 0 on success; nonzero or negative platform status on failure. + */ int init() { restore_token_t::load(); @@ -176,6 +225,11 @@ namespace portal { return 0; } + /** + * @brief Connect to xdg-desktop-portal and restore or create a screencast session. + * + * @return 0 when a portal session is ready; nonzero when D-Bus or portal setup fails. + */ int connect_to_portal() { g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, FALSE); g_autofree gchar *session_path = nullptr; @@ -203,6 +257,14 @@ namespace portal { // Try to create a combined RemoteDesktop + ScreenCast session // Returns true on success, false if should fall back to ScreenCast-only + /** + * @brief Try to create a RemoteDesktop portal session. + * + * @param loop GLib main loop associated with the portal request. + * @param session_path Session path. + * @param session_token Session token. + * @return True when the portal request or state check succeeds. + */ bool try_remote_desktop_session(GMainLoop *loop, gchar **session_path, const gchar *session_token) { if (create_portal_session(loop, session_path, session_token, false) < 0) { return false; @@ -226,6 +288,13 @@ namespace portal { } // Create a ScreenCast-only session + /** + * @brief Create a screencast-only portal session without remote-desktop control. + * + * @param loop GLib main loop associated with the portal request. + * @param session_path Session path. + * @return 0 when the portal returns a session path; nonzero on request failure. + */ int try_screencast_only_session(GMainLoop *loop, gchar **session_path) { g_autofree gchar *new_session_token = nullptr; create_session_path(conn, nullptr, &new_session_token); @@ -240,6 +309,11 @@ namespace portal { return 0; } + /** + * @brief Check whether session closed. + * + * @return True when the portal session has been closed. + */ bool is_session_closed() const { if (conn && !session_handle.empty()) { // Try to retrieve property org.freedesktop.portal.Session::version @@ -267,8 +341,8 @@ namespace portal { return false; } - std::vector pipewire_streams; - int pipewire_fd; + std::vector pipewire_streams; ///< Pipewire streams. + int pipewire_fd; ///< Pipewire fd. private: GDBusConnection *conn; @@ -628,6 +702,9 @@ namespace portal { } }; + /** + * @brief Portal screencast backend that negotiates PipeWire streams over DBus. + */ class portal_t: public pipewire::pipewire_display_t { public: int configure_stream(const std::string &display_name, int &out_pipewire_fd, uint32_t &out_pipewire_node, uint64_t &out_pipewire_object_serial [[maybe_unused]]) override { @@ -680,6 +757,12 @@ namespace portal { return 0; } + /** + * @brief Check stream dead. + * + * @param out_status Out status. + * @return True when the PipeWire stream can no longer produce frames. + */ bool check_stream_dead(platf::capture_e &out_status) override { // If the pipewire stream stopped due to closed portal session stop the capture with an error if (dbus.is_session_closed()) { @@ -700,14 +783,22 @@ namespace portal { } // DBus portal connection - dbus_t dbus; + dbus_t dbus; ///< DBus connection used for portal screencast requests. // Class variable to store runtime state of maxFramerate negotiation - static inline std::atomic negotiate_maxframerate {true}; + static inline std::atomic negotiate_maxframerate {true}; ///< Whether portal negotiation should request the maximum frame rate. }; } // namespace portal namespace platf { + /** + * @brief Create a portal-based display capture backend. + * + * @param hwdevice_type Hardware device type requested for capture or encode. + * @param display_name Display name. + * @param config Configuration values to apply. + * @return Display backend backed by xdg-desktop-portal and PipeWire, or nullptr. + */ std::shared_ptr portal_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) { using enum platf::mem_type_e; if (!pipewire::pipewire_display_t::init_pipewire_and_check_hwdevice_type(hwdevice_type)) { @@ -728,6 +819,11 @@ namespace platf { return portal; } + /** + * @brief Enumerate capture targets available through xdg-desktop-portal. + * + * @return Portal display names, or an empty list when portal discovery fails. + */ std::vector portal_display_names() { std::vector display_names; auto dbus = std::make_shared(); diff --git a/src/platform/linux/publish.cpp b/src/platform/linux/publish.cpp index 525da5839..099c112cc 100644 --- a/src/platform/linux/publish.cpp +++ b/src/platform/linux/publish.cpp @@ -85,14 +85,20 @@ namespace avahi { ERR_MAX = -54 ///< TODO }; - constexpr auto IF_UNSPEC = -1; + constexpr auto IF_UNSPEC = -1; ///< Protocol or platform constant for if unspec. + /** + * @brief Enumerates supported proto options. + */ enum proto { PROTO_INET = 0, ///< IPv4 PROTO_INET6 = 1, ///< IPv6 PROTO_UNSPEC = -1 ///< Unspecified/all protocol(s) }; + /** + * @brief Enumerates supported server state options. + */ enum ServerState { SERVER_INVALID, ///< Invalid state (initial) SERVER_REGISTERING, ///< Host RRs are being registered @@ -101,6 +107,9 @@ namespace avahi { SERVER_FAILURE ///< Some fatal failure happened, the server is unable to proceed }; + /** + * @brief Enumerates supported client state options. + */ enum ClientState { CLIENT_S_REGISTERING = SERVER_REGISTERING, ///< Server state: REGISTERING CLIENT_S_RUNNING = SERVER_RUNNING, ///< Server state: RUNNING @@ -109,6 +118,9 @@ namespace avahi { CLIENT_CONNECTING = 101 ///< We're still connecting. This state is only entered when AVAHI_CLIENT_NO_FAIL has been passed to avahi_client_new() and the daemon is not yet available. }; + /** + * @brief Enumerates supported entry group state options. + */ enum EntryGroupState { ENTRY_GROUP_UNCOMMITED, ///< The group has not yet been committed, the user must still call avahi_entry_group_commit() ENTRY_GROUP_REGISTERING, ///< The entries of the group are currently being registered @@ -117,6 +129,9 @@ namespace avahi { ENTRY_GROUP_FAILURE ///< Some kind of failure happened, the entries have been withdrawn }; + /** + * @brief Enumerates supported client flags options. + */ enum ClientFlags { CLIENT_IGNORE_USER_CONFIG = 1, ///< Don't read user configuration CLIENT_NO_FAIL = 2 ///< Don't fail if the daemon is not available when avahi_client_new() is called, instead enter CLIENT_CONNECTING state and wait for the daemon to appear @@ -137,7 +152,13 @@ namespace avahi { PUBLISH_USE_MULTICAST = 256 ///< Register the record using multicast DNS }; + /** + * @brief Network interface index type used by Avahi. + */ using IfIndex = int; + /** + * @brief Avahi protocol/address-family selector type. + */ using Protocol = int; struct EntryGroup; @@ -145,18 +166,45 @@ namespace avahi { struct SimplePoll; struct Client; + /** + * @brief Avahi client state-change callback signature. + */ typedef void (*ClientCallback)(Client *, ClientState, void *userdata); + /** + * @brief Avahi entry-group state-change callback signature. + */ typedef void (*EntryGroupCallback)(EntryGroup *g, EntryGroupState state, void *userdata); + /** + * @brief Function pointer used to free Avahi-allocated memory. + */ typedef void (*free_fn)(void *); + /** + * @brief Function pointer used to create an Avahi client. + */ typedef Client *(*client_new_fn)(const Poll *poll_api, ClientFlags flags, ClientCallback callback, void *userdata, int *error); + /** + * @brief Function pointer used to destroy an Avahi client. + */ typedef void (*client_free_fn)(Client *); + /** + * @brief Function pointer used to request an alternative Avahi service name. + */ typedef char *(*alternative_service_name_fn)(char *); + /** + * @brief Function pointer used to get an Avahi client from an entry group. + */ typedef Client *(*entry_group_get_client_fn)(EntryGroup *); + /** + * @brief Function pointer used to create an Avahi entry group. + */ typedef EntryGroup *(*entry_group_new_fn)(Client *, EntryGroupCallback, void *userdata); + /** + * @brief Function pointer used to add a service to an Avahi entry group. + */ typedef int (*entry_group_add_service_fn)( EntryGroup *group, IfIndex interface, @@ -170,39 +218,77 @@ namespace avahi { ... ); + /** + * @brief Function pointer used to test whether an Avahi entry group is empty. + */ typedef int (*entry_group_is_empty_fn)(EntryGroup *); + /** + * @brief Function pointer used to reset an Avahi entry group. + */ typedef int (*entry_group_reset_fn)(EntryGroup *); + /** + * @brief Function pointer used to publish an Avahi entry group. + */ typedef int (*entry_group_commit_fn)(EntryGroup *); + /** + * @brief Function pointer used to duplicate Avahi strings. + */ typedef char *(*strdup_fn)(const char *); + /** + * @brief Function pointer used to format Avahi error codes. + */ typedef char *(*strerror_fn)(int); + /** + * @brief Function pointer used to read the last Avahi client error. + */ typedef int (*client_errno_fn)(Client *); + /** + * @brief Function pointer used to get the Avahi simple-poll API. + */ typedef Poll *(*simple_poll_get_fn)(SimplePoll *); + /** + * @brief Function pointer used to run the Avahi simple-poll loop. + */ typedef int (*simple_poll_loop_fn)(SimplePoll *); + /** + * @brief Function pointer used to stop the Avahi simple-poll loop. + */ typedef void (*simple_poll_quit_fn)(SimplePoll *); + /** + * @brief Function pointer used to allocate an Avahi simple-poll instance. + */ typedef SimplePoll *(*simple_poll_new_fn)(); + /** + * @brief Function pointer used to free an Avahi simple-poll instance. + */ typedef void (*simple_poll_free_fn)(SimplePoll *); - free_fn free; - client_new_fn client_new; - client_free_fn client_free; - alternative_service_name_fn alternative_service_name; - entry_group_get_client_fn entry_group_get_client; - entry_group_new_fn entry_group_new; - entry_group_add_service_fn entry_group_add_service; - entry_group_is_empty_fn entry_group_is_empty; - entry_group_reset_fn entry_group_reset; - entry_group_commit_fn entry_group_commit; - strdup_fn strdup; - strerror_fn strerror; - client_errno_fn client_errno; - simple_poll_get_fn simple_poll_get; - simple_poll_loop_fn simple_poll_loop; - simple_poll_quit_fn simple_poll_quit; - simple_poll_new_fn simple_poll_new; - simple_poll_free_fn simple_poll_free; + free_fn free; ///< Free. + client_new_fn client_new; ///< Client new. + client_free_fn client_free; ///< Client free. + alternative_service_name_fn alternative_service_name; ///< Alternative service name. + entry_group_get_client_fn entry_group_get_client; ///< Entry group get client. + entry_group_new_fn entry_group_new; ///< Entry group new. + entry_group_add_service_fn entry_group_add_service; ///< Entry group add service. + entry_group_is_empty_fn entry_group_is_empty; ///< Entry group is empty. + entry_group_reset_fn entry_group_reset; ///< Entry group reset. + entry_group_commit_fn entry_group_commit; ///< Entry group commit. + strdup_fn strdup; ///< Strdup. + strerror_fn strerror; ///< Strerror. + client_errno_fn client_errno; ///< Client errno. + simple_poll_get_fn simple_poll_get; ///< Simple poll get. + simple_poll_loop_fn simple_poll_loop; ///< Simple poll loop. + simple_poll_quit_fn simple_poll_quit; ///< Simple poll quit. + simple_poll_new_fn simple_poll_new; ///< Simple poll new. + simple_poll_free_fn simple_poll_free; ///< Simple poll free. + /** + * @brief Load common Avahi client and entry-group function pointers. + * + * @return 0 when common Avahi functions are available; nonzero otherwise. + */ int init_common() { static void *handle {nullptr}; static bool funcs_loaded = false; @@ -238,6 +324,11 @@ namespace avahi { return 0; } + /** + * @brief Load Avahi threaded-poll and client function pointers. + * + * @return 0 when all client functions are available; nonzero otherwise. + */ int init_client() { if (init_common()) { return -1; @@ -280,25 +371,50 @@ namespace avahi { namespace platf::publish { + /** + * @brief Release memory allocated by Avahi. + * + * @param p Avahi-allocated pointer to release. + */ template void free(T *p) { avahi::free(p); } + /** + * @brief Owning pointer for Avahi allocations released with `avahi_free`. + */ template using ptr_t = util::safe_ptr>; + /** + * @brief Owning pointer for an Avahi client. + */ using client_t = util::dyn_safe_ptr; + /** + * @brief Owning pointer for an Avahi simple poll loop. + */ using poll_t = util::dyn_safe_ptr; - avahi::EntryGroup *group = nullptr; + avahi::EntryGroup *group = nullptr; ///< Active Avahi entry group that owns the published service. - poll_t poll; - client_t client; + poll_t poll; ///< Avahi poll loop used while the service is published. + client_t client; ///< Avahi client used to register the Sunshine service. - ptr_t name; + ptr_t name; ///< Current service name, updated when Avahi reports a collision. + /** + * @brief Create or update the Avahi service entry group. + * + * @param c Avahi client used to allocate and commit the service entry group. + */ void create_services(avahi::Client *c); + /** + * @brief React to Avahi entry-group state changes. + * + * @param g Entry group that emitted the state change. + * @param state Avahi entry-group state reported by the callback. + */ void entry_group_callback(avahi::EntryGroup *g, avahi::EntryGroupState state, void *) { group = g; @@ -322,6 +438,9 @@ namespace platf::publish { } } + /** + * @brief Publish Sunshine's mDNS service through Avahi. + */ void create_services(avahi::Client *c) { int ret; @@ -380,6 +499,12 @@ namespace platf::publish { fg.disable(); } + /** + * @brief React to Avahi client state changes and register services when ready. + * + * @param c Avahi client that emitted the state change. + * @param state Avahi client state reported by the callback. + */ void client_callback(avahi::Client *c, avahi::ClientState state, void *) { switch (state) { case avahi::CLIENT_S_RUNNING: @@ -399,14 +524,25 @@ namespace platf::publish { } } + /** + * @brief RAII helper that runs shutdown cleanup when destroyed. + */ class deinit_t: public ::platf::deinit_t { public: - std::thread poll_thread; + std::thread poll_thread; ///< Poll thread. + /** + * @brief Store the Avahi polling thread for shutdown on destruction. + * + * @param poll_thread Poll thread. + */ deinit_t(std::thread poll_thread): poll_thread {std::move(poll_thread)} { } + /** + * @brief Destroy the Avahi publisher deinitializer. + */ ~deinit_t() override { if (avahi::simple_poll_quit && poll) { avahi::simple_poll_quit(poll.get()); diff --git a/src/platform/linux/vaapi.cpp b/src/platform/linux/vaapi.cpp index 63167abd3..22843fe20 100644 --- a/src/platform/linux/vaapi.cpp +++ b/src/platform/linux/vaapi.cpp @@ -14,7 +14,14 @@ extern "C" { #include #include #if !VA_CHECK_VERSION(1, 9, 0) - // vaSyncBuffer stub allows Sunshine built against libva <2.9.0 to link against ffmpeg on libva 2.9.0 or later + /** + * @brief Stub vaSyncBuffer when building against libva before 2.9.0. + * + * @param dpy VA display. + * @param buf_id VA buffer ID. + * @param timeout_ns Sync timeout in nanoseconds. + * @return VA status code. + */ VAStatus vaSyncBuffer( VADisplay dpy, @@ -25,7 +32,15 @@ extern "C" { } #endif #if !VA_CHECK_VERSION(1, 21, 0) - // vaMapBuffer2 stub allows Sunshine built against libva <2.21.0 to link against ffmpeg on libva 2.21.0 or later + /** + * @brief Stub vaMapBuffer2 when building against libva before 2.21.0. + * + * @param dpy VA display. + * @param buf_id VA buffer ID. + * @param pbuf Output mapped buffer pointer. + * @param flags Mapping flags. + * @return VA status code. + */ VAStatus vaMapBuffer2( VADisplay dpy, @@ -52,24 +67,39 @@ using namespace std::literals; extern "C" struct AVBufferRef; namespace va { - constexpr auto SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2 = 0x40000000; - constexpr auto EXPORT_SURFACE_WRITE_ONLY = 0x0002; - constexpr auto EXPORT_SURFACE_SEPARATE_LAYERS = 0x0004; + constexpr auto SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2 = 0x40000000; ///< Protocol or platform constant for surface attrib mem type drm prime 2. + constexpr auto EXPORT_SURFACE_WRITE_ONLY = 0x0002; ///< GameStream port offset for export surface write only. + constexpr auto EXPORT_SURFACE_SEPARATE_LAYERS = 0x0004; ///< GameStream port offset for export surface separate layers. + /** + * @brief Native VA display handle. + */ using VADisplay = void *; + /** + * @brief Status code returned by VAAPI functions. + */ using VAStatus = int; + /** + * @brief Generic numeric VAAPI object identifier. + */ using VAGenericID = unsigned int; + /** + * @brief VAAPI surface identifier. + */ using VASurfaceID = VAGenericID; + /** + * @brief DRM PRIME descriptor imported from a VAAPI surface. + */ struct DRMPRIMESurfaceDescriptor { // VA Pixel format fourcc of the whole surface (VA_FOURCC_*). - uint32_t fourcc; + uint32_t fourcc; ///< VA fourcc pixel format for the imported surface. - uint32_t width; - uint32_t height; + uint32_t width; ///< Surface width in pixels. + uint32_t height; ///< Surface height in pixels. // Number of distinct DRM objects making up the surface. - uint32_t num_objects; + uint32_t num_objects; ///< Num objects. struct { // DRM PRIME file descriptor for this object. @@ -80,10 +110,10 @@ namespace va { uint32_t size; // Format modifier applied to this object, not sure what that means uint64_t drm_format_modifier; - } objects[4]; + } objects[4]; ///< DRM PRIME backing objects referenced by the descriptor.. // Number of layers making up the surface. - uint32_t num_layers; + uint32_t num_layers; ///< Num layers. struct { // DRM format fourcc of this layer (DRM_FOURCC_*). @@ -100,15 +130,36 @@ namespace va { // Pitch of each plane. uint32_t pitch[4]; - } layers[4]; + } layers[4]; ///< DRM PRIME layer descriptions for the frame.. }; + /** + * @brief VA display handle released with `vaTerminate`. + */ using display_t = util::safe_ptr_v2; + /** + * @brief Create an FFmpeg VA-API hardware device context from a Sunshine encode device. + * + * @param encode_device Encode device. + * @param hw_device_buf Output FFmpeg hardware device buffer. + * @return 0 when the buffer is initialized; negative FFmpeg error code on failure. + */ int vaapi_init_avcodec_hardware_input_buffer(platf::avcodec_encode_device_t *encode_device, AVBufferRef **hw_device_buf); + /** + * @brief VAAPI encode device that imports captured frames into VA surfaces. + */ class va_t: public platf::avcodec_encode_device_t { public: + /** + * @brief Initialize VAAPI display, EGL, and conversion resources. + * + * @param in_width In width. + * @param in_height In height. + * @param render_device Render device. + * @return 0 on success; nonzero or negative platform status on failure. + */ int init(int in_width, int in_height, file_t &&render_device) { file = std::move(render_device); @@ -229,6 +280,12 @@ namespace va { return VAProfileNone; } + /** + * @brief Initialize codec options. + * + * @param ctx Native context object used by the operation or callback. + * @param options Request options or socket options to apply. + */ void init_codec_options(AVCodecContext *ctx, AVDictionary **options) override { auto va_profile = get_va_profile(ctx); if (va_profile == VAProfileNone || !is_va_profile_supported(va_profile)) { @@ -303,6 +360,13 @@ namespace va { } } + /** + * @brief Attach frame resources used by the next conversion or encode operation. + * + * @param frame Video or graphics frame being processed. + * @param hw_frames_ctx_buf Hardware frames context buffer. + * @return Status from updating frame. + */ int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx_buf) override { this->hwframe.reset(frame); this->frame = frame; @@ -380,30 +444,42 @@ namespace va { return 0; } + /** + * @brief Apply the configured colorspace metadata to the active frame. + */ void apply_colorspace() override { sws.apply_colorspace(colorspace, false); } - va::display_t::pointer va_display; - file_t file; + va::display_t::pointer va_display; ///< VA display used to allocate and destroy surfaces. + file_t file; ///< DRM render-node file descriptor used by VAAPI and EGL. - gbm::gbm_t gbm; - egl::display_t display; - egl::ctx_t ctx; + gbm::gbm_t gbm; ///< GBM device used for buffer allocation. + egl::display_t display; ///< EGL display created from the DRM render node. + egl::ctx_t ctx; ///< EGL context used for VA-API frame conversion. // This must be destroyed before display_t to ensure the GPU // driver is still loaded when vaDestroySurfaces() is called. - frame_t hwframe; + frame_t hwframe; ///< FFmpeg hardware frame backed by a VAAPI surface. - egl::sws_t sws; - egl::nv12_t nv12; + egl::sws_t sws; ///< EGL/OpenGL conversion pipeline for VA-API frames. + egl::nv12_t nv12; ///< EGL/OpenGL resources used for NV12 output frames. - int width; - int height; + int width; ///< Frame or display width in pixels. + int height; ///< Frame or display height in pixels. }; + /** + * @brief VAAPI encode path that copies converted frames through system memory. + */ class va_ram_t: public va_t { public: + /** + * @brief Convert a captured VAAPI frame into system-memory encoder input. + * + * @param img Image or frame object to read from or populate. + * @return Conversion status. + */ int convert(platf::img_t &img) override { sws.load_ram(img); @@ -412,8 +488,17 @@ namespace va { } }; + /** + * @brief VAAPI encode path that keeps converted frames in GPU memory. + */ class va_vram_t: public va_t { public: + /** + * @brief Convert a captured VAAPI frame into GPU encoder input. + * + * @param img Image or frame object to read from or populate. + * @return Conversion status. + */ int convert(platf::img_t &img) override { auto &descriptor = (egl::img_descriptor_t &) img; @@ -440,6 +525,16 @@ namespace va { return 0; } + /** + * @brief Initialize VAAPI GPU-frame conversion for the selected display. + * + * @param in_width In width. + * @param in_height In height. + * @param render_device Render device. + * @param offset_x Offset x. + * @param offset_y Offset y. + * @return 0 on success; nonzero or negative platform status on failure. + */ int init(int in_width, int in_height, file_t &&render_device, int offset_x, int offset_y) { if (va_t::init(in_width, in_height, std::move(render_device))) { return -1; @@ -453,11 +548,11 @@ namespace va { return 0; } - std::uint64_t sequence; - egl::rgb_t rgb; + std::uint64_t sequence; ///< Monotonic sequence used to recreate imported EGL resources. + egl::rgb_t rgb; ///< Imported RGB image used before VAAPI conversion. - int offset_x; - int offset_y; + int offset_x; ///< Horizontal offset in physical pixels. + int offset_y; ///< Vertical offset in physical pixels. }; /** @@ -470,9 +565,9 @@ namespace va { union { void *xdisplay; int fd; - } drm; + } drm; ///< Native display or DRM fd passed to FFmpeg's VA-API context. - int drm_fd; + int drm_fd; ///< DRM fd. } VAAPIDevicePriv; /** @@ -508,6 +603,9 @@ namespace va { av_freep(&priv); } + /** + * @brief Initialize FFmpeg's VA-API hardware device buffer. + */ int vaapi_init_avcodec_hardware_input_buffer(platf::avcodec_encode_device_t *base, AVBufferRef **hw_device_buf) { auto va = (va::va_t *) base; auto fd = dup(va->file.el); @@ -583,6 +681,9 @@ namespace va { return false; } + /** + * @brief Validate that the configured VAAPI device can be used. + */ bool validate(int fd) { va::display_t display {vaGetDisplayDRM(fd)}; if (!display) { @@ -619,6 +720,9 @@ namespace va { return true; } + /** + * @brief Create AVCodec encode device. + */ std::unique_ptr make_avcodec_encode_device(int width, int height, file_t &&card, int offset_x, int offset_y, bool vram) { if (vram) { auto egl = std::make_unique(); @@ -639,6 +743,9 @@ namespace va { } } + /** + * @brief Create AVCodec encode device. + */ std::unique_ptr make_avcodec_encode_device(int width, int height, int offset_x, int offset_y, bool vram) { auto render_device = platf::resolve_render_device(); diff --git a/src/platform/linux/vaapi.h b/src/platform/linux/vaapi.h index 490df1bcc..c56cf2868 100644 --- a/src/platform/linux/vaapi.h +++ b/src/platform/linux/vaapi.h @@ -19,11 +19,43 @@ namespace va { * offset_x --> Horizontal offset of the image in the texture * offset_y --> Vertical offset of the image in the texture * file_t card --> The file descriptor of the render device used for encoding + * + * @param width Frame or display width in pixels. + * @param height Frame or display height in pixels. + * @param vram Whether the image should use GPU memory instead of system memory. + * @return Constructed AVCodec encode device object. */ std::unique_ptr make_avcodec_encode_device(int width, int height, bool vram); + /** + * @brief Create AVCodec encode device. + * + * @param width Frame or display width in pixels. + * @param height Frame or display height in pixels. + * @param offset_x Offset x. + * @param offset_y Offset y. + * @param vram Whether the image should use GPU memory instead of system memory. + * @return Constructed AVCodec encode device object. + */ std::unique_ptr make_avcodec_encode_device(int width, int height, int offset_x, int offset_y, bool vram); + /** + * @brief Create AVCodec encode device. + * + * @param width Frame or display width in pixels. + * @param height Frame or display height in pixels. + * @param card Video device path or render node used for VAAPI. + * @param offset_x Offset x. + * @param offset_y Offset y. + * @param vram Whether the image should use GPU memory instead of system memory. + * @return Constructed AVCodec encode device object. + */ std::unique_ptr make_avcodec_encode_device(int width, int height, file_t &&card, int offset_x, int offset_y, bool vram); // Ensure the render device pointed to by fd is capable of encoding h264 with the hevc_mode configured + /** + * @brief Validate that the configured VAAPI device can be used. + * + * @param fd Native file descriptor to wrap or inspect. + * @return True when the VAAPI device is usable for capture or encode. + */ bool validate(int fd); } // namespace va diff --git a/src/platform/linux/vulkan_encode.cpp b/src/platform/linux/vulkan_encode.cpp index 197d6b9c4..fef11ad48 100644 --- a/src/platform/linux/vulkan_encode.cpp +++ b/src/platform/linux/vulkan_encode.cpp @@ -106,23 +106,30 @@ namespace vk { return -1; } + /** + * @brief Vulkan shader constants used by the conversion pass. + */ struct PushConstants { - std::array color_vec_y; - std::array color_vec_u; - std::array color_vec_v; - std::array range_y; - std::array range_uv; - std::array src_offset; - std::array src_size; - std::array dst_offset; - std::array dst_size; - std::array dst_full_size; - std::array cursor_pos; - std::array cursor_size; - int32_t y_invert; + std::array color_vec_y; ///< Color vec y. + std::array color_vec_u; ///< Color vec u. + std::array color_vec_v; ///< Color vec v. + std::array range_y; ///< Range y. + std::array range_uv; ///< Range uv. + std::array src_offset; ///< Src offset. + std::array src_size; ///< Src size. + std::array dst_offset; ///< Dst offset. + std::array dst_size; ///< Dst size. + std::array dst_full_size; ///< Dst full size. + std::array cursor_pos; ///< Cursor pos. + std::array cursor_size; ///< Cursor size. + int32_t y_invert; ///< Y invert. }; // Helper to check VkResult +/** + * @def VK_CHECK(expr) + * @brief Macro for VK CHECK. + */ #define VK_CHECK(expr) \ do { \ VkResult _r = (expr); \ @@ -131,6 +138,10 @@ namespace vk { return -1; \ } \ } while (0) +/** + * @def VK_CHECK_BOOL(expr) + * @brief Macro for VK CHECK BOOL. + */ #define VK_CHECK_BOOL(expr) \ do { \ VkResult _r = (expr); \ @@ -140,12 +151,24 @@ namespace vk { } \ } while (0) + /** + * @brief Vulkan encode device that keeps converted frames in GPU memory. + */ class vk_vram_t: public platf::avcodec_encode_device_t { public: ~vk_vram_t() override { cleanup_pipeline(); } + /** + * @brief Initialize Vulkan encode device and conversion resources. + * + * @param in_width In width. + * @param in_height In height. + * @param in_offset_x In offset x. + * @param in_offset_y In offset y. + * @return 0 on success; nonzero or negative platform status on failure. + */ int init(int in_width, int in_height, int in_offset_x = 0, int in_offset_y = 0) { width = in_width; height = in_height; @@ -155,6 +178,12 @@ namespace vk { return 0; } + /** + * @brief Initialize codec options. + * + * @param ctx Native context object used by the operation or callback. + * @param options Request options or socket options to apply. + */ void init_codec_options(AVCodecContext *ctx, AVDictionary **options) override { // When VBR mode is selected (rc_mode=4), don't pin rc_min_rate to the target bitrate. // Having rc_min_rate == rc_max_rate == bit_rate in VBR mode prevents the encoder from @@ -165,6 +194,13 @@ namespace vk { } } + /** + * @brief Attach frame resources used by the next conversion or encode operation. + * + * @param new_frame Frame to attach. + * @param hw_frames_ctx_buf Hardware frames context buffer. + * @return Status from updating frame. + */ int set_frame(AVFrame *new_frame, AVBufferRef *hw_frames_ctx_buf) override { this->hwframe.reset(new_frame); this->frame = new_frame; @@ -212,6 +248,9 @@ namespace vk { return 0; } + /** + * @brief Apply the configured colorspace metadata to the active frame. + */ void apply_colorspace() override { auto *colors = video::color_vectors_from_colorspace(colorspace, true); if (colors) { @@ -223,6 +262,11 @@ namespace vk { } } + /** + * @brief Configure FFmpeg Vulkan hardware frames for video encode input. + * + * @param frames FFmpeg hardware frames context to initialize. + */ void init_hwframes(AVHWFramesContext *frames) override { frames->initial_pool_size = 4; auto *vk_frames = (AVVulkanFramesContext *) frames->hwctx; @@ -233,6 +277,12 @@ namespace vk { VK_IMAGE_USAGE_VIDEO_ENCODE_SRC_BIT_KHR); } + /** + * @brief Convert a captured frame into a Vulkan hardware frame. + * + * @param img Image or frame object to read from or populate. + * @return Conversion status. + */ int convert(platf::img_t &img) override { auto &descriptor = (egl::img_descriptor_t &) img; diff --git a/src/platform/linux/vulkan_encode.h b/src/platform/linux/vulkan_encode.h index db887f504..eb3dce33c 100644 --- a/src/platform/linux/vulkan_encode.h +++ b/src/platform/linux/vulkan_encode.h @@ -20,16 +20,28 @@ namespace vk { /** * @brief Create a Vulkan encode device for RAM capture. + * + * @param width Frame or display width in pixels. + * @param height Frame or display height in pixels. + * @return Constructed AVCodec encode device ram object. */ std::unique_ptr make_avcodec_encode_device_ram(int width, int height); /** * @brief Create a Vulkan encode device for VRAM capture. + * + * @param width Frame or display width in pixels. + * @param height Frame or display height in pixels. + * @param offset_x Offset x. + * @param offset_y Offset y. + * @return Constructed AVCodec encode device VRAM object. */ std::unique_ptr make_avcodec_encode_device_vram(int width, int height, int offset_x, int offset_y); /** * @brief Check if FFmpeg Vulkan Video encoding is available. + * + * @return True when FFmpeg Vulkan Video encoding is available. */ bool validate(); diff --git a/src/platform/linux/wayland.cpp b/src/platform/linux/wayland.cpp index afb8e098a..cdef9ae78 100644 --- a/src/platform/linux/wayland.cpp +++ b/src/platform/linux/wayland.cpp @@ -23,7 +23,7 @@ #include "src/utility.h" #include "wayland.h" -extern const wl_interface wl_output_interface; +extern const wl_interface wl_output_interface; ///< Wayland output interface. using namespace std::literals; @@ -40,6 +40,10 @@ namespace wl { return ((*reinterpret_cast(data)).*m)(params...); } +/** + * @def CLASS_CALL(c, m) + * @brief Macro for CLASS CALL. + */ #define CLASS_CALL(c, m) classCall // Define buffer params listener @@ -546,6 +550,9 @@ namespace wl { std::fill_n(sd.fds, 4, -1); }; + /** + * @brief Refresh the monitor list reported by the display server. + */ std::vector> monitors(const char *display_name) { display_t display; @@ -578,6 +585,9 @@ namespace wl { return display.init() == 0; } + /** + * @brief Initialize Wayland registry interfaces required for capture. + */ int init() { static bool validated = validate(); diff --git a/src/platform/linux/wayland.h b/src/platform/linux/wayland.h index 11b16b76d..137040021 100644 --- a/src/platform/linux/wayland.h +++ b/src/platform/linux/wayland.h @@ -26,19 +26,34 @@ #ifdef SUNSHINE_BUILD_WAYLAND namespace wl { + /** + * @brief Owning pointer for a Wayland display connection. + */ using display_internal_t = util::safe_ptr; + /** + * @brief Captured Wayland frame metadata and DMA-BUF surface state. + */ class frame_t { public: frame_t(); + /** + * @brief Release file descriptors and native buffers associated with this frame. + */ void destroy(); - egl::surface_descriptor_t sd; - std::optional frame_timestamp; + egl::surface_descriptor_t sd; ///< DMA-BUF surface descriptor received from the compositor. + std::optional frame_timestamp; ///< Capture timestamp associated with the frame. }; + /** + * @brief Listener state for Wayland screencopy frames backed by DMA-BUFs. + */ class dmabuf_t { public: + /** + * @brief Capture state for the active screencopy request. + */ enum status_e { WAITING, ///< Waiting for a frame READY, ///< Frame is ready @@ -53,25 +68,102 @@ namespace wl { dmabuf_t &operator=(const dmabuf_t &) = delete; dmabuf_t &operator=(dmabuf_t &&) = delete; + /** + * @brief Start a screencopy request and attach the DMA-BUF listener callbacks. + * + * @param screencopy_manager Compositor screencopy manager used to request frames. + * @param dmabuf_interface Compositor DMA-BUF interface used to allocate buffers. + * @param supported_modifiers DMA-BUF format modifiers supported by the compositor. + * @param output Wayland output to capture. + * @param blend_cursor Whether the compositor should include the cursor in the frame. + */ void listen(zwlr_screencopy_manager_v1 *screencopy_manager, zwp_linux_dmabuf_v1 *dmabuf_interface, const std::map> *supported_modifiers, wl_output *output, bool blend_cursor = false); + /** + * @brief Store the Wayland buffer created for a DMA-BUF parameter request. + * + * @param data Pointer to the `dmabuf_t` instance associated with the callback. + * @param params DMA-BUF parameter object that completed buffer creation. + * @param wl_buffer Wayland buffer created from the DMA-BUF planes. + */ static void buffer_params_created(void *data, struct zwp_linux_buffer_params_v1 *params, struct wl_buffer *wl_buffer); + /** + * @brief Mark DMA-BUF buffer creation as failed. + * + * @param data Pointer to the `dmabuf_t` instance associated with the callback. + * @param params DMA-BUF parameter object that failed buffer creation. + */ static void buffer_params_failed(void *data, struct zwp_linux_buffer_params_v1 *params); + /** + * @brief Record DMA-BUF buffer parameters from the compositor. + * + * @param frame Screencopy frame that advertised shared-memory fallback data. + * @param format DRM pixel format for the shared-memory buffer. + * @param width Frame width in pixels. + * @param height Frame height in pixels. + * @param stride Number of bytes per row in the buffer. + */ void buffer(zwlr_screencopy_frame_v1 *frame, std::uint32_t format, std::uint32_t width, std::uint32_t height, std::uint32_t stride); + /** + * @brief Record Linux DMA-BUF parameters advertised for the frame. + * + * @param frame Screencopy frame that advertised DMA-BUF data. + * @param format DRM pixel format for the DMA-BUF. + * @param width Frame width in pixels. + * @param height Frame height in pixels. + */ void linux_dmabuf(zwlr_screencopy_frame_v1 *frame, std::uint32_t format, std::uint32_t width, std::uint32_t height); + /** + * @brief Allocate or import the buffer once the compositor finished describing it. + * + * @param frame Screencopy frame whose buffer description is complete. + */ void buffer_done(zwlr_screencopy_frame_v1 *frame); + /** + * @brief Record frame flags reported by the compositor. + * + * @param frame Screencopy frame that reported flags. + * @param flags Wayland screencopy flags such as vertical inversion. + */ void flags(zwlr_screencopy_frame_v1 *frame, std::uint32_t flags); + /** + * @brief Record damaged frame bounds reported by the compositor. + * + * @param frame Screencopy frame that reported damage. + * @param x Left edge of the damaged rectangle. + * @param y Top edge of the damaged rectangle. + * @param width Damaged rectangle width in pixels. + * @param height Damaged rectangle height in pixels. + */ void damage(zwlr_screencopy_frame_v1 *frame, std::uint32_t x, std::uint32_t y, std::uint32_t width, std::uint32_t height); + /** + * @brief Mark the current screencopy frame as ready and store its timestamp. + * + * @param frame Screencopy frame that completed. + * @param tv_sec_hi High 32 bits of the compositor-provided seconds value. + * @param tv_sec_lo Low 32 bits of the compositor-provided seconds value. + * @param tv_nsec Nanosecond component of the compositor-provided timestamp. + */ void ready(zwlr_screencopy_frame_v1 *frame, std::uint32_t tv_sec_hi, std::uint32_t tv_sec_lo, std::uint32_t tv_nsec); + /** + * @brief Mark the frame capture as failed. + * + * @param frame Screencopy frame that failed. + */ void failed(zwlr_screencopy_frame_v1 *frame); + /** + * @brief Select the inactive frame slot for the next screencopy request. + * + * @return Inactive frame buffer that can receive the next capture. + */ frame_t *get_next_frame() { return current_frame == &frames[0] ? &frames[1] : &frames[0]; } - status_e status; - std::array frames; - frame_t *current_frame; - zwlr_screencopy_frame_v1_listener listener; + status_e status; ///< Current state of the active screencopy request. + std::array frames; ///< Double-buffered frame descriptors. + frame_t *current_frame; ///< Frame descriptor currently being filled by the compositor. + zwlr_screencopy_frame_v1_listener listener; ///< Callback table registered on screencopy frames. private: bool init_gbm(); @@ -102,8 +194,16 @@ namespace wl { bool y_invert {false}; }; + /** + * @brief Wayland output metadata used to match a configured display name. + */ class monitor_t { public: + /** + * @brief Track a Wayland output and its advertised modes. + * + * @param output Wayland output object being described. + */ explicit monitor_t(wl_output *output); monitor_t(monitor_t &&) = delete; @@ -111,30 +211,96 @@ namespace wl { monitor_t &operator=(const monitor_t &) = delete; monitor_t &operator=(monitor_t &&) = delete; + /** + * @brief Attach xdg-output and wl-output listeners for this monitor. + * + * @param output_manager xdg-output manager used to query logical monitor metadata. + */ void listen(zxdg_output_manager_v1 *output_manager); + /** + * @brief Store the xdg-output logical monitor name. + * + * @param name Human-readable name to assign. + */ void xdg_name(zxdg_output_v1 *, const char *name); + /** + * @brief Store the xdg-output human-readable monitor description. + * + * @param description Human-readable description used in log output. + */ void xdg_description(zxdg_output_v1 *, const char *description); + /** + * @brief Store the logical monitor position reported by xdg-output. + * + * @param x Horizontal coordinate reported by Wayland. + * @param y Vertical coordinate reported by Wayland. + */ void xdg_position(zxdg_output_v1 *, std::int32_t x, std::int32_t y); + /** + * @brief Store the logical monitor size reported by xdg-output. + * + * @param width Frame or display width in pixels. + * @param height Frame or display height in pixels. + */ void xdg_size(zxdg_output_v1 *, std::int32_t width, std::int32_t height); + /** + * @brief Acknowledge completion of an xdg-output metadata update. + */ void xdg_done(zxdg_output_v1 *) {} + /** + * @brief Receive physical wl-output geometry metadata. + * + * @param wl_output Wayland output. + * @param x Horizontal coordinate reported by Wayland. + * @param y Vertical coordinate reported by Wayland. + * @param physical_width Physical width in millimeters. + * @param physical_height Physical height in millimeters. + * @param subpixel Wayland subpixel layout enum. + * @param make Monitor manufacturer string reported by Wayland. + * @param model Monitor model string reported by Wayland. + * @param transform Wayland output transform value. + */ void wl_geometry(wl_output *wl_output, std::int32_t x, std::int32_t y, std::int32_t physical_width, std::int32_t physical_height, std::int32_t subpixel, const char *make, const char *model, std::int32_t transform) {} + /** + * @brief Store the active pixel mode reported by wl-output. + * + * @param wl_output Wayland output. + * @param flags Bit flags that modify the requested operation. + * @param width Frame or display width in pixels. + * @param height Frame or display height in pixels. + * @param refresh Output refresh rate in mHz. + */ void wl_mode(wl_output *wl_output, std::uint32_t flags, std::int32_t width, std::int32_t height, std::int32_t refresh); + /** + * @brief Acknowledge completion of a wl-output metadata update. + * + * @param wl_output Wayland output. + */ void wl_done(wl_output *wl_output) {} + /** + * @brief Receive the wl-output scale factor. + * + * @param wl_output Wayland output. + * @param factor Wayland output scale factor. + */ void wl_scale(wl_output *wl_output, std::int32_t factor) {} - wl_output *output; - std::string name; - std::string description; - platf::touch_port_t viewport; - wl_output_listener wl_listener; - zxdg_output_v1_listener xdg_listener; + wl_output *output; ///< Wayland output associated with this monitor. + std::string name; ///< xdg-output name used for display selection. + std::string description; ///< xdg-output description used for logs and UI. + platf::touch_port_t viewport; ///< Logical monitor bounds used to scale absolute input. + wl_output_listener wl_listener; ///< Callback table for wl-output events. + zxdg_output_v1_listener xdg_listener; ///< Callback table for xdg-output events. }; + /** + * @brief Wayland registry state for screencopy, DMA-BUF, and output globals. + */ class interface_t { struct bind_t { std::uint32_t id; @@ -142,6 +308,9 @@ namespace wl { }; public: + /** + * @brief Wayland globals required by the WLR capture backend. + */ enum interface_e { XDG_OUTPUT, ///< xdg-output WLR_EXPORT_DMABUF, ///< screencopy manager @@ -156,20 +325,45 @@ namespace wl { interface_t &operator=(const interface_t &) = delete; interface_t &operator=(interface_t &&) = delete; + /** + * @brief Discover compositor globals from the Wayland registry. + * + * @param registry Wayland registry announcing globals. + */ void listen(wl_registry *registry); + /** + * @brief Test whether a required Wayland global was advertised. + * + * @param bit Interface bit to test. + * @return True when the requested Wayland interface bit is set. + */ bool operator[](interface_e bit) const { return interface[bit]; } + /** + * @brief Record a DMA-BUF format advertised by the compositor. + * + * @param zwp_linux_dmabuf DMA-BUF interface that emitted the format event. + * @param format DRM format supported by the compositor. + */ void dmabuf_format(zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, uint32_t format); + /** + * @brief Record a DMA-BUF format modifier advertised by the compositor. + * + * @param zwp_linux_dmabuf DMA-BUF interface that emitted the modifier event. + * @param format DRM format associated with the modifier. + * @param modifier_hi High 32 bits of the DRM format modifier. + * @param modifier_lo Low 32 bits of the DRM format modifier. + */ void dmabuf_modifier(zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo); - std::vector> monitors; - std::map> supported_modifiers; - zwlr_screencopy_manager_v1 *screencopy_manager {nullptr}; - zwp_linux_dmabuf_v1 *dmabuf_interface {nullptr}; - zxdg_output_manager_v1 *output_manager {nullptr}; + std::vector> monitors; ///< Outputs discovered from the Wayland registry. + std::map> supported_modifiers; ///< DRM format modifiers grouped by format. + zwlr_screencopy_manager_v1 *screencopy_manager {nullptr}; ///< WLR screencopy global used to request frames. + zwp_linux_dmabuf_v1 *dmabuf_interface {nullptr}; ///< Linux DMA-BUF global used to allocate frame buffers. + zxdg_output_manager_v1 *output_manager {nullptr}; ///< xdg-output global used to query monitor names and sizes. private: void add_interface(wl_registry *registry, std::uint32_t id, const char *interface, std::uint32_t version); @@ -180,26 +374,40 @@ namespace wl { zwp_linux_dmabuf_v1_listener dmabuf_listener; }; + /** + * @brief Wayland display connection used to dispatch capture events. + */ class display_t { public: /** - * @brief Initialize display. - * If display_name == nullptr -> display_name = std::getenv("WAYLAND_DISPLAY") - * @param display_name The name of the display. - * @return 0 on success, -1 on failure. + * @brief Connect to the requested Wayland display. + * + * @param display_name Wayland display name, or nullptr to use WAYLAND_DISPLAY. + * @return 0 when the connection is opened and initialized; -1 on failure. */ int init(const char *display_name = nullptr); // Roundtrip with Wayland connection + /** + * @brief Flush pending Wayland requests and wait for replies. + */ void roundtrip(); // Wait up to the timeout to read and dispatch new events bool dispatch(std::chrono::milliseconds timeout); - // Get the registry associated with the display - // No need to manually free the registry + /** + * @brief Return the Wayland registry for global discovery. + * + * @return Wayland registry used to discover compositor globals. + */ wl_registry *registry(); + /** + * @brief Return the native Wayland display pointer. + * + * @return Native display connection owned by this wrapper. + */ inline display_internal_t::pointer get() { return display_internal.get(); } @@ -208,7 +416,18 @@ namespace wl { display_internal_t display_internal; }; + /** + * @brief Refresh the monitor list reported by the display server. + * + * @param display_name Display name. + * @return Monitors reported by the Wayland compositor, optionally filtered by name. + */ std::vector> monitors(const char *display_name = nullptr); + /** + * @brief Initialize Wayland registry interfaces required for capture. + * + * @return 0 on success; nonzero or negative platform status on failure. + */ int init(); } // namespace wl #else diff --git a/src/platform/linux/wlgrab.cpp b/src/platform/linux/wlgrab.cpp index f99e81c65..ba53d1629 100644 --- a/src/platform/linux/wlgrab.cpp +++ b/src/platform/linux/wlgrab.cpp @@ -19,15 +19,32 @@ namespace wl { static int env_width; static int env_height; + /** + * @brief Captured frame buffer shared between capture and encode stages. + */ struct img_t: public platf::img_t { + /** + * @brief Destroy the Wayland capture image. + */ ~img_t() override { delete[] data; data = nullptr; } }; + /** + * @brief Wayland screencopy capture backend shared by RAM and VRAM paths. + */ class wlr_t: public platf::display_t { public: + /** + * @brief Initialize Wayland screencopy capture for the selected output. + * + * @param hwdevice_type Hardware device type requested for capture or encode. + * @param display_name Display name. + * @param config Configuration values to apply. + * @return 0 on success; nonzero or negative platform status on failure. + */ int init(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) { // calculate frame interval we should capture at if (config.framerateX100 > 0) { @@ -124,10 +141,25 @@ namespace wl { return 0; } + /** + * @brief Populate a fallback image when real capture data is unavailable. + * + * @param img Image or frame object to read from or populate. + * @return Capture status reported to the streaming pipeline. + */ int dummy_img(platf::img_t *img) override { return 0; } + /** + * @brief Capture a display frame into the provided image object. + * + * @param pull_free_image_cb Callback that provides an available image buffer. + * @param img_out Captured wlroots image returned to the streaming pipeline. + * @param timeout Maximum time to wait for the operation. + * @param cursor Cursor image or visibility state to composite. + * @return Capture status reported to the streaming pipeline. + */ inline platf::capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor) { auto to = std::chrono::steady_clock::now() + timeout; @@ -153,17 +185,20 @@ namespace wl { return platf::capture_e::ok; } - platf::mem_type_e mem_type; + platf::mem_type_e mem_type; ///< Mem type. - std::chrono::nanoseconds delay; + std::chrono::nanoseconds delay; ///< Delay before the timer task becomes eligible to run. - wl::display_t display; - interface_t interface; - dmabuf_t dmabuf; + wl::display_t display; ///< Wayland display connection used for capture. + interface_t interface; ///< Wayland registry interfaces required by screencopy. + dmabuf_t dmabuf; ///< DMA-BUF feedback and format state advertised by the compositor. - wl_output *output; + wl_output *output; ///< Wayland output selected for capture. }; + /** + * @brief Wayland screencopy backend that copies frames into system memory. + */ class wlr_ram_t: public wlr_t { public: platf::capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override { @@ -211,6 +246,15 @@ namespace wl { return platf::capture_e::ok; } + /** + * @brief Capture a display frame into the provided image object. + * + * @param pull_free_image_cb Callback that provides an available image buffer. + * @param img_out Captured wlroots image returned to the streaming pipeline. + * @param timeout Maximum time to wait for the operation. + * @param cursor Cursor image or visibility state to composite. + * @return Capture status reported to the streaming pipeline. + */ platf::capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor) { auto status = wlr_t::snapshot(pull_free_image_cb, img_out, timeout, cursor); if (status != platf::capture_e::ok) { @@ -246,6 +290,14 @@ namespace wl { return platf::capture_e::ok; } + /** + * @brief Initialize Wayland capture that copies frames into system memory. + * + * @param hwdevice_type Hardware device type requested for capture or encode. + * @param display_name Display name. + * @param config Configuration values to apply. + * @return 0 on success; nonzero or negative platform status on failure. + */ int init(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) { if (wlr_t::init(hwdevice_type, display_name, config)) { return -1; @@ -266,6 +318,12 @@ namespace wl { return 0; } + /** + * @brief Create AVCodec encode device. + * + * @param pix_fmt Sunshine pixel format to convert or allocate for. + * @return Constructed AVCodec encode device object. + */ std::unique_ptr make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) override { #ifdef SUNSHINE_BUILD_VAAPI if (mem_type == platf::mem_type_e::vaapi) { @@ -282,6 +340,11 @@ namespace wl { return std::make_unique(); } + /** + * @brief Allocate an image buffer compatible with this display backend. + * + * @return Allocated img object, or null when unavailable. + */ std::shared_ptr alloc_img() override { auto img = std::make_shared(); img->width = width; @@ -293,10 +356,13 @@ namespace wl { return img; } - egl::display_t egl_display; - egl::ctx_t ctx; + egl::display_t egl_display; ///< EGL display. + egl::ctx_t ctx; ///< EGL context used for wlroots capture conversion. }; + /** + * @brief Wayland screencopy backend that exports frames as GPU resources. + */ class wlr_vram_t: public wlr_t { public: platf::capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override { @@ -344,6 +410,15 @@ namespace wl { return platf::capture_e::ok; } + /** + * @brief Capture a display frame into the provided image object. + * + * @param pull_free_image_cb Callback that provides an available image buffer. + * @param img_out Captured wlroots image returned to the streaming pipeline. + * @param timeout Maximum time to wait for the operation. + * @param cursor Cursor image or visibility state to composite. + * @return Capture status reported to the streaming pipeline. + */ platf::capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor) { auto status = wlr_t::snapshot(pull_free_image_cb, img_out, timeout, cursor); if (status != platf::capture_e::ok) { @@ -370,6 +445,11 @@ namespace wl { return platf::capture_e::ok; } + /** + * @brief Allocate an image buffer compatible with this display backend. + * + * @return Allocated img object, or null when unavailable. + */ std::shared_ptr alloc_img() override { auto img = std::make_shared(); @@ -385,6 +465,12 @@ namespace wl { return img; } + /** + * @brief Create AVCodec encode device. + * + * @param pix_fmt Sunshine pixel format to convert or allocate for. + * @return Constructed AVCodec encode device object. + */ std::unique_ptr make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) override { #ifdef SUNSHINE_BUILD_VAAPI if (mem_type == platf::mem_type_e::vaapi) { @@ -401,17 +487,26 @@ namespace wl { return std::make_unique(); } + /** + * @brief Populate a fallback image when real capture data is unavailable. + * + * @param img Image or frame object to read from or populate. + * @return Capture status reported to the streaming pipeline. + */ int dummy_img(platf::img_t *img) override { // Empty images are recognized as dummies by the zero sequence number return 0; } - std::uint64_t sequence {}; + std::uint64_t sequence {}; ///< Monotonic capture sequence assigned to Wayland frames. }; } // namespace wl namespace platf { + /** + * @brief Create a Wayland capture backend for the requested memory type. + */ std::shared_ptr wl_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) { if (hwdevice_type != platf::mem_type_e::system && hwdevice_type != platf::mem_type_e::vaapi && hwdevice_type != platf::mem_type_e::cuda) { BOOST_LOG(error) << "[wlgrab] Could not initialize display with the given hw device type."sv; @@ -435,6 +530,9 @@ namespace platf { return wlr; } + /** + * @brief Enumerate capture display names reported by the Wayland compositor. + */ std::vector wl_display_names() { std::vector display_names; diff --git a/src/platform/linux/x11grab.cpp b/src/platform/linux/x11grab.cpp index 6eef5a81a..18eb4cf75 100644 --- a/src/platform/linux/x11grab.cpp +++ b/src/platform/linux/x11grab.cpp @@ -33,12 +33,32 @@ using namespace std::literals; namespace platf { + /** + * @brief Load XCB entry points used by the X11 capture backend. + * + * @return 0 when required XCB symbols are loaded; nonzero otherwise. + */ int load_xcb(); + /** + * @brief Load X11 entry points used by the X11 capture backend. + * + * @return 0 when required X11 symbols are loaded; nonzero otherwise. + */ int load_x11(); namespace x11 { +/** + * @def _FN(x, ret, args) + * @brief Macro for FN. + */ #define _FN(x, ret, args) \ + /** \ + * @brief Function pointer type for the dynamically loaded X11 entry point. \ + */ \ typedef ret(*x##_fn) args; \ + /** \ + * @brief Loaded X11 entry point pointer. \ + */ \ static x##_fn x _FN(GetImage, XImage *, (Display * display, Drawable d, int x, int y, unsigned int width, unsigned int height, unsigned long plane_mask, int format)); @@ -174,6 +194,11 @@ namespace platf { _FN(setup_roots_iterator, xcb_screen_iterator_t, (const xcb_setup_t *R)); _FN(generate_id, std::uint32_t, (xcb_connection_t * c)); + /** + * @brief Initialize shared-memory support for X11 capture. + * + * @return 0 when XCB shared-memory functions are loaded; nonzero otherwise. + */ int init_shm() { static void *handle {nullptr}; static bool funcs_loaded = false; @@ -204,6 +229,11 @@ namespace platf { return 0; } + /** + * @brief Initialize XFixes cursor tracking for an X11 display. + * + * @return 0 on success; nonzero or negative platform status on failure. + */ int init() { static void *handle {nullptr}; static bool funcs_loaded = false; @@ -240,29 +270,73 @@ namespace platf { #undef _FN } // namespace xcb + /** + * @brief Release image resources. + * + * @param p Pointer passed to the deleter or conversion helper. + */ void freeImage(XImage *); + /** + * @brief Release x resources. + * + * @param p Pointer passed to the deleter or conversion helper. + */ void freeX(XFixesCursorImage *); + /** + * @brief XCB connection pointer released with `xcb_disconnect`. + */ using xcb_connect_t = util::dyn_safe_ptr; + /** + * @brief XCB image pointer released with `xcb_image_destroy`. + */ using xcb_img_t = util::c_ptr; + /** + * @brief XImage pointer released with `XDestroyImage`. + */ using ximg_t = util::safe_ptr; + /** + * @brief XFixes cursor image pointer released with `XFree`. + */ using xcursor_t = util::safe_ptr; + /** + * @brief XRandR CRTC info pointer released with `XRRFreeCrtcInfo`. + */ using crtc_info_t = util::dyn_safe_ptr<_XRRCrtcInfo, &x11::rr::FreeCrtcInfo>; + /** + * @brief XRandR output info pointer released with `XRRFreeOutputInfo`. + */ using output_info_t = util::dyn_safe_ptr<_XRROutputInfo, &x11::rr::FreeOutputInfo>; + /** + * @brief XRandR screen resources pointer released with `XRRFreeScreenResources`. + */ using screen_res_t = util::dyn_safe_ptr<_XRRScreenResources, &x11::rr::FreeScreenResources>; + /** + * @brief RAII wrapper that removes a SysV shared-memory segment. + */ class shm_id_t { public: shm_id_t(): id {-1} { } + /** + * @brief Take ownership of a SysV shared-memory segment ID. + * + * @param id SysV shared-memory segment ID. + */ shm_id_t(int id): id {id} { } + /** + * @brief Move ownership of a SysV shared-memory segment ID. + * + * @param other Shared-memory ID wrapper whose segment ownership is moved. + */ shm_id_t(shm_id_t &&other) noexcept: id(other.id) { other.id = -1; @@ -275,19 +349,32 @@ namespace platf { } } - int id; + int id; ///< SysV shared-memory segment identifier returned by shmget. }; + /** + * @brief RAII wrapper that detaches mapped SysV shared memory. + */ class shm_data_t { public: shm_data_t(): data {(void *) -1} { } + /** + * @brief Take ownership of an attached shared-memory mapping. + * + * @param data Pointer returned by shmat. + */ shm_data_t(void *data): data {data} { } + /** + * @brief Move ownership of an attached shared-memory mapping. + * + * @param other Shared-memory mapping wrapper whose attachment is moved. + */ shm_data_t(shm_data_t &&other) noexcept: data(other.data) { other.data = (void *) -1; @@ -299,13 +386,19 @@ namespace platf { } } - void *data; + void *data; ///< Address returned by shmat for the shared-memory segment. }; + /** + * @brief X11 image wrapper used by the software capture path. + */ struct x11_img_t: public img_t { - ximg_t img; + ximg_t img; ///< XImage backing the current software-captured frame. }; + /** + * @brief X11 shared-memory image and segment ownership. + */ struct shm_img_t: public img_t { ~shm_img_t() override { delete[] data; @@ -362,14 +455,17 @@ namespace platf { } } + /** + * @brief X11 display, window, and attribute handles for capture. + */ struct x11_attr_t: public display_t { - std::chrono::nanoseconds delay; + std::chrono::nanoseconds delay; ///< Delay before the timer task becomes eligible to run. - x11::xdisplay_t xdisplay; - Window xwindow; - XWindowAttributes xattr; + x11::xdisplay_t xdisplay; ///< X11 display connection used for capture. + Window xwindow; ///< Root window being captured. + XWindowAttributes xattr; ///< Cached X11 window attributes used to detect size changes. - mem_type_e mem_type; + mem_type_e mem_type; ///< Mem type. /** * Last X (NOT the streamed monitor!) size. @@ -377,6 +473,11 @@ namespace platf { */ // int env_width, env_height; + /** + * @brief Open the X11 display and initialize capture attributes. + * + * @param mem_type Requested memory path for the capture backend. + */ x11_attr_t(mem_type_e mem_type): xdisplay {x11::OpenDisplay(nullptr)}, xwindow {}, @@ -385,6 +486,13 @@ namespace platf { x11::InitThreads(); } + /** + * @brief Open the X11 display and cache capture window attributes. + * + * @param display_name Display name. + * @param config Configuration values to apply. + * @return 0 on success; nonzero or negative platform status on failure. + */ int init(const std::string &display_name, const ::video::config_t &config) { if (!xdisplay) { BOOST_LOG(error) << "Could not open X11 display"sv; @@ -501,6 +609,15 @@ namespace platf { return capture_e::ok; } + /** + * @brief Capture a display frame into the provided image object. + * + * @param pull_free_image_cb Callback that provides an available image buffer. + * @param img_out XImage-backed captured frame returned to the streaming pipeline. + * @param timeout Maximum time to wait for the operation. + * @param cursor Cursor image or visibility state to composite. + * @return Capture status reported to the streaming pipeline. + */ capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor) { refresh(); @@ -532,10 +649,21 @@ namespace platf { return capture_e::ok; } + /** + * @brief Allocate an image buffer compatible with this display backend. + * + * @return Allocated img object, or null when unavailable. + */ std::shared_ptr alloc_img() override { return std::make_shared(); } + /** + * @brief Create AVCodec encode device. + * + * @param pix_fmt Sunshine pixel format to convert or allocate for. + * @return Constructed AVCodec encode device object. + */ std::unique_ptr make_avcodec_encode_device(pix_fmt_e pix_fmt) override { #ifdef SUNSHINE_BUILD_VAAPI if (mem_type == mem_type_e::vaapi) { @@ -552,6 +680,12 @@ namespace platf { return std::make_unique(); } + /** + * @brief Populate a fallback image when real capture data is unavailable. + * + * @param img Image or frame object to read from or populate. + * @return Capture status reported to the streaming pipeline. + */ int dummy_img(img_t *img) override { // TODO: stop cheating and give black image if (!img) { @@ -567,24 +701,35 @@ namespace platf { } }; + /** + * @brief X11 shared-memory image dimensions and identifiers. + */ struct shm_attr_t: public x11_attr_t { - x11::xdisplay_t shm_xdisplay; // Prevent race condition with x11_attr_t::xdisplay - xcb_connect_t xcb; - xcb_screen_t *display; - std::uint32_t seg; + x11::xdisplay_t shm_xdisplay; ///< X11 display held separately to prevent races with x11_attr_t::xdisplay. + xcb_connect_t xcb; ///< XCB connection used by the shared-memory capture path. + xcb_screen_t *display; ///< XCB screen containing the captured root window. + std::uint32_t seg; ///< XCB shared-memory segment ID attached to the image. - shm_id_t shm_id; + shm_id_t shm_id; ///< Shm ID. - shm_data_t data; + shm_data_t data; ///< Attached SysV shared-memory data used by XShm. - task_pool_util::TaskPool::task_id_t refresh_task_id; + task_pool_util::TaskPool::task_id_t refresh_task_id; ///< Refresh task ID. + /** + * @brief Refresh X11 shared-memory capture after a scheduled delay. + */ void delayed_refresh() { refresh(); refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh, 2s, this).task_id; } + /** + * @brief Open an X11 shared-memory capture backend. + * + * @param mem_type Requested memory path for the capture backend. + */ shm_attr_t(mem_type_e mem_type): x11_attr_t(mem_type), shm_xdisplay {x11::OpenDisplay(nullptr)} { @@ -640,6 +785,15 @@ namespace platf { return capture_e::ok; } + /** + * @brief Capture a display frame into the provided image object. + * + * @param pull_free_image_cb Callback that provides an available image buffer. + * @param img_out Shared-memory captured frame returned to the streaming pipeline. + * @param timeout Maximum time to wait for the operation. + * @param cursor Cursor image or visibility state to composite. + * @return Capture status reported to the streaming pipeline. + */ capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor) { // The whole X server changed, so we must reinit everything if (xattr.width != env_width || xattr.height != env_height) { @@ -670,6 +824,11 @@ namespace platf { } } + /** + * @brief Allocate an image buffer compatible with this display backend. + * + * @return Allocated img object, or null when unavailable. + */ std::shared_ptr alloc_img() override { auto img = std::make_shared(); img->width = width; @@ -681,10 +840,23 @@ namespace platf { return img; } + /** + * @brief Populate a fallback image when real capture data is unavailable. + * + * @param img Image or frame object to read from or populate. + * @return Capture status reported to the streaming pipeline. + */ int dummy_img(platf::img_t *img) override { return 0; } + /** + * @brief Initialize X11 shared-memory capture for the selected display. + * + * @param display_name Display name. + * @param config Configuration values to apply. + * @return 0 on success; nonzero or negative platform status on failure. + */ int init(const std::string &display_name, const ::video::config_t &config) { if (x11_attr_t::init(display_name, config)) { return 1; @@ -724,11 +896,24 @@ namespace platf { return 0; } + /** + * @brief Calculate the XCB shared-memory frame size. + * + * @return Frame size in bytes for BGRA pixels. + */ std::uint32_t frame_size() { return width * height * 4; } }; + /** + * @brief Create an X11 display capture backend. + * + * @param hwdevice_type Hardware device type requested for capture or encode. + * @param display_name Display name. + * @param config Configuration values to apply. + * @return X11 display backend, or nullptr when initialization fails. + */ std::shared_ptr x11_display(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) { if (hwdevice_type != platf::mem_type_e::system && hwdevice_type != platf::mem_type_e::vaapi && hwdevice_type != platf::mem_type_e::cuda) { BOOST_LOG(error) << "Could not initialize x11 display with the given hw device type"sv; @@ -763,6 +948,11 @@ namespace platf { return x11_disp; } + /** + * @brief Enumerate display names accepted by the X11 backend. + * + * @return X11 display names, or an empty list when X11 probing fails. + */ std::vector x11_display_names() { if (load_x11() || load_xcb()) { BOOST_LOG(error) << "Couldn't init x11 libraries"sv; @@ -800,14 +990,23 @@ namespace platf { return names; } + /** + * @brief Release image resources. + */ void freeImage(XImage *p) { XDestroyImage(p); } + /** + * @brief Release x resources. + */ void freeX(XFixesCursorImage *p) { x11::Free(p); } + /** + * @brief Load XCB entry points used by the X11 capture backend. + */ int load_xcb() { // This will be called once only static int xcb_status = xcb::init_shm() || xcb::init(); @@ -815,6 +1014,9 @@ namespace platf { return xcb_status; } + /** + * @brief Load X11 entry points used by the X11 capture backend. + */ int load_x11() { // This will be called once only static int x11_status = @@ -868,14 +1070,25 @@ namespace platf { blend_cursor((xdisplay_t::pointer) ctx.get(), img, offsetX, offsetY); } + /** + * @brief Open and initialize the display connection used for capture. + */ xdisplay_t make_display() { return OpenDisplay(nullptr); } + /** + * @brief Release display resources. + */ void freeDisplay(_XDisplay *xdisplay) { CloseDisplay(xdisplay); } + /** + * @brief Release cursor context resources. + * + * @param ctx Native context object used by the operation or callback. + */ void freeCursorCtx(cursor_ctx_t::pointer ctx) { CloseDisplay((xdisplay_t::pointer) ctx); } diff --git a/src/platform/linux/x11grab.h b/src/platform/linux/x11grab.h index 9d0a18647..fd53c0fc2 100644 --- a/src/platform/linux/x11grab.h +++ b/src/platform/linux/x11grab.h @@ -20,16 +20,45 @@ namespace egl { namespace platf::x11 { struct cursor_ctx_raw_t; + /** + * @brief Release cursor context resources. + * + * @param ctx Native context object used by the operation or callback. + */ void freeCursorCtx(cursor_ctx_raw_t *ctx); + /** + * @brief Release display resources. + * + * @param xdisplay X11 display connection. + */ void freeDisplay(_XDisplay *xdisplay); + /** + * @brief XFixes cursor image pointer released with `XFree`. + */ using cursor_ctx_t = util::safe_ptr; + /** + * @brief X11 display pointer released with `XCloseDisplay`. + */ using xdisplay_t = util::safe_ptr<_XDisplay, freeDisplay>; + /** + * @brief X11 cursor image and positioning state used during capture. + */ class cursor_t { public: + /** + * @brief Allocate the underlying object and wrap it in the owning handle. + * + * @return Created backend object, or null when creation fails. + */ static std::optional make(); + /** + * @brief Run the capture loop for this backend. + * + * @param img Image or frame object to read from or populate. + */ void capture(egl::cursor_t &img); /** @@ -37,11 +66,19 @@ namespace platf::x11 { * * img <-- destination image * offsetX, offsetY <--- Top left corner of the virtual screen + * @param img Image or frame object to read from or populate. + * @param offsetX Offset x. + * @param offsetY Offset y. */ void blend(img_t &img, int offsetX, int offsetY); - cursor_ctx_t ctx; + cursor_ctx_t ctx; ///< X11 cursor context used to track and blend cursor images. }; + /** + * @brief Open and initialize the display connection used for capture. + * + * @return Constructed display object. + */ xdisplay_t make_display(); } // namespace platf::x11 diff --git a/src/platform/macos/av_audio.h b/src/platform/macos/av_audio.h index 12d3fba68..fda7adcb7 100644 --- a/src/platform/macos/av_audio.h +++ b/src/platform/macos/av_audio.h @@ -27,7 +27,29 @@ NS_ASSUME_NONNULL_BEGIN @class CATapDescription; namespace platf { + /** + * @brief Provide captured PCM frames to AudioConverter. + * + * @param inAudioConverter In audio converter. + * @param ioNumberDataPackets Requested and returned packet count. + * @param ioData Buffer list filled with input audio. + * @param outDataPacketDescription Optional packet description output. + * @param inUserData AudioConverterInputData state used by the callback. + * @return Core Audio status code from the callback. + */ OSStatus audioConverterComplexInputProc(AudioConverterRef _Nullable inAudioConverter, UInt32 *_Nonnull ioNumberDataPackets, AudioBufferList *_Nonnull ioData, AudioStreamPacketDescription *_Nullable *_Nullable outDataPacketDescription, void *_Nonnull inUserData); + /** + * @brief Receive system-audio tap samples from Core Audio. + * + * @param inDevice In device. + * @param inNow In now. + * @param inInputData In input data. + * @param inInputTime In input time. + * @param outOutputData Out output data. + * @param inOutputTime In output time. + * @param inClientData In client data. + * @return Core Audio status code from the IO callback. + */ OSStatus systemAudioIOProc(AudioObjectID inDevice, const AudioTimeStamp *_Nullable inNow, const AudioBufferList *_Nullable inInputData, const AudioTimeStamp *_Nullable inInputTime, AudioBufferList *_Nullable outOutputData, const AudioTimeStamp *_Nullable inOutputTime, void *_Nullable inClientData); } // namespace platf diff --git a/src/platform/macos/av_img_t.h b/src/platform/macos/av_img_t.h index 50896c4c5..431a27deb 100644 --- a/src/platform/macos/av_img_t.h +++ b/src/platform/macos/av_img_t.h @@ -12,9 +12,17 @@ #include "src/platform/common.h" namespace platf { + /** + * @brief CoreMedia sample buffer retained by an AV image wrapper. + */ struct av_sample_buf_t { - CMSampleBufferRef buf; + CMSampleBufferRef buf; ///< Retained CoreMedia sample buffer. + /** + * @brief Retain a CoreMedia sample buffer for captured-image lifetime. + * + * @param buf Sample buffer received from AVFoundation. + */ explicit av_sample_buf_t(CMSampleBufferRef buf): buf((CMSampleBufferRef) CFRetain(buf)) { } @@ -26,10 +34,18 @@ namespace platf { } }; + /** + * @brief CoreVideo pixel buffer retained by an AV image wrapper. + */ struct av_pixel_buf_t { - CVPixelBufferRef buf; + CVPixelBufferRef buf; ///< Pixel buffer extracted from the sample buffer. // Constructor + /** + * @brief Lock the sample buffer's pixel buffer for read-only access. + * + * @param sb Sample buffer that owns the image data. + */ explicit av_pixel_buf_t(CMSampleBufferRef sb): buf( CMSampleBufferGetImageBuffer(sb) @@ -37,6 +53,11 @@ namespace platf { CVPixelBufferLockBaseAddress(buf, kCVPixelBufferLock_ReadOnly); } + /** + * @brief Return the base address of the locked Core Video pixel buffer. + * + * @return Pointer to the first byte of image data in the pixel buffer. + */ [[nodiscard]] uint8_t *data() const { return static_cast(CVPixelBufferGetBaseAddress(buf)); } @@ -49,16 +70,29 @@ namespace platf { } }; + /** + * @brief Captured macOS image backed by an AV sample buffer. + */ struct av_img_t: img_t { - std::shared_ptr sample_buffer; - std::shared_ptr pixel_buffer; + std::shared_ptr sample_buffer; ///< Retained AVFoundation sample buffer for the captured frame. + std::shared_ptr pixel_buffer; ///< Retained CoreVideo pixel buffer extracted from the sample. }; + /** + * @brief Temporary retain wrapper used while AV image data is borrowed. + */ struct temp_retain_av_img_t { - std::shared_ptr sample_buffer; - std::shared_ptr pixel_buffer; - uint8_t *data; + std::shared_ptr sample_buffer; ///< Sample buffer kept alive while `data` is borrowed. + std::shared_ptr pixel_buffer; ///< Pixel buffer kept locked while `data` is borrowed. + uint8_t *data; ///< Pointer to the locked pixel buffer bytes. + /** + * @brief Construct a temporary AV image retain wrapper. + * + * @param sb Sample buffer to retain. + * @param pb Pixel buffer to retain. + * @param dt Image data pointer. + */ temp_retain_av_img_t( std::shared_ptr sb, std::shared_ptr pb, diff --git a/src/platform/macos/av_video.h b/src/platform/macos/av_video.h index b2fa5d4b2..7ba1522e2 100644 --- a/src/platform/macos/av_video.h +++ b/src/platform/macos/av_video.h @@ -8,34 +8,100 @@ #import #import +/** + * @brief macOS capture session and video output handles. + */ struct CaptureSession { - AVCaptureVideoDataOutput *output; - NSCondition *captureStopped; + AVCaptureVideoDataOutput *output; ///< Output. + NSCondition *captureStopped; ///< Capture stopped. }; static const int kMaxDisplays = 32; +/** + * @brief AVFoundation video capture controller used by the macOS backend. + */ @interface AVVideo: NSObject +/** + * @brief Display ID property. + */ @property (nonatomic, assign) CGDirectDisplayID displayID; +/** + * @brief Min frame duration property. + */ @property (nonatomic, assign) CMTime minFrameDuration; +/** + * @brief Pixel format property. + */ @property (nonatomic, assign) OSType pixelFormat; +/** + * @brief Frame width property. + */ @property (nonatomic, assign) int frameWidth; +/** + * @brief Frame height property. + */ @property (nonatomic, assign) int frameHeight; +/** + * @brief Objective-C block invoked for each captured sample buffer. + */ typedef bool (^FrameCallbackBlock)(CMSampleBufferRef); +/** + * @brief Capture session that owns the active AVFoundation inputs and outputs. + */ @property (nonatomic, assign) AVCaptureSession *session; +/** + * @brief Video outputs property. + */ @property (nonatomic, assign) NSMapTable *videoOutputs; +/** + * @brief Capture callbacks property. + */ @property (nonatomic, assign) NSMapTable *captureCallbacks; +/** + * @brief Capture signals property. + */ @property (nonatomic, assign) NSMapTable *captureSignals; +/** + * @brief List display names accepted by the selected capture backend. + * + * @return Display names accepted by the selected capture backend. + */ + (NSArray *)displayNames; +/** + * @brief Return the user-visible name for a CoreGraphics display. + * + * @param displayID Display ID. + * @return Display name for the supplied CoreGraphics display ID. + */ + (NSString *)getDisplayName:(CGDirectDisplayID)displayID; +/** + * @brief Initialize AVFoundation capture for a display and frame rate. + * + * @param displayID Display ID. + * @param frameRate Frame rate. + * @return Initialized AVVideo instance, or nil on failure. + */ - (id)initWithDisplay:(CGDirectDisplayID)displayID frameRate:(int)frameRate; +/** + * @brief Set frame width frame height. + * + * @param frameWidth Frame width. + * @param frameHeight Frame height. + */ - (void)setFrameWidth:(int)frameWidth frameHeight:(int)frameHeight; +/** + * @brief Run the capture loop for this backend. + * + * @param frameCallback Frame callback. + * @return Capture status reported to the streaming pipeline. + */ - (dispatch_semaphore_t)capture:(FrameCallbackBlock)frameCallback; @end diff --git a/src/platform/macos/coreaudio_helpers.h b/src/platform/macos/coreaudio_helpers.h index a2a1e46ff..5330b716e 100644 --- a/src/platform/macos/coreaudio_helpers.h +++ b/src/platform/macos/coreaudio_helpers.h @@ -52,8 +52,11 @@ namespace ca { } namespace detail { + /** + * @brief Small wrapper for displaying CoreAudio OSStatus values. + */ struct StatusView { - OSStatus e; + OSStatus e; ///< E. }; inline std::ostream &operator<<(std::ostream &os, StatusView v) { diff --git a/src/platform/macos/display.mm b/src/platform/macos/display.mm index 5d6c9a736..1801696d4 100644 --- a/src/platform/macos/display.mm +++ b/src/platform/macos/display.mm @@ -21,6 +21,10 @@ #include "src/platform/macos/nv12_zero_device.h" // Avoid conflict between AVFoundation and libavutil both defining AVMediaType +/** + * @def AVMediaType + * @brief Macro for AV media type. + */ #define AVMediaType AVMediaType_FFmpeg #include "src/video.h" #undef AVMediaType @@ -210,10 +214,13 @@ namespace platf { } } // namespace + /** + * @brief macOS display capture source and image buffers. + */ struct av_display_t: public display_t { - AVVideo *av_capture {}; - CGDirectDisplayID display_id {}; - IOPMAssertionID display_sleep_assertion {kIOPMNullAssertionID}; + AVVideo *av_capture {}; ///< AV capture. + CGDirectDisplayID display_id {}; ///< Display ID. + IOPMAssertionID display_sleep_assertion {kIOPMNullAssertionID}; ///< Display sleep assertion. ~av_display_t() override { [av_capture release]; @@ -226,6 +233,9 @@ namespace platf { } } + /** + * @brief Prevent macOS from sleeping the captured display while streaming. + */ void prevent_display_sleep() { if (display_sleep_assertion != kIOPMNullAssertionID) { return; @@ -292,10 +302,21 @@ namespace platf { return capture_e::ok; } + /** + * @brief Allocate an image buffer compatible with this display backend. + * + * @return Allocated img object, or null when unavailable. + */ std::shared_ptr alloc_img() override { return std::make_shared(); } + /** + * @brief Create AVCodec encode device. + * + * @param pix_fmt Sunshine pixel format to convert or allocate for. + * @return Constructed AVCodec encode device object. + */ std::unique_ptr make_avcodec_encode_device(pix_fmt_e pix_fmt) override { if (pix_fmt == pix_fmt_e::yuv420p) { av_capture.pixelFormat = kCVPixelFormatType_32BGRA; @@ -313,6 +334,12 @@ namespace platf { } } + /** + * @brief Populate a fallback image when real capture data is unavailable. + * + * @param img Image or frame object to read from or populate. + * @return Capture status reported to the streaming pipeline. + */ int dummy_img(img_t *img) override { if (!platf::is_screen_capture_allowed()) { // If we don't have the screen capture permission, this function will hang @@ -359,11 +386,20 @@ namespace platf { * display --> an opaque pointer to an object of this class * width --> the intended capture width * height --> the intended capture height + * @param display Display object or identifier associated with the operation. + * @param width Frame or display width in pixels. + * @param height Frame or display height in pixels. */ static void setResolution(void *display, int width, int height) { [static_cast(display) setFrameWidth:width frameHeight:height]; } + /** + * @brief Set pixel format. + * + * @param display Display object or identifier associated with the operation. + * @param pixelFormat Pixel format. + */ static void setPixelFormat(void *display, OSType pixelFormat) { static_cast(display).pixelFormat = pixelFormat; } @@ -440,8 +476,9 @@ namespace platf { } /** - * @brief Returns if GPUs/drivers have changed since the last call to this function. - * @return `true` if a change has occurred or if it is unknown whether a change occurred. + * @brief Report whether encoder backends should be probed again before streaming. + * + * @return Always `true` because macOS GPU changes are not tracked by this backend. */ bool needs_encoder_reenumeration() { // We don't track GPU state, so we will always reenumerate. Fortunately, it is fast on macOS. diff --git a/src/platform/macos/input.cpp b/src/platform/macos/input.cpp index 6eed2c1d3..fe17a9b35 100644 --- a/src/platform/macos/input.cpp +++ b/src/platform/macos/input.cpp @@ -24,49 +24,67 @@ #include "src/platform/common.h" #include "src/utility.h" -/** - * @brief Delay for a double click, in milliseconds. - * @todo Make this configurable. - */ -constexpr std::chrono::milliseconds MULTICLICK_DELAY_MS(500); +constexpr auto MULTICLICK_DELAY_MS = std::chrono::milliseconds(500); ///< Maximum gap between clicks that macOS should treat as a double click. namespace platf { using namespace std::literals; - constexpr int WHEEL_DELTA = 120; - constexpr double DEFAULT_SCROLLWHEEL_SCALING = 0.3125; - constexpr int DEFAULT_SCROLL_LINES_PER_DETENT = 5; + constexpr int WHEEL_DELTA = 120; ///< Protocol or platform constant for wheel delta. + constexpr double DEFAULT_SCROLLWHEEL_SCALING = 0.3125; ///< Protocol or platform constant for default scrollwheel scaling. + constexpr int DEFAULT_SCROLL_LINES_PER_DETENT = 5; ///< Protocol or platform constant for default scroll lines per detent. + /** + * @brief macOS input source and target display state. + */ struct macos_input_t { public: - CGDirectDisplayID display {}; - CGFloat displayScaling {}; - CGEventSourceRef source {}; + CGDirectDisplayID display {}; ///< CoreGraphics identifier for the display receiving injected input. + CGFloat displayScaling {}; ///< Scale factor used to translate client coordinates to display pixels. + CGEventSourceRef source {}; ///< CoreGraphics event source used for mouse and scroll events. // keyboard related stuff - CGEventSourceRef keyboard_source {}; - CGEventFlags kb_flags {}; + CGEventSourceRef keyboard_source {}; ///< CoreGraphics event source used for keyboard injection. + CGEventFlags kb_flags {}; ///< Active keyboard modifier flags currently held down by the client. // mouse related stuff - CGEventRef mouse_event {}; // mouse event source - double scrollwheel_scaling {DEFAULT_SCROLLWHEEL_SCALING}; - int scroll_lines_per_detent {DEFAULT_SCROLL_LINES_PER_DETENT}; + CGEventRef mouse_event {}; ///< Reusable CoreGraphics mouse event updated before posting. + double scrollwheel_scaling {DEFAULT_SCROLLWHEEL_SCALING}; ///< Multiplier applied to incoming scroll-wheel deltas. + int scroll_lines_per_detent {DEFAULT_SCROLL_LINES_PER_DETENT}; ///< Number of logical scroll lines represented by one wheel detent. + /** + * @brief Tracks whether the mouse button is currently pressed. + */ bool mouse_down[3] {}; // mouse button status + /** + * @brief Last mouse event. + */ std::chrono::steady_clock::steady_clock::time_point last_mouse_event[3][2]; // timestamp of last mouse events }; // A struct to hold a Windows keycode to Mac virtual keycode mapping. + /** + * @brief Mapping from Sunshine key symbols to macOS virtual key codes. + */ struct KeyCodeMap { - int win_keycode; - int mac_keycode; + int win_keycode; ///< Sunshine/Windows virtual key code received from the client. + int mac_keycode; ///< macOS virtual key code sent through CoreGraphics. }; // Customized less operator for using std::lower_bound() on a KeyCodeMap array. + /** + * @brief Order key mappings by Sunshine key code for binary search. + * + * @param a Left-hand mapping being compared. + * @param b Right-hand mapping being compared. + * @return True when `a` has a lower Sunshine key code than `b`. + */ bool operator<(const KeyCodeMap &a, const KeyCodeMap &b) { return a.win_keycode < b.win_keycode; } // clang-format off +/** + * @brief Key codes map. + */ const KeyCodeMap kKeyCodesMap[] = { { 0x08 /* VKEY_BACK */, kVK_Delete }, { 0x09 /* VKEY_TAB */, kVK_Tab }, @@ -238,6 +256,12 @@ const KeyCodeMap kKeyCodesMap[] = { }; // clang-format on + /** + * @brief Translate a platform keycode to a Sunshine key symbol. + * + * @param keycode Platform keycode being translated or emitted. + * @return Sunshine key symbol, or 0 when the keycode is unmapped. + */ int keysym(int keycode) { KeyCodeMap key_map {}; @@ -256,12 +280,22 @@ const KeyCodeMap kKeyCodesMap[] = { return temp_map->mac_keycode; } + /** + * @brief macOS modifier flags split into generic and device-specific bits. + */ struct modifier_flags_t { - CGEventFlags generic {}; - CGEventFlags device {}; - CGEventFlags all_devices {}; + CGEventFlags generic {}; ///< Modifier bits represented by device-independent CoreGraphics flags. + CGEventFlags device {}; ///< Modifier bits represented by left/right device-specific flags. + CGEventFlags all_devices {}; ///< Mask covering all device-specific variants for this modifier. }; + /** + * @brief Resolve the CoreGraphics modifier masks associated with a key code. + * + * @param key Sunshine key code that may represent a modifier key. + * @param flags Output masks for the matching CoreGraphics modifier. + * @return True when modifier flags were found for the key. + */ bool modifier_flags_for_key(int key, modifier_flags_t &flags) { switch (key) { case kVK_Shift: @@ -368,6 +402,16 @@ const KeyCodeMap kKeyCodesMap[] = { }; } + /** + * @brief Post a mouse event at the clamped display location. + * + * @param input Platform input context. + * @param button Mouse button. + * @param type CoreGraphics mouse event type. + * @param raw_location Requested mouse location. + * @param previous_location Previous mouse location. + * @param click_count Click count for the event. + */ void post_mouse( input_t &input, const CGMouseButton button, @@ -410,6 +454,12 @@ const KeyCodeMap kKeyCodesMap[] = { CGWarpMouseCursorPosition(location); } + /** + * @brief Choose the CoreGraphics mouse event type for the current button action. + * + * @param input Platform input backend that receives the event. + * @return CoreGraphics mouse event type for button press or release. + */ inline CGEventType event_type_mouse(input_t &input) { const auto macos_input = static_cast(input.get()); @@ -494,6 +544,12 @@ const KeyCodeMap kKeyCodesMap[] = { macos_input->last_mouse_event[mac_button][release] = now; } + /** + * @brief Get scroll lines per detent. + * + * @param scrollwheel_scaling Scrollwheel scaling. + * @return Number of logical lines represented by one wheel detent. + */ int get_scroll_lines_per_detent(double &scrollwheel_scaling) { double scale = DEFAULT_SCROLLWHEEL_SCALING; const auto value = CFPreferencesCopyValue(CFSTR("com.apple.scrollwheel.scaling"), kCFPreferencesAnyApplication, kCFPreferencesCurrentUser, kCFPreferencesAnyHost); @@ -521,6 +577,13 @@ const KeyCodeMap kKeyCodesMap[] = { return std::max(1, static_cast(std::ceil(1.0 + scroll_scale * lines_per_scroll_scale))); } + /** + * @brief Convert a high-resolution wheel delta to CoreGraphics scroll pixels. + * + * @param macos_input macOS input state containing scroll scaling settings. + * @param high_res_distance Wheel delta in Windows high-resolution units. + * @return Pixel distance to send to CoreGraphics. + */ int scroll_pixels(const macos_input_t *macos_input, const int high_res_distance) { const auto source_pixels_per_line = CGEventSourceGetPixelsPerLine(macos_input->source); const auto pixels_per_line = source_pixels_per_line > 0 ? static_cast(source_pixels_per_line + 0.5) : 10; @@ -529,6 +592,13 @@ const KeyCodeMap kKeyCodesMap[] = { return static_cast(scaled_pixels / WHEEL_DELTA); } + /** + * @brief Post a macOS scroll event to the target display. + * + * @param input Platform input backend that receives the event. + * @param wheelY Wheel y. + * @param wheelX Wheel x. + */ void post_scroll(input_t &input, const int wheelY, const int wheelX) { if (wheelY == 0 && wheelX == 0) { return; diff --git a/src/platform/macos/microphone.mm b/src/platform/macos/microphone.mm index 8f15af369..db877712d 100644 --- a/src/platform/macos/microphone.mm +++ b/src/platform/macos/microphone.mm @@ -11,13 +11,22 @@ namespace platf { using namespace std::literals; + /** + * @brief macOS microphone capture device and audio format state. + */ struct av_mic_t: public mic_t { - AVAudio *av_audio_capture {}; + AVAudio *av_audio_capture {}; ///< AV audio capture. ~av_mic_t() override { [av_audio_capture release]; } + /** + * @brief Deliver a captured audio sample to Sunshine's audio pipeline. + * + * @param sample_in Sample in. + * @return Capture status reported to the streaming pipeline. + */ capture_e sample(std::vector &sample_in) override { const uint32_t neededBytes = static_cast(sample_in.size() * sizeof(float)); uint8_t *dst = reinterpret_cast(sample_in.data()); @@ -54,15 +63,35 @@ namespace platf { } }; + /** + * @brief macOS audio control state used to create microphone streams. + */ struct macos_audio_control_t: public audio_control_t { - AVCaptureDevice *audio_capture_device {}; + AVCaptureDevice *audio_capture_device {}; ///< Audio capture device. public: + /** + * @brief Update the sink value on the backend. + * + * @param sink Audio sink name to route or capture. + * @return Status from updating sink. + */ int set_sink(const std::string &sink) override { BOOST_LOG(warning) << "audio_control_t::set_sink() unimplemented: "sv << sink; return 0; } + /** + * @brief Create a microphone capture stream for the requested layout. + * + * @param mapping Opus channel mapping table for the requested layout. + * @param channels Number of audio channels in the stream. + * @param sample_rate Audio sample rate in hertz. + * @param frame_size Number of samples captured per audio frame. + * @param continuous_audio Continuous audio. + * @param host_audio_enabled Whether host playback should remain enabled during capture. + * @return Microphone capture object for the requested audio layout. + */ std::unique_ptr microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size, bool continuous_audio, bool host_audio_enabled) override { auto mic = std::make_unique(); mic->av_audio_capture = [[AVAudio alloc] init]; @@ -110,6 +139,11 @@ namespace platf { return true; } + /** + * @brief Query host and virtual sink names available to Sunshine. + * + * @return Host and virtual sink names when the backend can report them. + */ std::optional sink_info() override { sink_t sink; diff --git a/src/platform/macos/misc.h b/src/platform/macos/misc.h index e791661d1..0af5d1ae2 100644 --- a/src/platform/macos/misc.h +++ b/src/platform/macos/misc.h @@ -11,13 +11,32 @@ #include namespace platf { + /** + * @brief Check whether macOS has granted screen-capture permission. + * + * @return True when Sunshine can capture the screen. + */ bool is_screen_capture_allowed(); -} +} // namespace platf namespace dyn { typedef void (*apiproc)(); + /** + * @brief Load persisted state from its backing store. + * + * @param handle Native library or object handle used by the operation. + * @param funcs Function table populated from the loaded library. + * @param strict Whether missing functions should be treated as an error. + * @return 0 when all required symbols are loaded; nonzero when loading fails. + */ int load(void *handle, const std::vector> &funcs, bool strict = true); + /** + * @brief Return the native handle owned by the wrapper. + * + * @param libs List of libraries to probe for the requested symbol. + * @return Native dynamic-library handle, or nullptr when no library can be opened. + */ void *handle(const std::vector &libs); } // namespace dyn diff --git a/src/platform/macos/misc.mm b/src/platform/macos/misc.mm index 12edfd7fc..56fca1340 100644 --- a/src/platform/macos/misc.mm +++ b/src/platform/macos/misc.mm @@ -5,6 +5,10 @@ // Required for IPV6_PKTINFO with Darwin headers #ifndef __APPLE_USE_RFC_3542 // NOLINT(bugprone-reserved-identifier) + /** + * @def __APPLE_USE_RFC_3542 + * @brief Macro for APPLE USE RFC 3542. + */ #define __APPLE_USE_RFC_3542 1 #endif @@ -43,7 +47,17 @@ namespace platf { #if __MAC_OS_X_VERSION_MAX_ALLOWED < 110000 // __MAC_11_0 // If they're not in the SDK then we can use our own function definitions. // Need to use weak import so that this will link in macOS 10.14 and earlier + /** + * @brief Query macOS screen-capture permission without prompting the user. + * + * @return True when screen-capture permission is granted. + */ extern "C" bool CGPreflightScreenCaptureAccess(void) __attribute__((weak_import)); + /** + * @brief Request macOS screen-capture permission from the user. + * + * @return True when screen-capture permission is granted. + */ extern "C" bool CGRequestScreenCaptureAccess(void) __attribute__((weak_import)); #endif @@ -52,6 +66,9 @@ namespace platf { } // namespace // Return whether screen capture is allowed for this process. + /** + * @brief Check whether screen capture allowed. + */ bool is_screen_capture_allowed() { return screen_capture_allowed; } @@ -436,8 +453,17 @@ namespace platf { // are disconnected. static std::atomic qos_ref_count = 0; + /** + * @brief Owns platform QoS state that is restored during cleanup. + */ class qos_t: public deinit_t { public: + /** + * @brief Apply macOS socket QoS settings for scoped cleanup. + * + * @param sockfd Native socket descriptor whose options are updated. + * @param options Request options or socket options to apply. + */ qos_t(int sockfd, std::vector> options): sockfd(sockfd), options(options) { @@ -462,11 +488,6 @@ namespace platf { /** * @brief Enables QoS on the given socket for traffic to the specified destination. - * @param native_socket The native socket handle. - * @param address The destination address for traffic sent on this socket. - * @param port The destination port for traffic sent on this socket. - * @param data_type The type of traffic sent on this socket. - * @param dscp_tagging Specifies whether to enable DSCP tagging on outgoing traffic. */ std::unique_ptr enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type, bool dscp_tagging) { int sockfd = (int) native_socket; @@ -546,6 +567,9 @@ namespace platf { } } + /** + * @brief macOS high-precision timer implementation backed by a worker thread. + */ class macos_high_precision_timer: public high_precision_timer { public: void sleep_for(const std::chrono::nanoseconds &duration) override { diff --git a/src/platform/macos/nv12_zero_device.cpp b/src/platform/macos/nv12_zero_device.cpp index b4fb28cb7..977019879 100644 --- a/src/platform/macos/nv12_zero_device.cpp +++ b/src/platform/macos/nv12_zero_device.cpp @@ -16,15 +16,24 @@ extern "C" { namespace platf { + /** + * @brief Release an FFmpeg frame allocated by the capture or conversion backend. + */ void free_frame(AVFrame *frame) { av_frame_free(&frame); } + /** + * @brief Release a backend buffer allocated for capture or conversion. + * + * @param opaque Opaque user pointer provided to the callback. + * @param data Payload or state data to serialize, deserialize, or forward. + */ void free_buffer(void *opaque, uint8_t *data) { CVPixelBufferRelease((CVPixelBufferRef) data); } - util::safe_ptr av_frame; + util::safe_ptr av_frame; ///< AV frame. int nv12_zero_device::convert(platf::img_t &img) { auto *av_img = (av_img_t *) &img; diff --git a/src/platform/macos/nv12_zero_device.h b/src/platform/macos/nv12_zero_device.h index 3aa7540c3..bd92b025b 100644 --- a/src/platform/macos/nv12_zero_device.h +++ b/src/platform/macos/nv12_zero_device.h @@ -10,8 +10,16 @@ struct AVFrame; namespace platf { + /** + * @brief Release an FFmpeg frame allocated by the capture or conversion backend. + * + * @param frame Video or graphics frame being processed. + */ void free_frame(AVFrame *frame); + /** + * @brief macOS zero-copy encode device that forwards NV12 frames from AVFoundation to FFmpeg. + */ class nv12_zero_device: public avcodec_encode_device_t { // display holds a pointer to an av_video object. Since the namespaces of AVFoundation // and FFMPEG collide, we need this opaque pointer and cannot use the definition @@ -20,13 +28,41 @@ namespace platf { public: // this function is used to set the resolution on an av_video object that we cannot // call directly because of namespace collisions between AVFoundation and FFMPEG + /** + * @brief Callback signature used to update the opaque AVFoundation display resolution. + */ using resolution_fn_t = std::function; - resolution_fn_t resolution_fn; + resolution_fn_t resolution_fn; ///< Callback stored for later AVFoundation resolution updates. + /** + * @brief Callback signature used to update the opaque AVFoundation pixel format. + */ using pixel_format_fn_t = std::function; + /** + * @brief Initialize zero-copy NV12 encoding for an AVFoundation display. + * + * @param display Display object or identifier associated with the operation. + * @param pix_fmt Sunshine pixel format to convert or allocate for. + * @param resolution_fn Callback used to resize the AVFoundation capture output. + * @param pixel_format_fn Pixel format. + * @return 0 on success; nonzero or negative platform status on failure. + */ int init(void *display, pix_fmt_e pix_fmt, resolution_fn_t resolution_fn, const pixel_format_fn_t &pixel_format_fn); + /** + * @brief Forward a captured AVFoundation frame to FFmpeg without copying. + * + * @param img Image or frame object to read from or populate. + * @return Conversion status. + */ int convert(img_t &img) override; + /** + * @brief Attach frame resources used by the next conversion or encode operation. + * + * @param frame Video or graphics frame being processed. + * @param hw_frames_ctx FFmpeg hardware frames context associated with the frame. + * @return Status from updating frame. + */ int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override; private: diff --git a/src/platform/windows/PolicyConfig.h b/src/platform/windows/PolicyConfig.h index a61d28733..4101217b3 100644 --- a/src/platform/windows/PolicyConfig.h +++ b/src/platform/windows/PolicyConfig.h @@ -24,8 +24,22 @@ DEFINE_GUID(CLSID_CPolicyConfigClient, 0x870af99c, 0x171d, 0x4f9e, 0xaf, 0x0d, 0 #endif +/** + * @brief Policy configuration COM interface. + */ +#ifdef DOXYGEN +class IPolicyConfig; +#else interface DECLSPEC_UUID("f8679f50-850a-41cf-9c72-430f290290c8") IPolicyConfig; +#endif +/** + * @brief Policy configuration COM class. + */ +#ifdef DOXYGEN +class CPolicyConfigClient; +#else class DECLSPEC_UUID("870af99c-171d-4f9e-af0d-e63df40c2bc9") CPolicyConfigClient; +#endif // ---------------------------------------------------------------------------- // class CPolicyConfigClient // {870af99c-171d-4f9e-af0d-e63df40c2bc9} @@ -41,21 +55,41 @@ class DECLSPEC_UUID("870af99c-171d-4f9e-af0d-e63df40c2bc9") CPolicyConfigClient; // ---------------------------------------------------------------------------- interface IPolicyConfig: public IUnknown { public: + /** + * @brief Get the mix format for an endpoint. + * + * @return HRESULT or platform status returned by the wrapped API. + */ virtual HRESULT GetMixFormat( PCWSTR, WAVEFORMATEX ** ); + /** + * @brief Get the device format for an endpoint. + * + * @return HRESULT or platform status returned by the wrapped API. + */ virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat( PCWSTR, INT, WAVEFORMATEX ** ); + /** + * @brief Reset the device format for an endpoint. + * + * @return HRESULT or platform status returned by the wrapped API. + */ virtual HRESULT STDMETHODCALLTYPE ResetDeviceFormat( PCWSTR ); + /** + * @brief Set the device format for an endpoint. + * + * @return HRESULT or platform status returned by the wrapped API. + */ virtual HRESULT STDMETHODCALLTYPE SetDeviceFormat( PCWSTR, @@ -63,6 +97,11 @@ public: WAVEFORMATEX * ); + /** + * @brief Get the processing period for an endpoint. + * + * @return HRESULT or platform status returned by the wrapped API. + */ virtual HRESULT STDMETHODCALLTYPE GetProcessingPeriod( PCWSTR, INT, @@ -70,38 +109,75 @@ public: PINT64 ); + /** + * @brief Set the processing period for an endpoint. + * + * @return HRESULT or platform status returned by the wrapped API. + */ virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod( PCWSTR, PINT64 ); + /** + * @brief Get the share mode for an endpoint. + * + * @return HRESULT or platform status returned by the wrapped API. + */ virtual HRESULT STDMETHODCALLTYPE GetShareMode( PCWSTR, struct DeviceShareMode * ); + /** + * @brief Set the share mode for an endpoint. + * + * @return HRESULT or platform status returned by the wrapped API. + */ virtual HRESULT STDMETHODCALLTYPE SetShareMode( PCWSTR, struct DeviceShareMode * ); + /** + * @brief Get a property value for an endpoint. + * + * @return HRESULT or platform status returned by the wrapped API. + */ virtual HRESULT STDMETHODCALLTYPE GetPropertyValue( PCWSTR, const PROPERTYKEY &, PROPVARIANT * ); + /** + * @brief Set a property value for an endpoint. + * + * @return HRESULT or platform status returned by the wrapped API. + */ virtual HRESULT STDMETHODCALLTYPE SetPropertyValue( PCWSTR, const PROPERTYKEY &, PROPVARIANT * ); + /** + * @brief Set the default endpoint for a role. + * + * @param wszDeviceId Endpoint device identifier. + * @param eRole Endpoint role. + * @return HRESULT or platform status returned by the wrapped API. + */ virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint( PCWSTR wszDeviceId, ERole eRole ); + /** + * @brief Set endpoint visibility. + * + * @return HRESULT or platform status returned by the wrapped API. + */ virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility( PCWSTR, INT diff --git a/src/platform/windows/audio.cpp b/src/platform/windows/audio.cpp index cc2788aa8..e12dc9033 100644 --- a/src/platform/windows/audio.cpp +++ b/src/platform/windows/audio.cpp @@ -26,9 +26,24 @@ #include "PolicyConfig.h" // clang-format on +#ifdef DOXYGEN +/** + * @brief Property key for a device description. + */ +extern const PROPERTYKEY PKEY_Device_DeviceDesc; +/** + * @brief Property key for a device friendly name. + */ +extern const PROPERTYKEY PKEY_Device_FriendlyName; +/** + * @brief Property key for a device interface friendly name. + */ +extern const PROPERTYKEY PKEY_DeviceInterface_FriendlyName; +#else DEFINE_PROPERTYKEY(PKEY_Device_DeviceDesc, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 2); // DEVPROP_TYPE_STRING DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); // DEVPROP_TYPE_STRING DEFINE_PROPERTYKEY(PKEY_DeviceInterface_FriendlyName, 0x026e516e, 0xb814, 0x414b, 0x83, 0xcd, 0x85, 0x6d, 0x6f, 0xef, 0x48, 0x22, 2); +#endif #if defined(__x86_64) || defined(__x86_64__) || defined(__amd64) || defined(__amd64__) || defined(_M_AMD64) #define STEAM_DRIVER_SUBDIR L"x64" @@ -203,27 +218,70 @@ namespace { using namespace std::literals; namespace platf::audio { + /** + * @brief Release the COM or platform reference owned by the pointer. + * + * @param p Pointer passed to the deleter or conversion helper. + */ template void Release(T *p) { p->Release(); } + /** + * @brief Free memory allocated by COM task APIs. + * + * @param p Pointer passed to the deleter or conversion helper. + */ template void co_task_free(T *p) { CoTaskMemFree((LPVOID) p); } + /** + * @brief COM device enumerator pointer for WASAPI endpoint discovery. + */ using device_enum_t = util::safe_ptr>; + /** + * @brief COM pointer to a Windows audio endpoint device. + */ using device_t = util::safe_ptr>; + /** + * @brief COM pointer to a collection of Windows audio endpoint devices. + */ using collection_t = util::safe_ptr>; + /** + * @brief COM pointer to the WASAPI audio client interface. + */ using audio_client_t = util::safe_ptr>; + /** + * @brief COM pointer to the WASAPI capture client interface. + */ using audio_capture_t = util::safe_ptr>; + /** + * @brief CoTaskMem-allocated WAVEFORMATEX pointer. + */ using wave_format_t = util::safe_ptr>; + /** + * @brief CoTaskMem-allocated wide string pointer. + */ using wstring_t = util::safe_ptr>; + /** + * @brief Windows HANDLE wrapper closed with `CloseHandle`. + */ using handle_t = util::safe_ptr_v2; + /** + * @brief COM pointer to the Windows policy configuration interface. + */ using policy_t = util::safe_ptr>; + /** + * @brief COM pointer to a Windows property store. + */ using prop_t = util::safe_ptr>; + /** + * @brief Initializes COM for the current thread and uninitializes it on exit. + */ class co_init_t: public deinit_t { public: co_init_t() { @@ -235,6 +293,9 @@ namespace platf::audio { } }; + /** + * @brief RAII wrapper that initializes and clears a Windows PROPVARIANT. + */ class prop_var_t { public: prop_var_t() { @@ -245,16 +306,22 @@ namespace platf::audio { PropVariantClear(&prop); } - PROPVARIANT prop; + PROPVARIANT prop; ///< Variant value returned by Windows property-store queries. }; + /** + * @brief Windows audio format details selected for capture. + */ struct format_t { - WORD channel_count; - std::string name; - int capture_waveformat_channel_mask; - virtual_sink_waveformats_t virtual_sink_waveformats; + WORD channel_count; ///< Channel count. + std::string name; ///< Human-readable name for this item. + int capture_waveformat_channel_mask; ///< Capture waveformat channel mask. + virtual_sink_waveformats_t virtual_sink_waveformats; ///< Virtual sink waveformats. }; + /** + * @brief Formats. + */ const std::array formats = { format_t { 2, @@ -276,6 +343,13 @@ namespace platf::audio { }, }; + /** + * @brief Create audio client. + * + * @param device D3D, audio, or platform device used by the operation. + * @param format Pixel, audio, or protocol format being converted. + * @return Constructed audio client object. + */ audio_client_t make_audio_client(device_t &device, const format_t &format) { audio_client_t audio_client; auto status = device->Activate( @@ -335,6 +409,12 @@ namespace platf::audio { return audio_client; } + /** + * @brief Query the default Windows render endpoint. + * + * @param device_enum Windows multimedia device enumerator. + * @return Default render endpoint, or an empty handle if lookup fails. + */ device_t default_device(device_enum_t &device_enum) { device_t device; HRESULT status; @@ -353,20 +433,40 @@ namespace platf::audio { return device; } + /** + * @brief Windows audio endpoint notification callback registered with MMDevice. + */ class audio_notification_t: public ::IMMNotificationClient { public: audio_notification_t() { } // IUnknown implementation (unused by IMMDeviceEnumerator) + /** + * @brief Satisfy IUnknown reference counting for the notification callback. + * + * @return Static reference count because the callback lifetime is externally owned. + */ ULONG STDMETHODCALLTYPE AddRef() { return 1; } + /** + * @brief Release the COM or platform reference owned by the pointer. + * + * @return Reference count or status returned after releasing the object. + */ ULONG STDMETHODCALLTYPE Release() { return 1; } + /** + * @brief Return the supported COM interface for the notification callback. + * + * @param riid COM interface identifier requested by QueryInterface. + * @param ppvInterface Output pointer receiving the requested interface. + * @return S_OK when the interface is supported; E_NOINTERFACE otherwise. + */ HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID **ppvInterface) { if (IID_IUnknown == riid) { AddRef(); @@ -383,6 +483,14 @@ namespace platf::audio { } // IMMNotificationClient + /** + * @brief Handle a Windows default-audio-device change notification. + * + * @param flow Audio endpoint data-flow direction. + * @param role Audio endpoint role used for default-device lookup. + * @param pwstrDeviceId Windows endpoint ID for the new default device. + * @return S_OK after recording the render-device change notification. + */ HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId) { if (flow == eRender) { default_render_device_changed_flag.store(true); @@ -390,14 +498,33 @@ namespace platf::audio { return S_OK; } + /** + * @brief Ignore endpoint-add notifications. + * + * @param pwstrDeviceId Windows endpoint ID for the added device. + * @return S_OK because Sunshine does not act on this notification. + */ HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR pwstrDeviceId) { return S_OK; } + /** + * @brief Ignore endpoint-removal notifications. + * + * @param pwstrDeviceId Windows endpoint ID for the removed device. + * @return S_OK because Sunshine does not act on this notification. + */ HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR pwstrDeviceId) { return S_OK; } + /** + * @brief Handle Windows audio endpoint state changes. + * + * @param pwstrDeviceId Audio device ID. + * @param dwNewState New device state. + * @return COM status code. + */ HRESULT STDMETHODCALLTYPE OnDeviceStateChanged( LPCWSTR pwstrDeviceId, DWORD dwNewState @@ -405,6 +532,13 @@ namespace platf::audio { return S_OK; } + /** + * @brief Handle Windows audio endpoint property changes. + * + * @param pwstrDeviceId Audio device ID. + * @param key Changed property key. + * @return COM status code. + */ HRESULT STDMETHODCALLTYPE OnPropertyValueChanged( LPCWSTR pwstrDeviceId, const PROPERTYKEY key @@ -424,8 +558,17 @@ namespace platf::audio { std::atomic_bool default_render_device_changed_flag; }; + /** + * @brief WASAPI microphone capture stream and endpoint notification state. + */ class mic_wasapi_t: public mic_t { public: + /** + * @brief Deliver a captured audio sample to Sunshine's audio pipeline. + * + * @param sample_out Sample out. + * @return Capture status reported to the streaming pipeline. + */ capture_e sample(std::vector &sample_out) override { auto sample_size = sample_out.size(); @@ -451,6 +594,15 @@ namespace platf::audio { return capture_e::ok; } + /** + * @brief Initialize WASAPI capture for the selected audio endpoint. + * + * @param sample_rate Audio sample rate in hertz. + * @param frame_size Number of samples captured per audio frame. + * @param channels_out Channels out. + * @param continuous Whether silent audio should continue to be emitted. + * @return 0 on success; nonzero or negative platform status on failure. + */ int init(std::uint32_t sample_rate, std::uint32_t frame_size, std::uint32_t channels_out, bool continuous) { audio_event.reset(CreateEventA(nullptr, FALSE, FALSE, nullptr)); if (!audio_event) { @@ -668,28 +820,36 @@ namespace platf::audio { } public: - handle_t audio_event; + handle_t audio_event; ///< Event signaled by WASAPI when captured audio is available. - device_enum_t device_enum; - device_t device; - audio_client_t audio_client; - audio_capture_t audio_capture; + device_enum_t device_enum; ///< Device enum. + device_t device; ///< WASAPI endpoint device selected for capture. + audio_client_t audio_client; ///< WASAPI audio client configured for shared-mode capture. + audio_capture_t audio_capture; ///< WASAPI capture client used to read sample packets. - audio_notification_t endpt_notification; - std::optional> default_endpt_changed_cb; + audio_notification_t endpt_notification; ///< Endpoint notification callback registered with Windows. + std::optional> default_endpt_changed_cb; ///< Callback invoked when the default endpoint changes. - REFERENCE_TIME default_latency_ms; + REFERENCE_TIME default_latency_ms; ///< WASAPI default device period used as capture latency. - util::buffer_t sample_buf; - float *sample_buf_pos; - int channels; - bool continuous_audio; + util::buffer_t sample_buf; ///< Floating-point sample buffer filled from WASAPI packets. + float *sample_buf_pos; ///< Current write position in `sample_buf`. + int channels; ///< Number of channels in the capture format. + bool continuous_audio; ///< Whether audio packets continue during silence. - HANDLE mmcss_task_handle = nullptr; + HANDLE mmcss_task_handle = nullptr; ///< MMCSS task handle for the audio capture thread. }; + /** + * @brief Platform audio controller that manages sinks and microphone capture. + */ class audio_control_t: public ::platf::audio_control_t { public: + /** + * @brief Query host and virtual sink names available to Sunshine. + * + * @return Host and virtual sink names when the backend can report them. + */ std::optional sink_info() override { sink_t sink; @@ -767,6 +927,17 @@ namespace platf::audio { return std::nullopt; } + /** + * @brief Create a microphone capture stream for the requested layout. + * + * @param mapping Opus channel mapping table for the requested layout. + * @param channels Number of audio channels in the stream. + * @param sample_rate Audio sample rate in hertz. + * @param frame_size Number of samples captured per audio frame. + * @param continuous_audio Continuous audio. + * @param host_audio_enabled Whether host playback should remain enabled during capture. + * @return Microphone capture object for the requested audio layout. + */ std::unique_ptr microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size, bool continuous_audio, [[maybe_unused]] bool host_audio_enabled) override { auto mic = std::make_unique(); @@ -793,6 +964,8 @@ namespace platf::audio { * Any virtual sink detected will be prefixed by: * virtual-(format name) * If it doesn't contain that prefix, then the format will not be changed + * @param sink Audio sink name to route or capture. + * @return Status from updating format. */ std::optional set_format(const std::string &sink) { if (sink.empty()) { @@ -854,6 +1027,12 @@ namespace platf::audio { return std::nullopt; } + /** + * @brief Update the sink value on the backend. + * + * @param sink Audio sink name to route or capture. + * @return Status from updating sink. + */ int set_sink(const std::string &sink) override { auto device_id = set_format(sink); if (!device_id) { @@ -884,6 +1063,9 @@ namespace platf::audio { return failure; } + /** + * @brief Enumerates supported match field options. + */ enum class match_field_e { device_id, ///< Match device_id device_friendly_name, ///< Match endpoint friendly name @@ -891,15 +1073,32 @@ namespace platf::audio { device_description, ///< Match endpoint description }; + /** + * @brief List of format fields used to compare audio formats. + */ using match_fields_list_t = std::vector>; + /** + * @brief One matched audio-format field and its expected value. + */ using matched_field_t = std::pair; + /** + * @brief Build matching fields for Steam Streaming Speakers. + * + * @return Field list used to identify Steam's virtual speaker endpoint. + */ audio_control_t::match_fields_list_t match_steam_speakers() { return { {match_field_e::adapter_friendly_name, L"Steam Streaming Speakers"} }; } + /** + * @brief Build matching fields that all contain the same endpoint name. + * + * @param name Endpoint name or identifier to match across all fields. + * @return Field list requiring every supported endpoint field to match the name. + */ audio_control_t::match_fields_list_t match_all_fields(const std::wstring &name) { return { {match_field_e::device_id, name}, // {0.0.0.00000000}.{29dd7668-45b2-4846-882d-950f55bf7eb8} @@ -1116,6 +1315,11 @@ namespace platf::audio { #endif } + /** + * @brief Initialize Windows audio policy interfaces. + * + * @return 0 on success; nonzero or negative platform status on failure. + */ int init() { auto status = CoCreateInstance( CLSID_CPolicyConfigClient, @@ -1147,12 +1351,15 @@ namespace platf::audio { return 0; } + /** + * @brief Destroy the Windows audio control. + */ ~audio_control_t() override { } - policy_t policy; - audio::device_enum_t device_enum; - std::string assigned_sink; + policy_t policy; ///< Windows policy configuration interface used to switch default audio devices. + audio::device_enum_t device_enum; ///< Device enumerator used to query and watch audio endpoints. + std::string assigned_sink; ///< Virtual sink assigned while Sunshine captures host audio. }; } // namespace platf::audio @@ -1160,8 +1367,13 @@ namespace platf { // It's not big enough to justify it's own source file :/ namespace dxgi { + /** + * @brief Initialize the Windows audio-control backend. + * + * @return 0 on success; nonzero or negative platform status on failure. + */ int init(); - } + } // namespace dxgi std::unique_ptr audio_control() { auto control = std::make_unique(); diff --git a/src/platform/windows/display.h b/src/platform/windows/display.h index 2fcb7dc3b..844ee82b4 100644 --- a/src/platform/windows/display.h +++ b/src/platform/windows/display.h @@ -24,69 +24,198 @@ namespace platf::dxgi { // Add D3D11_CREATE_DEVICE_DEBUG here to enable the D3D11 debug runtime. // You should have a debugger like WinDbg attached to receive debug messages. - auto constexpr D3D11_CREATE_DEVICE_FLAGS = 0; + auto constexpr D3D11_CREATE_DEVICE_FLAGS = 0; ///< Protocol or platform constant for d3 d11 create device flags. + /** + * @brief Release the COM or platform reference owned by the pointer. + * + * @param dxgi DXGI object whose COM reference should be released. + */ template void Release(T *dxgi) { dxgi->Release(); } + /** + * @brief Owning COM pointer for the DXGI factory used to enumerate adapters. + */ using factory1_t = util::safe_ptr>; + /** + * @brief Owning COM pointer for a DXGI device interface. + */ using dxgi_t = util::safe_ptr>; + /** + * @brief Owning COM pointer for a DXGI 1.1 device interface. + */ using dxgi1_t = util::safe_ptr>; + /** + * @brief Owning COM pointer for the D3D11 device used by capture and conversion. + */ using device_t = util::safe_ptr>; + /** + * @brief Owning COM pointer for the D3D11.1 device interface. + */ using device1_t = util::safe_ptr>; + /** + * @brief Owning COM pointer for the immediate D3D11 device context. + */ using device_ctx_t = util::safe_ptr>; + /** + * @brief Owning COM pointer for a DXGI display adapter. + */ using adapter_t = util::safe_ptr>; + /** + * @brief Owning COM pointer for a DXGI output. + */ using output_t = util::safe_ptr>; + /** + * @brief Owning COM pointer for a DXGI output that supports duplication. + */ using output1_t = util::safe_ptr>; + /** + * @brief Owning COM pointer for a DXGI output with HDR metadata support. + */ using output5_t = util::safe_ptr>; + /** + * @brief Owning COM pointer for a DXGI output with modern display descriptors. + */ using output6_t = util::safe_ptr>; + /** + * @brief Owning COM pointer for the DXGI desktop duplication session. + */ using dup_t = util::safe_ptr>; + /** + * @brief Owning COM pointer for a D3D11 2D texture. + */ using texture2d_t = util::safe_ptr>; + /** + * @brief Owning COM pointer for a D3D11 1D texture. + */ using texture1d_t = util::safe_ptr>; + /** + * @brief Owning COM pointer for the DXGI resource view of a shared texture. + */ using resource_t = util::safe_ptr>; + /** + * @brief Owning COM pointer for the DXGI 1.1 resource sharing interface. + */ using resource1_t = util::safe_ptr>; + /** + * @brief Owning COM pointer for the D3D11 multithread-protection interface. + */ using multithread_t = util::safe_ptr>; + /** + * @brief Owning COM pointer for a D3D11 vertex shader. + */ using vs_t = util::safe_ptr>; + /** + * @brief Owning COM pointer for a D3D11 pixel shader. + */ using ps_t = util::safe_ptr>; + /** + * @brief Owning COM pointer for a D3D11 blend state. + */ using blend_t = util::safe_ptr>; + /** + * @brief Owning COM pointer for a D3D11 input layout. + */ using input_layout_t = util::safe_ptr>; + /** + * @brief Owning COM pointer for a D3D11 render-target view. + */ using render_target_t = util::safe_ptr>; + /** + * @brief Owning COM pointer for a D3D11 shader-resource view. + */ using shader_res_t = util::safe_ptr>; + /** + * @brief Owning COM pointer for a D3D11 buffer. + */ using buf_t = util::safe_ptr>; + /** + * @brief D3D rasterizer state handle type. + */ using raster_state_t = util::safe_ptr>; + /** + * @brief D3D sampler state handle type. + */ using sampler_state_t = util::safe_ptr>; + /** + * @brief Owning COM pointer for a compiled shader bytecode blob. + */ using blob_t = util::safe_ptr>; + /** + * @brief D3D depth-stencil state handle type. + */ using depth_stencil_state_t = util::safe_ptr>; + /** + * @brief Owning COM pointer for a D3D11 depth-stencil view. + */ using depth_stencil_view_t = util::safe_ptr>; + /** + * @brief Owning COM pointer for a keyed mutex on a shared DXGI resource. + */ using keyed_mutex_t = util::safe_ptr>; namespace video { + /** + * @brief Owning COM pointer for the D3D11 video device interface. + */ using device_t = util::safe_ptr>; + /** + * @brief Owning COM pointer for the D3D11 video context interface. + */ using ctx_t = util::safe_ptr>; + /** + * @brief Owning COM pointer for a D3D11 video processor. + */ using processor_t = util::safe_ptr>; + /** + * @brief Owning COM pointer for a video processor output view. + */ using processor_out_t = util::safe_ptr>; + /** + * @brief Owning COM pointer for a video processor input view. + */ using processor_in_t = util::safe_ptr>; + /** + * @brief Owning COM pointer for a video processor capability enumerator. + */ using processor_enum_t = util::safe_ptr>; } // namespace video class hwdevice_t; + /** + * @brief Cursor position and visibility for the current capture frame. + */ struct cursor_t { - std::vector img_data; + std::vector img_data; ///< Raw pointer-shape bytes from DXGI output duplication. - DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info; - int x; - int y; - bool visible; + DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info; ///< Shape info. + int x; ///< X. + int y; ///< Y. + bool visible; ///< Whether the cursor is visible. }; + /** + * @brief GPU resources used to render a captured cursor shape. + */ class gpu_cursor_t { public: gpu_cursor_t(): cursor_view {0, 0, 0, 0, 0.0f, 1.0f} {}; + /** + * @brief Update the cursor position and display geometry used for rendering. + * + * @param topleft_x Cursor left edge in desktop coordinates. + * @param topleft_y Cursor top edge in desktop coordinates. + * @param display_width Captured display width in pixels. + * @param display_height Captured display height in pixels. + * @param display_rotation DXGI rotation applied to the captured display. + * @param visible Whether the cursor should be visible in the frame. + */ void set_pos(LONG topleft_x, LONG topleft_y, LONG display_width, LONG display_height, DXGI_MODE_ROTATION display_rotation, bool visible) { this->topleft_x = topleft_x; this->topleft_y = topleft_y; @@ -97,6 +226,13 @@ namespace platf::dxgi { update_viewport(); } + /** + * @brief Replace the cursor texture and update its render viewport. + * + * @param texture_width Cursor texture width in pixels. + * @param texture_height Cursor texture height in pixels. + * @param texture D3D11 cursor texture to render. + */ void set_texture(LONG texture_width, LONG texture_height, texture2d_t &&texture) { this->texture = std::move(texture); this->texture_width = texture_width; @@ -104,6 +240,9 @@ namespace platf::dxgi { update_viewport(); } + /** + * @brief Recalculate the D3D viewport for the cursor texture. + */ void update_viewport() { switch (display_rotation) { case DXGI_MODE_ROTATION_UNSPECIFIED: @@ -137,50 +276,63 @@ namespace platf::dxgi { } } - texture2d_t texture; - LONG texture_width; - LONG texture_height; + texture2d_t texture; ///< D3D11 texture backing the captured frame. + LONG texture_width; ///< Texture width. + LONG texture_height; ///< Texture height. - LONG topleft_x; - LONG topleft_y; + LONG topleft_x; ///< Topleft x. + LONG topleft_y; ///< Topleft y. - LONG display_width; - LONG display_height; - DXGI_MODE_ROTATION display_rotation; + LONG display_width; ///< Display width. + LONG display_height; ///< Display height. + DXGI_MODE_ROTATION display_rotation; ///< Display rotation. - shader_res_t input_res; + shader_res_t input_res; ///< Input res. - D3D11_VIEWPORT cursor_view; + D3D11_VIEWPORT cursor_view; ///< Cursor view. - bool visible; + bool visible; ///< Whether the cursor is visible. }; + /** + * @brief Shared D3D11/DXGI state used by Windows display capture backends. + */ class display_base_t: public display_t { public: + /** + * @brief Initialize D3D11 desktop duplication for the selected output. + * + * @param config Configuration values to apply. + * @param display_name Display name. + * @return 0 on success; nonzero or negative platform status on failure. + */ int init(const ::video::config_t &config, const std::string &display_name); capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override; - factory1_t factory; - adapter_t adapter; - output_t output; - device_t device; - device_ctx_t device_ctx; - DXGI_RATIONAL display_refresh_rate; - int display_refresh_rate_rounded; + factory1_t factory; ///< DXGI factory used to enumerate adapters and outputs. + adapter_t adapter; ///< DXGI adapter containing the selected output. + output_t output; ///< DXGI output duplicated for capture. + device_t device; ///< D3D11 device used for desktop duplication capture. + device_ctx_t device_ctx; ///< D3D11 device context used for copy and render operations. + DXGI_RATIONAL display_refresh_rate; ///< Display refresh rate. + int display_refresh_rate_rounded; ///< Display refresh rate rounded. - DXGI_MODE_ROTATION display_rotation = DXGI_MODE_ROTATION_UNSPECIFIED; - int width_before_rotation; - int height_before_rotation; + DXGI_MODE_ROTATION display_rotation = DXGI_MODE_ROTATION_UNSPECIFIED; ///< Display rotation. + int width_before_rotation; ///< Width before rotation. + int height_before_rotation; ///< Height before rotation. - int client_frame_rate; - DXGI_RATIONAL client_frame_rate_strict; + int client_frame_rate; ///< Client frame rate. + DXGI_RATIONAL client_frame_rate_strict; ///< Client frame rate strict. - DXGI_FORMAT capture_format; - D3D_FEATURE_LEVEL feature_level; + DXGI_FORMAT capture_format; ///< Capture format. + D3D_FEATURE_LEVEL feature_level; ///< Feature level. - std::unique_ptr timer = create_high_precision_timer(); + std::unique_ptr timer = create_high_precision_timer(); ///< Timer. + /** + * @brief Enumerates supported d3 DKMT SCHEDULINGPRIORITYCLASS options. + */ typedef enum _D3DKMT_SCHEDULINGPRIORITYCLASS { D3DKMT_SCHEDULINGPRIORITYCLASS_IDLE, ///< Idle priority class D3DKMT_SCHEDULINGPRIORITYCLASS_BELOW_NORMAL, ///< Below normal priority class @@ -188,15 +340,24 @@ namespace platf::dxgi { D3DKMT_SCHEDULINGPRIORITYCLASS_ABOVE_NORMAL, ///< Above normal priority class D3DKMT_SCHEDULINGPRIORITYCLASS_HIGH, ///< High priority class D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME ///< Realtime priority class - } D3DKMT_SCHEDULINGPRIORITYCLASS; + } D3DKMT_SCHEDULINGPRIORITYCLASS; ///< Alias for D3 DKMT SCHEDULINGPRIORITYCLASS. + /** + * @brief Kernel-mode D3DKMT adapter handle. + */ typedef UINT D3DKMT_HANDLE; + /** + * @brief Win32 D3DKMT adapter-open request structure. + */ typedef struct _D3DKMT_OPENADAPTERFROMLUID { - LUID AdapterLuid; - D3DKMT_HANDLE hAdapter; - } D3DKMT_OPENADAPTERFROMLUID; + LUID AdapterLuid; ///< Adapter luid. + D3DKMT_HANDLE hAdapter; ///< D3DKMT adapter handle used for low-level display queries. + } D3DKMT_OPENADAPTERFROMLUID; ///< Alias for D3 DKMT OPENADAPTERFROMLUID. + /** + * @brief Win32 D3DKMT WDDM 2.7 capability flags. + */ typedef struct _D3DKMT_WDDM_2_7_CAPS { union { struct @@ -210,40 +371,112 @@ namespace platf::dxgi { UINT Value; }; - } D3DKMT_WDDM_2_7_CAPS; + } D3DKMT_WDDM_2_7_CAPS; ///< Alias for D3 DKMT WDDM 2 7 CAPS. + /** + * @brief Win32 D3DKMT adapter information query. + */ typedef struct _D3DKMT_QUERYADAPTERINFO { - D3DKMT_HANDLE hAdapter; - UINT Type; - VOID *pPrivateDriverData; - UINT PrivateDriverDataSize; - } D3DKMT_QUERYADAPTERINFO; + D3DKMT_HANDLE hAdapter; ///< D3DKMT adapter handle used for low-level display queries. + UINT Type; ///< Type. + VOID *pPrivateDriverData; ///< P private driver data. + UINT PrivateDriverDataSize; ///< Private driver data size. + } D3DKMT_QUERYADAPTERINFO; ///< Alias for D3 DKMT QUERYADAPTERINFO. - const UINT KMTQAITYPE_WDDM_2_7_CAPS = 70; + const UINT KMTQAITYPE_WDDM_2_7_CAPS = 70; ///< Protocol or platform constant for kmtqaitype wddm 2 7 caps. + /** + * @brief Win32 D3DKMT adapter-close request structure. + */ typedef struct _D3DKMT_CLOSEADAPTER { - D3DKMT_HANDLE hAdapter; - } D3DKMT_CLOSEADAPTER; + D3DKMT_HANDLE hAdapter; ///< D3DKMT adapter handle used for low-level display queries. + } D3DKMT_CLOSEADAPTER; ///< Alias for D3 DKMT CLOSEADAPTER. + /** + * @brief Function pointer for setting D3DKMT process scheduling priority. + */ typedef NTSTATUS(WINAPI *PD3DKMTSetProcessSchedulingPriorityClass)(HANDLE, D3DKMT_SCHEDULINGPRIORITYCLASS); + /** + * @brief Function pointer for opening a D3DKMT adapter by LUID. + */ typedef NTSTATUS(WINAPI *PD3DKMTOpenAdapterFromLuid)(D3DKMT_OPENADAPTERFROMLUID *); + /** + * @brief Function pointer for querying D3DKMT adapter information. + */ typedef NTSTATUS(WINAPI *PD3DKMTQueryAdapterInfo)(D3DKMT_QUERYADAPTERINFO *); + /** + * @brief Function pointer for closing a D3DKMT adapter handle. + */ typedef NTSTATUS(WINAPI *PD3DKMTCloseAdapter)(D3DKMT_CLOSEADAPTER *); + /** + * @brief Report whether the active display mode is HDR. + * + * @return True when the active display mode is HDR. + */ virtual bool is_hdr() override; + /** + * @brief Read HDR metadata for the active display mode. + * + * @param metadata Output structure populated with HDR metadata. + * @return True when HDR metadata was written to `metadata`. + */ virtual bool get_hdr_metadata(SS_HDR_METADATA &metadata) override; + /** + * @brief Convert a DXGI format enum to a diagnostic string. + * + * @param format Pixel, audio, or protocol format being converted. + * @return Static string describing the DXGI format. + */ const char *dxgi_format_to_string(DXGI_FORMAT format); + /** + * @brief Convert a DXGI colorspace enum to a diagnostic string. + * + * @param type DXGI colorspace value reported by the output. + * @return Static string describing the colorspace. + */ const char *colorspace_to_string(DXGI_COLOR_SPACE_TYPE type); + /** + * @brief Get supported capture formats. + * + * @return Capture formats supported by this display backend. + */ virtual std::vector get_supported_capture_formats() = 0; protected: + /** + * @brief Get pixel pitch. + * + * @return Bytes per pixel for the active capture format. + */ int get_pixel_pitch() { return (capture_format == DXGI_FORMAT_R16G16B16A16_FLOAT) ? 8 : 4; } + /** + * @brief Capture a display frame into the provided image object. + * + * @param pull_free_image_cb Callback that provides an available image buffer. + * @param img_out Captured image buffer returned to the streaming pipeline. + * @param timeout Maximum time to wait for the operation. + * @param cursor_visible Cursor visible. + * @return Capture status reported to the streaming pipeline. + */ virtual capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor_visible) = 0; + /** + * @brief Release resources associated with the last captured snapshot. + * + * @return Capture status after releasing the current snapshot. + */ virtual capture_e release_snapshot() = 0; + /** + * @brief Finish cursor composition into a RAM-backed image. + * + * @param img Image or frame object to read from or populate. + * @param dummy Unused placeholder required by the interface signature. + * @return Capture status after finalizing the captured image. + */ virtual int complete_img(img_t *img, bool dummy) = 0; }; @@ -252,15 +485,44 @@ namespace platf::dxgi { */ class display_ram_t: public display_base_t { public: + /** + * @brief Allocate an image buffer compatible with this display backend. + * + * @return Allocated img object, or null when unavailable. + */ std::shared_ptr alloc_img() override; + /** + * @brief Populate a fallback image when real capture data is unavailable. + * + * @param img Image or frame object to read from or populate. + * @return Capture status reported to the streaming pipeline. + */ int dummy_img(img_t *img) override; + /** + * @brief Finish cursor composition into a GPU-backed image. + * + * @param img Image or frame object to read from or populate. + * @param dummy Unused placeholder required by the interface signature. + * @return Capture status after finalizing the captured image. + */ int complete_img(img_t *img, bool dummy) override; + /** + * @brief Get supported capture formats. + * + * @return Capture formats supported by this display backend. + */ std::vector get_supported_capture_formats() override; + /** + * @brief Create AVCodec encode device. + * + * @param pix_fmt Sunshine pixel format to convert or allocate for. + * @return Constructed AVCodec encode device object. + */ std::unique_ptr make_avcodec_encode_device(pix_fmt_e pix_fmt) override; - D3D11_MAPPED_SUBRESOURCE img_info; - texture2d_t texture; + D3D11_MAPPED_SUBRESOURCE img_info; ///< CPU mapping information for the captured texture. + texture2d_t texture; ///< D3D11 texture backing the captured frame. }; /** @@ -268,18 +530,53 @@ namespace platf::dxgi { */ class display_vram_t: public display_base_t, public std::enable_shared_from_this { public: + /** + * @brief Allocate an image buffer compatible with this display backend. + * + * @return Allocated img object, or null when unavailable. + */ std::shared_ptr alloc_img() override; + /** + * @brief Populate a fallback image when real capture data is unavailable. + * + * @param img_base Image buffer populated with a fallback frame. + * @return Capture status reported to the streaming pipeline. + */ int dummy_img(img_t *img_base) override; + /** + * @brief Finalize a VRAM image after capture or dummy-frame generation. + * + * @param img_base Image buffer whose D3D resources are finalized. + * @param dummy Unused placeholder required by the interface signature. + * @return Capture status after finalizing the captured image. + */ int complete_img(img_t *img_base, bool dummy) override; + /** + * @brief Get supported capture formats. + * + * @return Capture formats supported by this display backend. + */ std::vector get_supported_capture_formats() override; bool is_codec_supported(std::string_view name, const ::video::config_t &config) override; + /** + * @brief Create AVCodec encode device. + * + * @param pix_fmt Sunshine pixel format to convert or allocate for. + * @return Constructed AVCodec encode device object. + */ std::unique_ptr make_avcodec_encode_device(pix_fmt_e pix_fmt) override; + /** + * @brief Create NVENC encode device. + * + * @param pix_fmt Sunshine pixel format to convert or allocate for. + * @return Constructed NVENC encode device object. + */ std::unique_ptr make_nvenc_encode_device(pix_fmt_e pix_fmt) override; - std::atomic next_image_id; + std::atomic next_image_id; ///< Next image ID. }; /** @@ -287,13 +584,39 @@ namespace platf::dxgi { */ class duplication_t { public: - dup_t dup; - bool has_frame {}; - std::chrono::steady_clock::time_point last_protected_content_warning_time {}; + dup_t dup; ///< Desktop Duplication capture session. + bool has_frame {}; ///< Whether a frame is currently held by the duplication object. + std::chrono::steady_clock::time_point last_protected_content_warning_time {}; ///< Last protected content warning time. + /** + * @brief Initialize WGC capture for the selected display. + * + * @param display Display object or identifier associated with the operation. + * @param config Configuration values to apply. + * @return 0 on success; nonzero or negative platform status on failure. + */ int init(display_base_t *display, const ::video::config_t &config); + /** + * @brief Acquire the next frame from the Windows capture backend. + * + * @param frame_info Frame info. + * @param timeout Maximum time to wait for the operation. + * @param res_p Res p. + * @return Capture status for the frame acquisition attempt. + */ capture_e next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p); + /** + * @brief Reset the object to its initial empty state. + * + * @param dup_p Dup p. + * @return Reset status. + */ capture_e reset(dup_t::pointer dup_p = dup_t::pointer()); + /** + * @brief Release resources associated with frame. + * + * @return Capture status after releasing the current frame. + */ capture_e release_frame(); ~duplication_t(); @@ -304,12 +627,33 @@ namespace platf::dxgi { */ class display_ddup_ram_t: public display_ram_t { public: + /** + * @brief Initialize shared D3D capture resources for a RAM frame. + * + * @param config Configuration values to apply. + * @param display_name Display name. + * @return 0 on success; nonzero or negative platform status on failure. + */ int init(const ::video::config_t &config, const std::string &display_name); + /** + * @brief Capture a display frame into the provided image object. + * + * @param pull_free_image_cb Callback that provides an available image buffer. + * @param img_out Captured image buffer returned to the streaming pipeline. + * @param timeout Maximum time to wait for the operation. + * @param cursor_visible Cursor visible. + * @return Capture status reported to the streaming pipeline. + */ capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor_visible) override; + /** + * @brief Release resources associated with the last captured snapshot. + * + * @return Capture status after releasing the current snapshot. + */ capture_e release_snapshot() override; - duplication_t dup; - cursor_t cursor; + duplication_t dup; ///< Desktop Duplication session used to acquire frames. + cursor_t cursor; ///< Cursor. }; /** @@ -317,26 +661,47 @@ namespace platf::dxgi { */ class display_ddup_vram_t: public display_vram_t { public: + /** + * @brief Initialize D3D cursor rendering resources for RAM capture. + * + * @param config Configuration values to apply. + * @param display_name Display name. + * @return 0 on success; nonzero or negative platform status on failure. + */ int init(const ::video::config_t &config, const std::string &display_name); + /** + * @brief Capture a display frame into the provided image object. + * + * @param pull_free_image_cb Callback that provides an available image buffer. + * @param img_out Captured image buffer returned to the streaming pipeline. + * @param timeout Maximum time to wait for the operation. + * @param cursor_visible Cursor visible. + * @return Capture status reported to the streaming pipeline. + */ capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor_visible) override; + /** + * @brief Release resources associated with the last captured snapshot. + * + * @return Capture status after releasing the current snapshot. + */ capture_e release_snapshot() override; - duplication_t dup; - sampler_state_t sampler_linear; + duplication_t dup; ///< Desktop Duplication session used to acquire frames. + sampler_state_t sampler_linear; ///< Sampler linear. - blend_t blend_alpha; - blend_t blend_invert; - blend_t blend_disable; + blend_t blend_alpha; ///< Blend alpha. + blend_t blend_invert; ///< Blend invert. + blend_t blend_disable; ///< Blend disable. - ps_t cursor_ps; - vs_t cursor_vs; + ps_t cursor_ps; ///< Cursor ps. + vs_t cursor_vs; ///< Cursor vs. - gpu_cursor_t cursor_alpha; - gpu_cursor_t cursor_xor; + gpu_cursor_t cursor_alpha; ///< Cursor alpha. + gpu_cursor_t cursor_xor; ///< Cursor xor. - texture2d_t old_surface_delayed_destruction; - std::chrono::steady_clock::time_point old_surface_timestamp; - std::variant> last_frame_variant; + texture2d_t old_surface_delayed_destruction; ///< Old surface delayed destruction. + std::chrono::steady_clock::time_point old_surface_timestamp; ///< Old surface timestamp. + std::variant> last_frame_variant; ///< Last frame variant. }; /** @@ -358,10 +723,36 @@ namespace platf::dxgi { wgc_capture_t(); ~wgc_capture_t(); + /** + * @brief Initialize D3D cursor rendering resources for GPU capture. + * + * @param display Display object or identifier associated with the operation. + * @param config Configuration values to apply. + * @return 0 on success; nonzero or negative platform status on failure. + */ int init(display_base_t *display, const ::video::config_t &config); + /** + * @brief Acquire the next frame from the Windows capture backend. + * + * @param timeout Maximum time to wait for the operation. + * @param out Output object populated by the operation. + * @param out_time QPC timestamp associated with the acquired frame. + * @return Capture status for the frame acquisition attempt. + */ capture_e next_frame(std::chrono::milliseconds timeout, ID3D11Texture2D **out, uint64_t &out_time); + /** + * @brief Release resources associated with frame. + * + * @return Capture status after releasing the current frame. + */ capture_e release_frame(); - int set_cursor_visible(bool); + /** + * @brief Enable or disable cursor composition in Windows.Graphics.Capture frames. + * + * @param cursor_visible Whether the cursor should be included in captured frames. + * @return Zero when the cursor visibility state was accepted by the capture session. + */ + int set_cursor_visible(bool cursor_visible); }; /** @@ -371,8 +762,29 @@ namespace platf::dxgi { wgc_capture_t dup; public: + /** + * @brief Initialize shared D3D capture resources for a GPU frame. + * + * @param config Configuration values to apply. + * @param display_name Display name. + * @return 0 on success; nonzero or negative platform status on failure. + */ int init(const ::video::config_t &config, const std::string &display_name); + /** + * @brief Capture a display frame into the provided image object. + * + * @param pull_free_image_cb Callback that provides an available image buffer. + * @param img_out Captured image buffer returned to the streaming pipeline. + * @param timeout Maximum time to wait for the operation. + * @param cursor_visible Cursor visible. + * @return Capture status reported to the streaming pipeline. + */ capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor_visible) override; + /** + * @brief Release resources associated with the last captured snapshot. + * + * @return Capture status after releasing the current snapshot. + */ capture_e release_snapshot() override; }; @@ -383,8 +795,29 @@ namespace platf::dxgi { wgc_capture_t dup; public: + /** + * @brief Initialize Windows Graphics Capture frame-pool resources. + * + * @param config Configuration values to apply. + * @param display_name Display name. + * @return 0 on success; nonzero or negative platform status on failure. + */ int init(const ::video::config_t &config, const std::string &display_name); + /** + * @brief Capture a display frame into the provided image object. + * + * @param pull_free_image_cb Callback that provides an available image buffer. + * @param img_out Captured image buffer returned to the streaming pipeline. + * @param timeout Maximum time to wait for the operation. + * @param cursor_visible Cursor visible. + * @return Capture status reported to the streaming pipeline. + */ capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor_visible) override; + /** + * @brief Release resources associated with the last captured snapshot. + * + * @return Capture status after releasing the current snapshot. + */ capture_e release_snapshot() override; }; } // namespace platf::dxgi diff --git a/src/platform/windows/display_base.cpp b/src/platform/windows/display_base.cpp index e42bb8efa..c576ae0c9 100644 --- a/src/platform/windows/display_base.cpp +++ b/src/platform/windows/display_base.cpp @@ -19,9 +19,15 @@ // We have to include boost/process/v1.hpp before display.h due to WinSock.h, // but that prevents the definition of NTSTATUS so we must define it ourself. +/** + * @brief Windows NT status code returned by native APIs. + */ typedef long NTSTATUS; // Definition from the WDK's d3dkmthk.h +/** + * @brief Enumerates supported d3 DKMT GPU PREFERENCE QUERY STATE options. + */ typedef enum _D3DKMT_GPU_PREFERENCE_QUERY_STATE: DWORD { D3DKMT_GPU_PREFERENCE_STATE_UNINITIALIZED, ///< The GPU preference isn't initialized. D3DKMT_GPU_PREFERENCE_STATE_HIGH_PERFORMANCE, ///< The highest performing GPU is preferred. @@ -29,7 +35,7 @@ typedef enum _D3DKMT_GPU_PREFERENCE_QUERY_STATE: DWORD { D3DKMT_GPU_PREFERENCE_STATE_UNSPECIFIED, ///< A GPU preference isn't specified. D3DKMT_GPU_PREFERENCE_STATE_NOT_FOUND, ///< A GPU preference isn't found. D3DKMT_GPU_PREFERENCE_STATE_USER_SPECIFIED_GPU ///< A specific GPU is preferred. -} D3DKMT_GPU_PREFERENCE_QUERY_STATE; +} D3DKMT_GPU_PREFERENCE_QUERY_STATE; ///< Alias for D3 DKMT GPU PREFERENCE QUERY STATE. #include "display.h" #include "misc.h" @@ -354,6 +360,8 @@ namespace platf::dxgi { * @param adapter The DXGI adapter to use for capture. * @param output The DXGI output to capture. * @param enumeration_only Specifies whether this test is occurring for display enumeration. + * + * @return True when Desktop Duplication can capture the requested output. */ bool test_dxgi_duplication(adapter_t &adapter, output_t &output, bool enumeration_only) { D3D_FEATURE_LEVEL featureLevels[] { @@ -819,6 +827,9 @@ namespace platf::dxgi { return true; } + /** + * @brief Names for DXGI_FORMAT values used in diagnostic logging. + */ const char *format_str[] = { "DXGI_FORMAT_UNKNOWN", "DXGI_FORMAT_R32G32B32A32_TYPELESS", @@ -1109,7 +1120,8 @@ namespace platf { } /** - * @brief Returns if GPUs/drivers have changed since the last call to this function. + * @brief Check whether DXGI reports that adapter or driver enumeration is stale. + * * @return `true` if a change has occurred or if it is unknown whether a change occurred. */ bool needs_encoder_reenumeration() { diff --git a/src/platform/windows/display_ram.cpp b/src/platform/windows/display_ram.cpp index e8ec540eb..9a4553bff 100644 --- a/src/platform/windows/display_ram.cpp +++ b/src/platform/windows/display_ram.cpp @@ -12,13 +12,25 @@ namespace platf { } namespace platf::dxgi { + /** + * @brief Captured frame buffer shared between capture and encode stages. + */ struct img_t: public ::platf::img_t { + /** + * @brief Destroy the RAM-backed DXGI image. + */ ~img_t() override { delete[] data; data = nullptr; } }; + /** + * @brief Blend a monochrome cursor shape into a RAM frame. + * + * @param cursor Cursor image or visibility state to composite. + * @param img Image or frame object to read from or populate. + */ void blend_cursor_monochrome(const cursor_t &cursor, img_t &img) { int height = cursor.shape_info.Height / 2; int width = cursor.shape_info.Width; @@ -81,6 +93,12 @@ namespace platf::dxgi { } } + /** + * @brief Alpha-blend a color cursor pixel into a captured RAM frame. + * + * @param img_pixel_p Pixel in the destination frame. + * @param cursor_pixel ARGB cursor pixel to blend. + */ void apply_color_alpha(int *img_pixel_p, int cursor_pixel) { auto colors_out = (std::uint8_t *) &cursor_pixel; auto colors_in = (std::uint8_t *) img_pixel_p; @@ -96,6 +114,12 @@ namespace platf::dxgi { } } + /** + * @brief Apply a masked-color cursor pixel into a captured RAM frame. + * + * @param img_pixel_p Pixel in the destination frame. + * @param cursor_pixel ARGB cursor pixel with masked-cursor semantics. + */ void apply_color_masked(int *img_pixel_p, int cursor_pixel) { // TODO: When use of IDXGIOutput5 is implemented, support different color formats auto alpha = ((std::uint8_t *) &cursor_pixel)[3]; @@ -106,6 +130,13 @@ namespace platf::dxgi { } } + /** + * @brief Blend a color cursor shape into a RAM frame. + * + * @param cursor Cursor image or visibility state to composite. + * @param img Image or frame object to read from or populate. + * @param masked Whether the cursor image uses an AND/XOR mask. + */ void blend_cursor_color(const cursor_t &cursor, img_t &img, const bool masked) { int height = cursor.shape_info.Height; int width = cursor.shape_info.Width; @@ -152,6 +183,12 @@ namespace platf::dxgi { } } + /** + * @brief Blend the current cursor image into a RAM frame. + * + * @param cursor Cursor image or visibility state to composite. + * @param img Image or frame object to read from or populate. + */ void blend_cursor(const cursor_t &cursor, img_t &img) { switch (cursor.shape_info.Type) { case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR: diff --git a/src/platform/windows/display_vram.cpp b/src/platform/windows/display_vram.cpp index d659c4bbe..5e5f4ff97 100644 --- a/src/platform/windows/display_vram.cpp +++ b/src/platform/windows/display_vram.cpp @@ -31,6 +31,10 @@ extern "C" { #include "utf_utils.h" #if !defined(SUNSHINE_SHADERS_DIR) // for testing this needs to be defined in cmake as we don't do an install + /** + * @def SUNSHINE_SHADERS_DIR + * @brief Macro for SUNSHINE SHADERS DIR. + */ #define SUNSHINE_SHADERS_DIR SUNSHINE_ASSETS_DIR "/shaders/directx" #endif namespace platf { @@ -41,10 +45,20 @@ static void free_frame(AVFrame *frame) { av_frame_free(&frame); } +/** + * @brief FFmpeg hardware frame pointer released with `av_frame_free`. + */ using frame_t = util::safe_ptr; namespace platf::dxgi { + /** + * @brief Create a buffer object or message. + * + * @param device D3D, audio, or platform device used by the operation. + * @param t Initial value used to populate the GPU buffer. + * @return Constructed buffer object. + */ template buf_t make_buffer(device_t::pointer device, const T &t) { static_assert(sizeof(T) % 16 == 0, "Buffer needs to be aligned on a 16-byte alignment"); @@ -69,6 +83,14 @@ namespace platf::dxgi { return buf_t {buf_p}; } + /** + * @brief Create a blend object or message. + * + * @param device D3D, audio, or platform device used by the operation. + * @param enable Whether the blend state should enable blending. + * @param invert Whether the blend state should invert the color channels. + * @return Constructed blend object. + */ blend_t make_blend(device_t::pointer device, bool enable, bool invert) { D3D11_BLEND_DESC bdesc {}; auto &rt = bdesc.RenderTarget[0]; @@ -103,53 +125,56 @@ namespace platf::dxgi { return blend; } - blob_t convert_yuv420_packed_uv_type0_ps_hlsl; - blob_t convert_yuv420_packed_uv_type0_ps_linear_hlsl; - blob_t convert_yuv420_packed_uv_type0_ps_perceptual_quantizer_hlsl; - blob_t convert_yuv420_packed_uv_type0_vs_hlsl; - blob_t convert_yuv420_packed_uv_type0s_ps_hlsl; - blob_t convert_yuv420_packed_uv_type0s_ps_linear_hlsl; - blob_t convert_yuv420_packed_uv_type0s_ps_perceptual_quantizer_hlsl; - blob_t convert_yuv420_packed_uv_type0s_vs_hlsl; - blob_t convert_yuv420_planar_y_ps_hlsl; - blob_t convert_yuv420_planar_y_ps_linear_hlsl; - blob_t convert_yuv420_planar_y_ps_perceptual_quantizer_hlsl; - blob_t convert_yuv420_planar_y_vs_hlsl; - blob_t convert_yuv444_packed_ayuv_ps_hlsl; - blob_t convert_yuv444_packed_ayuv_ps_linear_hlsl; - blob_t convert_yuv444_packed_vs_hlsl; - blob_t convert_yuv444_planar_ps_hlsl; - blob_t convert_yuv444_planar_ps_linear_hlsl; - blob_t convert_yuv444_planar_ps_perceptual_quantizer_hlsl; - blob_t convert_yuv444_packed_y410_ps_hlsl; - blob_t convert_yuv444_packed_y410_ps_linear_hlsl; - blob_t convert_yuv444_packed_y410_ps_perceptual_quantizer_hlsl; - blob_t convert_yuv444_planar_vs_hlsl; - blob_t cursor_ps_hlsl; - blob_t cursor_ps_normalize_white_hlsl; - blob_t cursor_vs_hlsl; + blob_t convert_yuv420_packed_uv_type0_ps_hlsl; ///< Convert yuv420 packed uv type0 ps hlsl. + blob_t convert_yuv420_packed_uv_type0_ps_linear_hlsl; ///< Convert yuv420 packed uv type0 ps linear hlsl. + blob_t convert_yuv420_packed_uv_type0_ps_perceptual_quantizer_hlsl; ///< Convert yuv420 packed uv type0 ps perceptual quantizer hlsl. + blob_t convert_yuv420_packed_uv_type0_vs_hlsl; ///< Convert yuv420 packed uv type0 vs hlsl. + blob_t convert_yuv420_packed_uv_type0s_ps_hlsl; ///< Convert yuv420 packed uv type0s ps hlsl. + blob_t convert_yuv420_packed_uv_type0s_ps_linear_hlsl; ///< Convert yuv420 packed uv type0s ps linear hlsl. + blob_t convert_yuv420_packed_uv_type0s_ps_perceptual_quantizer_hlsl; ///< Convert yuv420 packed uv type0s ps perceptual quantizer hlsl. + blob_t convert_yuv420_packed_uv_type0s_vs_hlsl; ///< Convert yuv420 packed uv type0s vs hlsl. + blob_t convert_yuv420_planar_y_ps_hlsl; ///< Convert yuv420 planar y ps hlsl. + blob_t convert_yuv420_planar_y_ps_linear_hlsl; ///< Convert yuv420 planar y ps linear hlsl. + blob_t convert_yuv420_planar_y_ps_perceptual_quantizer_hlsl; ///< Convert yuv420 planar y ps perceptual quantizer hlsl. + blob_t convert_yuv420_planar_y_vs_hlsl; ///< Convert yuv420 planar y vs hlsl. + blob_t convert_yuv444_packed_ayuv_ps_hlsl; ///< Convert YUV444 packed ayuv ps hlsl. + blob_t convert_yuv444_packed_ayuv_ps_linear_hlsl; ///< Convert YUV444 packed ayuv ps linear hlsl. + blob_t convert_yuv444_packed_vs_hlsl; ///< Convert YUV444 packed vs hlsl. + blob_t convert_yuv444_planar_ps_hlsl; ///< Convert YUV444 planar ps hlsl. + blob_t convert_yuv444_planar_ps_linear_hlsl; ///< Convert YUV444 planar ps linear hlsl. + blob_t convert_yuv444_planar_ps_perceptual_quantizer_hlsl; ///< Convert YUV444 planar ps perceptual quantizer hlsl. + blob_t convert_yuv444_packed_y410_ps_hlsl; ///< Convert YUV444 packed y410 ps hlsl. + blob_t convert_yuv444_packed_y410_ps_linear_hlsl; ///< Convert YUV444 packed y410 ps linear hlsl. + blob_t convert_yuv444_packed_y410_ps_perceptual_quantizer_hlsl; ///< Convert YUV444 packed y410 ps perceptual quantizer hlsl. + blob_t convert_yuv444_planar_vs_hlsl; ///< Convert YUV444 planar vs hlsl. + blob_t cursor_ps_hlsl; ///< Cursor ps hlsl. + blob_t cursor_ps_normalize_white_hlsl; ///< Cursor ps normalize white hlsl. + blob_t cursor_vs_hlsl; ///< Cursor vs hlsl. + /** + * @brief D3D-backed captured image and duplication metadata. + */ struct img_d3d_t: public platf::img_t { // These objects are owned by the display_t's ID3D11Device - texture2d_t capture_texture; - render_target_t capture_rt; - keyed_mutex_t capture_mutex; + texture2d_t capture_texture; ///< Capture texture. + render_target_t capture_rt; ///< Capture rt. + keyed_mutex_t capture_mutex; ///< Capture mutex. // This is the shared handle used by hwdevice_t to open capture_texture - HANDLE encoder_texture_handle = {}; + HANDLE encoder_texture_handle = {}; ///< Encoder texture handle. // Set to true if the image corresponds to a dummy texture used prior to // the first successful capture of a desktop frame - bool dummy = false; + bool dummy = false; ///< Whether this image is a dummy placeholder. // Set to true if the image is blank (contains no content at all, including a cursor) - bool blank = true; + bool blank = true; ///< Whether the texture currently contains a blank frame. // Unique identifier for this image - uint32_t id = 0; + uint32_t id = 0; ///< Unique identifier used to cache encoder resources for this image. // DXGI format of this image texture - DXGI_FORMAT format; + DXGI_FORMAT format; ///< DXGI format of the captured texture. virtual ~img_d3d_t() override { if (encoder_texture_handle) { @@ -158,19 +183,33 @@ namespace platf::dxgi { }; }; + /** + * @brief Keyed-mutex guard used while sharing a D3D texture. + */ struct texture_lock_helper { - keyed_mutex_t _mutex; - bool _locked = false; + keyed_mutex_t _mutex; ///< D3D keyed mutex acquired for shared-texture access. + bool _locked = false; ///< Whether the keyed mutex is currently locked. texture_lock_helper(const texture_lock_helper &) = delete; texture_lock_helper &operator=(const texture_lock_helper &) = delete; + /** + * @brief Move a keyed-mutex lock helper while preserving lock ownership. + * + * @param other Helper whose acquired keyed mutex is transferred. + */ texture_lock_helper(texture_lock_helper &&other) { _mutex.reset(other._mutex.release()); _locked = other._locked; other._locked = false; } + /** + * @brief Assign state from another instance while preserving ownership semantics. + * + * @param other Source object whose state is copied or moved into this object. + * @return Reference or value produced by the operator. + */ texture_lock_helper &operator=(texture_lock_helper &&other) { if (_locked) { _mutex->ReleaseSync(0); @@ -181,6 +220,11 @@ namespace platf::dxgi { return *this; } + /** + * @brief Acquire a D3D keyed mutex for scoped texture access. + * + * @param mutex Keyed mutex to acquire for texture access. + */ texture_lock_helper(IDXGIKeyedMutex *mutex): _mutex(mutex) { if (_mutex) { @@ -194,6 +238,11 @@ namespace platf::dxgi { } } + /** + * @brief Acquire the underlying lock or keyed mutex. + * + * @return True when the keyed mutex is acquired. + */ bool lock() { if (_locked) { return true; @@ -208,6 +257,13 @@ namespace platf::dxgi { } }; + /** + * @brief Create cursor xor image. + * + * @param img_data Raw pointer-shape bytes returned by DXGI. + * @param shape_info DXGI metadata describing the pointer shape. + * @return Constructed cursor xor image object. + */ util::buffer_t make_cursor_xor_image(const util::buffer_t &img_data, DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info) { constexpr std::uint32_t inverted = 0xFFFFFFFF; constexpr std::uint32_t transparent = 0; @@ -277,6 +333,13 @@ namespace platf::dxgi { return cursor_img; } + /** + * @brief Create cursor alpha image. + * + * @param img_data Raw pointer-shape bytes returned by DXGI. + * @param shape_info DXGI metadata describing the pointer shape. + * @return Constructed cursor alpha image object. + */ util::buffer_t make_cursor_alpha_image(const util::buffer_t &img_data, DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info) { constexpr std::uint32_t black = 0xFF000000; constexpr std::uint32_t white = 0xFFFFFFFF; @@ -350,6 +413,14 @@ namespace platf::dxgi { return cursor_img; } + /** + * @brief Compile an HLSL shader from source text. + * + * @param file Optional stdio file handle connected to the child process. + * @param entrypoint Function entry point to resolve from the library. + * @param shader_model Shader model. + * @return Compiled shader blob, or an empty blob when compilation fails. + */ blob_t compile_shader(LPCSTR file, LPCSTR entrypoint, LPCSTR shader_model) { blob_t::pointer msg_p = nullptr; blob_t::pointer compiled_p; @@ -376,16 +447,37 @@ namespace platf::dxgi { return blob_t {compiled_p}; } + /** + * @brief Compile an HLSL pixel shader from source text. + * + * @param file Optional stdio file handle connected to the child process. + * @return Compiled pixel shader object, or an empty handle when compilation fails. + */ blob_t compile_pixel_shader(LPCSTR file) { return compile_shader(file, "main_ps", "ps_5_0"); } + /** + * @brief Compile an HLSL vertex shader from source text. + * + * @param file Optional stdio file handle connected to the child process. + * @return Compiled vertex shader object, or an empty handle when compilation fails. + */ blob_t compile_vertex_shader(LPCSTR file) { return compile_shader(file, "main_vs", "vs_5_0"); } + /** + * @brief Shared D3D11 conversion resources for AVCodec and NVENC encode devices. + */ class d3d_base_encode_device final { public: + /** + * @brief Convert a captured D3D image into encoder input textures. + * + * @param img_base D3D image supplied by the capture backend. + * @return Conversion status. + */ int convert(platf::img_t &img_base) { // Garbage collect mapped capture images whose weak references have expired for (auto it = img_ctx_map.begin(); it != img_ctx_map.end();) { @@ -457,6 +549,11 @@ namespace platf::dxgi { return 0; } + /** + * @brief Apply the configured colorspace metadata to the active frame. + * + * @param colorspace Colorimetry information used for conversion or encoding. + */ void apply_colorspace(const ::video::sunshine_colorspace_t &colorspace) { auto color_vectors = ::video::color_vectors_from_colorspace(colorspace, true); @@ -482,6 +579,14 @@ namespace platf::dxgi { this->color_matrix = std::move(color_matrix); } + /** + * @brief Create D3D11 output textures, views, and shaders for frame conversion. + * + * @param frame_texture Frame texture. + * @param width Frame or display width in pixels. + * @param height Frame or display height in pixels. + * @return 0 when output resources are initialized; nonzero on D3D failure. + */ int init_output(ID3D11Texture2D *frame_texture, int width, int height) { // The underlying frame pool owns the texture, so we must reference it for ourselves frame_texture->AddRef(); @@ -489,16 +594,20 @@ namespace platf::dxgi { HRESULT status = S_OK; -#define create_vertex_shader_helper(x, y) \ - if (FAILED(status = device->CreateVertexShader(x->GetBufferPointer(), x->GetBufferSize(), nullptr, &y))) { \ - BOOST_LOG(error) << "Failed to create vertex shader " << #x << ": " << util::log_hex(status); \ - return -1; \ - } -#define create_pixel_shader_helper(x, y) \ - if (FAILED(status = device->CreatePixelShader(x->GetBufferPointer(), x->GetBufferSize(), nullptr, &y))) { \ - BOOST_LOG(error) << "Failed to create pixel shader " << #x << ": " << util::log_hex(status); \ - return -1; \ - } +#ifndef DOXYGEN + #define create_vertex_shader_helper(x, y) \ + if (FAILED(status = device->CreateVertexShader(x->GetBufferPointer(), x->GetBufferSize(), nullptr, &y))) { \ + BOOST_LOG(error) << "Failed to create vertex shader " << #x << ": " << util::log_hex(status); \ + return -1; \ + } +#endif +#ifndef DOXYGEN + #define create_pixel_shader_helper(x, y) \ + if (FAILED(status = device->CreatePixelShader(x->GetBufferPointer(), x->GetBufferSize(), nullptr, &y))) { \ + BOOST_LOG(error) << "Failed to create pixel shader " << #x << ": " << util::log_hex(status); \ + return -1; \ + } +#endif const bool downscaling = display->width > width || display->height > height; @@ -709,6 +818,14 @@ namespace platf::dxgi { return 0; } + /** + * @brief Initialize shared D3D conversion resources for the encoder. + * + * @param display Display object or identifier associated with the operation. + * @param adapter_p Adapter p. + * @param pix_fmt Sunshine pixel format to convert or allocate for. + * @return 0 on success; nonzero or negative platform status on failure. + */ int init(std::shared_ptr display, adapter_t::pointer adapter_p, pix_fmt_e pix_fmt) { switch (pix_fmt) { case pix_fmt_e::nv12: @@ -823,17 +940,23 @@ namespace platf::dxgi { return 0; } + /** + * @brief D3D texture and format information for encoder input. + */ struct encoder_img_ctx_t { // Used to determine if the underlying texture changes. // Not safe for actual use by the encoder! - texture2d_t::const_pointer capture_texture_p; + texture2d_t::const_pointer capture_texture_p; ///< Capture texture p. - texture2d_t encoder_texture; - shader_res_t encoder_input_res; - keyed_mutex_t encoder_mutex; + texture2d_t encoder_texture; ///< Encoder texture. + shader_res_t encoder_input_res; ///< Encoder input res. + keyed_mutex_t encoder_mutex; ///< Encoder mutex. - std::weak_ptr img_weak; + std::weak_ptr img_weak; ///< Captured image lifetime tracked for cache cleanup. + /** + * @brief Reset the object to its initial empty state. + */ void reset() { capture_texture_p = nullptr; encoder_texture.reset(); @@ -843,6 +966,13 @@ namespace platf::dxgi { } }; + /** + * @brief Initialize encoder-side D3D resources for a captured image. + * + * @param img Image or frame object to read from or populate. + * @param img_ctx Cached encoder resources associated with the image ID. + * @return 0 when the shared texture is opened and bound; nonzero on D3D failure. + */ int initialize_image_context(const img_d3d_t &img, encoder_img_ctx_t &img_ctx) { // If we've already opened the shared texture, we're done if (img_ctx.encoder_texture && img.capture_texture.get() == img_ctx.capture_texture_p) { @@ -888,6 +1018,11 @@ namespace platf::dxgi { return 0; } + /** + * @brief Create black texture for rtv clear. + * + * @return Created black texture for rtv clear object or status. + */ shader_res_t create_black_texture_for_rtv_clear() { constexpr auto width = 32; constexpr auto height = 32; @@ -922,63 +1057,88 @@ namespace platf::dxgi { return resource_view; } - ::video::color_t *color_p; + ::video::color_t *color_p; ///< Color p. - buf_t subsample_offset; - buf_t color_matrix; + buf_t subsample_offset; ///< Subsample offset. + buf_t color_matrix; ///< Color matrix. - blend_t blend_disable; - sampler_state_t sampler_linear; + blend_t blend_disable; ///< Blend disable. + sampler_state_t sampler_linear; ///< Sampler linear. - render_target_t out_Y_or_YUV_rtv; - render_target_t out_UV_rtv; - bool rtvs_cleared = false; + render_target_t out_Y_or_YUV_rtv; ///< Out y or YUV rtv. + render_target_t out_UV_rtv; ///< Out UV rtv. + bool rtvs_cleared = false; ///< Whether render-target views have been cleared for the frame. // d3d_img_t::id -> encoder_img_ctx_t // These store the encoder textures for each img_t that passes through // convert(). We can't store them in the img_t itself because it is shared // amongst multiple hwdevice_t objects (and therefore multiple ID3D11Devices). - std::map img_ctx_map; + std::map img_ctx_map; ///< Encoder resources cached by captured image ID. - std::shared_ptr display; + std::shared_ptr display; ///< Display capture backend that supplies source textures. - vs_t convert_Y_or_YUV_vs; - ps_t convert_Y_or_YUV_ps; - ps_t convert_Y_or_YUV_fp16_ps; + vs_t convert_Y_or_YUV_vs; ///< Convert y or YUV vs. + ps_t convert_Y_or_YUV_ps; ///< Convert y or YUV ps. + ps_t convert_Y_or_YUV_fp16_ps; ///< Convert y or YUV fp16 ps. - vs_t convert_UV_vs; - ps_t convert_UV_ps; - ps_t convert_UV_fp16_ps; + vs_t convert_UV_vs; ///< Convert UV vs. + ps_t convert_UV_ps; ///< Convert UV ps. + ps_t convert_UV_fp16_ps; ///< Convert UV fp16 ps. - std::array out_Y_or_YUV_viewports; - std::array out_Y_or_YUV_viewports_for_clear; - D3D11_VIEWPORT out_UV_viewport; - D3D11_VIEWPORT out_UV_viewport_for_clear; + std::array out_Y_or_YUV_viewports; ///< Out y or YUV viewports. + std::array out_Y_or_YUV_viewports_for_clear; ///< Out y or YUV viewports for clear. + D3D11_VIEWPORT out_UV_viewport; ///< Out UV viewport. + D3D11_VIEWPORT out_UV_viewport_for_clear; ///< Out UV viewport for clear. - DXGI_FORMAT format; + DXGI_FORMAT format; ///< DXGI format required by the encoder input texture. - device_t device; - device_ctx_t device_ctx; + device_t device; ///< D3D11 device used for encoder-side texture conversion. + device_ctx_t device_ctx; ///< D3D11 device context used to issue conversion commands. - texture2d_t output_texture; + texture2d_t output_texture; ///< Output texture. }; + /** + * @brief D3D11 encode device that exposes captured textures to FFmpeg AVCodec. + */ class d3d_avcodec_encode_device_t: public avcodec_encode_device_t { public: + /** + * @brief Initialize the D3D11 AVCodec encode device. + * + * @param display Display object or identifier associated with the operation. + * @param adapter_p Adapter p. + * @param pix_fmt Sunshine pixel format to convert or allocate for. + * @return 0 on success; nonzero or negative platform status on failure. + */ int init(std::shared_ptr display, adapter_t::pointer adapter_p, pix_fmt_e pix_fmt) { int result = base.init(display, adapter_p, pix_fmt); data = base.device.get(); return result; } + /** + * @brief Convert a captured D3D texture for FFmpeg AVCodec encoding. + * + * @param img_base D3D image supplied by the capture backend. + * @return Conversion status. + */ int convert(platf::img_t &img_base) override { return base.convert(img_base); } + /** + * @brief Apply the configured colorspace metadata to the active frame. + */ void apply_colorspace() override { base.apply_colorspace(colorspace); } + /** + * @brief Configure FFmpeg hardware frames for D3D11 encoder input textures. + * + * @param frames FFmpeg hardware frames context to initialize. + */ void init_hwframes(AVHWFramesContext *frames) override { // We may be called with a QSV or D3D11VA context if (frames->device_ctx->type == AV_HWDEVICE_TYPE_D3D11VA) { @@ -993,6 +1153,12 @@ namespace platf::dxgi { frames->initial_pool_size = 1; } + /** + * @brief Prepare the D3D device before FFmpeg derives a child hardware context. + * + * @param hw_device_type FFmpeg hardware device type requested for context derivation. + * @return 0 when context derivation may continue; nonzero to abort. + */ int prepare_to_derive_context(int hw_device_type) override { // QuickSync requires our device to be multithread-protected if (hw_device_type == AV_HWDEVICE_TYPE_QSV) { @@ -1010,6 +1176,13 @@ namespace platf::dxgi { return 0; } + /** + * @brief Attach frame resources used by the next conversion or encode operation. + * + * @param frame Video or graphics frame being processed. + * @param hw_frames_ctx FFmpeg hardware frames context associated with the frame. + * @return Status from updating frame. + */ int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override { this->hwframe.reset(frame); this->frame = frame; @@ -1053,8 +1226,19 @@ namespace platf::dxgi { frame_t hwframe; }; + /** + * @brief D3D11 encode device that exposes captured textures to NVENC. + */ class d3d_nvenc_encode_device_t: public nvenc_encode_device_t { public: + /** + * @brief Create D3D11 and NVENC resources for texture-based encoding. + * + * @param display Display object or identifier associated with the operation. + * @param adapter_p Adapter p. + * @param pix_fmt Sunshine pixel format to convert or allocate for. + * @return True when the D3D11 device resources are initialized. + */ bool init_device(std::shared_ptr display, adapter_t::pointer adapter_p, pix_fmt_e pix_fmt) { buffer_format = nvenc::nvenc_format_from_sunshine_format(pix_fmt); if (buffer_format == NV_ENC_BUFFER_FORMAT_UNDEFINED) { @@ -1076,6 +1260,13 @@ namespace platf::dxgi { return true; } + /** + * @brief Initialize the platform encoder for the client stream configuration. + * + * @param client_config Client stream configuration negotiated for this session. + * @param colorspace Colorimetry information used for conversion or encoding. + * @return True when the NVENC encoder initializes for the client configuration. + */ bool init_encoder(const ::video::config_t &client_config, const ::video::sunshine_colorspace_t &colorspace) override { if (!nvenc_d3d) { return false; @@ -1090,6 +1281,12 @@ namespace platf::dxgi { return base.init_output(nvenc_d3d->get_input_texture(), client_config.width, client_config.height) == 0; } + /** + * @brief Convert a captured D3D texture for NVENC encoding. + * + * @param img_base D3D image supplied by the capture backend. + * @return Conversion status. + */ int convert(platf::img_t &img_base) override { return base.convert(img_base); } @@ -1100,6 +1297,15 @@ namespace platf::dxgi { NV_ENC_BUFFER_FORMAT buffer_format = NV_ENC_BUFFER_FORMAT_UNDEFINED; }; + /** + * @brief Set cursor texture. + * + * @param device D3D, audio, or platform device used by the operation. + * @param cursor Cursor image or visibility state to composite. + * @param cursor_img Cursor img. + * @param shape_info Shape info. + * @return True when the cursor image is uploaded to the D3D texture. + */ bool set_cursor_texture(device_t::pointer device, gpu_cursor_t &cursor, util::buffer_t &&cursor_img, DXGI_OUTDUPL_POINTER_SHAPE_INFO &shape_info) { // This cursor image may not be used if (cursor_img.size() == 0) { @@ -1638,10 +1844,6 @@ namespace platf::dxgi { /** * Get the next frame from the Windows.Graphics.Capture API and copy it into a new snapshot texture. - * @param pull_free_image_cb call this to get a new free image from the video subsystem. - * @param img_out the captured frame is returned here - * @param timeout how long to wait for the next frame - * @param cursor_visible */ capture_e display_wgc_vram_t::snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor_visible) { texture2d_t src; @@ -1938,15 +2140,22 @@ namespace platf::dxgi { return device; } + /** + * @brief Initialize global D3D11 desktop duplication support. + */ int init() { BOOST_LOG(info) << "Compiling shaders..."sv; -#define compile_vertex_shader_helper(x) \ - if (!(x##_hlsl = compile_vertex_shader(SUNSHINE_SHADERS_DIR "/" #x ".hlsl"))) \ - return -1; -#define compile_pixel_shader_helper(x) \ - if (!(x##_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/" #x ".hlsl"))) \ - return -1; +#ifndef DOXYGEN + #define compile_vertex_shader_helper(x) \ + if (!(x##_hlsl = compile_vertex_shader(SUNSHINE_SHADERS_DIR "/" #x ".hlsl"))) \ + return -1; +#endif +#ifndef DOXYGEN + #define compile_pixel_shader_helper(x) \ + if (!(x##_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/" #x ".hlsl"))) \ + return -1; +#endif compile_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps); compile_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps_linear); diff --git a/src/platform/windows/display_wgc.cpp b/src/platform/windows/display_wgc.cpp index 15cab7637..993960de5 100644 --- a/src/platform/windows/display_wgc.cpp +++ b/src/platform/windows/display_wgc.cpp @@ -29,6 +29,13 @@ namespace winrt { using namespace Windows::Graphics::DirectX::Direct3D11; extern "C" { + /** + * @brief Create direct3 D11 device from DXGI device. + * + * @param dxgiDevice DXGI device. + * @param graphicsDevice Graphics device. + * @return Created direct3 D11 device from DXGI device object or status. + */ HRESULT __stdcall CreateDirect3D11DeviceFromDXGIDevice(::IDXGIDevice *dxgiDevice, ::IInspectable **graphicsDevice); } @@ -42,6 +49,13 @@ namespace winrt { __declspec(uuid("A9B3D012-3DF2-4EE3-B8D1-8695F457D3C1")) #endif IDirect3DDxgiInterfaceAccess: ::IUnknown { + /** + * @brief Retrieve a DXGI interface from a WinRT Direct3D object. + * + * @param id COM interface ID requested from the WinRT wrapper. + * @param object Output pointer that receives the requested COM interface. + * @return HRESULT from the WinRT object's interface query. + */ virtual HRESULT __stdcall GetInterface(REFIID id, void **object) = 0; }; } // namespace winrt @@ -54,6 +68,11 @@ static constexpr GUID GUID__IDirect3DDxgiInterfaceAccess = { // compare with __declspec(uuid(...)) for the struct above. }; +/** + * @brief Return the GUID used to request IDirect3DDxgiInterfaceAccess. + * + * @return GUID for the WinRT DXGI interface-access helper. + */ template<> constexpr auto __mingw_uuidof() -> GUID const & { return GUID__IDirect3DDxgiInterfaceAccess; @@ -183,9 +202,6 @@ namespace platf::dxgi { /** * @brief Get the next frame from the producer thread. * If not available, the capture thread blocks until one is, or the wait times out. - * @param timeout how long to wait for the next frame - * @param out a texture containing the frame just captured - * @param out_time the timestamp of the frame just captured */ capture_e wgc_capture_t::next_frame(std::chrono::milliseconds timeout, ID3D11Texture2D **out, uint64_t &out_time) { // this CONSUMER runs in the capture thread @@ -248,10 +264,6 @@ namespace platf::dxgi { /** * @brief Get the next frame from the Windows.Graphics.Capture API and copy it into a new snapshot texture. - * @param pull_free_image_cb call this to get a new free image from the video subsystem. - * @param img_out the captured frame is returned here - * @param timeout how long to wait for the next frame - * @param cursor_visible whether to capture the cursor */ capture_e display_wgc_ram_t::snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor_visible) { HRESULT status; diff --git a/src/platform/windows/input.cpp b/src/platform/windows/input.cpp index f7fb32850..7fcbc3a2e 100644 --- a/src/platform/windows/input.cpp +++ b/src/platform/windows/input.cpp @@ -2,7 +2,16 @@ * @file src/platform/windows/input.cpp * @brief Definitions for input handling on Windows. */ -#define WINVER 0x0A00 +#ifndef DOXYGEN + #define WINVER 0x0A00 +#endif +#ifdef DOXYGEN + /** + * @def CALLBACK + * @brief Windows callback calling convention marker. + */ + #define CALLBACK +#endif // platform includes #include @@ -26,8 +35,11 @@ namespace platf { using namespace std::literals; - thread_local HDESK _lastKnownInputDesktop = nullptr; + thread_local HDESK _lastKnownInputDesktop = nullptr; ///< Last known input desktop. + /** + * @brief Target touch port. + */ constexpr touch_port_t target_touch_port { 0, 0, @@ -35,9 +47,24 @@ namespace platf { 65535 }; + /** + * @brief ViGEm client pointer released with `vigem_free`. + */ using client_t = util::safe_ptr<_VIGEM_CLIENT_T, vigem_free>; + /** + * @brief ViGEm target pointer released with `vigem_target_free`. + */ using target_t = util::safe_ptr<_VIGEM_TARGET_T, vigem_target_free>; + /** + * @brief Handle Xbox 360 virtual gamepad notification events. + * + * @param client ViGEm client. + * @param target ViGEm target. + * @param largeMotor Large motor strength. + * @param smallMotor Small motor strength. + * @param userdata User data pointer. + */ void CALLBACK x360_notify( client_t::pointer client, target_t::pointer target, @@ -47,6 +74,16 @@ namespace platf { void *userdata ); + /** + * @brief Handle DualShock 4 virtual gamepad notification events. + * + * @param client ViGEm client. + * @param target ViGEm target. + * @param largeMotor Large motor strength. + * @param smallMotor Small motor strength. + * @param led_color Requested lightbar color. + * @param userdata User data pointer. + */ void CALLBACK ds4_notify( client_t::pointer client, target_t::pointer target, @@ -56,41 +93,62 @@ namespace platf { void *userdata ); + /** + * @brief Last touch coordinates reported by a virtual gamepad. + */ struct gp_touch_context_t { - uint8_t pointerIndex; - uint16_t x; - uint16_t y; + uint8_t pointerIndex; ///< Pointer index. + uint16_t x; ///< X. + uint16_t y; ///< Y. }; + /** + * @brief ViGEm target and report buffers for one virtual gamepad. + */ struct gamepad_context_t { - target_t gp; - feedback_queue_t feedback_queue; + target_t gp; ///< Gp. + feedback_queue_t feedback_queue; ///< Feedback queue. union { XUSB_REPORT x360; DS4_REPORT_EX ds4; - } report; + } report; ///< Current HID report for the virtual controller.. // Map from pointer ID to pointer index - std::map pointer_id_map; - uint8_t available_pointers; + std::map pointer_id_map; ///< Pointer ID map. + uint8_t available_pointers; ///< Available pointers. - uint8_t client_relative_index; + uint8_t client_relative_index; ///< Client relative index. - thread_pool_util::ThreadPool::task_id_t repeat_task {}; - std::chrono::steady_clock::time_point last_report_ts; + thread_pool_util::ThreadPool::task_id_t repeat_task {}; ///< Repeat task. + std::chrono::steady_clock::time_point last_report_ts; ///< Last report ts. - gamepad_feedback_msg_t last_rumble; - gamepad_feedback_msg_t last_rgb_led; + gamepad_feedback_msg_t last_rumble; ///< Last rumble. + gamepad_feedback_msg_t last_rgb_led; ///< Last RGB led. }; - constexpr float EARTH_G = 9.80665f; + constexpr float EARTH_G = 9.80665f; ///< Meters per second squared represented by one gravity unit. +/** + * @def MPS2_TO_DS4_ACCEL(x) + * @brief Macro for MPS2 TO DS4 ACCEL. + */ #define MPS2_TO_DS4_ACCEL(x) (int32_t) (((x) / EARTH_G) * 8192) +/** + * @def DPS_TO_DS4_GYRO(x) + * @brief Macro for DPS TO DS4 GYRO. + */ #define DPS_TO_DS4_GYRO(x) (int32_t) ((x) * (1024 / 64)) +/** + * @def APPLY_CALIBRATION(val, bias, scale) + * @brief Macro for APPLY CALIBRATION. + */ #define APPLY_CALIBRATION(val, bias, scale) (int32_t) (((float) (val) + (bias)) / (scale)) + /** + * @brief DS4 touch unused. + */ constexpr DS4_TOUCH ds4_touch_unused = { .bPacketCounter = 0, .bIsUpTrackingNum1 = 0x80, @@ -100,6 +158,9 @@ namespace platf { }; // See https://github.com/ViGEm/ViGEmBus/blob/22835473d17fbf0c4d4bb2f2d42fd692b6e44df4/sys/Ds4Pdo.cpp#L153-L164 + /** + * @brief DS4 report init ex. + */ constexpr DS4_REPORT_EX ds4_report_init_ex = { {{.bThumbLX = 0x80, .bThumbLY = 0x80, @@ -191,8 +252,16 @@ namespace platf { } } + /** + * @brief ViGEm client connection and virtual gamepad collection. + */ class vigem_t { public: + /** + * @brief Connect to ViGEm and prepare virtual gamepad slots. + * + * @return 0 on success; nonzero or negative platform status on failure. + */ int init() { // Probe ViGEm during startup to see if we can successfully attach gamepads. This will allow us to // immediately display the error message in the web UI even before the user tries to stream. @@ -375,7 +444,7 @@ namespace platf { } /** - * @brief vigem_t destructor. + * @brief Detach all virtual gamepads and disconnect from the ViGEm client. */ ~vigem_t() { if (client) { @@ -392,9 +461,9 @@ namespace platf { } } - std::vector gamepads; + std::vector gamepads; ///< Virtual gamepads owned by this ViGEm connection. - client_t client; + client_t client; ///< ViGEm client connection used to create virtual gamepads. }; void CALLBACK x360_notify( @@ -431,16 +500,19 @@ namespace platf { task_pool.push(&vigem_t::set_rgb_led, (vigem_t *) userdata, target, led_color.Red, led_color.Green, led_color.Blue); } + /** + * @brief Global inputtino device handles shared by clients. + */ struct input_raw_t { ~input_raw_t() { delete vigem; } - vigem_t *vigem; + vigem_t *vigem; ///< Vigem. - decltype(CreateSyntheticPointerDevice) *fnCreateSyntheticPointerDevice; - decltype(InjectSyntheticPointerInput) *fnInjectSyntheticPointerInput; - decltype(DestroySyntheticPointerDevice) *fnDestroySyntheticPointerDevice; + decltype(CreateSyntheticPointerDevice) *fnCreateSyntheticPointerDevice; ///< Fn create synthetic pointer device. + decltype(InjectSyntheticPointerInput) *fnInjectSyntheticPointerInput; ///< Fn inject synthetic pointer input. + decltype(DestroySyntheticPointerDevice) *fnDestroySyntheticPointerDevice; ///< Fn destroy synthetic pointer device. }; input_t input() { @@ -653,7 +725,15 @@ namespace platf { send_input(i); } + /** + * @brief Per-client inputtino devices for touch and pen input. + */ struct client_input_raw_t: public client_input_t { + /** + * @brief Create per-client raw input devices for touch and pen events. + * + * @param input Platform input backend that receives the event. + */ client_input_raw_t(input_t &input) { global = (input_raw_t *) input.get(); } @@ -681,14 +761,14 @@ namespace platf { // pen/touch events. To maintain separation, we expose separate pen and touch devices // for each client. - HSYNTHETICPOINTERDEVICE pen {}; - POINTER_TYPE_INFO penInfo {}; - thread_pool_util::ThreadPool::task_id_t penRepeatTask {}; + HSYNTHETICPOINTERDEVICE pen {}; ///< Windows synthetic pointer device used for pen events. + POINTER_TYPE_INFO penInfo {}; ///< Pen info. + thread_pool_util::ThreadPool::task_id_t penRepeatTask {}; ///< Pen repeat task. - HSYNTHETICPOINTERDEVICE touch {}; - POINTER_TYPE_INFO touchInfo[10] {}; - UINT32 activeTouchSlots {}; - thread_pool_util::ThreadPool::task_id_t touchRepeatTask {}; + HSYNTHETICPOINTERDEVICE touch {}; ///< Windows synthetic pointer device used for touch events. + POINTER_TYPE_INFO touchInfo[10] {}; ///< Touch info. + UINT32 activeTouchSlots {}; ///< Active touch slots. + thread_pool_util::ThreadPool::task_id_t touchRepeatTask {}; ///< Touch repeat task. }; /** @@ -830,7 +910,7 @@ namespace platf { // Active pointer interactions sent via InjectSyntheticPointerInput() seem to be automatically // cancelled by Windows if not repeated/updated within about a second. To avoid this, refresh // the injected input periodically. - constexpr auto ISPI_REPEAT_INTERVAL = 50ms; + constexpr auto ISPI_REPEAT_INTERVAL = 50ms; ///< Protocol or platform constant for ispi repeat interval. /** * @brief Repeats the current touch state to avoid the interactions timing out. @@ -890,7 +970,7 @@ namespace platf { } // These are edge-triggered pointer state flags that should always be cleared next frame - constexpr auto EDGE_TRIGGERED_POINTER_FLAGS = POINTER_FLAG_DOWN | POINTER_FLAG_UP | POINTER_FLAG_CANCELED | POINTER_FLAG_UPDATE; + constexpr auto EDGE_TRIGGERED_POINTER_FLAGS = POINTER_FLAG_DOWN | POINTER_FLAG_UP | POINTER_FLAG_CANCELED | POINTER_FLAG_UPDATE; ///< Protocol or platform constant for edge triggered pointer flags. /** * @brief Sends a touch event to the OS. @@ -1288,7 +1368,7 @@ namespace platf { } /** - * @brief Updates the X360 input report with the provided gamepad state. + * @brief Update an X360 report from Sunshine gamepad state. * @param gamepad The gamepad to update. * @param gamepad_state The gamepad button/axis state sent from the client. */ @@ -1418,7 +1498,7 @@ namespace platf { } /** - * @brief Updates the DS4 input report with the provided gamepad state. + * @brief Update a DS4 report from Sunshine gamepad state. * @param gamepad The gamepad to update. * @param gamepad_state The gamepad button/axis state sent from the client. */ @@ -1474,7 +1554,7 @@ namespace platf { } /** - * @brief Updates virtual gamepad with the provided gamepad state. + * @brief Submit updated Sunshine gamepad state to the virtual device. * @param input The input context. * @param nr The gamepad index to update. * @param gamepad_state The gamepad button/axis state sent from the client. diff --git a/src/platform/windows/keylayout.h b/src/platform/windows/keylayout.h index 2d362ef2c..ac0a4fa20 100644 --- a/src/platform/windows/keylayout.h +++ b/src/platform/windows/keylayout.h @@ -11,6 +11,9 @@ namespace platf { // Virtual Key to Scan Code mapping for the US English layout (00000409). // GameStream uses this as the canonical key layout for scancode conversion. + /** + * @brief Protocol or platform constant for vk to scancode map. + */ constexpr std::array::max() + 1> VK_TO_SCANCODE_MAP { 0, /* 0x00 */ 0, /* 0x01 */ diff --git a/src/platform/windows/misc.cpp b/src/platform/windows/misc.cpp index 32bfef159..ee2ec7af5 100644 --- a/src/platform/windows/misc.cpp +++ b/src/platform/windows/misc.cpp @@ -35,6 +35,10 @@ // Boost overrides NTDDI_VERSION, so we re-override it here #undef NTDDI_VERSION +/** + * @def NTDDI_VERSION + * @brief Macro for NTDDI VERSION. + */ #define NTDDI_VERSION NTDDI_WIN10 #include @@ -50,22 +54,42 @@ // UDP_SEND_MSG_SIZE was added in the Windows 10 20H1 SDK #ifndef UDP_SEND_MSG_SIZE + /** + * @def UDP_SEND_MSG_SIZE + * @brief Macro for UDP SEND MSG SIZE. + */ #define UDP_SEND_MSG_SIZE 2 #endif // PROC_THREAD_ATTRIBUTE_JOB_LIST is currently missing from MinGW headers #ifndef PROC_THREAD_ATTRIBUTE_JOB_LIST + /** + * @def PROC_THREAD_ATTRIBUTE_JOB_LIST + * @brief Macro for PROC THREAD ATTRIBUTE JOB LIST. + */ #define PROC_THREAD_ATTRIBUTE_JOB_LIST ProcThreadAttributeValue(13, FALSE, TRUE, FALSE) #endif #include #ifndef WLAN_API_MAKE_VERSION + /** + * @def WLAN_API_MAKE_VERSION(_major, _minor) + * @brief Macro for WLAN API MAKE VERSION. + */ #define WLAN_API_MAKE_VERSION(_major, _minor) (((DWORD) (_minor)) << 16 | (_major)) #endif #include extern "C" { + /** + * @brief Dynamically resolve NtSetTimerResolution from ntdll. + * + * @param DesiredResolution Desired resolution. + * @param SetResolution Set resolution. + * @param CurrentResolution Current resolution. + * @return NTSTATUS reported by the system timer-resolution request. + */ NTSTATUS NTAPI NtSetTimerResolution(ULONG DesiredResolution, BOOLEAN SetResolution, PULONG CurrentResolution); } @@ -100,24 +124,27 @@ namespace bp = boost::process::v1; using namespace std::literals; namespace platf { + /** + * @brief Owning pointer for `GetAdaptersAddresses` results. + */ using adapteraddrs_t = util::c_ptr; - bool enabled_mouse_keys = false; - MOUSEKEYS previous_mouse_keys_state; + bool enabled_mouse_keys = false; ///< Tracks whether Windows Mouse Keys was enabled before Sunshine changed it. + MOUSEKEYS previous_mouse_keys_state; ///< Previous mouse keys state. - HANDLE qos_handle = nullptr; + HANDLE qos_handle = nullptr; ///< QoS handle. - decltype(QOSCreateHandle) *fn_QOSCreateHandle = nullptr; - decltype(QOSAddSocketToFlow) *fn_QOSAddSocketToFlow = nullptr; - decltype(QOSRemoveSocketFromFlow) *fn_QOSRemoveSocketFromFlow = nullptr; + decltype(QOSCreateHandle) *fn_QOSCreateHandle = nullptr; ///< Fn QoS create handle. + decltype(QOSAddSocketToFlow) *fn_QOSAddSocketToFlow = nullptr; ///< Fn QoS add socket to flow. + decltype(QOSRemoveSocketFromFlow) *fn_QOSRemoveSocketFromFlow = nullptr; ///< Fn QoS remove socket from flow. - HANDLE wlan_handle = nullptr; + HANDLE wlan_handle = nullptr; ///< Wlan handle. - decltype(WlanOpenHandle) *fn_WlanOpenHandle = nullptr; - decltype(WlanCloseHandle) *fn_WlanCloseHandle = nullptr; - decltype(WlanFreeMemory) *fn_WlanFreeMemory = nullptr; - decltype(WlanEnumInterfaces) *fn_WlanEnumInterfaces = nullptr; - decltype(WlanSetInterface) *fn_WlanSetInterface = nullptr; + decltype(WlanOpenHandle) *fn_WlanOpenHandle = nullptr; ///< Fn wlan open handle. + decltype(WlanCloseHandle) *fn_WlanCloseHandle = nullptr; ///< Fn wlan close handle. + decltype(WlanFreeMemory) *fn_WlanFreeMemory = nullptr; ///< Fn wlan free memory. + decltype(WlanEnumInterfaces) *fn_WlanEnumInterfaces = nullptr; ///< Fn wlan enum interfaces. + decltype(WlanSetInterface) *fn_WlanSetInterface = nullptr; ///< Fn wlan set interface. std::filesystem::path appdata() { WCHAR sunshine_path[MAX_PATH]; @@ -154,6 +181,11 @@ namespace platf { return {port, std::string {data}}; } + /** + * @brief Read Windows adapter addresses with automatic buffer sizing. + * + * @return Adapter-address list populated by GetAdaptersAddresses. + */ adapteraddrs_t get_adapteraddrs() { adapteraddrs_t info {nullptr}; ULONG size = 0; @@ -186,6 +218,9 @@ namespace platf { return "00:00:00:00:00:00"s; } + /** + * @brief Synchronize thread desktop. + */ HDESK syncThreadDesktop() { auto hDesk = OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK, FALSE, GENERIC_ALL); if (!hDesk) { @@ -205,6 +240,9 @@ namespace platf { return hDesk; } + /** + * @brief Write status details to the log. + */ void print_status(const std::string_view &prefix, HRESULT status) { char err_string[1024]; @@ -213,6 +251,12 @@ namespace platf { BOOST_LOG(error) << prefix << ": "sv << std::string_view {err_string, bytes}; } + /** + * @brief Check whether a Windows access token belongs to an administrator. + * + * @param user_token Windows access token to inspect. + * @return True when the inspected token has administrator privileges. + */ bool IsUserAdmin(HANDLE user_token) { WINBOOL ret; SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY; @@ -246,6 +290,8 @@ namespace platf { /** * @brief Obtain the current sessions user's primary token with elevated privileges. * @return The user's token. If user has admin capability it will be elevated, otherwise it will be a limited token. On error, `nullptr`. + * + * @param elevated Whether the command should run with elevated privileges. */ HANDLE retrieve_users_token(bool elevated) { DWORD consoleSessionId; @@ -308,6 +354,13 @@ namespace platf { return userToken; } + /** + * @brief Merge user environment variables into a Windows environment block. + * + * @param env Environment variables for the child process. + * @param shell_token Shell token. + * @return True when the user environment block was merged into `env`. + */ bool merge_user_environment_block(bp::environment &env, HANDLE shell_token) { // Get the target user's environment block PVOID env_block; @@ -378,11 +431,24 @@ namespace platf { } // Note: This does NOT append a null terminator + /** + * @brief Append a null-terminated string to a Windows environment block. + * + * @param env_block Env block. + * @param offset Byte offset used when converting from the wide string buffer. + * @param wstr Wide-character string being converted to UTF-8. + */ void append_string_to_environment_block(wchar_t *env_block, int &offset, const std::wstring &wstr) { std::memcpy(&env_block[offset], wstr.data(), wstr.length() * sizeof(wchar_t)); offset += wstr.length(); } + /** + * @brief Create environment block. + * + * @param env Environment variables for the child process. + * @return Created environment block object or status. + */ std::wstring create_environment_block(const bp::environment &env) { int size = 0; for (const auto &entry : env) { @@ -415,6 +481,12 @@ namespace platf { return std::wstring(env_block.data(), offset); } + /** + * @brief Allocate and initialize a Windows process-thread attribute list. + * + * @param attribute_count Attribute count. + * @return Initialized attribute list, or nullptr when allocation fails. + */ LPPROC_THREAD_ATTRIBUTE_LIST allocate_proc_thread_attr_list(DWORD attribute_count) { SIZE_T size; InitializeProcThreadAttributeList(nullptr, attribute_count, 0, &size); @@ -432,6 +504,11 @@ namespace platf { return list; } + /** + * @brief Release proc thread attr list resources. + * + * @param list Multi-string list returned by the Windows API. + */ void free_proc_thread_attr_list(LPPROC_THREAD_ATTRIBUTE_LIST list) { DeleteProcThreadAttributeList(list); HeapFree(GetProcessHeap(), 0, list); @@ -1255,9 +1332,12 @@ namespace platf { return _putenv_s(name.c_str(), ""); } + /** + * @brief Stores state while enumerating top-level Windows windows. + */ struct enum_wnd_context_t { - std::set process_ids; - bool requested_exit; + std::set process_ids; ///< Process ids. + bool requested_exit; ///< Whether a close request was observed while enumerating windows. }; static BOOL CALLBACK prgrp_enum_windows(HWND hwnd, LPARAM lParam) { @@ -1549,8 +1629,16 @@ namespace platf { return true; } + /** + * @brief Owns platform QoS state that is restored during cleanup. + */ class qos_t: public deinit_t { public: + /** + * @brief Store a Windows QoS flow ID for cleanup on destruction. + * + * @param flow_id Flow ID. + */ qos_t(QOS_FLOWID flow_id): flow_id(flow_id) { } @@ -1568,11 +1656,6 @@ namespace platf { /** * @brief Enables QoS on the given socket for traffic to the specified destination. - * @param native_socket The native socket handle. - * @param address The destination address for traffic sent on this socket. - * @param port The destination port for traffic sent on this socket. - * @param data_type The type of traffic sent on this socket. - * @param dscp_tagging Specifies whether to enable DSCP tagging on outgoing traffic. */ std::unique_ptr enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type, bool dscp_tagging) { SOCKADDR_IN saddr_v4; @@ -1683,6 +1766,9 @@ namespace platf { return std::make_unique(flow_id); } + /** + * @brief Read the current Windows high-resolution performance counter. + */ int64_t qpc_counter() { LARGE_INTEGER performance_counter; if (QueryPerformanceCounter(&performance_counter)) { @@ -1691,6 +1777,9 @@ namespace platf { return 0; } + /** + * @brief Convert the difference between two QPC readings to nanoseconds. + */ std::chrono::nanoseconds qpc_time_difference(int64_t performance_counter1, int64_t performance_counter2) { auto get_frequency = []() { LARGE_INTEGER frequency; @@ -1714,6 +1803,9 @@ namespace platf { return utf_utils::to_utf8(hostname); } + /** + * @brief Implements the high-precision timer using Win32 waitable timers. + */ class win32_high_precision_timer: public high_precision_timer { public: win32_high_precision_timer() { diff --git a/src/platform/windows/misc.h b/src/platform/windows/misc.h index 198c1b2bd..ac866f281 100644 --- a/src/platform/windows/misc.h +++ b/src/platform/windows/misc.h @@ -15,11 +15,34 @@ #include namespace platf { + /** + * @brief Write status details to the log. + * + * @param prefix Text prefix used when formatting the message. + * @param status Native status code returned by the platform API. + */ void print_status(const std::string_view &prefix, HRESULT status); + /** + * @brief Synchronize thread desktop. + * + * @return true when the thread desktop was synchronized successfully. + */ HDESK syncThreadDesktop(); + /** + * @brief Read the current Windows high-resolution performance counter. + * + * @return Raw QPC tick value from `QueryPerformanceCounter`. + */ int64_t qpc_counter(); + /** + * @brief Convert the difference between two QPC readings to nanoseconds. + * + * @param performance_counter1 Newer performance-counter reading. + * @param performance_counter2 Older performance-counter reading. + * @return Duration represented by the difference between two QPC values. + */ std::chrono::nanoseconds qpc_time_difference(int64_t performance_counter1, int64_t performance_counter2); /** diff --git a/src/platform/windows/nvprefs/driver_settings.h b/src/platform/windows/nvprefs/driver_settings.h index f2957156b..7e1f4b214 100644 --- a/src/platform/windows/nvprefs/driver_settings.h +++ b/src/platform/windows/nvprefs/driver_settings.h @@ -36,22 +36,61 @@ namespace nvprefs { + /** + * @brief NVIDIA driver profile settings loaded for inspection or modification. + */ class driver_settings_t { public: ~driver_settings_t(); + /** + * @brief Load NVIDIA profile settings for the current driver state. + * + * @return True when the NVIDIA driver-settings operation succeeds. + */ bool init(); + /** + * @brief Destroy the native resource owned by the wrapper. + */ void destroy(); + /** + * @brief Load settings data from the backing API or store. + * + * @return True when the NVIDIA driver-settings operation succeeds. + */ bool load_settings(); + /** + * @brief Save settings data through the backing API or store. + * + * @return True when the NVIDIA driver-settings operation succeeds. + */ bool save_settings(); + /** + * @brief Restore global NVIDIA profile settings from undo data. + * + * @param undo_data Driver settings captured before Sunshine modified them. + * @return True when the NVIDIA driver-settings operation succeeds. + */ bool restore_global_profile_to_undo(const undo_data_t &undo_data); + /** + * @brief Compare and update global NVIDIA profile settings. + * + * @param undo_data Driver settings captured before Sunshine modified them. + * @return True when the NVIDIA driver-settings operation succeeds. + */ bool check_and_modify_global_profile(std::optional &undo_data); + /** + * @brief Compare and update Sunshine NVIDIA application profile settings. + * + * @param modified Whether NVIDIA driver settings were changed and need saving. + * @return True when the NVIDIA driver-settings operation succeeds. + */ bool check_and_modify_application_profile(bool &modified); private: diff --git a/src/platform/windows/nvprefs/nvapi_opensource_wrapper.cpp b/src/platform/windows/nvprefs/nvapi_opensource_wrapper.cpp index d03c6bbb0..12d78a71a 100644 --- a/src/platform/windows/nvprefs/nvapi_opensource_wrapper.cpp +++ b/src/platform/windows/nvprefs/nvapi_opensource_wrapper.cpp @@ -31,11 +31,27 @@ namespace { } // namespace #undef NVAPI_INTERFACE +/** + * @def NVAPI_INTERFACE + * @brief Macro for NVAPI INTERFACE. + */ #define NVAPI_INTERFACE NvAPI_Status __cdecl +/** + * @brief NVAPI export used to resolve function pointers by interface ID. + * + * @param id NVAPI interface ID from the generated interface table. + * @return Function pointer for the requested NVAPI interface, or nullptr. + */ extern void *__cdecl nvapi_QueryInterface(NvU32 id); NVAPI_INTERFACE + +/** + * @brief Load NVAPI and resolve the subset of interfaces used by Sunshine. + * + * @return NVAPI_OK on success, or NVAPI_LIBRARY_NOT_FOUND when the DLL cannot be loaded. + */ NvAPI_Initialize() { if (dll) { return NVAPI_OK; @@ -60,6 +76,11 @@ NvAPI_Initialize() { return NVAPI_LIBRARY_NOT_FOUND; } +/** + * @brief Unload NVAPI and clear all resolved interface pointers. + * + * @return NVAPI_OK after cleanup. + */ NVAPI_INTERFACE NvAPI_Unload() { if (dll) { interfaces.clear(); @@ -69,56 +90,152 @@ NVAPI_INTERFACE NvAPI_Unload() { return NVAPI_OK; } +/** + * @brief Forward NvAPI_GetErrorMessage to the loaded NVAPI DLL. + * + * @param nr Controller index assigned by the client. + * @param szDesc Output buffer receiving the human-readable error string. + * @return NVAPI status returned by the loaded function. + */ NVAPI_INTERFACE NvAPI_GetErrorMessage(NvAPI_Status nr, NvAPI_ShortString szDesc) { return call_interface("NvAPI_GetErrorMessage", nr, szDesc); } // This is only a subset of NvAPI_DRS_* functions, more can be added if needed +/** + * @brief Forward NvAPI_DRS_CreateSession to the loaded NVAPI DLL. + * + * @param phSession Output handle for the created DRS session. + * @return NVAPI status returned by the loaded function. + */ NVAPI_INTERFACE NvAPI_DRS_CreateSession(NvDRSSessionHandle *phSession) { return call_interface("NvAPI_DRS_CreateSession", phSession); } +/** + * @brief Forward NvAPI_DRS_DestroySession to the loaded NVAPI DLL. + * + * @param hSession DRS session handle to destroy. + * @return NVAPI status returned by the loaded function. + */ NVAPI_INTERFACE NvAPI_DRS_DestroySession(NvDRSSessionHandle hSession) { return call_interface("NvAPI_DRS_DestroySession", hSession); } +/** + * @brief Forward NvAPI_DRS_LoadSettings to the loaded NVAPI DLL. + * + * @param hSession DRS session whose settings are loaded. + * @return NVAPI status returned by the loaded function. + */ NVAPI_INTERFACE NvAPI_DRS_LoadSettings(NvDRSSessionHandle hSession) { return call_interface("NvAPI_DRS_LoadSettings", hSession); } +/** + * @brief Forward NvAPI_DRS_SaveSettings to the loaded NVAPI DLL. + * + * @param hSession DRS session whose settings are saved. + * @return NVAPI status returned by the loaded function. + */ NVAPI_INTERFACE NvAPI_DRS_SaveSettings(NvDRSSessionHandle hSession) { return call_interface("NvAPI_DRS_SaveSettings", hSession); } +/** + * @brief Forward NvAPI_DRS_CreateProfile to the loaded NVAPI DLL. + * + * @param hSession DRS session that owns the profile. + * @param pProfileInfo Profile definition to create. + * @param phProfile Output handle for the created profile. + * @return NVAPI status returned by the loaded function. + */ NVAPI_INTERFACE NvAPI_DRS_CreateProfile(NvDRSSessionHandle hSession, NVDRS_PROFILE *pProfileInfo, NvDRSProfileHandle *phProfile) { return call_interface("NvAPI_DRS_CreateProfile", hSession, pProfileInfo, phProfile); } +/** + * @brief Forward NvAPI_DRS_FindProfileByName to the loaded NVAPI DLL. + * + * @param hSession DRS session to search. + * @param profileName Profile name. + * @param phProfile Output handle for the matched profile. + * @return NVAPI status returned by the loaded function. + */ NVAPI_INTERFACE NvAPI_DRS_FindProfileByName(NvDRSSessionHandle hSession, NvAPI_UnicodeString profileName, NvDRSProfileHandle *phProfile) { return call_interface("NvAPI_DRS_FindProfileByName", hSession, profileName, phProfile); } +/** + * @brief Forward NvAPI_DRS_CreateApplication to the loaded NVAPI DLL. + * + * @param hSession DRS session that owns the profile. + * @param hProfile Profile receiving the application entry. + * @param pApplication Application entry to create. + * @return NVAPI status returned by the loaded function. + */ NVAPI_INTERFACE NvAPI_DRS_CreateApplication(NvDRSSessionHandle hSession, NvDRSProfileHandle hProfile, NVDRS_APPLICATION *pApplication) { return call_interface("NvAPI_DRS_CreateApplication", hSession, hProfile, pApplication); } +/** + * @brief Forward NvAPI_DRS_GetApplicationInfo to the loaded NVAPI DLL. + * + * @param hSession DRS session that owns the profile. + * @param hProfile Profile containing the application entry. + * @param appName App name. + * @param pApplication Output application information. + * @return NVAPI status returned by the loaded function. + */ NVAPI_INTERFACE NvAPI_DRS_GetApplicationInfo(NvDRSSessionHandle hSession, NvDRSProfileHandle hProfile, NvAPI_UnicodeString appName, NVDRS_APPLICATION *pApplication) { return call_interface("NvAPI_DRS_GetApplicationInfo", hSession, hProfile, appName, pApplication); } +/** + * @brief Forward NvAPI_DRS_SetSetting to the loaded NVAPI DLL. + * + * @param hSession DRS session that owns the profile. + * @param hProfile Profile receiving the setting. + * @param pSetting Setting value to apply. + * @return NVAPI status returned by the loaded function. + */ NVAPI_INTERFACE NvAPI_DRS_SetSetting(NvDRSSessionHandle hSession, NvDRSProfileHandle hProfile, NVDRS_SETTING *pSetting) { return call_interface("NvAPI_DRS_SetSetting", hSession, hProfile, pSetting); } +/** + * @brief Forward NvAPI_DRS_GetSetting to the loaded NVAPI DLL. + * + * @param hSession DRS session that owns the profile. + * @param hProfile Profile containing the setting. + * @param settingId Setting ID. + * @param pSetting Output setting value. + * @return NVAPI status returned by the loaded function. + */ NVAPI_INTERFACE NvAPI_DRS_GetSetting(NvDRSSessionHandle hSession, NvDRSProfileHandle hProfile, NvU32 settingId, NVDRS_SETTING *pSetting) { return call_interface("NvAPI_DRS_GetSetting", hSession, hProfile, settingId, pSetting); } +/** + * @brief Forward NvAPI_DRS_DeleteProfileSetting to the loaded NVAPI DLL. + * + * @param hSession DRS session that owns the profile. + * @param hProfile Profile containing the setting. + * @param settingId Setting ID. + * @return NVAPI status returned by the loaded function. + */ NVAPI_INTERFACE NvAPI_DRS_DeleteProfileSetting(NvDRSSessionHandle hSession, NvDRSProfileHandle hProfile, NvU32 settingId) { return call_interface("NvAPI_DRS_DeleteProfileSetting", hSession, hProfile, settingId); } +/** + * @brief Forward NvAPI_DRS_GetBaseProfile to the loaded NVAPI DLL. + * + * @param hSession DRS session to query. + * @param phProfile Output handle for the base profile. + * @return NVAPI status returned by the loaded function. + */ NVAPI_INTERFACE NvAPI_DRS_GetBaseProfile(NvDRSSessionHandle hSession, NvDRSProfileHandle *phProfile) { return call_interface("NvAPI_DRS_GetBaseProfile", hSession, phProfile); } diff --git a/src/platform/windows/nvprefs/nvprefs_common.cpp b/src/platform/windows/nvprefs/nvprefs_common.cpp index 649ef60cc..53af20320 100644 --- a/src/platform/windows/nvprefs/nvprefs_common.cpp +++ b/src/platform/windows/nvprefs/nvprefs_common.cpp @@ -11,22 +11,37 @@ namespace nvprefs { + /** + * @brief Forward an informational NVPrefs message to the logger. + */ void info_message(const std::wstring &message) { BOOST_LOG(info) << "nvprefs: " << message; } + /** + * @brief Forward an informational NVPrefs message to the logger. + */ void info_message(const std::string &message) { BOOST_LOG(info) << "nvprefs: " << message; } + /** + * @brief Forward an NVPrefs error message to the logger. + */ void error_message(const std::wstring &message) { BOOST_LOG(error) << "nvprefs: " << message; } + /** + * @brief Forward an NVPrefs error message to the logger. + */ void error_message(const std::string &message) { BOOST_LOG(error) << "nvprefs: " << message; } + /** + * @brief Get nvprefs options. + */ nvprefs_options get_nvprefs_options() { nvprefs_options options; options.opengl_vulkan_on_dxgi = config::video.nv_opengl_vulkan_on_dxgi; diff --git a/src/platform/windows/nvprefs/nvprefs_common.h b/src/platform/windows/nvprefs/nvprefs_common.h index 61a7a6eb0..f422f159b 100644 --- a/src/platform/windows/nvprefs/nvprefs_common.h +++ b/src/platform/windows/nvprefs/nvprefs_common.h @@ -16,39 +16,87 @@ namespace nvprefs { + /** + * @brief Owning Windows HANDLE wrapper that closes handles automatically. + */ struct safe_handle: public util::safe_ptr_v2 { using util::safe_ptr_v2::safe_ptr_v2; + /** + * @brief Check whether the wrapped Windows handle is valid. + */ explicit operator bool() const { auto handle = get(); return handle != nullptr && handle != INVALID_HANDLE_VALUE; } }; + /** + * @brief Deleter for memory allocated by Windows LocalAlloc APIs. + */ struct safe_hlocal_deleter { + /** + * @brief Free memory allocated by Windows local-memory APIs. + * + * @param p LocalAlloc-compatible pointer to release. + */ void operator()(void *p) { LocalFree(p); } }; + /** + * @brief Unique pointer for memory released with `LocalFree`. + */ template using safe_hlocal = util::uniq_ptr, safe_hlocal_deleter>; + /** + * @brief Safe pointer for Windows security identifiers released with `FreeSid`. + */ using safe_sid = util::safe_ptr_v2; + /** + * @brief Forward an informational NVPrefs message to the logger. + * + * @param message Message text to log or report. + */ void info_message(const std::wstring &message); + /** + * @brief Forward an informational NVPrefs message to the logger. + * + * @param message Message text to log or report. + */ void info_message(const std::string &message); + /** + * @brief Forward an NVPrefs error message to the logger. + * + * @param message Message text to log or report. + */ void error_message(const std::wstring &message); + /** + * @brief Forward an NVPrefs error message to the logger. + * + * @param message Message text to log or report. + */ void error_message(const std::string &message); + /** + * @brief Parsed command-line options for the NVIDIA preferences helper. + */ struct nvprefs_options { - bool opengl_vulkan_on_dxgi = true; - bool sunshine_high_power_mode = true; + bool opengl_vulkan_on_dxgi = true; ///< Whether NVIDIA OpenGL/Vulkan-on-DXGI should be enabled. + bool sunshine_high_power_mode = true; ///< Whether NVIDIA high-power mode should be enabled for Sunshine. }; + /** + * @brief Get nvprefs options. + * + * @return Parsed command-line options for NVIDIA profile preference handling. + */ nvprefs_options get_nvprefs_options(); } // namespace nvprefs diff --git a/src/platform/windows/nvprefs/nvprefs_interface.cpp b/src/platform/windows/nvprefs/nvprefs_interface.cpp index 632ffa45b..b34b0b4b7 100644 --- a/src/platform/windows/nvprefs/nvprefs_interface.cpp +++ b/src/platform/windows/nvprefs/nvprefs_interface.cpp @@ -19,13 +19,16 @@ namespace { namespace nvprefs { + /** + * @brief Private state owned by the NVIDIA preferences interface. + */ struct nvprefs_interface::impl { - bool loaded = false; - driver_settings_t driver_settings; - std::filesystem::path undo_folder_path; - std::filesystem::path undo_file_path; - std::optional undo_data; - std::optional undo_file; + bool loaded = false; ///< Whether NVIDIA preference state has been loaded. + driver_settings_t driver_settings; ///< Driver settings. + std::filesystem::path undo_folder_path; ///< Undo folder path. + std::filesystem::path undo_file_path; ///< Undo file path. + std::optional undo_data; ///< Undo data. + std::optional undo_file; ///< Undo file. }; nvprefs_interface::nvprefs_interface(): diff --git a/src/platform/windows/nvprefs/nvprefs_interface.h b/src/platform/windows/nvprefs/nvprefs_interface.h index 655f114f4..1f682ce29 100644 --- a/src/platform/windows/nvprefs/nvprefs_interface.h +++ b/src/platform/windows/nvprefs/nvprefs_interface.h @@ -9,23 +9,59 @@ namespace nvprefs { + /** + * @brief High-level NVIDIA profile preferences interface with undo support. + */ class nvprefs_interface { public: nvprefs_interface(); ~nvprefs_interface(); + /** + * @brief Load persisted state from its backing store. + * + * @return True when the NVIDIA preferences operation succeeds. + */ bool load(); + /** + * @brief Release loaded NVIDIA preference state. + */ void unload(); + /** + * @brief Restore NVIDIA settings from the undo file, then remove it. + * + * @return True when the NVIDIA preferences operation succeeds. + */ bool restore_from_and_delete_undo_file_if_exists(); + /** + * @brief Apply Sunshine-specific NVIDIA application profile changes. + * + * @return True when the NVIDIA preferences operation succeeds. + */ bool modify_application_profile(); + /** + * @brief Apply Sunshine-specific NVIDIA global profile changes. + * + * @return True when the NVIDIA preferences operation succeeds. + */ bool modify_global_profile(); + /** + * @brief Check whether this interface owns the active undo file. + * + * @return True when the NVIDIA preferences operation succeeds. + */ bool owning_undo_file(); + /** + * @brief Restore NVIDIA global profile settings from saved undo data. + * + * @return True when the NVIDIA preferences operation succeeds. + */ bool restore_global_profile(); private: diff --git a/src/platform/windows/nvprefs/undo_data.cpp b/src/platform/windows/nvprefs/undo_data.cpp index 5a0928153..7c02e33f6 100644 --- a/src/platform/windows/nvprefs/undo_data.cpp +++ b/src/platform/windows/nvprefs/undo_data.cpp @@ -9,16 +9,34 @@ #include "nvprefs_common.h" #include "undo_data.h" +/** + * @brief nlohmann JSON type used for NVPrefs undo serialization. + */ using json = nlohmann::json; // Separate namespace for ADL, otherwise we need to define json // functions in the same namespace as our types namespace nlohmann { + /** + * @brief Serialized undo-data representation. + */ using data_t = nvprefs::undo_data_t::data_t; + /** + * @brief Serialized OpenGL swapchain profile settings. + */ using opengl_swapchain_t = data_t::opengl_swapchain_t; + /** + * @brief Helper specialization for optional values. + */ template struct adl_serializer> { + /** + * @brief Serialize the undo-data object to JSON. + * + * @param j JSON value being read from or written to. + * @param opt Optional value being serialized or restored. + */ static void to_json(json &j, const std::optional &opt) { if (opt == std::nullopt) { j = nullptr; @@ -27,6 +45,12 @@ namespace nlohmann { } } + /** + * @brief Deserialize the undo-data object from JSON. + * + * @param j JSON value being read from or written to. + * @param opt Optional value being serialized or restored. + */ static void from_json(const json &j, std::optional &opt) { if (j.is_null()) { opt = std::nullopt; @@ -36,19 +60,43 @@ namespace nlohmann { } }; + /** + * @brief JSON serializer specialization for undo-data records. + */ template<> struct adl_serializer { + /** + * @brief Serialize the undo-data object to JSON. + * + * @param j JSON value being read from or written to. + * @param data Payload or state data to serialize, deserialize, or forward. + */ static void to_json(json &j, const data_t &data) { j = json {{"opengl_swapchain", data.opengl_swapchain}}; } + /** + * @brief Deserialize the undo-data object from JSON. + * + * @param j JSON value being read from or written to. + * @param data Payload or state data to serialize, deserialize, or forward. + */ static void from_json(const json &j, data_t &data) { j.at("opengl_swapchain").get_to(data.opengl_swapchain); } }; + /** + * @brief JSON serializer specialization for OpenGL swapchain settings. + */ template<> struct adl_serializer { + /** + * @brief Serialize the undo-data object to JSON. + * + * @param j JSON value being read from or written to. + * @param opengl_swapchain Opengl swapchain. + */ static void to_json(json &j, const opengl_swapchain_t &opengl_swapchain) { j = json { {"our_value", opengl_swapchain.our_value}, @@ -56,6 +104,12 @@ namespace nlohmann { }; } + /** + * @brief Deserialize the undo-data object from JSON. + * + * @param j JSON value being read from or written to. + * @param opengl_swapchain Opengl swapchain. + */ static void from_json(const json &j, opengl_swapchain_t &opengl_swapchain) { j.at("our_value").get_to(opengl_swapchain.our_value); j.at("undo_value").get_to(opengl_swapchain.undo_value); diff --git a/src/platform/windows/nvprefs/undo_data.h b/src/platform/windows/nvprefs/undo_data.h index aa3592733..580d37e9f 100644 --- a/src/platform/windows/nvprefs/undo_data.h +++ b/src/platform/windows/nvprefs/undo_data.h @@ -12,25 +12,60 @@ namespace nvprefs { + /** + * @brief Serializable NVIDIA profile state saved before preference changes. + */ class undo_data_t { public: + /** + * @brief NVIDIA profile settings captured for undo. + */ struct data_t { + /** + * @brief OpenGL swapchain setting values captured from the driver. + */ struct opengl_swapchain_t { - uint32_t our_value; - std::optional undo_value; + uint32_t our_value; ///< NVIDIA setting value applied by Sunshine. + std::optional undo_value; ///< Previous NVIDIA setting value to restore, if present. }; - std::optional opengl_swapchain; + std::optional opengl_swapchain; ///< Opengl swapchain. }; + /** + * @brief Set opengl swapchain. + * + * @param our_value NVIDIA setting value applied by Sunshine. + * @param undo_value Previous NVIDIA setting value to restore, if present. + */ void set_opengl_swapchain(uint32_t our_value, std::optional undo_value); + /** + * @brief Get opengl swapchain. + * + * @return Stored OpenGL swapchain override and optional restore setting. + */ std::optional get_opengl_swapchain() const; + /** + * @brief Serialize undo data to its JSON representation. + * + * @return JSON string containing the undo data. + */ std::string write() const; + /** + * @brief Read persisted data into the current object. + * + * @param buffer Serialized byte buffer to read from or write to. + */ void read(const std::vector &buffer); + /** + * @brief Merge newer undo data with the current data set. + * + * @param newer_data Newer data. + */ void merge(const undo_data_t &newer_data); private: diff --git a/src/platform/windows/nvprefs/undo_file.h b/src/platform/windows/nvprefs/undo_file.h index 39689953f..fdf4e85d4 100644 --- a/src/platform/windows/nvprefs/undo_file.h +++ b/src/platform/windows/nvprefs/undo_file.h @@ -13,16 +13,48 @@ namespace nvprefs { + /** + * @brief File-backed storage for NVIDIA preference undo data. + */ class undo_file_t { public: + /** + * @brief Open existing file. + * + * @param file_path File path. + * @param access_denied Access denied. + * @return Open undo file wrapper, or std::nullopt when the file cannot be opened. + */ static std::optional open_existing_file(std::filesystem::path file_path, bool &access_denied); + /** + * @brief Create new file. + * + * @param file_path File path. + * @return Created new file object or status. + */ static std::optional create_new_file(std::filesystem::path file_path); + /** + * @brief Delete the persisted NVIDIA settings undo file. + * + * @return True when the undo-file operation succeeds. + */ bool delete_file(); + /** + * @brief Write undo data. + * + * @param undo_data Driver settings to persist for a later restore. + * @return True when the undo-file operation succeeds. + */ bool write_undo_data(const undo_data_t &undo_data); + /** + * @brief Read undo data. + * + * @return Parsed undo data, or std::nullopt when the file is empty or invalid. + */ std::optional read_undo_data(); private: diff --git a/src/platform/windows/publish.cpp b/src/platform/windows/publish.cpp index 9635e520c..f4a954d03 100644 --- a/src/platform/windows/publish.cpp +++ b/src/platform/windows/publish.cpp @@ -21,47 +21,75 @@ #include "src/thread_safe.h" #include "utf_utils.h" +/** + * @def _FN(x, ret, args) + * @brief Macro for FN. + */ #define _FN(x, ret, args) \ + /** \ + * @brief Function pointer type for the dynamically loaded DNS-SD entry point. \ + */ \ typedef ret(*x##_fn) args; \ + /** \ + * @brief Loaded DNS-SD entry point pointer. \ + */ \ static x##_fn x using namespace std::literals; +/** + * @def __SV(quote) + * @brief Macro for SV. + */ #define __SV(quote) L##quote##sv +/** + * @def SV(quote) + * @brief Macro for SV. + */ #define SV(quote) __SV(quote) extern "C" { #ifndef __MINGW32__ - constexpr auto DNS_REQUEST_PENDING = 9506L; - constexpr auto DNS_QUERY_REQUEST_VERSION1 = 0x1; - constexpr auto DNS_QUERY_RESULTS_VERSION1 = 0x1; + constexpr auto DNS_REQUEST_PENDING = 9506L; ///< Windows DNS API constant for request pending. + constexpr auto DNS_QUERY_REQUEST_VERSION1 = 0x1; ///< Windows DNS API constant for query request version1. + constexpr auto DNS_QUERY_RESULTS_VERSION1 = 0x1; ///< Windows DNS API constant for query results version1. #endif - constexpr auto SERVICE_DOMAIN = "local"; - const auto SERVICE_TYPE_DOMAIN = std::format("{}.{}"sv, platf::SERVICE_TYPE, SERVICE_DOMAIN); + constexpr auto SERVICE_DOMAIN = "local"; ///< Protocol or platform constant for service domain. + const auto SERVICE_TYPE_DOMAIN = std::format("{}.{}"sv, platf::SERVICE_TYPE, SERVICE_DOMAIN); ///< Protocol or platform constant for service type domain. #ifndef __MINGW32__ + /** + * @brief Windows DNS-SD service instance registration data. + */ typedef struct _DNS_SERVICE_INSTANCE { - LPWSTR pszInstanceName; - LPWSTR pszHostName; + LPWSTR pszInstanceName; ///< DNS-SD service instance name. + LPWSTR pszHostName; ///< Hostname advertising the DNS-SD service. - IP4_ADDRESS *ip4Address; - IP6_ADDRESS *ip6Address; + IP4_ADDRESS *ip4Address; ///< Optional IPv4 address advertised with the service. + IP6_ADDRESS *ip6Address; ///< Optional IPv6 address advertised with the service. - WORD wPort; - WORD wPriority; - WORD wWeight; + WORD wPort; ///< TCP or UDP port advertised for the service. + WORD wPriority; ///< DNS-SD priority value. + WORD wWeight; ///< DNS-SD weight value. // Property list - DWORD dwPropertyCount; + DWORD dwPropertyCount; ///< Number of TXT record key/value pairs. - PWSTR *keys; - PWSTR *values; + PWSTR *keys; ///< DNS TXT record keys. + PWSTR *values; ///< DNS TXT record values paired with `keys`. - DWORD dwInterfaceIndex; - } DNS_SERVICE_INSTANCE, *PDNS_SERVICE_INSTANCE; + DWORD dwInterfaceIndex; ///< Network interface index used for registration. + } DNS_SERVICE_INSTANCE, *PDNS_SERVICE_INSTANCE; ///< Alias for DNS SERVICE INSTANCE. #endif + /** + * @brief DNS service registration completion callback. + * + * @param Status Registration status. + * @param pQueryContext User query context. + * @param pInstance DNS service instance. + */ typedef VOID WINAPI DNS_SERVICE_REGISTER_COMPLETE( _In_ DWORD Status, @@ -69,22 +97,31 @@ extern "C" { _In_ PDNS_SERVICE_INSTANCE pInstance ); + /** + * @brief Pointer to the Windows DNS-SD registration completion callback. + */ typedef DNS_SERVICE_REGISTER_COMPLETE *PDNS_SERVICE_REGISTER_COMPLETE; #ifndef __MINGW32__ + /** + * @brief Windows DNS-SD cancellation request data. + */ typedef struct _DNS_SERVICE_CANCEL { - PVOID reserved; - } DNS_SERVICE_CANCEL, *PDNS_SERVICE_CANCEL; + PVOID reserved; ///< Reserved by the Windows DNS-SD API and left null. + } DNS_SERVICE_CANCEL, *PDNS_SERVICE_CANCEL; ///< Alias for DNS SERVICE CANCEL. + /** + * @brief Windows DNS-SD service registration request data. + */ typedef struct _DNS_SERVICE_REGISTER_REQUEST { - ULONG Version; - ULONG InterfaceIndex; - PDNS_SERVICE_INSTANCE pServiceInstance; - PDNS_SERVICE_REGISTER_COMPLETE pRegisterCompletionCallback; - PVOID pQueryContext; - HANDLE hCredentials; - BOOL unicastEnabled; - } DNS_SERVICE_REGISTER_REQUEST, *PDNS_SERVICE_REGISTER_REQUEST; + ULONG Version; ///< Windows DNS-SD request structure version. + ULONG InterfaceIndex; ///< Network interface index used for registration. + PDNS_SERVICE_INSTANCE pServiceInstance; ///< Service instance being registered. + PDNS_SERVICE_REGISTER_COMPLETE pRegisterCompletionCallback; ///< Callback invoked when registration completes. + PVOID pQueryContext; ///< Caller-provided context passed to the completion callback. + HANDLE hCredentials; ///< Optional credentials handle supplied to Windows DNS-SD. + BOOL unicastEnabled; ///< Whether the DNS-SD registration is unicast-only. + } DNS_SERVICE_REGISTER_REQUEST, *PDNS_SERVICE_REGISTER_REQUEST; ///< Alias for DNS SERVICE REGISTER REQUEST. #endif _FN(_DnsServiceFreeInstance, VOID, (_In_ PDNS_SERVICE_INSTANCE pInstance)); @@ -93,6 +130,14 @@ extern "C" { } /* extern "C" */ namespace platf::publish { + /** + * @brief Handle completion of a Windows DNS-SD registration request. + * + * @param status Native status code returned by the platform API. + * @param pQueryContext Alarm object signaled when registration completes. + * @param pInstance Registered DNS-SD service instance returned by Windows. + * @return Callback has no return value; completion is reported through the alarm. + */ VOID WINAPI register_cb(DWORD status, PVOID pQueryContext, PDNS_SERVICE_INSTANCE pInstance) { auto alarm = (safe::alarm_t::element_type *) pQueryContext; @@ -169,6 +214,9 @@ namespace platf::publish { return registered_instance ? 0 : -1; } + /** + * @brief Windows DNS-SD registration lifetime for the advertised Sunshine service. + */ class mdns_registration_t: public ::platf::deinit_t { public: mdns_registration_t(): @@ -196,6 +244,12 @@ namespace platf::publish { PDNS_SERVICE_INSTANCE existing_instance; }; + /** + * @brief Resolve required function pointers from the native library. + * + * @param handle Native library or object handle used by the operation. + * @return 0 when all required DNS-SD functions are resolved; nonzero otherwise. + */ int load_funcs(HMODULE handle) { auto fg = util::fail_guard([handle]() { FreeLibrary(handle); diff --git a/src/process.cpp b/src/process.cpp index 95c7bdaea..3a2c76d68 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -43,10 +43,16 @@ namespace proc { using namespace std::literals; namespace pt = boost::property_tree; - proc_t proc; + proc_t proc; ///< Global process registry used to track and terminate child processes. + /** + * @brief RAII helper that runs shutdown cleanup when destroyed. + */ class deinit_t: public platf::deinit_t { public: + /** + * @brief Destroy the process subsystem deinitializer. + */ ~deinit_t() { proc.terminate(); } @@ -94,6 +100,13 @@ namespace proc { } } + /** + * @brief Resolve the working directory for a configured command. + * + * @param cmd Command line to execute or inspect. + * @param env Environment variables for the child process. + * @return Directory used to launch the command, falling back to PATH lookup when needed. + */ boost::filesystem::path find_working_directory(const std::string &cmd, boost::process::v1::environment &env) { // Parse the raw command string into parts to get the actual command portion std::vector parts; @@ -388,6 +401,13 @@ namespace proc { assert(!_process.running()); } + /** + * @brief Find the closing parenthesis for an environment-variable expression. + * + * @param begin Iterator positioned at the opening parenthesis. + * @param end End iterator for the expression being scanned. + * @return Iterator for the matching closing parenthesis, or end when unmatched. + */ std::string_view::iterator find_match(std::string_view::iterator begin, std::string_view::iterator end) { int stack = 0; @@ -409,6 +429,13 @@ namespace proc { return begin; } + /** + * @brief Parse env val. + * + * @param env Environment variables for the child process. + * @param val_raw Raw value that may contain $(NAME) substitutions. + * @return Value with recognized environment-variable substitutions expanded. + */ std::string parse_env_val(boost::process::v1::native_environment &env, const std::string_view &val_raw) { auto pos = std::begin(val_raw); auto dollar = std::find(pos, std::end(val_raw), '$'); @@ -498,6 +525,9 @@ namespace proc { return header == PNG_SIGNATURE; } + /** + * @brief Validate app image path. + */ std::string validate_app_image_path(std::string app_image_path) { if (app_image_path.empty()) { return DEFAULT_APP_IMAGE_PATH; @@ -545,6 +575,12 @@ namespace proc { return app_image_path; } + /** + * @brief Calculate the SHA-256 digest for a file. + * + * @param filename File path whose contents should be hashed. + * @return Lowercase hexadecimal SHA-256 digest, or std::nullopt on read/hash failure. + */ std::optional calculate_sha256(const std::string &filename) { crypto::md_ctx_t ctx {EVP_MD_CTX_create()}; if (!ctx) { @@ -580,6 +616,12 @@ namespace proc { return ss.str(); } + /** + * @brief Calculate the CRC-32 checksum for a string. + * + * @param input Bytes to include in the checksum. + * @return CRC-32 value for the input bytes. + */ uint32_t calculate_crc32(const std::string &input) { boost::crc_32_type result; result.process_bytes(input.data(), input.length()); @@ -617,6 +659,9 @@ namespace proc { return std::make_tuple(id_no_index, id_with_index); } + /** + * @brief Parse serialized text into the corresponding runtime representation. + */ std::optional parse(const std::string &file_name) { pt::ptree tree; @@ -748,6 +793,9 @@ namespace proc { return std::nullopt; } + /** + * @brief Refresh cached platform state from the operating system. + */ void refresh(const std::string &file_name) { auto proc_opt = proc::parse(file_name); diff --git a/src/process.h b/src/process.h index 0f2f5f51f..75a9de09e 100644 --- a/src/process.h +++ b/src/process.h @@ -5,6 +5,10 @@ #pragma once #ifndef __kernel_entry + /** + * @def __kernel_entry + * @brief Macro for kernel entry. + */ #define __kernel_entry #endif @@ -21,11 +25,21 @@ #include "rtsp.h" #include "utility.h" +/** + * @def DEFAULT_APP_IMAGE_PATH + * @brief Macro for DEFAULT APP IMAGE PATH. + */ #define DEFAULT_APP_IMAGE_PATH SUNSHINE_ASSETS_DIR "/box.png" namespace proc { + /** + * @brief Boost.Process pipe stream used for child-process I/O. + */ using file_t = util::safe_ptr_v2; + /** + * @brief Parsed command arguments used when launching a child process. + */ typedef config::prep_cmd_t cmd_t; /** @@ -41,7 +55,7 @@ namespace proc { * filename -- The output of the commands are appended to filename */ struct ctx_t { - std::vector prep_cmds; + std::vector prep_cmds; ///< Prep cmds. /** * Some applications, such as Steam, either exit quickly, or keep running indefinitely. @@ -57,22 +71,31 @@ namespace proc { */ std::vector detached; - std::string name; - std::string cmd; - std::string working_dir; - std::string output; - std::string image_path; - std::string id; - bool elevated; - bool auto_detach; - bool wait_all; - std::chrono::seconds exit_timeout; + std::string name; ///< Human-readable name for this item. + std::string cmd; ///< Command line used to launch the application. + std::string working_dir; ///< Working dir. + std::string output; ///< Captured output from the launched process. + std::string image_path; ///< Image path. + std::string id; ///< Stable identifier for the configured application. + bool elevated; ///< Whether the process should be launched elevated. + bool auto_detach; ///< Whether the process should detach automatically. + bool wait_all; ///< Whether Sunshine waits for all child processes. + std::chrono::seconds exit_timeout; ///< Exit timeout. }; + /** + * @brief Tracks launched child processes and terminates them during shutdown. + */ class proc_t { public: KITTY_DEFAULT_CONSTR_MOVE_THROW(proc_t) + /** + * @brief Construct a process manager. + * + * @param env Environment used when launching processes. + * @param apps Application launch contexts. + */ proc_t( boost::process::v1::environment &&env, std::vector &&apps @@ -82,6 +105,13 @@ namespace proc { _apps(std::move(apps)) { } + /** + * @brief Launch the configured application process. + * + * @param app_id App ID. + * @param launch_session Launch session. + * @return Process exit code or launch error status. + */ int execute(int app_id, std::shared_ptr launch_session); /** @@ -91,10 +121,34 @@ namespace proc { ~proc_t(); + /** + * @brief Return the configured applications. + * + * @return Immutable application list owned by the process manager. + */ const std::vector &get_apps() const; + /** + * @brief Return the configured applications. + * + * @return Mutable application list owned by the process manager. + */ std::vector &get_apps(); + /** + * @brief Get app image. + * + * @param app_id App ID. + * @return Validated image path for the requested application. + */ std::string get_app_image(int app_id); + /** + * @brief Get last run app name. + * + * @return Name of the most recently launched application. + */ std::string get_last_run_app_name(); + /** + * @brief Terminate the launched application process. + */ void terminate(); private: @@ -119,12 +173,33 @@ namespace proc { /** * @brief Calculate a stable id based on name and image data * @return Tuple of id calculated without index (for use if no collision) and one with. + * + * @param app_name App name. + * @param app_image_path App image path. + * @param index Zero-based index of the item being addressed. */ std::tuple calculate_app_id(const std::string &app_name, std::string app_image_path, int index); bool check_valid_png(const std::filesystem::path &path); + /** + * @brief Validate app image path. + * + * @param app_image_path Candidate image path from the application configuration. + * @return Existing PNG path, or the default application image when validation fails. + */ std::string validate_app_image_path(std::string app_image_path); + /** + * @brief Refresh cached platform state from the operating system. + * + * @param file_name File name. + */ void refresh(const std::string &file_name); + /** + * @brief Parse serialized text into the corresponding runtime representation. + * + * @param file_name File name. + * @return Parsed value or parse status. + */ std::optional parse(const std::string &file_name); /** diff --git a/src/round_robin.h b/src/round_robin.h index 205369550..55df231a1 100644 --- a/src/round_robin.h +++ b/src/round_robin.h @@ -13,20 +13,56 @@ * @tparam T The iterator type. */ namespace round_robin_util { + /** + * @brief CRTP base that provides iterator operators for round-robin iterators. + */ template class it_wrap_t { public: + /** + * @brief Iterator category advertised to standard algorithms. + */ using iterator_category = std::random_access_iterator_tag; + /** + * @brief Value type exposed by the wrapped iterator. + */ using value_type = V; + /** + * @brief Difference type used for iterator movement. + */ using difference_type = V; + /** + * @brief Mutable pointer to a value in the cycled range. + */ using pointer = V *; + /** + * @brief Const pointer to a value in the cycled range. + */ using const_pointer = V const *; + /** + * @brief Mutable reference to a value in the cycled range. + */ using reference = V &; + /** + * @brief Const reference to a value in the cycled range. + */ using const_reference = V const &; + /** + * @brief Concrete iterator type supplied by the CRTP child. + */ typedef T iterator; + /** + * @brief Signed offset type used when moving through the range. + */ typedef std::ptrdiff_t diff_t; + /** + * @brief Advance this iterator by repeatedly wrapping at the end of the range. + * + * @param step Number of positions to advance. + * @return Iterator positioned after the requested number of wrapped increments. + */ iterator operator+=(diff_t step) { while (step-- > 0) { ++_this(); @@ -35,6 +71,12 @@ namespace round_robin_util { return _this(); } + /** + * @brief Move this iterator backward by repeatedly wrapping at the beginning of the range. + * + * @param step Number of positions to rewind. + * @return Iterator positioned after the requested number of wrapped decrements. + */ iterator operator-=(diff_t step) { while (step-- > 0) { --_this(); @@ -43,18 +85,36 @@ namespace round_robin_util { return _this(); } + /** + * @brief Return a copy advanced by a wrapped offset. + * + * @param step Number of positions to advance. + * @return Advanced iterator copy. + */ iterator operator+(diff_t step) { iterator new_ = _this(); return new_ += step; } + /** + * @brief Return a copy moved backward by a wrapped offset. + * + * @param step Number of positions to rewind. + * @return Rewound iterator copy. + */ iterator operator-(diff_t step) { iterator new_ = _this(); return new_ -= step; } + /** + * @brief Count wrapped increments needed to reach this iterator from another one. + * + * @param first Iterator used as the starting position. + * @return Number of increments from `first` to this iterator. + */ diff_t operator-(iterator first) { diff_t step = 0; while (first != _this()) { @@ -65,16 +125,31 @@ namespace round_robin_util { return step; } + /** + * @brief Advance to the next element, wrapping back to the beginning when needed. + * + * @return Iterator after advancing. + */ iterator operator++() { _this().inc(); return _this(); } + /** + * @brief Move to the previous element, wrapping to the end when needed. + * + * @return Iterator after moving backward. + */ iterator operator--() { _this().dec(); return _this(); } + /** + * @brief Advance to the next element and return the previous iterator position. + * + * @return Iterator position before advancing. + */ iterator operator++(int) { iterator new_ = _this(); @@ -83,6 +158,11 @@ namespace round_robin_util { return new_; } + /** + * @brief Move to the previous element and return the previous iterator position. + * + * @return Iterator position before moving backward. + */ iterator operator--(int) { iterator new_ = _this(); @@ -91,42 +171,98 @@ namespace round_robin_util { return new_; } + /** + * @brief Dereference the current element in the cycled range. + * + * @return Mutable reference to the current value. + */ reference operator*() { return *_this().get(); } + /** + * @brief Dereference the current element in the cycled range. + * + * @return Const reference to the current value. + */ const_reference operator*() const { return *_this().get(); } + /** + * @brief Access the current element in the cycled range. + * + * @return Mutable pointer to the current value. + */ pointer operator->() { return &*_this(); } + /** + * @brief Access the current element in the cycled range. + * + * @return Const pointer to the current value. + */ const_pointer operator->() const { return &*_this(); } + /** + * @brief Compare whether two wrapped iterator positions differ. + * + * @param other Iterator position to compare against. + * @return True when the iterators do not refer to the same position. + */ bool operator!=(const iterator &other) const { return !(_this() == other); } + /** + * @brief Compare whether this wrapped position sorts before another one. + * + * @param other Iterator position to compare against. + * @return True when this iterator is ordered before `other`. + */ bool operator<(const iterator &other) const { return !(_this() >= other); } + /** + * @brief Compare whether this wrapped position sorts at or after another one. + * + * @param other Iterator position to compare against. + * @return True when this iterator is ordered at or after `other`. + */ bool operator>=(const iterator &other) const { return _this() == other || _this() > other; } + /** + * @brief Compare whether this wrapped position sorts at or before another one. + * + * @param other Iterator position to compare against. + * @return True when this iterator is ordered at or before `other`. + */ bool operator<=(const iterator &other) const { return _this() == other || _this() < other; } + /** + * @brief Compare whether two wrapped iterator positions are equal. + * + * @param other Iterator position to compare against. + * @return True when the iterators refer to the same wrapped position. + */ bool operator==(const iterator &other) const { return _this().eq(other); }; + /** + * @brief Compare whether this wrapped position sorts after another one. + * + * @param other Iterator position to compare against. + * @return True when this iterator is ordered after `other`. + */ bool operator>(const iterator &other) const { return _this().gt(other); } @@ -141,18 +277,36 @@ namespace round_robin_util { } }; + /** + * @brief Iterator that cycles indefinitely over a fixed begin/end range. + */ template class round_robin_t: public it_wrap_t> { public: + /** + * @brief Underlying iterator type for the cycled range. + */ using iterator = It; + /** + * @brief Mutable pointer to values in the cycled range. + */ using pointer = V *; + /** + * @brief Construct a round-robin iterator over a fixed range. + * + * @param begin First element in the range to cycle through. + * @param end One-past-the-end iterator for the range to cycle through. + */ round_robin_t(iterator begin, iterator end): _begin(begin), _end(end), _pos(begin) { } + /** + * @brief Advance the iterator to the next element. + */ void inc() { ++_pos; @@ -161,6 +315,9 @@ namespace round_robin_util { } } + /** + * @brief Move the iterator to the previous element. + */ void dec() { if (_pos == _begin) { _pos = _end; @@ -169,10 +326,21 @@ namespace round_robin_util { --_pos; } + /** + * @brief Compare two iterators for equality. + * + * @param other Iterator or container to compare against. + * @return True when both iterators point to equivalent values. + */ bool eq(const round_robin_t &other) const { return *_pos == *other._pos; } + /** + * @brief Return the currently wrapped value or handle. + * + * @return Underlying native handle or object pointer. + */ pointer get() const { return &*_pos; } @@ -184,6 +352,13 @@ namespace round_robin_util { It _pos; }; + /** + * @brief Create a round-robin iterator over a fixed range. + * + * @param begin Iterator or pointer marking the start of the input range. + * @param end Iterator or pointer marking the end of the input range. + * @return Iterator initialized to `begin` and wrapping before `end`. + */ template round_robin_t make_round_robin(It begin, It end) { return round_robin_t(begin, end); diff --git a/src/rswrapper.c b/src/rswrapper.c index 530696200..70043c3e7 100644 --- a/src/rswrapper.c +++ b/src/rswrapper.c @@ -12,31 +12,107 @@ // The assert() function is decorated with __cold on macOS which // is incompatible with Clang's target multiversioning feature #ifndef NDEBUG + /** + * @def NDEBUG + * @brief Macro for NDEBUG. + */ #define NDEBUG #endif +/** + * @def DECORATE_FUNC_I(a, b) + * @brief Macro for DECORATE FUNC i. + */ #define DECORATE_FUNC_I(a, b) a##b +/** + * @def DECORATE_FUNC(a, b) + * @brief Macro for DECORATE FUNC. + */ #define DECORATE_FUNC(a, b) DECORATE_FUNC_I(a, b) // Append an ISA suffix to the public RS API #define reed_solomon_init DECORATE_FUNC(reed_solomon_init, ISA_SUFFIX) +/** + * @def reed_solomon_new + * @brief Macro for reed solomon new. + */ #define reed_solomon_new DECORATE_FUNC(reed_solomon_new, ISA_SUFFIX) +/** + * @def reed_solomon_new_static + * @brief Macro for reed solomon new static. + */ #define reed_solomon_new_static DECORATE_FUNC(reed_solomon_new_static, ISA_SUFFIX) +/** + * @def reed_solomon_release + * @brief Macro for reed solomon release. + */ #define reed_solomon_release DECORATE_FUNC(reed_solomon_release, ISA_SUFFIX) +/** + * @def reed_solomon_decode + * @brief Macro for reed solomon decode. + */ #define reed_solomon_decode DECORATE_FUNC(reed_solomon_decode, ISA_SUFFIX) +/** + * @def reed_solomon_encode + * @brief Macro for reed solomon encode. + */ #define reed_solomon_encode DECORATE_FUNC(reed_solomon_encode, ISA_SUFFIX) // Append an ISA suffix to internal functions to prevent multiple definition errors +/** + * @def obl_axpy_ref + * @brief Macro for obl axpy ref. + */ #define obl_axpy_ref DECORATE_FUNC(obl_axpy_ref, ISA_SUFFIX) +/** + * @def obl_scal_ref + * @brief Macro for obl scal ref. + */ #define obl_scal_ref DECORATE_FUNC(obl_scal_ref, ISA_SUFFIX) +/** + * @def obl_axpyb32_ref + * @brief Macro for obl axpyb32 ref. + */ #define obl_axpyb32_ref DECORATE_FUNC(obl_axpyb32_ref, ISA_SUFFIX) +/** + * @def obl_axpy + * @brief Macro for obl axpy. + */ #define obl_axpy DECORATE_FUNC(obl_axpy, ISA_SUFFIX) +/** + * @def obl_scal + * @brief Macro for obl scal. + */ #define obl_scal DECORATE_FUNC(obl_scal, ISA_SUFFIX) +/** + * @def obl_swap + * @brief Macro for obl swap. + */ #define obl_swap DECORATE_FUNC(obl_swap, ISA_SUFFIX) +/** + * @def obl_axpyb32 + * @brief Macro for obl axpyb32. + */ #define obl_axpyb32 DECORATE_FUNC(obl_axpyb32, ISA_SUFFIX) +/** + * @def axpy + * @brief Macro for axpy. + */ #define axpy DECORATE_FUNC(axpy, ISA_SUFFIX) +/** + * @def scal + * @brief Macro for scal. + */ #define scal DECORATE_FUNC(scal, ISA_SUFFIX) +/** + * @def gemm + * @brief Macro for gemm. + */ #define gemm DECORATE_FUNC(gemm, ISA_SUFFIX) +/** + * @def invert_mat + * @brief Macro for invert mat. + */ #define invert_mat DECORATE_FUNC(invert_mat, ISA_SUFFIX) #if defined(__x86_64) || defined(__x86_64__) || defined(__amd64) || defined(__amd64__) || defined(_M_AMD64) @@ -98,6 +174,10 @@ #endif // Compile a default variant +/** + * @def ISA_SUFFIX + * @brief Macro for ISA SUFFIX. + */ #define ISA_SUFFIX _def #include "../third-party/nanors/deps/obl/autoshim.h" #include "../third-party/nanors/rs.c" @@ -112,10 +192,10 @@ #include "rswrapper.h" -reed_solomon_new_t reed_solomon_new_fn; -reed_solomon_release_t reed_solomon_release_fn; -reed_solomon_encode_t reed_solomon_encode_fn; -reed_solomon_decode_t reed_solomon_decode_fn; +reed_solomon_new_t reed_solomon_new_fn; ///< Reed solomon new. +reed_solomon_release_t reed_solomon_release_fn; ///< Reed solomon release. +reed_solomon_encode_t reed_solomon_encode_fn; ///< Reed solomon encode. +reed_solomon_decode_t reed_solomon_decode_fn; ///< Reed solomon decode. /** * @brief This initializes the RS function pointers to the best vectorized version available. diff --git a/src/rswrapper.h b/src/rswrapper.h index 2a4ba0d26..14aa7928f 100644 --- a/src/rswrapper.h +++ b/src/rswrapper.h @@ -8,23 +8,58 @@ // standard includes #include +/** + * @def DATA_SHARDS_MAX + * @brief Macro for DATA SHARDS MAX. + */ #define DATA_SHARDS_MAX 255 +/** + * @brief Opaque Reed-Solomon encoder/decoder context. + */ typedef struct _reed_solomon reed_solomon; +/** + * @brief Function pointer used to create a Reed-Solomon context. + */ typedef reed_solomon *(*reed_solomon_new_t)(int data_shards, int parity_shards); +/** + * @brief Function pointer used to release a Reed-Solomon context. + */ typedef void (*reed_solomon_release_t)(reed_solomon *rs); +/** + * @brief Function pointer used to encode Reed-Solomon recovery shards. + */ typedef int (*reed_solomon_encode_t)(reed_solomon *rs, uint8_t **shards, int nr_shards, int bs); +/** + * @brief Function pointer used to decode Reed-Solomon recovery shards. + */ typedef int (*reed_solomon_decode_t)(reed_solomon *rs, uint8_t **shards, uint8_t *marks, int nr_shards, int bs); -extern reed_solomon_new_t reed_solomon_new_fn; -extern reed_solomon_release_t reed_solomon_release_fn; -extern reed_solomon_encode_t reed_solomon_encode_fn; -extern reed_solomon_decode_t reed_solomon_decode_fn; +extern reed_solomon_new_t reed_solomon_new_fn; ///< Reed solomon new. +extern reed_solomon_release_t reed_solomon_release_fn; ///< Reed solomon release. +extern reed_solomon_encode_t reed_solomon_encode_fn; ///< Reed solomon encode. +extern reed_solomon_decode_t reed_solomon_decode_fn; ///< Reed solomon decode. +/** + * @def reed_solomon_new + * @brief Macro for reed solomon new. + */ #define reed_solomon_new reed_solomon_new_fn +/** + * @def reed_solomon_release + * @brief Macro for reed solomon release. + */ #define reed_solomon_release reed_solomon_release_fn +/** + * @def reed_solomon_encode + * @brief Macro for reed solomon encode. + */ #define reed_solomon_encode reed_solomon_encode_fn +/** + * @def reed_solomon_decode + * @brief Macro for reed solomon decode. + */ #define reed_solomon_decode reed_solomon_decode_fn /** diff --git a/src/rtsp.cpp b/src/rtsp.cpp index 0953cd59f..9ef67a7e6 100644 --- a/src/rtsp.cpp +++ b/src/rtsp.cpp @@ -40,6 +40,11 @@ using asio::ip::udp; using namespace std::literals; namespace rtsp_stream { + /** + * @brief Release msg resources. + * + * @param msg Raw RTSP message buffer being parsed or encrypted. + */ void free_msg(PRTSP_MESSAGE msg) { freeMessage(msg); @@ -48,46 +53,102 @@ namespace rtsp_stream { #pragma pack(push, 1) + /** + * @brief Encrypted RTSP message header used by GameStream. + */ struct encrypted_rtsp_header_t { // We set the MSB in encrypted RTSP messages to allow format-agnostic // parsing code to be able to tell encrypted from plaintext messages. - static constexpr std::uint32_t ENCRYPTED_MESSAGE_TYPE_BIT = 0x80000000; + static constexpr std::uint32_t ENCRYPTED_MESSAGE_TYPE_BIT = 0x80000000; ///< Protocol or platform constant for encrypted message type bit. + /** + * @brief Return a pointer to the protocol payload following the packet header. + * + * @return Parsed or serialized payload data. + */ uint8_t *payload() { return (uint8_t *) (this + 1); } + /** + * @brief Decode the RTSP interleaved-frame payload length. + * + * @return Payload byte count with the encrypted-message flag masked out. + */ std::uint32_t payload_length() { return util::endian::big(typeAndLength) & ~ENCRYPTED_MESSAGE_TYPE_BIT; } + /** + * @brief Check whether encrypted. + * + * @return True when the RTSP connection is using TLS. + */ bool is_encrypted() { return !!(util::endian::big(typeAndLength) & ENCRYPTED_MESSAGE_TYPE_BIT); } // This field is the length of the payload + ENCRYPTED_MESSAGE_TYPE_BIT in big-endian - std::uint32_t typeAndLength; + std::uint32_t typeAndLength; ///< Type and length. // This field is the number used to initialize the bottom 4 bytes of the AES IV in big-endian - std::uint32_t sequenceNumber; + std::uint32_t sequenceNumber; ///< Sequence number. // This field is the AES GCM authentication tag - std::uint8_t tag[16]; + std::uint8_t tag[16]; ///< Authentication tag appended to the encrypted payload. }; #pragma pack(pop) class rtsp_server_t; + /** + * @brief Owning pointer for an RTSP message allocated by moonlight-common-c. + */ using msg_t = util::safe_ptr; + /** + * @brief RTSP command handler signature used by the server dispatch table. + */ using cmd_func_t = std::function; + /** + * @brief Write msg details to the log. + * + * @param msg Raw RTSP message buffer being parsed or encrypted. + */ void print_msg(PRTSP_MESSAGE msg); + /** + * @brief Send the RTSP response used for unsupported commands. + * + * @param sock Socket used to read or write the protocol message. + * @param session Active streaming or pairing session for the request. + * @param req Parsed RTSP request being handled. + */ void cmd_not_found(tcp::socket &sock, launch_session_t &, msg_t &&req); + /** + * @brief Send the protocol response for the current request. + * + * @param sock Socket used to read or write the protocol message. + * @param session Active streaming or pairing session for the request. + * @param options Request options or socket options to apply. + * @param statuscode RTSP status code to send in the response. + * @param status_msg Status msg. + * @param seqn RTSP CSeq value associated with the request. + * @param payload Optional payload body to include in the response. + */ void respond(tcp::socket &sock, launch_session_t &session, POPTION_ITEM options, int statuscode, const char *status_msg, int seqn, const std::string_view &payload); + /** + * @brief RTSP client socket state and receive buffer parser. + */ class socket_t: public std::enable_shared_from_this { public: + /** + * @brief Construct an RTSP socket wrapper around an accepted TCP connection. + * + * @param io_context Io context. + * @param handle_data_fn Handle data. + */ socket_t(boost::asio::io_context &io_context, std::function &&handle_data_fn): handle_data_fn {std::move(handle_data_fn)}, sock {io_context} { @@ -125,7 +186,7 @@ namespace rtsp_stream { } /** - * @brief Handle the initial read of the header of an encrypted message. + * @brief Read the encrypted RTSP header before receiving the encrypted payload. * @param socket The socket the message was received on. * @param ec The error code of the read operation. * @param bytes The number of bytes read. @@ -174,7 +235,7 @@ namespace rtsp_stream { } /** - * @brief Handle the final read of the content of an encrypted message. + * @brief Read and decrypt the encrypted RTSP payload. * @param socket The socket the message was received on. * @param ec The error code of the read operation. * @param bytes The number of bytes read. @@ -265,7 +326,7 @@ namespace rtsp_stream { } /** - * @brief Handle the read of the payload portion of a plaintext message. + * @brief Read the plaintext RTSP payload after the header is parsed. * @param socket The socket the message was received on. * @param ec The error code of the read operation. * @param bytes The number of bytes read. @@ -335,7 +396,7 @@ namespace rtsp_stream { } /** - * @brief Handle the read of the header portion of a plaintext message. + * @brief Read and parse the plaintext RTSP request header. * @param socket The socket the message was received on. * @param ec The error code of the read operation. * @param bytes The number of bytes read. @@ -382,28 +443,44 @@ namespace rtsp_stream { handle_plaintext_payload(socket, ec, buf_size); } + /** + * @brief Dispatch a parsed RTSP request to the socket's command handler. + * + * @param req Parsed RTSP request being handled. + */ void handle_data(msg_t &&req) { handle_data_fn(sock, *session, std::move(req)); } - std::function handle_data_fn; + std::function handle_data_fn; ///< Command dispatcher installed by the RTSP server. - tcp::socket sock; + tcp::socket sock; ///< TCP socket connected to the RTSP client. - std::array msg_buf; + std::array msg_buf; ///< Receive buffer for one RTSP request header and payload. - char *crlf; - char *begin = msg_buf.data(); + char *crlf; ///< Pointer to the next CRLF delimiter while parsing the receive buffer. + char *begin = msg_buf.data(); ///< Start of the unparsed data currently in `msg_buf`. - std::shared_ptr session; + std::shared_ptr session; ///< Launch session claimed by this RTSP socket. }; + /** + * @brief RTSP listener that matches incoming clients to pending launch sessions. + */ class rtsp_server_t { public: ~rtsp_server_t() { clear(); } + /** + * @brief Bind the underlying socket or graphics resource to its target. + * + * @param af Address family used for socket creation or binding. + * @param port TCP or UDP port number. + * @param ec Error code returned by the asynchronous operation. + * @return Network operation status. + */ int bind(net::af_e af, std::uint16_t port, boost::system::error_code &ec) { acceptor.open(af == net::IPV4 ? tcp::v4() : tcp::v6(), ec); if (ec) { @@ -440,6 +517,13 @@ namespace rtsp_stream { return 0; } + /** + * @brief Dispatch a parsed RTSP request to its handler. + * + * @param sock Socket used to read or write the protocol message. + * @param session Active streaming or pairing session for the request. + * @param req Parsed RTSP request being handled. + */ void handle_msg(tcp::socket &sock, launch_session_t &session, msg_t &&req) { auto func = _map_cmd_cb.find(req->message.request.command); if (func != std::end(_map_cmd_cb)) { @@ -452,6 +536,11 @@ namespace rtsp_stream { sock.shutdown(boost::asio::socket_base::shutdown_type::shutdown_both, ec); } + /** + * @brief Accept a pending connection and arm the server for the next client. + * + * @param ec Error code returned by the asynchronous operation. + */ void handle_accept(const boost::system::error_code &ec) { if (ec) { BOOST_LOG(error) << "Couldn't accept incoming connections: "sv << ec.message(); @@ -486,6 +575,12 @@ namespace rtsp_stream { }); } + /** + * @brief Register or visit handlers stored in the map. + * + * @param type Protocol, message, or resource type selector. + * @param cb Callback invoked for each matching message or session. + */ void map(const std::string_view &type, cmd_func_t cb) { _map_cmd_cb.emplace(type, std::move(cb)); } @@ -544,7 +639,7 @@ namespace rtsp_stream { return (int) _session_slots->size(); } - safe::event_t> launch_event; + safe::event_t> launch_event; ///< Launch event. /** * @brief Clear launch sessions. @@ -569,6 +664,11 @@ namespace rtsp_stream { } } + /** + * @brief Clear by cert state. + * + * @param cert Certificate data or object used by the operation. + */ void clear_by_cert(std::string_view cert) { auto lg = _session_slots.lock(); for (auto i = _session_slots->begin(); i != _session_slots->end();) { @@ -636,8 +736,11 @@ namespace rtsp_stream { std::shared_ptr next_socket; }; - rtsp_server_t server {}; + rtsp_server_t server {}; ///< Singleton RTSP server used by GameStream launch sessions. + /** + * @brief Queue a launch session until the RTSP client connects. + */ void launch_session_raise(std::shared_ptr launch_session) { server.session_raise(std::move(launch_session)); } @@ -657,10 +760,20 @@ namespace rtsp_stream { server.clear(true); } + /** + * @brief Terminate active sessions associated with a client certificate. + */ void terminate_sessions_by_cert(std::string_view cert) { server.clear_by_cert(cert); } + /** + * @brief Send the serialized response over the active socket. + * + * @param sock Socket used to read or write the protocol message. + * @param sv String view containing the text to inspect. + * @return Network operation status. + */ int send(tcp::socket &sock, const std::string_view &sv) { std::size_t bytes_send = 0; @@ -677,6 +790,13 @@ namespace rtsp_stream { return 0; } + /** + * @brief Send the protocol response for the current request. + * + * @param sock Socket used to read or write the protocol message. + * @param session Active streaming or pairing session for the request. + * @param resp RTSP response string to send to the client. + */ void respond(tcp::socket &sock, launch_session_t &session, msg_t &resp) { auto payload = std::make_pair(resp->payload, resp->payloadLength); @@ -745,6 +865,9 @@ namespace rtsp_stream { } } + /** + * @brief Send the protocol response for the current request. + */ void respond(tcp::socket &sock, launch_session_t &session, POPTION_ITEM options, int statuscode, const char *status_msg, int seqn, const std::string_view &payload) { msg_t resp {new msg_t::element_type}; createRtspResponse(resp.get(), nullptr, 0, const_cast("RTSP/1.0"), statuscode, const_cast(status_msg), seqn, options, const_cast(payload.data()), (int) payload.size()); @@ -752,10 +875,21 @@ namespace rtsp_stream { respond(sock, session, resp); } + /** + * @brief Handle an unsupported RTSP command. + */ void cmd_not_found(tcp::socket &sock, launch_session_t &session, msg_t &&req) { respond(sock, session, nullptr, 404, "NOT FOUND", req->sequenceNumber, {}); } + /** + * @brief Handle RTSP OPTIONS requests. + * + * @param server RTSP server instance handling the request. + * @param sock Socket used to read or write the protocol message. + * @param session Active streaming or pairing session for the request. + * @param req Parsed RTSP request being handled. + */ void cmd_option(rtsp_server_t *server, tcp::socket &sock, launch_session_t &session, msg_t &&req) { OPTION_ITEM option {}; @@ -768,6 +902,14 @@ namespace rtsp_stream { respond(sock, session, &option, 200, "OK", req->sequenceNumber, {}); } + /** + * @brief Handle RTSP DESCRIBE requests. + * + * @param server RTSP server instance. + * @param sock Client socket. + * @param session Launch session. + * @param req RTSP request message. + */ void cmd_describe(rtsp_server_t *server, tcp::socket &sock, launch_session_t &session, msg_t &&req) { OPTION_ITEM option {}; @@ -851,6 +993,14 @@ namespace rtsp_stream { respond(sock, session, &option, 200, "OK", req->sequenceNumber, ss.str()); } + /** + * @brief Handle RTSP SETUP requests and prepare transport state. + * + * @param server RTSP server instance handling the request. + * @param sock Socket used to read or write the protocol message. + * @param session Active streaming or pairing session for the request. + * @param req Parsed RTSP request being handled. + */ void cmd_setup(rtsp_server_t *server, tcp::socket &sock, launch_session_t &session, msg_t &&req) { OPTION_ITEM options[4] {}; @@ -910,6 +1060,14 @@ namespace rtsp_stream { respond(sock, session, &seqn, 200, "OK", req->sequenceNumber, {}); } + /** + * @brief Handle RTSP ANNOUNCE requests from the client. + * + * @param server RTSP server instance handling the request. + * @param sock Socket used to read or write the protocol message. + * @param session Active streaming or pairing session for the request. + * @param req Parsed RTSP request being handled. + */ void cmd_announce(rtsp_server_t *server, tcp::socket &sock, launch_session_t &session, msg_t &&req) { OPTION_ITEM option {}; @@ -1153,6 +1311,14 @@ namespace rtsp_stream { respond(sock, session, &option, 200, "OK", req->sequenceNumber, {}); } + /** + * @brief Handle RTSP PLAY requests and start streaming. + * + * @param server RTSP server instance handling the request. + * @param sock Socket used to read or write the protocol message. + * @param session Active streaming or pairing session for the request. + * @param req Parsed RTSP request being handled. + */ void cmd_play(rtsp_server_t *server, tcp::socket &sock, launch_session_t &session, msg_t &&req) { OPTION_ITEM option {}; @@ -1209,6 +1375,9 @@ namespace rtsp_stream { rtsp_thread.join(); } + /** + * @brief Write msg details to the log. + */ void print_msg(PRTSP_MESSAGE msg) { std::string_view type = msg->type == TYPE_RESPONSE ? "RESPONSE"sv : "REQUEST"sv; diff --git a/src/rtsp.h b/src/rtsp.h index d2bf39a07..7857835f0 100644 --- a/src/rtsp.h +++ b/src/rtsp.h @@ -12,36 +12,44 @@ #include "thread_safe.h" namespace rtsp_stream { - constexpr auto RTSP_SETUP_PORT = 21; + constexpr auto RTSP_SETUP_PORT = 21; ///< GameStream base-port offset used for the RTSP setup listener. + /** + * @brief RTSP launch session state shared with stream setup. + */ struct launch_session_t { - uint32_t id; + uint32_t id; ///< RTSP launch-session identifier assigned before stream startup. - crypto::aes_t gcm_key; - crypto::aes_t iv; + crypto::aes_t gcm_key; ///< AES-GCM key negotiated for encrypted RTSP messages. + crypto::aes_t iv; ///< Initial RTSP AES-GCM IV supplied by the client. - std::string av_ping_payload; - uint32_t control_connect_data; + std::string av_ping_payload; ///< AV ping payload. + uint32_t control_connect_data; ///< Client-provided token used when connecting the control channel. - bool host_audio; - std::string unique_id; - int width; - int height; - int fps; - int gcmap; - int appid; - int surround_info; - std::string surround_params; - bool continuous_audio; - bool enable_hdr; - bool enable_sops; + bool host_audio; ///< Whether host audio should be played locally. + std::string unique_id; ///< Moonlight client unique identifier for this launch request. + int width; ///< Frame or display width in pixels. + int height; ///< Frame or display height in pixels. + int fps; ///< Requested video frame rate. + int gcmap; ///< Game controller mapping requested by the client. + int appid; ///< Application ID requested for launch or resume. + int surround_info; ///< Encoded GameStream surround-sound capability flags. + std::string surround_params; ///< Client-provided surround-sound layout parameters. + bool continuous_audio; ///< Whether audio packets continue during silence. + bool enable_hdr; ///< Whether HDR streaming is requested. + bool enable_sops; ///< Whether sequence output protection is requested. - std::optional rtsp_cipher; - std::string rtsp_url_scheme; - uint32_t rtsp_iv_counter; - std::string client_cert; + std::optional rtsp_cipher; ///< AES-GCM cipher used once encrypted RTSP is negotiated. + std::string rtsp_url_scheme; ///< URL scheme selected by the RTSP SETUP flow. + uint32_t rtsp_iv_counter; ///< Counter value mixed into encrypted RTSP IVs. + std::string client_cert; ///< PEM certificate for the paired Moonlight client. }; + /** + * @brief Queue a launch session until the RTSP client connects. + * + * @param launch_session Session state prepared by the GameStream launch handler. + */ void launch_session_raise(std::shared_ptr launch_session); /** @@ -60,6 +68,11 @@ namespace rtsp_stream { * @brief Terminates all running streaming sessions. */ void terminate_sessions(); + /** + * @brief Terminate active sessions associated with a client certificate. + * + * @param cert Certificate data or object used by the operation. + */ void terminate_sessions_by_cert(std::string_view cert); /** diff --git a/src/stat_trackers.cpp b/src/stat_trackers.cpp index 13cc4170b..90fce2ae8 100644 --- a/src/stat_trackers.cpp +++ b/src/stat_trackers.cpp @@ -7,10 +7,16 @@ namespace stat_trackers { + /** + * @brief Create a Boost formatter with one fractional digit. + */ boost::format one_digit_after_decimal() { return boost::format("%1$.1f"); } + /** + * @brief Create a Boost formatter with two fractional digits. + */ boost::format two_digits_after_decimal() { return boost::format("%1$.2f"); } diff --git a/src/stat_trackers.h b/src/stat_trackers.h index 2e3873d42..be2903405 100644 --- a/src/stat_trackers.h +++ b/src/stat_trackers.h @@ -14,15 +14,38 @@ namespace stat_trackers { + /** + * @brief Create a formatter for values with one digit after the decimal point. + * + * @return Boost format configured for one fractional digit. + */ boost::format one_digit_after_decimal(); + /** + * @brief Create a formatter for values with two digits after the decimal point. + * + * @return Boost format configured for two fractional digits. + */ boost::format two_digits_after_decimal(); + /** + * @brief Accumulates minimum, maximum, and average values between periodic callbacks. + */ template class min_max_avg_tracker { public: + /** + * @brief Callback invoked with the minimum, maximum, and average for one interval. + */ using callback_function = std::function; + /** + * @brief Add one statistic sample and invoke the callback when the interval elapses. + * + * @param stat Statistic value used to update the tracker. + * @param callback Callback invoked with the calculated statistic. + * @param interval_in_seconds Minimum time between callback invocations. + */ void collect_and_callback_on_interval(T stat, const callback_function &callback, std::chrono::seconds interval_in_seconds) { if (data.calls == 0) { data.last_callback_time = std::chrono::steady_clock::now(); @@ -36,6 +59,9 @@ namespace stat_trackers { data.calls += 1; } + /** + * @brief Reset the object to its initial empty state. + */ void reset() { data = {}; } diff --git a/src/stream.cpp b/src/stream.cpp index c0da8671c..903486982 100644 --- a/src/stream.cpp +++ b/src/stream.cpp @@ -34,21 +34,21 @@ extern "C" { #include "thread_safe.h" #include "utility.h" -constexpr int IDX_START_A = 0; -constexpr int IDX_START_B = 1; -constexpr int IDX_INVALIDATE_REF_FRAMES = 2; -constexpr int IDX_LOSS_STATS = 3; -constexpr int IDX_INPUT_DATA = 5; -constexpr int IDX_RUMBLE_DATA = 6; -constexpr int IDX_TERMINATION = 7; -constexpr int IDX_PERIODIC_PING = 8; -constexpr int IDX_REQUEST_IDR_FRAME = 9; -constexpr int IDX_ENCRYPTED = 10; -constexpr int IDX_HDR_MODE = 11; -constexpr int IDX_RUMBLE_TRIGGER_DATA = 12; -constexpr int IDX_SET_MOTION_EVENT = 13; -constexpr int IDX_SET_RGB_LED = 14; -constexpr int IDX_SET_ADAPTIVE_TRIGGERS = 15; +constexpr int IDX_START_A = 0; ///< Control-stream message index for the first stream-start packet. +constexpr int IDX_START_B = 1; ///< Control-stream message index for the second stream-start packet. +constexpr int IDX_INVALIDATE_REF_FRAMES = 2; ///< Control-stream message index for invalidate ref frames. +constexpr int IDX_LOSS_STATS = 3; ///< Control-stream message index for loss stats. +constexpr int IDX_INPUT_DATA = 5; ///< Control-stream message index for input data. +constexpr int IDX_RUMBLE_DATA = 6; ///< Control-stream message index for rumble data. +constexpr int IDX_TERMINATION = 7; ///< Control-stream message index for termination. +constexpr int IDX_PERIODIC_PING = 8; ///< Control-stream message index for periodic ping. +constexpr int IDX_REQUEST_IDR_FRAME = 9; ///< Control-stream message index for request idr frame. +constexpr int IDX_ENCRYPTED = 10; ///< Control-stream message index for encrypted. +constexpr int IDX_HDR_MODE = 11; ///< Control-stream message index for hdr mode. +constexpr int IDX_RUMBLE_TRIGGER_DATA = 12; ///< Control-stream message index for rumble trigger data. +constexpr int IDX_SET_MOTION_EVENT = 13; ///< Control-stream message index for set motion event. +constexpr int IDX_SET_RGB_LED = 14; ///< Control-stream message index for set rgb led. +constexpr int IDX_SET_ADAPTIVE_TRIGGERS = 15; ///< Control-stream message index for set adaptive triggers. static const short packetTypes[] = { 0x0305, // Start A @@ -79,6 +79,9 @@ using namespace std::literals; namespace stream { + /** + * @brief Enumerates supported socket options. + */ enum class socket_e : int { video, ///< Video audio ///< Audio @@ -86,30 +89,38 @@ namespace stream { #pragma pack(push, 1) + /** + * @brief Packed short video frame header sent before video payload bytes. + */ struct video_short_frame_header_t { + /** + * @brief Return a pointer to the protocol payload following the packet header. + * + * @return Parsed or serialized payload data. + */ uint8_t *payload() { return (uint8_t *) (this + 1); } - std::uint8_t headerType; // Always 0x01 for short headers + std::uint8_t headerType; ///< Always 0x01 for short headers. // Sunshine extension // Frame processing latency, in 1/10 ms units // zero when the frame is repeated or there is no backend implementation - boost::endian::little_uint16_at frame_processing_latency; + boost::endian::little_uint16_at frame_processing_latency; ///< Frame processing latency. // Currently known values: // 1 = Normal P-frame // 2 = IDR-frame // 4 = P-frame with intra-refresh blocks // 5 = P-frame after reference frame invalidation - std::uint8_t frameType; + std::uint8_t frameType; ///< Frame type. // Length of the final packet payload for codecs that cannot handle // zero padding, such as AV1 (Sunshine extension). - boost::endian::little_uint16_at lastPayloadLen; + boost::endian::little_uint16_at lastPayloadLen; ///< Last payload len. - std::uint8_t unknown[2]; + std::uint8_t unknown[2]; ///< Reserved bytes with no known client-visible meaning. }; static_assert( @@ -117,132 +128,207 @@ namespace stream { "Short frame header must be 8 bytes" ); + /** + * @brief Packed RTP and video headers for an unencrypted video packet. + */ struct video_packet_raw_t { + /** + * @brief Return a pointer to the protocol payload following the packet header. + * + * @return Parsed or serialized payload data. + */ uint8_t *payload() { return (uint8_t *) (this + 1); } - RTP_PACKET rtp; - char reserved[4]; + RTP_PACKET rtp; ///< RTP header that prefixes this payload. + char reserved[4]; ///< Reserved protocol padding bytes. - NV_VIDEO_PACKET packet; + NV_VIDEO_PACKET packet; ///< GameStream video packet header. }; + /** + * @brief AES-GCM prefix written before encrypted video packet payloads. + */ struct video_packet_enc_prefix_t { + /** + * @brief IV. + */ std::uint8_t iv[12]; // 12-byte IV is ideal for AES-GCM - std::uint32_t frameNumber; - std::uint8_t tag[16]; + std::uint32_t frameNumber; ///< Frame number. + std::uint8_t tag[16]; ///< Authentication tag appended to the encrypted payload. }; + /** + * @brief Packed RTP header for an audio packet. + */ struct audio_packet_t { - RTP_PACKET rtp; + RTP_PACKET rtp; ///< RTP header that prefixes this payload. }; + /** + * @brief Packed control-channel header used before control payloads. + */ struct control_header_v2 { - std::uint16_t type; - std::uint16_t payloadLength; + std::uint16_t type; ///< Control message type. + std::uint16_t payloadLength; ///< Payload length. + /** + * @brief Return a pointer to the protocol payload following the packet header. + * + * @return Parsed or serialized payload data. + */ uint8_t *payload() { return (uint8_t *) (this + 1); } }; + /** + * @brief Control-channel termination message payload. + */ struct control_terminate_t { - control_header_v2 header; + control_header_v2 header; ///< Control message header preceding this payload. - std::uint32_t ec; + std::uint32_t ec; ///< Error code reported by the termination message. }; + /** + * @brief Control payload that sets controller rumble motors. + */ struct control_rumble_t { - control_header_v2 header; + control_header_v2 header; ///< Control message header preceding this payload. - std::uint32_t useless; + std::uint32_t useless; ///< Reserved field kept for protocol compatibility. - std::uint16_t id; - std::uint16_t lowfreq; - std::uint16_t highfreq; + std::uint16_t id; ///< Controller identifier associated with this message. + std::uint16_t lowfreq; ///< Low-frequency rumble motor intensity. + std::uint16_t highfreq; ///< High-frequency rumble motor intensity. }; + /** + * @brief Control payload that sets trigger rumble motors. + */ struct control_rumble_triggers_t { - control_header_v2 header; + control_header_v2 header; ///< Control message header preceding this payload. - std::uint16_t id; - std::uint16_t left; - std::uint16_t right; + std::uint16_t id; ///< Controller identifier associated with this message. + std::uint16_t left; ///< Left trigger or motor intensity. + std::uint16_t right; ///< Right trigger or motor intensity. }; + /** + * @brief Control payload that enables or disables motion reports. + */ struct control_set_motion_event_t { - control_header_v2 header; + control_header_v2 header; ///< Control message header preceding this payload. - std::uint16_t id; - std::uint16_t reportrate; - std::uint8_t type; + std::uint16_t id; ///< Controller identifier associated with this message. + std::uint16_t reportrate; ///< Requested motion report rate. + std::uint8_t type; ///< Protocol or controller type discriminator. }; + /** + * @brief Control payload that sets controller RGB LED color. + */ struct control_set_rgb_led_t { - control_header_v2 header; + control_header_v2 header; ///< Control message header preceding this payload. - std::uint16_t id; - std::uint8_t r; - std::uint8_t g; - std::uint8_t b; + std::uint16_t id; ///< Controller identifier associated with this message. + std::uint8_t r; ///< Red LED channel. + std::uint8_t g; ///< Green LED channel. + std::uint8_t b; ///< Blue LED channel. }; + /** + * @brief Control payload that configures DualSense adaptive triggers. + */ struct control_adaptive_triggers_t { - control_header_v2 header; + control_header_v2 header; ///< Control message header preceding this payload. - std::uint16_t id; + std::uint16_t id; ///< Controller identifier associated with this message. /** * 0x04 - Right trigger * 0x08 - Left trigger */ std::uint8_t event_flags; - std::uint8_t type_left; - std::uint8_t type_right; - std::uint8_t left[DS_EFFECT_PAYLOAD_SIZE]; - std::uint8_t right[DS_EFFECT_PAYLOAD_SIZE]; + std::uint8_t type_left; ///< Adaptive-trigger mode for the left trigger. + std::uint8_t type_right; ///< Adaptive-trigger mode for the right trigger. + std::uint8_t left[DS_EFFECT_PAYLOAD_SIZE]; ///< Left adaptive-trigger effect payload. + std::uint8_t right[DS_EFFECT_PAYLOAD_SIZE]; ///< Right adaptive-trigger effect payload. }; + /** + * @brief Control payload that toggles HDR mode and carries metadata. + */ struct control_hdr_mode_t { - control_header_v2 header; + control_header_v2 header; ///< Control message header preceding this payload. - std::uint8_t enabled; + std::uint8_t enabled; ///< Nonzero when HDR should be enabled. // Sunshine protocol extension - SS_HDR_METADATA metadata; + SS_HDR_METADATA metadata; ///< HDR10 metadata sent with the control message. }; + /** + * @brief Packed encrypted control-channel envelope. + */ typedef struct control_encrypted_t { - std::uint16_t encryptedHeaderType; // Always LE 0x0001 - std::uint16_t length; // sizeof(seq) + 16 byte tag + secondary header and data + std::uint16_t encryptedHeaderType; ///< Always LE 0x0001. + std::uint16_t length; ///< Size of seq, tag, secondary header, and data. // seq is accepted as an arbitrary value in Moonlight - std::uint32_t seq; // Monotonically increasing sequence number (used as IV for AES-GCM) + std::uint32_t seq; ///< Monotonically increasing sequence number used as the AES-GCM IV. + /** + * @brief Return a pointer to the protocol payload following the packet header. + * + * @return Parsed or serialized payload data. + */ uint8_t *payload() { return (uint8_t *) (this + 1); } // encrypted control_header_v2 and payload data follow - } *control_encrypted_p; + } *control_encrypted_p; ///< Alias for control encrypted p. + /** + * @brief Packed RTP and FEC headers for an audio recovery packet. + */ struct audio_fec_packet_t { - RTP_PACKET rtp; - AUDIO_FEC_HEADER fecHeader; + RTP_PACKET rtp; ///< RTP header that prefixes this payload. + AUDIO_FEC_HEADER fecHeader; ///< Audio forward-error-correction header. }; #pragma pack(pop) + /** + * @brief Round a byte count up to the next PKCS#7 padding boundary. + * + * @param size Number of bytes or elements requested. + * @return `size` rounded up to the next PKCS#7 block boundary. + */ constexpr std::size_t round_to_pkcs7_padded(std::size_t size) { return ((size + 15) / 16) * 16; } - constexpr std::size_t MAX_AUDIO_PACKET_SIZE = 1400; + constexpr std::size_t MAX_AUDIO_PACKET_SIZE = 1400; ///< Protocol or platform constant for max audio packet size. + /** + * @brief AES key storage used for audio packet encryption. + */ using audio_aes_t = std::array; + /** + * @brief Audio/video session identifier carried by GameStream packets. + */ using av_session_id_t = std::variant; // IP address or SS-Ping-Payload from RTSP handshake + /** + * @brief Mail queue carrying encoded stream packets to sender threads. + */ using message_queue_t = std::shared_ptr>>; + /** + * @brief Shared queue set used to distribute packet queues to broadcast workers. + */ using message_queue_queue_t = std::shared_ptr>>; // return bytes written on success @@ -263,8 +349,18 @@ namespace stream { } } + /** + * @brief ENet control server that routes incoming control packets to stream sessions. + */ class control_server_t { public: + /** + * @brief Bind the underlying socket or graphics resource to its target. + * + * @param address_family Address family. + * @param port TCP or UDP port number. + * @return Network operation status. + */ int bind(net::af_e address_family, std::uint16_t port) { _host = net::host_create(address_family, _addr, port); @@ -274,6 +370,13 @@ namespace stream { // Get session associated with address. // If none are found, try to find a session not yet claimed. (It will be marked by a port of value 0 // If none of those are found, return nullptr + /** + * @brief Return the session value from the backend. + * + * @param peer Remote endpoint associated with the socket. + * @param connect_data Connect data. + * @return Existing session for the peer/connect-data pair, or nullptr when none matches. + */ session_t *get_session(const net::peer_t peer, uint32_t connect_data); // Circular dependency: @@ -281,6 +384,11 @@ namespace stream { // session refers to broadcast_ctx_t // broadcast_ctx_t refers to control_server_t // Therefore, iterate is implemented further down the source file + /** + * @brief Visit each active control server session. + * + * @param timeout Maximum time to wait for the operation. + */ void iterate(std::chrono::milliseconds timeout); /** @@ -292,10 +400,23 @@ namespace stream { */ void call(std::uint16_t type, session_t *session, const std::string_view &payload, bool reinjected); + /** + * @brief Register or visit handlers stored in the map. + * + * @param type Protocol, message, or resource type selector. + * @param cb Callback invoked for each matching message or session. + */ void map(uint16_t type, std::function cb) { _map_type_cb.emplace(type, std::move(cb)); } + /** + * @brief Send the serialized response over the active socket. + * + * @param payload Optional payload body to include in the response. + * @param peer Remote endpoint associated with the socket. + * @return Network operation status. + */ int send(const std::string_view &payload, net::peer_t peer) { auto packet = enet_packet_create(payload.data(), payload.size(), ENET_PACKET_FLAG_RELIABLE); if (enet_peer_send(peer, 0, packet)) { @@ -307,54 +428,63 @@ namespace stream { return 0; } + /** + * @brief Flush pending packets to the stream socket. + */ void flush() { enet_host_flush(_host.get()); } // Callbacks - std::unordered_map> _map_type_cb; + std::unordered_map> _map_type_cb; ///< Control-message handlers keyed by packet type. // All active sessions (including those still waiting for a peer to connect) - sync_util::sync_t> _sessions; + sync_util::sync_t> _sessions; ///< Active sessions registered with the control server. // ENet peer to session mapping for sessions with a peer connected - sync_util::sync_t> _peer_to_session; + sync_util::sync_t> _peer_to_session; ///< Peer to session. - ENetAddress _addr; - net::host_t _host; + ENetAddress _addr; ///< Local ENet address used by the control channel. + net::host_t _host; ///< ENet host object that owns the control socket. }; + /** + * @brief UDP broadcast socket and target address state. + */ struct broadcast_ctx_t { - message_queue_queue_t message_queue_queue; + message_queue_queue_t message_queue_queue; ///< Queues carrying encoded video and audio packets to sender threads. - std::thread recv_thread; - std::thread video_thread; - std::thread audio_thread; - std::thread control_thread; + std::thread recv_thread; ///< Thread that receives incoming control-channel messages. + std::thread video_thread; ///< Thread that sends encoded video packets. + std::thread audio_thread; ///< Thread that sends encoded audio packets. + std::thread control_thread; ///< Thread that runs the ENet control server. - asio::io_context io_context; + asio::io_context io_context; ///< Asio context used by the UDP broadcast sockets. - udp::socket video_sock {io_context}; - udp::socket audio_sock {io_context}; + udp::socket video_sock {io_context}; ///< UDP socket bound for video packet transmission. + udp::socket audio_sock {io_context}; ///< UDP socket bound for audio packet transmission. - control_server_t control_server; + control_server_t control_server; ///< ENet server for GameStream control packets. }; + /** + * @brief Runtime state for one audio/video streaming session. + */ struct session_t { - config_t config; + config_t config; ///< Stream or encoder configuration captured for the worker. - safe::mail_t mail; + safe::mail_t mail; ///< Mailbox used to distribute packets and lifecycle events. - std::shared_ptr input; + std::shared_ptr input; ///< Platform input device state for this stream. - std::thread audioThread; - std::thread videoThread; + std::thread audioThread; ///< Audio thread. + std::thread videoThread; ///< Video thread. - std::chrono::steady_clock::time_point pingTimeout; + std::chrono::steady_clock::time_point pingTimeout; ///< Deadline for receiving the next client ping. - safe::shared_t::ptr_t broadcast_ref; + safe::shared_t::ptr_t broadcast_ref; ///< Shared broadcast context retained while the session is active. - boost::asio::ip::address localAddress; + boost::asio::ip::address localAddress; ///< Local address. struct { std::string ping_payload; @@ -369,7 +499,7 @@ namespace stream { safe::mail_raw_t::event_t> invalidate_ref_frames_events; std::unique_ptr qos; - } video; + } video; ///< Video worker thread state for the active stream. struct { crypto::cipher::cbc_t cipher; @@ -386,7 +516,7 @@ namespace stream { audio_fec_packet_t fec_packet; std::unique_ptr qos; - } audio; + } audio; ///< Audio capture configuration for the stream.. struct { crypto::cipher::gcm_t cipher; @@ -402,15 +532,15 @@ namespace stream { platf::feedback_queue_t feedback_queue; safe::mail_raw_t::event_t hdr_queue; - } control; + } control; ///< Runtime state for the encrypted GameStream control channel. - std::uint32_t launch_session_id; - std::string client_cert; + std::uint32_t launch_session_id; ///< RTSP launch-session ID associated with this stream. + std::string client_cert; ///< PEM certificate for the paired client owning the stream. - safe::mail_raw_t::event_t shutdown_event; - safe::signal_t controlEnd; + safe::mail_raw_t::event_t shutdown_event; ///< Event raised when the stream should shut down. + safe::signal_t controlEnd; ///< Signal raised when the control channel exits. - std::atomic state; + std::atomic state; ///< Current lifecycle state observed by stream workers. }; /** @@ -470,7 +600,18 @@ namespace stream { return std::string_view {(char *) tagged_cipher.data(), packet_length + sizeof(control_encrypted_t) - sizeof(control_encrypted_t::seq)}; } + /** + * @brief Start periodic mDNS and service-discovery broadcasts. + * + * @param ctx Native context object used by the operation or callback. + * @return 0 on success; nonzero when broadcast setup fails. + */ int start_broadcast(broadcast_ctx_t &ctx); + /** + * @brief Stop broadcast processing. + * + * @param ctx Native context object used by the operation or callback. + */ void end_broadcast(broadcast_ctx_t &ctx); static auto broadcast = safe::make_shared(start_broadcast, end_broadcast); @@ -609,31 +750,54 @@ namespace stream { } namespace fec { + /** + * @brief Owning pointer for a Reed-Solomon encoder instance. + */ using rs_t = util::safe_ptr; + /** + * @brief Reed-Solomon FEC encoder state for video packets. + */ struct fec_t { - size_t data_shards; - size_t nr_shards; - size_t percentage; + size_t data_shards; ///< Number of original packet shards in each FEC block. + size_t nr_shards; ///< Total data and recovery shards generated for each FEC block. + size_t percentage; ///< Recovery-shard percentage requested for the stream. - size_t blocksize; - size_t prefixsize; - util::buffer_t shards; - util::buffer_t headers; - util::buffer_t shards_p; + size_t blocksize; ///< Bytes reserved for the payload portion of each shard. + size_t prefixsize; ///< Bytes reserved before each shard payload for protocol headers. + util::buffer_t shards; ///< Contiguous backing storage for all encoded FEC shards. + util::buffer_t headers; ///< Backing storage for the RTP/FEC headers attached to shards. + util::buffer_t shards_p; ///< Pointer table passed to the Reed-Solomon encoder. - std::vector payload_buffers; + std::vector payload_buffers; ///< Platform send descriptors for FEC payload buffers. + /** + * @brief Return the FEC shard data pointer for a packet-group element. + * + * @param el Packet-group element index. + * @return Pointer to the shard bytes for the requested element. + */ char *data(size_t el) { return (char *) shards_p[el]; } + /** + * @brief Return the FEC prefix bytes for the current packet group. + * + * @param el Packet-group element index. + * @return Pointer to the element's prefix bytes, or nullptr when no prefix is used. + */ char *prefix(size_t el) { return prefixsize ? &headers[el * prefixsize] : nullptr; } + /** + * @brief Return the serialized size of the current object. + * + * @return Number of elements currently stored. + */ size_t size() const { return nr_shards; } @@ -724,6 +888,8 @@ namespace stream { * @param slice_size The number of bytes between insertions. * @param data1 The first data buffer. * @param data2 The second data buffer. + * + * @return Combined buffer with insert padding written at each slice boundary. */ std::vector concat_and_insert(uint64_t insert_size, uint64_t slice_size, const std::string_view &data1, const std::string_view &data2) { auto data_size = data1.size() + data2.size(); @@ -763,6 +929,14 @@ namespace stream { return result; } + /** + * @brief Replace a byte sequence in an encoded packet. + * + * @param original Original text value used when reporting a parsing failure. + * @param old Byte sequence to replace in encoded packets. + * @param _new Replacement byte sequence inserted into encoded packets. + * @return Copy of the original buffer with each matching byte sequence replaced. + */ std::vector replace(const std::string_view &original, const std::string_view &old, const std::string_view &_new) { std::vector replaced; replaced.reserve(original.size() + _new.size() - old.size()); @@ -891,6 +1065,13 @@ namespace stream { return 0; } + /** + * @brief Send the selected HDR mode to the connected client over the control channel. + * + * @param session Active streaming or pairing session for the request. + * @param hdr_info HDR info. + * @return 0 when the control message is queued; nonzero when no control peer is ready. + */ int send_hdr_mode(session_t *session, video::hdr_info_t hdr_info) { if (!session->control.peer) { BOOST_LOG(warning) << "Couldn't send HDR mode, still waiting for PING from Moonlight"sv; @@ -920,6 +1101,11 @@ namespace stream { return 0; } + /** + * @brief Run the broadcast control-channel worker thread. + * + * @param server RTSP server instance handling the request. + */ void controlBroadcastThread(control_server_t *server) { server->map(packetTypes[IDX_PERIODIC_PING], [](session_t *session, const std::string_view &payload) { BOOST_LOG(verbose) << "type [IDX_PERIODIC_PING]"sv; @@ -1174,6 +1360,11 @@ namespace stream { server->flush(); } + /** + * @brief Receive thread data. + * + * @param ctx Native context object used by the operation or callback. + */ void recvThread(broadcast_ctx_t &ctx) { std::map peer_to_video_session; std::map peer_to_audio_session; @@ -1269,6 +1460,11 @@ namespace stream { } } + /** + * @brief Run the broadcast video sender thread. + * + * @param sock Socket used to read or write the protocol message. + */ void videoBroadcastThread(udp::socket &sock) { auto shutdown_event = mail::man->event(mail::broadcast_shutdown); auto packets = mail::man->queue(mail::video_packets); @@ -1593,6 +1789,11 @@ namespace stream { shutdown_event->raise(true); } + /** + * @brief Run the broadcast audio sender thread. + * + * @param sock Socket used to read or write the protocol message. + */ void audioBroadcastThread(udp::socket &sock) { auto shutdown_event = mail::man->event(mail::broadcast_shutdown); auto packets = mail::man->queue(mail::audio_packets); @@ -1698,6 +1899,9 @@ namespace stream { shutdown_event->raise(true); } + /** + * @brief Bind the GameStream UDP and control sockets used for a streaming session. + */ int start_broadcast(broadcast_ctx_t &ctx) { auto address_family = net::af_from_enum_string(config::sunshine.address_family); auto protocol = address_family == net::IPV4 ? udp::v4() : udp::v6(); @@ -1765,6 +1969,9 @@ namespace stream { return 0; } + /** + * @brief Stop broadcast processing. + */ void end_broadcast(broadcast_ctx_t &ctx) { auto broadcast_shutdown_event = mail::man->event(mail::broadcast_shutdown); @@ -1799,6 +2006,17 @@ namespace stream { broadcast_shutdown_event->reset(); } + /** + * @brief Receive ping data. + * + * @param session Active streaming or pairing session for the request. + * @param ref Reference frame metadata used by the encoder. + * @param type Protocol, message, or resource type selector. + * @param expected_payload Expected payload. + * @param peer Remote endpoint associated with the socket. + * @param timeout Maximum time to wait for the operation. + * @return Network operation status. + */ int recv_ping(session_t *session, decltype(broadcast)::ptr_t ref, socket_e type, std::string_view expected_payload, udp::endpoint &peer, std::chrono::milliseconds timeout) { auto messages = std::make_shared(30); av_session_id_t session_id = std::string {expected_payload}; @@ -1852,6 +2070,11 @@ namespace stream { return -1; } + /** + * @brief Run the session video capture and encode thread. + * + * @param session Active streaming or pairing session for the request. + */ void videoThread(session_t *session) { platf::set_thread_name("session::video"); auto fg = util::fail_guard([&]() { @@ -1874,6 +2097,11 @@ namespace stream { video::capture(session->mail, session->config.monitor, session); } + /** + * @brief Run the session audio capture and encode thread. + * + * @param session Active streaming or pairing session for the request. + */ void audioThread(session_t *session) { platf::set_thread_name("session::audio"); auto fg = util::fail_guard([&]() { @@ -1897,16 +2125,25 @@ namespace stream { } namespace session { - std::atomic_uint running_sessions; + std::atomic_uint running_sessions; ///< Running sessions. + /** + * @brief Platform handle returned from stream setup. + */ state_e state(session_t &session) { return session.state.load(std::memory_order_relaxed); } + /** + * @brief Return the paired client certificate for this session. + */ const std::string &client_cert(session_t &session) { return session.client_cert; } + /** + * @brief Stop the active streaming session and prevent new packets from being queued. + */ void stop(session_t &session) { while_starting_do_nothing(session.state); auto expected = state_e::RUNNING; @@ -1918,6 +2155,9 @@ namespace stream { session.shutdown_event->raise(true); } + /** + * @brief Wait for worker threads owned by the session to exit. + */ void join(session_t &session) { // Current Nvidia drivers have a bug where NVENC can deadlock the encoder thread with hardware-accelerated // GPU scheduling enabled. If this happens, we will terminate ourselves and the service can restart. @@ -1965,6 +2205,9 @@ namespace stream { BOOST_LOG(debug) << "Session ended"sv; } + /** + * @brief Start the audio, video, and control workers for a streaming session. + */ int start(session_t &session, const std::string &addr_string) { session.input = input::alloc(session.mail); @@ -2007,6 +2250,9 @@ namespace stream { return 0; } + /** + * @brief Allocate and initialize platform input state for a stream. + */ std::shared_ptr alloc(config_t &config, rtsp_stream::launch_session_t &launch_session) { auto session = std::make_shared(); diff --git a/src/stream.h b/src/stream.h index 5ee377764..c0b0f9623 100644 --- a/src/stream.h +++ b/src/stream.h @@ -16,29 +16,35 @@ #include "video.h" namespace stream { - constexpr auto VIDEO_STREAM_PORT = 9; - constexpr auto CONTROL_PORT = 10; - constexpr auto AUDIO_STREAM_PORT = 11; + constexpr auto VIDEO_STREAM_PORT = 9; ///< GameStream base-port offset used for the video UDP stream. + constexpr auto CONTROL_PORT = 10; ///< GameStream base-port offset used for the control channel. + constexpr auto AUDIO_STREAM_PORT = 11; ///< GameStream base-port offset used for the audio UDP stream. struct session_t; + /** + * @brief Stream configuration shared by capture and network senders. + */ struct config_t { - audio::config_t audio; - video::config_t monitor; + audio::config_t audio; ///< Audio capture configuration for the stream. + video::config_t monitor; ///< Video capture and encoder configuration for the selected monitor. - int packetsize; - int minRequiredFecPackets; - int mlFeatureFlags; - int controlProtocolType; - int audioQosType; - int videoQosType; + int packetsize; ///< Maximum payload size for network packets. + int minRequiredFecPackets; ///< Minimum recovery packets required before FEC is emitted. + int mlFeatureFlags; ///< Moonlight feature flags negotiated for this session. + int controlProtocolType; ///< GameStream control protocol variant selected by the client. + int audioQosType; ///< Audio QoS type. + int videoQosType; ///< Video QoS type. - uint32_t encryptionFlagsEnabled; + uint32_t encryptionFlagsEnabled; ///< Bitmask of GameStream encryption features enabled for the session. - std::optional gcmap; + std::optional gcmap; ///< Optional game-controller mapping override from the launch request. }; namespace session { + /** + * @brief Enumerates supported state options. + */ enum class state_e : int { STOPPED, ///< The session is stopped STOPPING, ///< The session is stopping @@ -46,11 +52,47 @@ namespace stream { RUNNING, ///< The session is running }; + /** + * @brief Allocate and initialize platform input state for a stream. + * + * @param config Configuration values to apply. + * @param launch_session Launch session. + * @return Allocated object or identifier, or an error value on failure. + */ std::shared_ptr alloc(config_t &config, rtsp_stream::launch_session_t &launch_session); + /** + * @brief Start a streaming session for the supplied peer address. + * + * @param session Active streaming or pairing session for the request. + * @param addr_string Addr string. + * @return Start status. + */ int start(session_t &session, const std::string &addr_string); + /** + * @brief Stop a streaming session and prevent more packets from being queued. + * + * @param session Active streaming or pairing session for the request. + */ void stop(session_t &session); + /** + * @brief Wait for worker threads owned by the session to exit. + * + * @param session Active streaming or pairing session for the request. + */ void join(session_t &session); + /** + * @brief Platform handle returned from stream setup. + * + * @param session Active streaming or pairing session for the request. + * @return Current lifecycle state for the stream session. + */ state_e state(session_t &session); + /** + * @brief Return the paired client certificate for a stream session. + * + * @param session Active streaming or pairing session for the request. + * @return PEM certificate associated with the session's client. + */ const std::string &client_cert(session_t &session); } // namespace session } // namespace stream diff --git a/src/sync.h b/src/sync.h index a9472a72d..23ed8666b 100644 --- a/src/sync.h +++ b/src/sync.h @@ -11,21 +11,46 @@ namespace sync_util { + /** + * @brief Value wrapper that pairs an object with the mutex protecting it. + */ template class sync_t { public: + /** + * @brief Type stored behind the synchronization wrapper. + */ using value_t = T; + /** + * @brief Mutex type used to protect the stored value. + */ using mutex_t = M; + /** + * @brief Acquire the underlying lock or keyed mutex. + * + * @return Lock guard owning the synchronized object until destruction. + */ std::lock_guard lock() { return std::lock_guard {_lock}; } + /** + * @brief Initialize the protected object from forwarded arguments. + * + * @param args Arguments forwarded to the protected object's constructor. + */ template sync_t(Args &&...args): raw {std::forward(args)...} { } + /** + * @brief Move another synchronized value into this instance while locking both wrappers. + * + * @param other Source object whose state is copied or moved into this object. + * @return Reference to this wrapper. + */ sync_t &operator=(sync_t &&other) noexcept { std::lock(_lock, other._lock); @@ -37,6 +62,12 @@ namespace sync_util { return *this; } + /** + * @brief Copy another synchronized value into this instance while locking both wrappers. + * + * @param other Source object whose state is copied or moved into this object. + * @return Reference to this wrapper. + */ sync_t &operator=(sync_t &other) noexcept { std::lock(_lock, other._lock); @@ -48,6 +79,12 @@ namespace sync_util { return *this; } + /** + * @brief Assign a new value while holding this wrapper's lock. + * + * @param val Value assigned to the synchronized object. + * @return Reference to this wrapper. + */ template sync_t &operator=(V &&val) { auto lg = lock(); @@ -57,6 +94,12 @@ namespace sync_util { return *this; } + /** + * @brief Copy-assign the protected value while holding this wrapper's lock. + * + * @param val Value assigned to the synchronized object. + * @return Reference to this wrapper. + */ sync_t &operator=(const value_t &val) noexcept { auto lg = lock(); @@ -65,6 +108,12 @@ namespace sync_util { return *this; } + /** + * @brief Move-assign the protected value while holding this wrapper's lock. + * + * @param val Value assigned to the synchronized object. + * @return Reference to this wrapper. + */ sync_t &operator=(value_t &&val) noexcept { auto lg = lock(); @@ -73,19 +122,34 @@ namespace sync_util { return *this; } + /** + * @brief Access the protected value directly. + * + * @return Pointer to the protected value. + */ value_t *operator->() { return &raw; } + /** + * @brief Dereference the protected value. + * + * @return Mutable reference to the protected value. + */ value_t &operator*() { return raw; } + /** + * @brief Dereference the protected value. + * + * @return Const reference to the protected value. + */ const value_t &operator*() const { return raw; } - value_t raw; + value_t raw; ///< Protected value accessed while this helper's lock is held. private: mutex_t _lock; diff --git a/src/system_tray.cpp b/src/system_tray.cpp index 36959df9e..9fd7931fa 100644 --- a/src/system_tray.cpp +++ b/src/system_tray.cpp @@ -6,12 +6,32 @@ #if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1 #if defined(_WIN32) + /** + * @def WIN32_LEAN_AND_MEAN + * @brief Macro for WIN32 LEAN AND MEAN. + */ #define WIN32_LEAN_AND_MEAN #include #include + /** + * @def TRAY_ICON + * @brief Macro for TRAY ICON. + */ #define TRAY_ICON WEB_DIR "images/sunshine.ico" + /** + * @def TRAY_ICON_PLAYING + * @brief Macro for TRAY ICON PLAYING. + */ #define TRAY_ICON_PLAYING WEB_DIR "images/sunshine-playing.ico" + /** + * @def TRAY_ICON_PAUSING + * @brief Macro for TRAY ICON PAUSING. + */ #define TRAY_ICON_PAUSING WEB_DIR "images/sunshine-pausing.ico" + /** + * @def TRAY_ICON_LOCKED + * @brief Macro for TRAY ICON LOCKED. + */ #define TRAY_ICON_LOCKED WEB_DIR "images/sunshine-locked.ico" #elif defined(__linux__) || defined(linux) || defined(__linux) || defined(__FreeBSD__) #define TRAY_ICON WEB_DIR "images/logo-sunshine.svg" @@ -156,6 +176,12 @@ namespace system_tray { .allIconPaths = {TRAY_ICON, TRAY_ICON_LOCKED, TRAY_ICON_PLAYING, TRAY_ICON_PAUSING}, }; + /** + * @brief Get resource path. + * + * @param relativePath Relative path. + * @return Absolute path to the resource file for the current platform bundle layout. + */ const char *GetResourcePath(const char *relativePath) { #ifdef __APPLE__ if (!relativePath || !*relativePath) { diff --git a/src/task_pool.h b/src/task_pool.h index 081be24d0..887db57f6 100644 --- a/src/task_pool.h +++ b/src/task_pool.h @@ -21,42 +21,80 @@ namespace task_pool_util { + /** + * @brief Type-erased task interface stored by the task pool. + */ class _ImplBase { public: // _unique_base_type _this_ptr; inline virtual ~_ImplBase() = default; + /** + * @brief Execute the queued task body. + */ virtual void run() = 0; }; + /** + * @brief Concrete task wrapper that stores and invokes a callable. + */ template class _Impl: public _ImplBase { Function _func; public: + /** + * @brief Store a concrete callable behind the task type-erasure interface. + * + * @param f Callable executed by the helper. + */ _Impl(Function &&f): _func(std::forward(f)) { } + /** + * @brief Execute the queued task body. + */ void run() override { _func(); } }; + /** + * @brief Queue of immediate and delayed tasks executed by worker threads. + */ class TaskPool { public: + /** + * @brief Type-erased task owned by the task pool. + */ typedef std::unique_ptr<_ImplBase> __task; + /** + * @brief Stable pointer used to identify a queued task. + */ typedef _ImplBase *task_id_t; + /** + * @brief Steady-clock timestamp used to schedule delayed tasks. + */ typedef std::chrono::steady_clock::time_point __time_point; + /** + * @brief Queued delayed task identifier paired with its future result. + */ template class timer_task_t { public: - task_id_t task_id; - std::future future; + task_id_t task_id; ///< Task ID. + std::future future; ///< Future that resolves with the timer task result. + /** + * @brief Track a queued timer task and the future for its result. + * + * @param task_id Identifier returned when the task was queued. + * @param future Future object whose result will be collected later. + */ timer_task_t(task_id_t task_id, std::future &future): task_id {task_id}, future {std::move(future)} { @@ -64,18 +102,29 @@ namespace task_pool_util { }; protected: - std::deque<__task> _tasks; - std::vector> _timer_tasks; - std::mutex _task_mutex; + std::deque<__task> _tasks; ///< Immediate tasks waiting for worker execution. + std::vector> _timer_tasks; ///< Delayed tasks sorted by their due time. + std::mutex _task_mutex; ///< Mutex protecting task queues and worker wakeups. public: TaskPool() = default; + /** + * @brief Move queued tasks and timers from another task pool. + * + * @param other Pool whose queues and worker state are moved into this pool. + */ TaskPool(TaskPool &&other) noexcept: _tasks {std::move(other._tasks)}, _timer_tasks {std::move(other._timer_tasks)} { } + /** + * @brief Assign state from another instance while preserving ownership semantics. + * + * @param other Source object whose state is copied or moved into this object. + * @return Reference or value produced by the operator. + */ TaskPool &operator=(TaskPool &&other) noexcept { std::swap(_tasks, other._tasks); std::swap(_timer_tasks, other._timer_tasks); @@ -83,6 +132,13 @@ namespace task_pool_util { return *this; } + /** + * @brief Queue work for asynchronous execution. + * + * @param newTask New task. + * @param args Arguments forwarded to the callable or parser. + * @return Identifier assigned to the queued task. + */ template auto push(Function &&newTask, Args &&...args) { static_assert(std::is_invocable_v, "arguments don't match the function"); @@ -104,6 +160,11 @@ namespace task_pool_util { return future; } + /** + * @brief Queue a task that becomes runnable after a delay. + * + * @param task Task object to enqueue or execute. + */ void pushDelayed(std::pair<__time_point, __task> &&task) { std::lock_guard lg(_task_mutex); @@ -119,6 +180,10 @@ namespace task_pool_util { /** * @return An id to potentially delay the task. + * + * @param newTask New task. + * @param duration Delay before the timed task should run. + * @param args Arguments forwarded to the callable or parser. */ template auto pushDelayed(Function &&newTask, std::chrono::duration duration, Args &&...args) { @@ -185,6 +250,12 @@ namespace task_pool_util { } } + /** + * @brief Cancel a queued or delayed task before it runs. + * + * @param task_id Identifier returned when the task was queued. + * @return True when the task was found and cancelled. + */ bool cancel(task_id_t task_id) { std::lock_guard lg(_task_mutex); @@ -202,6 +273,12 @@ namespace task_pool_util { return false; } + /** + * @brief Remove and return the next queued item, waiting when requested. + * + * @param task_id Identifier returned when the task was queued. + * @return Removed queue item, or empty result when the queue is stopped or empty. + */ std::optional> pop(task_id_t task_id) { std::lock_guard lg(_task_mutex); @@ -216,6 +293,11 @@ namespace task_pool_util { return std::move(*pos); } + /** + * @brief Remove and return the next queued item, waiting when requested. + * + * @return Removed queue item, or empty result when the queue is stopped or empty. + */ std::optional<__task> pop() { std::lock_guard lg(_task_mutex); @@ -234,12 +316,22 @@ namespace task_pool_util { return std::nullopt; } + /** + * @brief Check whether the task pool has active workers. + * + * @return True when the task was found and cancelled. + */ bool ready() { std::lock_guard lg(_task_mutex); return !_tasks.empty() || (!_timer_tasks.empty() && std::get<0>(_timer_tasks.back()) <= std::chrono::steady_clock::now()); } + /** + * @brief Return the next timer task ready for execution. + * + * @return Time point for the next queued timer task, or std::nullopt when none exist. + */ std::optional<__time_point> next() { std::lock_guard lg(_task_mutex); diff --git a/src/thread_pool.h b/src/thread_pool.h index 46558ad88..bb8c234b0 100644 --- a/src/thread_pool.h +++ b/src/thread_pool.h @@ -17,6 +17,9 @@ namespace thread_pool_util { */ class ThreadPool: public task_pool_util::TaskPool { public: + /** + * @brief Callable unit executed by a worker thread. + */ typedef TaskPool::__task __task; private: @@ -32,6 +35,11 @@ namespace thread_pool_util { _continue {false} { } + /** + * @brief Start a pool with the requested number of worker threads. + * + * @param threads Number of worker threads to start. + */ explicit ThreadPool(int threads): _thread(threads), _continue {true} { @@ -49,6 +57,13 @@ namespace thread_pool_util { join(); } + /** + * @brief Queue work for asynchronous execution. + * + * @param newTask New task. + * @param args Arguments forwarded to the callable or parser. + * @return Identifier assigned to the queued task. + */ template auto push(Function &&newTask, Args &&...args) { std::lock_guard lg(_lock); @@ -58,12 +73,25 @@ namespace thread_pool_util { return future; } + /** + * @brief Queue a task that becomes runnable after a delay. + * + * @param task Task object to enqueue or execute. + */ void pushDelayed(std::pair<__time_point, __task> &&task) { std::lock_guard lg(_lock); TaskPool::pushDelayed(std::move(task)); } + /** + * @brief Queue a task that becomes runnable after a delay. + * + * @param newTask New task. + * @param duration Delay before the timed task should run. + * @param args Arguments forwarded to the callable or parser. + * @return Future that becomes ready after the delayed task completes. + */ template auto pushDelayed(Function &&newTask, std::chrono::duration duration, Args &&...args) { std::lock_guard lg(_lock); @@ -74,6 +102,11 @@ namespace thread_pool_util { return future; } + /** + * @brief Start worker threads for queued thread-pool tasks. + * + * @param threads Number of worker threads to start. + */ void start(int threads) { _continue = true; @@ -84,6 +117,9 @@ namespace thread_pool_util { } } + /** + * @brief Stop worker threads and prevent additional task execution. + */ void stop() { std::lock_guard lg(_lock); @@ -91,6 +127,9 @@ namespace thread_pool_util { _cv.notify_all(); } + /** + * @brief Wait for worker threads owned by the session to exit. + */ void join() { for (auto &t : _thread) { t.join(); @@ -98,6 +137,9 @@ namespace thread_pool_util { } public: + /** + * @brief Run the main application or worker loop. + */ void _main() { platf::set_thread_name("TaskPool::worker"); while (_continue) { diff --git a/src/thread_safe.h b/src/thread_safe.h index 4f2448f1b..692e3d243 100644 --- a/src/thread_safe.h +++ b/src/thread_safe.h @@ -17,11 +17,22 @@ #include "utility.h" namespace safe { + /** + * @brief Thread-safe event value that blocks readers until a value is raised. + */ template class event_t { public: + /** + * @brief Status value stored in the event. + */ using status_t = util::optional_t; + /** + * @brief Notify waiters that a new event value is available. + * + * @param args Arguments forwarded to the callable or parser. + */ template void raise(Args &&...args) { std::lock_guard lg {_lock}; @@ -39,6 +50,11 @@ namespace safe { } // pop and view should not be used interchangeably + /** + * @brief Remove and return the next queued item, waiting when requested. + * + * @return Removed queue item, or empty result when the queue is stopped or empty. + */ status_t pop() { std::unique_lock ul {_lock}; @@ -60,6 +76,12 @@ namespace safe { } // pop and view should not be used interchangeably + /** + * @brief Remove and return the next queued item, waiting when requested. + * + * @param delay Maximum wait duration before timing out. + * @return Removed queue item, or empty result when the queue is stopped or empty. + */ template status_t pop(std::chrono::duration delay) { std::unique_lock ul {_lock}; @@ -77,6 +99,11 @@ namespace safe { } // pop and view should not be used interchangeably + /** + * @brief Read the current value without removing it from the queue. + * + * @return Optional copy of the front item, or empty status when the queue is closed. + */ status_t view() { std::unique_lock ul {_lock}; @@ -96,6 +123,12 @@ namespace safe { } // pop and view should not be used interchangeably + /** + * @brief Read the current value without removing it from the queue. + * + * @param delay Maximum wait duration before timing out. + * @return Optional copy of the front item, or empty status after timeout/closure. + */ template status_t view(std::chrono::duration delay) { std::unique_lock ul {_lock}; @@ -113,10 +146,18 @@ namespace safe { return _status; } + /** + * @brief Inspect the next queued value without popping it. + * + * @return True when a value is available to inspect. + */ bool peek() { return _continue && (bool) _status; } + /** + * @brief Raise the alarm and wake all waiting threads. + */ void stop() { std::lock_guard lg {_lock}; @@ -125,6 +166,9 @@ namespace safe { _cv.notify_all(); } + /** + * @brief Reset the object to its initial empty state. + */ void reset() { std::lock_guard lg {_lock}; @@ -133,6 +177,11 @@ namespace safe { _status = util::false_v; } + /** + * @brief Return whether the queue is still running. + * + * @return True while the queue accepts producers and consumers. + */ [[nodiscard]] bool running() const { return _continue; } @@ -145,11 +194,22 @@ namespace safe { std::mutex _lock; }; + /** + * @brief Alarm primitive used to wait for and raise shutdown-style signals. + */ template class alarm_raw_t { public: + /** + * @brief Status value carried by the alarm. + */ using status_t = util::optional_t; + /** + * @brief Mark the alarm as rung and wake waiting threads. + * + * @param status Native status code returned by the platform API. + */ void ring(const status_t &status) { std::lock_guard lg(_lock); @@ -158,6 +218,11 @@ namespace safe { _cv.notify_one(); } + /** + * @brief Mark the alarm as rung and wake waiting threads. + * + * @param status Native status code returned by the platform API. + */ void ring(status_t &&status) { std::lock_guard lg(_lock); @@ -166,6 +231,12 @@ namespace safe { _cv.notify_one(); } + /** + * @brief Wait for for. + * + * @param rel_time Rel time. + * @return Wait result indicating whether the condition was satisfied before timeout. + */ template auto wait_for(const std::chrono::duration &rel_time) { std::unique_lock ul(_lock); @@ -175,6 +246,13 @@ namespace safe { }); } + /** + * @brief Wait for for. + * + * @param rel_time Rel time. + * @param pred Predicate used to decide whether the wait is complete. + * @return Wait result indicating whether the condition was satisfied before timeout. + */ template auto wait_for(const std::chrono::duration &rel_time, Pred &&pred) { std::unique_lock ul(_lock); @@ -184,6 +262,12 @@ namespace safe { }); } + /** + * @brief Wait for until. + * + * @param rel_time Rel time. + * @return Wait result indicating whether the condition was satisfied before the deadline. + */ template auto wait_until(const std::chrono::duration &rel_time) { std::unique_lock ul(_lock); @@ -193,6 +277,13 @@ namespace safe { }); } + /** + * @brief Wait for until. + * + * @param rel_time Rel time. + * @param pred Predicate used to decide whether the wait is complete. + * @return Wait result indicating whether the condition was satisfied before the deadline. + */ template auto wait_until(const std::chrono::duration &rel_time, Pred &&pred) { std::unique_lock ul(_lock); @@ -202,6 +293,11 @@ namespace safe { }); } + /** + * @brief Block until the event is raised. + * + * @return Lock object held after the event is observed. + */ auto wait() { std::unique_lock ul(_lock); _cv.wait(ul, [this]() { @@ -209,6 +305,12 @@ namespace safe { }); } + /** + * @brief Block until the event is raised and the predicate accepts the state. + * + * @param pred Predicate used to decide whether the wait is complete. + * @return Lock object held after the predicate succeeds. + */ template auto wait(Pred &&pred) { std::unique_lock ul(_lock); @@ -217,14 +319,27 @@ namespace safe { }); } + /** + * @brief Return or update the current status value. + * + * @return Status status. + */ const status_t &status() const { return _status; } + /** + * @brief Return or update the current status value. + * + * @return Status status. + */ status_t &status() { return _status; } + /** + * @brief Reset the object to its initial empty state. + */ void reset() { _status = status_t {}; _rang = false; @@ -238,23 +353,47 @@ namespace safe { bool _rang {false}; }; + /** + * @brief Shared alarm primitive used for cross-thread shutdown signals. + */ template using alarm_t = std::shared_ptr>; + /** + * @brief Create a alarm object or message. + * + * @return Constructed alarm object. + */ template alarm_t make_alarm() { return std::make_shared>(); } + /** + * @brief Thread-safe queue with blocking and shutdown-aware consumers. + */ template class queue_t { public: + /** + * @brief Value type stored in the queue. + */ using status_t = util::optional_t; + /** + * @brief Construct a bounded blocking queue. + * + * @param max_elements Maximum number of queued elements before producers block. + */ queue_t(std::uint32_t max_elements = 32): _max_elements {max_elements} { } + /** + * @brief Notify waiters that a new event value is available. + * + * @param args Arguments forwarded to the callable or parser. + */ template void raise(Args &&...args) { std::lock_guard ul {_lock}; @@ -272,10 +411,21 @@ namespace safe { _cv.notify_all(); } + /** + * @brief Inspect the next queued value without popping it. + * + * @return True when a value is available to inspect. + */ bool peek() { return _continue && !_queue.empty(); } + /** + * @brief Remove and return the next queued item, waiting when requested. + * + * @param delay Maximum wait duration before timing out. + * @return Removed queue item, or empty result when the queue is stopped or empty. + */ template status_t pop(std::chrono::duration delay) { std::unique_lock ul {_lock}; @@ -296,6 +446,11 @@ namespace safe { return val; } + /** + * @brief Remove and return the next queued item, waiting when requested. + * + * @return Removed queue item, or empty result when the queue is stopped or empty. + */ status_t pop() { std::unique_lock ul {_lock}; @@ -317,10 +472,18 @@ namespace safe { return val; } + /** + * @brief Return the wrapped pointer without synchronization checks. + * + * @return Mutable queue storage for callers that already hold external synchronization. + */ std::vector &unsafe() { return _queue; } + /** + * @brief Shut down the queue and wake blocked consumers. + */ void stop() { std::lock_guard lg {_lock}; @@ -329,6 +492,11 @@ namespace safe { _cv.notify_all(); } + /** + * @brief Return whether the queue is still running. + * + * @return True while the queue accepts producers and consumers. + */ [[nodiscard]] bool running() const { return _continue; } @@ -343,30 +511,60 @@ namespace safe { std::vector _queue; }; + /** + * @brief Shared object storage with custom construction and destruction hooks. + */ template class shared_t { public: + /** + * @brief Object type stored in shared protected storage. + */ using element_type = T; + /** + * @brief Callback signature used to construct the protected object. + */ using construct_f = std::function; + /** + * @brief Callback signature used to destroy the protected object. + */ using destruct_f = std::function; + /** + * @brief Pointer wrapper state protected by synchronization. + */ struct ptr_t { - shared_t *owner; + shared_t *owner; ///< Shared object that owns the protected value. ptr_t(): owner {nullptr} { } + /** + * @brief Construct a reference bound to a shared protected object. + * + * @param owner Shared object that owns this pointer wrapper. + */ explicit ptr_t(shared_t *owner): owner {owner} { } + /** + * @brief Move a shared-object reference without locking the protected object. + * + * @param ptr Pointer managed by the safe pointer wrapper. + */ ptr_t(ptr_t &&ptr) noexcept: owner {ptr.owner} { ptr.owner = nullptr; } + /** + * @brief Copy a shared-object reference without locking the protected object. + * + * @param ptr Pointer managed by the safe pointer wrapper. + */ ptr_t(const ptr_t &ptr) noexcept: owner {ptr.owner} { if (!owner) { @@ -377,6 +575,12 @@ namespace safe { tmp.owner = nullptr; } + /** + * @brief Assign state from another instance while preserving ownership semantics. + * + * @param ptr Pointer managed by the safe pointer wrapper. + * @return Reference or value produced by the operator. + */ ptr_t &operator=(const ptr_t &ptr) noexcept { if (!ptr.owner) { release(); @@ -387,6 +591,12 @@ namespace safe { return *this = std::move(*ptr.owner->ref()); } + /** + * @brief Assign state from another instance while preserving ownership semantics. + * + * @param ptr Pointer managed by the safe pointer wrapper. + * @return Reference or value produced by the operator. + */ ptr_t &operator=(ptr_t &&ptr) noexcept { if (owner) { release(); @@ -403,10 +613,16 @@ namespace safe { } } + /** + * @brief Check whether this shared pointer wrapper owns a live object. + */ operator bool() const { return owner != nullptr; } + /** + * @brief Release the COM or platform reference owned by the pointer. + */ void release() { std::lock_guard lg {owner->_lock}; @@ -418,21 +634,42 @@ namespace safe { owner = nullptr; } + /** + * @brief Return the currently wrapped value or handle. + * + * @return Underlying native handle or object pointer. + */ element_type *get() const { return reinterpret_cast(owner->_object_buf.data()); } + /** + * @brief Access the shared protected object. + * + * @return Pointer to the object stored in the shared wrapper. + */ element_type *operator->() { return reinterpret_cast(owner->_object_buf.data()); } }; + /** + * @brief Construct a shared protected object with custom close and destruction hooks. + * + * @param fc Callable invoked when the shared object is closed. + * @param fd Callable invoked when the shared object is destroyed. + */ template shared_t(FC &&fc, FD &&fd): _construct {std::forward(fc)}, _destruct {std::forward(fd)} { } + /** + * @brief Return a shared reference to the protected object. + * + * @return Reference handle that keeps the protected object alive. + */ [[nodiscard]] ptr_t ref() { std::lock_guard lg {_lock}; @@ -458,6 +695,13 @@ namespace safe { std::mutex _lock; }; + /** + * @brief Create a shared object or message. + * + * @param fc Callable executed while the shared object is locked. + * @param fd Native file descriptor to wrap or inspect. + * @return Constructed shared object. + */ template auto make_shared(F_Construct &&fc, F_Destruct &&fd) { return shared_t { @@ -466,42 +710,83 @@ namespace safe { }; } + /** + * @brief Boolean alarm used as a simple signal. + */ using signal_t = event_t; class mail_raw_t; + /** + * @brief Shared mailbox handle used by event and queue wrappers. + */ using mail_t = std::shared_ptr; + /** + * @brief Run cleanup for completed asynchronous work. + * + * @param mail Mailbox used to exchange messages with worker threads. + */ void cleanup(mail_raw_t *); + /** + * @brief Wrapper that posts cleanup work to a mailbox when destroyed. + */ template class post_t: public T { public: + /** + * @brief Construct a message-posting wrapper around an existing type. + * + * @param mail Mailbox used to exchange messages with worker threads. + * @param args Arguments forwarded to the wrapped type constructor. + */ template post_t(mail_t mail, Args &&...args): T(std::forward(args)...), mail {std::move(mail)} { } - mail_t mail; + mail_t mail; ///< Mailbox kept alive until the posted object is destroyed. ~post_t() { cleanup(mail.get()); } }; + /** + * @brief Acquire the underlying lock or keyed mutex. + * + * @param wp Weak pointer used to test whether the object is still alive. + * @return Lock guard owning the synchronized object until destruction. + */ template inline auto lock(const std::weak_ptr &wp) { return std::reinterpret_pointer_cast(wp.lock()); } + /** + * @brief Mailbox backing store for events, queues, and posted cleanup objects. + */ class mail_raw_t: public std::enable_shared_from_this { public: + /** + * @brief Mailbox-backed event wrapper for a typed value. + */ template using event_t = std::shared_ptr>>; + /** + * @brief Mailbox-backed queue wrapper for typed messages. + */ template using queue_t = std::shared_ptr>>; + /** + * @brief Create a typed event channel from the raw mailbox. + * + * @param id Identifier for the controller, session, display, or resource. + * @return Typed event channel associated with the supplied identifier. + */ template event_t event(const std::string_view &id) { std::lock_guard lg {mutex}; @@ -517,6 +802,12 @@ namespace safe { return post; } + /** + * @brief Create a typed queue channel from the raw mailbox. + * + * @param id Identifier for the controller, session, display, or resource. + * @return Typed queue channel associated with the supplied identifier. + */ template queue_t queue(const std::string_view &id) { std::lock_guard lg {mutex}; @@ -532,6 +823,9 @@ namespace safe { return post; } + /** + * @brief Run cleanup for completed asynchronous work. + */ void cleanup() { std::lock_guard lg {mutex}; @@ -546,11 +840,14 @@ namespace safe { } } - std::mutex mutex; + std::mutex mutex; ///< Mutex protecting the map of live posted objects. - std::map, std::less<>> id_to_post; + std::map, std::less<>> id_to_post; ///< Posted objects keyed by cleanup identifier. }; + /** + * @brief Run cleanup for completed asynchronous work. + */ inline void cleanup(mail_raw_t *mail) { mail->cleanup(); } diff --git a/src/upnp.cpp b/src/upnp.cpp index c65e0a664..96e51e397 100644 --- a/src/upnp.cpp +++ b/src/upnp.cpp @@ -25,14 +25,17 @@ using namespace std::literals; namespace upnp { + /** + * @brief UPnP port mapping description and lease state. + */ struct mapping_t { struct { std::string wan; std::string lan; std::string proto; - } port; + } port; ///< WAN/LAN/protocol tuple for the mapped port. - std::string description; + std::string description; ///< Human-readable UPnP lease description advertised to the gateway. }; static std::string_view status_string(int status) { @@ -62,6 +65,9 @@ namespace upnp { #endif } + /** + * @brief RAII helper that runs shutdown cleanup when destroyed. + */ class deinit_t: public platf::deinit_t { public: deinit_t() { @@ -91,6 +97,9 @@ namespace upnp { upnp_thread = std::thread {&deinit_t::upnp_thread_proc, this}; } + /** + * @brief Destroy the UPnP deinitializer. + */ ~deinit_t() { upnp_thread.join(); } @@ -363,10 +372,13 @@ namespace upnp { } } - std::vector mappings; - std::thread upnp_thread; + std::vector mappings; ///< Port mappings Sunshine should keep registered with the gateway. + std::thread upnp_thread; ///< Worker thread that refreshes mappings until shutdown. }; + /** + * @brief Start UPnP port mapping and return its shutdown guard. + */ std::unique_ptr start() { if (!config::sunshine.flags[config::flag::UPNP]) { return nullptr; diff --git a/src/upnp.h b/src/upnp.h index cc7eb32a4..f34b90fa6 100644 --- a/src/upnp.h +++ b/src/upnp.h @@ -14,17 +14,20 @@ * @brief UPnP port mapping. */ namespace upnp { - constexpr auto INET6_ADDRESS_STRLEN = 46; - constexpr auto IPv4 = 0; - constexpr auto IPv6 = 1; - constexpr auto PORT_MAPPING_LIFETIME = 3600s; - constexpr auto REFRESH_INTERVAL = 120s; + constexpr auto INET6_ADDRESS_STRLEN = 46; ///< Protocol or platform constant for inet6 address strlen. + constexpr auto IPv4 = 0; ///< I pv4. + constexpr auto IPv6 = 1; ///< I pv6. + constexpr auto PORT_MAPPING_LIFETIME = 3600s; ///< GameStream port offset for port mapping lifetime. + constexpr auto REFRESH_INTERVAL = 120s; ///< Protocol or platform constant for refresh interval. + /** + * @brief Owning pointer to miniupnpc device discovery results. + */ using device_t = util::safe_ptr; KITTY_USING_MOVE_T(urls_t, UPNPUrls, , { FreeUPNPUrls(&el); - }); + }); ///< Alias for element type. /** * @brief Get the valid IGD status. @@ -40,5 +43,10 @@ namespace upnp { */ int UPNP_GetValidIGDStatus(device_t &device, urls_t *urls, IGDdatas *data, std::array &lan_addr); + /** + * @brief Start UPnP port mapping and return its shutdown guard. + * + * @return Start status. + */ [[nodiscard]] std::unique_ptr start(); } // namespace upnp diff --git a/src/utility.h b/src/utility.h index db5d7f9d2..3bb07957a 100644 --- a/src/utility.h +++ b/src/utility.h @@ -17,6 +17,10 @@ #include #include +/** + * @def KITTY_WHILE_LOOP(x, y, z) + * @brief Execute an initializer followed by a while loop body without leaking helper names. + */ #define KITTY_WHILE_LOOP(x, y, z) \ { \ x; \ @@ -26,99 +30,239 @@ template struct argument_type; +/** + * @brief Extracts the argument type from a single-argument function signature. + */ template struct argument_type { + /** + * @brief Type extracted from the matched function signature. + */ typedef U type; }; +/** + * @def KITTY_USING_MOVE_T(move_t, t, init_val, z) + * @brief Define a move-only RAII wrapper with caller-provided initial value and destructor body. + */ #define KITTY_USING_MOVE_T(move_t, t, init_val, z) \ + /** \ + * @brief Move-only RAII wrapper generated by KITTY_USING_MOVE_T. \ + */ \ class move_t { \ public: \ + /** \ + * @brief Wrapped element type. \ + */ \ using element_type = typename argument_type::type; \ \ + /** \ + * @brief Initialize the generated wrapper with the configured initial value. \ + */ \ move_t(): \ el {init_val} { \ } \ + /** \ + * @brief Initialize the generated wrapper from forwarded element arguments. \ + * \ + * @param args Arguments forwarded to the wrapped element. \ + */ \ template \ move_t(Args &&...args): \ el {std::forward(args)...} { \ } \ + /** \ + * @brief Copy construction is disabled. \ + */ \ move_t(const move_t &) = delete; \ \ + /** \ + * @brief Move-construct the wrapper and reset the source wrapper. \ + * \ + * @param other Wrapper to move from. \ + */ \ move_t(move_t &&other) noexcept: \ el {std::move(other.el)} { \ other.el = element_type {init_val}; \ } \ \ + /** \ + * @brief Copy assignment is disabled. \ + */ \ move_t &operator=(const move_t &) = delete; \ \ + /** \ + * @brief Move-assign the wrapped element by swapping with another wrapper. \ + * \ + * @param other Wrapper to move from. \ + * @return This wrapper. \ + */ \ move_t &operator=(move_t &&other) { \ std::swap(el, other.el); \ return *this; \ } \ + /** \ + * @brief Access the wrapped element. \ + * \ + * @return Pointer to the wrapped element. \ + */ \ element_type *operator->() { \ return ⪙ \ } \ + /** \ + * @brief Access the wrapped element. \ + * \ + * @return Pointer to the wrapped element. \ + */ \ const element_type *operator->() const { \ return ⪙ \ } \ \ + /** \ + * @brief Release the wrapped element and reset the wrapper. \ + * \ + * @return Previously wrapped element after ownership is released. \ + */ \ inline element_type release() { \ element_type val = std::move(el); \ el = element_type {init_val}; \ return val; \ } \ \ + /** \ + * @brief Destroy the wrapper and run the configured cleanup body. \ + */ \ ~move_t() z \ \ + /** \ + * @brief Wrapped element value. \ + */ \ element_type el; \ } +/** + * @def KITTY_DECL_CONSTR(x) + * @brief Declare the standard move operations and out-of-line default constructor for a type. + */ #define KITTY_DECL_CONSTR(x) \ + /** \ + * @brief Defaulted move constructor for the generated type. \ + */ \ x(x &&) noexcept = default; \ + /** \ + * @brief Defaulted move assignment for the generated type. \ + * \ + * @return This instance. \ + */ \ x &operator=(x &&) noexcept = default; \ + /** \ + * @brief Default constructor for the generated type. \ + */ \ x(); +/** + * @def KITTY_DEFAULT_CONSTR_MOVE(x) + * @brief Declare defaulted noexcept move construction and assignment for a type. + */ #define KITTY_DEFAULT_CONSTR_MOVE(x) \ + /** \ + * @brief Defaulted move constructor for the generated type. \ + */ \ x(x &&) noexcept = default; \ + /** \ + * @brief Defaulted move assignment for the generated type. \ + * \ + * @return This instance. \ + */ \ x &operator=(x &&) noexcept = default; +/** + * @def KITTY_DEFAULT_CONSTR_MOVE_THROW(x) + * @brief Declare defaulted move construction and assignment that may throw. + */ #define KITTY_DEFAULT_CONSTR_MOVE_THROW(x) \ + /** \ + * @brief Defaulted move constructor for the generated type. \ + */ \ x(x &&) = default; \ + /** \ + * @brief Defaulted move assignment for the generated type. \ + * \ + * @return This instance. \ + */ \ x &operator=(x &&) = default; \ + /** \ + * @brief Default constructor for the generated type. \ + */ \ x() = default; +/** + * @def KITTY_DEFAULT_CONSTR(x) + * @brief Declare defaulted copy and move operations for a value type. + */ #define KITTY_DEFAULT_CONSTR(x) \ KITTY_DEFAULT_CONSTR_MOVE(x) \ + /** \ + * @brief Defaulted copy constructor for the generated type. \ + */ \ x(const x &) noexcept = default; \ + /** \ + * @brief Defaulted copy assignment for the generated type. \ + * \ + * @return This instance. \ + */ \ x &operator=(const x &) = default; +/** + * @def TUPLE_2D(a, b, expr) + * @brief Evaluate an expression returning a 2-tuple and bind both elements to local references. + */ #define TUPLE_2D(a, b, expr) \ decltype(expr) a##_##b = expr; \ auto &a = std::get<0>(a##_##b); \ auto &b = std::get<1>(a##_##b) +/** + * @def TUPLE_2D_REF(a, b, expr) + * @brief Bind both elements of an existing 2-tuple expression to local references. + */ #define TUPLE_2D_REF(a, b, expr) \ auto &a##_##b = expr; \ auto &a = std::get<0>(a##_##b); \ auto &b = std::get<1>(a##_##b) +/** + * @def TUPLE_3D(a, b, c, expr) + * @brief Evaluate an expression returning a 3-tuple and bind all elements to local references. + */ #define TUPLE_3D(a, b, c, expr) \ decltype(expr) a##_##b##_##c = expr; \ auto &a = std::get<0>(a##_##b##_##c); \ auto &b = std::get<1>(a##_##b##_##c); \ auto &c = std::get<2>(a##_##b##_##c) +/** + * @def TUPLE_3D_REF(a, b, c, expr) + * @brief Bind all elements of an existing 3-tuple expression to local references. + */ #define TUPLE_3D_REF(a, b, c, expr) \ auto &a##_##b##_##c = expr; \ auto &a = std::get<0>(a##_##b##_##c); \ auto &b = std::get<1>(a##_##b##_##c); \ auto &c = std::get<2>(a##_##b##_##c) +/** + * @def TUPLE_EL(a, b, expr) + * @brief Evaluate a tuple expression and bind one selected element to a local reference. + */ #define TUPLE_EL(a, b, expr) \ decltype(expr) a##_ = expr; \ auto &a = std::get(a##_) +/** + * @def TUPLE_EL_REF(a, b, expr) + * @brief Bind one selected element of an existing tuple expression to a local reference. + */ #define TUPLE_EL_REF(a, b, expr) \ auto &a = std::get(expr) @@ -136,16 +280,31 @@ namespace util { template struct __either; + /** + * @brief Type selector that chooses the first type when the condition is true. + */ template struct __either { + /** + * @brief Type extracted from the matched function signature. + */ using type = X; }; + /** + * @brief Type selector that chooses the second type when the condition is false. + */ template struct __either { + /** + * @brief Type extracted from the matched function signature. + */ using type = Y; }; + /** + * @brief Compile-time selector for one of two types. + */ template using either_t = typename __either::type; @@ -153,18 +312,34 @@ namespace util { struct overloaded: Ts... { using Ts::operator()...; }; + /** + * @brief Combine multiple callables into one overload set for std::visit. + */ template overloaded(Ts...) -> overloaded; + /** + * @brief Scope guard that runs a cleanup action unless it is disabled. + */ template class FailGuard { public: FailGuard() = delete; + /** + * @brief Construct a fail guard that owns the cleanup callback. + * + * @param f Callable executed by the helper. + */ FailGuard(T &&f) noexcept: _func {std::forward(f)} { } + /** + * @brief Construct a fail guard that owns the cleanup callback. + * + * @param other Source object whose state is copied or moved into this object. + */ FailGuard(FailGuard &&other) noexcept: _func {std::move(other._func)} { this->failure = other.failure; @@ -183,21 +358,36 @@ namespace util { } } + /** + * @brief Disable the fail guard so destruction will not run the cleanup action. + */ void disable() { failure = false; } - bool failure {true}; + bool failure {true}; ///< Whether the fail guard should run its cleanup action. private: T _func; }; + /** + * @brief Create a scope guard that runs unless it is disabled. + * + * @param f Callable executed by the helper. + * @return Fail guard object that invokes the callable on scope exit. + */ template [[nodiscard]] auto fail_guard(T &&f) { return FailGuard {std::forward(f)}; } + /** + * @brief Append the raw bytes of a trivially-copyable structure to a byte buffer. + * + * @param buf Destination buffer used for protocol serialization. + * @param _struct Structure instance whose in-memory bytes should be appended. + */ template void append_struct(std::vector &buf, const T &_struct) { constexpr size_t data_len = sizeof(_struct); @@ -211,9 +401,15 @@ namespace util { } } + /** + * @brief Formatter that exposes a value's bytes as hexadecimal text. + */ template class Hex { public: + /** + * @brief Integral type used for one formatted hex element. + */ typedef T elem_type; private: @@ -239,6 +435,12 @@ namespace util { char _hex[sizeof(elem_type) * 2]; public: + /** + * @brief Construct a hexadecimal byte view for the supplied value. + * + * @param elem Element value being serialized as bytes. + * @param rev Whether bytes should be emitted in reverse order. + */ Hex(const elem_type &elem, bool rev) { if (!rev) { const uint8_t *data = reinterpret_cast(&elem) + sizeof(elem_type) - 1; @@ -255,49 +457,110 @@ namespace util { } } + /** + * @brief Return an iterator to the first byte in the buffer view. + * + * @return Iterator to the first element. + */ char *begin() { return _hex; } + /** + * @brief Return an iterator one past the final byte in the buffer view. + * + * @return Iterator one past the last element. + */ char *end() { return _hex + sizeof(elem_type) * 2; } + /** + * @brief Return an iterator to the first byte in the buffer view. + * + * @return Iterator to the first element. + */ const char *begin() const { return _hex; } + /** + * @brief Return an iterator one past the final byte in the buffer view. + * + * @return Iterator one past the last element. + */ const char *end() const { return _hex + sizeof(elem_type) * 2; } + /** + * @brief Return a const iterator to the first byte in the view. + * + * @return Pointer to the first formatted hexadecimal character. + */ const char *cbegin() const { return _hex; } + /** + * @brief Return a const iterator one past the last byte in the view. + * + * @return Pointer one past the last formatted hexadecimal character. + */ const char *cend() const { return _hex + sizeof(elem_type) * 2; } + /** + * @brief Convert to string. + * + * @return Value converted to string. + */ std::string to_string() const { return {begin(), end()}; } + /** + * @brief Convert to string view. + * + * @return Value converted to string view. + */ std::string_view to_string_view() const { return {begin(), sizeof(elem_type) * 2}; } }; + /** + * @brief Serialize an element as hexadecimal text. + * + * @param elem Element value being serialized as bytes. + * @param rev Whether bytes should be emitted in reverse order. + * @return Lightweight hexadecimal formatter for the input object. + */ template Hex hex(const T &elem, bool rev = false) { return Hex(elem, rev); } + /** + * @brief Format a value as hexadecimal text for logging. + * + * @param value Value whose bytes should be logged in hexadecimal. + * @return Hexadecimal string representation of the input object. + */ template std::string log_hex(const T &value) { return "0x" + Hex(value, false).to_string(); } + /** + * @brief Convert a value to a vector of hexadecimal bytes. + * + * @param begin Iterator or pointer marking the start of the input range. + * @param end Iterator or pointer marking the end of the input range. + * @param rev Whether bytes should be emitted in reverse order. + * @return Hexadecimal string for the byte range. + */ template std::string hex_vec(It begin, It end, bool rev = false) { auto str_size = 2 * std::distance(begin, end); @@ -340,11 +603,25 @@ namespace util { return hex; } + /** + * @brief Parse hexadecimal text into a byte vector. + * + * @param c Character or context value being converted or released. + * @param rev Whether bytes should be emitted in reverse order. + * @return Hexadecimal string for the contiguous container. + */ template std::string hex_vec(C &&c, bool rev = false) { return hex_vec(std::begin(c), std::end(c), rev); } + /** + * @brief Convert from hex. + * + * @param hex Hexadecimal text to decode. + * @param rev Whether bytes should be emitted in reverse order. + * @return Value converted from hex. + */ template T from_hex(const std::string_view &hex, bool rev = false) { std::uint8_t buf[sizeof(T)]; @@ -401,6 +678,13 @@ namespace util { return *reinterpret_cast(buf); } + /** + * @brief Convert from hex vec. + * + * @param hex Hexadecimal text to decode. + * @param rev Whether bytes should be emitted in reverse order. + * @return Value converted from hex vec. + */ inline std::string from_hex_vec(const std::string &hex, bool rev = false) { std::string buf; @@ -453,11 +737,23 @@ namespace util { return buf; } + /** + * @brief Hash functor that hashes the raw bytes of trivially-copyable values. + */ template class hash { public: + /** + * @brief Value type accepted by the hash functor. + */ using value_type = T; + /** + * @brief Hash a value by viewing its object representation as bytes. + * + * @param value Value whose raw bytes are hashed. + * @return Hash value for the byte representation. + */ std::size_t operator()(const value_type &value) const { const auto *p = reinterpret_cast(&value); @@ -465,16 +761,35 @@ namespace util { } }; + /** + * @brief Convert between enum values and their string names. + * + * @param val Value assigned to the synchronized object. + * @return Const reference to the enum's underlying integral storage. + */ template auto enm(const T &val) -> const std::underlying_type_t & { return *reinterpret_cast *>(&val); } + /** + * @brief Convert between enum values and their string names. + * + * @param val Value assigned to the synchronized object. + * @return Mutable reference to the enum's underlying integral storage. + */ template auto enm(T &val) -> std::underlying_type_t & { return *reinterpret_cast *>(&val); } + /** + * @brief Convert from chars. + * + * @param begin Iterator or pointer marking the start of the input range. + * @param end Iterator or pointer marking the end of the input range. + * @return Value converted from chars. + */ inline std::int64_t from_chars(const char *begin, const char *end) { if (begin == end) { return 0; @@ -491,53 +806,110 @@ namespace util { return *begin != '-' ? res + (std::int64_t) (*begin - '0') * mul : -res; } + /** + * @brief Convert from view. + * + * @param number Integer value to serialize or convert. + * @return Value converted from view. + */ inline std::int64_t from_view(const std::string_view &number) { return from_chars(std::begin(number), std::end(number)); } + /** + * @brief Tagged storage for one of two possible value types. + */ template class Either: public std::variant { public: using std::variant::variant; + /** + * @brief Check whether left. + * + * @return True when the comparison condition is satisfied. + */ constexpr bool has_left() const { return std::holds_alternative(*this); } + /** + * @brief Check whether right. + * + * @return True when the comparison condition is satisfied. + */ constexpr bool has_right() const { return std::holds_alternative(*this); } + /** + * @brief Return the left-hand alternative held by the variant. + * + * @return Mutable reference to the left-hand alternative. + */ X &left() { return std::get(*this); } + /** + * @brief Return the right-hand alternative held by the variant. + * + * @return Mutable reference to the right-hand alternative. + */ Y &right() { return std::get(*this); } + /** + * @brief Return the left-hand alternative held by the variant. + * + * @return Const reference to the left-hand alternative. + */ const X &left() const { return std::get(*this); } + /** + * @brief Return the right-hand alternative held by the variant. + * + * @return Const reference to the right-hand alternative. + */ const Y &right() const { return std::get(*this); } }; // Compared to std::unique_ptr, it adds the ability to get the address of the pointer itself + /** + * @brief Unique pointer wrapper with customizable pointer and deleter types. + */ template> class uniq_ptr { public: + /** + * @brief Object type managed by the unique pointer wrapper. + */ using element_type = T; + /** + * @brief Pointer type stored by the unique pointer wrapper. + */ using pointer = element_type *; + /** + * @brief Const pointer type exposed by the unique pointer wrapper. + */ using const_pointer = element_type const *; + /** + * @brief Callable type used to release the managed pointer. + */ using deleter_type = D; constexpr uniq_ptr() noexcept: _p {nullptr} { } + /** + * @brief Construct a unique ownership wrapper. + */ constexpr uniq_ptr(std::nullptr_t) noexcept: _p {nullptr} { } @@ -545,24 +917,45 @@ namespace util { uniq_ptr(const uniq_ptr &other) noexcept = delete; uniq_ptr &operator=(const uniq_ptr &other) noexcept = delete; + /** + * @brief Construct a unique ownership wrapper. + * + * @param p Pointer passed to the deleter or conversion helper. + */ template uniq_ptr(V *p) noexcept: _p {p} { static_assert(std::is_same_v || std::is_same_v || std::is_base_of_v, "element_type must be base class of V"); } + /** + * @brief Construct a unique ownership wrapper. + * + * @param uniq Unique pointer whose owned value is transferred. + */ template uniq_ptr(std::unique_ptr &&uniq) noexcept: _p {uniq.release()} { static_assert(std::is_same_v || std::is_same_v || std::is_base_of_v, "element_type must be base class of V"); } + /** + * @brief Construct a unique ownership wrapper. + * + * @param other Source object whose state is copied or moved into this object. + */ template uniq_ptr(uniq_ptr &&other) noexcept: _p {other.release()} { static_assert(std::is_same_v || std::is_same_v || std::is_base_of_v, "element_type must be base class of V"); } + /** + * @brief Assign state from another instance while preserving ownership semantics. + * + * @param other Source object whose state is copied or moved into this object. + * @return Reference returned by the operator overload. + */ template uniq_ptr &operator=(uniq_ptr &&other) noexcept { static_assert(std::is_same_v || std::is_same_v || std::is_base_of_v, "element_type must be base class of V"); @@ -571,6 +964,12 @@ namespace util { return *this; } + /** + * @brief Assign state from another instance while preserving ownership semantics. + * + * @param uniq Unique pointer whose owned value is transferred. + * @return Reference returned by the operator overload. + */ template uniq_ptr &operator=(std::unique_ptr &&uniq) noexcept { static_assert(std::is_same_v || std::is_same_v || std::is_base_of_v, "element_type must be base class of V"); @@ -584,6 +983,11 @@ namespace util { reset(); } + /** + * @brief Reset the object to its initial empty state. + * + * @param p Pointer passed to the deleter or conversion helper. + */ void reset(pointer p = pointer()) { if (_p) { _deleter(_p); @@ -592,126 +996,277 @@ namespace util { _p = p; } + /** + * @brief Release the COM or platform reference owned by the pointer. + * + * @return Reference count or status returned after releasing the object. + */ pointer release() { auto tmp = _p; _p = nullptr; return tmp; } + /** + * @brief Return the currently wrapped value or handle. + * + * @return Underlying native handle or object pointer. + */ pointer get() { return _p; } + /** + * @brief Return the currently wrapped value or handle. + * + * @return Underlying native handle or object pointer. + */ const_pointer get() const { return _p; } + /** + * @brief Dereference the managed pointer. + * + * @return Const reference to the pointed-to object. + */ std::add_lvalue_reference_t operator*() const { return *_p; } + /** + * @brief Dereference the managed pointer. + * + * @return Mutable reference to the pointed-to object. + */ std::add_lvalue_reference_t operator*() { return *_p; } + /** + * @brief Access members of the managed pointer. + * + * @return Const pointer to the managed object. + */ const_pointer operator->() const { return _p; } + /** + * @brief Access members of the managed pointer. + * + * @return Mutable pointer to the managed object. + */ pointer operator->() { return _p; } + /** + * @brief Expose the stored pointer address for C APIs that write handles. + * + * @return Address of the stored pointer. + */ pointer *operator&() const { return &_p; } + /** + * @brief Expose the stored pointer address for C APIs that write handles. + * + * @return Address of the stored pointer. + */ pointer *operator&() { return &_p; } + /** + * @brief Return the deleter used when resetting the wrapped pointer. + * + * @return Mutable deleter stored by the wrapper. + */ deleter_type &get_deleter() { return _deleter; } + /** + * @brief Return the deleter used when resetting the wrapped pointer. + * + * @return Const deleter stored by the wrapper. + */ const deleter_type &get_deleter() const { return _deleter; } + /** + * @brief Check whether the wrapper currently owns a non-null pointer. + */ explicit operator bool() const { return _p != nullptr; } protected: - pointer _p; - deleter_type _deleter; + pointer _p; ///< Pointer currently owned by the wrapper. + deleter_type _deleter; ///< Callable used to release `_p`. }; + /** + * @brief Compare two Sunshine unique pointers by their stored addresses. + * + * @param x Left-hand pointer or value being compared. + * @param y Right-hand pointer or value being compared. + * @return True when both wrappers store the same pointer address. + */ template bool operator==(const uniq_ptr &x, const uniq_ptr &y) { return x.get() == y.get(); } + /** + * @brief Compare two Sunshine unique pointers by their stored addresses. + * + * @param x Left-hand pointer or value being compared. + * @param y Right-hand pointer or value being compared. + * @return True when the wrappers store different pointer addresses. + */ template bool operator!=(const uniq_ptr &x, const uniq_ptr &y) { return x.get() != y.get(); } + /** + * @brief Compare a standard unique pointer with a Sunshine unique pointer. + * + * @param x Left-hand pointer or value being compared. + * @param y Right-hand pointer or value being compared. + * @return True when both wrappers store the same pointer address. + */ template bool operator==(const std::unique_ptr &x, const uniq_ptr &y) { return x.get() == y.get(); } + /** + * @brief Compare a standard unique pointer with a Sunshine unique pointer. + * + * @param x Left-hand pointer or value being compared. + * @param y Right-hand pointer or value being compared. + * @return True when the wrappers store different pointer addresses. + */ template bool operator!=(const std::unique_ptr &x, const uniq_ptr &y) { return x.get() != y.get(); } + /** + * @brief Compare a Sunshine unique pointer with a standard unique pointer. + * + * @param x Left-hand pointer or value being compared. + * @param y Right-hand pointer or value being compared. + * @return True when both wrappers store the same pointer address. + */ template bool operator==(const uniq_ptr &x, const std::unique_ptr &y) { return x.get() == y.get(); } + /** + * @brief Compare a Sunshine unique pointer with a standard unique pointer. + * + * @param x Left-hand pointer or value being compared. + * @param y Right-hand pointer or value being compared. + * @return True when the wrappers store different pointer addresses. + */ template bool operator!=(const uniq_ptr &x, const std::unique_ptr &y) { return x.get() != y.get(); } + /** + * @brief Compare a Sunshine unique pointer with null. + * + * @param x Left-hand pointer or value being compared. + * @return True when the wrapper does not own a pointer. + */ template bool operator==(const uniq_ptr &x, std::nullptr_t) { return !(bool) x; } + /** + * @brief Compare a Sunshine unique pointer with null. + * + * @param x Left-hand pointer or value being compared. + * @return True when the wrapper owns a pointer. + */ template bool operator!=(const uniq_ptr &x, std::nullptr_t) { return (bool) x; } + /** + * @brief Compare null with a Sunshine unique pointer. + * + * @param y Right-hand pointer or value being compared. + * @return True when the wrapper does not own a pointer. + */ template bool operator==(std::nullptr_t, const uniq_ptr &y) { return !(bool) y; } + /** + * @brief Compare null with a Sunshine unique pointer. + * + * @param y Right-hand pointer or value being compared. + * @return True when the wrapper owns a pointer. + */ template bool operator!=(std::nullptr_t, const uniq_ptr &y) { return (bool) y; } + /** + * @brief Shared pointer type matching a safe pointer wrapper's element type. + */ template using shared_t = std::shared_ptr; + /** + * @brief Create a shared object or message. + * + * @param pointer Raw pointer adopted by the safe pointer wrapper. + * @return Constructed shared object. + */ template shared_t

make_shared(T *pointer) { return shared_t

(reinterpret_cast(pointer), typename P::deleter_type()); } + /** + * @brief Pointer wrapper that may borrow or own the pointee. + */ template class wrap_ptr { public: + /** + * @brief Object type referenced by the pointer wrapper. + */ using element_type = T; + /** + * @brief Mutable pointer type exposed by the pointer wrapper. + */ using pointer = element_type *; + /** + * @brief Const pointer type exposed by the pointer wrapper. + */ using const_pointer = element_type const *; + /** + * @brief Mutable reference type exposed by the pointer wrapper. + */ using reference = element_type &; + /** + * @brief Const reference type exposed by the pointer wrapper. + */ using const_reference = element_type const &; wrap_ptr(): @@ -719,22 +1274,43 @@ namespace util { _p {nullptr} { } + /** + * @brief Construct an owning or non-owning wrapper around a raw pointer. + * + * @param p Pointer passed to the deleter or conversion helper. + */ wrap_ptr(pointer p): _own_ptr {false}, _p {p} { } + /** + * @brief Construct an owning or non-owning wrapper around a raw pointer. + * + * @param uniq_p Uniq p. + */ wrap_ptr(std::unique_ptr &&uniq_p): _own_ptr {true}, _p {uniq_p.release()} { } + /** + * @brief Construct an owning or non-owning wrapper around a raw pointer. + * + * @param other Source object whose state is copied or moved into this object. + */ wrap_ptr(wrap_ptr &&other): _own_ptr {other._own_ptr}, _p {other._p} { other._own_ptr = false; } + /** + * @brief Assign state from another instance while preserving ownership semantics. + * + * @param other Source object whose state is copied or moved into this object. + * @return Reference returned by the operator overload. + */ wrap_ptr &operator=(wrap_ptr &&other) noexcept { if (_own_ptr) { delete _p; @@ -748,6 +1324,12 @@ namespace util { return *this; } + /** + * @brief Assign state from another instance while preserving ownership semantics. + * + * @param uniq_ptr Unique pointer whose ownership is transferred to the wrapper. + * @return Reference returned by the operator overload. + */ template wrap_ptr &operator=(std::unique_ptr &&uniq_ptr) { static_assert(std::is_base_of_v, "element_type must be base class of V"); @@ -757,6 +1339,12 @@ namespace util { return *this; } + /** + * @brief Assign state from another instance while preserving ownership semantics. + * + * @param p Pointer passed to the deleter or conversion helper. + * @return Reference returned by the operator overload. + */ wrap_ptr &operator=(pointer p) { if (_own_ptr) { delete _p; @@ -776,18 +1364,38 @@ namespace util { _own_ptr = false; } + /** + * @brief Dereference the wrapped pointer. + * + * @return Const reference to the pointed-to object. + */ const_reference operator*() const { return *_p; } + /** + * @brief Dereference the wrapped pointer. + * + * @return Mutable reference to the pointed-to object. + */ reference operator*() { return *_p; } + /** + * @brief Access members of the wrapped pointer. + * + * @return Const pointer to the wrapped object. + */ const_pointer operator->() const { return _p; } + /** + * @brief Access members of the wrapped pointer. + * + * @return Mutable pointer to the wrapped object. + */ pointer operator->() { return _p; } @@ -798,6 +1406,9 @@ namespace util { }; template + /** + * @brief Trait value indicating whether the template argument is a pointer. + */ constexpr bool is_pointer_v = instantiation_of_v || instantiation_of_v || @@ -807,48 +1418,79 @@ namespace util { template struct __false_v; + /** + * @brief Helper specialization for optional values. + */ template struct __false_v>> { - static constexpr std::nullopt_t value = std::nullopt; + static constexpr std::nullopt_t value = std::nullopt; ///< Value. }; + /** + * @brief Enables an overload only when the provided type is a pointer. + */ template struct __false_v>> { - static constexpr std::nullptr_t value = nullptr; + static constexpr std::nullptr_t value = nullptr; ///< Value. }; + /** + * @brief Type trait comparing a value with a boolean template argument. + */ template struct __false_v>> { - static constexpr bool value = false; + static constexpr bool value = false; ///< Trait value for the false specialization. }; template static constexpr auto false_v = __false_v::value; + /** + * @brief Optional trait value used by endian serialization helpers. + */ template using optional_t = either_t< (std::is_same_v || is_pointer_v), T, std::optional>; + /** + * @brief Owning contiguous buffer with an explicit logical element count. + */ template class buffer_t { public: buffer_t(): _els {0} {}; + /** + * @brief Construct an owning contiguous buffer. + * + * @param o Source object used for comparison or assignment. + */ buffer_t(buffer_t &&o) noexcept: _els {o._els}, _buf {std::move(o._buf)} { o._els = 0; } + /** + * @brief Construct an owning contiguous buffer. + * + * @param o Source object used for comparison or assignment. + */ buffer_t(const buffer_t &o): _els {o._els}, _buf {std::make_unique(_els)} { std::copy(o.begin(), o.end(), begin()); } + /** + * @brief Assign state from another instance while preserving ownership semantics. + * + * @param o Source object used for comparison or assignment. + * @return Reference returned by the operator overload. + */ buffer_t &operator=(buffer_t &&o) noexcept { std::swap(_els, o._els); std::swap(_buf, o._buf); @@ -856,45 +1498,98 @@ namespace util { return *this; }; + /** + * @brief Construct an owning contiguous buffer. + * + * @param elements Elements copied into the buffer. + */ explicit buffer_t(size_t elements): _els {elements}, _buf {std::make_unique(elements)} { } + /** + * @brief Construct an owning contiguous buffer. + * + * @param elements Elements copied into the buffer. + * @param t Initial value used to populate the GPU buffer. + */ explicit buffer_t(size_t elements, const T &t): _els {elements}, _buf {std::make_unique(elements)} { std::fill_n(_buf.get(), elements, t); } + /** + * @brief Access an element in the owning buffer. + * + * @param el Zero-based element index. + * @return Mutable reference to the requested element. + */ T &operator[](size_t el) { return _buf[el]; } + /** + * @brief Access an element in the owning buffer. + * + * @param el Zero-based element index. + * @return Const reference to the requested element. + */ const T &operator[](size_t el) const { return _buf[el]; } + /** + * @brief Return the serialized size of the current object. + * + * @return Number of elements currently stored. + */ size_t size() const { return _els; } + /** + * @brief Update the logical element count without reallocating storage. + * + * @param els Elements used to initialize the buffer. + */ void fake_resize(std::size_t els) { _els = els; } + /** + * @brief Return an iterator to the first byte in the buffer view. + * + * @return Iterator to the first element. + */ T *begin() { return _buf.get(); } + /** + * @brief Return an iterator to the first byte in the buffer view. + * + * @return Iterator to the first element. + */ const T *begin() const { return _buf.get(); } + /** + * @brief Return an iterator one past the final byte in the buffer view. + * + * @return Iterator one past the last element. + */ T *end() { return _buf.get() + _els; } + /** + * @brief Return an iterator one past the final byte in the buffer view. + * + * @return Iterator one past the last element. + */ const T *end() const { return _buf.get() + _els; } @@ -904,6 +1599,13 @@ namespace util { std::unique_ptr _buf; }; + /** + * @brief Build an either value from a left-hand value. + * + * @param l Left-hand value used to construct the either object. + * @param r Fallback value returned when the optional left-hand value is empty. + * @return Left-hand optional value when present, otherwise the fallback. + */ template T either(std::optional &&l, T &&r) { if (l) { @@ -913,66 +1615,130 @@ namespace util { return std::forward(r); } + /** + * @brief Callable wrapper used by utility metaprogramming helpers. + */ template struct Function { + /** + * @brief Type extracted from the matched function signature. + */ typedef ReturnType (*type)(Args...); }; + /** + * @brief Deleter adapter that destroys the wrapped pointer. + */ template::type function> struct Destroy { + /** + * @brief Pointer type accepted by this deleter. + */ typedef T pointer; + /** + * @brief Invoke the configured destroy function on a pointer. + * + * @param p Pointer to release. + */ void operator()(pointer p) { function(p); } }; + /** + * @brief Unique pointer using a compile-time C-style destroy function. + */ template::type function> using safe_ptr = uniq_ptr>; // You cannot specialize an alias + /** + * @brief Safe pointer wrapper for APIs whose release function returns a status value. + */ template::type function> using safe_ptr_v2 = uniq_ptr>; + /** + * @brief Release memory allocated by C APIs with `free`. + * + * @param p Pointer passed to the deleter or conversion helper. + */ template void c_free(T *p) { free(p); } + /** + * @brief Create a non-owning dynamic buffer view over contiguous memory. + * + * @param p Pointer passed to the deleter or conversion helper. + */ template void dynamic(T *p) { (*function)(p); } + /** + * @brief Unique pointer using a runtime-provided destroy function. + */ template using dyn_safe_ptr = safe_ptr>; + /** + * @brief Safe pointer wrapper for runtime-resolved release functions with status returns. + */ template using dyn_safe_ptr_v2 = safe_ptr>; + /** + * @brief Safe pointer wrapper for memory released by `std::free`. + */ template using c_ptr = safe_ptr>; + /** + * @brief Read the current value without removing it from the queue. + * + * @param begin Iterator or pointer marking the start of the input range. + * @param end Iterator or pointer marking the end of the input range. + * @return String view spanning the iterator range. + */ template std::string_view view(It begin, It end) { return std::string_view {(const char *) begin, (std::size_t) (end - begin)}; } + /** + * @brief Read the current value without removing it from the queue. + * + * @param data Payload or state data to serialize, deserialize, or forward. + * @return String view over the contiguous object's bytes. + */ template std::string_view view(const T &data) { return std::string_view((const char *) &data, sizeof(T)); } + /** + * @brief Two-dimensional integer point. + */ struct point_t { - double x; - double y; + double x; ///< X. + double y; ///< Y. + /** + * @brief Operator. + */ friend std::ostream &operator<<(std::ostream &os, const point_t &p) { return (os << "Point(x: " << p.x << ", y: " << p.y << ")"); } }; namespace endian { + /** + * @brief Describes the byte order used when serializing values. + */ template struct endianness { enum : bool { @@ -1002,8 +1768,17 @@ namespace util { template struct endian_helper {}; + /** + * @brief Helper specialization for optional values. + */ template struct endian_helper)>> { + /** + * @brief Convert a value to or from big-endian byte order. + * + * @param x Value to convert. + * @return Value represented in big-endian byte order. + */ static inline T big(T x) { if constexpr (endianness::little) { uint8_t *data = reinterpret_cast(&x); @@ -1014,6 +1789,12 @@ namespace util { return x; } + /** + * @brief Convert a value to or from little-endian byte order. + * + * @param x Value to convert. + * @return Value represented in little-endian byte order. + */ static inline T little(T x) { if constexpr (endianness::big) { uint8_t *data = reinterpret_cast(&x); @@ -1025,8 +1806,17 @@ namespace util { } }; + /** + * @brief Helper specialization for optional values. + */ template struct endian_helper>> { + /** + * @brief Convert a value to or from little-endian byte order. + * + * @param x Pointer to convert. + * @return Pointer with the pointed-to value represented in little-endian byte order. + */ static inline T little(T x) { if (!x) { return x; @@ -1041,6 +1831,12 @@ namespace util { return x; } + /** + * @brief Convert a value to or from big-endian byte order. + * + * @param x Pointer to convert. + * @return Pointer with the pointed-to value represented in big-endian byte order. + */ static inline T big(T x) { if (!x) { return x; @@ -1056,11 +1852,23 @@ namespace util { } }; + /** + * @brief Convert a value to or from little-endian byte order. + * + * @param x Value or pointer to convert. + * @return Input represented in little-endian byte order. + */ template inline auto little(T x) { return endian_helper::little(x); } + /** + * @brief Convert a value to or from big-endian byte order. + * + * @param x Value or pointer to convert. + * @return Input represented in big-endian byte order. + */ template inline auto big(T x) { return endian_helper::big(x); diff --git a/src/uuid.h b/src/uuid.h index df3d78ae4..00e397e34 100644 --- a/src/uuid.h +++ b/src/uuid.h @@ -11,12 +11,21 @@ * @brief UUID utilities. */ namespace uuid_util { + /** + * @brief UUID value exposed through multiple integer views. + */ union uuid_t { - std::uint8_t b8[16]; - std::uint16_t b16[8]; - std::uint32_t b32[4]; - std::uint64_t b64[2]; + std::uint8_t b8[16]; ///< UUID bytes. + std::uint16_t b16[8]; ///< UUID viewed as 16-bit words. + std::uint32_t b32[4]; ///< UUID viewed as 32-bit words. + std::uint64_t b64[2]; ///< UUID viewed as 64-bit words. + /** + * @brief Generate a UUID value. + * + * @param engine Random-number engine used to generate UUID bytes. + * @return Random UUID generated from the supplied engine. + */ static uuid_t generate(std::default_random_engine &engine) { std::uniform_int_distribution dist(0, std::numeric_limits::max()); @@ -31,6 +40,11 @@ namespace uuid_util { return buf; } + /** + * @brief Generate a UUID value. + * + * @return Random UUID generated from std::random_device seeding. + */ static uuid_t generate() { std::random_device r; @@ -39,6 +53,11 @@ namespace uuid_util { return generate(engine); } + /** + * @brief Format the UUID using canonical text form. + * + * @return Canonical lowercase UUID string. + */ [[nodiscard]] std::string string() const { std::string result; @@ -66,14 +85,32 @@ namespace uuid_util { return result; } + /** + * @brief Compare two UUID values for equality. + * + * @param other UUID value to compare against. + * @return True when both UUID values contain identical bytes. + */ constexpr bool operator==(const uuid_t &other) const { return b64[0] == other.b64[0] && b64[1] == other.b64[1]; } + /** + * @brief Order UUID values by their 64-bit word representation. + * + * @param other UUID value to compare against. + * @return True when this UUID sorts before `other`. + */ constexpr bool operator<(const uuid_t &other) const { return (b64[0] < other.b64[0] || (b64[0] == other.b64[0] && b64[1] < other.b64[1])); } + /** + * @brief Order UUID values by their 64-bit word representation. + * + * @param other UUID value to compare against. + * @return True when this UUID sorts after `other`. + */ constexpr bool operator>(const uuid_t &other) const { return (b64[0] > other.b64[0] || (b64[0] == other.b64[0] && b64[1] > other.b64[1])); } diff --git a/src/video.cpp b/src/video.cpp index 0900d999d..ddf3c4fda 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -72,25 +72,40 @@ namespace video { } } // namespace + /** + * @brief Release context resources. + */ void free_ctx(AVCodecContext *ctx) { avcodec_free_context(&ctx); } + /** + * @brief Release an FFmpeg frame allocated by the capture or conversion backend. + */ void free_frame(AVFrame *frame) { av_frame_free(&frame); } + /** + * @brief Release a backend buffer allocated for capture or conversion. + */ void free_buffer(AVBufferRef *ref) { av_buffer_unref(&ref); } namespace nv { + /** + * @brief Enumerates supported profile h264 options. + */ enum class profile_h264_e : int { high = 2, ///< High profile high_444p = 3, ///< High 4:4:4 Predictive profile }; + /** + * @brief Enumerates supported profile HEVC options. + */ enum class profile_hevc_e : int { main = 0, ///< Main profile main_10 = 1, ///< Main 10 profile @@ -101,17 +116,26 @@ namespace video { namespace qsv { + /** + * @brief Enumerates supported profile h264 options. + */ enum class profile_h264_e : int { high = 100, ///< High profile high_444p = 244, ///< High 4:4:4 Predictive profile }; + /** + * @brief Enumerates supported profile HEVC options. + */ enum class profile_hevc_e : int { main = 1, ///< Main profile main_10 = 2, ///< Main 10 profile rext = 4, ///< RExt profile }; + /** + * @brief Enumerates supported profile AV1 options. + */ enum class profile_av1_e : int { main = 1, ///< Main profile high = 2, ///< High profile @@ -119,14 +143,52 @@ namespace video { } // namespace qsv + /** + * @brief Create an FFmpeg hardware device buffer for D3D11VA input. + * + * @param encode_device Encode device. + * @return Hardware buffer on success, or an error code on failure. + */ util::Either dxgi_init_avcodec_hardware_input_buffer(platf::avcodec_encode_device_t *); + /** + * @brief Create an FFmpeg hardware device buffer for VA-API input. + * + * @param encode_device Encode device. + * @return Hardware buffer on success, or an error code on failure. + */ util::Either vaapi_init_avcodec_hardware_input_buffer(platf::avcodec_encode_device_t *); + /** + * @brief Create an FFmpeg hardware device buffer for CUDA input. + * + * @param encode_device Encode device. + * @return Hardware buffer on success, or an error code on failure. + */ util::Either cuda_init_avcodec_hardware_input_buffer(platf::avcodec_encode_device_t *); + /** + * @brief Create an FFmpeg hardware device buffer for VideoToolbox input. + * + * @param encode_device Encode device. + * @return Hardware buffer on success, or an error code on failure. + */ util::Either vt_init_avcodec_hardware_input_buffer(platf::avcodec_encode_device_t *); + /** + * @brief Create an FFmpeg hardware device buffer for Vulkan input. + * + * @return Hardware buffer on success, or an error code on failure. + */ util::Either vulkan_init_avcodec_hardware_input_buffer(platf::avcodec_encode_device_t *); + /** + * @brief FFmpeg software encode device used when no hardware frames are required. + */ class avcodec_software_encode_device_t: public platf::avcodec_encode_device_t { public: + /** + * @brief Accept a software frame without additional hardware conversion. + * + * @param img Image or frame object to read from or populate. + * @return Conversion status. + */ int convert(platf::img_t &img) override { // If we need to add aspect ratio padding, we need to scale into an intermediate output buffer bool requires_padding = (sw_frame->width != sws_output_frame->width || sw_frame->height != sws_output_frame->height); @@ -173,6 +235,13 @@ namespace video { return 0; } + /** + * @brief Attach frame resources used by the next conversion or encode operation. + * + * @param frame Video or graphics frame being processed. + * @param hw_frames_ctx FFmpeg hardware frames context associated with the frame. + * @return Status from updating frame. + */ int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override { this->frame = frame; @@ -190,6 +259,9 @@ namespace video { return 0; } + /** + * @brief Apply the configured colorspace metadata to the active frame. + */ void apply_colorspace() override { auto avcodec_colorspace = avcodec_colorspace_from_sunshine_colorspace(colorspace); sws_setColorspaceDetails(sws.get(), sws_getCoefficients(SWS_CS_DEFAULT), 0, sws_getCoefficients(avcodec_colorspace.software_format), avcodec_colorspace.range - 1, 0, 1 << 16, 1 << 16); @@ -206,6 +278,16 @@ namespace video { av_image_fill_black(frame->data, linesize, (AVPixelFormat) frame->format, frame->color_range, frame->width, frame->height); } + /** + * @brief Initialize FFmpeg software encoding for the requested codec. + * + * @param in_width In width. + * @param in_height In height. + * @param frame Video or graphics frame being processed. + * @param format Pixel, audio, or protocol format being converted. + * @param hardware Whether the frame is backed by hardware resources. + * @return 0 on success; nonzero or negative platform status on failure. + */ int init(int in_width, int in_height, AVFrame *frame, AVPixelFormat format, bool hardware) { // If the device used is hardware, yet the image resides on main memory if (hardware) { @@ -277,18 +359,21 @@ namespace video { } // Store ownership when frame is hw_frame - avcodec_frame_t hw_frame; + avcodec_frame_t hw_frame; ///< Hw frame. - avcodec_frame_t sw_frame; - avcodec_frame_t sws_input_frame; - avcodec_frame_t sws_output_frame; - sws_t sws; + avcodec_frame_t sw_frame; ///< Sw frame. + avcodec_frame_t sws_input_frame; ///< Sws input frame. + avcodec_frame_t sws_output_frame; ///< Sws output frame. + sws_t sws; ///< Software scaler used when frames need CPU-side pixel conversion. // Offset of input image to output frame in pixels - int offsetW; - int offsetH; + int offsetW; ///< Offset w. + int offsetH; ///< Offset h. }; + /** + * @brief Enumerates supported flag options. + */ enum flag_e : uint32_t { DEFAULT = 0, ///< Default flags PARALLEL_ENCODING = 1 << 1, ///< Capture and encoding can run concurrently on separate threads @@ -305,16 +390,31 @@ namespace video { FIXED_GOP_SIZE = 1 << 12, ///< Use fixed small GOP size (encoder doesn't support on-demand IDR frames) }; + /** + * @brief FFmpeg AVCodec encode session and parameter-set rewriting state. + */ class avcodec_encode_session_t: public encode_session_t { public: avcodec_encode_session_t() = default; + /** + * @brief Initialize an FFmpeg encode session and its hardware encode device. + * + * @param avcodec_ctx Open FFmpeg codec context for the selected encoder. + * @param encode_device Platform encode device that supplies frames to FFmpeg. + * @param inject Whether SPS/VPS replacement data should be injected. + */ avcodec_encode_session_t(avcodec_ctx_t &&avcodec_ctx, std::unique_ptr encode_device, int inject): avcodec_ctx {std::move(avcodec_ctx)}, device {std::move(encode_device)}, inject {inject} { } + /** + * @brief Move an FFmpeg encode session without duplicating codec/device ownership. + * + * @param other Source object whose state is copied or moved into this object. + */ avcodec_encode_session_t(avcodec_encode_session_t &&other) noexcept = default; ~avcodec_encode_session_t() { @@ -330,6 +430,12 @@ namespace video { } // Ensure objects are destroyed in the correct order + /** + * @brief Assign state from another instance while preserving ownership semantics. + * + * @param other Source object whose state is copied or moved into this object. + * @return Reference or value produced by the operator. + */ avcodec_encode_session_t &operator=(avcodec_encode_session_t &&other) { device = std::move(other.device); avcodec_ctx = std::move(other.avcodec_ctx); @@ -342,6 +448,12 @@ namespace video { return *this; } + /** + * @brief Encode one frame with FFmpeg AVCodec and prepare packet replacements. + * + * @param img Image or frame object to read from or populate. + * @return Conversion status. + */ int convert(platf::img_t &img) override { if (!device) { return -1; @@ -349,6 +461,9 @@ namespace video { return device->convert(img); } + /** + * @brief Mark the frame as a request for an IDR frame. + */ void request_idr_frame() override { if (device && device->frame) { auto &frame = device->frame; @@ -357,6 +472,9 @@ namespace video { } } + /** + * @brief Mark the frame as a request for a normal inter frame. + */ void request_normal_frame() override { if (device && device->frame) { auto &frame = device->frame; @@ -365,29 +483,49 @@ namespace video { } } + /** + * @brief Mark the frame range whose references must be invalidated. + * + * @param first_frame First frame. + * @param last_frame Last frame. + */ void invalidate_ref_frames(int64_t first_frame, int64_t last_frame) override { BOOST_LOG(error) << "Encoder doesn't support reference frame invalidation"; request_idr_frame(); } - avcodec_ctx_t avcodec_ctx; - std::unique_ptr device; + avcodec_ctx_t avcodec_ctx; ///< FFmpeg codec context owned by the encode session. + std::unique_ptr device; ///< Platform device used by the FFmpeg hardware encoder. - std::vector replacements; + std::vector replacements; ///< NAL-unit byte ranges that must be replaced before packet send. - cbs::nal_t sps; - cbs::nal_t vps; + cbs::nal_t sps; ///< Original and rewritten sequence parameter set for IDR injection. + cbs::nal_t vps; ///< Original and rewritten HEVC video parameter set for IDR injection. // inject sps/vps data into idr pictures - int inject; + int inject; ///< Number of upcoming IDR frames that should receive rewritten parameter sets. }; + /** + * @brief NVENC encode session and device state for hardware encoding. + */ class nvenc_encode_session_t: public encode_session_t { public: + /** + * @brief Initialize an NVENC encode session and take ownership of its device. + * + * @param encode_device Encode device. + */ nvenc_encode_session_t(std::unique_ptr encode_device): device(std::move(encode_device)) { } + /** + * @brief Encode one frame with NVENC and return the packet payload. + * + * @param img Image or frame object to read from or populate. + * @return Conversion status. + */ int convert(platf::img_t &img) override { if (!device) { return -1; @@ -395,14 +533,26 @@ namespace video { return device->convert(img); } + /** + * @brief Mark the frame as a request for an IDR frame. + */ void request_idr_frame() override { force_idr = true; } + /** + * @brief Mark the frame as a request for a normal inter frame. + */ void request_normal_frame() override { force_idr = false; } + /** + * @brief Mark the frame range whose references must be invalidated. + * + * @param first_frame First frame. + * @param last_frame Last frame. + */ void invalidate_ref_frames(int64_t first_frame, int64_t last_frame) override { if (!device || !device->nvenc) { return; @@ -413,6 +563,12 @@ namespace video { } } + /** + * @brief Submit the next frame to NVENC and return the encoded payload. + * + * @param frame_index Monotonic frame index assigned by the video pipeline. + * @return Encoded NVENC frame payload and frame metadata. + */ nvenc::nvenc_encoded_frame encode_frame(uint64_t frame_index) { if (!device || !device->nvenc) { return {}; @@ -428,55 +584,101 @@ namespace video { bool force_idr = false; }; + /** + * @brief Context object used while synchronizing encode sessions. + */ struct sync_session_ctx_t { - safe::signal_t *join_event; - safe::mail_raw_t::event_t shutdown_event; - safe::mail_raw_t::queue_t packets; - safe::mail_raw_t::event_t idr_events; - safe::mail_raw_t::event_t hdr_events; - safe::mail_raw_t::event_t touch_port_events; + safe::signal_t *join_event; ///< Signal raised when the capture and encode workers should join. + safe::mail_raw_t::event_t shutdown_event; ///< Event raised when the stream should shut down. + safe::mail_raw_t::queue_t packets; ///< Queue receiving encoded video packets for the stream sender. + safe::mail_raw_t::event_t idr_events; ///< Event raised when an IDR frame is requested. + safe::mail_raw_t::event_t hdr_events; ///< Event carrying updated HDR metadata. + safe::mail_raw_t::event_t touch_port_events; ///< Event carrying updated touch viewport metadata. - config_t config; - int frame_nr; - void *channel_data; + config_t config; ///< Stream or encoder configuration captured for the worker. + int frame_nr; ///< Next capture-frame number assigned to encoded packets. + void *channel_data; ///< Platform-specific channel data forwarded to packet senders. }; + /** + * @brief Synchronization state for one encode session. + */ struct sync_session_t { - sync_session_ctx_t *ctx; - std::unique_ptr session; + sync_session_ctx_t *ctx; ///< Shared capture/encode synchronization context. + std::unique_ptr session; ///< Active encoder session used by the capture thread. }; + /** + * @brief Queue of encode-session contexts waiting for capture work. + */ using encode_session_ctx_queue_t = safe::queue_t; + /** + * @brief Platform capture status returned by encode operations. + */ using encode_e = platf::capture_e; + /** + * @brief Capture thread context shared with the encoder session. + */ struct capture_ctx_t { - img_event_t images; - config_t config; + img_event_t images; ///< Queue of captured images waiting for encode. + config_t config; ///< Stream or encoder configuration captured for the worker. }; + /** + * @brief Asynchronous capture thread state. + */ struct capture_thread_async_ctx_t { - std::shared_ptr> capture_ctx_queue; - std::thread capture_thread; + std::shared_ptr> capture_ctx_queue; ///< Capture ctx queue. + std::thread capture_thread; ///< Capture thread. - safe::signal_t reinit_event; - const encoder_t *encoder_p; - sync_util::sync_t> display_wp; + safe::signal_t reinit_event; ///< Reinit event. + const encoder_t *encoder_p; ///< Encoder p. + sync_util::sync_t> display_wp; ///< Display wp. }; + /** + * @brief Synchronous capture thread state. + */ struct capture_thread_sync_ctx_t { - encode_session_ctx_queue_t encode_session_ctx_queue {30}; + encode_session_ctx_queue_t encode_session_ctx_queue {30}; ///< Encode session ctx queue. }; + /** + * @brief Start the synchronous multi-client capture thread. + * + * @param ctx Native context object used by the operation or callback. + * @return 0 when the capture thread is started. + */ int start_capture_sync(capture_thread_sync_ctx_t &ctx); + /** + * @brief Stop capture sync processing. + * + * @param ctx Native context object used by the operation or callback. + */ void end_capture_sync(capture_thread_sync_ctx_t &ctx); + /** + * @brief Start the asynchronous capture thread. + * + * @param ctx Native context object used by the operation or callback. + * @return 0 when the capture thread is started; nonzero on setup failure. + */ int start_capture_async(capture_thread_async_ctx_t &ctx); + /** + * @brief Stop capture async processing. + * + * @param ctx Native context object used by the operation or callback. + */ void end_capture_async(capture_thread_async_ctx_t &ctx); // Keep a reference counter to ensure the capture thread only runs when other threads have a reference to the capture thread - auto capture_thread_async = safe::make_shared(start_capture_async, end_capture_async); - auto capture_thread_sync = safe::make_shared(start_capture_sync, end_capture_sync); + auto capture_thread_async = safe::make_shared(start_capture_async, end_capture_async); ///< Capture thread async. + auto capture_thread_sync = safe::make_shared(start_capture_sync, end_capture_sync); ///< Capture thread sync. #ifdef _WIN32 + /** + * @brief NVENC. + */ encoder_t nvenc { "nvenc"sv, std::make_unique( @@ -615,6 +817,9 @@ namespace video { #endif #ifdef _WIN32 + /** + * @brief Quicksync. + */ encoder_t quicksync { "quicksync"sv, std::make_unique( @@ -723,6 +928,9 @@ namespace video { PARALLEL_ENCODING | CBR_WITH_VBR | RELAXED_COMPLIANCE | NO_RC_BUF_LIMIT | YUV444_SUPPORT }; + /** + * @brief Amdvce. + */ encoder_t amdvce { "amdvce"sv, std::make_unique( @@ -829,6 +1037,9 @@ namespace video { PARALLEL_ENCODING }; + /** + * @brief Mediafoundation. + */ encoder_t mediafoundation { "mediafoundation"sv, std::make_unique( @@ -887,6 +1098,9 @@ namespace video { }; #endif + /** + * @brief Software. + */ encoder_t software { "software"sv, std::make_unique( @@ -958,6 +1172,9 @@ namespace video { }; #if defined(__linux__) || defined(linux) || defined(__linux) || defined(__FreeBSD__) + /** + * @brief VA-API. + */ encoder_t vaapi { "vaapi"sv, std::make_unique( @@ -1088,6 +1305,9 @@ namespace video { #endif // linux #ifdef __APPLE__ + /** + * @brief Videotoolbox. + */ encoder_t videotoolbox { "videotoolbox"sv, std::make_unique( @@ -1181,11 +1401,19 @@ namespace video { }; static encoder_t *chosen_encoder; - int active_hevc_mode; - int active_av1_mode; - bool last_encoder_probe_supported_ref_frames_invalidation = false; - std::array last_encoder_probe_supported_yuv444_for_codec = {}; + int active_hevc_mode; ///< HEVC mode selected by the most recent encoder probe. + int active_av1_mode; ///< AV1 mode selected by the most recent encoder probe. + bool last_encoder_probe_supported_ref_frames_invalidation = false; ///< Whether the last probe found reference-frame invalidation support. + std::array last_encoder_probe_supported_yuv444_for_codec = {}; ///< YUV444 support discovered for each probed codec. + /** + * @brief Recreate a display capture object after a capture failure. + * + * @param disp Display connection or display handle. + * @param type Protocol, message, or resource type selector. + * @param display_name Display name. + * @param config Configuration values to apply. + */ void reset_display(std::shared_ptr &disp, const platf::mem_type_e &type, const std::string &display_name, const config_t &config) { // We try this twice, in case we still get an error on reinitialization for (int x = 0; x < 2; ++x) { @@ -1254,6 +1482,14 @@ namespace video { } } + /** + * @brief Run the shared display capture thread for asynchronous encoding. + * + * @param capture_ctx_queue Capture context queue. + * @param display_wp Weak pointer holder for the active display. + * @param reinit_event Signal raised while the display is being reinitialized. + * @param encoder Selected encoder. + */ void captureThread( std::shared_ptr> capture_ctx_queue, sync_util::sync_t> &display_wp, @@ -1511,6 +1747,16 @@ namespace video { } } + /** + * @brief Drain encoded packets from an FFmpeg encoder session. + * + * @param frame_nr Monotonic frame index assigned by the video pipeline. + * @param session Active FFmpeg encoder session. + * @param packets Output queue that receives encoded packets. + * @param channel_data Platform or protocol state attached to each packet. + * @param frame_timestamp Capture timestamp associated with the encoded frame. + * @return 0 when packets are queued; nonzero when encoding or packetization fails. + */ int encode_avcodec(int64_t frame_nr, avcodec_encode_session_t &session, safe::mail_raw_t::queue_t &packets, void *channel_data, std::optional frame_timestamp) { auto &frame = session.device->frame; frame->pts = frame_nr; @@ -1585,6 +1831,16 @@ namespace video { return 0; } + /** + * @brief Encode one frame through NVENC and queue the resulting packet. + * + * @param frame_nr Monotonic frame index assigned by the video pipeline. + * @param session Active NVENC encoder session. + * @param packets Output queue that receives the encoded packet. + * @param channel_data Platform or protocol state attached to the packet. + * @param frame_timestamp Capture timestamp associated with the encoded frame. + * @return 0 when packets are queued; nonzero when NVENC encoding fails. + */ int encode_nvenc(int64_t frame_nr, nvenc_encode_session_t &session, safe::mail_raw_t::queue_t &packets, void *channel_data, std::optional frame_timestamp) { auto encoded_frame = session.encode_frame(frame_nr); if (encoded_frame.data.empty()) { @@ -1605,6 +1861,16 @@ namespace video { return 0; } + /** + * @brief Encode one captured frame and queue packets for transmission. + * + * @param frame_nr Frame nr. + * @param session Active streaming or pairing session for the request. + * @param packets Packets queued or emitted by the stream. + * @param channel_data Channel data. + * @param frame_timestamp Frame timestamp. + * @return 0 when the frame is encoded and queued; nonzero on encoder failure. + */ int encode(int64_t frame_nr, encode_session_t &session, safe::mail_raw_t::queue_t &packets, void *channel_data, std::optional frame_timestamp) { if (auto avcodec_session = dynamic_cast(&session)) { return encode_avcodec(frame_nr, *avcodec_session, packets, channel_data, frame_timestamp); @@ -1615,6 +1881,17 @@ namespace video { return -1; } + /** + * @brief Create an AVCodec encode session. + * + * @param disp Display being encoded. + * @param encoder Selected encoder. + * @param config Video configuration. + * @param width Encoded frame width. + * @param height Encoded frame height. + * @param encode_device AVCodec encode device. + * @return AVCodec encode session, or nullptr on failure. + */ std::unique_ptr make_avcodec_encode_session( platf::display_t *disp, const encoder_t &encoder, @@ -2007,6 +2284,13 @@ namespace video { return session; } + /** + * @brief Create NVENC encode session. + * + * @param client_config Client stream configuration negotiated for this session. + * @param encode_device Encode device. + * @return Constructed NVENC encode session object. + */ std::unique_ptr make_nvenc_encode_session(const config_t &client_config, std::unique_ptr encode_device) { if (!encode_device->init_encoder(client_config, encode_device->colorspace)) { return nullptr; @@ -2015,6 +2299,17 @@ namespace video { return std::make_unique(std::move(encode_device)); } + /** + * @brief Create encode session. + * + * @param disp Display connection or display handle. + * @param encoder Encoder configuration or encoder instance. + * @param config Configuration values to apply. + * @param width Frame or display width in pixels. + * @param height Frame or display height in pixels. + * @param encode_device Encode device. + * @return Constructed encode session object. + */ std::unique_ptr make_encode_session(platf::display_t *disp, const encoder_t &encoder, const config_t &config, int width, int height, std::unique_ptr encode_device) { if (dynamic_cast(encode_device.get())) { auto avcodec_encode_device = boost::dynamic_pointer_cast(std::move(encode_device)); @@ -2027,6 +2322,19 @@ namespace video { return nullptr; } + /** + * @brief Run one encode loop for a display capture stream. + * + * @param frame_nr Frame counter updated as frames are encoded. + * @param mail Session mail bus. + * @param images Captured image event source. + * @param config Video configuration. + * @param disp Display being encoded. + * @param encode_device Platform encode device. + * @param reinit_event Signal raised while the encoder/display is reinitializing. + * @param encoder Selected encoder. + * @param channel_data Opaque channel data passed to packets. + */ void encode_run( int &frame_nr, // Store progress of the frame number safe::mail_t mail, @@ -2138,6 +2446,13 @@ namespace video { } } + /** + * @brief Create a port object or message. + * + * @param display Display object or identifier associated with the operation. + * @param config Configuration values to apply. + * @return Constructed port object. + */ input::touch_port_t make_port(platf::display_t *display, const config_t &config) { float wd = display->width; float hd = display->height; @@ -2183,6 +2498,14 @@ namespace video { }; } + /** + * @brief Create encode device. + * + * @param disp Display connection or display handle. + * @param encoder Encoder configuration or encoder instance. + * @param config Configuration values to apply. + * @return Constructed encode device object. + */ std::unique_ptr make_encode_device(platf::display_t &disp, const encoder_t &encoder, const config_t &config) { std::unique_ptr result; @@ -2234,6 +2557,15 @@ namespace video { return result; } + /** + * @brief Create synced session. + * + * @param disp Display connection or display handle. + * @param encoder Encoder configuration or encoder instance. + * @param img Image or frame object to read from or populate. + * @param ctx Native context object used by the operation or callback. + * @return Constructed synced session object. + */ std::optional make_synced_session(platf::display_t *disp, const encoder_t &encoder, platf::img_t &img, sync_session_ctx_t &ctx) { sync_session_t encode_session; @@ -2274,6 +2606,15 @@ namespace video { return encode_session; } + /** + * @brief Run synchronized capture and encoding. + * + * @param synced_session_ctxs Active synchronized session contexts. + * @param encode_session_ctx_queue Pending synchronized session context queue. + * @param display_names Cached display names. + * @param display_p Active display index. + * @return Encoder loop result. + */ encode_e encode_run_sync( std::vector> &synced_session_ctxs, encode_session_ctx_queue_t &encode_session_ctx_queue, @@ -2425,6 +2766,9 @@ namespace video { return encode_e::ok; } + /** + * @brief Run synchronous capture and encode work on the capture thread. + */ void captureThreadSync() { auto ref = capture_thread_sync.ref(); @@ -2454,6 +2798,13 @@ namespace video { while (encode_run_sync(synced_session_ctxs, ctx, display_names, display_p) == encode_e::reinit) {} } + /** + * @brief Capture and encode video using the asynchronous capture thread. + * + * @param mail Session mail bus. + * @param config Video configuration. + * @param channel_data Opaque channel data passed to packets. + */ void capture_async( safe::mail_t mail, config_t &config, @@ -2538,6 +2889,13 @@ namespace video { } } + /** + * @brief Capture and encode video for a streaming session. + * + * @param mail Session mail bus. + * @param config Video configuration. + * @param channel_data Opaque channel data passed to packets. + */ void capture( safe::mail_t mail, config_t config, @@ -2568,10 +2926,21 @@ namespace video { } } + /** + * @brief Enumerates supported validate flag options. + */ enum validate_flag_e { VUI_PARAMS = 0x01, ///< VUI parameters }; + /** + * @brief Validate config before it is used. + * + * @param disp Display connection or display handle. + * @param encoder Encoder configuration or encoder instance. + * @param config Configuration values to apply. + * @return 0 when the selected encoder/device accepts the configuration; nonzero otherwise. + */ int validate_config(std::shared_ptr disp, const encoder_t &encoder, const config_t &config) { auto encode_device = make_encode_device(*disp, encoder, config); if (!encode_device) { @@ -2624,6 +2993,9 @@ namespace video { return flag; } + /** + * @brief Validate encoder before it is used. + */ bool validate_encoder(encoder_t &encoder, bool expect_failure) { const auto output_name {display_device::map_output_name(config::video.output_name)}; std::shared_ptr disp; @@ -3073,8 +3445,14 @@ namespace video { } // Linux only declaration + /** + * @brief Callback signature for VA-API AVCodec hardware input initialization. + */ typedef int (*vaapi_init_avcodec_hardware_input_buffer_fn)(platf::avcodec_encode_device_t *encode_device, AVBufferRef **hw_device_buf); + /** + * @brief Initialize AVCodec hardware input buffers for VA-API. + */ util::Either vaapi_init_avcodec_hardware_input_buffer(platf::avcodec_encode_device_t *encode_device) { avcodec_buffer_t hw_device_buf; @@ -3136,6 +3514,9 @@ namespace video { } #endif + /** + * @brief Initialize AVCodec hardware input buffers for CUDA. + */ util::Either cuda_init_avcodec_hardware_input_buffer(platf::avcodec_encode_device_t *encode_device) { avcodec_buffer_t hw_device_buf; @@ -3149,6 +3530,9 @@ namespace video { return hw_device_buf; } + /** + * @brief Initialize AVCodec hardware input buffers for VideoToolbox. + */ util::Either vt_init_avcodec_hardware_input_buffer(platf::avcodec_encode_device_t *encode_device) { avcodec_buffer_t hw_device_buf; @@ -3165,10 +3549,16 @@ namespace video { #ifdef _WIN32 } +/** + * @brief No-op lock callback used when FFmpeg requires a D3D11VA lock function. + */ void do_nothing(void *) { } namespace video { + /** + * @brief Create an FFmpeg D3D11VA hardware device from Sunshine's DXGI device. + */ util::Either dxgi_init_avcodec_hardware_input_buffer(platf::avcodec_encode_device_t *encode_device) { avcodec_buffer_t ctx_buf {av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_D3D11VA)}; auto ctx = (AVD3D11VADeviceContext *) ((AVHWDeviceContext *) ctx_buf->data)->hwctx; @@ -3196,6 +3586,9 @@ namespace video { } #endif + /** + * @brief Start capture async. + */ int start_capture_async(capture_thread_async_ctx_t &capture_thread_ctx) { capture_thread_ctx.encoder_p = chosen_encoder; capture_thread_ctx.reinit_event.reset(); @@ -3213,20 +3606,32 @@ namespace video { return 0; } + /** + * @brief Stop capture async processing. + */ void end_capture_async(capture_thread_async_ctx_t &capture_thread_ctx) { capture_thread_ctx.capture_ctx_queue->stop(); capture_thread_ctx.capture_thread.join(); } + /** + * @brief Start capture sync. + */ int start_capture_sync(capture_thread_sync_ctx_t &ctx) { std::thread {&captureThreadSync}.detach(); return 0; } + /** + * @brief Stop capture sync processing. + */ void end_capture_sync(capture_thread_sync_ctx_t &ctx) { } + /** + * @brief Map base dev type values. + */ platf::mem_type_e map_base_dev_type(AVHWDeviceType type) { switch (type) { case AV_HWDEVICE_TYPE_D3D11VA: @@ -3250,6 +3655,9 @@ namespace video { return platf::mem_type_e::unknown; } + /** + * @brief Map pix fmt values. + */ platf::pix_fmt_e map_pix_fmt(AVPixelFormat fmt) { switch (fmt) { case AV_PIX_FMT_VUYX: diff --git a/src/video.h b/src/video.h index 5b474d3f1..0a49741c0 100644 --- a/src/video.h +++ b/src/video.h @@ -19,57 +19,112 @@ struct AVPacket; namespace video { - /* Encoding configuration requested by remote client */ + /** + * @brief Encoding configuration requested by a remote client. + */ struct config_t { - int width; // Video width in pixels - int height; // Video height in pixels - int framerate; // Requested framerate, used in individual frame bitrate budget calculation - int framerateX100; // Optional field for streaming at NTSC or similar rates e.g. 59.94 = 5994 - int bitrate; // Video bitrate in kilobits (1000 bits) for requested framerate - int slicesPerFrame; // Number of slices per frame - int numRefFrames; // Max number of reference frames - - /* Requested color range and SDR encoding colorspace, HDR encoding colorspace is always BT.2020+ST2084 - Color range (encoderCscMode & 0x1) : 0 - limited, 1 - full - SDR encoding colorspace (encoderCscMode >> 1) : 0 - BT.601, 1 - BT.709, 2 - BT.2020 */ - int encoderCscMode; - - int videoFormat; // 0 - H.264, 1 - HEVC, 2 - AV1 - - /* Encoding color depth (bit depth): 0 - 8-bit, 1 - 10-bit - HDR encoding activates when color depth is higher than 8-bit and the display which is being captured is operating in HDR mode */ - int dynamicRange; - - int chromaSamplingType; // 0 - 4:2:0, 1 - 4:4:4 - - int enableIntraRefresh; // 0 - disabled, 1 - enabled + int width; ///< Video width in pixels. + int height; ///< Video height in pixels. + int framerate; ///< Requested framerate used in the per-frame bitrate budget. + int framerateX100; ///< Optional NTSC-style framerate value, e.g. 59.94 as 5994. + int bitrate; ///< Video bitrate in kilobits for the requested framerate. + int slicesPerFrame; ///< Number of slices per frame. + int numRefFrames; ///< Maximum number of reference frames. + int encoderCscMode; ///< Requested color range and SDR colorspace; HDR always uses BT.2020 and ST2084. + int videoFormat; ///< Video codec format: 0 = H.264, 1 = HEVC, 2 = AV1. + int dynamicRange; ///< Encoding color depth: 0 = 8-bit, 1 = 10-bit. + int chromaSamplingType; ///< Chroma sampling type: 0 = 4:2:0, 1 = 4:4:4. + int enableIntraRefresh; ///< Intra refresh setting: 0 = disabled, 1 = enabled. }; + /** + * @brief Map an FFmpeg hardware device type to Sunshine's memory type. + * + * @param type FFmpeg hardware device type reported by the encoder backend. + * @return Sunshine memory type used by the capture and encode pipeline. + */ platf::mem_type_e map_base_dev_type(AVHWDeviceType type); + /** + * @brief Map an FFmpeg pixel format to Sunshine's pixel format enum. + * + * @param fmt FFmpeg pixel format to convert. + * @return Sunshine pixel format used by display and encoder backends. + */ platf::pix_fmt_e map_pix_fmt(AVPixelFormat fmt); + /** + * @brief Release an FFmpeg codec context. + * + * @param ctx Codec context allocated by FFmpeg. + */ void free_ctx(AVCodecContext *ctx); + /** + * @brief Release an FFmpeg frame allocated by the capture or conversion backend. + * + * @param frame Video or graphics frame being processed. + */ void free_frame(AVFrame *frame); + /** + * @brief Release a backend buffer allocated for capture or conversion. + * + * @param ref FFmpeg buffer reference to unref and free. + */ void free_buffer(AVBufferRef *ref); + /** + * @brief Owning pointer for an FFmpeg codec context. + */ using avcodec_ctx_t = util::safe_ptr; + /** + * @brief Owning pointer for an FFmpeg frame. + */ using avcodec_frame_t = util::safe_ptr; + /** + * @brief Owning pointer for an FFmpeg buffer reference. + */ using avcodec_buffer_t = util::safe_ptr; + /** + * @brief Owning pointer for an FFmpeg software-scaling context. + */ using sws_t = util::safe_ptr; + /** + * @brief Shared event that transports captured images between capture and encode threads. + */ using img_event_t = std::shared_ptr>>; + /** + * @brief Pixel formats supported by one encoder backend. + */ struct encoder_platform_formats_t { virtual ~encoder_platform_formats_t() = default; - platf::mem_type_e dev_type; - platf::pix_fmt_e pix_fmt_8bit; - platf::pix_fmt_e pix_fmt_10bit; - platf::pix_fmt_e pix_fmt_yuv444_8bit; - platf::pix_fmt_e pix_fmt_yuv444_10bit; + platf::mem_type_e dev_type; ///< Platform memory type required by this encoder. + platf::pix_fmt_e pix_fmt_8bit; ///< 8-bit 4:2:0 input format accepted by this encoder. + platf::pix_fmt_e pix_fmt_10bit; ///< 10-bit 4:2:0 input format accepted by this encoder. + platf::pix_fmt_e pix_fmt_yuv444_8bit; ///< 8-bit 4:4:4 input format accepted by this encoder. + platf::pix_fmt_e pix_fmt_yuv444_10bit; ///< 10-bit 4:4:4 input format accepted by this encoder. }; + /** + * @brief AVCodec-specific pixel formats supported by a platform. + */ struct encoder_platform_formats_avcodec: encoder_platform_formats_t { + /** + * @brief Callback that prepares an FFmpeg hardware input buffer for the encode device. + */ using init_buffer_function_t = std::function(platf::avcodec_encode_device_t *)>; + /** + * @brief Construct AVCodec platform format mappings. + * + * @param avcodec_base_dev_type Base AVCodec hardware device type. + * @param avcodec_derived_dev_type Derived AVCodec hardware device type. + * @param avcodec_dev_pix_fmt AVCodec device pixel format. + * @param avcodec_pix_fmt_8bit AVCodec 8-bit pixel format. + * @param avcodec_pix_fmt_10bit AVCodec 10-bit pixel format. + * @param avcodec_pix_fmt_yuv444_8bit AVCodec 8-bit YUV444 pixel format. + * @param avcodec_pix_fmt_yuv444_10bit AVCodec 10-bit YUV444 pixel format. + * @param init_avcodec_hardware_input_buffer_function Hardware input buffer initialization callback. + */ encoder_platform_formats_avcodec( const AVHWDeviceType &avcodec_base_dev_type, const AVHWDeviceType &avcodec_derived_dev_type, @@ -95,18 +150,30 @@ namespace video { pix_fmt_yuv444_10bit = map_pix_fmt(avcodec_pix_fmt_yuv444_10bit); } - AVHWDeviceType avcodec_base_dev_type; - AVHWDeviceType avcodec_derived_dev_type; - AVPixelFormat avcodec_dev_pix_fmt; - AVPixelFormat avcodec_pix_fmt_8bit; - AVPixelFormat avcodec_pix_fmt_10bit; - AVPixelFormat avcodec_pix_fmt_yuv444_8bit; - AVPixelFormat avcodec_pix_fmt_yuv444_10bit; + AVHWDeviceType avcodec_base_dev_type; ///< FFmpeg device type used to create the primary hardware context. + AVHWDeviceType avcodec_derived_dev_type; ///< FFmpeg device type derived from the primary hardware context. + AVPixelFormat avcodec_dev_pix_fmt; ///< FFmpeg hardware-device pixel format for frames handed to the encoder. + AVPixelFormat avcodec_pix_fmt_8bit; ///< FFmpeg 8-bit 4:2:0 software pixel format for this encoder. + AVPixelFormat avcodec_pix_fmt_10bit; ///< FFmpeg 10-bit 4:2:0 software pixel format for this encoder. + AVPixelFormat avcodec_pix_fmt_yuv444_8bit; ///< FFmpeg 8-bit 4:4:4 software pixel format for this encoder. + AVPixelFormat avcodec_pix_fmt_yuv444_10bit; ///< FFmpeg 10-bit 4:4:4 software pixel format for this encoder. - init_buffer_function_t init_avcodec_hardware_input_buffer; + init_buffer_function_t init_avcodec_hardware_input_buffer; ///< Backend hook that allocates or imports hardware frames for FFmpeg. }; + /** + * @brief NVENC-specific pixel formats supported by a platform. + */ struct encoder_platform_formats_nvenc: encoder_platform_formats_t { + /** + * @brief Construct NVENC platform format mappings. + * + * @param dev_type Platform memory type. + * @param pix_fmt_8bit Platform 8-bit pixel format. + * @param pix_fmt_10bit Platform 10-bit pixel format. + * @param pix_fmt_yuv444_8bit Platform 8-bit YUV444 pixel format. + * @param pix_fmt_yuv444_10bit Platform 10-bit YUV444 pixel format. + */ encoder_platform_formats_nvenc( const platf::mem_type_e &dev_type, const platf::pix_fmt_e &pix_fmt_8bit, @@ -122,9 +189,15 @@ namespace video { } }; + /** + * @brief Encoder name and feature flags advertised by Sunshine. + */ struct encoder_t { - std::string_view name; + std::string_view name; ///< Encoder name used in logs, configuration, and capability probes. + /** + * @brief Capability flags that describe which stream modes an encoder supports. + */ enum flag_e { PASSED, ///< Indicates the encoder is supported. REF_FRAMES_RESTRICT, ///< Set maximum reference frames. @@ -135,10 +208,18 @@ namespace video { MAX_FLAGS ///< Maximum number of flags. }; + /** + * @brief Convert an encoder capability flag to its diagnostic string. + * + * @param flag Feature flag to name. + * @return Stable string used in logs and probe output. + */ static std::string_view from_flag(flag_e flag) { -#define _CONVERT(x) \ - case flag_e::x: \ - return std::string_view(#x) +#ifndef DOXYGEN + #define _CONVERT(x) \ + case flag_e::x: \ + return std::string_view(#x) +#endif switch (flag) { _CONVERT(PASSED); _CONVERT(REF_FRAMES_RESTRICT); @@ -153,45 +234,78 @@ namespace video { return {"unknown"}; } + /** + * @brief Runtime encoder option exposed to configuration parsing. + */ struct option_t { KITTY_DEFAULT_CONSTR_MOVE(option_t) + /** + * @brief Copy an encoder option descriptor. + */ option_t(const option_t &) = default; - std::string name; - std::variant *, std::function, std::string, std::string *, std::function> value; + std::string name; ///< Encoder command-line option name. + std::variant *, std::function, std::string, std::string *, std::function> value; ///< Literal, pointer, or callback that supplies the option value. + /** + * @brief Store a named encoder option and its value source. + * + * @param name Human-readable name to assign. + * @param value Literal value, pointer, or callback used to resolve the option. + */ option_t(std::string &&name, decltype(value) &&value): name {std::move(name)}, value {std::move(value)} { } }; - const std::unique_ptr platform_formats; + const std::unique_ptr platform_formats; ///< Platform-specific memory and pixel formats accepted by the encoder. + /** + * @brief Codec capabilities for AV1, HEVC, or H.264. + */ struct codec_t { - std::vector common_options; - std::vector sdr_options; - std::vector hdr_options; - std::vector sdr444_options; - std::vector hdr444_options; - std::vector fallback_options; + std::vector common_options; ///< Options applied to every encode mode for this codec. + std::vector sdr_options; ///< Options applied to SDR 4:2:0 streams. + std::vector hdr_options; ///< Options applied to HDR 4:2:0 streams. + std::vector sdr444_options; ///< Options applied to SDR 4:4:4 streams. + std::vector hdr444_options; ///< Options applied to HDR 4:4:4 streams. + std::vector fallback_options; ///< Options used when the preferred mode cannot be selected. - std::string name; - std::bitset capabilities; + std::string name; ///< Codec name passed to the encoder backend. + std::bitset capabilities; ///< Capability flags supported by this codec on the encoder. + /** + * @brief Test whether a codec capability is enabled. + * + * @param flag Feature flag to test or modify. + * @return True when the requested video flag is set. + */ bool operator[](flag_e flag) const { return capabilities[(std::size_t) flag]; } + /** + * @brief Return a mutable reference to a codec capability flag. + * + * @param flag Feature flag to test or modify. + * @return Mutable bitset reference for the requested capability. + */ std::bitset::reference operator[](flag_e flag) { return capabilities[(std::size_t) flag]; } }; - codec_t av1; - codec_t hevc; - codec_t h264; + codec_t av1; ///< AV1 codec capability and option set. + codec_t hevc; ///< HEVC codec capability and option set. + codec_t h264; ///< H.264 codec capability and option set. + /** + * @brief Select the codec descriptor requested by a stream configuration. + * + * @param config Configuration values to apply. + * @return Codec descriptor for H.264, HEVC, or AV1. + */ const codec_t &codec_from_config(const config_t &config) const { switch (config.videoFormat) { default: @@ -206,18 +320,39 @@ namespace video { } } - uint32_t flags; + uint32_t flags; ///< Encoder flags advertised to clients through GameStream capability responses. }; + /** + * @brief Encoder session state shared by capture and encoding threads. + */ struct encode_session_t { virtual ~encode_session_t() = default; + /** + * @brief Convert a captured frame into the encoder's required input representation. + * + * @param img Captured image supplied by the platform display backend. + * @return Zero when the frame was converted or imported successfully. + */ virtual int convert(platf::img_t &img) = 0; + /** + * @brief Mark the frame as a request for an IDR frame. + */ virtual void request_idr_frame() = 0; + /** + * @brief Mark the frame as a request for a normal inter frame. + */ virtual void request_normal_frame() = 0; + /** + * @brief Mark the frame range whose references must be invalidated. + * + * @param first_frame First frame. + * @param last_frame Last frame. + */ virtual void invalidate_ref_frames(int64_t first_frame, int64_t last_frame) = 0; }; @@ -242,35 +377,70 @@ namespace video { extern encoder_t videotoolbox; #endif + /** + * @brief Encoded packet wrapper used by the streaming pipeline. + */ struct packet_raw_t { virtual ~packet_raw_t() = default; + /** + * @brief Report whether this packet starts an IDR frame. + * + * @return True when this frame is an IDR frame. + */ virtual bool is_idr() = 0; + /** + * @brief Return the frame index associated with this encoded packet. + * + * @return Monotonic frame index assigned to this frame. + */ virtual int64_t frame_index() = 0; + /** + * @brief Return writable access to the encoded packet bytes. + * + * @return Pointer to the first byte of encoded frame data. + */ virtual uint8_t *data() = 0; + /** + * @brief Return the encoded payload length. + * + * @return Size of the encoded frame payload in bytes. + */ virtual size_t data_size() = 0; + /** + * @brief Packet byte-range replacement descriptor. + */ struct replace_t { - std::string_view old; - std::string_view _new; + std::string_view old; ///< Byte sequence to find in the encoded packet. + std::string_view _new; ///< Byte sequence that replaces `old` before transmission. KITTY_DEFAULT_CONSTR_MOVE(replace_t) + /** + * @brief Store an encoded-byte replacement rule. + * + * @param old Byte sequence to replace in encoded packets. + * @param _new Replacement byte sequence inserted into encoded packets. + */ replace_t(std::string_view old, std::string_view _new) noexcept: old {std::move(old)}, _new {std::move(_new)} { } }; - std::vector *replacements = nullptr; - void *channel_data = nullptr; - bool after_ref_frame_invalidation = false; - std::optional frame_timestamp; + std::vector *replacements = nullptr; ///< Optional encoded-byte substitutions applied before packetization. + void *channel_data = nullptr; ///< Platform or protocol state carried with this packet. + bool after_ref_frame_invalidation = false; ///< Whether the frame follows reference-frame invalidation. + std::optional frame_timestamp; ///< Capture timestamp associated with the frame. }; + /** + * @brief AVCodec packet wrapper with codec-specific metadata. + */ struct packet_raw_avcodec: packet_raw_t { packet_raw_avcodec() { av_packet = av_packet_alloc(); @@ -280,67 +450,137 @@ namespace video { av_packet_free(&this->av_packet); } + /** + * @brief Report whether the FFmpeg packet is marked as a key frame. + * + * @return True when this frame is an IDR frame. + */ bool is_idr() override { return av_packet->flags & AV_PKT_FLAG_KEY; } + /** + * @brief Return the FFmpeg packet PTS used as Sunshine's frame index. + * + * @return Monotonic frame index assigned to this frame. + */ int64_t frame_index() override { return av_packet->pts; } + /** + * @brief Return writable access to the FFmpeg packet payload. + * + * @return Pointer to the first encoded byte in the FFmpeg packet. + */ uint8_t *data() override { return av_packet->data; } + /** + * @brief Return the FFmpeg packet payload length. + * + * @return Size of the encoded frame payload in bytes. + */ size_t data_size() override { return av_packet->size; } - AVPacket *av_packet; + AVPacket *av_packet; ///< FFmpeg packet that owns encoded data and frame metadata. }; + /** + * @brief Generic encoded packet bytes and metadata. + */ struct packet_raw_generic: packet_raw_t { + /** + * @brief Wrap generic encoded frame bytes in Sunshine packet metadata. + * + * @param frame_data Encoded frame bytes produced by a non-FFmpeg encoder. + * @param frame_index Monotonic frame index assigned to the encoded frame. + * @param idr Whether the packet begins an IDR frame. + */ packet_raw_generic(std::vector &&frame_data, int64_t frame_index, bool idr): frame_data {std::move(frame_data)}, index {frame_index}, idr {idr} { } + /** + * @brief Report whether the generic packet starts an IDR frame. + * + * @return True when this frame is an IDR frame. + */ bool is_idr() override { return idr; } + /** + * @brief Return the stored frame index for this generic packet. + * + * @return Monotonic frame index assigned to this frame. + */ int64_t frame_index() override { return index; } + /** + * @brief Return writable access to the generic packet payload. + * + * @return Pointer to the first encoded byte in `frame_data`. + */ uint8_t *data() override { return frame_data.data(); } + /** + * @brief Return the generic packet payload length. + * + * @return Size of the encoded frame payload in bytes. + */ size_t data_size() override { return frame_data.size(); } - std::vector frame_data; - int64_t index; - bool idr; + std::vector frame_data; ///< Encoded frame bytes owned by this packet. + int64_t index; ///< Monotonic frame index assigned to this packet. + bool idr; ///< Whether the packet belongs to an IDR frame. }; + /** + * @brief Owning pointer to an encoded packet abstraction. + */ using packet_t = std::unique_ptr; + /** + * @brief Raw HDR metadata fields parsed from the display backend. + */ struct hdr_info_raw_t { + /** + * @brief Initialize HDR metadata with only the enabled state. + * + * @param enabled Whether the feature should be enabled. + */ explicit hdr_info_raw_t(bool enabled): enabled {enabled}, metadata {} {}; + /** + * @brief Initialize HDR metadata with display metadata from the backend. + * + * @param enabled Whether the feature should be enabled. + * @param metadata Output structure populated with HDR metadata. + */ explicit hdr_info_raw_t(bool enabled, const SS_HDR_METADATA &metadata): enabled {enabled}, metadata {metadata} {}; - bool enabled; - SS_HDR_METADATA metadata; + bool enabled; ///< Whether HDR mode should be enabled. + SS_HDR_METADATA metadata; ///< Display HDR metadata forwarded to the encoder and client. }; + /** + * @brief Owning pointer to optional HDR metadata for a captured display. + */ using hdr_info_t = std::unique_ptr; extern int active_hevc_mode; @@ -354,6 +594,13 @@ namespace video { void *channel_data ); + /** + * @brief Validate encoder before it is used. + * + * @param encoder Encoder configuration or encoder instance. + * @param expect_failure Expect failure. + * @return True when encoder validation matches `expect_failure`. + */ bool validate_encoder(encoder_t &encoder, bool expect_failure); /** @@ -363,6 +610,7 @@ namespace video { * at runtime due to all sorts of things from driver updates to eGPUs. * * @warning This is only safe to call when there is no client actively streaming. + * @return 0 when a usable encoder is selected; nonzero when probing fails. */ int probe_encoders(); @@ -371,6 +619,12 @@ namespace video { // reduce 29.97 to 2997/100 but this would be slightly wrong. We also include // support for 23.976 film in case someone wants to stream a film at the perfect // framerate. + /** + * @brief Convert a framerate stored as hundredths of Hz to an FFmpeg rational. + * + * @param framerateX100 Framerate multiplied by 100, such as 5994 for 59.94 Hz. + * @return Rational frame rate preserving NTSC fractional rates when applicable. + */ inline AVRational framerateX100_to_rational(const int framerateX100) { if (framerateX100 % 2997 == 0) { // Multiples of NTSC 29.97 e.g. 59.94, 119.88 diff --git a/src/video_colorspace.cpp b/src/video_colorspace.cpp index d155d3b9b..fc6eb793f 100644 --- a/src/video_colorspace.cpp +++ b/src/video_colorspace.cpp @@ -15,10 +15,16 @@ extern "C" { namespace video { + /** + * @brief Check whether a Sunshine colorspace represents HDR video. + */ bool colorspace_is_hdr(const sunshine_colorspace_t &colorspace) { return colorspace.colorspace == colorspace_e::bt2020; } + /** + * @brief Derive Sunshine colorspace metadata from client stream configuration. + */ sunshine_colorspace_t colorspace_from_client_config(const config_t &config, bool hdr_display) { sunshine_colorspace_t colorspace; @@ -76,6 +82,9 @@ namespace video { return colorspace; } + /** + * @brief Convert Sunshine colorspace metadata to FFmpeg AVCodec fields. + */ avcodec_colorspace_t avcodec_colorspace_from_sunshine_colorspace(const sunshine_colorspace_t &sunshine_colorspace) { avcodec_colorspace_t avcodec_colorspace; diff --git a/src/video_colorspace.h b/src/video_colorspace.h index db0e11077..690afeab3 100644 --- a/src/video_colorspace.h +++ b/src/video_colorspace.h @@ -10,6 +10,9 @@ extern "C" { namespace video { + /** + * @brief Enumerates supported colorspace options. + */ enum class colorspace_e { rec601, ///< Rec. 601 rec709, ///< Rec. 709 @@ -17,35 +20,63 @@ namespace video { bt2020, ///< Rec. 2020 HDR }; + /** + * @brief Sunshine colorimetry values derived from stream configuration. + */ struct sunshine_colorspace_t { - colorspace_e colorspace; - bool full_range; - unsigned bit_depth; + colorspace_e colorspace; ///< Colorspace. + bool full_range; ///< Whether the video range is full-range instead of limited. + unsigned bit_depth; ///< Bit depth. }; + /** + * @brief Check whether a Sunshine colorspace represents HDR video. + * + * @param colorspace Colorimetry information used for conversion or encoding. + * @return True when the colorspace values describe the same metadata. + */ bool colorspace_is_hdr(const sunshine_colorspace_t &colorspace); // Declared in video.h struct config_t; + /** + * @brief Derive Sunshine colorspace metadata from the negotiated client config. + * + * @param config Configuration values to apply. + * @param hdr_display HDR display. + * @return Colorspace, range, transfer, and HDR flags used for encoding. + */ sunshine_colorspace_t colorspace_from_client_config(const config_t &config, bool hdr_display); + /** + * @brief FFmpeg colorimetry values used by AVCodec encoders. + */ struct avcodec_colorspace_t { - AVColorPrimaries primaries; - AVColorTransferCharacteristic transfer_function; - AVColorSpace matrix; - AVColorRange range; - int software_format; + AVColorPrimaries primaries; ///< FFmpeg color primaries selected for the encoded stream. + AVColorTransferCharacteristic transfer_function; ///< FFmpeg transfer function selected for the encoded stream. + AVColorSpace matrix; ///< FFmpeg YUV matrix coefficients selected for the encoded stream. + AVColorRange range; ///< FFmpeg full-range or limited-range flag selected for the encoded stream. + int software_format; ///< FFmpeg software pixel format used when hardware frames are not supplied. }; + /** + * @brief Convert Sunshine colorspace metadata to FFmpeg AVCodec fields. + * + * @param sunshine_colorspace Sunshine colorspace. + * @return FFmpeg color range, primaries, transfer, and software format values. + */ avcodec_colorspace_t avcodec_colorspace_from_sunshine_colorspace(const sunshine_colorspace_t &sunshine_colorspace); + /** + * @brief Pair of Sunshine and AVCodec colorimetry descriptions. + */ struct alignas(16) color_t { - float color_vec_y[4]; - float color_vec_u[4]; - float color_vec_v[4]; - float range_y[2]; - float range_uv[2]; + float color_vec_y[4]; ///< Color vec y. + float color_vec_u[4]; ///< Color vec u. + float color_vec_v[4]; ///< Color vec v. + float range_y[2]; ///< Range y. + float range_uv[2]; ///< Range uv. }; /** diff --git a/third-party/lizardbyte-common b/third-party/lizardbyte-common index 8d7dcc97d..bd1b0bb88 160000 --- a/third-party/lizardbyte-common +++ b/third-party/lizardbyte-common @@ -1 +1 @@ -Subproject commit 8d7dcc97d0795e4eb2efdb50a86f83060ed47934 +Subproject commit bd1b0bb88d47f953875170151d86ae464a7175e8 diff --git a/uv.lock b/uv.lock index 92a5d5068..cbc48adc4 100644 --- a/uv.lock +++ b/uv.lock @@ -90,6 +90,73 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, ] +[[package]] +name = "babel" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, +] + +[[package]] +name = "clang-format" +version = "21.1.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/29/ed/019e682e9f8a5a5abf4258b293453092ef9524b540ada591ccfdff1246df/clang_format-21.1.8.tar.gz", hash = "sha256:99369fe76526ba6be6d7a8093fee6cd266bbf5ce72597a0d79a4ce9a625b28cf", size = 11509, upload-time = "2025-12-16T20:34:33.065Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/1d/4b1d85acb99a2ee3bad0b20cc3e82c58fbfb39bf4dd5856ac1f2c4f36e65/clang_format-21.1.8-py2.py3-none-macosx_10_9_x86_64.whl", hash = "sha256:f447091c346027a09728a0a96128fe058419fe06cf200ccc3dc98bcd4399e351", size = 1465604, upload-time = "2025-12-16T20:34:03.955Z" }, + { url = "https://files.pythonhosted.org/packages/84/38/a61466227a8a6bf22c583f7bd810e75c9a356cfc63c6a712a201103a4ad3/clang_format-21.1.8-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:9a78fabba6b382866819b42a7b220d235a3baf6128a40fb7bd590c037f69879b", size = 1459014, upload-time = "2025-12-16T20:34:05.929Z" }, + { url = "https://files.pythonhosted.org/packages/ab/5d/6720c895c5cfc01831fac55ecf849f10d03d6222700909af14f6047c933b/clang_format-21.1.8-py2.py3-none-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c2a044953efab5f7d0261f3a76aabc01358592faac1b73af38d8c83db6e91a69", size = 1725482, upload-time = "2025-12-16T20:34:07.525Z" }, + { url = "https://files.pythonhosted.org/packages/54/0d/074b940884ced1b5c93e2e7fc8a941673519a938e7f31aeade321e3ab094/clang_format-21.1.8-py2.py3-none-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:143d2dffa71058d05ac5ad71321682e23d638bce22c8f2e3d01ed457d841a5f3", size = 1856680, upload-time = "2025-12-16T20:34:09.987Z" }, + { url = "https://files.pythonhosted.org/packages/34/d5/46e22ae8c4385a7836db7403ced63ea8056c971cbb806bf0c6f0d2133acc/clang_format-21.1.8-py2.py3-none-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6a4b61a743dad5afc5e60be0c5c8f162f6cf27fff9eed56ec0bf65c2bcbd8a8d", size = 2031282, upload-time = "2025-12-16T20:34:11.565Z" }, + { url = "https://files.pythonhosted.org/packages/e0/2a/cad75567c312d945cc26aaee7e1b7135703fed9c7e7ad15c427bf6ad7a84/clang_format-21.1.8-py2.py3-none-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:94de66c5eeca1270825348687d384750d91f1d8d12216b2df0aea892859be33a", size = 2047763, upload-time = "2025-12-16T20:34:13.175Z" }, + { url = "https://files.pythonhosted.org/packages/a8/c2/403e7371b1ab0f6175d708ecc93cf18085ea9f903e8fe7d17c43df698f06/clang_format-21.1.8-py2.py3-none-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d12b864b596b80810cdd7f97556c485dc09cfe2952503958535f01359e025fbb", size = 1804755, upload-time = "2025-12-16T20:34:14.813Z" }, + { url = "https://files.pythonhosted.org/packages/76/33/9e7db5fc4abefb25022d266957f666bfc263ae3e51abdd2c997d2880ceee/clang_format-21.1.8-py2.py3-none-manylinux_2_31_armv7l.whl", hash = "sha256:b81f1e909f5e7ef862a7818dc22b302a3ff407f3534e3395aa7ba26746cc890e", size = 1643208, upload-time = "2025-12-16T20:34:16.898Z" }, + { url = "https://files.pythonhosted.org/packages/aa/9f/43f4e384cb8943418f94943f682835f375f15b9cc64c526d73a7360f2b2f/clang_format-21.1.8-py2.py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e10a5dca18a04997ad55c082bc0759edc7e337b24a2373ded109634fde12662a", size = 2701696, upload-time = "2025-12-16T20:34:19.043Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6a/378187d72a465cebd3dbf7cd455e33b4931daa33e815b7865e6e65591a72/clang_format-21.1.8-py2.py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:10f7d7004d70b5e03fd3f764fc19fae89cd38d68b3482162cb09cac47c12c7b0", size = 2481266, upload-time = "2025-12-16T20:34:20.336Z" }, + { url = "https://files.pythonhosted.org/packages/99/eb/2332edb1846d6832932191a590203a53ec69f585e718b51796335a1dcb6e/clang_format-21.1.8-py2.py3-none-musllinux_1_2_i686.whl", hash = "sha256:303d5fd53090422119136b1a458e98db429dedd5db0add50e70f885f8c95d82a", size = 2954000, upload-time = "2025-12-16T20:34:21.739Z" }, + { url = "https://files.pythonhosted.org/packages/7b/b5/eed5c95521b77d3cde024c3a23c7a80c46e78aa5b5a81c5cf935e7267588/clang_format-21.1.8-py2.py3-none-musllinux_1_2_ppc64le.whl", hash = "sha256:6b9e0b45cfaf4a18336a6db4666dd6a6aae840e38b038e6d568a65f43ade007c", size = 3076720, upload-time = "2025-12-16T20:34:23.139Z" }, + { url = "https://files.pythonhosted.org/packages/14/55/7527fbe31423a1bb53653c2a2b20aa34a73ec33b99199bceaec0f5907295/clang_format-21.1.8-py2.py3-none-musllinux_1_2_s390x.whl", hash = "sha256:4dd0fee9eaee9915ba7fa08e60ee9ddfba1f754d10d71164482d9eb387c431f0", size = 3160892, upload-time = "2025-12-16T20:34:24.618Z" }, + { url = "https://files.pythonhosted.org/packages/53/61/1623382bc78ef139c196473a5c76b840a071c9002cb5cd2694ac50327db9/clang_format-21.1.8-py2.py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9731aecf954651004f184347d0c589d4e91a39ccda21262ee17c60f80a4408b2", size = 2812487, upload-time = "2025-12-16T20:34:26.505Z" }, + { url = "https://files.pythonhosted.org/packages/53/e1/74f2000d44e4904472e5685afb98d19566c515026214733f02bbdc73d6d8/clang_format-21.1.8-py2.py3-none-win32.whl", hash = "sha256:2f5883ca83f718d8c2272e98b3cbda422fec520824b5a1e335c631dc6d812da7", size = 1271310, upload-time = "2025-12-16T20:34:28.179Z" }, + { url = "https://files.pythonhosted.org/packages/69/7a/c49c8af9135c6a6dfb9cd103328ba7b6551643dce71b57e58ea940884015/clang_format-21.1.8-py2.py3-none-win_amd64.whl", hash = "sha256:a7606da55e31ebf5b63dd75800392e6cca7c595a74100c2cebcda2d742130732", size = 1426467, upload-time = "2025-12-16T20:34:29.805Z" }, + { url = "https://files.pythonhosted.org/packages/ad/d3/8790b539afb6cb0d4474705d3750e78a1f0847ab6d3ef1b00dd1ae52f364/clang_format-21.1.8-py2.py3-none-win_arm64.whl", hash = "sha256:1aa10b3f647268361d08bf4f17ce70964b8d9c04d5539e7d8acbebd14dc4a49c", size = 1327254, upload-time = "2025-12-16T20:34:31.579Z" }, +] + +[[package]] +name = "cmakelang" +version = "0.6.13" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/c0/75d4806cf21dcb4198e9fba02f4d2fa61c8db919b7db788862d9cd5f4433/cmakelang-0.6.13.tar.gz", hash = "sha256:03982e87b00654d024d73ef972d9d9bb0e5726cdb6b8a424a15661fb6278e67f", size = 123111, upload-time = "2020-08-19T17:15:25.44Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/a8/c4676cac062d133c6b909d7def80a3194162597968953a3291b309878721/cmakelang-0.6.13-py3-none-any.whl", hash = "sha256:764b9467195c7c36453d60a829f30229720d26c7dffd41cb516b99bd9c7daf4e", size = 159803, upload-time = "2020-08-19T17:15:23.981Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "colorlog" +version = "6.10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a2/61/f083b5ac52e505dfc1c624eafbf8c7589a0d7f32daa398d2e7590efa5fda/colorlog-6.10.1.tar.gz", hash = "sha256:eb4ae5cb65fe7fec7773c2306061a8e63e02efc2c72eba9d27b0fa23c94f1321", size = 17162, upload-time = "2025-10-16T16:14:11.978Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/c1/e419ef3723a074172b68aaa89c9f3de486ed4c2399e2dbd8113a4fdcaf9e/colorlog-6.10.1-py3-none-any.whl", hash = "sha256:2d7e8348291948af66122cff006c9f8da6255d224e7cf8e37d8de2df3bad8c9c", size = 11743, upload-time = "2025-10-16T16:14:10.512Z" }, +] + [[package]] name = "flatpak-node-generator" version = "0.1.0" @@ -169,6 +236,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, ] +[[package]] +name = "gcovr" +version = "8.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorlog" }, + { name = "jinja2" }, + { name = "lxml" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/07/37/b4a87dff166dc0a5002e9d03fcb6ca8eeff048247b011b67f047e31122c9/gcovr-8.6.tar.gz", hash = "sha256:b2e7042abca9321cadbab8a06eb34d19f801b831557b28cdc30a029313de8b9e", size = 199997, upload-time = "2026-01-13T20:04:30.019Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/be/f722c843e7875c7cf92cf0e0c1604cddda55a70278c768c6327a78fdba79/gcovr-8.6-py3-none-any.whl", hash = "sha256:dbf9d87c38042752ad6f530aa8210427e22b526611bb7b7bfed0e81977d1f1ef", size = 254618, upload-time = "2026-01-13T20:04:28.15Z" }, +] + [[package]] name = "glad2" source = { directory = "third-party/glad" } @@ -204,6 +286,109 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] +[[package]] +name = "lizardbyte-common" +version = "0.0.0" +source = { directory = "third-party/lizardbyte-common" } + +[package.optional-dependencies] +c = [ + { name = "babel" }, + { name = "clang-format" }, + { name = "cmakelang" }, + { name = "gcovr" }, +] + +[package.metadata] +requires-dist = [ + { name = "babel", marker = "extra == 'locale'", specifier = "==2.18.0" }, + { name = "clang-format", marker = "extra == 'lint-c'", specifier = "==21.1.8" }, + { name = "cmakelang", marker = "extra == 'lint-c'", specifier = "==0.6.13" }, + { name = "flake8", marker = "extra == 'lint-python'", specifier = "==7.3.0" }, + { name = "gcovr", marker = "extra == 'test-c'", specifier = "==8.6" }, + { name = "lizardbyte-common", extras = ["c"], marker = "extra == 'dev'" }, + { name = "lizardbyte-common", extras = ["lint-c"], marker = "extra == 'c'" }, + { name = "lizardbyte-common", extras = ["lint-python"], marker = "extra == 'dev'" }, + { name = "lizardbyte-common", extras = ["locale"], marker = "extra == 'c'" }, + { name = "lizardbyte-common", extras = ["test-c"], marker = "extra == 'c'" }, + { name = "lizardbyte-common", extras = ["test-python"], marker = "extra == 'dev'" }, + { name = "pytest", marker = "extra == 'test-python'", specifier = "==9.0.3" }, + { name = "pytest-cov", marker = "extra == 'test-python'", specifier = "==7.1.0" }, +] +provides-extras = ["dev", "c", "lint-c", "lint-python", "locale", "test-c", "test-python"] + +[package.metadata.requires-dev] +c = [ + { name = "babel", specifier = "==2.18.0" }, + { name = "clang-format", specifier = "==21.1.8" }, + { name = "cmakelang", specifier = "==0.6.13" }, + { name = "gcovr", specifier = "==8.6" }, +] +dev = [ + { name = "babel", specifier = "==2.18.0" }, + { name = "clang-format", specifier = "==21.1.8" }, + { name = "cmakelang", specifier = "==0.6.13" }, + { name = "flake8", specifier = "==7.3.0" }, + { name = "gcovr", specifier = "==8.6" }, + { name = "pytest", specifier = "==9.0.3" }, + { name = "pytest-cov", specifier = "==7.1.0" }, +] +lint-c = [ + { name = "clang-format", specifier = "==21.1.8" }, + { name = "cmakelang", specifier = "==0.6.13" }, +] +lint-python = [{ name = "flake8", specifier = "==7.3.0" }] +locale = [{ name = "babel", specifier = "==2.18.0" }] +test-c = [{ name = "gcovr", specifier = "==8.6" }] +test-python = [ + { name = "pytest", specifier = "==9.0.3" }, + { name = "pytest-cov", specifier = "==7.1.0" }, +] + +[[package]] +name = "lxml" +version = "6.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/3b/aab6728cae887456f409b4d75e8a01856e4f04bd510de38052a47768b680/lxml-6.1.1.tar.gz", hash = "sha256:ba96ae44888e0185281e937633a743ea90d5a196c6000f82565ebb0580012d40", size = 4197430, upload-time = "2026-05-18T19:19:06.424Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/e2/2e325795566de01d0d7c3bb57d3c370616b2d07b01214e84eec5d3b10963/lxml-6.1.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:19b7ab10b210b0b3ad7985d9ac4eb66ab09a90b20fe6e2f7ba55d01a234345d0", size = 8577146, upload-time = "2026-05-18T19:18:17.765Z" }, + { url = "https://files.pythonhosted.org/packages/93/cf/5630b5e4be7d2e6bee8efe83865c925221103cf0221303b104ce134b01e2/lxml-6.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c08e5c694306507275f2290073350c4f32e383db15213b2c69e7ff39c1193840", size = 4623866, upload-time = "2026-05-18T19:18:30.669Z" }, + { url = "https://files.pythonhosted.org/packages/d2/51/3904907c063451cf8d4a5c9fe0cad95fa1f4ec57f4e3884fa0731bd7a305/lxml-6.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:74a9717fd0d82effef5c2854f0d917231d5324b5a3eb7275c43ac9fa32f97a14", size = 4950022, upload-time = "2026-05-18T19:19:31.958Z" }, + { url = "https://files.pythonhosted.org/packages/94/cd/9c7611a51c37a2830928405817cc5d56a97f64fab83cc3f628748b135749/lxml-6.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:efe0374196335f93b53269acd811b944f2e6bdc88e8894f214bd636455484909", size = 5086695, upload-time = "2026-05-18T19:19:34.764Z" }, + { url = "https://files.pythonhosted.org/packages/da/d6/24e3b5906abb0b674ff2ae195bc3ce59708df2bcd17cf17703b2d7dd643a/lxml-6.1.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac931cdc9442c1763b8a8f6cd62c0c938737eafc5be75eff88df55fc73bc0d00", size = 5031642, upload-time = "2026-05-18T19:19:37.771Z" }, + { url = "https://files.pythonhosted.org/packages/2d/db/6ec54f99019838bff54785c51da07f189eb4676861c5f2730962b0d8d665/lxml-6.1.1-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:aee395f5d0927f947758b4ec119fd5fc8ec71f07a1c5c52077b30b04c0fa6955", size = 5647338, upload-time = "2026-05-18T19:19:40.553Z" }, + { url = "https://files.pythonhosted.org/packages/42/3d/ef4dcfffd22d27a61805d8ed9f7fb888495bc6aa88648fa07c1eaa5586b6/lxml-6.1.1-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9395002973c827b3ed67db77e6ec09f092919a587022174554096a269378fb13", size = 5239528, upload-time = "2026-05-18T19:19:43.657Z" }, + { url = "https://files.pythonhosted.org/packages/62/bb/37fb3f0dff146bdcfa78eec47879273820b2a0bf350ec236ce14bd0b1c26/lxml-6.1.1-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:73bc2086f141224ebddb7fc5c6a36ca58b31b94b561e1dfe8e073e3270fad1e7", size = 5350730, upload-time = "2026-05-18T19:19:46.307Z" }, + { url = "https://files.pythonhosted.org/packages/90/42/43253f168388df4fae1f38c01df36ddb9bee39e2048167b54cdcbae85ea3/lxml-6.1.1-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:3779def59032b81e44a5f70096ef6bf2082f8d901937dca354474ba09782e245", size = 4697530, upload-time = "2026-05-18T19:19:49.889Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a8/c5a8504f81bbdfc8e7094c2c850cdb4ed6777fc4d5ddd9e5ab819f3b0d54/lxml-6.1.1-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:86c89b9d55ebf820ad7c90bc533410f0d098054f293351f10603c0c46ff598f5", size = 5250670, upload-time = "2026-05-18T19:19:53.199Z" }, + { url = "https://files.pythonhosted.org/packages/77/b7/c7e76ab18744d75e21f320ebf9ff9d1ceae2b54dd431ea5a64caf26c9672/lxml-6.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19607c6bbff2a44cf3fe8250abccd20942d3462473e0a721d01d379ed017e462", size = 5084485, upload-time = "2026-05-18T19:19:08.422Z" }, + { url = "https://files.pythonhosted.org/packages/31/31/b35c53f8ef7b7c31cacd23d3638652fff7bcd1deb6eedb709ab43b685908/lxml-6.1.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:c6ed5141a5c7507cf3ee76bd363b0d6f801e3321adc35b5d825a23115faa5465", size = 4737635, upload-time = "2026-05-18T19:19:12.321Z" }, + { url = "https://files.pythonhosted.org/packages/d9/06/31f23c813a7fe8e0cb1b175e915b08c9bf4e86d225b210feadbdbe519667/lxml-6.1.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:62aeb7e85b5d60320b9d77eef2e773994e2c0ce10121b277e0a19804e1654a5a", size = 5670681, upload-time = "2026-05-18T19:19:15.001Z" }, + { url = "https://files.pythonhosted.org/packages/1a/bc/ce619bccc89b1fd9ad8a8e1330ee3f3beff9f2ff95b712d7bbcdd6e22fc3/lxml-6.1.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:b1b963fd8f5caa68e99dfae060d54de1fe9cba899b8718b44a00cdca53c3e590", size = 5238229, upload-time = "2026-05-18T19:19:18.131Z" }, + { url = "https://files.pythonhosted.org/packages/2f/5d/b329acbbedc0b619ebc2be6cf7ee9ed07e80892c88d4dfd612c33805789a/lxml-6.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:63876be28efefa04a1df615b46770e82042cce445cfdce55160522f57b231ccb", size = 5264191, upload-time = "2026-05-18T19:19:21.118Z" }, + { url = "https://files.pythonhosted.org/packages/d6/85/be36fb1425b30db3c3f9df75fe86343ebffb79e6320bd7f588e25bfeac39/lxml-6.1.1-cp314-cp314-win32.whl", hash = "sha256:7f7a92e8583f06b1fd49d01158143b8461cfcd135dcb10ec807270a3051bd603", size = 3657202, upload-time = "2026-05-18T19:17:39.509Z" }, + { url = "https://files.pythonhosted.org/packages/b8/ce/3cf9a827342269f54d405a6202397de63f07c69cbd6ce7d183a3f0cba1e9/lxml-6.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:b2d444f2e66624d68e9c6b211e28a76e22fff5fcabcfff4deac18b529b7d4137", size = 4064497, upload-time = "2026-05-18T19:18:14.662Z" }, + { url = "https://files.pythonhosted.org/packages/d9/3e/1a957bde8f0760039e627f94699f82caa782c9d838d86c3d28245ee67212/lxml-6.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:3fd9728a2735fda14f4e8235830c86b539e9661e849665bf926d3f867943b4bf", size = 3741991, upload-time = "2026-05-19T19:22:59.111Z" }, + { url = "https://files.pythonhosted.org/packages/78/b2/00ed55b3a2efa4658fb795c38d1090ec9b3e8a6c3683d4441fa517f09c3b/lxml-6.1.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:787b2496d0dbe8cd180984e8d29e3a6f76e7ea34db781cb3bd55e4ba1ef8b4ee", size = 8827545, upload-time = "2026-05-18T19:18:41.193Z" }, + { url = "https://files.pythonhosted.org/packages/c0/73/74573db19baa618d5f266f2407898b087ff6927115b00b71e5fc1b700847/lxml-6.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2c8daa471358dc2d6fcf02165e80ec68f77871a286df95bc5cc3816153b0fd2c", size = 4735736, upload-time = "2026-05-18T19:18:46.761Z" }, + { url = "https://files.pythonhosted.org/packages/16/02/6f7061f4f95f51e545d48e87647c54791d204a4e881be4156e7a26ba5338/lxml-6.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:acd7d70b64c0aae0c7922cca83d288a16f5f6da523637697872253415269baef", size = 4970291, upload-time = "2026-05-18T19:19:56.215Z" }, + { url = "https://files.pythonhosted.org/packages/b0/02/55fc057d8283427dea7d6edb102e7a840239c77a64a983d92f62a304c0e9/lxml-6.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4f0dd2f01f9f8a89f565d000e03abcf0a13d692a346c8d22f628d49af098777a", size = 5102822, upload-time = "2026-05-18T19:19:59.223Z" }, + { url = "https://files.pythonhosted.org/packages/e4/48/8e1cf78d89d66850121d9255a2a24414c98f775da93b90cf976956c24b14/lxml-6.1.1-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b7e8a14c8634bf6f7a568634cb395305a6d964aeb5b7ee32248094bed3a7e2c", size = 5027923, upload-time = "2026-05-18T19:20:01.549Z" }, + { url = "https://files.pythonhosted.org/packages/ed/00/0632a0647612c8af24d26997b3b961397daa9d5b2581444805933629a4cb/lxml-6.1.1-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:86281fbdd6a8162756f8d603f37e3435bfa38043adb79c6dc6a2dfee065e7525", size = 5595843, upload-time = "2026-05-18T19:20:03.93Z" }, + { url = "https://files.pythonhosted.org/packages/bc/86/ab008a7dc360711b66858d61c80a5979a70a09f2aa2b05d9698df80b803d/lxml-6.1.1-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5d7152ec39ca7c402d8fb9bad86140a15b9503bd0c54484e3f1bbe3dd37ceca", size = 5224515, upload-time = "2026-05-18T19:20:06.381Z" }, + { url = "https://files.pythonhosted.org/packages/75/c6/2702ff375e728e34f56d9a45339a9cf7e4427e917f542225242d63a05afa/lxml-6.1.1-cp314-cp314t-manylinux_2_28_i686.whl", hash = "sha256:88d8cb75b9d82858497a5393e3c63cfbf03035225e4b35a49ed7ccb151e4dc0e", size = 5312511, upload-time = "2026-05-18T19:20:09.308Z" }, + { url = "https://files.pythonhosted.org/packages/b7/57/a5807c98f87a86f10ef9ffab35516df7c0f0c4b6d5d33e9f608ab9c04a31/lxml-6.1.1-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f64ec5397ea6a41fc1b4af0380d79b44a755b5531dcaccd9940fb260dca93038", size = 4639206, upload-time = "2026-05-18T19:20:11.704Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e1/8a0a2c35734812395f4da4eaf33748a7e5705bfb2a58b128da764339d5ec/lxml-6.1.1-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d34bbf07dbc7ca5970671b1512e928991fb5e9d95365636c9b2d8b4f53af405e", size = 5232404, upload-time = "2026-05-18T19:20:14.064Z" }, + { url = "https://files.pythonhosted.org/packages/c2/e2/0e6a4dd5ad84d01d99aa7bae7cfefd4a760a0e0f8176818241de17d9b6c0/lxml-6.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:17e0e18d4ad8adbd0399291bc44845b69d9dd68439a3cdebdf35ff902ec05072", size = 5083769, upload-time = "2026-05-18T19:19:23.758Z" }, + { url = "https://files.pythonhosted.org/packages/a0/7e/161f33d463f6ffc1c7679104b65086dea120080d49dde4d238f015aaee2f/lxml-6.1.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:3ab541146f1f6968c462d6c2ac495148e8cdba2f8347700b2141b6ec5a75bf52", size = 4758936, upload-time = "2026-05-18T19:19:27.256Z" }, + { url = "https://files.pythonhosted.org/packages/f1/fb/2369825e3f6ca99305bf9f7b7085fda91c8b0922a89e54d900974aa3ef85/lxml-6.1.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2a0217714657e023ef4293500f65aa20fce6164c8fd6b08fa5bd4a859fb14b9b", size = 5620296, upload-time = "2026-05-18T19:19:29.993Z" }, + { url = "https://files.pythonhosted.org/packages/30/90/d61e383146f74c5ab683947ea14dc7b82778838ab9b95ea73a23b60d0191/lxml-6.1.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:05a82eb6e1530a64f26225b55cbd178113bd0b5af1c2b625f25e5296742c26d2", size = 5228598, upload-time = "2026-05-18T19:19:33.523Z" }, + { url = "https://files.pythonhosted.org/packages/76/2d/2dafd8149e94b05bb070690efd5bb2680720681e03ff03fc57d2b70a1105/lxml-6.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9e36f163528fc50cbef305f02a5fd66d404edf7049cdaff211dbc2cba5a7013e", size = 5247845, upload-time = "2026-05-18T19:19:36.649Z" }, + { url = "https://files.pythonhosted.org/packages/ce/68/b30e913340c380ddac9580c6e6230991fc37240ec4f64704833e4f3e2769/lxml-6.1.1-cp314-cp314t-win32.whl", hash = "sha256:649dda677cf3bd6ac9ae14007ba0c824ded8ce5808b53fc7431d9140399118c1", size = 3897345, upload-time = "2026-05-18T19:17:33.562Z" }, + { url = "https://files.pythonhosted.org/packages/3c/4e/9eb2af5335545f9fbcd7af57bcf87c6025d31eaa31b14ec184a6c8675328/lxml-6.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:793033d6c5cdf33a573f910d9bea14ef8f5771820411d118da8e1182edb53d5e", size = 4393350, upload-time = "2026-05-18T19:18:10.076Z" }, + { url = "https://files.pythonhosted.org/packages/7f/2c/0f1e93c636720e8a3eb59af2bfda99d98b55891e1c53bc30c2e0e865f01b/lxml-6.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:58bb955caba94e467d2a96da17660d2d704e0675894cba21ab8a775b8621fd1c", size = 3817223, upload-time = "2026-05-19T19:22:56.823Z" }, +] + [[package]] name = "markupsafe" version = "3.0.3" @@ -331,6 +516,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3a/ed/1cdcab6ba3d6ab7feca11fc14f0eeea80755bb53ef4e892079f31b10a25f/propcache-0.5.2-py3-none-any.whl", hash = "sha256:be1ddfcbb376e3de5d2e2db1d58d6d67463e6b4f9f040c000de8e300295465fe", size = 14036, upload-time = "2026-05-08T21:02:10.673Z" }, ] +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + [[package]] name = "pyyaml" version = "6.0.3" @@ -378,10 +572,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e1/e3/c164c88b2e5ce7b24d667b9bd83589cf4f3520d97cad01534cd3c4f55fdb/setuptools-81.0.0-py3-none-any.whl", hash = "sha256:fdd925d5c5d9f62e4b74b30d6dd7828ce236fd6ed998a08d81de62ce5a6310d6", size = 1062021, upload-time = "2026-02-06T21:10:37.175Z" }, ] +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + [[package]] name = "sunshine" version = "0.0.0" source = { editable = "." } +dependencies = [ + { name = "lizardbyte-common", extra = ["c"] }, +] [package.dev-dependencies] flatpak = [ @@ -394,6 +600,7 @@ glad = [ ] [package.metadata] +requires-dist = [{ name = "lizardbyte-common", extras = ["c"], directory = "third-party/lizardbyte-common" }] [package.metadata.requires-dev] flatpak = [