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