From 9db491d72f91a132c7d227a49e8287d0982b5e37 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Thu, 25 Jun 2026 14:25:47 -0400 Subject: [PATCH 1/9] chore(homebrew): fix formula audit --- packaging/sunshine.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/sunshine.rb b/packaging/sunshine.rb index 08f68709f80..3101e10238b 100644 --- a/packaging/sunshine.rb +++ b/packaging/sunshine.rb @@ -221,7 +221,7 @@ def configure_static_boost(args) 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 From c90cbe213d6924619a5d96ec0bd2caf0754f98a7 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Thu, 25 Jun 2026 14:26:05 -0400 Subject: [PATCH 2/9] ci(copr): add virustotal secret --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b11e7214212..831b76adef4 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 From 1c6bfcc2944fc37ffeef6fc14a0e6be7fbb88e66 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Thu, 25 Jun 2026 14:26:29 -0400 Subject: [PATCH 3/9] chore: update AGENTS.md --- AGENTS.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index f532e021ca5..af378f917fa 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,6 +8,27 @@ 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. From 92cfae143c1771571db39a8a30a90cee547d5039 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Thu, 25 Jun 2026 14:42:11 -0400 Subject: [PATCH 4/9] chore: store CLion project run file for docs --- .run/docs.run.xml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .run/docs.run.xml diff --git a/.run/docs.run.xml b/.run/docs.run.xml new file mode 100644 index 00000000000..9585c399b55 --- /dev/null +++ b/.run/docs.run.xml @@ -0,0 +1,7 @@ + + + + + + From 6b0a5ed97f559da18766fcd7702064184407acc5 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Thu, 25 Jun 2026 17:57:39 -0400 Subject: [PATCH 5/9] Integrate lizardbyte-common into Sunshine pyproject Move Python tool dependencies (gcovr, clang-format, etc.) from the lizardbyte-common subproject into Sunshine's own pyproject.toml via the lizardbyte-common[c] extra. Update CI workflows and contributing docs to use `uv sync --locked` and `uv run --project ..` instead of referencing the subproject directly. --- .github/workflows/ci-freebsd.yml | 7 +- .github/workflows/ci-linux.yml | 4 +- .github/workflows/ci-macos.yml | 4 +- .github/workflows/ci-windows.yml | 4 +- docs/contributing.md | 20 +-- pyproject.toml | 5 +- third-party/lizardbyte-common | 2 +- uv.lock | 207 +++++++++++++++++++++++++++++++ 8 files changed, 230 insertions(+), 23 deletions(-) diff --git a/.github/workflows/ci-freebsd.yml b/.github/workflows/ci-freebsd.yml index 0ca81cff60b..0fc48089afc 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 ce5af5ae4af..91ec40d652f 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 65ecfee1daa..299f5686919 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 35596367a1a..70010522318 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/docs/contributing.md b/docs/contributing.md index 7595918bf0e..113bb173059 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/pyproject.toml b/pyproject.toml index ce5686c2abf..75d971822cb 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/third-party/lizardbyte-common b/third-party/lizardbyte-common index 8d7dcc97d07..bd1b0bb88d4 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 92a5d50685d..cbc48adc4c0 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 = [ From bea91f34d3040352738dc06d44a61396b058eafa Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Thu, 25 Jun 2026 20:11:40 -0400 Subject: [PATCH 6/9] docs(doxygen): warn and error if undocumented --- docs/Doxyfile | 3 - 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 | 224 ++++- 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 | 21 +- 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 | 684 ++++++++++++--- src/platform/linux/audio.cpp | 179 +++- 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 | 126 ++- 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 | 567 ++++++++++-- src/platform/windows/display_base.cpp | 18 +- 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 | 460 +++++++--- src/stream.h | 68 +- src/sync.h | 66 +- src/system_tray.cpp | 26 + src/task_pool.h | 104 ++- 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 | 47 +- src/video.cpp | 486 ++++++++++- src/video.h | 390 +++++++-- src/video_colorspace.cpp | 9 + src/video_colorspace.h | 57 +- 128 files changed, 11559 insertions(+), 1510 deletions(-) diff --git a/docs/Doxyfile b/docs/Doxyfile index 36196192614..0c84d823d9e 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/src/audio.cpp b/src/audio.cpp index da5a221e516..55d50c47507 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 763cc9886dd..e0e919a54d4 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 67cda0c1f49..03f5f04e236 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 5dfddab5d77..13820edf87f 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 fb67fef71c7..e1dab3516fb 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 f074de579c8..cbe0978ede4 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 5fd6a19d83c..8b9468e3482 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 e8c4ea0036b..0806ad4afa0 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 f6ec0cb78c8..a871cf89dc4 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 944cd77e279..e823cdebac2 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 7d24d524b62..256997d9c4e 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 d49698bdb52..e59334c832b 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 02afcb0615a..e5e18920b6b 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 8d5a03e7132..32aba38e601 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 1617b7c6f51..2c7ee9b121b 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 2f09268510a..de261f5b9ab 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 8b9799943b4..9e7375307dc 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 d01b4f149f6..8108bcba378 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 0e7007c9d03..42e2e680784 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 d360d6af9a1..f5f9eaf7b41 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; - -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 +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. #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 02e01467936..7cfa381948c 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 eb42cf1ba8d..cb73acf0024 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 f1a6e15c4bd..ce5088c3d9f 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 d1c0181d38f..f6d1c2493e9 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 aad65a7a08f..2a78b2f954b 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 1b99a1c14e9..679f35b8abe 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 13b2fa03a14..2514f1f2415 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 bb2cc3f1ef7..9bed2416501 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 a31ca426d01..a4330a7c8ce 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 b2143456a5c..3ae227683d0 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 19facee0218..73e0ad7a7b0 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 ff94de463f9..a625a0e2c87 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 1d4271a0ff1..cf716d7a442 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 e8b48eee455..439b493ffba 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 3f30a9804f4..0ef9df41862 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 f502c1b35cf..2542df1ec67 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 e0e87928c33..2bbcee8ff07 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 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 auto MAX_GAMEPADS = 16; ///< Maximum number of simultaneously tracked gamepads. + + 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; - - std::uintptr_t native_socket; - boost::asio::ip::address &target_address; - uint16_t target_port; - boost::asio::ip::address &source_address; + 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; ///< 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 bd043912df1..ac3aa7b361c 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 937d478098c..ce1387092da 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 0be85cba1db..fbc5401fe3a 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 395d4528cc3..1ef27c7166e 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 33fcaa79f8d..2a2a06429c1 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 3102fdd9120..c0e8a1dcde0 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 acc1b759def..a6eba5e7292 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 dfecc10844b..c5c9d3230bd 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 8d26c9e1ff6..76d385318fe 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 72c456956a2..7d466566e1e 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 6405b62587c..2bf28575ae1 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 f8d822de22c..c837c8154b3 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 67eaf97af6b..0bd5a2e7119 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 ed10f4071a1..b4e245e1eaf 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 667f4ad8d58..0c2cb3f8c43 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 63fe54013e2..00474f7ecef 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 c7dce6044d9..e8c535661c6 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 5e1d6581616..026e9dc2455 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 11571c7c361..470e27e52ca 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 03831984cb4..fd31248708e 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 5f417810f6f..1965e9f20ae 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 c9f98f44de2..fc36045a376 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 a571f0e471c..9d9a92c8552 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 f5bd863d79a..83f5afdc84b 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 525da58398d..099c112cc91 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 63167abd3d2..22843fe2007 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 490df1bccea..c56cf286891 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 197d6b9c44e..fef11ad4869 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 db887f504c7..eb3dce33cd6 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 afb8e098a62..cdef9ae78d6 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 11b16b76d53..137040021b8 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 f99e81c65b4..ba53d1629f1 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 6eef5a81abc..c45e7cc509d 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 9d0a1864722..fd53c0fc293 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 12d3fba6816..fda7adcb792 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 50896c4c568..431a27debcb 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 b2fa5d4b255..7ba1522e25f 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 a2a1e46ffed..5330b716e14 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 5d6c9a7362f..1801696d405 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 @@ void wake_displays_for_detection(const std::string &display_name) { } } // 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 @@ void wake_displays_for_detection(const std::string &display_name) { } } + /** + * @brief Prevent macOS from sleeping the captured display while streaming. + */ void prevent_display_sleep() { if (display_sleep_assertion != kIOPMNullAssertionID) { return; @@ -292,10 +302,21 @@ capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const 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 @@ capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const } } + /** + * @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 @@ int dummy_img(img_t *img) override { * 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 @@ static void setPixelFormat(void *display, OSType pixelFormat) { } /** - * @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 6eed2c1d365..fe17a9b35a1 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 8f15af369af..db877712db8 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 @@ capture_e sample(std::vector &sample_in) override { } }; + /** + * @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 @@ bool is_sink_available(const std::string &sink) override { 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 e791661d1c4..0af5d1ae252 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 12edfd7fc14..56fca134028 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 @@ #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 // 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 @@ bool send(send_info_t &send_info) { // 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 @@ bool send(send_info_t &send_info) { /** * @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 @@ bool send(send_info_t &send_info) { } } + /** + * @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 b4fb28cb736..97701987923 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 3aa7540c3f3..bd92b025bd0 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 a61d28733b5..4101217b3c5 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 @@ interface IPolicyConfig: public IUnknown { 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 @@ interface IPolicyConfig: public IUnknown { 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 cc2788aa8cd..e12dc9033df 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 2fcb7dc3bbb..844ee82b446 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 e42bb8efa4b..24d60471f54 100644 --- a/src/platform/windows/display_base.cpp +++ b/src/platform/windows/display_base.cpp @@ -19,17 +19,23 @@ // 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 -typedef enum _D3DKMT_GPU_PREFERENCE_QUERY_STATE: DWORD { +/** + * @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. D3DKMT_GPU_PREFERENCE_STATE_MINIMUM_POWER, ///< The minimum-powered GPU is preferred. 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 e8ec540ebca..9a4553bff93 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 d659c4bbed7..5e5f4ff977e 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 15cab763708..993960de57d 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 f7fb328504e..4a614704680 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 2d362ef2cf0..ac0a4fa2060 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 32bfef15943..ee2ec7af5de 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 198c1b2bd43..ac866f281a1 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 f2957156bbd..7e1f4b214f9 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 d03c6bbb08c..12d78a71aa5 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 649ef60cc21..53af20320c4 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 61a7a6eb05d..f422f159bc6 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 632ffa45b87..b34b0b4b7b1 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 655f114f419..1f682ce2931 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 5a092815307..7c02e33f6b2 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 aa359273303..580d37e9f83 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 39689953f86..fdf4e85d484 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 9635e520c59..f4a954d0379 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 95c7bdaeaa5..3a2c76d682c 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 0f2f5f51fb4..75a9de09ef8 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 20536955072..55df231a1f0 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 53069620011..70043c3e735 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 2a4ba0d268e..14aa7928f6f 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 0953cd59f60..9ef67a7e6c5 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 d2bf39a072c..7857835f013 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 13cc4170bff..90fce2ae843 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 2e3873d4208..be290340558 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 c0da8671cd5..903486982fa 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 blocksize; - size_t prefixsize; - util::buffer_t shards; - util::buffer_t headers; - util::buffer_t shards_p; - - std::vector payload_buffers; - + 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; ///< 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; ///< 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 5ee377764e7..c0b0f962378 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 a9472a72d2a..23ed8666b77 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 36959df9e80..9fd7931fa61 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 081be24d069..887db57f631 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 46558ad88ab..bb8c234b0da 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 4f2448f1b59..692e3d243b4 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 c65e0a664eb..96e51e39720 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 cc7eb32a412..f34b90fa63a 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 db5d7f9d24d..3bb07957aba 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 df3d78ae46b..00e397e3420 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 0900d999de6..ddf3c4fdad6 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; - - config_t config; - int frame_nr; - void *channel_data; + 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; ///< 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 5b474d3f101..a0d81c96da3 100644 --- a/src/video.h +++ b/src/video.h @@ -19,57 +19,120 @@ 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 + int width; ///< Video width in pixels. + int height; ///< Video height in pixels. + int framerate; ///< Requested framerate used in the per-frame bitrate budget. + /** + * @brief Framerate X100. + */ + 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. - /* 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 encoderCscMode; ///< Requested color range and SDR colorspace; HDR always uses BT.2020 and ST2084. - int videoFormat; // 0 - H.264, 1 - HEVC, 2 - AV1 + int videoFormat; ///< Video codec format: 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 dynamicRange; ///< Encoding color depth: 0 = 8-bit, 1 = 10-bit. - int chromaSamplingType; // 0 - 4:2:0, 1 - 4:4:4 + int chromaSamplingType; ///< Chroma sampling type: 0 = 4:2:0, 1 = 4:4:4. - int enableIntraRefresh; // 0 - disabled, 1 - enabled + 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 +158,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 +197,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 +216,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 +242,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::string name; - std::bitset capabilities; - + 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; ///< 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 +328,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 +385,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 +458,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 +602,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 +618,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 +627,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 d155d3b9b20..fc6eb793fc8 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 db0e11077f5..690afeab317 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. }; /** From 5a7b35fea431b49f653affe032227b6ad77344bb Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Thu, 25 Jun 2026 20:59:38 -0400 Subject: [PATCH 7/9] Update AGENTS.md --- AGENTS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index af378f917fa..b89e31407c8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -32,3 +32,5 @@ Always follow the style guidelines defined in .clang-format for c/c++ code. 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. From 80841bd1dc9ec6d284b1b45a77d6db8e2cfd9db7 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Thu, 25 Jun 2026 21:05:46 -0400 Subject: [PATCH 8/9] Normalize Doxygen comment formatting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes indentation of several Doxygen blocks in Linux and Windows platform code to match the project’s comment style, and removes a redundant multi-line Doxygen block in `video::config_t` where an inline field doc already provides the same context. --- src/platform/linux/x11grab.cpp | 4 ++-- src/platform/windows/display_base.cpp | 2 +- src/platform/windows/input.cpp | 6 +++--- src/video.h | 8 -------- 4 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/platform/linux/x11grab.cpp b/src/platform/linux/x11grab.cpp index c45e7cc509d..18eb4cf75a9 100644 --- a/src/platform/linux/x11grab.cpp +++ b/src/platform/linux/x11grab.cpp @@ -315,7 +315,7 @@ namespace platf { using screen_res_t = util::dyn_safe_ptr<_XRRScreenResources, &x11::rr::FreeScreenResources>; /** - * @brief RAII wrapper that removes a SysV shared-memory segment. + * @brief RAII wrapper that removes a SysV shared-memory segment. */ class shm_id_t { public: @@ -353,7 +353,7 @@ namespace platf { }; /** - * @brief RAII wrapper that detaches mapped SysV shared memory. + * @brief RAII wrapper that detaches mapped SysV shared memory. */ class shm_data_t { public: diff --git a/src/platform/windows/display_base.cpp b/src/platform/windows/display_base.cpp index 24d60471f54..c576ae0c93a 100644 --- a/src/platform/windows/display_base.cpp +++ b/src/platform/windows/display_base.cpp @@ -28,7 +28,7 @@ typedef long NTSTATUS; /** * @brief Enumerates supported d3 DKMT GPU PREFERENCE QUERY STATE options. */ -typedef enum _D3DKMT_GPU_PREFERENCE_QUERY_STATE : DWORD { +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. D3DKMT_GPU_PREFERENCE_STATE_MINIMUM_POWER, ///< The minimum-powered GPU is preferred. diff --git a/src/platform/windows/input.cpp b/src/platform/windows/input.cpp index 4a614704680..7fcbc3a2ef1 100644 --- a/src/platform/windows/input.cpp +++ b/src/platform/windows/input.cpp @@ -7,9 +7,9 @@ #endif #ifdef DOXYGEN /** - * @def CALLBACK - * @brief Windows callback calling convention marker. - */ + * @def CALLBACK + * @brief Windows callback calling convention marker. + */ #define CALLBACK #endif diff --git a/src/video.h b/src/video.h index a0d81c96da3..0a49741c01e 100644 --- a/src/video.h +++ b/src/video.h @@ -26,22 +26,14 @@ namespace video { int width; ///< Video width in pixels. int height; ///< Video height in pixels. int framerate; ///< Requested framerate used in the per-frame bitrate budget. - /** - * @brief Framerate X100. - */ 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. }; From ea5ac2e2bb0c4bbbce70be4fdf43d3c045ec57ae Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Thu, 25 Jun 2026 21:45:53 -0400 Subject: [PATCH 9/9] Add SonarCloud badge to README Adds a SonarCloud quality gate badge to the README status badges section, making project code quality status visible alongside existing CI and coverage badges. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b79fe591f38..b9563661c2e 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ GitHub Workflow Status (localize) Read the Docs Codecov + SonarCloud ## ℹ️ About